diff --git a/CHANGELOG.md b/CHANGELOG.md index 1653fd1..8ffd8ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## version 1.1.2 +- 去除多余客户端代码 + ## version 1.1.1 - 重新写原本的客户端 - 减少许多多余的包引用 diff --git a/package.json b/package.json index e903ed0..3316188 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-leetcode-problem-rating", "displayName": "LeetCode problem rating", "description": "为LeetCode题目难度进行打分。避免只有简单、中等、困难三种难度", - "version": "1.1.1", + "version": "1.1.2", "author": "ccagml", "publisher": "ccagml", "license": "MIT", diff --git a/src/utils/cpUtils.ts b/src/utils/cpUtils.ts index c71ae61..5811ce9 100644 --- a/src/utils/cpUtils.ts +++ b/src/utils/cpUtils.ts @@ -26,7 +26,14 @@ export async function executeCommand(command: string, args: string[], options: c childProc.on("error", reject); childProc.on("close", (code: number) => { - if (code !== 0 || result.indexOf("ERROR") > -1) { + + var try_result_json; + try { + try_result_json = JSON.parse(result); + } catch (e) { + try_result_json; + } + if (code !== 0 || (try_result_json ? try_result_json.code < 0 : (result.indexOf("ERROR") > -1))) { const error: IExecError = new Error(`Command "${command} ${args.toString()}" failed with exit code "${code}".`); if (result) { error.result = result; // leetcode-cli may print useful content by exit with error code diff --git a/src/vsc-leetcode-cli/lib/cache.js b/src/vsc-leetcode-cli/lib/cache.js deleted file mode 100644 index 121d91a..0000000 --- a/src/vsc-leetcode-cli/lib/cache.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; -var path = require('path'); - -var file = require('./file'); - -const cache = {}; - -cache.init = function () { - file.mkdir(file.cacheDir()); -}; - -cache.deleteAll = function () { - cache.list().forEach(value => { - cache.del(value.name); - }) -}; - -cache.get = function (k) { - const fullpath = file.cacheFile(k); - if (!file.exist(fullpath)) return null; - - return JSON.parse(file.data(fullpath)); -}; - -cache.set = function (k, v) { - const fullpath = file.cacheFile(k); - file.write(fullpath, JSON.stringify(v)); - return true; -}; - -cache.del = function (k) { - const fullpath = file.cacheFile(k); - if (!file.exist(fullpath)) return false; - - file.rm(fullpath); - return true; -}; - -cache.list = function () { - return file.list(file.cacheDir()) - .filter(x => path.extname(x) === '.json') - .map(function (filename) { - const k = path.basename(filename, '.json'); - const stat = file.stat(file.cacheFile(k)); - return { - name: k, - size: stat.size, - mtime: stat.mtime - }; - }); -}; - -module.exports = cache; diff --git a/src/vsc-leetcode-cli/lib/cli.js b/src/vsc-leetcode-cli/lib/cli.js deleted file mode 100644 index 139ffa8..0000000 --- a/src/vsc-leetcode-cli/lib/cli.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var cache = require('./cache'); -var config = require('./config'); -var h = require('./helper'); -var file = require('./file'); -var icon = require('./icon'); -var log = require('./log'); -var Plugin = require('./plugin'); - -// We are expecting a tier configuration like: -// global config < local config < cli params -// Color is a tricky one so we manually handle it here. - - -function initIcon() { - icon.init(); - icon.setTheme(config.icon.theme); -} - -function initLogLevel() { - log.init(); - - let level = 'INFO'; - if (process.argv.indexOf('-v') >= 0) level = 'DEBUG'; - if (process.argv.indexOf('-vv') >= 0) level = 'TRACE'; - - // print HTTP details in TRACE - if (level === 'TRACE') { - const request = require('request'); - request.debug = true; - - console.error = _.wrap(console.error, function (func) { - let args = Array.from(arguments); - args.shift(); - - // FIXME: hack HTTP request log, hope no one else use it... - if (args.length > 0 && args[0].indexOf('REQUEST ') === 0) { - args = args.map((x) => h.printSafeHTTP(x)); - log.trace.apply(log, args); - } else { - log.info.apply(log, args); - } - }); - } - - log.setLevel(level); -} - -function initDir() { - file.init(); - file.mkdir(file.homeDir()) -} - -function initPlugins(cb) { - if (Plugin.init()) { - Plugin.save(); - return cb(); - } else { - Plugin.installMissings(function (e) { - if (e) return cb(e); - Plugin.init(); - return cb(); - }); - } -} - -var cli = {}; - -function runCommand() { - var yargs = require('yargs'); - h.width = yargs.terminalWidth(); - yargs.commandDir('commands') - .completion() - .help('h') - .alias('h', 'help') - .version(false) - .wrap(Math.min(h.width, 120)) - .argv; -} -function runCommand_new() { - var com_str = process.argv[2] - var auto_js = require("./commands/" + com_str) - auto_js.handler(auto_js.process_argv(process.argv)) -} - -cli.run = function () { - process.stdout.on('error', function (e) { - if (e.code === 'EPIPE') process.exit(); - }); - var test_argv = process.argv - - config.init(); - - // initColor(); - initIcon(); - initLogLevel(); - initDir() - initPlugins(function (e) { - if (e) return log.fatal(e); - cache.init(); - runCommand_new(); - // runCommand() - }); -}; - -module.exports = cli; diff --git a/src/vsc-leetcode-cli/lib/commands/cache.js b/src/vsc-leetcode-cli/lib/commands/cache.js deleted file mode 100644 index 33fa484..0000000 --- a/src/vsc-leetcode-cli/lib/commands/cache.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var h = require('../helper'); -var log = require('../log'); -var cache = require('../cache'); -var session = require('../session'); -var sprintf = require('../sprintf'); - -const cmd = { - command: 'cache [keyword]', - desc: 'Manage local cache', - builder: function (yargs) { - return yargs - .option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Delete cache by keyword', - default: false - }) - .positional('keyword', { - type: 'string', - describe: 'Cache name or question id', - default: '' - }) - .example('leetcode cache', 'Show all cache') - .example('leetcode cache 1', 'Show cache of question 1') - .example('', '') - .example('leetcode cache -d', 'Delete all cache') - .example('leetcode cache 1 -d', 'Delete cache of question 1'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Delete cache by keyword', - default: false - }) - .positional('keyword', { - type: 'string', - describe: 'Cache name or question id', - default: '' - }) - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -cmd.handler = function (argv) { - session.argv = argv; - - const name = argv.keyword; - const isInteger = Number.isInteger(Number(name)); - - const caches = cache.list() - .filter(function (f) { - return (name.length === 0) || - (isInteger ? f.name.startsWith(name + '.') : f.name === name); - }); - - if (argv.delete) { - for (let f of caches) cache.del(f.name); - } else { - log.info(sprintf(' %s %63s %s', 'Cache', 'Size', 'Created')); - log.info('-'.repeat(86)); - - _.sortBy(caches, function (f) { - let x = parseInt(f.name.split('.')[0], 10); - if (Number.isNaN(x)) x = 0; - return x; - }) - .forEach(function (f) { - log.printf(' %-60s %8s %s ago', - f.name, - h.prettySize(f.size), - h.prettyTime((Date.now() - f.mtime) / 1000)); - }); - } -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/config.js b/src/vsc-leetcode-cli/lib/commands/config.js deleted file mode 100644 index 03de9ee..0000000 --- a/src/vsc-leetcode-cli/lib/commands/config.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; -var _ = require('underscore'); -var nconf = require('nconf'); - -var file = require('../file'); -var config = require('../config'); -var log = require('../log'); -var session = require('../session'); - -const cmd = { - command: 'config [key] [value]', - aliases: ['conf', 'cfg', 'setting'], - desc: 'Manage user configs', - builder: function (yargs) { - return yargs - .option('a', { - alias: 'all', - type: 'boolean', - describe: 'Show all config', - default: false - }) - .option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Delete config by key', - default: false - }) - .positional('key', { - type: 'string', - describe: 'Config key, delimited by colon', - default: '' - }) - .positional('value', { - type: 'string', - describe: 'Config value', - default: '' - }) - .example('leetcode config', 'Show user configs') - .example('leetcode config -a', 'Show all configs = user + default') - .example('leetcode config plugins:github', 'Show config by key') - .example('', '') - .example('leetcode config plugins:github:repo "your repo URL"', 'Set config by key') - .example('leetcode config plugins:github -d', 'Delete config by key'); - } -}; - - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('a', { - alias: 'all', - type: 'boolean', - describe: 'Show all config', - default: false - }) - .option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Delete config by key', - default: false - }) - .positional('key', { - type: 'string', - describe: 'Config key, delimited by colon', - default: '' - }) - .positional('value', { - type: 'string', - describe: 'Config value', - default: '' - }) - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -function prettyConfig(cfg) { - return JSON.stringify(cfg, null, 2); -} - -function loadConfig(showall) { - const cfg = showall ? config.getAll(true) : nconf.get(); - return _.omit(cfg, 'type'); -} - -function saveConfig() { - file.write(file.configFile(), prettyConfig(loadConfig(false))); -} - -cmd.handler = function (argv) { - session.argv = argv; - nconf.file('local', file.configFile()); - - // show all - if (argv.key.length === 0) - return log.info(prettyConfig(loadConfig(argv.all))); - - // sugar: notice user that use ':' instead of '.' - if (argv.key.includes('.') && !argv.key.includes(':')) - return log.printf('Key should use colon(:) as the delimiter, do you mean %s?', - argv.key.replace(/\./g, ':')); - - const v = nconf.get(argv.key); - - // delete - if (argv.delete) { - if (v === undefined) return log.fatal('Key not found: ' + argv.key); - nconf.clear(argv.key); - return saveConfig(); - } - - // show - if (argv.value.length === 0) { - if (v === undefined) return log.fatal('Key not found: ' + argv.key); - return log.info(prettyConfig(v)); - } - - // set - try { - nconf.set(argv.key, JSON.parse(argv.value)); - } catch (e) { - nconf.set(argv.key, JSON.parse('"' + argv.value + '"')); - } - return saveConfig(); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/list.js b/src/vsc-leetcode-cli/lib/commands/list.js deleted file mode 100644 index b0a6e73..0000000 --- a/src/vsc-leetcode-cli/lib/commands/list.js +++ /dev/null @@ -1,149 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var h = require('../helper'); - -var icon = require('../icon'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'list [keyword]', - aliases: ['ls'], - desc: 'List questions', - builder: function (yargs) { - return yargs - .option('q', core.filters.query) - .option('s', { - alias: 'stat', - type: 'boolean', - default: false, - describe: 'Show statistics of listed questions' - }) - .option('t', core.filters.tag) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra details: category, companies, tags.' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Filter questions by keyword' - }) - .example('leetcode list', 'List all questions') - .example('leetcode list -x', 'Show extra info of questions, e.g. tags') - .example('', '') - .example('leetcode list array', 'List questions that has "array" in name') - .example('leetcode list -q eD', 'List questions that with easy level and not done') - .example('leetcode list -t google', 'List questions from Google company (require plugin)') - .example('leetcode list -t stack', 'List questions realted to stack (require plugin)'); - } -}; - - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('q', core.filters.query) - .option('s', { - alias: 'stat', - type: 'boolean', - default: false, - describe: 'Show statistics of listed questions' - }) - .option('t', core.filters.tag) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra details: category, companies, tags.' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Filter questions by keyword' - }) - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - -cmd.handler = function (argv) { - session.argv = argv; - core.filterProblems(argv, function (e, problems) { - if (e) return log.fail(e); - - - log.info(JSON.stringify(problems)); - // const word = argv.keyword.toLowerCase(); - // if (word) { - // if (word.endsWith(word.substr(-1).repeat(6))) { - // log.warn('Hmmm...you might need a new keyboard?'); - // } - // problems = problems.filter(x => x.name.toLowerCase().includes(word)); - // } - - // const stat = {}; - // for (let x of ['locked', 'starred', 'ac', 'notac', 'None', 'Easy', 'Medium', 'Hard']) stat[x] = 0; - - // problems = _.sortBy(problems, x => -x.fid); - // for (let problem of problems) { - // stat[problem.level] = (stat[problem.level] || 0) + 1; - // stat[problem.state] = (stat[problem.state] || 0) + 1; - // if (problem.locked) ++stat.locked; - // if (problem.starred) ++stat.starred; - - // log.printf('%s %s %s [%=4s] %-60s %-6s (%s %%)', - // (problem.starred ? icon.like : icon.empty), - // (problem.locked ? icon.lock : icon.nolock), - // h.prettyState(problem.state), - // problem.fid, - // problem.name, - // h.prettyLevel(problem.level), - // (problem.percent || 0).toFixed(2)); - - // if (argv.extra) { - // let badges = [problem.category]; - // badges = badges.concat(problem.companies || []); - // badges = badges.concat(problem.tags || []); - - // let buf = []; - // let len = 0; - // for (let x of badges) { - // if (len + x.length + 3 >= 60) { - // log.printf('%12s%s', ' ', buf.join(' | ')); - // buf = []; - // len = 0; - // } - // buf.push(x); - // len += x.length + 3; - // } - // if (buf.length > 0) - // log.printf('%12s%s', ' ', buf.join(' | ')); - // } - // } - - // if (argv.stat) { - // log.info(); - // log.printf(' Listed: %-9s Locked: %-9s Starred: %-9s', problems.length, stat.locked, stat.starred); - // log.printf(' Accept: %-9s Not-AC: %-9s Remain: %-9s', stat.ac, stat.notac, stat.None); - // log.printf(' Easy: %-9s Medium: %-9s Hard: %-9s', stat.Easy, stat.Medium, stat.Hard); - // } - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/plugin.js b/src/vsc-leetcode-cli/lib/commands/plugin.js deleted file mode 100644 index 75764f5..0000000 --- a/src/vsc-leetcode-cli/lib/commands/plugin.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict'; -var h = require('../helper'); -var config = require('../config'); -var log = require('../log'); -var Plugin = require('../plugin'); -var session = require('../session'); - -const cmd = { - command: 'plugin [name]', - aliases: ['extension', 'ext'], - desc: 'Manage plugins', - builder: function (yargs) { - return yargs - .option('c', { - alias: 'config', - type: 'boolean', - describe: 'Show plugin config', - default: false - }) - .option('d', { - alias: 'disable', - type: 'boolean', - describe: 'Disable plugin', - default: false - }) - .option('D', { - alias: 'delete', - type: 'boolean', - describe: 'Delete plugin', - default: false - }) - .option('e', { - alias: 'enable', - type: 'boolean', - describe: 'Enable plugin', - default: false - }) - .option('i', { - alias: 'install', - type: 'boolean', - describe: 'Install plugin', - default: false - }) - .positional('name', { - type: 'string', - describe: 'Filter plugin by name', - default: '' - }) - .example('leetcode plugin', 'Show all plugins') - .example('leetcode plugin company', 'Show company plugin') - .example('leetcode plugin company -', 'Show config of company plugin') - .example('', '') - .example('leetcode plugin -i', 'Install all missing plugins from GitHub') - .example('leetcode plugin -i company', 'Install company plugin from GitHub') - .example('leetcode plugin -d company', 'Disable company plugin') - .example('leetcode plugin -e company', 'Enable company plugin') - .example('leetcode plugin -D company', 'Delete company plugin'); - } -}; - - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('d', { - alias: 'disable', - type: 'boolean', - describe: 'Disable plugin', - default: false - }).option('e', { - alias: 'enable', - type: 'boolean', - describe: 'Enable plugin', - default: false - }).option('i', { - alias: 'install', - type: 'boolean', - describe: 'Install plugin', - default: false - }).positional('name', { - type: 'string', - describe: 'Filter plugin by name', - default: '' - }) - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - -cmd.handler = function (argv) { - session.argv = argv; - - let plugins = Plugin.plugins; - const name = argv.name; - - if (argv.install) { - const cb = function (e, p) { - if (e) return log.fatal(e); - p.help(); - p.save(); - Plugin.init(); - }; - - if (name) { - Plugin.install(name, cb); - } else { - Plugin.installMissings(cb); - } - return; - } - - if (name) plugins = plugins.filter(x => x.name === name); - if (plugins.length === 0) return log.fatal('Plugin not found!'); - - const p = plugins[0]; - if (p.missing && (argv.enable || argv.disable)) - return log.fatal('Plugin missing, install it first'); - - if (argv.enable) { - p.enabled = true; - p.save(); - } else if (argv.disable) { - p.enabled = false; - p.save(); - } else if (argv.delete) { - p.delete(); - p.save(); - Plugin.init(); - } else if (argv.config) { - log.info(JSON.stringify(config.plugins[name] || {}, null, 2)); - } else { - } -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/query.js b/src/vsc-leetcode-cli/lib/commands/query.js deleted file mode 100644 index 1dee46b..0000000 --- a/src/vsc-leetcode-cli/lib/commands/query.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var h = require('../helper'); - -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'query [keyword]', - aliases: ['ls'], - desc: 'query something', - builder: function (yargs) { - return yargs - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .option('a', { - alias: 'getTodayQuestion', - type: 'boolean', - default: false, - describe: 'getTodayQuestion', - }) - .option('b', { - alias: 'username', - type: 'string', - default: "", - describe: 'user name', - }).option('z', { - alias: 'test', - type: 'string', - default: "", - describe: 'test', - }) - .example('leetcode query today', 'query today question') - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .option('a', { - alias: 'getTodayQuestion', - type: 'boolean', - default: false, - describe: 'getTodayQuestion', - }) - .option('b', { - alias: 'username', - type: 'string', - default: "", - describe: 'user name', - }).option('z', { - alias: 'test', - type: 'string', - default: "", - describe: 'test', - }) - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - -cmd.handler = function (argv) { - session.argv = argv; - if (argv.a) { - core.getTodayQuestion(function (e, result) { - if (e) return; - log.info(JSON.stringify(result)); - }); - } else if (argv.b) { - core.getUserContest(argv.b, function (e, result) { - if (e) return; - log.info(JSON.stringify(result)); - }); - } else if (argv.z) { - core.getQueryZ(argv.z, function (e, result) { - if (e) return; - log.info(JSON.stringify(result)); - }); - } -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/session.js b/src/vsc-leetcode-cli/lib/commands/session.js deleted file mode 100644 index bc358cf..0000000 --- a/src/vsc-leetcode-cli/lib/commands/session.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; -var prompt = require('prompt'); - -var h = require('../helper'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); -var sprintf = require('../sprintf'); - -const cmd = { - command: 'session [keyword]', - aliases: ['branch'], - desc: 'Manage sessions', - builder: function (yargs) { - return yargs - .option('c', { - alias: 'create', - type: 'boolean', - describe: 'Create session', - default: false - }) - .option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Delete session', - default: false - }) - .option('e', { - alias: 'enable', - type: 'boolean', - describe: 'Enable/activate session', - default: false - }) - .positional('keyword', { - type: 'string', - describe: 'Session name or id', - default: '' - }) - .example('leetcode session', 'Show all cache') - .example('leetcode session xxx', 'Show session by keyword') - .example('', '') - .example('leetcode session -c xxx', 'Create session with name') - .example('leetcode session -e xxx', 'Enable session by keyword') - .example('leetcode session -d xxx', 'Delete session by keyword'); - } -}; - - -function printSessions(e, sessions) { - if (e) return log.fail(e); - - log.info(sprintf(' %6s %5s %18s %28s %16s', - 'Active', 'Id', 'Name', 'AC Questions', 'AC Submits')); - log.info('-'.repeat(80)); - - for (let s of sessions) { - let questionRate = 0; - let submissionRate = 0; - if (s.submitted_questions > 0) - questionRate = s.ac_questions * 100 / s.submitted_questions; - if (s.total_submitted > 0) - submissionRate = s.total_acs * 100 / s.total_submitted; - - log.printf(' %s %8s %-26s %6s (%6s %%) %6s (%6s %%)', - s.is_active ? h.prettyState('ac') : ' ', - s.id, - s.name || 'Anonymous Session', - s.ac_questions, - questionRate.toFixed(2), - s.total_acs, - submissionRate.toFixed(2)); - } -} - -cmd.handler = function (argv) { - session.argv = argv; - - if (argv.create) - return core.createSession(argv.keyword, printSessions); - - core.getSessions(function (e, sessions) { - if (e) return log.fail(e); - - if (argv.keyword) { - const id = Number(argv.keyword); - sessions = sessions.filter(x => x.name === argv.keyword || x.id === id); - if (sessions.length > 1) return log.fail('Ambiguous sessions?'); - - const session = sessions[0]; - if (!session) return log.fail('Session not found!'); - - if (argv.enable && !session.is_active) { - core.activateSession(session, function (e, sessions) { - if (e) return log.fail(e); - require('../session').deleteCodingSession(); - printSessions(e, sessions); - }); - return; - } - - if (argv.delete) { - return core.deleteSession(session, printSessions); - } - } - printSessions(null, sessions); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/show.js b/src/vsc-leetcode-cli/lib/commands/show.js deleted file mode 100644 index 9b5ee6b..0000000 --- a/src/vsc-leetcode-cli/lib/commands/show.js +++ /dev/null @@ -1,268 +0,0 @@ -'use strict'; -var util = require('util'); - -var _ = require('underscore'); -var childProcess = require('child_process'); - -var h = require('../helper'); -var file = require('../file'); -var icon = require('../icon'); -var log = require('../log'); -var config = require('../config'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'show [keyword]', - aliases: ['view', 'pick'], - desc: 'Show question', - builder: function (yargs) { - return yargs - .option('c', { - alias: 'codeonly', - type: 'boolean', - default: false, - describe: 'Only show code template' - }) - .option('e', { - alias: 'editor', - type: 'string', - describe: 'Open source code in editor' - }) - .option('g', { - alias: 'gen', - type: 'boolean', - default: false, - describe: 'Generate source code' - }) - .option('l', { - alias: 'lang', - type: 'string', - default: config.code.lang, - describe: 'Programming language of the source code', - choices: config.sys.langs - }) - .option('o', { - alias: 'outdir', - type: 'string', - describe: 'Where to save source code', - default: '.' - }) - .option('q', core.filters.query) - .option('t', core.filters.tag) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra question details in source code' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Show question by name or id' - }) - .example('leetcode show 1', 'Show question 1') - .example('leetcode show 1 -gx -l java', 'Show question 1 and generate Java code') - .example('leetcode show 1 -gxe', 'Open generated code in editor') - .example('', '') - .example('leetcode show', 'Show random question') - .example('leetcode show -q h', 'Show random hard question') - .example('leetcode show -t google', 'Show random question from Google (require plugin)'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('c', { - alias: 'codeonly', - type: 'boolean', - default: false, - describe: 'Only show code template' - }) - .option('e', { - alias: 'editor', - type: 'string', - describe: 'Open source code in editor' - }) - .option('g', { - alias: 'gen', - type: 'boolean', - default: false, - describe: 'Generate source code' - }) - .option('l', { - alias: 'lang', - type: 'string', - default: config.code.lang, - describe: 'Programming language of the source code', - choices: config.sys.langs - }) - .option('o', { - alias: 'outdir', - type: 'string', - describe: 'Where to save source code', - default: '.' - }) - .option('q', core.filters.query) - .option('t', core.filters.tag) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra question details in source code' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Show question by name or id' - }) - - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -function genFileName(problem, opts) { - const path = require('path'); - const params = [ - file.fmt(config.file.show, problem), - '', - h.langToExt(opts.lang) - ]; - - // try new name to avoid overwrite by mistake - for (let i = 0; ; ++i) { - const name = path.join(opts.outdir, params.join('.').replace(/\.+/g, '.')); - if (!file.exist(name)) - return name; - params[1] = i; - } -} - -function showProblem(problem, argv) { - const taglist = [problem.category] - .concat(problem.companies || []) - .concat(problem.tags || []) - .map(x => h.badge(x, 'blue')) - .join(' '); - const langlist = problem.templates - .map(x => h.badge(x.value, 'yellow')) - .sort() - .join(' '); - - let code; - const needcode = argv.gen || argv.codeonly; - if (needcode) { - const template = problem.templates.find(x => x.value === argv.lang); - if (!template) { - log.fail('Not supported language "' + argv.lang + '"'); - log.warn('Supported languages: ' + langlist); - return; - } - - const opts = { - lang: argv.lang, - code: template.defaultCode, - tpl: argv.extra ? 'detailed' : 'codeonly' - }; - code = core.exportProblem(problem, opts); - } - - let filename; - if (argv.gen) { - file.mkdir(argv.outdir); - filename = genFileName(problem, argv); - file.write(filename, code); - - if (argv.editor !== undefined) { - childProcess.spawn(argv.editor || config.code.editor, [filename], { - // in case your editor of choice is vim or emacs - stdio: 'inherit' - }); - } - } else { - if (argv.codeonly) { - log.info(code); - return; - } - } - - log.printf('[%s] %s %s', problem.fid, problem.name, - (problem.starred ? icon.like : icon.empty)); - log.info(); - log.info(problem.link); - if (argv.extra) { - log.info(); - log.info('Tags: ' + taglist); - log.info(); - log.info('Langs: ' + langlist); - } - - log.info(); - log.printf('* %s', problem.category); - log.printf('* %s (%s%%)', h.prettyLevel(problem.level), problem.percent.toFixed(2)); - - if (problem.likes) - log.printf('* Likes: %s', problem.likes); - if (problem.dislikes) - log.printf('* Dislikes: %s', problem.dislikes); - else - log.printf('* Dislikes: -'); - if (problem.totalAC) - log.printf('* Total Accepted: %s', problem.totalAC); - if (problem.totalSubmit) - log.printf('* Total Submissions: %s', problem.totalSubmit); - if (problem.testable && problem.testcase) - log.printf('* Testcase Example: %s', util.inspect(problem.testcase)); - if (filename) - log.printf('* Source Code: %s', filename); - - log.info(); - log.info(problem.desc); -} - -cmd.handler = function (argv) { - session.argv = argv; - if (argv.keyword.length > 0) { - // show specific one - core.getProblem(argv.keyword, !argv.dontTranslate, function (e, problem) { - if (e) return log.fail(e); - showProblem(problem, argv); - }); - } else { - // show random one - // core.filterProblems(argv, function (e, problems) { - // if (e) return log.fail(e); - - // // random select one that not AC-ed yet - // const user = session.getUser(); - // problems = problems.filter(function (x) { - // if (x.state === 'ac') return false; - // if (!user.paid && x.locked) return false; - // return true; - // }); - // if (problems.length === 0) return log.fail('Problem not found!'); - - // const problem = _.sample(problems); - // core.getProblem(problem, !argv.dontTranslate, function (e, problem) { - // if (e) return log.fail(e); - // showProblem(problem, argv); - // }); - // }); - } -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/star.js b/src/vsc-leetcode-cli/lib/commands/star.js deleted file mode 100644 index eddc582..0000000 --- a/src/vsc-leetcode-cli/lib/commands/star.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -var icon = require('../icon'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'star ', - aliases: ['like', 'favorite'], - desc: 'Star favorite question', - builder: function (yargs) { - return yargs - .option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Unstar question', - default: false - }) - .positional('keyword', { - type: 'string', - describe: 'Question name or id', - default: '' - }) - .example('leetcode star 1', 'Mark favorite to question 1') - .example('leetcode star 1 -d', 'Unmark favorite to question 1'); - } -}; - - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('d', { - alias: 'delete', - type: 'boolean', - describe: 'Unstar question', - default: false - }) - .positional('keyword', { - type: 'string', - describe: 'Question name or id', - default: '' - }) - - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - -cmd.handler = function (argv) { - session.argv = argv; - // translation doesn't affect question lookup - core.getProblem(argv.keyword, true, function (e, problem) { - if (e) return log.fail(e); - - core.starProblem(problem, !argv.delete, function (e, starred) { - if (e) return log.fail(e); - - log.printf('[%s] %s %s', problem.fid, problem.name, - starred ? icon.like : icon.unlike); - - core.updateProblem(problem, { starred: starred }); - }); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/stat.js b/src/vsc-leetcode-cli/lib/commands/stat.js deleted file mode 100644 index 86c7965..0000000 --- a/src/vsc-leetcode-cli/lib/commands/stat.js +++ /dev/null @@ -1,250 +0,0 @@ -'use strict'; -var moment = require('moment'); -var _ = require('underscore'); - - -var icon = require('../icon'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); -var sprintf = require('../sprintf'); -var h = require('../helper'); - -const cmd = { - command: 'stat', - desc: 'Show statistics', - aliases: ['stats', 'progress', 'report'], - builder: function (yargs) { - return yargs - .option('c', { - alias: 'cal', - type: 'boolean', - default: false, - describe: 'Show calendar statistics' - }) - .option('g', { - alias: 'graph', - type: 'boolean', - default: false, - describe: 'Show graphic statistics' - }) - .option('l', { - alias: 'lock', - type: 'boolean', - default: true, - describe: 'Include locked questions' - }) - .option('q', core.filters.query) - .option('t', core.filters.tag) - .example('leetcode stat', 'Show progress status') - .example('leetcode stat -g', 'Show detailed status in graph') - .example('leetcode stat -c', 'Show accepted status in calendar') - .example('', '') - .example('leetcode stat --no-lock', 'Show status without locked questions') - .example('leetcode stat -t algorithms', 'Show status of algorithms questions only') - .example('leetcode stat -q h', 'Show status of hard questions only'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('c', { - alias: 'cal', - type: 'boolean', - default: false, - describe: 'Show calendar statistics' - }) - .option('g', { - alias: 'graph', - type: 'boolean', - default: false, - describe: 'Show graphic statistics' - }) - .option('l', { - alias: 'lock', - type: 'boolean', - default: true, - describe: 'Include locked questions' - }) - .option('q', core.filters.query) - .option('t', core.filters.tag) - argv_config.process_argv(argv) - - return argv_config.get_result() -} - -function printLine(key, done, all) { - const n = 30; - const percent = (all > 0) ? done / all : 0; - const x = Math.ceil(n * percent); - log.printf(' %s\t%3s/%-3s (%6s %%) %s%s', - h.prettyLevel(key), done, all, - (100 * percent).toFixed(2), - '█'.repeat(x), - '░'.repeat(n - x)); -} - -function showProgress(problems) { - const stats = { - easy: { all: 0, ac: 0 }, - medium: { all: 0, ac: 0 }, - hard: { all: 0, ac: 0 } - }; - - for (let problem of problems) { - const level = problem.level.toLowerCase(); - const state = problem.state.toLowerCase(); - - if (!(level in stats)) continue; - ++stats[level].all; - - if (!(state in stats[level])) continue; - ++stats[level][state]; - } - - printLine('Easy', stats.easy.ac, stats.easy.all); - printLine('Medium', stats.medium.ac, stats.medium.all); - printLine('Hard', stats.hard.ac, stats.hard.all); -} - -function showGraph(problems) { - const ICONS = { - ac: icon.ac, - notac: icon.notac, - none: icon.none, - empty: icon.empty - }; - - // row header is 4 bytes - // each question takes 2 bytes - // each group has 10 questions, which takes (2*10=20) + 3 paddings - let groups = Math.floor((h.width - 4) / (3 + 2 * 10)); - if (groups < 1) groups = 1; - if (groups > 5) groups = 5; - - const header = _.range(groups) - .map(x => sprintf('%4s%18s', x * 10 + 1, x * 10 + 10)) - .join(''); - log.info(' ' + header); - - const graph = []; - for (let problem of problems) - graph[problem.fid] = ICONS[problem.state] || ICONS.none; - - let line = [sprintf(' %04s', 0)]; - for (let i = 1, n = graph.length; i <= n; ++i) { - // padding before group - if (i % 10 === 1) line.push(' '); - - line.push(graph[i] || ICONS.empty); - - // time to start new row - if (i % (10 * groups) === 0 || i === n) { - log.info(line.join(' ')); - line = [sprintf(' %04s', i)]; - } - } - - log.info(); - log.printf('%7s%s%3s%s%3s%s', - ' ', ICONS.ac + ' Accepted', - ' ', ICONS.notac + ' Not Accepted', - ' ', ICONS.none + ' Remaining'); -} - -function showCal(problems) { - const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - const ICONS = [ - icon.none, - icon.ac, - icon.ac, - icon.ac, - icon.ac - ]; - - const N_MONTHS = 12; - const N_WEEKS = 53; - const N_WEEKDAYS = 7; - - const now = moment(); - - const SCORES = { easy: 1, medium: 2, hard: 5 }; - function toScore(sum, id) { - const problem = problems.find(x => x.fid === id); - if (problem) sum += (SCORES[problem.level.toLowerCase()] || 1); - return sum; - } - - // load historical stats - const graph = []; - const stats = require('../cache').get(h.KEYS.stat) || {}; - for (let k of _.keys(stats)) { - const score = (stats[k]['ac.set'] || []).reduce(toScore, 0); - if (score === 0) continue; - - const d = moment(k, 'YYYY-MM-DD'); - graph[now.diff(d, 'days')] = score; - } - - // print header - const buf = Buffer.alloc(120, ' ', 'ascii'); - for (let i = 0; i <= N_MONTHS; ++i) { - // for day 1 in each month, calculate its column position in graph - const d = now.clone().subtract(i, 'months').date(1); - const idx = now.diff(d, 'days'); - - const j = (N_WEEKS - idx / N_WEEKDAYS + 1) * 2; - if (j >= 0) buf.write(MONTHS[d.month()], j); - } - log.printf('%7s%s', ' ', buf.toString()); - - // print graph - for (let i = 0; i < N_WEEKDAYS; ++i) { - const line = []; - // print day in week - const idx = (now.day() + i + 1) % N_WEEKDAYS; - line.push(sprintf('%4s ', WEEKDAYS[idx])); - - for (let j = 0; j < N_WEEKS; ++j) { - let idx = (N_WEEKS - j - 1) * N_WEEKDAYS + N_WEEKDAYS - i - 1; - const d = now.clone().subtract(idx, 'days'); - - // map count to icons index: - // [0] => 0, [1,5] => 1, [6,10] => 2, [11,15] => 3, [16,) => 4 - const count = graph[idx] || 0; - idx = Math.floor((count - 1) / 5) + 1; - if (idx > 4) idx = 4; - - let icon = ICONS[idx]; - // use different colors for adjacent months - if (idx === 0 && d.month() % 2) icon = icon; - line.push(icon); - } - log.info(line.join(' ')); - } - - log.info(); - log.printf('%8s%s%3s%s%3s%s%3s%s', - ' ', ICONS[1] + ' 1~5', - ' ', ICONS[2] + ' 6~10', - ' ', ICONS[3] + ' 11~15', - ' ', ICONS[4] + ' 16+'); -} - -cmd.handler = function (argv) { - session.argv = argv; - core.filterProblems(argv, function (e, problems) { - if (e) return log.fail(e); - - if (!argv.lock) - problems = problems.filter(x => !x.locked); - - log.info(); - if (argv.graph) showGraph(problems); - else if (argv.cal) showCal(problems); - else showProgress(problems); - log.info(); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/submission.js b/src/vsc-leetcode-cli/lib/commands/submission.js deleted file mode 100644 index 4ea7801..0000000 --- a/src/vsc-leetcode-cli/lib/commands/submission.js +++ /dev/null @@ -1,192 +0,0 @@ -'use strict'; -var path = require('path'); - -var _ = require('underscore'); - -var h = require('../helper'); -var file = require('../file'); - -var config = require('../config'); -var log = require('../log'); -var Queue = require('../queue'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'submission [keyword]', - aliases: ['pull'], - desc: 'Download submission code', - builder: function (yargs) { - return yargs - .option('a', { - alias: 'all', - type: 'boolean', - default: false, - describe: 'Download all questions' - }) - .option('l', { - alias: 'lang', - type: 'string', - default: 'all', - describe: 'Filter by programming language' - }) - .option('o', { - alias: 'outdir', - type: 'string', - describe: 'Where to save submission code', - default: '.' - }) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra question details in submission code' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Download specific question by id' - }) - .example('leetcode submission -a -o mydir', 'Download all to folder mydir') - .example('leetcode submission -x -a', 'Add descriptions in the downloaded codes') - .example('leetcode submission -l cpp 1', 'Download cpp submission of question 1'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('a', { - alias: 'all', - type: 'boolean', - default: false, - describe: 'Download all questions' - }) - .option('l', { - alias: 'lang', - type: 'string', - default: 'all', - describe: 'Filter by programming language' - }) - .option('o', { - alias: 'outdir', - type: 'string', - describe: 'Where to save submission code', - default: '.' - }) - .option('x', { - alias: 'extra', - type: 'boolean', - default: false, - describe: 'Show extra question details in submission code' - }) - .option('T', { - alias: 'dontTranslate', - type: 'boolean', - default: false, - describe: 'Set to true to disable endpoint\'s translation', - }) - .positional('keyword', { - type: 'string', - default: '', - describe: 'Download specific question by id' - }) - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -function doTask(problem, queue, cb) { - const argv = queue.ctx.argv; - - function onTaskDone(e, msg) { - // NOTE: msg color means different purpose: - // - red: error - // - green: accepted, fresh download - // - yellow: not ac-ed, fresh download - // - white: existed already, skip download - log.printf('[%=4s] %-60s %s', problem.fid, problem.name, - (e ? 'ERROR: ' + (e.msg || e) : msg)); - if (cb) cb(e); - } - - if (argv.extra) { - // have to get problem details, e.g. problem description. - core.getProblem(problem.fid, !argv.dontTranslate, function (e, problem) { - if (e) return cb(e); - exportSubmission(problem, argv, onTaskDone); - }); - } else { - exportSubmission(problem, argv, onTaskDone); - } -} - -function exportSubmission(problem, argv, cb) { - core.getSubmissions(problem, function (e, submissions) { - if (e) return cb(e); - if (submissions.length === 0) - return cb('No submissions?'); - - // get obj list contain required filetype - submissions = submissions.filter(x => argv.lang === 'all' || argv.lang === x.lang); - if (submissions.length === 0) - return cb('No submissions in required language.'); - - // if no accepted, use the latest non-accepted one - const submission = submissions.find(x => x.status_display === 'Accepted') || submissions[0]; - submission.ac = (submission.status_display === 'Accepted'); - - const data = _.extend({}, submission, problem); - data.sid = submission.id; - data.ac = submission.ac ? 'ac' : 'notac'; - const basename = file.fmt(config.file.submission, data); - const f = path.join(argv.outdir, basename + h.langToExt(submission.lang)); - - file.mkdir(argv.outdir); - // skip the existing cached submissions - if (file.exist(f)) - return cb(null, f); - - core.getSubmission(submission, function (e, submission) { - if (e) return cb(e); - - const opts = { - lang: submission.lang, - code: submission.code, - tpl: argv.extra ? 'detailed' : 'codeonly' - }; - file.write(f, core.exportProblem(problem, opts)); - cb(null, submission.ac ? f - : f); - }); - }); -} - -cmd.handler = function (argv) { - session.argv = argv; - const q = new Queue(null, { argv: argv }, doTask); - - if (argv.all) { - core.getProblems(function (e, problems) { - if (e) return log.fail(e); - problems = problems.filter(x => x.state === 'ac' || x.state === 'notac'); - q.addTasks(problems).run(); - }); - return; - } - - if (!argv.keyword) - return log.fail('missing keyword?'); - - core.getProblem(argv.keyword, !argv.dontTranslate, function (e, problem) { - if (e) return log.fail(e); - q.addTask(problem).run(); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/submit.js b/src/vsc-leetcode-cli/lib/commands/submit.js deleted file mode 100644 index 7b926ec..0000000 --- a/src/vsc-leetcode-cli/lib/commands/submit.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; -var util = require('util'); -var lodash = require('lodash'); - -var h = require('../helper'); -var file = require('../file'); - -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'submit ', - aliases: ['push', 'commit'], - desc: 'Submit code', - builder: function (yargs) { - return yargs - .positional('filename', { - type: 'string', - describe: 'Code file to submit', - default: '' - }) - .example('leetcode submit 1.two-sum.cpp', 'Submit code'); - } -}; - - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().positional('filename', { - type: 'string', - describe: 'Code file to submit', - default: '' - }) - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -function printResult(actual, k, log_obj) { - if (!actual.hasOwnProperty(k)) return; - - const v = actual[k] || ''; - const lines = Array.isArray(v) ? v : [v]; - for (let line of lines) { - if (k !== 'state') { - if (!log_obj.hasOwnProperty(lodash.startCase(k))) { - log_obj[lodash.startCase(k)] = [line] - } else { - log_obj[lodash.startCase(k)].push(line) - } - } else { - log_obj.messages.push(line) - } - } -} - -function printLine(log_obj) { - const args = Array.from(arguments).slice(1); - const actual = args.shift(); - const line = util.format.apply(util, args); - log_obj.messages.push(line) -} - -cmd.handler = function (argv) { - session.argv = argv; - if (!file.exist(argv.filename)) - return log.fatal('File ' + argv.filename + ' not exist!'); - - const meta = file.meta(argv.filename); - - // translation doesn't affect problem lookup - core.getProblem(meta, true, function (e, problem) { - if (e) return log.fail(e); - - problem.file = argv.filename; - problem.lang = meta.lang; - - core.submitProblem(problem, function (e, results) { - if (e) return log.fail(e); - - const result = results[0]; - - var log_obj = {} - log_obj.messages = [] - log_obj.system_message = {} - log_obj.system_message.fid = problem.fid - log_obj.system_message.id = problem.id - log_obj.system_message.qid = problem.id - log_obj.system_message.sub_type = "submit" - log_obj.system_message.accepted = false; - - printResult(result, 'state', log_obj); - printLine(log_obj, result, '%d/%d cases passed (%s)', - result.passed, result.total, result.runtime); - - if (result.ok) { - session.updateStat('ac', 1); - session.updateStat('ac.set', problem.fid); - log_obj.system_message.accepted = true; - - (function () { - if (result.runtime_percentile) - printLine(log_obj, result, 'Your runtime beats %d %% of %s submissions', - result.runtime_percentile.toFixed(2), result.lang); - else - return log.warn('Failed to get runtime percentile.'); - if (result.memory && result.memory_percentile) - printLine(log_obj, result, 'Your memory usage beats %d %% of %s submissions (%s)', - result.memory_percentile.toFixed(2), result.lang, result.memory); - else - return log.warn('Failed to get memory percentile.'); - })(); - } else { - result.testcase = result.testcase.slice(1, -1).replace(/\\n/g, '\n'); - printResult(result, 'error', log_obj); - printResult(result, 'testcase', log_obj); - printResult(result, 'answer', log_obj); - printResult(result, 'expected_answer', log_obj); - printResult(result, 'stdout', log_obj); - } - log.info(JSON.stringify(log_obj)) - core.updateProblem(problem, { state: (result.ok ? 'ac' : 'notac') }); - }); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/test.js b/src/vsc-leetcode-cli/lib/commands/test.js deleted file mode 100644 index db9923e..0000000 --- a/src/vsc-leetcode-cli/lib/commands/test.js +++ /dev/null @@ -1,240 +0,0 @@ -'use strict'; -var _ = require('underscore'); -var lodash = require('lodash'); -var util = require('util'); - -var h = require('../helper'); -var file = require('../file'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); - -const cmd = { - command: 'test ', - aliases: ['run'], - desc: 'Test code', - builder: function (yargs) { - return yargs - .option('i', { - alias: 'interactive', - type: 'boolean', - default: false, - describe: 'Provide test case interactively' - }) - .option('t', { - alias: 'testcase', - type: 'string', - default: '', - describe: 'Provide test case' - }) - .positional('filename', { - type: 'string', - default: '', - describe: 'Code file to test' - }) - .example('leetcode test 1.two-sum.cpp', 'Test code with default test case') - .example('leetcode test 1.two-sum.cpp -t "[1,2,3]\\n4"', 'Test code with customized test case'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('i', { - alias: 'interactive', - type: 'boolean', - default: false, - describe: 'Provide test case interactively' - }) - .option('t', { - alias: 'testcase', - type: 'string', - default: '', - describe: 'Provide test case' - }) - .option('a', { - alias: 'allcase', - type: 'boolean', - default: false, - describe: 'Provide all test case' - }) - .positional('filename', { - type: 'string', - default: '', - describe: 'Code file to test' - }) - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -function printResult(actual, extra, k, log_obj) { - if (!actual.hasOwnProperty(k)) return; - // HACk: leetcode still return 'Accepted' even the answer is wrong!! - const v = actual[k] || ''; - if (k === 'state' && v === 'Accepted') return; - - let ok = actual.ok; - - const lines = Array.isArray(v) ? v : [v]; - for (let line of lines) { - const extraInfo = extra ? ` (${extra})` : ''; - if (k !== 'state') { - var new_kk = lodash.startCase(k) + extraInfo; - if (!log_obj.hasOwnProperty(new_kk)) { - log_obj[new_kk] = [line] - } else { - log_obj[new_kk].push(line) - } - } else { - log_obj.messages.push(line) - } - } -} - -function runTest(argv) { - if (!file.exist(argv.filename)) - return log.fatal('File ' + argv.filename + ' not exist!'); - - const meta = file.meta(argv.filename); - - // [key: string]: string[]; - // messages: string[]; - - core.getProblem(meta, true, function (e, problem) { - if (e) return log.fail(JSON.stringify({ messages: ["error"], code: [-1], error: [e.msg || e] })); - - if (!problem.testable) - return log.fail(JSON.stringify({ messages: ["error"], code: [-2], error: ['not testable? please submit directly!'] })); - - if (argv.testcase) { - problem.testcase = argv.testcase.replace(/\\n/g, '\n'); - } - - if (argv.allcase) { - let new_desc = problem.desc; - new_desc = new_desc.replace(/<\/sup>/gm, '').replace(//gm, '^'); - new_desc = require('he').decode(require('cheerio').load(new_desc).root().text()); - // NOTE: wordwrap internally uses '\n' as EOL, so here we have to - // remove all '\r' in the raw string. - new_desc = new_desc.replace(/\r\n/g, '\n').replace(/^ /mg, '⁠'); - var input = (require('wordwrap')(120))(new_desc).split('\n'); - var temp_test = [] - var start_flag = false; - var temp_collect = ""; - for (let all_input = 0; all_input < input.length; all_input++) { - const element = input[all_input]; - var check_index = element.indexOf("输入"); - if (check_index == -1) { - check_index = element.indexOf("Input:"); - } - if (check_index != -1) { - temp_collect += element.substring(check_index + 1) - start_flag = true; - continue; - } - - var check_index = element.indexOf("输出"); - if (check_index == -1) { - check_index = element.indexOf("Output:"); - } - if (check_index != -1) { - start_flag = false; - } - if (start_flag) { - temp_collect += element; - } else { - if (temp_collect.length > 0) { - var new_ele = temp_collect; - var temp_case = [] - var wait_cur = "" - var no_need_flag = false - for (let index = new_ele.length - 1; index >= 0; index--) { - if (no_need_flag) { - if (new_ele[index] == ",") { - no_need_flag = false; - } - } else { - if (new_ele[index] == "=") { - temp_case.push(wait_cur.trim()) - no_need_flag = true; - wait_cur = "" - } else { - wait_cur = new_ele[index] + wait_cur - } - } - } - for (let index = temp_case.length - 1; index >= 0; index--) { - temp_test.push(temp_case[index]) - } - temp_collect = ""; - } - } - - } - - if (temp_test.length < 1) { - return; - } - var all_case = temp_test.join("\n") - problem.testcase = all_case - } - - if (!problem.testcase) - return log.fail(JSON.stringify({ messages: ["error"], code: [-3], error: ['missing testcase?'] })); - - problem.file = argv.filename; - problem.lang = meta.lang; - - core.testProblem(problem, function (e, results) { - if (e) return log.fail(e); - - - results = _.sortBy(results, x => x.type); - - var log_obj = {} - log_obj.messages = [] - log_obj.system_message = {} - log_obj.system_message.fid = problem.fid - log_obj.system_message.id = problem.id - log_obj.system_message.qid = problem.id - log_obj.system_message.sub_type = "test" - log_obj.system_message.accepted = false; - - if (results[0].state === 'Accepted') { - results[0].state = 'Finished'; - log_obj.system_message.accepted = true; - } - printResult(results[0], null, 'state', log_obj); - printResult(results[0], null, 'error', log_obj); - - results[0].your_input = problem.testcase; - results[0].output = results[0].answer; - // LeetCode-CN returns the actual and expected answer into two separate responses - if (results[1]) { - results[0].expected_answer = results[1].answer; - } - results[0].stdout = results[0].stdout.slice(1, -1).replace(/\\n/g, '\n'); - printResult(results[0], null, 'your_input', log_obj); - printResult(results[0], results[0].runtime, 'output', log_obj); - printResult(results[0], null, 'expected_answer', log_obj); - printResult(results[0], null, 'stdout', log_obj); - log.info(JSON.stringify(log_obj)); - }); - }); -} - -cmd.handler = function (argv) { - session.argv = argv; - if (!argv.i) - return runTest(argv); - - h.readStdin(function (e, data) { - if (e) return log.fail(e); - - argv.testcase = data; - return runTest(argv); - }); -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/commands/user.js b/src/vsc-leetcode-cli/lib/commands/user.js deleted file mode 100644 index f29bc7c..0000000 --- a/src/vsc-leetcode-cli/lib/commands/user.js +++ /dev/null @@ -1,175 +0,0 @@ -'use strict'; -var prompt = require('prompt'); - -var h = require('../helper'); -var config = require('../config'); -var log = require('../log'); -var core = require('../core'); -var session = require('../session'); -var sprintf = require('../sprintf'); - -const cmd = { - command: 'user', - aliases: ['account'], - desc: 'Manage account', - builder: function (yargs) { - return yargs - .option('l', { - alias: 'login', - type: 'boolean', - default: false, - describe: 'Login' - }) - .option('c', { - alias: 'cookie', - type: 'boolean', - default: false, - describe: 'cookieLogin' - }) - .option('g', { - alias: 'github', - type: 'boolean', - default: false, - describe: 'githubLogin' - }) - .option('i', { - alias: 'linkedin', - type: 'boolean', - default: false, - describe: 'linkedinLogin' - }) - .option('L', { - alias: 'logout', - type: 'boolean', - default: false, - describe: 'Logout' - }) - .example('leetcode user', 'Show current user') - .example('leetcode user -l', 'User login') - .example('leetcode user -c', 'User Cookie login') - .example('leetcode user -g', 'User GitHub login') - .example('leetcode user -i', 'User LinkedIn login') - .example('leetcode user -L', 'User logout'); - } -}; - -cmd.process_argv = function (argv) { - var argv_config = h.base_argv().option('l', { - alias: 'login', - type: 'boolean', - default: false, - describe: 'Login' - }) - .option('c', { - alias: 'cookie', - type: 'boolean', - default: false, - describe: 'cookieLogin' - }) - .option('g', { - alias: 'github', - type: 'boolean', - default: false, - describe: 'githubLogin' - }) - .option('i', { - alias: 'linkedin', - type: 'boolean', - default: false, - describe: 'linkedinLogin' - }) - .option('L', { - alias: 'logout', - type: 'boolean', - default: false, - describe: 'Logout' - }) - - argv_config.process_argv(argv) - - return argv_config.get_result() -} - - -cmd.handler = function (argv) { - session.argv = argv; - let user = null; - if (argv.login) { - // login - prompt.colors = false; - prompt.message = ''; - prompt.start(); - prompt.get([ - { name: 'login', required: true }, - { name: 'pass', required: true, hidden: true } - ], function (e, user) { - if (e) { - return log.fail(JSON.stringify({ code: -1, msg: e.msg || e })); - } - - core.login(user, function (e, user) { - if (e) { - return log.fail(JSON.stringify({ code: -2, msg: e.msg || e })); - } - log.info(JSON.stringify({ code: 100, user_name: user.name })); - }); - }); - } else if (argv.logout) { - // logout - user = core.logout(user, true); - if (user) - log.info(JSON.stringify({ code: 100, user_name: user.name })); - else - log.fail(JSON.stringify({ code: -3, msg: 'You are not login yet?' })); - // third parties - } else if (argv.github || argv.linkedin) { - // add future third parties here - const functionMap = new Map( - [ - ['g', core.githubLogin], - ['github', core.githubLogin], - ['i', core.linkedinLogin], - ['linkedin', core.linkedinLogin], - ] - ); - const keyword = Object.entries(argv).filter((i) => (i[1] === true))[0][0]; - const coreFunction = functionMap.get(keyword); - prompt.colors = false; - prompt.message = ''; - prompt.start(); - prompt.get([ - { name: 'login', required: true }, - { name: 'pass', required: true, hidden: true } - ], function (e, user) { - if (e) return log.fail(JSON.stringify({ code: -4, msg: e.msg || e })); - coreFunction(user, function (e, user) { - if (e) return log.fail(JSON.stringify({ code: -5, msg: e.msg || e })); - log.info(JSON.stringify({ code: 100, user_name: user.name })); - }); - }); - } else if (argv.cookie) { - // session - prompt.colors = false; - prompt.message = ''; - prompt.start(); - prompt.get([ - { name: 'login', required: true }, - { name: 'cookie', required: true } - ], function (e, user) { - if (e) return log.fail(e); - core.cookieLogin(user, function (e, user) { - if (e) return log.fail(JSON.stringify({ code: -6, msg: e.msg || e })); - log.info(JSON.stringify({ code: 100, user_name: user.name })); - }); - }); - } else { - // show current user - user = session.getUser(); - if (user) { - log.info(JSON.stringify({ code: 100, user_name: user.name })); - } else - return log.fail(JSON.stringify({ code: -7, msg: 'You are not login yet?' })); - } -}; - -module.exports = cmd; diff --git a/src/vsc-leetcode-cli/lib/config.js b/src/vsc-leetcode-cli/lib/config.js deleted file mode 100644 index ddc5513..0000000 --- a/src/vsc-leetcode-cli/lib/config.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; -var _ = require('underscore'); -var nconf = require('nconf'); - -var file = require('./file'); - -const DEFAULT_CONFIG = { - // usually you don't wanna change those - sys: { - categories: [ - 'algorithms', - 'LCCI', - 'LCOF', - 'LCOF2' - ], - langs: [ - 'bash', - 'c', - 'cpp', - 'csharp', - 'golang', - 'java', - 'javascript', - 'kotlin', - 'mysql', - 'php', - 'python', - 'python3', - 'ruby', - 'rust', - 'scala', - 'swift', - 'typescript' - ], - urls: { - // base urls - base: 'https://leetcode.com', - graphql: 'https://leetcode.com/graphql', - login: 'https://leetcode.com/accounts/login/', - // third part login base urls. TODO facebook google - github_login: 'https://leetcode.com/accounts/github/login/?next=%2F', - facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F', - linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F', - // redirect urls - leetcode_redirect: 'https://leetcode.com/', - github_tf_redirect: 'https://github.com/sessions/two-factor', - // simulate login urls - github_login_request: 'https://github.com/login', - github_session_request: 'https://github.com/session', - github_tf_session_request: 'https://github.com/sessions/two-factor', - linkedin_login_request: 'https://www.linkedin.com/login', - linkedin_session_request: 'https://www.linkedin.com/checkpoint/lg/login-submit', - // questions urls - problems: 'https://leetcode.com/api/problems/$category/', - problem: 'https://leetcode.com/problems/$slug/description/', - test: 'https://leetcode.com/problems/$slug/interpret_solution/', - session: 'https://leetcode.com/session/', - submit: 'https://leetcode.com/problems/$slug/submit/', - submissions: 'https://leetcode.com/api/submissions/$slug', - submission: 'https://leetcode.com/submissions/detail/$id/', - verify: 'https://leetcode.com/submissions/detail/$id/check/', - favorites: 'https://leetcode.com/list/api/questions', - favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id', - plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli/master/lib/plugins/$name.js' - }, - }, - - // but you will want change these - autologin: { - enable: false, - retry: 2 - }, - code: { - editor: 'vim', - lang: 'cpp' - }, - file: { - show: '${fid}.${slug}', - submission: '${fid}.${slug}.${sid}.${ac}' - }, - color: { - enable: true, - theme: 'default' - }, - icon: { - theme: '' - }, - network: { - concurrency: 10, - delay: 1 - }, - plugins: {} -}; - -function Config() { } - -Config.prototype.init = function () { - nconf.file('local', file.configFile()) - .add('global', { type: 'literal', store: DEFAULT_CONFIG }) - .defaults({}); - - const cfg = nconf.get(); - nconf.remove('local'); - nconf.remove('global'); - - // HACK: remove old style configs - for (const x in cfg) { - if (x === x.toUpperCase()) delete cfg[x]; - } - delete DEFAULT_CONFIG.type; - delete cfg.type; - - _.extendOwn(this, cfg); -}; - -Config.prototype.getAll = function (useronly) { - const cfg = _.extendOwn({}, this); - if (useronly) delete cfg.sys; - return cfg; -}; - -module.exports = new Config(); diff --git a/src/vsc-leetcode-cli/lib/core.js b/src/vsc-leetcode-cli/lib/core.js deleted file mode 100644 index 5e94edc..0000000 --- a/src/vsc-leetcode-cli/lib/core.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; -var util = require('util'); - -var _ = require('underscore'); -var cheerio = require('cheerio'); -var he = require('he'); - -var log = require('./log'); -var h = require('./helper'); -var file = require('./file'); -var Plugin = require('./plugin'); - -const core = new Plugin(99999999, 'core', '20170722', 'Plugins manager'); - -core.filters = { - query: { - alias: 'query', - type: 'string', - default: '', - describe: [ - 'Filter questions by condition:', - 'Uppercase means negative', - 'e = easy E = m+h', - 'm = medium M = e+h', - 'h = hard H = e+m', - 'd = done D = not done', - 'l = locked L = non locked', - 's = starred S = not starred' - ].join('\n') - }, - tag: { - alias: 'tag', - type: 'array', - default: [], - describe: 'Filter questions by tag' - } -}; - -function hasTag(o, tag) { - return Array.isArray(o) && o.some(x => x.indexOf(tag.toLowerCase()) >= 0); -} - -const isLevel = (x, q) => x.level[0].toLowerCase() === q.toLowerCase(); -const isACed = x => x.state === 'ac'; -const isLocked = x => x.locked; -const isStarred = x => x.starred; - -const QUERY_HANDLERS = { - e: isLevel, - E: _.negate(isLevel), - m: isLevel, - M: _.negate(isLevel), - h: isLevel, - H: _.negate(isLevel), - l: isLocked, - L: _.negate(isLocked), - d: isACed, - D: _.negate(isACed), - s: isStarred, - S: _.negate(isStarred) -}; - -core.filterProblems = function (opts, cb) { - this.getProblems(!opts.dontTranslate, function (e, problems) { - if (e) return cb(e); - - for (let q of (opts.query || '').split('')) { - const f = QUERY_HANDLERS[q]; - if (!f) continue; - problems = problems.filter(x => f(x, q)); - } - - for (let t of (opts.tag || [])) { - problems = problems.filter(function (x) { - return x.category === t || - hasTag(x.companies, t) || - hasTag(x.tags, t); - }); - } - - return cb(null, problems); - }); -}; - -core.getProblem = function (keyword, needTranslation, cb) { - // if (keyword.id) - // return core.next.getProblem(keyword, needTranslation, cb); - - this.getProblems(needTranslation, function (e, problems) { - if (e) return cb(e); - - keyword = Number(keyword) || keyword; - const metaFid = file.exist(keyword) ? Number(file.meta(keyword).id) : NaN; - const problem = problems.find(function (x) { - if (keyword?.fid) { - return x.fid + '' === keyword.fid + '' - } else if (keyword?.qid) { - return x.id + '' === keyword.qid + '' - } else { - return x.id + '' === keyword + '' || x.name === keyword || x.slug === keyword; - } - }); - if (!problem) return cb('Problem not found!'); - core.next.getProblem(problem, needTranslation, cb); - }); -}; - -core.starProblem = function (problem, starred, cb) { - if (problem.starred === starred) { - log.debug('problem is already ' + (starred ? 'starred' : 'unstarred')); - return cb(null, starred); - } - - core.next.starProblem(problem, starred, cb); -}; - -core.exportProblem = function (problem, opts) { - const data = _.extend({}, problem); - - // unify format before rendering - data.app = require('./config').app || 'leetcode'; - if (!data.fid) data.fid = data.id; - if (!data.lang) data.lang = opts.lang; - data.code = (opts.code || data.code || '').replace(/\r\n/g, '\n'); - data.comment = h.langToCommentStyle(data.lang); - data.percent = data.percent.toFixed(2); - data.testcase = util.inspect(data.testcase || ''); - - if (opts.tpl === 'detailed') { - let desc = data.desc; - // Replace with '^' as the power operator - desc = desc.replace(/<\/sup>/gm, '').replace(//gm, '^'); - desc = he.decode(cheerio.load(desc).root().text()); - // NOTE: wordwrap internally uses '\n' as EOL, so here we have to - // remove all '\r' in the raw string. - desc = desc.replace(/\r\n/g, '\n').replace(/^ /mg, '⁠'); - const wrap = require('wordwrap')(79 - data.comment.line.length); - data.desc = wrap(desc).split('\n'); - } - - return file.render(opts.tpl, data); -}; - -core.getTodayQuestion = function (cb) { - this.getQuestionOfToday(function (e, result) { - if (e) return cb(e); - return cb(null, result); - }); -} -core.getUserContest = function (username, cb) { - this.getUserContestP(username, function (e, result) { - if (e) return cb(e); - return cb(null, result); - }); -} - -core.getQueryZ = function (username, cb) { - this.getTestApi(username, function (e, result) { - if (e) return cb(e); - return cb(null, result); - }); -} - -module.exports = core; diff --git a/src/vsc-leetcode-cli/lib/file.js b/src/vsc-leetcode-cli/lib/file.js deleted file mode 100644 index ff58c2c..0000000 --- a/src/vsc-leetcode-cli/lib/file.js +++ /dev/null @@ -1,189 +0,0 @@ -'use strict'; -var fs = require('fs'); -var os = require('os'); -var path = require('path'); - -var _ = require('underscore'); -var mkdirp = require('mkdirp'); - -const file = {} - -file.init = function () { - _.templateSettings = { - evaluate: /\{\{(.+?)\}\}/g, - interpolate: /\$\{(.+?)\}/g - }; -}; - -file.isWindows = function () { - return process.platform === 'win32'; -}; - -file.userHomeDir = function () { - return process.env.HOME || process.env.USERPROFILE; -}; - -file.homeDir = function () { - return path.join(this.userHomeDir(), '.lc'); -}; - -file.appDir = function () { - const config = require('./config'); - return path.join(this.homeDir(), config.app || 'leetcode'); -}; - -file.cacheDir = function () { - return path.join(this.appDir(), 'cache'); -}; - -file.codeDir = function (dir) { - return path.join(__dirname, '..', dir || ''); -}; - -file.cacheFile = function (k) { - return path.join(this.cacheDir(), k + '.json'); -}; - -file.configFile = function () { - return path.join(this.homeDir(), 'config.json'); -}; - -file.pluginFile = function (name) { - return path.join(this.codeDir('lib/plugins'), path.basename(name)); -}; - -file.listCodeDir = function (dir) { - dir = this.codeDir(dir); - return this.list(dir).map(function (f) { - const fullpath = path.join(dir, f); - const ext = path.extname(f); - const name = path.basename(f, ext); - - let data = null; - switch (ext) { - case '.js': data = require(fullpath); break; - case '.json': data = JSON.parse(file.data(fullpath)); break; - } - return { name: name, data: data, file: f }; - }); -}; - -file.mkdir = function (fullpath) { - if (fs.existsSync(fullpath)) return; - mkdirp.sync(fullpath); -}; - -file.exist = function (fullpath) { - return fs.existsSync(fullpath); -}; - -file.rm = function (fullpath) { - return fs.unlinkSync(fullpath); -}; - -file.mv = function (src, dst) { - return fs.renameSync(src, dst); -}; - -file.list = function (dir) { - return fs.readdirSync(dir); -}; - -file.stat = function (fullpath) { - return fs.statSync(fullpath); -}; - -file.write = function (fullpath, data) { - return fs.writeFileSync(fullpath, data); -}; - -file.name = function (fullpath) { - return path.basename(fullpath, path.extname(fullpath)); -}; - -file.data = function (fullpath) { - return fs.existsSync(fullpath) ? fs.readFileSync(fullpath).toString() : null; -}; - -file.codeData = function (fullpath) { - const data = this.data(fullpath); - - if (data === null) { - return null; - } - - const lines = data.split(/\r\n|\n|\r/); - const start = lines.findIndex(x => x.indexOf('@lc code=start') !== -1); - const end = lines.findIndex(x => x.indexOf('@lc code=end') !== -1); - - if (start !== -1 && end !== -1 && start + 1 <= end) { - return lines.slice(start + 1, end).join(os.EOL); - } - - return data; -}; - -file.render = function (tpl, data) { - const tplfile = path.join(__dirname, "..", "..", "..", "..", "resources", "templates", tpl + '.tpl') - let result = _.template(this.data(tplfile).replace(/\r\n/g, '\n'))(data); - if (this.isWindows()) { - result = result.replace(/\n/g, '\r\n'); - } else { - result = result.replace(/\r\n/g, '\n'); - } - return result; -}; - -file.fmt = function (format, data) { - return _.template(format)(data); -}; - -file.metaByName = function (filename) { - const m = {}; - - m.id = file.name(filename).split('.')[0]; - - - if (filename.endsWith('.py3') || filename.endsWith('.python3.py')) - m.lang = 'python3'; - else - m.lang = require('./helper').extToLang(filename); - - return m; -}; - -file.meta = function (filename) { - const m = {}; - - - const line = this.data(filename).split('\n') - .find(x => x.indexOf(' @lc app=') >= 0) || ''; - - // @lc app=leetcode.cn id=剑指 Offer II 116 lang=cpp - - var id_right = line.split('id=')[1] - var lang_cat = id_right.split('lang=') - var id = lang_cat[0].trim(); - var lang = lang_cat[1].trim(); - m.id = id - m.fid = id - m.lang = lang - return m; - // line.split(' ').forEach(function (x) { - // const v = x.split('='); - // if (v.length == 2) { - // m[v[0]] = v[1].trim(); - // } - // }); - - - // if (!m.id || !m.lang) { - // const olddata = this.metaByName(filename); - // m.id = m.id || olddata.id; - // m.lang = m.lang || olddata.lang; - // } - - return m; -}; - -module.exports = file; diff --git a/src/vsc-leetcode-cli/lib/helper.js b/src/vsc-leetcode-cli/lib/helper.js deleted file mode 100644 index d2b6108..0000000 --- a/src/vsc-leetcode-cli/lib/helper.js +++ /dev/null @@ -1,278 +0,0 @@ -'use strict'; -var _ = require('underscore'); -var ora = require('ora'); - -var file = require('./file'); - -const UNITS_SIZE = [ - { unit: 'B', name: 'Bytes', count: 1024 }, - { unit: 'K', name: 'KBytes', count: 1024 }, - { unit: 'M', name: 'MBytes', count: 1024 }, - { unit: 'G', name: 'GBytes', count: -1 } -]; - -const UNITS_TIME = [ - { unit: 's', name: 'seconds', count: 60 }, - { unit: 'm', name: 'minutes', count: 60 }, - { unit: 'h', name: 'hours', count: 24 }, - { unit: 'd', name: 'days', count: 7 }, - { unit: 'w', name: 'weeks', count: 4 }, - { unit: 'm', name: 'months', count: 12 }, - { unit: 'y', name: 'years', count: -1 } -]; - -function getUnit(units, v) { - for (let i = 0; i < units.length; ++i) { - if (units[i].count <= 0 || v < units[i].count) - return [v, units[i]]; - v /= units[i].count; - } -} - -const LANGS = [ - { lang: 'bash', ext: '.sh', style: '#' }, - { lang: 'c', ext: '.c', style: 'c' }, - { lang: 'cpp', ext: '.cpp', style: 'c' }, - { lang: 'csharp', ext: '.cs', style: 'c' }, - { lang: 'golang', ext: '.go', style: 'c' }, - { lang: 'java', ext: '.java', style: 'c' }, - { lang: 'javascript', ext: '.js', style: 'c' }, - { lang: 'kotlin', ext: '.kt', style: 'c' }, - { lang: 'mysql', ext: '.sql', style: '--' }, - { lang: 'php', ext: '.php', style: 'c' }, - { lang: 'python', ext: '.py', style: '#' }, - { lang: 'python3', ext: '.py', style: '#' }, - { lang: 'ruby', ext: '.rb', style: '#' }, - { lang: 'rust', ext: '.rs', style: 'c' }, - { lang: 'scala', ext: '.scala', style: 'c' }, - { lang: 'swift', ext: '.swift', style: 'c' }, - { lang: 'typescript', ext: '.ts', style: 'c' } -]; - -const h = {}; - -h.KEYS = { - user: '../user', - stat: '../stat', - plugins: '../../plugins', - problems: 'problems', - translation: 'translationConfig', - problem: p => p.fid + '.' + p.slug + '.' + p.category -}; - -h.prettyState = function (state) { - switch (state) { - case 'ac': return this.prettyText('', true); - case 'notac': return this.prettyText('', false); - default: return ' '; - } -}; - -h.prettyText = function (text, yesNo) { - const icon = require('./icon'); - switch (yesNo) { - case true: return (icon.yes + text); - case false: return (icon.no + text); - default: return text; - } -}; - -h.prettySize = function (n) { - const res = getUnit(UNITS_SIZE, n); - return res[0].toFixed(2) + res[1].unit; -}; - -h.prettyTime = function (n) { - const res = getUnit(UNITS_TIME, n); - return res[0].toFixed(0) + ' ' + res[1].name; -}; - -h.prettyLevel = function (level) { - switch (level.toLowerCase().trim()) { - case 'easy': return (level); - case 'medium': return (level); - case 'hard': return (level); - default: return level; - } -}; - -h.levelToName = function (level) { - switch (level) { - case 1: return 'Easy'; - case 2: return 'Medium'; - case 3: return 'Hard'; - default: return ' '; - } -}; - -h.statusToName = function (sc) { - switch (sc) { - case 10: return 'Accepted'; - case 11: return 'Wrong Answer'; - case 12: return 'Memory Limit Exceeded'; - case 13: return 'Output Limit Exceeded'; - case 14: return 'Time Limit Exceeded'; - case 15: return 'Runtime Error'; - case 16: return 'Internal Error'; - case 20: return 'Compile Error'; - case 21: return 'Unknown Error'; - default: return 'Unknown'; - } -}; - -h.langToExt = function (lang) { - const res = LANGS.find(x => x.lang === lang); - return res ? res.ext : '.raw'; -}; - -h.extToLang = function (fullpath) { - const res = LANGS.find(x => fullpath.endsWith(x.ext)); - return res ? res.lang : 'unknown'; -}; - -h.langToCommentStyle = function (lang) { - const res = LANGS.find(x => x.lang === lang); - - return (res && res.style === 'c') ? - { start: '/*', line: ' *', end: ' */', singleLine: '//' } : - { start: res.style, line: res.style, end: res.style, singleLine: res.style }; -}; - -h.readStdin = function (cb) { - const stdin = process.stdin; - const bufs = []; - - console.log('NOTE: to finish the input, press ' + - (file.isWindows() ? ' and ' : '')); - - stdin.on('readable', function () { - const data = stdin.read(); - if (data) { - // windows doesn't treat ctrl-D as EOF - if (file.isWindows() && data.toString() === '\x04\r\n') { - stdin.emit('end'); - } else { - bufs.push(data); - } - } - }); - stdin.on('end', function () { - cb(null, Buffer.concat(bufs).toString()); - }); - stdin.on('error', cb); -}; - -h.getSetCookieValue = function (resp, key) { - const cookies = resp.headers['set-cookie']; - if (!cookies) return null; - - for (let i = 0; i < cookies.length; ++i) { - const sections = cookies[i].split(';'); - for (let j = 0; j < sections.length; ++j) { - const kv = sections[j].trim().split('='); - if (kv[0] === key) return kv[1]; - } - } - return null; -}; - -h.printSafeHTTP = function (msg) { - return msg.replace(/(Cookie\s*:\s*)'.*?'/, '$1') - .replace(/('X-CSRFToken'\s*:\s*)'.*?'/, '$1') - .replace(/('set-cookie'\s*:\s*)\[.*?\]/, '$1'); -}; - -h.spin = function (s) { - return ora(s).start(); -}; - -const COLORS = { - blue: { fg: 'white', bg: 'bgBlue' }, - cyan: { fg: 'white', bg: 'bgCyan' }, - gray: { fg: 'white', bg: 'bgGray' }, - green: { fg: 'black', bg: 'bgGreen' }, - magenta: { fg: 'white', bg: 'bgMagenta' }, - red: { fg: 'white', bg: 'bgRed' }, - yellow: { fg: 'black', bg: 'bgYellow' }, - white: { fg: 'black', bg: 'bgWhite' } -}; -h.badge = function (s, color) { - s = ' ' + s + ' '; - return (s); -}; - -h.base_argv = function () { - var base = { - all_base_data: {}, - positional_index: 0, - positional_key: {}, - option: function (key, value) { - this.all_base_data[key] = value.default - this.all_base_data[value.alias] = value.default - this[key] = value - return this - }, - positional: function (key, value) { - this.positional_key[this.positional_index] = key - this.positional_index = this.positional_index + 1; - this.all_base_data[key] = value.default - this.all_base_data[value.alias] = value.default - this[key] = value - return this - }, - set_opt: function (key, temp_val) { - var cfg = this[key] - if (cfg) { - if (cfg.type == "boolean") { - this.all_base_data[key] = true; - if (cfg.alias) { - this.all_base_data[cfg.alias] = true; - } - return false; - } else { - this.all_base_data[key] = temp_val; - if (cfg.alias) { - this.all_base_data[cfg.alias] = temp_val; - } - return true; - } - } else { - this.all_base_data[key] = true; - } - }, - set_posi: function (value, index) { - var cfg_key = this.positional_key[index] - var cfg = this[cfg_key] - if (cfg) { - this.all_base_data[cfg_key] = value; - if (cfg.alias) { - this.all_base_data[cfg.alias] = value; - } - } - }, - process_argv: function (argv) { - var all_posi = 0 - for (let index = 3; index < argv.length; index++) { - var con = argv[index] - if (con[0] == '-' && con[1] == '-') { - this.set_opt(con.substring(2)) - } - else if (con[0] == '-') { - for (let con_index = 1; con_index < con.length; con_index++) { - if (this.set_opt(con[con_index], argv[index + 1])) { - con_index++; - } - } - } else { - this.set_posi(con, all_posi); - all_posi = all_posi + 1; - } - } - }, - get_result: function () { - return this.all_base_data - } - } - return base; -} -module.exports = h; diff --git a/src/vsc-leetcode-cli/lib/icon.js b/src/vsc-leetcode-cli/lib/icon.js deleted file mode 100644 index b033b29..0000000 --- a/src/vsc-leetcode-cli/lib/icon.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var file = require('./file'); - -const icons = { - yes: '✔', - no: '✘', - like: '★', - unlike: '☆', - lock: '🔒', - nolock: ' ', - empty: ' ', - ac: '▣', - notac: '▤', - none: '⬚', - - themes: new Map() -}; - -icons.setTheme = function (name) { - const defaultName = 'default'; - const theme = this.themes.get(name) || this.themes.get(defaultName) || {}; - _.extendOwn(this, theme); -}; - -icons.init = function () { - // for (let f of file.listCodeDir('icons')) - icons.themes.set("default", icons); -}; - -module.exports = icons; diff --git a/src/vsc-leetcode-cli/lib/log.js b/src/vsc-leetcode-cli/lib/log.js deleted file mode 100644 index bc6c9d1..0000000 --- a/src/vsc-leetcode-cli/lib/log.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; -var _ = require('underscore'); - - -var sprintf = require('./sprintf'); - -const log = { - output: _.bind(console.log, console), - level: null, - levels: new Map([ - ['TRACE', { value: 0, color: 'gray' }], - ['DEBUG', { value: 1, color: 'gray' }], - ['INFO', { value: 2, color: '' }], - ['WARN', { value: 3, color: 'yellow' }], - ['ERROR', { value: 4, color: 'red' }] - ]) -}; - -log.setLevel = function (name) { - this.level = this.levels.get(name) || this.levels.get('INFO'); -}; - -log.isEnabled = function (name) { - return this.level.value <= this.levels.get(name).value; -}; - -log.fail = function (e) { - let msg = sprintf('%s', (e.msg || e)); - if (e.statusCode) { - msg += sprintf(' [code=%s]', e.statusCode); - } - log.error(msg); -}; - -log.fatal = function (e) { - log.error(e); - process.exit(1); -}; - -log.printf = function () { - log.info(sprintf.apply(null, Array.from(arguments))); -}; - -log.init = function () { - this.setLevel('INFO'); - - for (let name of this.levels.keys()) { - log[name.toLowerCase()] = function () { - const level = log.levels.get(name); - if (log.level.value > level.value) return; - - const args = Array.from(arguments); - if (name !== 'INFO') args.unshift('[' + name + ']'); - - let s = args.map(x => x.toString()).join(' '); - this.output(s); - }; - } -}; - -module.exports = log; diff --git a/src/vsc-leetcode-cli/lib/plugin.js b/src/vsc-leetcode-cli/lib/plugin.js deleted file mode 100644 index 332bd1b..0000000 --- a/src/vsc-leetcode-cli/lib/plugin.js +++ /dev/null @@ -1,216 +0,0 @@ -'use strict'; -var cp = require('child_process'); -var fs = require('fs'); -var path = require('path'); - -var _ = require('underscore'); -var request = require('request'); - -var h = require('./helper'); -var file = require('./file'); -var cache = require('./cache'); -var config = require('./config'); -var log = require('./log'); -var Queue = require('./queue'); - -function Plugin(id, name, ver, desc, deps) { - this.id = id; - this.name = name; - this.ver = ver || 'default'; - this.desc = desc || ''; - - this.enabled = true; - this.deleted = false; - this.missing = (this.ver === 'missing'); - this.builtin = (this.ver === 'default'); - - // only need deps for current platform - this.deps = _.chain(deps || []) - .filter(x => !x.includes(':') || x.includes(':' + process.platform)) - .map(x => x.split(':')[0]) - .value(); -} - -Plugin.prototype.init = function () { - this.config = config.plugins[this.name] || {}; - this.next = null; -}; - -Plugin.prototype.setNext = function (next) { - Object.setPrototypeOf(this, next); - this.next = next; -}; - -Plugin.prototype.delete = function () { - if (!this.missing) { - try { - const fullpath = file.pluginFile(this.file); - file.rm(fullpath); - } catch (e) { - return log.error(e.message); - } - } - this.deleted = true; -}; - -Plugin.prototype.save = function () { - const stats = cache.get(h.KEYS.plugins) || {}; - - if (this.deleted) delete stats[this.name]; - else if (this.missing) return; - else stats[this.name] = this.enabled; - - cache.set(h.KEYS.plugins, stats); -}; - -Plugin.prototype.install = function (cb) { - if (this.deps.length === 0) return cb(); - - const cmd = 'npm install --save ' + this.deps.join(' '); - log.debug(cmd); - const spin = h.spin(cmd); - cp.exec(cmd, { cwd: file.codeDir() }, function (e) { - spin.stop(); - return cb(e); - }); -}; - -Plugin.prototype.help = function () { }; - -Plugin.plugins = []; - -Plugin.init = function (head) { - log.trace('initializing all plugins'); - head = head || require('./core'); - - const stats = cache.get(h.KEYS.plugins) || {}; - - // 1. find installed plugins - let installed = []; - for (let f of file.listCodeDir('lib/plugins')) { - const p = f.data; - if (!p) continue; - log.trace('found plugin: ' + p.name + '=' + p.ver); - - p.file = f.file; - p.enabled = stats[p.name]; - - if (!(p.name in stats)) { - if (p.builtin) { - log.trace('new builtin plugin, enable by default'); - p.enabled = true; - } else { - log.trace('new 3rd party plugin, disable by default'); - p.enabled = false; - } - } - installed.push(p); - } - // the one with bigger `id` comes first - installed = _.sortBy(installed, x => -x.id); - - // 2. init all in reversed order - for (let i = installed.length - 1; i >= 0; --i) { - const p = installed[i]; - if (p.enabled) { - p.init(); - log.trace('inited plugin: ' + p.name); - } else { - log.trace('skipped plugin: ' + p.name); - } - } - - // 3. chain together - const plugins = installed.filter(x => x.enabled); - let last = head; - for (let p of plugins) { - last.setNext(p); - last = p; - } - - // 4. check missing plugins - const missings = []; - for (let k of _.keys(stats)) { - if (installed.find(x => x.name === k)) continue; - const p = new Plugin(-1, k, 'missing'); - p.enabled = stats[k]; - missings.push(p); - log.trace('missing plugin:' + p.name); - } - - Plugin.plugins = installed.concat(missings); - return missings.length === 0; -}; - -Plugin.copy = function (src, cb) { - // FIXME: remove local file support? - if (path.extname(src) !== '.js') { - src = config.sys.urls.plugin.replace('$name', src); - } - const dst = file.pluginFile(src); - - const srcstream = src.startsWith('https://') ? request(src) : fs.createReadStream(src); - const dststream = fs.createWriteStream(dst); - let error; - - srcstream.on('response', function (resp) { - if (resp.statusCode !== 200) - srcstream.emit('error', 'HTTP Error: ' + resp.statusCode); - }); - srcstream.on('error', function (e) { - dststream.emit('error', e); - }); - - dststream.on('error', function (e) { - error = e; - dststream.end(); - }); - dststream.on('close', function () { - spin.stop(); - if (error) file.rm(dst); - return cb(error, dst); - }); - - log.debug('copying from ' + src); - const spin = h.spin('Downloading ' + src); - srcstream.pipe(dststream); -}; - -Plugin.install = function (name, cb) { - Plugin.copy(name, function (e, fullpath) { - if (e) return cb(e); - log.debug('copied to ' + fullpath); - - const p = require(fullpath); - p.file = path.basename(fullpath); - p.install(function () { - return cb(null, p); - }); - }); -}; - -Plugin.installMissings = function (cb) { - function doTask(plugin, queue, cb) { - Plugin.install(plugin.name, function (e, p) { - if (!e) { - p.enabled = plugin.enabled; - p.save(); - p.help(); - } - return cb(e, p); - }); - } - - const missings = Plugin.plugins.filter(x => x.missing); - if (missings.length === 0) return cb(); - - log.warn('Installing missing plugins, might take a while ...'); - const q = new Queue(missings, {}, doTask); - q.run(1, cb); -}; - -Plugin.save = function () { - for (let p of this.plugins) p.save(); -}; - -module.exports = Plugin; diff --git a/src/vsc-leetcode-cli/lib/plugins/cache.js b/src/vsc-leetcode-cli/lib/plugins/cache.js deleted file mode 100644 index 81988ca..0000000 --- a/src/vsc-leetcode-cli/lib/plugins/cache.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; -var _ = require('underscore'); - -var cache = require('../cache'); -var h = require('../helper'); -var log = require('../log'); -var Plugin = require('../plugin'); -var session = require('../session'); - -const plugin = new Plugin(50, 'cache', '', 'Plugin to provide local cache.'); - -// this function will clear all caches if needTranslation is different than stored -// it will also store the new needTranslation into cache automatically -function clearCacheIfTchanged(needTranslation) { - const translationConfig = cache.get(h.KEYS.translation); - if (!translationConfig || translationConfig['useEndpointTranslation'] != needTranslation) { - // cache doesn't have the key => old cache version, need to update - // or cache does have the key but it contains a different value - cache.deleteAll(); - cache.set(h.KEYS.translation, { useEndpointTranslation: needTranslation }); - log.debug('cache cleared: -T option changed'); - } -} - -plugin.getProblems = function (needTranslation, cb) { - clearCacheIfTchanged(needTranslation); - const problems = cache.get(h.KEYS.problems); - if (problems) { - log.debug('cache hit: problems.json'); - return cb(null, problems); - } - - plugin.next.getProblems(needTranslation, function (e, problems) { - if (e) return cb(e); - - cache.set(h.KEYS.problems, problems); - return cb(null, problems); - }); -}; - -plugin.getProblem = function (problem, needTranslation, cb) { - clearCacheIfTchanged(needTranslation); - const k = h.KEYS.problem(problem); - const _problem = cache.get(k); - if (_problem) { - if (!_problem.desc.includes('
')) {
-      // do not hit problem without html tags in desc (
 always exists for presenting testcase)
