Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b9d426f

Browse files
committed
[WIP] lintcode plugin.
Signed-off-by: Eric Wang <skygragon@gmail.com>
1 parent 1c00492 commit b9d426f

File tree

1 file changed

+344
-0
lines changed

1 file changed

+344
-0
lines changed

lintcode.js

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
var _ = require('underscore');
2+
var cheerio = require('cheerio');
3+
var request = require('request');
4+
var util = require('util');
5+
6+
var h = require('../helper');
7+
var log = require('../log');
8+
var Plugin = require('../plugin');
9+
var queue = require('../queue');
10+
var session = require('../session');
11+
12+
// Still working in progress!
13+
//
14+
// TODO: star/submissions/submission
15+
// FIXME: why [ERROR] Error: read ECONNRESET [0]??
16+
//
17+
var plugin = new Plugin(15, 'lintcode', '2017.08.04',
18+
'Plugin to talk with lintcode APIs.');
19+
20+
var config = {
21+
URL_BASE: 'http://www.lintcode.com/en',
22+
URL_PROBLEMS: 'http://www.lintcode.com/en/problem?page=$page',
23+
URL_PROBLEM: 'http://www.lintcode.com/en/problem/$slug/',
24+
URL_PROBLEM_CODE: 'http://www.lintcode.com/en/problem/api/code/?problem_id=$id&language=$lang',
25+
URL_TEST: 'http://www.lintcode.com/submission/api/submit/',
26+
URL_TEST_VERIFY: 'http://www.lintcode.com/submission/api/refresh/?id=$id&waiting_time=0&is_test_submission=true',
27+
URL_SUBMIT_VERIFY: 'http://www.lintcode.com/submission/api/refresh/?id=$id&waiting_time=0',
28+
URL_LOGIN: 'http://www.lintcode.com/en/accounts/signin/'
29+
};
30+
31+
var LANGS = [
32+
{value: 'cpp', text: 'C++'},
33+
{value: 'java', text: 'Java'},
34+
{value: 'python', text: 'Python'}
35+
];
36+
37+
function signOpts(opts, user) {
38+
opts.headers.Cookie = 'sessionid=' + user.sessionId +
39+
';csrftoken=' + user.sessionCSRF + ';';
40+
}
41+
42+
function makeOpts(url) {
43+
var opts = {};
44+
opts.url = url;
45+
opts.headers = {};
46+
47+
if (session.isLogin())
48+
signOpts(opts, session.getUser());
49+
return opts;
50+
}
51+
52+
function checkError(e, resp, expectedStatus) {
53+
if (!e && resp && resp.statusCode !== expectedStatus) {
54+
var code = resp.statusCode;
55+
log.debug('http error: ' + code);
56+
57+
if (code === 403 || code === 401) {
58+
e = session.errors.EXPIRED;
59+
} else {
60+
e = {msg: 'http error', statusCode: code};
61+
}
62+
}
63+
return e;
64+
}
65+
66+
function _split(s, delim) {
67+
return (s || '').split(delim).map(function(x) {
68+
return x.trim();
69+
}).filter(function(x) {
70+
return x.length > 0;
71+
});
72+
}
73+
74+
function _strip(s) {
75+
s = s.replace(/^<pre><code>/, '').replace(/<\/code><\/pre>$/, '');
76+
return util.inspect(s.trim());
77+
}
78+
79+
plugin.getProblems = function(cb) {
80+
log.debug('running lintcode.getProblems');
81+
82+
var problems = [];
83+
var doTask = function(page, taskDone) {
84+
plugin.getPageProblems(page, function(e, _problems) {
85+
if (!e) problems = problems.concat(_problems);
86+
return taskDone(e);
87+
});
88+
};
89+
90+
// FIXME: remove this hardcoded range!
91+
var pages = [0, 1, 2, 3, 4];
92+
queue.run(pages, doTask, function(e) {
93+
problems = _.sortBy(problems, function(x) {
94+
return -x.id;
95+
});
96+
return cb(e, problems);
97+
});
98+
};
99+
100+
plugin.getPageProblems = function(page, cb) {
101+
log.debug('running lintcode.getPageProblems: ' + page);
102+
var opts = makeOpts(config.URL_PROBLEMS.replace('$page', page));
103+
104+
request(opts, function(e, resp, body) {
105+
e = checkError(e, resp, 200);
106+
if (e) return cb(e);
107+
108+
var $ = cheerio.load(body);
109+
var problems = $('div[id=problem_list_pagination] a').map(function(i, a) {
110+
var problem = {
111+
locked: false,
112+
category: 'lintcode',
113+
state: 'None',
114+
starred: false,
115+
companies: [],
116+
tags: []
117+
};
118+
problem.slug = $(a).attr('href').split('/').pop();
119+
problem.link = config.URL_PROBLEM.replace('$slug', problem.slug);
120+
121+
$(a).children('span').each(function(i, span) {
122+
var text = $(span).text().trim();
123+
var type = _split($(span).attr('class'), ' ');
124+
type = type.concat(_split($(span).find('i').attr('class'), ' '));
125+
126+
if (type.indexOf('title') >= 0) {
127+
problem.id = Number(text.split('.')[0]);
128+
problem.name = text.split('.')[1].trim();
129+
} else if (type.indexOf('difficulty') >= 0) problem.level = text;
130+
else if (type.indexOf('rate') >= 0) problem.percent = parseInt(text, 10);
131+
else if (type.indexOf('fa-star') >= 0) problem.starred = true;
132+
else if (type.indexOf('fa-check') >= 0) problem.state = 'ac';
133+
else if (type.indexOf('fa-minus') >= 0) problem.state = 'notac';
134+
else if (type.indexOf('fa-briefcase') >= 0) problem.companies = _split($(span).attr('title'), ',');
135+
});
136+
137+
return problem;
138+
}).get();
139+
140+
return cb(null, problems);
141+
});
142+
};
143+
144+
plugin.getProblem = function(problem, cb) {
145+
log.debug('running lintcode.getProblem');
146+
var opts = makeOpts(problem.link);
147+
148+
request(opts, function(e, resp, body) {
149+
e = checkError(e, resp, 200);
150+
if (e) return cb(e);
151+
152+
var $ = cheerio.load(body);
153+
problem.testcase = $('textarea[id=input-testcase]').text();
154+
problem.testable = problem.testcase.length > 0;
155+
156+
var lines = [];
157+
$('div[id=description] > div').each(function(i, div) {
158+
if (i === 0) {
159+
div = $(div).find('div')[0];
160+
lines.push($(div).text().trim());
161+
return;
162+
}
163+
164+
var text = $(div).text().trim();
165+
var type = $(div).find('b').text().trim();
166+
167+
if (type === 'Tags') {
168+
problem.tags = _split(text, '\n');
169+
problem.tags.shift();
170+
} else if (type === 'Related Problems') return;
171+
else lines.push(text);
172+
});
173+
problem.desc = lines.join('\n').replace(/\n{2,}/g, '\n');
174+
problem.totalAC = '';
175+
problem.totalSubmit = '';
176+
problem.templates = [];
177+
178+
var doTask = function(lang, taskDone) {
179+
plugin.getProblemCode(problem, lang, function(e, code) {
180+
if (e) return taskDone(e);
181+
182+
lang = _.clone(lang);
183+
lang.defaultCode = code;
184+
problem.templates.push(lang);
185+
return taskDone();
186+
});
187+
};
188+
189+
queue.run(LANGS, doTask, function(e) {
190+
return cb(e, problem);
191+
});
192+
});
193+
};
194+
195+
plugin.getProblemCode = function(problem, lang, cb) {
196+
log.debug('running lintcode.getProblemCode:' + lang.value);
197+
var url = config.URL_PROBLEM_CODE
198+
.replace('$id', problem.id)
199+
.replace('$lang', lang.text.replace(/\+/g, '%2b'));
200+
var opts = makeOpts(url);
201+
202+
request(opts, function(e, resp, body) {
203+
e = checkError(e, resp, 200);
204+
if (e) return cb(e);
205+
206+
var json = JSON.parse(body);
207+
return cb(null, json.code);
208+
});
209+
};
210+
211+
function runCode(problem, isTest, cb) {
212+
var lang = _.find(LANGS, function(x) {
213+
return x.value === h.extToLang(problem.file);
214+
});
215+
216+
var opts = makeOpts(config.URL_TEST);
217+
opts.form = {
218+
problem_id: problem.id,
219+
code: h.getFileData(problem.file),
220+
language: lang.text,
221+
csrfmiddlewaretoken: session.getUser().sessionCSRF
222+
};
223+
if (isTest) {
224+
opts.form.input = problem.testcase;
225+
opts.form.is_test_submission = true;
226+
}
227+
228+
request.post(opts, function(e, resp, body) {
229+
e = checkError(e, resp, 200);
230+
if (e) return cb(e);
231+
232+
var json = JSON.parse(body);
233+
if (!json.id || !json.success) return cb(json.message);
234+
235+
verifyResult(json.id, isTest, cb);
236+
});
237+
}
238+
239+
function verifyResult(id, isTest, cb) {
240+
log.debug('running verifyResult:' + id);
241+
var url = isTest ? config.URL_TEST_VERIFY : config.URL_SUBMIT_VERIFY;
242+
var opts = makeOpts(url.replace('$id', id));
243+
244+
request(opts, function(e, resp, body) {
245+
e = checkError(e, resp, 200);
246+
if (e) return cb(e);
247+
248+
var result = JSON.parse(body);
249+
if (result.status === 'Compiling' || result.status === 'Running')
250+
return setTimeout(verifyResult, 1000, id, isTest, cb);
251+
252+
return cb(null, formatResult(result));
253+
});
254+
}
255+
256+
function formatResult(result) {
257+
var x = {
258+
ok: result.status === 'Accepted',
259+
type: 'Actual',
260+
state: result.status,
261+
runtime: result.time_cost + ' ms',
262+
answer: _strip(result.output),
263+
stdout: _strip(result.stdout),
264+
expected_answer: _strip(result.expected),
265+
testcase: _strip(result.input),
266+
passed: result.data_accepted_count || 0,
267+
total: result.data_total_count || 0
268+
};
269+
270+
var error = [];
271+
if (result.compile_info.length > 0)
272+
error = error.concat(_split(result.compile_info, '<br>'));
273+
if (result.error_message.length > 0)
274+
error = error.concat(_split(result.error_message, '<br>'));
275+
x.error = error;
276+
277+
// make sure everything is ok
278+
if (error.length > 0) x.ok = false;
279+
if (x.passed !== x.total) x.ok = false;
280+
281+
return x;
282+
}
283+
284+
plugin.testProblem = function(problem, cb) {
285+
log.debug('running lintcode.testProblem');
286+
runCode(problem, true, function(e, result) {
287+
if (e) return cb(e);
288+
289+
var expected = {
290+
ok: true,
291+
type: 'Expected',
292+
answer: result.expected_answer,
293+
stdout: "''"
294+
};
295+
return cb(null, [result, expected]);
296+
});
297+
};
298+
299+
plugin.submitProblem = function(problem, cb) {
300+
log.debug('running lintcode.submitProblem');
301+
runCode(problem, false, function(e, result) {
302+
if (e) return cb(e);
303+
return cb(null, [result]);
304+
});
305+
};
306+
307+
plugin.getSubmission = function(submission, cb) {
308+
// FIXME
309+
return cb('Not implemented');
310+
};
311+
312+
plugin.login = function(user, cb) {
313+
log.debug('running lintcode.login');
314+
request(config.URL_LOGIN, function(e, resp, body) {
315+
e = checkError(e, resp, 200);
316+
if (e) return cb(e);
317+
318+
user.loginCSRF = h.getSetCookieValue(resp, 'csrftoken');
319+
320+
var opts = {
321+
url: config.URL_LOGIN,
322+
headers: {
323+
Cookie: 'csrftoken=' + user.loginCSRF + ';'
324+
},
325+
form: {
326+
csrfmiddlewaretoken: user.loginCSRF,
327+
username_or_email: user.login,
328+
password: user.pass
329+
}
330+
};
331+
request.post(opts, function(e, resp, body) {
332+
if (e) return cb(e);
333+
if (resp.statusCode !== 302) return cb('invalid password?');
334+
335+
user.sessionCSRF = h.getSetCookieValue(resp, 'csrftoken');
336+
user.sessionId = h.getSetCookieValue(resp, 'sessionid');
337+
user.name = user.login; // FIXME
338+
339+
return cb(null, user);
340+
});
341+
});
342+
};
343+
344+
module.exports = plugin;

0 commit comments

Comments
 (0)