-      log.debug('cache discarded for being no longer valid: ' + k + '.json');
-    } else if (!['likes', 'dislikes'].every(p => p in _problem)) {
-      // do not hit problem without likes & dislikes (logic will be improved in new lib)
-      log.debug('cache discarded for being too old: ' + k + '.json');
-    } else {
-      // cache hit
-      log.debug('cache hit: ' + k + '.json');
-      _.extendOwn(problem, _problem);
-      return cb(null, problem);
-    }
-  }
-
-  plugin.next.getProblem(problem, needTranslation, function (e, _problem) {
-    if (e) return cb(e);
-
-    plugin.saveProblem(_problem);
-    return cb(null, _problem);
-  });
-};
-
-plugin.saveProblem = function (problem) {
-  // it would be better to leave specific problem cache being user
-  // independent, thus try to reuse existing cache as much as possible
-  // after changing user.
-  const _problem = _.omit(problem, ['locked', 'state', 'starred']);
-  return cache.set(h.KEYS.problem(problem), _problem);
-};
-
-plugin.updateProblem = function (problem, kv) {
-  const problems = cache.get(h.KEYS.problems);
-  if (!problems) return false;
-
-  const _problem = problems.find(x => x.id === problem.id);
-  if (!_problem) return false;
-
-  _.extend(_problem, kv);
-  return cache.set(h.KEYS.problems, problems);
-};
-
-plugin.login = function (user, cb) {
-  this.logout(user, false);
-  plugin.next.login(user, function (e, user) {
-    if (e) return cb(e);
-    session.saveUser(user);
-    return cb(null, user);
-  });
-};
-
-plugin.logout = function (user, purge) {
-  if (!user) user = session.getUser();
-  if (purge) session.deleteUser();
-  // NOTE: need invalidate any user related cache
-  session.deleteCodingSession();
-  return user;
-};
-
-module.exports = plugin;
diff --git a/src/vsc-leetcode-cli/lib/plugins/leetcode.cn.js b/src/vsc-leetcode-cli/lib/plugins/leetcode.cn.js
deleted file mode 100644
index f7dbff8..0000000
--- a/src/vsc-leetcode-cli/lib/plugins/leetcode.cn.js
+++ /dev/null
@@ -1,300 +0,0 @@
-'use strict';
-var request = require('request');
-
-var config = require('../config');
-var h = require('../helper');
-var log = require('../log');
-var Plugin = require('../plugin');
-var session = require('../session');
-
-//
-// [Usage]
-//
-// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md
-//
-var plugin = new Plugin(15, 'leetcode.cn', '',
-  'Plugin to talk with leetcode-cn APIs.');
-
-plugin.init = function () {
-  config.app = 'leetcode.cn';
-  config.sys.urls.base = 'https://leetcode.cn';
-  config.sys.urls.login = 'https://leetcode.cn/accounts/login/';
-  config.sys.urls.problems = 'https://leetcode.cn/api/problems/$category/';
-  config.sys.urls.problem = 'https://leetcode.cn/problems/$slug/description/';
-  config.sys.urls.graphql = 'https://leetcode.cn/graphql';
-  config.sys.urls.problem_detail = 'https://leetcode.cn/graphql';
-  config.sys.urls.test = 'https://leetcode.cn/problems/$slug/interpret_solution/';
-  config.sys.urls.session = 'https://leetcode.cn/session/';
-  config.sys.urls.submit = 'https://leetcode.cn/problems/$slug/submit/';
-  config.sys.urls.submissions = 'https://leetcode.cn/api/submissions/$slug';
-  config.sys.urls.submission = 'https://leetcode.cn/submissions/detail/$id/';
-  config.sys.urls.verify = 'https://leetcode.cn/submissions/detail/$id/check/';
-  config.sys.urls.favorites = 'https://leetcode.cn/list/api/questions';
-  config.sys.urls.favorite_delete = 'https://leetcode.cn/list/api/questions/$hash/$id';
-  config.sys.urls.noj_go = 'https://leetcode.cn/graphql/noj-go/'
-  config.sys.urls.u = 'https://leetcode.cn/u/$username/'
-
-  // third parties
-  config.sys.urls.github_login = 'https://leetcode.cn/accounts/github/login/?next=%2F';
-  config.sys.urls.linkedin_login = 'https://leetcode.cn/accounts/linkedin_oauth2/login/?next=%2F';
-  config.sys.urls.leetcode_redirect = 'https://leetcode.cn/';
-};
-
-// FIXME: refactor those
-// update options with user credentials
-function signOpts(opts, user) {
-  opts.headers.Cookie = 'LEETCODE_SESSION=' + user.sessionId +
-    ';csrftoken=' + user.sessionCSRF + ';';
-  opts.headers['X-CSRFToken'] = user.sessionCSRF;
-  opts.headers['X-Requested-With'] = 'XMLHttpRequest';
-}
-
-function makeOpts(url) {
-  const opts = {};
-  opts.url = url;
-  opts.headers = {};
-
-  if (session.isLogin())
-    signOpts(opts, session.getUser());
-  return opts;
-}
-
-function checkError(e, resp, expectedStatus) {
-  if (!e && resp && resp.statusCode !== expectedStatus) {
-    const code = resp.statusCode;
-    log.debug('http error: ' + code);
-
-    if (code === 403 || code === 401) {
-      e = session.errors.EXPIRED;
-    } else {
-      e = { msg: 'http error', statusCode: code };
-    }
-  }
-  return e;
-}
-
-// overloading getProblems here to make sure everything related
-//   to listing out problems can have a chance to be translated.
-// NOTE: Details of the problem is translated inside leetcode.js
-plugin.getProblems = function (needTranslation, cb) {
-  plugin.next.getProblems(needTranslation, function (e, problems) {
-    if (e) return cb(e);
-
-    if (needTranslation) {
-      // only translate titles of the list if user requested
-      plugin.getProblemsTitle(function (e, titles) {
-        if (e) return cb(e);
-
-        problems.forEach(function (problem) {
-          const title = titles[problem.id];
-          if (title)
-            problem.name = title;
-        });
-
-        return cb(null, problems);
-      });
-    } else {
-      return cb(null, problems);
-    }
-  });
-};
-
-plugin.getProblemsTitle = function (cb) {
-  log.debug('running leetcode.cn.getProblemNames');
-
-  const opts = makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = 'https://leetcode.cn/api/problems/algorithms/';
-
-  opts.json = true;
-  opts.body = {
-    query: [
-      'query getQuestionTranslation($lang: String) {',
-      '  translations: allAppliedQuestionTranslations(lang: $lang) {',
-      '    title',
-      '    questionId',
-      '    __typename',
-      '    }',
-      '}'
-    ].join('\n'),
-    variables: {},
-    operationName: 'getQuestionTranslation'
-  };
-
-  const spin = h.spin('Downloading questions titles');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    const titles = [];
-    body.data.translations.forEach(function (x) {
-      titles[x.questionId] = x.title;
-    });
-
-    return cb(null, titles);
-  });
-};
-
-// 获取每日一题
-plugin.getQuestionOfToday = function (cb) {
-  log.debug('running leetcode.cn.getQuestionOfToday');
-
-  const opts = makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = 'https://leetcode.cn/';
-
-  opts.json = true;
-  opts.body = {
-    operationName: "questionOfToday",
-    variables: {},
-    query: [
-      'query questionOfToday {',
-      '  todayRecord {',
-      '    date',
-      '    userStatus',
-      '    question {',
-      '      titleSlug',
-      '      questionId',
-      '      questionFrontendId',
-      // '      content',
-      // '      stats',
-      // '      likes',
-      // '      dislikes',
-      // '      codeDefinition',
-      // '      sampleTestCase',
-      // '      enableRunCode',
-      // '      metaData',
-      // '      translatedContent',
-      '      __typename',
-      '    }',
-      '  __typename',
-      '  }',
-      '}'
-    ].join('\n'),
-  };
-
-  const spin = h.spin('Downloading today question');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = checkError(e, resp, 200);
-    if (e) return cb(e);
-    const result = {}
-    result.titleSlug = body.data.todayRecord[0].question.titleSlug
-    result.questionId = body.data.todayRecord[0].question.questionId
-    result.fid = body.data.todayRecord[0].question.questionFrontendId
-    result.date = body.data.todayRecord[0].data
-    result.userStatus = body.data.todayRecord[0].userStatus
-    return cb(null, result);
-  });
-};
-
-plugin.getUserContestP = function (username, cb) {
-  log.debug('running leetcode.cn.getUserContest');
-
-
-  // config.sys.urls.noj_go = 'https://leetcode.cn/graphql/noj-go/'
-  // config.sys.urls.u = 'https://leetcode.cn/u/$username/'
-
-  const opts = makeOpts(config.sys.urls.noj_go);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = config.sys.urls.u.replace('$username', username);
-
-  opts.json = true;
-  opts.body = {
-    variables: {
-      userSlug: username
-    },
-    query: [
-      '        query userContestRankingInfo($userSlug: String!) {',
-      '          userContestRanking(userSlug: $userSlug) {',
-      '            attendedContestsCount',
-      '            rating',
-      '            globalRanking',
-      '            localRanking',
-      '            globalTotalParticipants',
-      '            localTotalParticipants',
-      '            topPercentage',
-      '        }',
-      // '      userContestRankingHistory(userSlug: $userSlug) {',
-      // '            attended',
-      // '            totalProblems',
-      // '            trendingDirection',
-      // '            finishTimeInSeconds',
-      // '            rating',
-      // '            score',
-      // '            ranking',
-      // '            contest {',
-      // '              title',
-      // '              titleCn',
-      // '              startTime',
-      // '            }',
-      // '        }',
-      '    }'
-    ].join('\n'),
-  };
-
-  const spin = h.spin('Downloading userContest');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    return cb(null, body.data);
-  });
-};
-
-plugin.getTestApi = function (value, cb) {
-  log.debug('running leetcode.cn.getTestApi');
-
-  const opts = makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-
-  const value_array = value.split("-")
-
-  opts.json = true;
-  opts.body = {
-    variables: {
-      categorySlug: "",
-      skip: value_array[0],
-      limit: value_array[1],
-      filters: {},
-    },
-    query: [
-      '    query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) {',
-      '      problemsetQuestionList(',
-      '        categorySlug: $categorySlug',
-      '        limit: $limit',
-      '        skip: $skip',
-      '        filters: $filters',
-      '      ) {',
-      '        hasMore',
-      '        total',
-      '        questions {',
-      '          frontendQuestionId',
-      '          topicTags {',
-      '            slug',
-      '          }',
-      '        }',
-      '       }',
-      '  }',
-    ].join('\n'),
-  };
-
-  const spin = h.spin('Downloading ');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = checkError(e, resp, 200);
-    if (e) return cb(e);
-    let result = {}
-    body.data.problemsetQuestionList.questions.forEach(element => {
-      result[element.frontendQuestionId] = {
-        topicTags: element.topicTags.map(function (p) { return p.slug; }),
-        CompanyTags: element.extra.topCompanyTags.map(function (p) { return p.slug; }),
-      }
-    })
-    return cb(null, result);
-  });
-};
-
-module.exports = plugin;
diff --git a/src/vsc-leetcode-cli/lib/plugins/leetcode.js b/src/vsc-leetcode-cli/lib/plugins/leetcode.js
deleted file mode 100644
index a642bd2..0000000
--- a/src/vsc-leetcode-cli/lib/plugins/leetcode.js
+++ /dev/null
@@ -1,711 +0,0 @@
-'use strict';
-var util = require('util');
-
-var _ = require('underscore');
-var request = require('request');
-var prompt = require('prompt');
-
-var config = require('../config');
-var h = require('../helper');
-var file = require('../file');
-var log = require('../log');
-var Plugin = require('../plugin');
-var Queue = require('../queue');
-var session = require('../session');
-
-const plugin = new Plugin(10, 'leetcode', '',
-  'Plugin to talk with leetcode APIs.');
-
-var spin;
-
-// update options with user credentials
-plugin.signOpts = function (opts, user) {
-  opts.headers.Cookie = 'LEETCODE_SESSION=' + user.sessionId +
-    ';csrftoken=' + user.sessionCSRF + ';';
-  opts.headers['X-CSRFToken'] = user.sessionCSRF;
-  opts.headers['X-Requested-With'] = 'XMLHttpRequest';
-};
-
-plugin.makeOpts = function (url) {
-  const opts = {};
-  opts.url = url;
-  opts.headers = {};
-
-  if (session.isLogin())
-    plugin.signOpts(opts, session.getUser());
-  return opts;
-};
-
-plugin.checkError = function (e, resp, expectedStatus) {
-  if (!e && resp && resp.statusCode !== expectedStatus) {
-    const code = resp.statusCode;
-    log.debug('http error: ' + code);
-
-    if (code === 403 || code === 401) {
-      e = session.errors.EXPIRED;
-    } else {
-      e = { msg: 'http error', statusCode: code };
-    }
-  }
-  return e;
-};
-
-plugin.init = function () {
-  config.app = 'leetcode';
-};
-
-plugin.getProblems = function (needTranslation, cb) {
-  log.debug('running leetcode.getProblems');
-  let problems = [];
-  const getCategory = function (category, queue, cb) {
-    plugin.getCategoryProblems(category, function (e, _problems) {
-      if (e) {
-        log.debug(category + ': failed to getProblems: ' + e.msg);
-      } else {
-        log.debug(category + ': getProblems got ' + _problems.length + ' problems');
-        problems = problems.concat(_problems);
-      }
-      return cb(e);
-    });
-  };
-
-  spin = h.spin('Downloading problems');
-  const q = new Queue(config.sys.categories, {}, getCategory);
-  q.run(null, function (e) {
-    spin.stop();
-    return cb(e, problems);
-  });
-};
-
-plugin.getCategoryProblems = function (category, cb) {
-  log.debug('running leetcode.getCategoryProblems: ' + category);
-  const opts = plugin.makeOpts(config.sys.urls.problems.replace('$category', category));
-
-  spin.text = 'Downloading category ' + category;
-  request(opts, function (e, resp, body) {
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    const json = JSON.parse(body);
-
-    // leetcode permits anonymous access to the problem list
-    // while we require login first to make a better experience.
-    if (json.user_name.length === 0) {
-      log.debug('no user info in list response, maybe session expired...');
-      return cb(session.errors.EXPIRED);
-    }
-
-    const problems = json.stat_status_pairs
-      .filter((p) => !p.stat.question__hide)
-      .map(function (p) {
-        return {
-          state: p.status || 'None',
-          id: p.stat.question_id,
-          fid: p.stat.frontend_question_id,
-          name: p.stat.question__title,
-          slug: p.stat.question__title_slug,
-          link: config.sys.urls.problem.replace('$slug', p.stat.question__title_slug),
-          locked: p.paid_only,
-          percent: p.stat.total_acs * 100 / p.stat.total_submitted,
-          level: h.levelToName(p.difficulty.level),
-          starred: p.is_favor,
-          category: json.category_slug
-        };
-      });
-
-    return cb(null, problems);
-  });
-};
-
-plugin.getProblem = function (problem, needTranslation, cb) {
-  log.debug('running leetcode.getProblem');
-  const user = session.getUser();
-  if (problem.locked && !user.paid) return cb('failed to load locked problem!');
-
-  const opts = plugin.makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = problem.link;
-
-  opts.json = true;
-  opts.body = {
-    query: [
-      'query getQuestionDetail($titleSlug: String!) {',
-      '  question(titleSlug: $titleSlug) {',
-      '    content',
-      '    stats',
-      '    likes',
-      '    dislikes',
-      '    codeDefinition',
-      '    sampleTestCase',
-      '    enableRunCode',
-      '    metaData',
-      '    translatedContent',
-      '  }',
-      '}'
-    ].join('\n'),
-    variables: { titleSlug: problem.slug },
-    operationName: 'getQuestionDetail'
-  };
-
-  const spin = h.spin('Downloading ' + problem.slug);
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    const q = body.data.question;
-    if (!q) return cb('failed to load problem!');
-
-    problem.totalAC = JSON.parse(q.stats).totalAccepted;
-    problem.totalSubmit = JSON.parse(q.stats).totalSubmission;
-    problem.likes = q.likes;
-    problem.dislikes = q.dislikes;
-
-    problem.desc = (q.translatedContent && needTranslation) ? q.translatedContent : q.content;
-
-    problem.templates = JSON.parse(q.codeDefinition);
-    problem.testcase = q.sampleTestCase;
-    problem.testable = q.enableRunCode;
-    problem.templateMeta = JSON.parse(q.metaData);
-    // @si-yao: seems below property is never used.
-    // problem.discuss =  q.discussCategoryId;
-
-    return cb(null, problem);
-  });
-};
-
-function runCode(opts, problem, cb) {
-  opts.method = 'POST';
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = problem.link;
-  opts.json = true;
-  opts._delay = opts._delay || config.network.delay || 1; // in seconds
-
-  opts.body = opts.body || {};
-  _.extendOwn(opts.body, {
-    lang: problem.lang,
-    question_id: parseInt(problem.id, 10),
-    test_mode: false,
-    typed_code: file.codeData(problem.file)
-  });
-
-  const spin = h.spin('Sending code to judge');
-  request(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    if (body.error) {
-      if (!body.error.includes('too soon'))
-        return cb(body.error);
-
-      // hit 'run code too soon' error, have to wait a bit
-      log.debug(body.error);
-
-      // linear wait
-      ++opts._delay;
-      log.debug('Will retry after %d seconds...', opts._delay);
-
-      const reRun = _.partial(runCode, opts, problem, cb);
-      return setTimeout(reRun, opts._delay * 1000);
-    }
-
-    opts.json = false;
-    opts.body = null;
-
-    return cb(null, body);
-  });
-}
-
-function verifyResult(task, queue, cb) {
-  const opts = queue.ctx.opts;
-  opts.method = 'GET';
-  opts.url = config.sys.urls.verify.replace('$id', task.id);
-
-  const spin = h.spin('Waiting for judge result');
-  request(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    let result = JSON.parse(body);
-    if (result.state === 'SUCCESS') {
-      result = formatResult(result);
-      _.extendOwn(result, task);
-      queue.ctx.results.push(result);
-    } else {
-      queue.addTask(task);
-    }
-    return cb();
-  });
-}
-
-function formatResult(result) {
-  const x = {
-    ok: result.run_success,
-    lang: result.lang,
-    runtime: result.status_runtime || '',
-    runtime_percentile: result.runtime_percentile || '',
-    memory: result.status_memory || '',
-    memory_percentile: result.memory_percentile || '',
-    state: result.status_msg,
-    testcase: util.inspect(result.input || result.last_testcase || ''),
-    passed: result.total_correct || 0,
-    total: result.total_testcases || 0
-  };
-
-  x.error = _.chain(result)
-    .pick((v, k) => /_error$/.test(k) && v.length > 0)
-    .values()
-    .value();
-
-  if (/[runcode|interpret].*/.test(result.submission_id)) {
-    // It's testing
-    let output = result.code_output || [];
-    if (Array.isArray(output)) {
-      output = output.join('\n');
-    }
-    x.stdout = util.inspect(output);
-    x.answer = result.code_answer;
-    // LeetCode use 'expected_code_answer' to store the expected answer
-    x.expected_answer = result.expected_code_answer;
-  } else {
-    // It's submitting
-    x.answer = result.code_output;
-    x.expected_answer = result.expected_output;
-    x.stdout = result.std_output;
-  }
-
-  // make sure we pass eveything!
-  if (x.passed !== x.total) x.ok = false;
-  if (x.state !== 'Accepted') x.ok = false;
-  if (x.error.length > 0) x.ok = false;
-
-  return x;
-}
-
-plugin.testProblem = function (problem, cb) {
-  log.debug('running leetcode.testProblem');
-  const opts = plugin.makeOpts(config.sys.urls.test.replace('$slug', problem.slug));
-  opts.body = { data_input: problem.testcase };
-
-  runCode(opts, problem, function (e, task) {
-    if (e) return cb(e);
-
-    const tasks = [
-      { type: 'Actual', id: task.interpret_id },
-    ];
-
-    // Used by LeetCode-CN
-    if (task.interpret_expected_id) {
-      tasks.push({ type: 'Expected', id: task.interpret_expected_id });
-    }
-    const q = new Queue(tasks, { opts: opts, results: [] }, verifyResult);
-    q.run(null, function (e, ctx) {
-      return cb(e, ctx.results);
-    });
-  });
-};
-
-plugin.submitProblem = function (problem, cb) {
-  log.debug('running leetcode.submitProblem');
-  const opts = plugin.makeOpts(config.sys.urls.submit.replace('$slug', problem.slug));
-  opts.body = { judge_type: 'large' };
-
-  runCode(opts, problem, function (e, task) {
-    if (e) return cb(e);
-
-    const tasks = [{ type: 'Actual', id: task.submission_id }];
-    const q = new Queue(tasks, { opts: opts, results: [] }, verifyResult);
-    q.run(null, function (e, ctx) {
-      return cb(e, ctx.results);
-    });
-  });
-};
-
-plugin.getSubmissions = function (problem, cb) {
-  log.debug('running leetcode.getSubmissions');
-  const opts = plugin.makeOpts(config.sys.urls.submissions.replace('$slug', problem.slug));
-  opts.headers.Referer = config.sys.urls.problem.replace('$slug', problem.slug);
-
-  request(opts, function (e, resp, body) {
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    // FIXME: this only return the 1st 20 submissions, we should get next if necessary.
-    const submissions = JSON.parse(body).submissions_dump;
-    for (const submission of submissions)
-      submission.id = _.last(_.compact(submission.url.split('/')));
-
-    return cb(null, submissions);
-  });
-};
-
-plugin.getSubmission = function (submission, cb) {
-  log.debug('running leetcode.getSubmission');
-  const opts = plugin.makeOpts(config.sys.urls.submission.replace('$id', submission.id));
-
-  request(opts, function (e, resp, body) {
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    let re = body.match(/submissionCode:\s('[^']*')/);
-    if (re) submission.code = eval(re[1]);
-
-    re = body.match(/runtimeDistributionFormatted:\s('[^']+')/);
-    if (re) submission.distributionChart = JSON.parse(eval(re[1]));
-    return cb(null, submission);
-  });
-};
-
-plugin.starProblem = function (problem, starred, cb) {
-  log.debug('running leetcode.starProblem');
-  const user = session.getUser();
-  const operationName = starred ? 'addQuestionToFavorite' : 'removeQuestionFromFavorite';
-  const opts = plugin.makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = problem.link;
-
-  opts.json = true;
-  opts.body = {
-    query: `mutation ${operationName}($favoriteIdHash: String!, $questionId: String!) {\n  ${operationName}(favoriteIdHash: $favoriteIdHash, questionId: $questionId) {\n    ok\n    error\n    favoriteIdHash\n    questionId\n    __typename\n  }\n}\n`,
-    variables: { favoriteIdHash: user.hash, questionId: '' + problem.id },
-    operationName: operationName
-  };
-
-  const spin = h.spin(starred ? 'star' : 'unstar' + 'problem');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-    return cb(null, starred);
-  });
-};
-
-plugin.getFavorites = function (cb) {
-  log.debug('running leetcode.getFavorites');
-  const opts = plugin.makeOpts(config.sys.urls.favorites);
-
-  const spin = h.spin('Retrieving user favorites');
-  request(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    const favorites = JSON.parse(body);
-    return cb(null, favorites);
-  });
-};
-
-plugin.getUserInfo = function (cb) {
-  log.debug('running leetcode.getUserInfo');
-  const opts = plugin.makeOpts(config.sys.urls.graphql);
-  opts.headers.Origin = config.sys.urls.base;
-  opts.headers.Referer = config.sys.urls.base;
-  opts.json = true;
-  opts.body = {
-    query: [
-      '{',
-      '  user {',
-      '    username',
-      '    isCurrentUserPremium',
-      '  }',
-      '}'
-    ].join('\n'),
-    variables: {}
-  };
-
-  const spin = h.spin('Retrieving user profile');
-  request.post(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    const user = body.data.user;
-    return cb(null, user);
-  });
-};
-
-function runSession(method, data, cb) {
-  const opts = plugin.makeOpts(config.sys.urls.session);
-  opts.json = true;
-  opts.method = method;
-  opts.body = data;
-
-  const spin = h.spin('Waiting session result');
-  request(opts, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e && e.statusCode === 302) e = session.errors.EXPIRED;
-
-    return e ? cb(e) : cb(null, body.sessions);
-  });
-}
-
-plugin.getSessions = function (cb) {
-  log.debug('running leetcode.getSessions');
-  runSession('POST', {}, cb);
-};
-
-plugin.activateSession = function (session, cb) {
-  log.debug('running leetcode.activateSession');
-  const data = { func: 'activate', target: session.id };
-  runSession('PUT', data, cb);
-};
-
-plugin.createSession = function (name, cb) {
-  log.debug('running leetcode.createSession');
-  const data = { func: 'create', name: name };
-  runSession('PUT', data, cb);
-};
-
-plugin.deleteSession = function (session, cb) {
-  log.debug('running leetcode.deleteSession');
-  const data = { target: session.id };
-  runSession('DELETE', data, cb);
-};
-
-plugin.signin = function (user, cb) {
-  const isCN = config.app === 'leetcode.cn';
-  const spin = isCN ? h.spin('Signing in leetcode.cn') : h.spin('Signing in leetcode.com');
-  request(config.sys.urls.login, function (e, resp, body) {
-    spin.stop();
-    e = plugin.checkError(e, resp, 200);
-    if (e) return cb(e);
-
-    user.loginCSRF = h.getSetCookieValue(resp, 'csrftoken');
-
-    const opts = {
-      url: config.sys.urls.login,
-      headers: {
-        Origin: config.sys.urls.base,
-        Referer: config.sys.urls.login,
-        Cookie: 'csrftoken=' + user.loginCSRF + ';'
-      },
-      form: {
-        csrfmiddlewaretoken: user.loginCSRF,
-        login: user.login,
-        password: user.pass
-      }
-    };
-    request.post(opts, function (e, resp, body) {
-      if (e) return cb(e);
-      if (resp.statusCode !== 302) return cb('invalid password?');
-
-      user.sessionCSRF = h.getSetCookieValue(resp, 'csrftoken');
-      user.sessionId = h.getSetCookieValue(resp, 'LEETCODE_SESSION');
-      session.saveUser(user);
-      return cb(null, user);
-    });
-  });
-};
-
-plugin.getUser = function (user, cb) {
-  plugin.getFavorites(function (e, favorites) {
-    if (!e) {
-      const f = favorites.favorites.private_favorites.find((f) => f.name === 'Favorite');
-      if (f) {
-        user.hash = f.id_hash;
-        user.name = favorites.user_name;
-      } else {
-        log.warn('Favorite not found?');
-      }
-    } else {
-      log.warn('Failed to retrieve user favorites: ' + e);
-    }
-
-    plugin.getUserInfo(function (e, _user) {
-      if (!e) {
-        user.paid = _user.isCurrentUserPremium;
-        user.name = _user.username;
-      }
-      session.saveUser(user);
-      return cb(null, user);
-    });
-  });
-};
-
-plugin.login = function (user, cb) {
-  log.debug('running leetcode.login');
-  plugin.signin(user, function (e, user) {
-    if (e) return cb(e);
-    plugin.getUser(user, cb);
-  });
-};
-
-function parseCookie(cookie, body, cb) {
-  const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
-  const csrfPattern = /csrftoken=(.+?)(;|$)/;
-  const reCsrfResult = csrfPattern.exec(cookie);
-  const reSessionResult = SessionPattern.exec(cookie);
-  if (reSessionResult === null || reCsrfResult === null) {
-    return cb('invalid cookie?');
-  }
-  return {
-    sessionId: reSessionResult[1],
-    sessionCSRF: reCsrfResult[1],
-  };
-}
-
-function requestLeetcodeAndSave(request, leetcodeUrl, user, cb) {
-  request.get({ url: leetcodeUrl }, function (e, resp, body) {
-    const redirectUri = resp.request.uri.href;
-    if (redirectUri !== config.sys.urls.leetcode_redirect) {
-      return cb('Login failed. Please make sure the credential is correct.');
-    }
-    const cookieData = parseCookie(resp.request.headers.cookie, body, cb);
-    user.sessionId = cookieData.sessionId;
-    user.sessionCSRF = cookieData.sessionCSRF;
-    session.saveUser(user);
-    plugin.getUser(user, cb);
-  });
-}
-
-plugin.cookieLogin = function (user, cb) {
-  const cookieData = parseCookie(user.cookie, cb);
-  user.sessionId = cookieData.sessionId;
-  user.sessionCSRF = cookieData.sessionCSRF;
-  session.saveUser(user);
-  plugin.getUser(user, cb);
-};
-
-plugin.githubLogin = function (user, cb) {
-  const urls = config.sys.urls;
-  const leetcodeUrl = urls.github_login;
-  const _request = request.defaults({ jar: true });
-  _request(urls.github_login_request, function (e, resp, body) {
-    const authenticityToken = body.match(/name="authenticity_token" value="(.*?)"/);
-    let gaId = body.match(/name="ga_id" value="(.*?)"/);
-    if (!gaId) {
-      gaId = '';
-    }
-    let requiredField = body.match(/name="required_field_(.*?)"/);
-    const timestamp = body.match(/name="timestamp" value="(.*?)"/);
-    const timestampSecret = body.match(/name="timestamp_secret" value="(.*?)"/);
-
-    if (!(authenticityToken && timestamp && timestampSecret && requiredField)) {
-      return cb('Get GitHub payload failed');
-    }
-    requiredField = 'required_field_' + requiredField[1];
-    const options = {
-      url: urls.github_session_request,
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/x-www-form-urlencoded',
-      },
-      followAllRedirects: true,
-      form: {
-        'login': user.login,
-        'password': user.pass,
-        'authenticity_token': authenticityToken[1],
-        'commit': encodeURIComponent('Sign in'),
-        'ga_id': gaId,
-        'webauthn-support': 'supported',
-        'webauthn-iuvpaa-support': 'unsupported',
-        'return_to': '',
-        'requiredField': '',
-        'timestamp': timestamp[1],
-        'timestamp_secret': timestampSecret[1],
-      },
-    };
-    _request(options, function (e, resp, body) {
-      if (resp.statusCode !== 200) {
-        return cb('GitHub login failed');
-      }
-      if (resp.request.uri.href !== urls.github_tf_redirect) {
-        return requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
-      }
-      prompt.colors = false;
-      prompt.message = '';
-      prompt.start();
-      prompt.get([
-        {
-          name: 'twoFactorCode',
-          required: true
-        }
-      ], function (e, result) {
-        if (e) return log.fail(e);
-        const authenticityTokenTwoFactor = body.match(/name="authenticity_token" value="(.*?)"/);
-        if (authenticityTokenTwoFactor === null) {
-          return cb('Get GitHub two-factor token failed');
-        }
-        const optionsTwoFactor = {
-          url: urls.github_tf_session_request,
-          method: 'POST',
-          headers: {
-            'Content-Type': 'application/x-www-form-urlencoded',
-          },
-          followAllRedirects: true,
-          form: {
-            'otp': result.twoFactorCode,
-            'authenticity_token': authenticityTokenTwoFactor[1],
-            'utf8': encodeURIComponent('✓'),
-          },
-        };
-        _request(optionsTwoFactor, function (e, resp, body) {
-          if (resp.request.uri.href === urls.github_tf_session_request) {
-            return cb('Invalid two-factor code please check');
-          }
-          requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
-        });
-      });
-    });
-  });
-};
-
-plugin.linkedinLogin = function (user, cb) {
-  const urls = config.sys.urls;
-  const leetcodeUrl = urls.linkedin_login;
-  const _request = request.defaults({
-    jar: true,
-    headers: {
-      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
-    }
-  });
-  _request(urls.linkedin_login_request, function (e, resp, body) {
-    if (resp.statusCode !== 200) {
-      return cb('Get LinkedIn session failed');
-    }
-    const csrfToken = body.match(/input type="hidden" name="csrfToken" value="(.*?)"/);
-    const loginCsrfToken = body.match(/input type="hidden" name="loginCsrfParam" value="(.*?)"/);
-    const sIdString = body.match(/input type="hidden" name="sIdString" value="(.*?)"/);
-    const pageInstance = body.match(/input type="hidden" name="pageInstance" value="(.*?)"/);
-    if (!(csrfToken && loginCsrfToken && sIdString && pageInstance)) {
-      return cb('Get LinkedIn payload failed');
-    }
-    const options = {
-      url: urls.linkedin_session_request,
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/x-www-form-urlencoded',
-      },
-      followAllRedirects: true,
-      form: {
-        'csrfToken': csrfToken[1],
-        'session_key': user.login,
-        'ac': 2,
-        'sIdString': sIdString[1],
-        'parentPageKey': 'd_checkpoint_lg_consumerLogin',
-        'pageInstance': pageInstance[1],
-        'trk': 'public_profile_nav-header-signin',
-        'authUUID': '',
-        'session_redirect': 'https://www.linkedin.com/feed/',
-        'loginCsrfParam': loginCsrfToken[1],
-        'fp_data': 'default',
-        '_d': 'd',
-        'showGoogleOneTapLogin': true,
-        'controlId': 'd_checkpoint_lg_consumerLogin-login_submit_button',
-        'session_password': user.pass,
-        'loginFlow': 'REMEMBER_ME_OPTIN'
-      },
-    };
-    _request(options, function (e, resp, body) {
-      if (resp.statusCode !== 200) {
-        return cb('LinkedIn login failed');
-      }
-      requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
-    });
-  });
-};
-
-module.exports = plugin;
diff --git a/src/vsc-leetcode-cli/lib/plugins/retry.js b/src/vsc-leetcode-cli/lib/plugins/retry.js
deleted file mode 100644
index d990ac3..0000000
--- a/src/vsc-leetcode-cli/lib/plugins/retry.js
+++ /dev/null
@@ -1,84 +0,0 @@
-'use strict';
-var config = require('../config');
-var log = require('../log');
-var Plugin = require('../plugin');
-var session = require('../session');
-
-var plugin = new Plugin(30, 'retry', '',
-  'Plugin to retry last failed request if autologin.enable is on.');
-
-const count = {};
-
-function canRetry(e, name) {
-  return config.autologin.enable &&
-    (e === session.errors.EXPIRED) &&
-    (count[name] || 0) < config.autologin.retry;
-}
-
-plugin.init = function () {
-  const names = [
-    'activateSession',
-    'createSession',
-    'deleteSession',
-    'getProblems',
-    'getProblem',
-    'getSessions',
-    'getSubmissions',
-    'getSubmission',
-    'getFavorites',
-    'testProblem',
-    'submitProblem',
-    'starProblem'
-  ];
-
-  for (let name of names) {
-    count[name] = 0;
-    plugin[name] = function () {
-      const args = Array.from(arguments);
-      const cb = args.pop();
-
-      const _cb = function () {
-        const results = Array.from(arguments);
-        const e = results[0];
-        if (!canRetry(e, name)) {
-          count[name] = 0;
-          return cb.apply(null, results);
-        }
-
-        ++count[name];
-        plugin.relogin(function () {
-          // for now we don't care result, just blindly retry
-          plugin[name].apply(plugin, args.concat(cb));
-        });
-      };
-
-      const next = plugin.next;
-      next[name].apply(next, args.concat(_cb));
-    };
-  }
-};
-
-// leetcode.com is limiting one session alive in the same time,
-// which means once you login on web, your cli session will get
-// expired immediately. In that case we will try to re-login in
-// the backend to give a seamless user experience.
-plugin.relogin = function (cb) {
-  log.debug('session expired, try to re-login...');
-
-  const user = session.getUser();
-  if (!user) {
-    log.debug('relogin failed: no user found, please login again');
-    return cb();
-  }
-
-  this.login(user, function (e) {
-    if (e) {
-      log.debug('login failed:' + e.msg);
-    } else {
-      log.debug('login successfully, cont\'d...');
-    }
-    return cb();
-  });
-};
-
-module.exports = plugin;
diff --git a/src/vsc-leetcode-cli/lib/plugins/solution.discuss.js b/src/vsc-leetcode-cli/lib/plugins/solution.discuss.js
deleted file mode 100644
index aacd058..0000000
--- a/src/vsc-leetcode-cli/lib/plugins/solution.discuss.js
+++ /dev/null
@@ -1,101 +0,0 @@
-var request = require('request');
-
-var log = require('../log');
-var Plugin = require('../plugin');
-var session = require('../session');
-
-//
-// [Usage]
-//
-// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/solution.discuss.md
-//
-var plugin = new Plugin(200, 'solution.discuss', '',
-  'Plugin to fetch most voted solution in discussions.');
-
-var URL_DISCUSSES = 'https://leetcode.com/graphql';
-var URL_DISCUSS = 'https://leetcode.com/problems/$slug/discuss/$id';
-
-function getSolution(problem, lang, cb) {
-  if (!problem) return cb();
-
-  if (lang === 'python3') lang = 'python';
-
-  var opts = {
-    url: URL_DISCUSSES,
-    json: true,
-    body: {
-      query: [
-        'query questionTopicsList($questionId: String!, $orderBy: TopicSortingOption, $skip: Int, $query: String, $first: Int!, $tags: [String!]) {',
-        '  questionTopicsList(questionId: $questionId, orderBy: $orderBy, skip: $skip, query: $query, first: $first, tags: $tags) {',
-        '    ...TopicsList',
-        '  }',
-        '}',
-        'fragment TopicsList on TopicConnection {',
-        '  totalNum',
-        '  edges {',
-        '    node {',
-        '      id',
-        '      title',
-        '      post {',
-        '        content',
-        '        voteCount',
-        '        author {',
-        '          username',
-        '        }',
-        '      }',
-        '    }',
-        '  }',
-        '}'
-      ].join('\n'),
-
-      operationName: 'questionTopicsList',
-      variables: JSON.stringify({
-        query: '',
-        first: 1,
-        skip: 0,
-        orderBy: 'most_votes',
-        questionId: '' + problem.id,
-        tags: [lang]
-      })
-    }
-  };
-  request(opts, function (e, resp, body) {
-    if (e) return cb(e);
-    if (resp.statusCode !== 200)
-      return cb({ msg: 'http error', statusCode: resp.statusCode });
-
-    const solutions = body.data.questionTopicsList.edges;
-    const solution = solutions.length > 0 ? solutions[0].node : null;
-    return cb(null, solution);
-  });
-}
-
-plugin.getProblem = function (problem, needTranslation, cb) {
-  plugin.next.getProblem(problem, needTranslation, function (e, problem) {
-    if (e || !session.argv.solution) return cb(e, problem);
-
-    var lang = session.argv.lang;
-    getSolution(problem, lang, function (e, solution) {
-      if (e) return cb(e);
-      if (!solution) return log.error('Solution not found for ' + lang);
-
-      var link = URL_DISCUSS.replace('$slug', problem.slug).replace('$id', solution.id);
-      var content = solution.post.content.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
-
-      log.info();
-      log.info(problem.name);
-      log.info();
-      log.info(solution.title);
-      log.info();
-      log.info(link);
-      log.info();
-      log.info('* Lang:    ' + lang);
-      log.info('* Author:  ' + solution.post.author.username);
-      log.info('* Votes:   ' + solution.post.voteCount);
-      log.info();
-      log.info(content);
-    });
-  });
-};
-
-module.exports = plugin;
diff --git a/src/vsc-leetcode-cli/lib/queue.js b/src/vsc-leetcode-cli/lib/queue.js
deleted file mode 100644
index 98b66db..0000000
--- a/src/vsc-leetcode-cli/lib/queue.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-var _ = require('underscore');
-
-var config = require('./config');
-
-function Queue(tasks, ctx, onTask) {
-  this.tasks = _.clone(tasks) || [];
-  this.ctx = ctx || {};
-  this.onTask = onTask;
-  this.error = null;
-}
-
-Queue.prototype.addTask = function (task) {
-  this.tasks.push(task);
-  return this;
-};
-
-Queue.prototype.addTasks = function (tasks) {
-  this.tasks = this.tasks.concat(tasks);
-  return this;
-};
-
-Queue.prototype.run = function (concurrency, onDone) {
-  this.concurrency = concurrency || config.network.concurrency || 1;
-  this.onDone = onDone;
-
-  const self = this;
-  for (let i = 0; i < this.concurrency; ++i) {
-    setImmediate(function () { self.workerRun(); });
-  }
-};
-
-Queue.prototype.workerRun = function () {
-  // no more tasks, quit now
-  if (this.tasks.length === 0) {
-    if (--this.concurrency === 0 && this.onDone)
-      this.onDone(this.error, this.ctx);
-    return;
-  }
-
-  const task = this.tasks.shift();
-  const self = this;
-  this.onTask(task, self, function (e) {
-    if (e) self.error = e;
-
-    // TODO: could retry failed task here.
-    setImmediate(function () { self.workerRun(); });
-  });
-};
-
-module.exports = Queue;
diff --git a/src/vsc-leetcode-cli/lib/session.js b/src/vsc-leetcode-cli/lib/session.js
deleted file mode 100644
index 2623f5c..0000000
--- a/src/vsc-leetcode-cli/lib/session.js
+++ /dev/null
@@ -1,58 +0,0 @@
-'use strict';
-var moment = require('moment');
-var _ = require('underscore');
-
-var cache = require('./cache');
-var config = require('./config');
-var h = require('./helper');
-
-const session = {};
-
-session.errors = {
-  EXPIRED: {
-    msg: 'session expired, please login again',
-    statusCode: -1
-  }
-};
-
-session.getUser = function () {
-  return cache.get(h.KEYS.user);
-};
-
-session.saveUser = function (user) {
-  // when auto login enabled, have to save password to re-login later
-  // otherwise don't dump password for the sake of security.
-  const _user = _.omit(user, config.autologin.enable ? [] : ['pass']);
-  cache.set(h.KEYS.user, _user);
-};
-
-session.deleteUser = function () {
-  cache.del(h.KEYS.user);
-};
-
-session.deleteCodingSession = function () {
-  cache.del(h.KEYS.problems);
-};
-
-session.isLogin = function () {
-  return this.getUser() !== null;
-};
-
-session.updateStat = function (k, v) {
-  // TODO: use other storage if too many stat data
-  const today = moment().format('YYYY-MM-DD');
-  const stats = cache.get(h.KEYS.stat) || {};
-  const stat = stats[today] = stats[today] || {};
-
-  if (k.endsWith('.set')) {
-    const s = new Set(stat[k] || []);
-    s.add(v);
-    stat[k] = Array.from(s);
-  } else {
-    stat[k] = (stat[k] || 0) + v;
-  }
-
-  cache.set(h.KEYS.stat, stats);
-};
-
-module.exports = session;
diff --git a/src/vsc-leetcode-cli/lib/sprintf.js b/src/vsc-leetcode-cli/lib/sprintf.js
deleted file mode 100644
index c070697..0000000
--- a/src/vsc-leetcode-cli/lib/sprintf.js
+++ /dev/null
@@ -1,60 +0,0 @@
-'use strict'
-
-function len(s) {
-  let s1 = s.replace(/\u001b\[[^m]*m/g, ''); // remove color controls
-  s1 = s1.replace(/[^\x00-\xff]/g, '  '); // fix non-ascii
-  return s1.length;
-}
-
-function padLeft(s, n, c) {
-  let k = Math.max(0, n - len(s));
-  return c.repeat(k) + s;
-}
-
-function padRight(s, n, c) {
-  let k = Math.max(0, n - len(s));
-  return s + c.repeat(k);
-}
-
-function padCenter(s, n, c) {
-  let k = Math.max(0, n - len(s));
-  let r = (k - k % 2) / 2, l = k - r;
-  return c.repeat(l) + s + c.repeat(r);
-}
-
-const tsprintf = function () {
-  const args = Array.from(arguments);
-  let fmt = args.shift();
-  return fmt.replace(/%[^s%]*[s%]/g, function (s) {
-    if (s === '%%') return '%';
-
-    let x = '' + args.shift();
-    let n = 0;
-
-    s = s.slice(1, s.length - 1);
-    if (s.length > 0) {
-      switch (s[0]) {
-        case '-':
-          n = parseInt(s.slice(1)) || 0;
-          x = padRight(x, n, ' ');
-          break;
-        case '=':
-          n = parseInt(s.slice(1)) || 0;
-          x = padCenter(x, n, ' ');
-          break;
-        case '0':
-          n = parseInt(s.slice(1)) || 0;
-          x = padLeft(x, n, '0');
-          break;
-        default:
-          n = parseInt(s) || 0;
-          x = padLeft(x, n, ' ');
-          break;
-      }
-    }
-
-    return x;
-  });
-};
-
-module.exports = tsprintf;
diff --git a/src/vsc-leetcode-cli/new_lib/commands/list.ts b/src/vsc-leetcode-cli/new_lib/commands/list.ts
index ce06081..7e95189 100644
--- a/src/vsc-leetcode-cli/new_lib/commands/list.ts
+++ b/src/vsc-leetcode-cli/new_lib/commands/list.ts
@@ -45,10 +45,17 @@ class ListCommand {
     session.argv = argv;
     corePlugin.filterProblems(argv, function (e, problems) {
       if (e) return log.info(e);
-
-
-      log.info(JSON.stringify(problems));
-
+      let new_objcet: Array = [];
+      problems.forEach(element => {
+        let temp_ele: any = {}
+        for (const key in element) {
+          if (key != "link") {
+            temp_ele[key] = element[key]
+          }
+        }
+        new_objcet.push(temp_ele);
+      });
+      log.info(JSON.stringify(new_objcet));
     });
   };
 }
diff --git a/src/vsc-leetcode-cli/new_lib/commands/test.ts b/src/vsc-leetcode-cli/new_lib/commands/test.ts
index 7cd61b9..868fe65 100644
--- a/src/vsc-leetcode-cli/new_lib/commands/test.ts
+++ b/src/vsc-leetcode-cli/new_lib/commands/test.ts
@@ -167,7 +167,7 @@ class TestCommand {
       problem.lang = meta.lang;
 
       corePlugin.testProblem(problem, function (e, results) {
-        if (e) return log.info(e);
+        if (e) return log.info(JSON.stringify(e));
 
 
         results = _.sortBy(results, x => x.type);
diff --git a/src/vsc-leetcode-cli/new_lib/plugins/leetcode.cn.ts b/src/vsc-leetcode-cli/new_lib/plugins/leetcode.cn.ts
index acfa6e8..7682fbf 100644
--- a/src/vsc-leetcode-cli/new_lib/plugins/leetcode.cn.ts
+++ b/src/vsc-leetcode-cli/new_lib/plugins/leetcode.cn.ts
@@ -6,13 +6,8 @@ var request = require('request');
 
 import { config } from "../config";
 
-// import { log } from "../log";
 import { session } from "../session";
 
-
-// var plugin = new Plugin(15, 'leetcode.cn', '',
-//   'Plugin to talk with leetcode-cn APIs.');
-
 class LeetCodeCn extends MyPluginBase {
   id = 15
   name = 'leetcode.cn'
diff --git a/src/vsc-leetcode-cli/new_lib/plugins/leetcode.ts b/src/vsc-leetcode-cli/new_lib/plugins/leetcode.ts
index 7904b4f..b4de85a 100644
--- a/src/vsc-leetcode-cli/new_lib/plugins/leetcode.ts
+++ b/src/vsc-leetcode-cli/new_lib/plugins/leetcode.ts
@@ -21,10 +21,6 @@ class LeetCode extends MyPluginBase {
     super()
   }
 
-  test_sub(a) {
-    log.info(a)
-  }
-
   signOpts(opts, user) {
     opts.headers.Cookie = 'LEETCODE_SESSION=' + user.sessionId +
       ';csrftoken=' + user.sessionCSRF + ';';
@@ -59,12 +55,10 @@ class LeetCode extends MyPluginBase {
     config.app = 'leetcode';
   };
 
-  getProblems = (needTranslation, cb) => {
-    this.test_sub(needTranslation)
+  getProblems = (_, cb) => {
     var that = this;
     let problems = [];
-    const getCategory = function (category, queue, cb) {
-      that.test_sub(queue)
+    const getCategory = function (category, _, cb) {
       that.getCategoryProblems(category, function (e, _problems) {
         if (e) {
 
@@ -380,8 +374,7 @@ class LeetCode extends MyPluginBase {
 
 
     var that = this;
-    request.post(opts, function (e, resp, body) {
-      that.test_sub(body)
+    request.post(opts, function (e, resp, _) {
 
       e = that.checkError(e, resp, 200);
       if (e) return cb(e);
@@ -478,8 +471,7 @@ class LeetCode extends MyPluginBase {
     const isCN = config.app === 'leetcode.cn';
     const spin = isCN ? helper.spin('Signing in leetcode.cn') : helper.spin('Signing in leetcode.com');
     var that = this;
-    request(config.sys.urls.login, function (e, resp, body) {
-      that.test_sub(body)
+    request(config.sys.urls.login, function (e, resp, _) {
       spin.stop();
       e = that.checkError(e, resp, 200);
       if (e) return cb(e);
@@ -499,8 +491,7 @@ class LeetCode extends MyPluginBase {
           password: user.pass
         }
       };
-      request.post(opts, function (e, resp, body) {
-        that.test_sub(body)
+      request.post(opts, function (e, resp, _) {
         if (e) return cb(e);
         if (resp.statusCode !== 302) return cb('invalid password?');
 
@@ -562,9 +553,7 @@ class LeetCode extends MyPluginBase {
 
   requestLeetcodeAndSave = (request, leetcodeUrl, user, cb) => {
     var that = this;
-    request.get({ url: leetcodeUrl }, function (e, resp, body) {
-      that.test_sub(e)
-      that.test_sub(body)
+    request.get({ url: leetcodeUrl }, function (_, resp, __) {
       const redirectUri = resp.request.uri.href;
       if (redirectUri !== config.sys.urls.leetcode_redirect) {
         return cb('Login failed. Please make sure the credential is correct.');
@@ -590,9 +579,8 @@ class LeetCode extends MyPluginBase {
     const leetcodeUrl = urls.github_login;
     const _request = request.defaults({ jar: true });
     var that = this;
-    _request(urls.github_login_request, function (e, resp, body) {
-      that.test_sub(e)
-      that.test_sub(resp)
+    _request(urls.github_login_request, function (_, __, body) {
+
       const authenticityToken = body.match(/name="authenticity_token" value="(.*?)"/);
       let gaId = body.match(/name="ga_id" value="(.*?)"/);
       if (!gaId) {
@@ -627,8 +615,8 @@ class LeetCode extends MyPluginBase {
           'timestamp_secret': timestampSecret[1],
         },
       };
-      _request(options, function (e, resp, body) {
-        that.test_sub(e)
+      _request(options, function (_, resp, body) {
+
         if (resp.statusCode !== 200) {
           return cb('GitHub login failed');
         }
@@ -662,9 +650,8 @@ class LeetCode extends MyPluginBase {
               'utf8': encodeURIComponent('✓'),
             },
           };
-          _request(optionsTwoFactor, function (e, resp, body) {
-            that.test_sub(e)
-            that.test_sub(body)
+          _request(optionsTwoFactor, function (_, resp, __) {
+
             if (resp.request.uri.href === urls.github_tf_session_request) {
               return cb('Invalid two-factor code please check');
             }
@@ -685,8 +672,8 @@ class LeetCode extends MyPluginBase {
       }
     });
     var that = this;
-    _request(urls.linkedin_login_request, function (e, resp, body) {
-      that.test_sub(e)
+    _request(urls.linkedin_login_request, function (_, resp, body) {
+
       if (resp.statusCode !== 200) {
         return cb('Get LinkedIn session failed');
       }
@@ -723,9 +710,8 @@ class LeetCode extends MyPluginBase {
           'loginFlow': 'REMEMBER_ME_OPTIN'
         },
       };
-      _request(options, function (e, resp, body) {
-        that.test_sub(body)
-        that.test_sub(e)
+      _request(options, function (_, resp, __) {
+
         if (resp.statusCode !== 200) {
           return cb('LinkedIn login failed');
         }