From b72d4d69fe6b3279d3f40f00bb836696139a2fcc Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 28 Oct 2018 11:31:33 +0800 Subject: [PATCH 01/45] move plugins' specific data into separate dirs. * now leetcode/lintcode have different cache Signed-off-by: Eric Wang --- lib/cli.js | 29 ++++++++++++++++++----------- lib/helper.js | 9 +++++++-- lib/plugins/cache.js | 5 ----- lib/plugins/leetcode.js | 4 ++++ test/test_helper.js | 6 +++--- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 051bbee2..9b12bc0b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -52,9 +52,23 @@ function initLogLevel() { log.setLevel(level); } +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() { + //console.log(Plugin.getType()) var yargs = require('yargs'); h.width = yargs.terminalWidth(); yargs.commandDir('commands') @@ -73,22 +87,15 @@ cli.run = function() { }); config.init(); - cache.init(); initColor(); initIcon(); initLogLevel(); - - if (Plugin.init()) { - Plugin.save(); + initPlugins(function(e) { + if (e) return log.error(e); + cache.init(); runCommand(); - } else { - Plugin.installMissings(function(e) { - if (e) return log.error(e); - Plugin.init(); - runCommand(); - }); - } + }); }; module.exports = cli; diff --git a/lib/helper.js b/lib/helper.js index 33b9e1f3..ec6071f8 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -53,7 +53,7 @@ const h = {}; h.KEYS = { user: '../user', stat: '../stat', - plugins: '../plugins', + plugins: '../../plugins', problems: 'problems', problem: p => p.fid + '.' + p.slug + '.' + p.category }; @@ -186,8 +186,13 @@ h.getHomeDir = function() { return path.join(this.getUserHomeDir(), '.lc'); }; +h.getAppDir = function() { + const config = require('./config'); + return path.join(this.getHomeDir(), config.app || 'leetcode'); +}; + h.getCacheDir = function() { - return path.join(this.getHomeDir(), 'cache'); + return path.join(this.getAppDir(), 'cache'); }; h.getCodeDir = function(dir) { diff --git a/lib/plugins/cache.js b/lib/plugins/cache.js index efe8d231..677c6c84 100644 --- a/lib/plugins/cache.js +++ b/lib/plugins/cache.js @@ -9,11 +9,6 @@ var session = require('../session'); const plugin = new Plugin(50, 'cache', '', 'Plugin to provide local cache.'); -plugin.init = function() { - Plugin.prototype.init.call(this); - cache.init(); -}; - plugin.getProblems = function(cb) { const problems = cache.get(h.KEYS.problems); if (problems) { diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 186e00fd..6f1f78b2 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -50,6 +50,10 @@ function checkError(e, resp, expectedStatus) { return e; } +plugin.init = function() { + config.app = 'leetcode'; +} + plugin.getProblems = function(cb) { log.debug('running leetcode.getProblems'); let problems = []; diff --git a/test/test_helper.js b/test/test_helper.js index ac47664e..ccb81599 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -188,10 +188,10 @@ describe('helper', function() { assert.equal(h.getUserHomeDir(), '/home/skygragon'); assert.equal(h.getHomeDir(), '/home/skygragon/.lc'); - assert.equal(h.getCacheDir(), '/home/skygragon/.lc/cache'); - assert.equal(h.getCacheFile('xxx'), '/home/skygragon/.lc/cache/xxx.json'); + assert.equal(h.getCacheDir(), '/home/skygragon/.lc/leetcode/cache'); + assert.equal(h.getCacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); assert.equal(h.getConfigFile(), '/home/skygragon/.lc/config.json'); - assert.equal(h.getFilename('/home/skygragon/.lc/cache/xxx.json'), 'xxx'); + assert.equal(h.getFilename('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); process.env.HOME = ''; process.env.USERPROFILE = 'C:\\Users\\skygragon'; From 9f5db49f0933f19c1c383880c1f4485404869eb9 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 28 Oct 2018 12:12:25 +0800 Subject: [PATCH 02/45] Bump to 2.5.3 Signed-off-by: Eric Wang --- docs/commands.md | 4 ++-- docs/releases.md | 8 ++++++++ lib/cli.js | 1 - package.json | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 6ff28658..d8da1796 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -465,7 +465,7 @@ Display version information. Short: $ leetcode version - 2.5.2 + 2.5.3 Verbose: @@ -475,7 +475,7 @@ Verbose: | | ___ ___| |_ ___ ___ __| | ___ | |/ _ \/ _ \ __|/ __|/ _ \ / _` |/ _ \ | | __/ __/ |_ (__| (_) | (_| | __/ - |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.5.2 + |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.5.3 [Environment] Node v8.1.4 diff --git a/docs/releases.md b/docs/releases.md index e2bd6a12..4234cce3 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,14 @@ layout: default title: Release Notes --- +# 2.5.3 + +* fixes "Failed to load locked problem" issue. +* move plugin's data into separate folders: + * login info + * problems list + * problem cache + # 2.5.2 * `show` diff --git a/lib/cli.js b/lib/cli.js index 9b12bc0b..443c532c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -68,7 +68,6 @@ function initPlugins(cb) { var cli = {}; function runCommand() { - //console.log(Plugin.getType()) var yargs = require('yargs'); h.width = yargs.terminalWidth(); yargs.commandDir('commands') diff --git a/package.json b/package.json index d61ef84a..e25643b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode-cli", - "version": "2.5.2", + "version": "2.5.3", "description": "A cli tool to enjoy leetcode!", "preferGlobal": "true", "engines": { From 657c6c538a05d0104c54e2bd346700152da6b68e Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 17 Nov 2018 12:35:37 +0800 Subject: [PATCH 03/45] fixes #130: create home dir if necessary Signed-off-by: Eric Wang --- lib/cli.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cli.js b/lib/cli.js index 443c532c..be78c406 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -52,6 +52,10 @@ function initLogLevel() { log.setLevel(level); } +function initDir() { + h.mkdir(h.getHomeDir()) +} + function initPlugins(cb) { if (Plugin.init()) { Plugin.save(); @@ -90,6 +94,7 @@ cli.run = function() { initColor(); initIcon(); initLogLevel(); + initDir() initPlugins(function(e) { if (e) return log.error(e); cache.init(); From 61becf9408ea3972c26c65ec15ef4f20ed4aab36 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 17 Nov 2018 18:45:48 +0800 Subject: [PATCH 04/45] [refactor] extract into file utils Signed-off-by: Eric Wang --- lib/cache.js | 14 +++---- lib/chalk.js | 5 ++- lib/cli.js | 3 +- lib/commands/config.js | 6 +-- lib/commands/show.js | 3 +- lib/commands/submission.js | 3 +- lib/commands/submit.js | 3 +- lib/commands/test.js | 3 +- lib/commands/version.js | 6 +-- lib/config.js | 4 +- lib/core.js | 7 ++-- lib/file.js | 82 ++++++++++++++++++++++++++++++++++++++ lib/helper.js | 77 ++--------------------------------- lib/icon.js | 6 +-- lib/plugin.js | 17 ++++---- lib/plugins/leetcode.js | 3 +- test/plugins/test_cache.js | 9 +++-- test/test_cache.js | 6 +-- test/test_config.js | 6 +-- test/test_core.js | 6 +-- test/test_file.js | 58 +++++++++++++++++++++++++++ test/test_helper.js | 47 ---------------------- test/test_icon.js | 10 ++--- test/test_plugin.js | 16 ++++---- 24 files changed, 217 insertions(+), 183 deletions(-) create mode 100644 lib/file.js create mode 100644 test/test_file.js diff --git a/lib/cache.js b/lib/cache.js index a6097c2a..4d0470fe 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -2,29 +2,29 @@ var fs = require('fs'); var path = require('path'); -var h = require('./helper'); +var file = require('./file'); const cache = {}; cache.init = function() { - h.mkdir(h.getCacheDir()); + file.mkdir(file.cacheDir()); }; cache.get = function(k) { - const fullpath = h.getCacheFile(k); + const fullpath = file.cacheFile(k); if (!fs.existsSync(fullpath)) return null; return JSON.parse(fs.readFileSync(fullpath)); }; cache.set = function(k, v) { - const fullpath = h.getCacheFile(k); + const fullpath = file.cacheFile(k); fs.writeFileSync(fullpath, JSON.stringify(v)); return true; }; cache.del = function(k) { - const fullpath = h.getCacheFile(k); + const fullpath = file.cacheFile(k); if (!fs.existsSync(fullpath)) return false; fs.unlinkSync(fullpath); @@ -32,11 +32,11 @@ cache.del = function(k) { }; cache.list = function() { - return fs.readdirSync(h.getCacheDir()) + return file.list(file.cacheDir()) .filter(x => path.extname(x) === '.json') .map(function(filename) { const k = path.basename(filename, '.json'); - const stat = fs.statSync(h.getCacheFile(k)); + const stat = fs.statSync(file.cacheFile(k)); return { name: k, size: stat.size, diff --git a/lib/chalk.js b/lib/chalk.js index 090ec573..ef78e9d8 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -3,6 +3,8 @@ var _ = require('underscore'); var style = require('ansi-styles'); var supportsColor = require('supports-color'); +var file = require('./file'); + const chalk = { enabled: supportsColor.stdout, use256: supportsColor.stdout && supportsColor.stdout.has256, @@ -54,8 +56,7 @@ chalk.wrap = function(pre, post) { const bgName = x => 'bg' + x[0].toUpperCase() + x.substr(1); chalk.init = function() { - const h = require('./helper'); - for (let f of h.getCodeDirData('colors')) { + for (let f of file.listCodeDir('colors')) { const theme = {}; const data = _.extendOwn({}, DEFAULT, f.data); for (let x of _.pairs(data)) { diff --git a/lib/cli.js b/lib/cli.js index be78c406..40dc8327 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -5,6 +5,7 @@ var chalk = require('./chalk'); 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'); @@ -53,7 +54,7 @@ function initLogLevel() { } function initDir() { - h.mkdir(h.getHomeDir()) + file.mkdir(file.homeDir()) } function initPlugins(cb) { diff --git a/lib/commands/config.js b/lib/commands/config.js index 8d684939..eb8a7b6a 100644 --- a/lib/commands/config.js +++ b/lib/commands/config.js @@ -2,7 +2,7 @@ var _ = require('underscore'); var nconf = require('nconf'); -var h = require('../helper'); +var file = require('../file'); var chalk = require('../chalk'); var config = require('../config'); var log = require('../log'); @@ -55,12 +55,12 @@ function loadConfig(showall) { } function saveConfig() { - require('fs').writeFileSync(h.getConfigFile(), prettyConfig(loadConfig(false))); + require('fs').writeFileSync(file.configFile(), prettyConfig(loadConfig(false))); } cmd.handler = function(argv) { session.argv = argv; - nconf.file('local', h.getConfigFile()); + nconf.file('local', file.configFile()); // show all if (argv.key.length === 0) diff --git a/lib/commands/show.js b/lib/commands/show.js index d930740f..6cc51bed 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -6,6 +6,7 @@ var _ = require('underscore'); var childProcess = require('child_process'); var h = require('../helper'); +var file = require('../file'); var chalk = require('../chalk'); var icon = require('../icon'); var log = require('../log'); @@ -123,7 +124,7 @@ function showProblem(problem, argv) { let filename; if (argv.gen) { filename = genFileName(problem, argv); - h.mkdir(argv.outdir); + file.mkdir(argv.outdir); fs.writeFileSync(filename, code); if (argv.editor !== undefined) { diff --git a/lib/commands/submission.js b/lib/commands/submission.js index ff570674..c9b07ce3 100644 --- a/lib/commands/submission.js +++ b/lib/commands/submission.js @@ -4,6 +4,7 @@ var fs = require('fs'); var sprintf = require('sprintf-js').sprintf; var h = require('../helper'); +var file = require('../file'); var chalk = require('../chalk'); var log = require('../log'); var Queue = require('../queue'); @@ -99,7 +100,7 @@ function exportSubmission(problem, argv, cb) { submission.ac ? 'ac' : 'notac', h.langToExt(submission.lang)); - h.mkdir(argv.outdir); + file.mkdir(argv.outdir); // skip the existing cached submissions if (fs.existsSync(f)) return cb(null, chalk.underline(f)); diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 297084f7..fd30cc53 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -3,6 +3,7 @@ var fs = require('fs'); var util = require('util'); var h = require('../helper'); +var file = require('../file'); var chalk = require('../chalk'); var log = require('../log'); var core = require('../core'); @@ -48,7 +49,7 @@ cmd.handler = function(argv) { // use the 1st section in filename as keyword // e.g. two-sum.cpp, or two-sum.78502271.ac.cpp - const keyword = h.getFilename(argv.filename).split('.')[0]; + const keyword = file.name(argv.filename).split('.')[0]; core.getProblem(keyword, function(e, problem) { if (e) return log.fail(e); diff --git a/lib/commands/test.js b/lib/commands/test.js index ff320548..0410ee58 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -3,6 +3,7 @@ var fs = require('fs'); var _ = require('underscore'); var h = require('../helper'); +var file = require('../file'); var chalk = require('../chalk'); var log = require('../log'); var core = require('../core'); @@ -58,7 +59,7 @@ function runTest(argv) { // use the 1st section in filename as keyword // e.g. two-sum.cpp, or two-sum.78502271.ac.cpp - const keyword = h.getFilename(argv.filename).split('.')[0]; + const keyword = file.name(argv.filename).split('.')[0]; core.getProblem(keyword, function(e, problem) { if (e) return log.fail(e); diff --git a/lib/commands/version.js b/lib/commands/version.js index 684166a7..257f631a 100644 --- a/lib/commands/version.js +++ b/lib/commands/version.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('underscore'); +var file = require('../file'); var chalk = require('../chalk'); var icon = require('../icon'); var log = require('../log'); @@ -50,15 +51,14 @@ cmd.handler = function(argv) { ].join('\n'); log.info(logo); - const h = require('../helper'); const os = require('os'); const config = require('../config'); log.info('\n[Environment]'); printLine('Node', process.version); printLine('OS', os.platform() + ' ' + os.release()); - printLine('Cache', h.getCacheDir()); - printLine('Config', h.getConfigFile()); + printLine('Cache', file.cacheDir()); + printLine('Config', file.configFile()); log.info('\n[Configuration]'); _.each(config.getAll(true), function(v, k) { diff --git a/lib/config.js b/lib/config.js index 1f53cce9..21979dee 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,7 @@ var _ = require('underscore'); var nconf = require('nconf'); -var h = require('./helper'); +var file = require('./file'); const DEFAULT_CONFIG = { // usually you don't wanna change those @@ -71,7 +71,7 @@ const DEFAULT_CONFIG = { function Config() {} Config.prototype.init = function() { - nconf.file('local', h.getConfigFile()) + nconf.file('local', file.configFile()) .add('global', {type: 'literal', store: DEFAULT_CONFIG}) .defaults({}); diff --git a/lib/core.js b/lib/core.js index a1356a62..a0c3adf9 100644 --- a/lib/core.js +++ b/lib/core.js @@ -6,6 +6,7 @@ var _ = require('underscore'); 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'); @@ -122,10 +123,10 @@ core.exportProblem = function(problem, opts) { input.desc = wrap(desc).split('\n'); } - const tplfile = path.join(h.getCodeDir('templates'), opts.tpl + '.tpl'); - let output = _.template(h.getFileData(tplfile))(input); + const tplfile = path.join(file.codeDir('templates'), opts.tpl + '.tpl'); + let output = _.template(file.data(tplfile))(input); - if (h.isWindows()) { + if (file.isWindows()) { output = output.replace(/\n/g, '\r\n'); } else { output = output.replace(/\r\n/g, '\n'); diff --git a/lib/file.js b/lib/file.js new file mode 100644 index 00000000..ae148b76 --- /dev/null +++ b/lib/file.js @@ -0,0 +1,82 @@ +'use strict'; +var fs = require('fs'); +var path = require('path'); + +var mkdirp = require('mkdirp'); + +const file = {} + +file.isWindows = function() { + return process.platform === 'win32'; +}; + +/// app dirs /// +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 || ''); +}; + +/// app files /// +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}; + }); +}; + +/// general dirs & files +file.mkdir = function(fullpath) { + if (fs.existsSync(fullpath)) return; + mkdirp.sync(fullpath); +}; + +file.list = function(dir) { + return fs.readdirSync(dir); +} + +file.name = function(fullpath) { + return path.basename(fullpath, path.extname(fullpath)); +}; + +file.data = function(fullpath) { + return fs.existsSync(fullpath) ? fs.readFileSync(fullpath).toString() : null; +}; + +module.exports = file; diff --git a/lib/helper.js b/lib/helper.js index ec6071f8..47fff2d5 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -1,11 +1,9 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); - var _ = require('underscore'); -var mkdirp = require('mkdirp'); var ora = require('ora'); +var file = require('./file'); + const UNITS_SIZE = [ {unit: 'B', name: 'Bytes', count: 1024}, {unit: 'K', name: 'KBytes', count: 1024}, @@ -58,10 +56,6 @@ h.KEYS = { problem: p => p.fid + '.' + p.slug + '.' + p.category }; -h.isWindows = function() { - return process.platform === 'win32'; -}; - h.prettyState = function(state) { switch (state) { case 'ac': return this.prettyText('', true); @@ -148,81 +142,18 @@ h.langToCommentStyle = function(lang) { {start: '/*', line: ' *', end: ' */'}; }; -h.mkdir = function(fullpath) { - if (fs.existsSync(fullpath)) return; - mkdirp.sync(fullpath); -}; - -h.getCodeDirData = function(dir) { - dir = h.getCodeDir(dir); - return fs.readdirSync(dir).map(function(file) { - const fullpath = path.join(dir, file); - const ext = path.extname(file); - - const name = path.basename(file, ext); - let data = null; - - switch (ext) { - case '.js': data = require(fullpath); break; - case '.json': data = JSON.parse(h.getFileData(fullpath)); break; - } - return {name: name, data: data, file: file}; - }); -}; - -h.getFilename = function(fullpath) { - return path.basename(fullpath, path.extname(fullpath)); -}; - -h.getFileData = function(fullpath) { - return fs.existsSync(fullpath) ? fs.readFileSync(fullpath).toString() : null; -}; - -h.getUserHomeDir = function() { - return process.env.HOME || process.env.USERPROFILE; -}; - -h.getHomeDir = function() { - return path.join(this.getUserHomeDir(), '.lc'); -}; - -h.getAppDir = function() { - const config = require('./config'); - return path.join(this.getHomeDir(), config.app || 'leetcode'); -}; - -h.getCacheDir = function() { - return path.join(this.getAppDir(), 'cache'); -}; - -h.getCodeDir = function(dir) { - return path.join(__dirname, '..', dir || ''); -}; - -h.getCacheFile = function(k) { - return path.join(this.getCacheDir(), k + '.json'); -}; - -h.getConfigFile = function() { - return path.join(this.getHomeDir(), 'config.json'); -}; - -h.getPluginFile = function(name) { - return path.join(this.getCodeDir('lib/plugins'), path.basename(name)); -}; - h.readStdin = function(cb) { const stdin = process.stdin; const bufs = []; console.log('NOTE: to finish the input, press ' + - (this.isWindows() ? ' and ' : '')); + (file.isWindows() ? ' and ' : '')); stdin.on('readable', function() { const data = stdin.read(); if (data) { // windows doesn't treat ctrl-D as EOF - if (h.isWindows() && data.toString() === '\x04\r\n') { + if (file.isWindows() && data.toString() === '\x04\r\n') { stdin.emit('end'); } else { bufs.push(data); diff --git a/lib/icon.js b/lib/icon.js index af95137b..c147a792 100644 --- a/lib/icon.js +++ b/lib/icon.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('underscore'); -var h = require('./helper'); +var file = require('./file'); const icons = { yes: '✔', @@ -18,13 +18,13 @@ const icons = { }; icons.setTheme = function(name) { - const defaultName = h.isWindows() ? 'win7' : 'default'; + const defaultName = file.isWindows() ? 'win7' : 'default'; const theme = this.themes.get(name) || this.themes.get(defaultName) || {}; _.extendOwn(this, theme); }; icons.init = function() { - for (let f of h.getCodeDirData('icons')) + for (let f of file.listCodeDir('icons')) icons.themes.set(f.name, f.data); }; diff --git a/lib/plugin.js b/lib/plugin.js index 5f430ebf..be5dfe32 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -7,6 +7,7 @@ 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'); @@ -39,16 +40,16 @@ Plugin.prototype.setNext = function(next) { this.next = next; }; -Plugin.prototype.setFile = function(file) { - this.file = file; - this.enabled = (file[0] !== '.'); +Plugin.prototype.setFile = function(f) { + this.file = f; + this.enabled = (this.file[0] !== '.'); }; Plugin.prototype.enable = function(enabled) { if (this.enabled === enabled) return; const newfile = enabled ? this.file.substr(1) : '.' + this.file; try { - fs.renameSync(h.getPluginFile(this.file), h.getPluginFile(newfile)); + fs.renameSync(file.pluginFile(this.file), file.pluginFile(newfile)); } catch(e) { log.error(e.message); } @@ -58,7 +59,7 @@ Plugin.prototype.enable = function(enabled) { Plugin.prototype.delete = function() { if (!this.missing) { try { - const fullpath = h.getPluginFile(this.file); + const fullpath = file.pluginFile(this.file); fs.unlinkSync(fullpath); } catch(e) { return log.error(e.message); @@ -83,7 +84,7 @@ Plugin.prototype.install = function(cb) { const cmd = 'npm install --save ' + this.deps.join(' '); log.debug(cmd); const spin = h.spin(cmd); - cp.exec(cmd, {cwd: h.getCodeDir()}, function() { + cp.exec(cmd, {cwd: file.codeDir()}, function() { spin.stop(); return cb(); }); @@ -99,7 +100,7 @@ Plugin.init = function(head) { // 1. check installed plugins let plugins = []; - for (let f of h.getCodeDirData('lib/plugins')) { + for (let f of file.listCodeDir('lib/plugins')) { const p = f.data; if (!p) continue; @@ -146,7 +147,7 @@ Plugin.copy = function(src, cb) { if (path.extname(src) !== '.js') { src = config.sys.urls.plugin.replace('$name', src); } - const dst = h.getPluginFile(src); + const dst = file.pluginFile(src); const srcstream = src.startsWith('https://') ? request(src) : fs.createReadStream(src); srcstream.on('response', function(resp) { diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 6f1f78b2..c8563d25 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -8,6 +8,7 @@ var request = require('request'); var config = require('../config'); var h = require('../helper'); +var file = require('../file'); var log = require('../log'); var Plugin = require('../plugin'); var Queue = require('../queue'); @@ -184,7 +185,7 @@ function runCode(opts, problem, cb) { lang: h.extToLang(problem.file), question_id: parseInt(problem.id, 10), test_mode: false, - typed_code: h.getFileData(problem.file) + typed_code: file.data(problem.file) }); const spin = h.spin('Sending code to judge'); diff --git a/test/plugins/test_cache.js b/test/plugins/test_cache.js index a3980418..6b3114ca 100644 --- a/test/plugins/test_cache.js +++ b/test/plugins/test_cache.js @@ -3,6 +3,7 @@ const _ = require('underscore'); const assert = require('chai').assert; const rewire = require('rewire'); +const h = require('../../lib/helper'); const log = require('../../lib/log'); const config = require('../../lib/config'); const th = require('../helper'); @@ -11,7 +12,7 @@ describe('plugin:cache', function() { let plugin; let next; let cache; - let h; + let file; let session; const PROBLEMS = [ @@ -29,11 +30,11 @@ describe('plugin:cache', function() { th.clean(); next = {}; - h = rewire('../../lib/helper'); - h.getCacheDir = () => th.DIR; + file = rewire('../../lib/file'); + file.cacheDir = () => th.DIR; cache = rewire('../../lib/cache'); - cache.__set__('h', h); + cache.__set__('file', file); cache.init(); session = rewire('../../lib/session'); diff --git a/test/test_cache.js b/test/test_cache.js index b3fb669d..caba14c1 100644 --- a/test/test_cache.js +++ b/test/test_cache.js @@ -13,11 +13,11 @@ describe('cache', function() { beforeEach(function() { th.clean(); - const h = rewire('../lib/helper'); - h.getCacheDir = () => th.DIR; + const file = rewire('../lib/file'); + file.cacheDir = () => th.DIR; cache = rewire('../lib/cache'); - cache.__set__('h', h); + cache.__set__('file', file); cache.init(); }); diff --git a/test/test_config.js b/test/test_config.js index 6cfc0210..9ae828dd 100644 --- a/test/test_config.js +++ b/test/test_config.js @@ -12,11 +12,11 @@ describe('config', function() { beforeEach(function() { th.clean(); - const h = rewire('../lib/helper'); - h.getConfigFile = () => FILE; + const file = rewire('../lib/file'); + file.configFile = () => FILE; config = rewire('../lib/config'); - config.__set__('h', h); + config.__set__('file', file); }); function createConfigFile(data) { diff --git a/test/test_core.js b/test/test_core.js index c61031c0..1145bc69 100644 --- a/test/test_core.js +++ b/test/test_core.js @@ -171,9 +171,9 @@ describe('core', function() { }); it('should codeonly ok in windows', function() { - const h = rewire('../lib/helper'); - h.isWindows = () => true; - core.__set__('h', h); + const file = rewire('../lib/file'); + file.isWindows = () => true; + core.__set__('file', file); const expected = [ '/**', diff --git a/test/test_file.js b/test/test_file.js new file mode 100644 index 00000000..d5e3fbda --- /dev/null +++ b/test/test_file.js @@ -0,0 +1,58 @@ +'use strict'; +const path = require('path'); + +const assert = require('chai').assert; +const rewire = require('rewire'); + +describe('file', function() { + let file; + + beforeEach(function() { + file = rewire('../lib/file'); + }); + + describe('#dirAndFiles', function() { + const HOME = path.join(__dirname, '..'); + + it('should ok', function() { + process.env.HOME = '/home/skygragon'; + + assert.equal(file.userHomeDir(), '/home/skygragon'); + assert.equal(file.homeDir(), '/home/skygragon/.lc'); + assert.equal(file.cacheDir(), '/home/skygragon/.lc/leetcode/cache'); + assert.equal(file.cacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); + assert.equal(file.configFile(), '/home/skygragon/.lc/config.json'); + assert.equal(file.name('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); + + process.env.HOME = ''; + process.env.USERPROFILE = 'C:\\Users\\skygragon'; + assert.equal(file.userHomeDir(), 'C:\\Users\\skygragon'); + }); + + it('should codeDir ok', function() { + assert.equal(file.codeDir(), HOME); + assert.equal(file.codeDir('.'), HOME); + assert.equal(file.codeDir('icons'), path.join(HOME, 'icons')); + assert.equal(file.codeDir('lib/plugins'), path.join(HOME, 'lib', 'plugins')); + }); + + it('should listCodeDir ok', function() { + const files = file.listCodeDir('lib/plugins'); + assert.equal(files.length, 3); + assert.equal(files[0].name, 'cache'); + assert.equal(files[1].name, 'leetcode'); + assert.equal(files[2].name, 'retry'); + }); + + it('should pluginFile ok', function() { + const expect = path.join(HOME, 'lib/plugins/cache.js'); + assert.equal(file.pluginFile('cache.js'), expect); + assert.equal(file.pluginFile('./cache.js'), expect); + assert.equal(file.pluginFile('https://github.com/skygragon/cache.js'), expect); + }); + + it('should data ok with missing file', function() { + assert.equal(file.data('non-exist'), null); + }); + }); // #dirAndFiles +}); diff --git a/test/test_helper.js b/test/test_helper.js index ccb81599..e88349a9 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -1,6 +1,4 @@ 'use strict'; -const path = require('path'); - const assert = require('chai').assert; const rewire = require('rewire'); const _ = require('underscore'); @@ -180,51 +178,6 @@ describe('helper', function() { }); }); // #langToCommentStyle - describe('#dirAndFiles', function() { - const HOME = path.join(__dirname, '..'); - - it('should ok', function() { - process.env.HOME = '/home/skygragon'; - - assert.equal(h.getUserHomeDir(), '/home/skygragon'); - assert.equal(h.getHomeDir(), '/home/skygragon/.lc'); - assert.equal(h.getCacheDir(), '/home/skygragon/.lc/leetcode/cache'); - assert.equal(h.getCacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); - assert.equal(h.getConfigFile(), '/home/skygragon/.lc/config.json'); - assert.equal(h.getFilename('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); - - process.env.HOME = ''; - process.env.USERPROFILE = 'C:\\Users\\skygragon'; - assert.equal(h.getUserHomeDir(), 'C:\\Users\\skygragon'); - }); - - it('should getCodeDir ok', function() { - assert.equal(h.getCodeDir(), HOME); - assert.equal(h.getCodeDir('.'), HOME); - assert.equal(h.getCodeDir('icons'), path.join(HOME, 'icons')); - assert.equal(h.getCodeDir('lib/plugins'), path.join(HOME, 'lib', 'plugins')); - }); - - it('should getCodeDirData ok', function() { - const files = h.getCodeDirData('lib/plugins'); - assert.equal(files.length, 3); - assert.equal(files[0].name, 'cache'); - assert.equal(files[1].name, 'leetcode'); - assert.equal(files[2].name, 'retry'); - }); - - it('should getPluginFile ok', function() { - const expect = path.join(HOME, 'lib/plugins/cache.js'); - assert.equal(h.getPluginFile('cache.js'), expect); - assert.equal(h.getPluginFile('./cache.js'), expect); - assert.equal(h.getPluginFile('https://github.com/skygragon/cache.js'), expect); - }); - - it('should getFileData ok with missing file', function() { - assert.equal(h.getFileData('non-exist'), null); - }); - }); // #dirAndFiles - describe('#getSetCookieValue', function() { it('should ok', function() { const resp = { diff --git a/test/test_icon.js b/test/test_icon.js index 8408a2c9..559eeb51 100644 --- a/test/test_icon.js +++ b/test/test_icon.js @@ -4,11 +4,11 @@ const rewire = require('rewire'); describe('icon', function() { let icon; - let h; + let file; beforeEach(function() { - h = rewire('../lib/helper'); - h.getCodeDirData = function() { + file = rewire('../lib/file'); + file.listCodeDir = function() { return [ {name: 'mac', data: {yes: 'yes', no: 'no', lock: 'lock', like: 'like', unlike: 'unlike'}}, {name: 'win7', data: {yes: 'YES', no: 'NO', lock: 'LOCK', like: 'LIKE', unlike: 'UNLIKE'}} @@ -16,7 +16,7 @@ describe('icon', function() { }; icon = rewire('../lib/icon'); - icon.__set__('h', h); + icon.__set__('file', file); icon.init(); }); @@ -40,7 +40,7 @@ describe('icon', function() { }); it('should ok with unknown theme on windows', function() { - h.isWindows = () => true; + file.isWindows = () => true; icon.setTheme('non-exist'); assert.equal(icon.yes, 'YES'); diff --git a/test/test_plugin.js b/test/test_plugin.js index d758b9b9..a6cdf156 100644 --- a/test/test_plugin.js +++ b/test/test_plugin.js @@ -13,7 +13,7 @@ const th = require('./helper'); const Plugin = rewire('../lib/plugin'); describe('plugin', function() { - let h; + let file; let cache; const NOOP = () => {}; @@ -23,9 +23,9 @@ describe('plugin', function() { chalk.init(); config.init(); - h = rewire('../lib/helper'); + file = rewire('../lib/file'); cache = rewire('../lib/cache'); - Plugin.__set__('h', h); + Plugin.__set__('file', file); Plugin.__set__('cache', cache); }); @@ -42,7 +42,7 @@ describe('plugin', function() { before(function() { p1.init = p2.init = p3.init = p4.init = NOOP; - h.getCodeDirData = function() { + file.listCodeDir = function() { return [ {name: 'cache', data: p2, file: 'cache.js'}, {name: 'leetcode', data: p1, file: '.leetcode.js'}, // disabled @@ -123,7 +123,7 @@ describe('plugin', function() { const DST = path.resolve(th.DIR, 'copy.test.js'); before(function() { - h.getPluginFile = () => DST; + file.pluginFile = () => DST; }); it('should copy from http error', function(done) { @@ -162,7 +162,7 @@ describe('plugin', function() { beforeEach(function() { expected = []; - h.getPluginFile = x => th.DIR + x; + file.pluginFile = x => th.DIR + x; Plugin.install = (name, cb) => { expected.push(name); return cb(null, PLUGINS[+name]); @@ -183,7 +183,7 @@ describe('plugin', function() { const FILE = path.resolve(th.DIR, 'leetcode.js'); before(function() { - h.getPluginFile = () => FILE; + file.pluginFile = () => FILE; }); it('should ok', function() { @@ -208,7 +208,7 @@ describe('plugin', function() { describe('#delete', function() { it('should ok', function() { - h.getPluginFile = x => th.DIR + x; + file.pluginFile = x => th.DIR + x; const p = new Plugin(0, '0', '2018.01.01'); p.file = '0.js'; From d9e07e415a574fd1c3737964a7868f8b40da580b Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 09:52:53 +0800 Subject: [PATCH 05/45] Embed meta in comment instead of filename. refs #55 #65 Signed-off-by: Eric Wang --- lib/commands/submit.js | 7 +++--- lib/commands/test.js | 7 +++--- lib/core.js | 32 ++++++++++-------------- lib/file.js | 55 ++++++++++++++++++++++++++++++++++++++++- lib/helper.js | 12 +++------ lib/plugins/leetcode.js | 2 +- templates/detailed.tpl | 4 ++- test/test_core.js | 4 +++ test/test_file.js | 44 +++++++++++++++++++++++++++++++++ test/test_helper.js | 4 +-- 10 files changed, 129 insertions(+), 42 deletions(-) diff --git a/lib/commands/submit.js b/lib/commands/submit.js index fd30cc53..2f83d7b0 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -47,14 +47,13 @@ cmd.handler = function(argv) { if (!fs.existsSync(argv.filename)) return log.error('File ' + argv.filename + ' not exist!'); - // use the 1st section in filename as keyword - // e.g. two-sum.cpp, or two-sum.78502271.ac.cpp - const keyword = file.name(argv.filename).split('.')[0]; + const meta = file.meta(argv.filename); - core.getProblem(keyword, function(e, problem) { + core.getProblem(meta.id, 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); diff --git a/lib/commands/test.js b/lib/commands/test.js index 0410ee58..4992a06c 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -57,11 +57,9 @@ function runTest(argv) { if (!fs.existsSync(argv.filename)) return log.error('File ' + argv.filename + ' not exist!'); - // use the 1st section in filename as keyword - // e.g. two-sum.cpp, or two-sum.78502271.ac.cpp - const keyword = file.name(argv.filename).split('.')[0]; + const meta = file.meta(argv.filename); - core.getProblem(keyword, function(e, problem) { + core.getProblem(meta.id, function(e, problem) { if (e) return log.fail(e); if (!problem.testable) @@ -74,6 +72,7 @@ function runTest(argv) { return log.fail('missing testcase?'); problem.file = argv.filename; + problem.lang = meta.lang; log.info('\nInput data:'); log.info(problem.testcase); diff --git a/lib/core.js b/lib/core.js index a0c3adf9..e4d4b645 100644 --- a/lib/core.js +++ b/lib/core.js @@ -1,5 +1,4 @@ 'use strict'; -var path = require('path'); var util = require('util'); var _ = require('underscore'); @@ -107,31 +106,26 @@ core.starProblem = function(problem, starred, cb) { }; core.exportProblem = function(problem, opts) { - // copy problem attrs thus we can render it in template - const input = _.extend({}, problem); + const data = _.extend({}, problem); - input.code = opts.code.replace(/\r\n/g, '\n'); - input.comment = h.langToCommentStyle(opts.lang); - input.percent = input.percent.toFixed(2); - input.testcase = util.inspect(input.testcase || ''); + // unify format before rendering + data.app = require('./config').app; + 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') { // NOTE: wordwrap internally uses '\n' as EOL, so here we have to // remove all '\r' in the raw string. - const desc = input.desc.replace(/\r\n/g, '\n').replace(/^ /mg, '⁠'); - const wrap = require('wordwrap')(79 - input.comment.line.length); - input.desc = wrap(desc).split('\n'); + const desc = data.desc.replace(/\r\n/g, '\n').replace(/^ /mg, '⁠'); + const wrap = require('wordwrap')(79 - data.comment.line.length); + data.desc = wrap(desc).split('\n'); } - const tplfile = path.join(file.codeDir('templates'), opts.tpl + '.tpl'); - let output = _.template(file.data(tplfile))(input); - - if (file.isWindows()) { - output = output.replace(/\n/g, '\r\n'); - } else { - output = output.replace(/\r\n/g, '\n'); - } - return output; + return file.render(opts.tpl, data); }; module.exports = core; diff --git a/lib/file.js b/lib/file.js index ae148b76..1db36ca2 100644 --- a/lib/file.js +++ b/lib/file.js @@ -2,6 +2,7 @@ var fs = require('fs'); var path = require('path'); +var _ = require('underscore'); var mkdirp = require('mkdirp'); const file = {} @@ -61,7 +62,7 @@ file.listCodeDir = function(dir) { }); }; -/// general dirs & files +/// general dirs & files /// file.mkdir = function(fullpath) { if (fs.existsSync(fullpath)) return; mkdirp.sync(fullpath); @@ -79,4 +80,56 @@ file.data = function(fullpath) { return fs.existsSync(fullpath) ? fs.readFileSync(fullpath).toString() : null; }; +/// templates & metadata /// +file.render = function(tpl, data) { + const tplfile = path.join(this.codeDir('templates'), tpl + '.tpl'); + let result = _.template(this.data(tplfile))(data); + + if (this.isWindows()) { + result = result.replace(/\n/g, '\r\n'); + } else { + result = result.replace(/\r\n/g, '\n'); + } + return result; +}; + +file.metaByName = function(filename) { + const m = {}; + + // expect the 1st section in filename as id + // e.g. 1.two-sum.cpp + m.id = file.name(filename).split('.')[0]; + + // HACK: compatible with old ext + 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 = {}; + + // first look into the file data + const line = this.data(filename).split('\n') + .find(x => x.indexOf(' @lc ') >= 0) || ''; + line.split(' ').forEach(function(x) { + const v = x.split('='); + if (v.length == 2) { + m[v[0]] = v[1]; + } + }); + + // otherwise, look into file name + 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/lib/helper.js b/lib/helper.js index 47fff2d5..b4794bb1 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -40,7 +40,7 @@ const LANGS = [ {lang: 'kotlin', ext: '.kt', style: 'c'}, {lang: 'mysql', ext: '.sql', style: '#'}, {lang: 'python', ext: '.py', style: '#'}, - {lang: 'python3', ext: '.python3.py', style: '#'}, + {lang: 'python3', ext: '.py', style: '#'}, {lang: 'ruby', ext: '.rb', style: '#'}, {lang: 'scala', ext: '.scala', style: 'c'}, {lang: 'swift', ext: '.swift', style: 'c'} @@ -124,14 +124,8 @@ h.langToExt = function(lang) { }; h.extToLang = function(fullpath) { - // HACK: compatible with old ext - if (fullpath.endsWith('.py3')) return 'python3'; - - const res = _.chain(LANGS) - .filter(x => fullpath.endsWith(x.ext)) - .sortBy(x => -x.ext.length) - .value(); - return res.length ? res[0].lang : 'unknown'; + const res = LANGS.find(x => fullpath.endsWith(x.ext)); + return res ? res.lang : 'unknown'; }; h.langToCommentStyle = function(lang) { diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index c8563d25..8a3a8d8c 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -182,7 +182,7 @@ function runCode(opts, problem, cb) { opts.body = opts.body || {}; _.extendOwn(opts.body, { - lang: h.extToLang(problem.file), + lang: problem.lang, question_id: parseInt(problem.id, 10), test_mode: false, typed_code: file.data(problem.file) diff --git a/templates/detailed.tpl b/templates/detailed.tpl index b8a762e2..6758aa10 100644 --- a/templates/detailed.tpl +++ b/templates/detailed.tpl @@ -1,5 +1,7 @@ <%= comment.start %> -<%= comment.line %> [<%= id %>] <%= name %> +<%= comment.line %> @lc app=<%= app %> id=<%= fid %> lang=<%= lang %> +<%= comment.line %> +<%= comment.line %> [<%= fid %>] <%= name %> <%= comment.line %> <%= comment.line %> <%= link %> <%= comment.line %> diff --git a/test/test_core.js b/test/test_core.js index 1145bc69..5932cd4d 100644 --- a/test/test_core.js +++ b/test/test_core.js @@ -205,6 +205,8 @@ describe('core', function() { it('should detailed ok with cpp', function() { const expected = [ '/*', + ' * @lc app=leetcode id=2 lang=cpp', + ' *', ' * [2] Add Two Numbers', ' *', ' * https://leetcode.com/problems/add-two-numbers', @@ -250,6 +252,8 @@ describe('core', function() { it('should detailed ok with ruby', function() { const expected = [ + '#', + '# @lc app=leetcode id=2 lang=ruby', '#', '# [2] Add Two Numbers', '#', diff --git a/test/test_file.js b/test/test_file.js index d5e3fbda..481fa9d3 100644 --- a/test/test_file.js +++ b/test/test_file.js @@ -55,4 +55,48 @@ describe('file', function() { assert.equal(file.data('non-exist'), null); }); }); // #dirAndFiles + + describe('#meta', function() { + it('should meta ok within file content', function() { + file.data = x => [ + '/ *', + ' * @lc app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + const meta = file.meta('dummy'); + assert.equal(meta.app, 'leetcode') + assert.equal(meta.id, '123'); + assert.equal(meta.lang, 'javascript'); + }); + + it('should meta ok within file name', function() { + file.data = x => [ + '/ *', + ' * no meta app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + const meta = file.meta('321.dummy.py'); + assert(!meta.app) + assert.equal(meta.id, '321'); + assert.equal(meta.lang, 'python'); + }); + + it('should meta ok within deprecated file name', function() { + file.data = x => [ + '/ *', + ' * no meta app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + + var meta = file.meta('111.dummy.py3'); + assert(!meta.app) + assert.equal(meta.id, '111'); + assert.equal(meta.lang, 'python3'); + + meta = file.meta('222.dummy.python3.py'); + assert(!meta.app) + assert.equal(meta.id, '222'); + assert.equal(meta.lang, 'python3'); + }); + }); // #meta }); diff --git a/test/test_helper.js b/test/test_helper.js index e88349a9..8b6f64a6 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -129,7 +129,7 @@ describe('helper', function() { assert.equal(h.langToExt('javascript'), '.js'); assert.equal(h.langToExt('mysql'), '.sql'); assert.equal(h.langToExt('python'), '.py'); - assert.equal(h.langToExt('python3'), '.python3.py'); + assert.equal(h.langToExt('python3'), '.py'); assert.equal(h.langToExt('ruby'), '.rb'); assert.equal(h.langToExt('scala'), '.scala'); assert.equal(h.langToExt('swift'), '.swift'); @@ -147,8 +147,6 @@ describe('helper', function() { assert.equal(h.extToLang('file.java'), 'java'); assert.equal(h.extToLang('c:/file.js'), 'javascript'); assert.equal(h.extToLang('c:/Users/skygragon/file.py'), 'python'); - assert.equal(h.extToLang('c:/Users/skygragon/file.py3'), 'python3'); - assert.equal(h.extToLang('c:/Users/skygragon/file.python3.py'), 'python3'); assert.equal(h.extToLang('~/file.rb'), 'ruby'); assert.equal(h.extToLang('/tmp/file.scala'), 'scala'); assert.equal(h.extToLang('~/leetcode/file.swift'), 'swift'); From aca460901ea15fda7041f4ddbf2f7f3b83db78ce Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 10:10:37 +0800 Subject: [PATCH 06/45] Update dev dependecies. Signed-off-by: Eric Wang --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e25643b8..bffa63d2 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,12 @@ "yargs": "10.0.3" }, "devDependencies": { - "chai": "4.1.2", - "eslint": "4.13.1", - "eslint-config-google": "0.9.1", - "mocha": "4.0.1", - "nock": "9.1.4", - "nyc": "11.3.0", - "rewire": "2.5.2" + "chai": "4.2.0", + "eslint": "5.9.0", + "eslint-config-google": "0.11.0", + "mocha": "5.2.0", + "nock": "10.0.2", + "nyc": "13.1.0", + "rewire": "4.0.1" } } From c20b0d9a567777a22e09312f60064e4ef73c40a9 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 10:15:58 +0800 Subject: [PATCH 07/45] Update dependencies. Signed-off-by: Eric Wang --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index bffa63d2..a188e57a 100644 --- a/package.json +++ b/package.json @@ -33,20 +33,20 @@ }, "homepage": "https://github.com/skygragon/leetcode-cli#readme", "dependencies": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "cheerio": "0.20.0", - "he": "1.1.1", + "he": "1.2.0", "mkdirp": "0.5.1", "moment": "^2.20.1", "nconf": "0.10.0", - "ora": "^1.3.0", + "ora": "3.0.0", "prompt": "1.0.0", - "request": "2.83.0", + "request": "2.88.0", "sprintf-js": "1.1.1", - "supports-color": "5.1.0", - "underscore": "1.8.3", + "supports-color": "5.5.0", + "underscore": "1.9.1", "wordwrap": "1.0.0", - "yargs": "10.0.3" + "yargs": "12.0.4" }, "devDependencies": { "chai": "4.2.0", From 045528eada8b995a8e9be90edaf2613e5213d6aa Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 10:46:01 +0800 Subject: [PATCH 08/45] Bump to 2.5.4 Signed-off-by: Eric Wang --- docs/commands.md | 4 ++-- docs/releases.md | 5 +++++ package.json | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index d8da1796..e3270b44 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -465,7 +465,7 @@ Display version information. Short: $ leetcode version - 2.5.3 + 2.5.4 Verbose: @@ -475,7 +475,7 @@ Verbose: | | ___ ___| |_ ___ ___ __| | ___ | |/ _ \/ _ \ __|/ __|/ _ \ / _` |/ _ \ | | __/ __/ |_ (__| (_) | (_| | __/ - |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.5.3 + |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.5.4 [Environment] Node v8.1.4 diff --git a/docs/releases.md b/docs/releases.md index 4234cce3..1794ecb5 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,11 @@ layout: default title: Release Notes --- +# 2.5.4 +* fixes error in fresh env without .lc existed. +* embed meta in file content instead of file name. +* update dependencies. + # 2.5.3 * fixes "Failed to load locked problem" issue. diff --git a/package.json b/package.json index a188e57a..28bdfef8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode-cli", - "version": "2.5.3", + "version": "2.5.4", "description": "A cli tool to enjoy leetcode!", "preferGlobal": "true", "engines": { From fb5f5973a8614ad198eba0b9716998d35493ae8c Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 11:09:24 +0800 Subject: [PATCH 09/45] fixes #112: use "--" as comment in sql file Signed-off-by: Eric Wang --- lib/helper.js | 8 ++++---- test/test_helper.js | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/helper.js b/lib/helper.js index b4794bb1..5ca496aa 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -38,7 +38,7 @@ const LANGS = [ {lang: 'java', ext: '.java', style: 'c'}, {lang: 'javascript', ext: '.js', style: 'c'}, {lang: 'kotlin', ext: '.kt', style: 'c'}, - {lang: 'mysql', ext: '.sql', style: '#'}, + {lang: 'mysql', ext: '.sql', style: '--'}, {lang: 'python', ext: '.py', style: '#'}, {lang: 'python3', ext: '.py', style: '#'}, {lang: 'ruby', ext: '.rb', style: '#'}, @@ -131,9 +131,9 @@ h.extToLang = function(fullpath) { h.langToCommentStyle = function(lang) { const res = LANGS.find(x => x.lang === lang); - return (res && res.style === '#') ? - {start: '#', line: '#', end: '#'} : - {start: '/*', line: ' *', end: ' */'}; + return (res && res.style === 'c') ? + {start: '/*', line: ' *', end: ' */'} : + {start: res.style, line: res.style, end: res.style}; }; h.readStdin = function(cb) { diff --git a/test/test_helper.js b/test/test_helper.js index 8b6f64a6..cfc502d4 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -159,6 +159,7 @@ describe('helper', function() { it('should ok', function() { const C_STYLE = {start: '/*', line: ' *', end: ' */'}; const RUBY_STYLE = {start: '#', line: '#', end: '#'}; + const SQL_STYLE = {start: '--', line: '--', end: '--'}; assert.deepEqual(h.langToCommentStyle('bash'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('c'), C_STYLE); @@ -167,7 +168,7 @@ describe('helper', function() { assert.deepEqual(h.langToCommentStyle('golang'), C_STYLE); assert.deepEqual(h.langToCommentStyle('java'), C_STYLE); assert.deepEqual(h.langToCommentStyle('javascript'), C_STYLE); - assert.deepEqual(h.langToCommentStyle('mysql'), RUBY_STYLE); + assert.deepEqual(h.langToCommentStyle('mysql'), SQL_STYLE); assert.deepEqual(h.langToCommentStyle('python'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('python3'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('ruby'), RUBY_STYLE); From 167fa23b5f68a4b17046e1005789dccacb0d0fb5 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 12:46:51 +0800 Subject: [PATCH 10/45] [refactor] simplify template format. Signed-off-by: Eric Wang --- lib/cli.js | 1 + lib/file.js | 7 +++++++ templates/codeonly.tpl | 2 +- templates/detailed.tpl | 32 ++++++++++++++++---------------- test/test_core.js | 10 ++++++++-- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 40dc8327..e267deda 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -54,6 +54,7 @@ function initLogLevel() { } function initDir() { + file.init(); file.mkdir(file.homeDir()) } diff --git a/lib/file.js b/lib/file.js index 1db36ca2..6d02f878 100644 --- a/lib/file.js +++ b/lib/file.js @@ -7,6 +7,13 @@ var mkdirp = require('mkdirp'); const file = {} +file.init = function() { + _.templateSettings = { + evaluate: /\{\{(.+?)\}\}/g, + interpolate: /\$\{(.+?)\}/g + }; +}; + file.isWindows = function() { return process.platform === 'win32'; }; diff --git a/templates/codeonly.tpl b/templates/codeonly.tpl index 38efb755..d8baa802 100644 --- a/templates/codeonly.tpl +++ b/templates/codeonly.tpl @@ -1 +1 @@ -<%= code %> +${code} diff --git a/templates/detailed.tpl b/templates/detailed.tpl index 6758aa10..cf501c0d 100644 --- a/templates/detailed.tpl +++ b/templates/detailed.tpl @@ -1,16 +1,16 @@ -<%= comment.start %> -<%= comment.line %> @lc app=<%= app %> id=<%= fid %> lang=<%= lang %> -<%= comment.line %> -<%= comment.line %> [<%= fid %>] <%= name %> -<%= comment.line %> -<%= comment.line %> <%= link %> -<%= comment.line %> -<%= comment.line %> <%= category %> -<%= comment.line %> <%= level %> (<%= percent %>%) -<%= comment.line %> Total Accepted: <%= totalAC %> -<%= comment.line %> Total Submissions: <%= totalSubmit %> -<%= comment.line %> Testcase Example: <%= testcase %> -<%= comment.line %> -<% desc.forEach(function(x) { %><%= comment.line %> <%= x %> -<% }) %><%= comment.end %> -<%= code %> +${comment.start} +${comment.line} @lc app=${app} id=${fid} lang=${lang} +${comment.line} +${comment.line} [${fid}] ${name} +${comment.line} +${comment.line} ${link} +${comment.line} +${comment.line} ${category} +${comment.line} ${level} (${percent}%) +${comment.line} Total Accepted: ${totalAC} +${comment.line} Total Submissions: ${totalSubmit} +${comment.line} Testcase Example: ${testcase} +${comment.line} +{{ desc.forEach(function(x) { }}${comment.line} ${x} +{{ }) }}${comment.end} +${code} diff --git a/test/test_core.js b/test/test_core.js index 5932cd4d..1c8b4c5f 100644 --- a/test/test_core.js +++ b/test/test_core.js @@ -142,6 +142,14 @@ describe('core', function() { }); // #starProblem describe('#exportProblem', function() { + let file; + + beforeEach(function() { + file = rewire('../lib/file'); + file.init(); + core.__set__('file', file); + }); + it('should codeonly ok', function() { const expected = [ '/**', @@ -171,9 +179,7 @@ describe('core', function() { }); it('should codeonly ok in windows', function() { - const file = rewire('../lib/file'); file.isWindows = () => true; - core.__set__('file', file); const expected = [ '/**', From e7ac6a63ccb35719a624e371b4d704e46f1227a7 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 14:49:53 +0800 Subject: [PATCH 11/45] refs #121: support customized filename. Signed-off-by: Eric Wang --- lib/commands/show.js | 20 +++++++++----------- lib/commands/submission.js | 16 ++++++++-------- lib/config.js | 4 ++++ lib/file.js | 4 ++++ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/commands/show.js b/lib/commands/show.js index 6cc51bed..8d238cea 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -76,20 +76,18 @@ const cmd = { function genFileName(problem, opts) { const path = require('path'); const params = [ - problem.fid, - problem.slug, + file.fmt(config.file.show, problem), '', h.langToExt(opts.lang) ]; - // try to use a new filename to avoid overwrite by mistake - let i = 0; - let name; - do { - name = path.join(opts.outdir, params.join('.').replace(/\.+/g, '.')); - params[2] = i++; - } while (fs.existsSync(name)); - return name; + // try new name to avoid overwrite by mistake + for (let i = 0; ; ++i) { + const name = path.join(opts.outdir, params.join('.').replace(/\.+/g, '.')); + if (!fs.existsSync(name)) + return name; + params[2] = i; + } } function showProblem(problem, argv) { @@ -123,8 +121,8 @@ function showProblem(problem, argv) { let filename; if (argv.gen) { - filename = genFileName(problem, argv); file.mkdir(argv.outdir); + filename = genFileName(problem, argv); fs.writeFileSync(filename, code); if (argv.editor !== undefined) { diff --git a/lib/commands/submission.js b/lib/commands/submission.js index c9b07ce3..c90588a8 100644 --- a/lib/commands/submission.js +++ b/lib/commands/submission.js @@ -1,11 +1,13 @@ 'use strict'; var fs = require('fs'); +var path = require('path'); -var sprintf = require('sprintf-js').sprintf; +var _ = require('underscore'); var h = require('../helper'); var file = require('../file'); var chalk = require('../chalk'); +var config = require('../config'); var log = require('../log'); var Queue = require('../queue'); var core = require('../core'); @@ -92,13 +94,11 @@ function exportSubmission(problem, argv, cb) { const submission = submissions.find(x => x.status_display === 'Accepted') || submissions[0]; submission.ac = (submission.status_display === 'Accepted'); - const f = sprintf('%s/%d.%s.%s.%s%s', - argv.outdir, - problem.fid, - problem.slug, - submission.id, - submission.ac ? 'ac' : 'notac', - h.langToExt(submission.lang)); + 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 diff --git a/lib/config.js b/lib/config.js index 21979dee..692b958d 100644 --- a/lib/config.js +++ b/lib/config.js @@ -55,6 +55,10 @@ const DEFAULT_CONFIG = { editor: 'vim', lang: 'cpp' }, + file: { + show: '${fid}.${slug}', + submission: '${fid}.${slug}.${sid}.${ac}' + }, color: { enable: true, theme: 'default' diff --git a/lib/file.js b/lib/file.js index 6d02f878..996ab9e2 100644 --- a/lib/file.js +++ b/lib/file.js @@ -100,6 +100,10 @@ file.render = function(tpl, data) { return result; }; +file.fmt = function(format, data) { + return _.template(format)(data); +}; + file.metaByName = function(filename) { const m = {}; From 6b2d514846138b4deb3b5a19a2e0579422099a13 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 15:07:14 +0800 Subject: [PATCH 12/45] [refactor] use file utils. Signed-off-by: Eric Wang --- lib/cache.js | 13 ++++++------- lib/commands/config.js | 2 +- lib/commands/show.js | 5 ++--- lib/commands/submission.js | 5 ++--- lib/commands/submit.js | 3 +-- lib/commands/test.js | 3 +-- lib/file.js | 22 +++++++++++++++++++++- lib/plugin.js | 6 +++--- 8 files changed, 37 insertions(+), 22 deletions(-) diff --git a/lib/cache.js b/lib/cache.js index 4d0470fe..42efad0b 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -1,5 +1,4 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); var file = require('./file'); @@ -12,22 +11,22 @@ cache.init = function() { cache.get = function(k) { const fullpath = file.cacheFile(k); - if (!fs.existsSync(fullpath)) return null; + if (!file.exist(fullpath)) return null; - return JSON.parse(fs.readFileSync(fullpath)); + return JSON.parse(file.data(fullpath)); }; cache.set = function(k, v) { const fullpath = file.cacheFile(k); - fs.writeFileSync(fullpath, JSON.stringify(v)); + file.write(fullpath, JSON.stringify(v)); return true; }; cache.del = function(k) { const fullpath = file.cacheFile(k); - if (!fs.existsSync(fullpath)) return false; + if (!file.exist(fullpath)) return false; - fs.unlinkSync(fullpath); + file.rm(fullpath); return true; }; @@ -36,7 +35,7 @@ cache.list = function() { .filter(x => path.extname(x) === '.json') .map(function(filename) { const k = path.basename(filename, '.json'); - const stat = fs.statSync(file.cacheFile(k)); + const stat = file.stat(file.cacheFile(k)); return { name: k, size: stat.size, diff --git a/lib/commands/config.js b/lib/commands/config.js index eb8a7b6a..a14fb5b5 100644 --- a/lib/commands/config.js +++ b/lib/commands/config.js @@ -55,7 +55,7 @@ function loadConfig(showall) { } function saveConfig() { - require('fs').writeFileSync(file.configFile(), prettyConfig(loadConfig(false))); + file.write(file.configFile(), prettyConfig(loadConfig(false))); } cmd.handler = function(argv) { diff --git a/lib/commands/show.js b/lib/commands/show.js index 8d238cea..d63a7191 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -1,5 +1,4 @@ 'use strict'; -var fs = require('fs'); var util = require('util'); var _ = require('underscore'); @@ -84,7 +83,7 @@ function genFileName(problem, opts) { // try new name to avoid overwrite by mistake for (let i = 0; ; ++i) { const name = path.join(opts.outdir, params.join('.').replace(/\.+/g, '.')); - if (!fs.existsSync(name)) + if (!file.exist(name)) return name; params[2] = i; } @@ -123,7 +122,7 @@ function showProblem(problem, argv) { if (argv.gen) { file.mkdir(argv.outdir); filename = genFileName(problem, argv); - fs.writeFileSync(filename, code); + file.write(filename, code); if (argv.editor !== undefined) { childProcess.spawn(argv.editor || config.code.editor, [filename], { diff --git a/lib/commands/submission.js b/lib/commands/submission.js index c90588a8..4cd0af46 100644 --- a/lib/commands/submission.js +++ b/lib/commands/submission.js @@ -1,5 +1,4 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); var _ = require('underscore'); @@ -102,7 +101,7 @@ function exportSubmission(problem, argv, cb) { file.mkdir(argv.outdir); // skip the existing cached submissions - if (fs.existsSync(f)) + if (file.exist(f)) return cb(null, chalk.underline(f)); core.getSubmission(submission, function(e, submission) { @@ -113,7 +112,7 @@ function exportSubmission(problem, argv, cb) { code: submission.code, tpl: argv.extra ? 'detailed' : 'codeonly' }; - fs.writeFileSync(f, core.exportProblem(problem, opts)); + file.write(f, core.exportProblem(problem, opts)); cb(null, submission.ac ? chalk.green.underline(f) : chalk.yellow.underline(f)); }); diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 2f83d7b0..9bb52cc4 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -1,5 +1,4 @@ 'use strict'; -var fs = require('fs'); var util = require('util'); var h = require('../helper'); @@ -44,7 +43,7 @@ function printLine() { cmd.handler = function(argv) { session.argv = argv; - if (!fs.existsSync(argv.filename)) + if (!file.exist(argv.filename)) return log.error('File ' + argv.filename + ' not exist!'); const meta = file.meta(argv.filename); diff --git a/lib/commands/test.js b/lib/commands/test.js index 4992a06c..02520508 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -1,5 +1,4 @@ 'use strict'; -var fs = require('fs'); var _ = require('underscore'); var h = require('../helper'); @@ -54,7 +53,7 @@ function printResult(actual, expect, k) { } function runTest(argv) { - if (!fs.existsSync(argv.filename)) + if (!file.exist(argv.filename)) return log.error('File ' + argv.filename + ' not exist!'); const meta = file.meta(argv.filename); diff --git a/lib/file.js b/lib/file.js index 996ab9e2..5cda4170 100644 --- a/lib/file.js +++ b/lib/file.js @@ -75,9 +75,29 @@ file.mkdir = function(fullpath) { 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)); diff --git a/lib/plugin.js b/lib/plugin.js index be5dfe32..cfd492a7 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -49,7 +49,7 @@ Plugin.prototype.enable = function(enabled) { if (this.enabled === enabled) return; const newfile = enabled ? this.file.substr(1) : '.' + this.file; try { - fs.renameSync(file.pluginFile(this.file), file.pluginFile(newfile)); + file.mv(file.pluginFile(this.file), file.pluginFile(newfile)); } catch(e) { log.error(e.message); } @@ -60,7 +60,7 @@ Plugin.prototype.delete = function() { if (!this.missing) { try { const fullpath = file.pluginFile(this.file); - fs.unlinkSync(fullpath); + file.rm(fullpath); } catch(e) { return log.error(e.message); } @@ -156,7 +156,7 @@ Plugin.copy = function(src, cb) { }); srcstream.on('error', function(e) { spin.stop(); - fs.unlinkSync(dst); + file.rm(dst); return cb(e); }); From 196cf50a9a8e85366ac6c737905dbca6c5f13da8 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 17:03:14 +0800 Subject: [PATCH 13/45] fixes typo in filename. Signed-off-by: Eric Wang --- lib/commands/show.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/show.js b/lib/commands/show.js index d63a7191..319d5d53 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -85,7 +85,7 @@ function genFileName(problem, opts) { const name = path.join(opts.outdir, params.join('.').replace(/\.+/g, '.')); if (!file.exist(name)) return name; - params[2] = i; + params[1] = i; } } From 10e80189a354e1b32559528aa3d15932a6837471 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 17:58:25 +0800 Subject: [PATCH 14/45] [pkg] add pkg config. Signed-off-by: Eric Wang --- .dockerignore | 4 ++++ package.json | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index ed775dc7..c34cc8cd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,14 @@ .git +.github .npm .nyc_output .DS_Store + coverage +dist node_modules npm-debug.log* tmp + *.log *.swp diff --git a/package.json b/package.json index 28bdfef8..943fab87 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,23 @@ }, "scripts": { "lint": "eslint lib/ test/", - "test": "npm run lint && nyc mocha test/** && nyc report --reporter=lcov" + "test": "npm run lint && nyc mocha test/** && nyc report --reporter=lcov", + "pkg": "pkg . --out-path=dist/" + }, + "pkg": { + "scripts": [ + "lib" + ], + "assets": [ + "colors", + "icons", + "templates" + ], + "targets": [ + "node10-linux-x64", + "node10-macos-x64", + "node10-win-x64" + ] }, "repository": { "type": "git", From a37018de6de704e098fc91de5c8426a8cd09d1c3 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 19:22:19 +0800 Subject: [PATCH 15/45] [plugin] enable/disable wont touch plugins js. Signed-off-by: Eric Wang --- .gitignore | 1 + lib/commands/plugin.js | 6 ++--- lib/plugin.js | 55 ++++++++++++++++++------------------------ test/test_plugin.js | 46 +++++++++-------------------------- 4 files changed, 39 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index a833ee3d..c08d1d39 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ jspm_packages # Optional REPL history .node_repl_history +dist/ tmp/ *.swp .DS_Store diff --git a/lib/commands/plugin.js b/lib/commands/plugin.js index 6a5b9721..41d7aef4 100644 --- a/lib/commands/plugin.js +++ b/lib/commands/plugin.js @@ -70,7 +70,6 @@ function print(plugins) { log.printf(' %s %-18s %-15s %s', h.prettyText('', p.enabled && !p.missing), p.name, p.ver, p.desc); - Plugin.save(); } cmd.handler = function(argv) { @@ -83,6 +82,7 @@ cmd.handler = function(argv) { const cb = function(e) { if (e) return log.error(e); Plugin.init(); + Plugin.save(); print(); }; @@ -102,11 +102,11 @@ cmd.handler = function(argv) { return log.error('Plugin missing, install it first'); if (argv.enable) { - p.enable(true); + p.enabled = true; p.save(); print(); } else if (argv.disable) { - p.enable(false); + p.enabled = false; p.save(); print(); } else if (argv.delete) { diff --git a/lib/plugin.js b/lib/plugin.js index cfd492a7..fedf1081 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -40,22 +40,6 @@ Plugin.prototype.setNext = function(next) { this.next = next; }; -Plugin.prototype.setFile = function(f) { - this.file = f; - this.enabled = (this.file[0] !== '.'); -}; - -Plugin.prototype.enable = function(enabled) { - if (this.enabled === enabled) return; - const newfile = enabled ? this.file.substr(1) : '.' + this.file; - try { - file.mv(file.pluginFile(this.file), file.pluginFile(newfile)); - } catch(e) { - log.error(e.message); - } - this.setFile(newfile); -}; - Plugin.prototype.delete = function() { if (!this.missing) { try { @@ -69,13 +53,13 @@ Plugin.prototype.delete = function() { }; Plugin.prototype.save = function() { - const data = cache.get(h.KEYS.plugins) || {}; + const stats = cache.get(h.KEYS.plugins) || {}; - if (this.deleted) delete data[this.name]; + if (this.deleted) delete stats[this.name]; else if (this.missing) return; - else data[this.name] = this.enabled; + else stats[this.name] = this.enabled; - cache.set(h.KEYS.plugins, data); + cache.set(h.KEYS.plugins, stats); }; Plugin.prototype.install = function(cb) { @@ -84,9 +68,9 @@ Plugin.prototype.install = function(cb) { const cmd = 'npm install --save ' + this.deps.join(' '); log.debug(cmd); const spin = h.spin(cmd); - cp.exec(cmd, {cwd: file.codeDir()}, function() { + cp.exec(cmd, {cwd: file.codeDir()}, function(e) { spin.stop(); - return cb(); + return cb(e); }); }; @@ -98,14 +82,23 @@ Plugin.init = function(head) { log.trace('initializing all plugins'); head = head || require('./core'); + const stats = cache.get(h.KEYS.plugins) || {}; + // 1. check installed plugins let plugins = []; for (let f of file.listCodeDir('lib/plugins')) { const p = f.data; if (!p) continue; - - p.setFile(f.file); log.trace('found plugin: ' + p.name + '=' + p.ver); + + p.file = f.file; + p.enabled = stats[p.name]; + // not saved before? enable it by default + if (!(p.name in stats)) { + log.trace('new plugin, enable by default'); + p.enabled = true; + } + if (p.enabled) { p.init(); log.trace('inited plugin: ' + p.name); @@ -129,11 +122,10 @@ Plugin.init = function(head) { // 2. check saved plugins const missings = []; - const data = cache.get(h.KEYS.plugins) || {}; - for (let k of _.keys(data)) { + for (let k of _.keys(stats)) { if (plugins.find(x => x.name === k)) continue; const p = new Plugin(-1, k, 'missing'); - p.enabled = data[k]; + p.enabled = stats[k]; missings.push(p); } log.trace('missing plugins: ' + missings.length); @@ -177,6 +169,7 @@ Plugin.install = function(name, cb) { log.debug('copied to ' + fullpath); const p = require(fullpath); + p.file = path.basename(fullpath); p.install(function() { return cb(null, p); }); @@ -187,7 +180,7 @@ Plugin.installMissings = function(cb) { function doTask(plugin, queue, cb) { Plugin.install(plugin.name, function(e, p) { if (!e) { - p.enable(plugin.enabled); + p.enabled = plugin.enabled; p.save(); p.help(); } @@ -195,11 +188,11 @@ Plugin.installMissings = function(cb) { }); } - const plugins = Plugin.plugins.filter(x => x.missing); - if (plugins.length === 0) return cb(); + 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(plugins, {}, doTask); + const q = new Queue(missings, {}, doTask); q.run(1, cb); }; diff --git a/test/test_plugin.js b/test/test_plugin.js index a6cdf156..19d2cdc1 100644 --- a/test/test_plugin.js +++ b/test/test_plugin.js @@ -35,17 +35,17 @@ describe('plugin', function() { }); describe('#Plugin.init', function() { - const p1 = new Plugin(0, 'Leetcode', '2.0'); - const p2 = new Plugin(1, 'Cache', '1.0'); - const p3 = new Plugin(2, 'Retry', '3.0'); - const p4 = new Plugin(3, 'Core', '4.0'); + const p1 = new Plugin(0, 'leetcode', '2.0'); + const p2 = new Plugin(1, 'cache', '1.0'); + const p3 = new Plugin(2, 'retry', '3.0'); + const p4 = new Plugin(3, 'core', '4.0'); before(function() { p1.init = p2.init = p3.init = p4.init = NOOP; file.listCodeDir = function() { return [ {name: 'cache', data: p2, file: 'cache.js'}, - {name: 'leetcode', data: p1, file: '.leetcode.js'}, // disabled + {name: 'leetcode', data: p1, file: 'leetcode.js'}, {name: 'retry', data: p3, file: 'retry.js'}, {name: 'bad', data: null} ]; @@ -53,6 +53,9 @@ describe('plugin', function() { }); it('should init ok', function() { + cache.get = () => { + return {cache: true, leetcode: false, retry: true}; + }; assert.deepEqual(Plugin.plugins, []); const res = Plugin.init(p4); @@ -60,7 +63,7 @@ describe('plugin', function() { assert.deepEqual(Plugin.plugins.length, 3); const names = Plugin.plugins.map(p => p.name); - assert.deepEqual(names, ['Retry', 'Cache', 'Leetcode']); + assert.deepEqual(names, ['retry', 'cache', 'leetcode']); assert.equal(p4.next, p3); assert.equal(p3.next, p2); @@ -70,7 +73,7 @@ describe('plugin', function() { it('should find missing ok', function() { cache.get = () => { - return {company: true, solution: true}; + return {company: true, leetcode: false, solution: true}; }; const res = Plugin.init(p4); @@ -78,7 +81,7 @@ describe('plugin', function() { assert.deepEqual(Plugin.plugins.length, 5); const names = Plugin.plugins.map(p => p.name); - assert.deepEqual(names, ['Retry', 'Cache', 'Leetcode', 'company', 'solution']); + assert.deepEqual(names, ['retry', 'cache', 'leetcode', 'company', 'solution']); assert.equal(p4.next, p3); assert.equal(p3.next, p2); @@ -179,33 +182,6 @@ describe('plugin', function() { }); }); // #Plugin.installMissings - describe('#enable', function() { - const FILE = path.resolve(th.DIR, 'leetcode.js'); - - before(function() { - file.pluginFile = () => FILE; - }); - - it('should ok', function() { - const p = new Plugin(0, 'Leetcode', '2.0', ''); - assert.equal(p.enabled, true); - - p.setFile('.leetcode.js'); - fs.writeFileSync(FILE, ''); - assert.equal(p.enabled, false); - assert.equal(p.file, '.leetcode.js'); - p.enable(false); - assert.equal(p.enabled, false); - assert.equal(p.file, '.leetcode.js'); - p.enable(true); - assert.equal(p.enabled, true); - assert.equal(p.file, 'leetcode.js'); - p.enable(false); - assert.equal(p.enabled, false); - assert.equal(p.file, '.leetcode.js'); - }); - }); // #enable - describe('#delete', function() { it('should ok', function() { file.pluginFile = x => th.DIR + x; From 73bf1a9494fc5a41e3a5bc7a83be4f41589b992a Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 23:39:55 +0800 Subject: [PATCH 16/45] fixes plugin init order issue. Signed-off-by: Eric Wang --- lib/commands/plugin.js | 5 +++-- lib/plugin.js | 28 +++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/commands/plugin.js b/lib/commands/plugin.js index 41d7aef4..f9905780 100644 --- a/lib/commands/plugin.js +++ b/lib/commands/plugin.js @@ -79,10 +79,11 @@ cmd.handler = function(argv) { const name = argv.name; if (argv.install) { - const cb = function(e) { + const cb = function(e, p) { if (e) return log.error(e); + p.help(); + p.save(); Plugin.init(); - Plugin.save(); print(); }; diff --git a/lib/plugin.js b/lib/plugin.js index fedf1081..25665332 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -84,8 +84,8 @@ Plugin.init = function(head) { const stats = cache.get(h.KEYS.plugins) || {}; - // 1. check installed plugins - let plugins = []; + // 1. find installed plugins + let installed = []; for (let f of file.listCodeDir('lib/plugins')) { const p = f.data; if (!p) continue; @@ -98,39 +98,41 @@ Plugin.init = function(head) { log.trace('new plugin, enable by default'); p.enabled = true; } + 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); } - - plugins.push(p); } - // chain the plugins together - // the one has bigger `id` comes first - plugins = _.sortBy(plugins, x => -x.id); - + // 3. chain together + const plugins = installed.filter(x => x.enabled); let last = head; for (let p of plugins) { - if (!p.enabled) continue; last.setNext(p); last = p; } - // 2. check saved plugins + // 4. check missing plugins const missings = []; for (let k of _.keys(stats)) { - if (plugins.find(x => x.name === k)) continue; + 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); } - log.trace('missing plugins: ' + missings.length); - Plugin.plugins = plugins.concat(missings); + Plugin.plugins = installed.concat(missings); return missings.length === 0; }; From 465b3c37c9f7adf7cb4f4cb3cf2dfa9e1b7f2b0f Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 19 Nov 2018 00:11:53 +0800 Subject: [PATCH 17/45] [plugin] always enable builtin plugin by default. Signed-off-by: Eric Wang --- lib/plugin.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/plugin.js b/lib/plugin.js index 25665332..90d48489 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -21,7 +21,8 @@ function Plugin(id, name, ver, desc, deps) { this.enabled = true; this.deleted = false; - this.missing = (ver === 'missing'); + this.missing = (this.ver === 'missing'); + this.builtin = (this.ver === 'default'); // only need deps for current platform this.deps = _.chain(deps || []) @@ -93,10 +94,15 @@ Plugin.init = function(head) { p.file = f.file; p.enabled = stats[p.name]; - // not saved before? enable it by default + if (!(p.name in stats)) { - log.trace('new plugin, enable by default'); - p.enabled = true; + 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); } From 98d9a2fb71bc88aae4147120fcaaf534b9aea3c9 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 19 Nov 2018 12:29:54 +0800 Subject: [PATCH 18/45] [pkg] add script for linux/macos * refs #133 Signed-off-by: Eric Wang --- bin/pkg | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 bin/pkg diff --git a/bin/pkg b/bin/pkg new file mode 100755 index 00000000..c61c1d2a --- /dev/null +++ b/bin/pkg @@ -0,0 +1,25 @@ +#!/bin/bash + +plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss" + +for plugin in $plugins; do + echo "[$plugin]" + ./bin/leetcode ext -i $plugin +done + +ver=`node -v | sed 's/^v//' | awk -F. '{print $1}'` +case `uname` in + Linux) os=linux ;; + Darwin) os=macos ;; + *) echo "OS not supported!" && exit 1 +esac +case `uname -m` in + x86_64) arch=x64 ;; + i?86) arch=x32 ;; + *) echo "Arch not supported!" && exit 1 +esac + +rm -rf dist/* +find node_modules -name "*.node" -exec cp {} dist/ \; +npm run pkg -- node$ver-$os-$arch +tar zcvf leetcode-cli.$os.tar.gz dist/ diff --git a/package.json b/package.json index 943fab87..cff45fda 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "lint": "eslint lib/ test/", "test": "npm run lint && nyc mocha test/** && nyc report --reporter=lcov", - "pkg": "pkg . --out-path=dist/" + "pkg": "pkg . --out-path=dist/ --targets" }, "pkg": { "scripts": [ From 863157a03f64e48fc7a885110d2090dc309acafa Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 19 Nov 2018 21:31:36 +0800 Subject: [PATCH 19/45] fixes format in `list` Signed-off-by: Eric Wang --- bin/pkg | 1 + icons/ascii.json | 1 + icons/default.json | 1 + icons/win7.json | 1 + lib/commands/list.js | 4 ++-- lib/commands/submission.js | 2 +- 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/pkg b/bin/pkg index c61c1d2a..790a8a91 100755 --- a/bin/pkg +++ b/bin/pkg @@ -19,6 +19,7 @@ case `uname -m` in *) echo "Arch not supported!" && exit 1 esac +mkdir -p dist/ rm -rf dist/* find node_modules -name "*.node" -exec cp {} dist/ \; npm run pkg -- node$ver-$os-$arch diff --git a/icons/ascii.json b/icons/ascii.json index 6660733f..1a7664f1 100644 --- a/icons/ascii.json +++ b/icons/ascii.json @@ -4,6 +4,7 @@ "like": "*", "unlike": " ", "lock": "$", + "nolock": " ", "empty": " ", "ac": "O", "notac": "X", diff --git a/icons/default.json b/icons/default.json index 42bdbecd..a5263a7f 100644 --- a/icons/default.json +++ b/icons/default.json @@ -4,6 +4,7 @@ "like": "★", "unlike": "☆", "lock": "🔒", + "nolock": " ", "empty": " ", "ac": "▣", "notac": "▤", diff --git a/icons/win7.json b/icons/win7.json index 7f032a83..0e79a481 100644 --- a/icons/win7.json +++ b/icons/win7.json @@ -4,6 +4,7 @@ "like": "♥", "unlike": " ", "lock": "$", + "nolock": " ", "empty": " ", "ac": "O", "notac": "X", diff --git a/lib/commands/list.js b/lib/commands/list.js index f3e69143..67427f89 100644 --- a/lib/commands/list.js +++ b/lib/commands/list.js @@ -67,9 +67,9 @@ cmd.handler = function(argv) { if (problem.locked) ++stat.locked; if (problem.starred) ++stat.starred; - log.printf('%s %s %s [%3d] %-60s %-6s (%.2f %%)', + log.printf('%s %s %s [%4d] %-60s %-6s (%.2f %%)', (problem.starred ? chalk.yellow(icon.like) : icon.empty), - (problem.locked ? chalk.red(icon.lock) : icon.empty), + (problem.locked ? chalk.red(icon.lock) : icon.nolock), h.prettyState(problem.state), problem.fid, problem.name, diff --git a/lib/commands/submission.js b/lib/commands/submission.js index 4cd0af46..6c9c6909 100644 --- a/lib/commands/submission.js +++ b/lib/commands/submission.js @@ -62,7 +62,7 @@ function doTask(problem, queue, cb) { // - green: accepted, fresh download // - yellow: not ac-ed, fresh download // - white: existed already, skip download - log.printf('[%3d] %-60s %s', problem.fid, problem.name, + log.printf('[%4d] %-60s %s', problem.fid, problem.name, (e ? chalk.red('ERROR: ' + (e.msg || e)) : msg)); if (cb) cb(e); } From e3f108b5197ed9f46682a698381a06215a852857 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 00:34:17 +0800 Subject: [PATCH 20/45] fixes UT issues on windows. Signed-off-by: Eric Wang --- lib/core.js | 2 +- lib/file.js | 2 +- lib/plugin.js | 15 ++++++++----- package.json | 2 +- test/helper.js | 9 ++++++-- test/test_core.js | 6 ++++++ test/test_file.js | 51 ++++++++++++++++++++++++++++++++++++++++++++- test/test_icon.js | 4 +++- test/test_plugin.js | 4 ++-- 9 files changed, 81 insertions(+), 14 deletions(-) diff --git a/lib/core.js b/lib/core.js index e4d4b645..74362f78 100644 --- a/lib/core.js +++ b/lib/core.js @@ -109,7 +109,7 @@ core.exportProblem = function(problem, opts) { const data = _.extend({}, problem); // unify format before rendering - data.app = require('./config').app; + 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'); diff --git a/lib/file.js b/lib/file.js index 5cda4170..493e9ebd 100644 --- a/lib/file.js +++ b/lib/file.js @@ -110,7 +110,7 @@ file.data = function(fullpath) { /// templates & metadata /// file.render = function(tpl, data) { const tplfile = path.join(this.codeDir('templates'), tpl + '.tpl'); - let result = _.template(this.data(tplfile))(data); + let result = _.template(this.data(tplfile).replace(/\r\n/g, '\n'))(data); if (this.isWindows()) { result = result.replace(/\n/g, '\r\n'); diff --git a/lib/plugin.js b/lib/plugin.js index 90d48489..bbd6da44 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -150,20 +150,25 @@ Plugin.copy = function(src, cb) { 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) { - spin.stop(); - file.rm(dst); - return cb(e); + dststream.emit('error', e); }); - const dststream = fs.createWriteStream(dst); + dststream.on('error', function(e) { + error = e; + dststream.end(); + }); dststream.on('close', function() { spin.stop(); - return cb(null, dst); + if (error) file.rm(dst); + return cb(error, dst); }); log.debug('copying from ' + src); diff --git a/package.json b/package.json index cff45fda..864d7edc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "scripts": { "lint": "eslint lib/ test/", - "test": "npm run lint && nyc mocha test/** && nyc report --reporter=lcov", + "test": "npm run lint && nyc mocha test test/plugins && nyc report --reporter=lcov", "pkg": "pkg . --out-path=dist/ --targets" }, "pkg": { diff --git a/test/helper.js b/test/helper.js index 56ee5363..ca0f532e 100644 --- a/test/helper.js +++ b/test/helper.js @@ -8,8 +8,13 @@ const h = { h.clean = function() { if (!fs.existsSync(this.DIR)) fs.mkdirSync(this.DIR); - for (let f of fs.readdirSync(this.DIR)) - fs.unlinkSync(this.DIR + f); + for (let f of fs.readdirSync(this.DIR)) { + const fullpath = this.DIR + f; + if (fs.statSync(fullpath).isDirectory()) + fs.rmdirSync(fullpath); + else + fs.unlinkSync(fullpath); + } }; module.exports = h; diff --git a/test/test_core.js b/test/test_core.js index 1c8b4c5f..0a436bb4 100644 --- a/test/test_core.js +++ b/test/test_core.js @@ -151,6 +151,8 @@ describe('core', function() { }); it('should codeonly ok', function() { + file.isWindows = () => false; + const expected = [ '/**', ' * Definition for singly-linked list.', @@ -209,6 +211,8 @@ describe('core', function() { }); it('should detailed ok with cpp', function() { + file.isWindows = () => false; + const expected = [ '/*', ' * @lc app=leetcode id=2 lang=cpp', @@ -257,6 +261,8 @@ describe('core', function() { }); it('should detailed ok with ruby', function() { + file.isWindows = () => false; + const expected = [ '#', '# @lc app=leetcode id=2 lang=ruby', diff --git a/test/test_file.js b/test/test_file.js index 481fa9d3..16d04ba3 100644 --- a/test/test_file.js +++ b/test/test_file.js @@ -1,9 +1,12 @@ 'use strict'; +const fs = require('fs'); const path = require('path'); const assert = require('chai').assert; const rewire = require('rewire'); +const th = require('./helper'); + describe('file', function() { let file; @@ -14,7 +17,8 @@ describe('file', function() { describe('#dirAndFiles', function() { const HOME = path.join(__dirname, '..'); - it('should ok', function() { + it('should ok on linux', function() { + if (file.isWindows()) this.skip(); process.env.HOME = '/home/skygragon'; assert.equal(file.userHomeDir(), '/home/skygragon'); @@ -23,10 +27,18 @@ describe('file', function() { assert.equal(file.cacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); assert.equal(file.configFile(), '/home/skygragon/.lc/config.json'); assert.equal(file.name('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); + }); + it('should ok on windows', function() { + if (!file.isWindows()) this.skip(); process.env.HOME = ''; process.env.USERPROFILE = 'C:\\Users\\skygragon'; assert.equal(file.userHomeDir(), 'C:\\Users\\skygragon'); + assert.equal(file.homeDir(), 'C:\\Users\\skygragon\\.lc'); + assert.equal(file.cacheDir(), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache'); + assert.equal(file.cacheFile('xxx'), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'); + assert.equal(file.configFile(), 'C:\\Users\\skygragon\\.lc\\config.json'); + assert.equal(file.name('C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'), 'xxx'); }); it('should codeDir ok', function() { @@ -98,5 +110,42 @@ describe('file', function() { assert.equal(meta.id, '222'); assert.equal(meta.lang, 'python3'); }); + + it('should fmt ok', function() { + file.init(); + const data = file.fmt('${id}', {id: 123}); + assert.equal(data, '123'); + }); }); // #meta + + describe('#genneral', function() { + beforeEach(function() { + th.clean(); + }); + afterEach(function() { + th.clean(); + }); + + it('should mkdir ok', function() { + const dir = th.DIR + 'dir'; + assert.equal(fs.existsSync(dir), false); + file.mkdir(dir); + assert.equal(fs.existsSync(dir), true); + file.mkdir(dir); + assert.equal(fs.existsSync(dir), true); + }); + + it('should mv ok', function() { + const SRC = th.Dir + 'src'; + const DST = th.DIR + 'dst'; + assert.equal(fs.existsSync(SRC), false); + assert.equal(fs.existsSync(DST), false); + file.mkdir(SRC); + assert.equal(fs.existsSync(SRC), true); + assert.equal(fs.existsSync(DST), false); + file.mv(SRC, DST); + assert.equal(fs.existsSync(SRC), false); + assert.equal(fs.existsSync(DST), true); + }); + }); // #general }); diff --git a/test/test_icon.js b/test/test_icon.js index 559eeb51..5da832ab 100644 --- a/test/test_icon.js +++ b/test/test_icon.js @@ -30,7 +30,9 @@ describe('icon', function() { assert.equal(icon.unlike, 'unlike'); }); - it('should ok with unknown theme', function() { + it('should ok with unknown theme on linux', function() { + file.isWindows = () => false; + icon.setTheme('non-exist'); assert.equal(icon.yes, '✔'); assert.equal(icon.no, '✘'); diff --git a/test/test_plugin.js b/test/test_plugin.js index 19d2cdc1..aa3a40be 100644 --- a/test/test_plugin.js +++ b/test/test_plugin.js @@ -146,9 +146,9 @@ describe('plugin', function() { ]; fs.writeFileSync(SRC, data.join('\n')); - Plugin.install(SRC, function(e, p) { + Plugin.copy(SRC, function(e, fullpath) { assert.notExists(e); - assert.equal(p.x, 123); + assert.equal(fullpath, DST); assert.equal(fs.existsSync(DST), true); done(); }); From c957d787035e587590c8f5d6c8a23f43d7309bd1 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Tue, 20 Nov 2018 22:44:28 +0800 Subject: [PATCH 21/45] [travis] enable more platforms Signed-off-by: Eric Wang --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9f9d47a..40ece843 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,11 @@ language: node_js node_js: - - stable + - "node" +os: + - linux + - osx + - windows install: - npm install +script: + - npm test From a3b384f34ae3dde018af69262a18c92119f3eb6f Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 11:10:43 +0800 Subject: [PATCH 22/45] [pkg] refactor script for linux Signed-off-by: Eric Wang --- bin/pkg | 37 +++++++++++++++---------------------- bin/pkg.sh | 23 +++++++++++++++++++++++ package.json | 1 + 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100755 bin/pkg.sh diff --git a/bin/pkg b/bin/pkg index 790a8a91..33af5e64 100755 --- a/bin/pkg +++ b/bin/pkg @@ -1,26 +1,19 @@ -#!/bin/bash +#!/usr/bin/env node -plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss" +const arch = require('os').arch(); +var os = process.platform; +const ver = process.versions.node.split('.')[0]; -for plugin in $plugins; do - echo "[$plugin]" - ./bin/leetcode ext -i $plugin -done +var bin = './bin/pkg.sh'; +var args = [arch, os, ver]; -ver=`node -v | sed 's/^v//' | awk -F. '{print $1}'` -case `uname` in - Linux) os=linux ;; - Darwin) os=macos ;; - *) echo "OS not supported!" && exit 1 -esac -case `uname -m` in - x86_64) arch=x64 ;; - i?86) arch=x32 ;; - *) echo "Arch not supported!" && exit 1 -esac +if (os === 'darwin') { + args[1] = 'macos'; +} else if (os === 'win32') { + bin = 'cmd.exe'; + args = ['/c', 'pkg.bat'].concat(args); +} -mkdir -p dist/ -rm -rf dist/* -find node_modules -name "*.node" -exec cp {} dist/ \; -npm run pkg -- node$ver-$os-$arch -tar zcvf leetcode-cli.$os.tar.gz dist/ +var proc = require('child_process').spawn(bin, args); +proc.stdout.on('data', x => console.log(x.toString().trimRight('\n'))); +proc.stderr.on('data', x => console.log(x.toString().trimRight('\n'))); diff --git a/bin/pkg.sh b/bin/pkg.sh new file mode 100755 index 00000000..d747804c --- /dev/null +++ b/bin/pkg.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +arch=$1 +os=$2 +ver=$3 + +plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss" + +for plugin in $plugins; do + echo "[$plugin]" + #./bin/leetcode ext -i $plugin +done + +DIST=./dist +mkdir -p $DIST +rm -rf $DIST/* + +find node_modules -name "*.node" -exec cp {} $DIST \; +npm run pkg -- node$ver-$os-$arch + +FILE=leetcode-cli.node$ver.$os.$arch.tar.gz +tar zcvf $FILE $DIST +ls -al $FILE diff --git a/package.json b/package.json index 864d7edc..b65b1e44 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "mocha": "5.2.0", "nock": "10.0.2", "nyc": "13.1.0", + "pkg": "^4.3.4", "rewire": "4.0.1" } } From fc4f4acededff5831a84a6316417b1cd4359e2a4 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 12:24:10 +0800 Subject: [PATCH 23/45] [pkg] add script for windows Signed-off-by: Eric Wang --- bin/pkg | 2 +- bin/pkg.bat | 19 +++++++++++++++++++ bin/pkg.sh | 4 ++-- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 bin/pkg.bat diff --git a/bin/pkg b/bin/pkg index 33af5e64..548a8be7 100755 --- a/bin/pkg +++ b/bin/pkg @@ -11,7 +11,7 @@ if (os === 'darwin') { args[1] = 'macos'; } else if (os === 'win32') { bin = 'cmd.exe'; - args = ['/c', 'pkg.bat'].concat(args); + args = ['/c', 'bin\\pkg.bat'].concat(args); } var proc = require('child_process').spawn(bin, args); diff --git a/bin/pkg.bat b/bin/pkg.bat new file mode 100644 index 00000000..12d2f200 --- /dev/null +++ b/bin/pkg.bat @@ -0,0 +1,19 @@ +@echo off +set arch=%1 +set os=%2 +set ver=%3 + +for %%x in (company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss) do ( + echo [%%x] + node bin\leetcode ext -i %%x +) + +set dist=dist\ +mkdir %dist% +del /q %dist%\* + +for /r . %%x in (*.node) do copy %%x %dist% +npm run pkg -- node%ver%-%os%-%arch% + +set file=leetcode-cli.node%ver%.%os%.%arch%.zip +7z a %file% %dist% \ No newline at end of file diff --git a/bin/pkg.sh b/bin/pkg.sh index d747804c..eaf0ac71 100755 --- a/bin/pkg.sh +++ b/bin/pkg.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -e arch=$1 os=$2 @@ -8,7 +8,7 @@ plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.c for plugin in $plugins; do echo "[$plugin]" - #./bin/leetcode ext -i $plugin + ./bin/leetcode ext -i $plugin done DIST=./dist From 450f85a51ac3ec870acb9dd9d8f5b3868ae3d368 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 12:45:00 +0800 Subject: [PATCH 24/45] exit with error code if necessary Signed-off-by: Eric Wang --- lib/cli.js | 2 +- lib/commands/config.js | 4 ++-- lib/commands/plugin.js | 6 +++--- lib/commands/submit.js | 2 +- lib/commands/test.js | 2 +- lib/log.js | 11 ++++++++++- test/test_log.js | 4 ++-- 7 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index e267deda..e59cf7f5 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -98,7 +98,7 @@ cli.run = function() { initLogLevel(); initDir() initPlugins(function(e) { - if (e) return log.error(e); + if (e) return log.fatal(e); cache.init(); runCommand(); }); diff --git a/lib/commands/config.js b/lib/commands/config.js index a14fb5b5..6a1fe364 100644 --- a/lib/commands/config.js +++ b/lib/commands/config.js @@ -75,14 +75,14 @@ cmd.handler = function(argv) { // delete if (argv.delete) { - if (v === undefined) return log.error('Key not found: ' + argv.key); + 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.error('Key not found: ' + argv.key); + if (v === undefined) return log.fatal('Key not found: ' + argv.key); return log.info(prettyConfig(v)); } diff --git a/lib/commands/plugin.js b/lib/commands/plugin.js index f9905780..623fd513 100644 --- a/lib/commands/plugin.js +++ b/lib/commands/plugin.js @@ -80,7 +80,7 @@ cmd.handler = function(argv) { if (argv.install) { const cb = function(e, p) { - if (e) return log.error(e); + if (e) return log.fatal(e); p.help(); p.save(); Plugin.init(); @@ -96,11 +96,11 @@ cmd.handler = function(argv) { } if (name) plugins = plugins.filter(x => x.name === name); - if (plugins.length === 0) return log.error('Plugin not found!'); + if (plugins.length === 0) return log.fatal('Plugin not found!'); const p = plugins[0]; if (p.missing && (argv.enable || argv.disable)) - return log.error('Plugin missing, install it first'); + return log.fatal('Plugin missing, install it first'); if (argv.enable) { p.enabled = true; diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 9bb52cc4..7376598b 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -44,7 +44,7 @@ function printLine() { cmd.handler = function(argv) { session.argv = argv; if (!file.exist(argv.filename)) - return log.error('File ' + argv.filename + ' not exist!'); + return log.fatal('File ' + argv.filename + ' not exist!'); const meta = file.meta(argv.filename); diff --git a/lib/commands/test.js b/lib/commands/test.js index 02520508..21c4a4eb 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -54,7 +54,7 @@ function printResult(actual, expect, k) { function runTest(argv) { if (!file.exist(argv.filename)) - return log.error('File ' + argv.filename + ' not exist!'); + return log.fatal('File ' + argv.filename + ' not exist!'); const meta = file.meta(argv.filename); diff --git a/lib/log.js b/lib/log.js index cb8435de..acccc73c 100644 --- a/lib/log.js +++ b/lib/log.js @@ -25,7 +25,16 @@ log.isEnabled = function(name) { }; log.fail = function(e) { - log.error(sprintf('%s [%d]', (e.msg || e), (e.statusCode || 0))); + let msg = sprintf('%s', (e.msg || e)); + if (e.statusCode) { + msg += sprintf(' [code=%d]', e.statusCode); + } + log.error(msg); +}; + +log.fatal = function(e) { + log.error(e); + process.exit(1); }; log.printf = function() { diff --git a/test/test_log.js b/test/test_log.js index 3fd58ff6..a749af50 100644 --- a/test/test_log.js +++ b/test/test_log.js @@ -93,10 +93,10 @@ describe('log', function() { it('should ok with log.fail', function() { log.fail({msg: 'some error', statusCode: 500}); - assert.equal(expected, chalk.red('[ERROR] some error [500]')); + assert.equal(expected, chalk.red('[ERROR] some error [code=500]')); log.fail('some error'); - assert.equal(expected, chalk.red('[ERROR] some error [0]')); + assert.equal(expected, chalk.red('[ERROR] some error')); }); }); // #levels From 7704e1181650481fe0609aad3eeb954c53c3d25c Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 13:32:42 +0800 Subject: [PATCH 25/45] [pkg] bail out if error occurs Signed-off-by: Eric Wang --- bin/pkg | 1 + bin/pkg.bat | 20 +++++++++++++------- bin/pkg.sh | 15 +++++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/bin/pkg b/bin/pkg index 548a8be7..0cc22db1 100755 --- a/bin/pkg +++ b/bin/pkg @@ -17,3 +17,4 @@ if (os === 'darwin') { var proc = require('child_process').spawn(bin, args); proc.stdout.on('data', x => console.log(x.toString().trimRight('\n'))); proc.stderr.on('data', x => console.log(x.toString().trimRight('\n'))); +proc.on('close', process.exit); \ No newline at end of file diff --git a/bin/pkg.bat b/bin/pkg.bat index 12d2f200..0294b7f5 100644 --- a/bin/pkg.bat +++ b/bin/pkg.bat @@ -3,17 +3,23 @@ set arch=%1 set os=%2 set ver=%3 +set dist=dist\ +set file=leetcode-cli.node%ver%.%os%.%arch%.zip + +mkdir %dist% +del /q %dist%\* +del /q *.zip + for %%x in (company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss) do ( echo [%%x] node bin\leetcode ext -i %%x + if %ERRORLEVEL% gtr 0 exit /b 1 ) -set dist=dist\ -mkdir %dist% -del /q %dist%\* - for /r . %%x in (*.node) do copy %%x %dist% -npm run pkg -- node%ver%-%os%-%arch% +call npm run pkg -- node%ver%-%os%-%arch% +if %ERRORLEVEL% gtr 0 exit /b 1 -set file=leetcode-cli.node%ver%.%os%.%arch%.zip -7z a %file% %dist% \ No newline at end of file +7z a %file% %dist% +if %ERRORLEVEL% gtr 0 exit /b 1 +exit 0 \ No newline at end of file diff --git a/bin/pkg.sh b/bin/pkg.sh index eaf0ac71..abeb778d 100755 --- a/bin/pkg.sh +++ b/bin/pkg.sh @@ -4,6 +4,13 @@ arch=$1 os=$2 ver=$3 +DIST=./dist +FILE=leetcode-cli.node$ver.$os.$arch.tar.gz + +mkdir -p $DIST +rm -rf $DIST/* +rm -rf $FILE + plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss" for plugin in $plugins; do @@ -11,13 +18,9 @@ for plugin in $plugins; do ./bin/leetcode ext -i $plugin done -DIST=./dist -mkdir -p $DIST -rm -rf $DIST/* - find node_modules -name "*.node" -exec cp {} $DIST \; npm run pkg -- node$ver-$os-$arch -FILE=leetcode-cli.node$ver.$os.$arch.tar.gz -tar zcvf $FILE $DIST +tar zcvf $FILE $DIST ls -al $FILE +exit 0 \ No newline at end of file From 70179c893e91f552386dd1b1f6e329d6e0205182 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 14:28:45 +0800 Subject: [PATCH 26/45] [pkg] build from travis Signed-off-by: Eric Wang --- .travis.yml | 17 ++++++++++++++++- package.json | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 40ece843..247f9281 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,26 @@ language: node_js node_js: - - "node" + - 10 + os: - linux - osx - windows + install: - npm install + script: - npm test + - if [ -n "$TRAVIS_TAG" ]; then npm run travis; fi + +deploy: + provider: releases + api_key: + secure: "ayYe6HlYFrFposeIh2xX1DbdF3CRFnAHM5VvdtfVh/TtpcEvg4GRCanvzaSvsVajLjFZOZhGVgm+uZ1H6ba6jQuoOUvFJ667EVwQk7c8KDJrvZIMvzMxCgvSHb6N8VBh/5svWYa+7Kbd++3WP7XmkLpWli/DXvOSu6I6M7w+m/OI157mWPp0a7iy+Q+o1vSl/3INNIrd/vMT5F+ae1iBLFn3aHndtezhdQr+HrQCHaVP8OiK96rtjzaiRp+dyoMf4U71LoJGRpGZURv9imyXholoQutlT+bhRaumPqrqiwFRGMaL+xhfBZMySMND8wcO9rQnabiQf5Wo9J5aJMnixWjEIg9gGhJ8E96j9VwdUBA7yfHAbVhLrQ0h2TkZuUdqU1EnOWIbnPtjC9exv8R5X2WRs1fMz9j+XpNYclB4YdLclQ662nfsquccqfksDG1rS249WkSl1RIxr9fcD+60xYXgkG78wrTN8cr9NMGk5/AyMyHcvYjA+rGg1V8DZhzC3WZn9Q0NRJoc3b+xx9pxkaO7epBck5sAsNPO8b/bMGGKmgmR5tKSZUN+lTUKLI2znJcUC1dMKKpRCqr1To94ZYVe0G7SFbe+MH4guQXkd7sB6GnsR8/7g8OsVcAtV4DoEWfHwJQIE0bg/UzqubyBPSGPs1JBZm8nks/zTpOJ65o=" + file: leetcode-cli.* + file_glob: true + skip_cleanup: true + overwrite: true + on: + tags: true diff --git a/package.json b/package.json index b65b1e44..f013bcd7 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "scripts": { "lint": "eslint lib/ test/", "test": "npm run lint && nyc mocha test test/plugins && nyc report --reporter=lcov", + "travis": "node bin/pkg", "pkg": "pkg . --out-path=dist/ --targets" }, "pkg": { From 24b85ea5a7be57b4265ffe9fb3dd484f3d6f861d Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 17:38:22 +0800 Subject: [PATCH 27/45] Bump to 2.6.0 Signed-off-by: Eric Wang --- docs/commands.md | 4 ++-- docs/releases.md | 9 +++++++++ package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index e3270b44..06cfebaa 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -465,7 +465,7 @@ Display version information. Short: $ leetcode version - 2.5.4 + 2.6.0 Verbose: @@ -475,7 +475,7 @@ Verbose: | | ___ ___| |_ ___ ___ __| | ___ | |/ _ \/ _ \ __|/ __|/ _ \ / _` |/ _ \ | | __/ __/ |_ (__| (_) | (_| | __/ - |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.5.4 + |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.6.0 [Environment] Node v8.1.4 diff --git a/docs/releases.md b/docs/releases.md index 1794ecb5..c26de3dc 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,15 @@ layout: default title: Release Notes --- +# 2.6.0 +* build all-in-one binary for linux/macos/windows. +* `show` + * support customized filename. + * use "--" as comment in sql file. +* `list` + * fixes format issue. +* fixes UT failures on windows. + # 2.5.4 * fixes error in fresh env without .lc existed. * embed meta in file content instead of file name. diff --git a/package.json b/package.json index f013bcd7..7453aa5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode-cli", - "version": "2.5.4", + "version": "2.6.0", "description": "A cli tool to enjoy leetcode!", "preferGlobal": "true", "engines": { From b5a5cfa0160555151d70b7655f02cf0433f0a8bf Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 18:38:58 +0800 Subject: [PATCH 28/45] [pages] update docs. Signed-off-by: Eric Wang --- docs/advanced.md | 23 +++++++++++++++++++++++ docs/install.md | 11 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/advanced.md b/docs/advanced.md index a7cc593b..42904e91 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -9,6 +9,7 @@ title: Advanced Topic * [Cache](#cache) * [Configuration](#configuration) * [Color Themes](#color-themes) +* [File Name](#file-name) * [Log Levels](#log-levels) * [Plugins](#plugins) @@ -91,6 +92,10 @@ The config file is saved in `~/.lc/config.json`, here is a full exmaple (include "enable": true, "theme": "default" }, + "file": { + "show": "${fid}.${slug}", + "submission": "${fid}.${slug}.${sid}.${ac}" + }, "icon": { "theme": "" }, @@ -107,6 +112,7 @@ Here are some useful settings: * `code:lang` to set your default language used in coding. * `color:enable` to enable colorful output. * `color:theme` to set color theme used in output. (see [Color Theme](#color-theme)) +* `file.show` to set filename pattern for generated code file. (see [File Name](#file-name)) * `icon:theme` to set icon them used in output. * `plugins` to config each installed plugins. (see [Plugins](#plugins)) @@ -167,6 +173,23 @@ Of course you can create your own themes if you like, look into `colors` folder "yellow": "#ffff00" } +# File Name + +You could configure file name pattern in code generation. + +* config `file.show` for generated file in `show`. +* config `file.submission` for downloaded file in `submission`. + +Followings are some variables you could used in the pattern: + +* `${fid}` for question id. (e.g. `123`) +* `${slug}` for dash-separated question name. (e.g. `add-two`) +* `${name}` for space-separated questions name. (e.g. `Add Two`) +* `${level}` for question level. (e.g. `Hard`) +* `${category}` for question category. (e.g. `algorithms`) +* `${sid}` for submission id. +* `${ac}` for accept status of existing submission. + # Log Levels * `-v` to enable debug output. diff --git a/docs/install.md b/docs/install.md index 5b8e0706..c2e20595 100644 --- a/docs/install.md +++ b/docs/install.md @@ -3,6 +3,12 @@ layout: default title: Installation --- +# All in One (beta) + +No need to install node.js. Now available on 64bits linux, mac, and windows. + +[Download](https://github.com/skygragon/leetcode-cli/releases) + # Prerequisites Install the latest LTS version of `node.js` (`npm` included): @@ -49,6 +55,11 @@ Similar with above, while you can introduce your own changes as you wish. $ cd leetcode-cli && ./bin/install $ leetcode version +### From source (all-in-one) + + $ git clone http://github.com/skygragon/leetcode-cli + $ cd leetcode-cli && node ./bin/pkg + ### From docker NOTE: This is just a tiny taste to let you feel that leetcode-cli is. Please use other ways above to install leetcode-cli if you like it. From bd80b3afc44371a01ce026b523eb75b260c45d53 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 25 Nov 2018 15:47:13 +0800 Subject: [PATCH 29/45] expose more code to plugins. * leetcode.cn/lintcode could reuse this. Signed-off-by: Eric Wang --- lib/plugins/leetcode.js | 56 ++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 8a3a8d8c..a2e17b8f 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -20,24 +20,24 @@ const plugin = new Plugin(10, 'leetcode', '', var spin; // update options with user credentials -function signOpts(opts, user) { +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'; -} +}; -function makeOpts(url) { +plugin.makeOpts = function(url) { const opts = {}; opts.url = url; opts.headers = {}; if (session.isLogin()) - signOpts(opts, session.getUser()); + plugin.signOpts(opts, session.getUser()); return opts; -} +}; -function checkError(e, resp, expectedStatus) { +plugin.checkError = function(e, resp, expectedStatus) { if (!e && resp && resp.statusCode !== expectedStatus) { const code = resp.statusCode; log.debug('http error: ' + code); @@ -49,7 +49,7 @@ function checkError(e, resp, expectedStatus) { } } return e; -} +}; plugin.init = function() { config.app = 'leetcode'; @@ -80,11 +80,11 @@ plugin.getProblems = function(cb) { plugin.getCategoryProblems = function(category, cb) { log.debug('running leetcode.getCategoryProblems: ' + category); - const opts = makeOpts(config.sys.urls.problems.replace('$category', category)); + const opts = plugin.makeOpts(config.sys.urls.problems.replace('$category', category)); spin.text = 'Downloading category ' + category; request(opts, function(e, resp, body) { - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); const json = JSON.parse(body); @@ -123,7 +123,7 @@ plugin.getProblem = function(problem, cb) { const user = session.getUser(); if (problem.locked && !user.paid) return cb('failed to load locked problem!'); - const opts = makeOpts(config.sys.urls.graphql); + const opts = plugin.makeOpts(config.sys.urls.graphql); opts.headers.Origin = config.sys.urls.base; opts.headers.Referer = problem.link; @@ -149,7 +149,7 @@ plugin.getProblem = function(problem, cb) { const spin = h.spin('Downloading ' + problem.slug); request.post(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); const q = body.data.question; @@ -191,7 +191,7 @@ function runCode(opts, problem, cb) { const spin = h.spin('Sending code to judge'); request(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); if (body.error) { @@ -224,7 +224,7 @@ function verifyResult(task, queue, cb) { const spin = h.spin('Waiting for judge result'); request(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); let result = JSON.parse(body); @@ -273,7 +273,7 @@ function formatResult(result) { plugin.testProblem = function(problem, cb) { log.debug('running leetcode.testProblem'); - const opts = makeOpts(config.sys.urls.test.replace('$slug', problem.slug)); + const opts = plugin.makeOpts(config.sys.urls.test.replace('$slug', problem.slug)); opts.body = {data_input: problem.testcase}; runCode(opts, problem, function(e, task) { @@ -292,7 +292,7 @@ plugin.testProblem = function(problem, cb) { plugin.submitProblem = function(problem, cb) { log.debug('running leetcode.submitProblem'); - const opts = makeOpts(config.sys.urls.submit.replace('$slug', problem.slug)); + const opts = plugin.makeOpts(config.sys.urls.submit.replace('$slug', problem.slug)); opts.body = {judge_type: 'large'}; runCode(opts, problem, function(e, task) { @@ -308,11 +308,11 @@ plugin.submitProblem = function(problem, cb) { plugin.getSubmissions = function(problem, cb) { log.debug('running leetcode.getSubmissions'); - const opts = makeOpts(config.sys.urls.submissions.replace('$slug', problem.slug)); + 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 = checkError(e, resp, 200); + 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. @@ -326,10 +326,10 @@ plugin.getSubmissions = function(problem, cb) { plugin.getSubmission = function(submission, cb) { log.debug('running leetcode.getSubmission'); - const opts = makeOpts(config.sys.urls.submission.replace('$id', submission.id)); + const opts = plugin.makeOpts(config.sys.urls.submission.replace('$id', submission.id)); request(opts, function(e, resp, body) { - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); let re = body.match(/submissionCode:\s('[^']*')/); @@ -343,7 +343,7 @@ plugin.getSubmission = function(submission, cb) { plugin.starProblem = function(problem, starred, cb) { log.debug('running leetcode.starProblem'); - const opts = makeOpts(); + const opts = plugin.makeOpts(); opts.headers.Origin = config.sys.urls.base; opts.headers.Referer = problem.link; @@ -364,7 +364,7 @@ plugin.starProblem = function(problem, starred, cb) { } request(opts, function(e, resp, body) { - e = checkError(e, resp, 204); + e = plugin.checkError(e, resp, 204); if (e) return cb(e); cb(null, starred); @@ -373,12 +373,12 @@ plugin.starProblem = function(problem, starred, cb) { plugin.getFavorites = function(cb) { log.debug('running leetcode.getFavorites'); - const opts = makeOpts(config.sys.urls.favorites); + const opts = plugin.makeOpts(config.sys.urls.favorites); const spin = h.spin('Retrieving user favorites'); request(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); const favorites = JSON.parse(body); @@ -388,7 +388,7 @@ plugin.getFavorites = function(cb) { plugin.getUserInfo = function(cb) { log.debug('running leetcode.getUserInfo'); - const opts = makeOpts(config.sys.urls.graphql); + 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; @@ -407,7 +407,7 @@ plugin.getUserInfo = function(cb) { const spin = h.spin('Retrieving user profile'); request.post(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); const user = body.data.user; @@ -416,7 +416,7 @@ plugin.getUserInfo = function(cb) { }; function runSession(method, data, cb) { - const opts = makeOpts(config.sys.urls.session); + const opts = plugin.makeOpts(config.sys.urls.session); opts.json = true; opts.method = method; opts.body = data; @@ -424,7 +424,7 @@ function runSession(method, data, cb) { const spin = h.spin('Waiting session result'); request(opts, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e && e.statusCode === 302) e = session.errors.EXPIRED; return e ? cb(e) : cb(null, body.sessions); @@ -459,7 +459,7 @@ plugin.signin = function(user, cb) { const spin = h.spin('Signing in leetcode.com'); request(config.sys.urls.login, function(e, resp, body) { spin.stop(); - e = checkError(e, resp, 200); + e = plugin.checkError(e, resp, 200); if (e) return cb(e); user.loginCSRF = h.getSetCookieValue(resp, 'csrftoken'); From aaf64517d61c90c019b85fb59ddf59e5cbcdafc2 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 25 Nov 2018 21:35:25 +0800 Subject: [PATCH 30/45] remove sprintf-js Signed-off-by: Eric Wang --- .eslintrc.js | 1 + lib/commands/cache.js | 6 ++-- lib/commands/list.js | 13 ++++----- lib/commands/plugin.js | 3 +- lib/commands/session.js | 12 ++++---- lib/commands/show.js | 4 +-- lib/commands/star.js | 2 +- lib/commands/stat.js | 12 ++++---- lib/commands/submission.js | 2 +- lib/commands/user.js | 6 ++-- lib/log.js | 4 +-- lib/sprintf.js | 56 ++++++++++++++++++++++++++++++++++++++ package.json | 1 - test/test_log.js | 2 +- test/test_sprintf.js | 33 ++++++++++++++++++++++ 15 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 lib/sprintf.js create mode 100644 test/test_sprintf.js diff --git a/.eslintrc.js b/.eslintrc.js index 8cce4ef7..5ef04108 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { "curly": 0, "key-spacing": [2, {align: "value"}], "max-len": [1, 120], + "no-control-regex": 0, "no-console": 1, "no-empty": [2, { "allowEmptyCatch": true }], "no-eval": 1, // we use it on purpose diff --git a/lib/commands/cache.js b/lib/commands/cache.js index 30bc24b4..154a3302 100644 --- a/lib/commands/cache.js +++ b/lib/commands/cache.js @@ -1,12 +1,12 @@ 'use strict'; var _ = require('underscore'); -var sprintf = require('sprintf-js').sprintf; var h = require('../helper'); var chalk = require('../chalk'); var log = require('../log'); var cache = require('../cache'); var session = require('../session'); +var sprintf = require('../sprintf'); const cmd = { command: 'cache [keyword]', @@ -56,8 +56,8 @@ cmd.handler = function(argv) { return x; }) .forEach(function(f) { - log.printf(' %s %8s %s ago', - chalk.green(sprintf('%-60s', f.name)), + log.printf(' %-60s %8s %s ago', + chalk.green(f.name), h.prettySize(f.size), h.prettyTime((Date.now() - f.mtime) / 1000)); }); diff --git a/lib/commands/list.js b/lib/commands/list.js index 67427f89..c010de86 100644 --- a/lib/commands/list.js +++ b/lib/commands/list.js @@ -1,6 +1,5 @@ 'use strict'; var _ = require('underscore'); -var sprintf = require('sprintf-js').sprintf; var h = require('../helper'); var chalk = require('../chalk'); @@ -67,14 +66,14 @@ cmd.handler = function(argv) { if (problem.locked) ++stat.locked; if (problem.starred) ++stat.starred; - log.printf('%s %s %s [%4d] %-60s %-6s (%.2f %%)', + log.printf('%s %s %s [%=4s] %-60s %-6s (%s %%)', (problem.starred ? chalk.yellow(icon.like) : icon.empty), (problem.locked ? chalk.red(icon.lock) : icon.nolock), h.prettyState(problem.state), problem.fid, problem.name, - h.prettyLevel(sprintf('%-6s', problem.level)), - problem.percent); + h.prettyLevel(problem.level), + problem.percent.toFixed(2)); if (argv.extra) { let badges = [problem.category]; @@ -99,9 +98,9 @@ cmd.handler = function(argv) { if (argv.stat) { log.info(); - log.printf(' Listed: %-9d Locked: %-9d Starred: %-9d', problems.length, stat.locked, stat.starred); - log.printf(' Accept: %-9d Not-AC: %-9d Remain: %-9d', stat.ac, stat.notac, stat.None); - log.printf(' Easy: %-9d Medium: %-9d Hard: %-9d', stat.Easy, stat.Medium, stat.Hard); + 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); } }); }; diff --git a/lib/commands/plugin.js b/lib/commands/plugin.js index 623fd513..78a966b5 100644 --- a/lib/commands/plugin.js +++ b/lib/commands/plugin.js @@ -1,12 +1,11 @@ 'use strict'; -var sprintf = require('sprintf-js').sprintf; - var h = require('../helper'); var chalk = require('../chalk'); var config = require('../config'); var log = require('../log'); var Plugin = require('../plugin'); var session = require('../session'); +var sprintf = require('../sprintf'); const cmd = { command: 'plugin [name]', diff --git a/lib/commands/session.js b/lib/commands/session.js index d348dea5..64d460d6 100644 --- a/lib/commands/session.js +++ b/lib/commands/session.js @@ -1,12 +1,12 @@ 'use strict'; var prompt = require('prompt'); -var sprintf = require('sprintf-js').sprintf; var h = require('../helper'); var chalk = require('../chalk'); var log = require('../log'); var core = require('../core'); var session = require('../session'); +var sprintf = require('../sprintf'); const cmd = { command: 'session [keyword]', @@ -61,14 +61,14 @@ function printSessions(e, sessions) { if (s.total_submitted > 0) submissionRate = s.total_acs * 100 / s.total_submitted; - log.printf(' %s %8d %-26s %s (%6s %%) %s (%6s %%)', + log.printf(' %s %8s %-26s %6s (%6s %%) %6s (%6s %%)', s.is_active ? h.prettyState('ac') : ' ', s.id, s.name || 'Anonymous Session', - chalk.green(sprintf('%6s', s.ac_questions)), - sprintf('%.2f', questionRate), - chalk.green(sprintf('%6s', s.total_acs)), - sprintf('%.2f', submissionRate)); + chalk.green(s.ac_questions), + questionRate.toFixed(2), + chalk.green(s.total_acs), + submissionRate.toFixed(2)); } } diff --git a/lib/commands/show.js b/lib/commands/show.js index 319d5d53..7c66204a 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -137,7 +137,7 @@ function showProblem(problem, argv) { } } - log.printf('[%d] %s %s', problem.fid, problem.name, + log.printf('[%s] %s %s', problem.fid, problem.name, (problem.starred ? chalk.yellow(icon.like) : icon.empty)); log.info(); log.info(chalk.underline(problem.link)); @@ -150,7 +150,7 @@ function showProblem(problem, argv) { log.info(); log.printf('* %s', problem.category); - log.printf('* %s (%.2f%%)', h.prettyLevel(problem.level), problem.percent); + log.printf('* %s (%s%%)', h.prettyLevel(problem.level), problem.percent.toFixed(2)); if (filename) log.printf('* Source Code: %s', chalk.yellow.underline(filename)); diff --git a/lib/commands/star.js b/lib/commands/star.js index 991e10e4..3660432b 100644 --- a/lib/commands/star.js +++ b/lib/commands/star.js @@ -35,7 +35,7 @@ cmd.handler = function(argv) { core.starProblem(problem, !argv.delete, function(e, starred) { if (e) return log.fail(e); - log.printf('[%d] %s %s', problem.fid, problem.name, + log.printf('[%s] %s %s', problem.fid, problem.name, chalk.yellow(starred ? icon.like : icon.unlike)); core.updateProblem(problem, {starred: starred}); diff --git a/lib/commands/stat.js b/lib/commands/stat.js index 54f7a41a..5a4ffd57 100644 --- a/lib/commands/stat.js +++ b/lib/commands/stat.js @@ -1,6 +1,5 @@ 'use strict'; var moment = require('moment'); -var sprintf = require('sprintf-js').sprintf; var _ = require('underscore'); var chalk = require('../chalk'); @@ -8,6 +7,7 @@ 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 = { @@ -50,9 +50,9 @@ 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%3d/%-3d (%6s %%) %s%s', + log.printf(' %s\t%3s/%-3s (%6s %%) %s%s', h.prettyLevel(key), done, all, - sprintf('%.2f', 100 * percent), + (100 * percent).toFixed(2), chalk.green('█'.repeat(x)), chalk.red('░'.repeat(n - x))); } @@ -96,7 +96,7 @@ function showGraph(problems) { if (groups > 5) groups = 5; const header = _.range(groups) - .map(x => sprintf('%4d%18d', x * 10 + 1, x * 10 + 10)) + .map(x => sprintf('%4s%18s', x * 10 + 1, x * 10 + 10)) .join(''); log.info(' ' + header); @@ -104,7 +104,7 @@ function showGraph(problems) { for (let problem of problems) graph[problem.fid] = ICONS[problem.state] || ICONS.none; - let line = [sprintf(' %03d', 0)]; + let line = [sprintf('%04s', 0)]; for (let i = 1, n = graph.length; i <= n; ++i) { // padding before group if (i % 10 === 1) line.push(' '); @@ -114,7 +114,7 @@ function showGraph(problems) { // time to start new row if (i % (10 * groups) === 0 || i === n) { log.info(line.join(' ')); - line = [sprintf(' %03d', i)]; + line = [sprintf('%04s', i)]; } } diff --git a/lib/commands/submission.js b/lib/commands/submission.js index 6c9c6909..de0449a3 100644 --- a/lib/commands/submission.js +++ b/lib/commands/submission.js @@ -62,7 +62,7 @@ function doTask(problem, queue, cb) { // - green: accepted, fresh download // - yellow: not ac-ed, fresh download // - white: existed already, skip download - log.printf('[%4d] %-60s %s', problem.fid, problem.name, + log.printf('[%=4s] %-60s %s', problem.fid, problem.name, (e ? chalk.red('ERROR: ' + (e.msg || e)) : msg)); if (cb) cb(e); } diff --git a/lib/commands/user.js b/lib/commands/user.js index af9ea4c2..4cd903cd 100644 --- a/lib/commands/user.js +++ b/lib/commands/user.js @@ -1,6 +1,5 @@ 'use strict'; var prompt = require('prompt'); -var sprintf = require('sprintf-js').sprintf; var h = require('../helper'); var config = require('../config'); @@ -8,6 +7,7 @@ var chalk = require('../chalk'); var log = require('../log'); var core = require('../core'); var session = require('../session'); +var sprintf = require('../sprintf'); const cmd = { command: 'user', @@ -65,9 +65,9 @@ cmd.handler = function(argv) { if (user) { log.info(chalk.gray(sprintf(' %-9s %-20s %s', 'Premium', 'User', 'Host'))); log.info(chalk.gray('-'.repeat(60))); - log.printf(' %s %s %s', + log.printf(' %s %-20s %s', h.prettyText('', user.paid || false), - chalk.yellow(sprintf('%-20s', user.name)), + chalk.yellow(user.name), config.sys.urls.base); } else return log.fail('You are not login yet?'); diff --git a/lib/log.js b/lib/log.js index acccc73c..394b356c 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('underscore'); -var sprintf = require('sprintf-js').sprintf; var chalk = require('./chalk'); +var sprintf = require('./sprintf'); const log = { output: _.bind(console.log, console), @@ -27,7 +27,7 @@ log.isEnabled = function(name) { log.fail = function(e) { let msg = sprintf('%s', (e.msg || e)); if (e.statusCode) { - msg += sprintf(' [code=%d]', e.statusCode); + msg += sprintf(' [code=%s]', e.statusCode); } log.error(msg); }; diff --git a/lib/sprintf.js b/lib/sprintf.js new file mode 100644 index 00000000..133e32c8 --- /dev/null +++ b/lib/sprintf.js @@ -0,0 +1,56 @@ +'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; + default: + n = parseInt(s) || 0; + x = padLeft(x, n, ' '); + break; + } + } + + return x; + }); +}; + +module.exports = tsprintf; diff --git a/package.json b/package.json index 7453aa5b..5c043b8d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "ora": "3.0.0", "prompt": "1.0.0", "request": "2.88.0", - "sprintf-js": "1.1.1", "supports-color": "5.5.0", "underscore": "1.9.1", "wordwrap": "1.0.0", diff --git a/test/test_log.js b/test/test_log.js index a749af50..92e5ef4b 100644 --- a/test/test_log.js +++ b/test/test_log.js @@ -102,7 +102,7 @@ describe('log', function() { describe('#printf', function() { it('should ok', function() { - log.printf('%s and %d and %%', 'string', 100); + log.printf('%s and %s and %%', 'string', 100); assert.equal(expected, 'string and 100 and %'); }); }); // #printf diff --git a/test/test_sprintf.js b/test/test_sprintf.js new file mode 100644 index 00000000..8b509903 --- /dev/null +++ b/test/test_sprintf.js @@ -0,0 +1,33 @@ +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); + +const sprintf = require('../lib/sprintf'); + +describe('sprintf', function() { + it('should ok', function() { + assert.equal(sprintf('%%'), '%'); + assert.equal(sprintf('%s', 123), '123'); + assert.equal(sprintf('%6s', 123), ' 123'); + assert.equal(sprintf('%-6s', 123), '123 '); + assert.equal(sprintf('%=6s', 123), ' 123 '); + + assert.equal(sprintf('%4s,%=4s,%-4s', 123, 'xy', 3.1), ' 123, xy ,3.1 '); + }); + + it('should non-ascii ok', function() { + assert.equal(sprintf('%4s', '中'), ' 中'); + assert.equal(sprintf('%-4s', '中'), '中 '); + assert.equal(sprintf('%=4s', '中'), ' 中 '); + + assert.equal(sprintf('%=14s', '12你好34世界'), ' 12你好34世界 '); + }); + + it('should color ok', function() { + const chalk = rewire('../lib/chalk'); + chalk.init(); + chalk.setTheme('default'); + + assert.equal(sprintf('%=3s', chalk.red('X')), ' \u001b[38;5;196mX\u001b[39m '); + }); +}); From 168b0f41f502db038ccae29fea841d48b98f1550 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 25 Nov 2018 21:46:05 +0800 Subject: [PATCH 31/45] fixes UT bug. Signed-off-by: Eric Wang --- lib/commands/stat.js | 4 ++-- lib/commands/version.js | 2 +- lib/sprintf.js | 4 ++++ test/test_sprintf.js | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/commands/stat.js b/lib/commands/stat.js index 5a4ffd57..3c51ab98 100644 --- a/lib/commands/stat.js +++ b/lib/commands/stat.js @@ -104,7 +104,7 @@ function showGraph(problems) { for (let problem of problems) graph[problem.fid] = ICONS[problem.state] || ICONS.none; - let line = [sprintf('%04s', 0)]; + let line = [sprintf(' %03s', 0)]; for (let i = 1, n = graph.length; i <= n; ++i) { // padding before group if (i % 10 === 1) line.push(' '); @@ -114,7 +114,7 @@ function showGraph(problems) { // time to start new row if (i % (10 * groups) === 0 || i === n) { log.info(line.join(' ')); - line = [sprintf('%04s', i)]; + line = [sprintf(' %03s', i)]; } } diff --git a/lib/commands/version.js b/lib/commands/version.js index 257f631a..4ba16749 100644 --- a/lib/commands/version.js +++ b/lib/commands/version.js @@ -47,7 +47,7 @@ cmd.handler = function(argv) { '| | ___ ___| |_ ___ ___ __| | ___ ', '| |/ _ \\/ _ \\ __|/ __|/ _ \\ / _` |/ _ \\', '| | __/ __/ |_ (__| (_) | (_| | __/', - '|_|\\___|\\___|\\__|\\___|\\___/ \\__,_|\\___| CLI v' + version + '|_|\\___|\\___|\\__|\\___|\\___/ \\__,_|\\___| CLI ' + chalk.green('v' + version) ].join('\n'); log.info(logo); diff --git a/lib/sprintf.js b/lib/sprintf.js index 133e32c8..739f3d06 100644 --- a/lib/sprintf.js +++ b/lib/sprintf.js @@ -42,6 +42,10 @@ const tsprintf = function() { 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, ' '); diff --git a/test/test_sprintf.js b/test/test_sprintf.js index 8b509903..aed7cbc3 100644 --- a/test/test_sprintf.js +++ b/test/test_sprintf.js @@ -9,6 +9,7 @@ describe('sprintf', function() { assert.equal(sprintf('%%'), '%'); assert.equal(sprintf('%s', 123), '123'); assert.equal(sprintf('%6s', 123), ' 123'); + assert.equal(sprintf('%06s', 123), '000123'); assert.equal(sprintf('%-6s', 123), '123 '); assert.equal(sprintf('%=6s', 123), ' 123 '); @@ -26,8 +27,7 @@ describe('sprintf', function() { it('should color ok', function() { const chalk = rewire('../lib/chalk'); chalk.init(); - chalk.setTheme('default'); - assert.equal(sprintf('%=3s', chalk.red('X')), ' \u001b[38;5;196mX\u001b[39m '); + assert.equal(sprintf('%=3s', chalk.red('X')), ' ' + chalk.red('X') + ' '); }); }); From c95cea8bab7cc8cd111414a88d5650d5c5e35e81 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Fri, 30 Nov 2018 20:40:31 +0800 Subject: [PATCH 32/45] refs #134: remove whitespace on windows. Signed-off-by: Eric Wang --- lib/file.js | 2 +- test/test_file.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/file.js b/lib/file.js index 493e9ebd..51ea21c3 100644 --- a/lib/file.js +++ b/lib/file.js @@ -149,7 +149,7 @@ file.meta = function(filename) { line.split(' ').forEach(function(x) { const v = x.split('='); if (v.length == 2) { - m[v[0]] = v[1]; + m[v[0]] = v[1].trim(); } }); diff --git a/test/test_file.js b/test/test_file.js index 16d04ba3..d458e83f 100644 --- a/test/test_file.js +++ b/test/test_file.js @@ -81,6 +81,18 @@ describe('file', function() { assert.equal(meta.lang, 'javascript'); }); + it('should meta ok with white space', function() { + file.data = x => [ + '/ *', + ' * @lc app=leetcode id=123\t \t lang=javascript\r', + ' * /' + ].join('\n'); + const meta = file.meta('dummy'); + assert.equal(meta.app, 'leetcode') + assert.equal(meta.id, '123'); + assert.equal(meta.lang, 'javascript'); + }); + it('should meta ok within file name', function() { file.data = x => [ '/ *', From 99f921ec806e59dce7386c9429e4d92f5a3fbcb3 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 3 Dec 2018 23:05:54 +0800 Subject: [PATCH 33/45] Bump to 2.6.1 Signed-off-by: Eric Wang --- docs/commands.md | 4 ++-- docs/releases.md | 4 ++++ package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 06cfebaa..2eda14a7 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -465,7 +465,7 @@ Display version information. Short: $ leetcode version - 2.6.0 + 2.6.1 Verbose: @@ -475,7 +475,7 @@ Verbose: | | ___ ___| |_ ___ ___ __| | ___ | |/ _ \/ _ \ __|/ __|/ _ \ / _` |/ _ \ | | __/ __/ |_ (__| (_) | (_| | __/ - |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.6.0 + |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.6.1 [Environment] Node v8.1.4 diff --git a/docs/releases.md b/docs/releases.md index c26de3dc..7b78a5f3 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,10 @@ layout: default title: Release Notes --- +# 2.6.1 +* `submit` + * fixes 500 error on windows. + # 2.6.0 * build all-in-one binary for linux/macos/windows. * `show` diff --git a/package.json b/package.json index 5c043b8d..ee8f2414 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode-cli", - "version": "2.6.0", + "version": "2.6.1", "description": "A cli tool to enjoy leetcode!", "preferGlobal": "true", "engines": { From 1b5ea18f15b4189c05385e4dc00017128bd54de7 Mon Sep 17 00:00:00 2001 From: sunway Date: Fri, 21 Dec 2018 11:18:42 +0800 Subject: [PATCH 34/45] add rust lang --- lib/config.js | 1 + lib/helper.js | 1 + test/test_helper.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/config.js b/lib/config.js index 692b958d..cd849fae 100644 --- a/lib/config.js +++ b/lib/config.js @@ -25,6 +25,7 @@ const DEFAULT_CONFIG = { 'python', 'python3', 'ruby', + 'rust', 'scala', 'swift' ], diff --git a/lib/helper.js b/lib/helper.js index 5ca496aa..c2085134 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -42,6 +42,7 @@ const LANGS = [ {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'} ]; diff --git a/test/test_helper.js b/test/test_helper.js index cfc502d4..6eb6e106 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -133,7 +133,7 @@ describe('helper', function() { assert.equal(h.langToExt('ruby'), '.rb'); assert.equal(h.langToExt('scala'), '.scala'); assert.equal(h.langToExt('swift'), '.swift'); - assert.equal(h.langToExt('rust'), '.raw'); + assert.equal(h.langToExt('rust'), '.rs'); }); }); // #langToExt @@ -152,6 +152,7 @@ describe('helper', function() { assert.equal(h.extToLang('~/leetcode/file.swift'), 'swift'); assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); assert.equal(h.extToLang('/home/skygragon/file.dat'), 'unknown'); + assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); }); }); // #extToLang From 2287d334e0e92cacf06a19faf8bd1e67536cdc77 Mon Sep 17 00:00:00 2001 From: "sheche@microsoft.com" Date: Sat, 22 Dec 2018 16:31:44 +0800 Subject: [PATCH 35/45] Replace the with '^' --- lib/plugins/leetcode.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index a2e17b8f..373aadfe 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -157,11 +157,12 @@ plugin.getProblem = function(problem, cb) { problem.totalAC = JSON.parse(q.stats).totalAccepted; problem.totalSubmit = JSON.parse(q.stats).totalSubmission; - if (!q.translatedContent) { - problem.desc = he.decode(cheerio.load(q.content).root().text()); - }else{ - problem.desc = he.decode(cheerio.load(q.translatedContent).root().text()); - } + + let content = q.translatedContent ? q.translatedContent : q.content; + // Replace with '^' as the power operator + content = content.replace(/<\/sup>/gm, '').replace(//gm, '^'); + problem.desc = he.decode(cheerio.load(content).root().text()); + problem.templates = JSON.parse(q.codeDefinition); problem.testcase = q.sampleTestCase; problem.testable = q.enableRunCode; From 1b217b9fcee57bb9d8df0f320b06689cfd45d9eb Mon Sep 17 00:00:00 2001 From: zeke Date: Tue, 8 Jan 2019 22:27:18 -0500 Subject: [PATCH 36/45] minor spelling update --- lib/commands/plugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/commands/plugin.js b/lib/commands/plugin.js index 78a966b5..ca5a9cff 100644 --- a/lib/commands/plugin.js +++ b/lib/commands/plugin.js @@ -52,10 +52,10 @@ const cmd = { .example(chalk.yellow('leetcode plugin company'), 'Show company plugin') .example(chalk.yellow('leetcode plugin company -c'), 'Show config of company plugin') .example('', '') - .example(chalk.yellow('leetcode plugin -i'), 'Install all missing plugins from GtiHub') - .example(chalk.yellow('leetcode plugin -i company'), 'Install company plugin from GtiHub') + .example(chalk.yellow('leetcode plugin -i'), 'Install all missing plugins from GitHub') + .example(chalk.yellow('leetcode plugin -i company'), 'Install company plugin from GitHub') .example(chalk.yellow('leetcode plugin -d company'), 'Disable company plugin') - .example(chalk.yellow('leetcode plugin -e company'), 'Enable comapny plugin') + .example(chalk.yellow('leetcode plugin -e company'), 'Enable company plugin') .example(chalk.yellow('leetcode plugin -D company'), 'Delete company plugin'); } }; From 8dc0b2ab9b04771bfeef29f02e52371ff8f16db6 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 3 Feb 2019 11:31:08 +0800 Subject: [PATCH 37/45] refs #139: customized delay before next try Signed-off-by: Eric Wang --- lib/config.js | 3 ++- lib/plugins/leetcode.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index cd849fae..a427b8f3 100644 --- a/lib/config.js +++ b/lib/config.js @@ -68,7 +68,8 @@ const DEFAULT_CONFIG = { theme: '' }, network: { - concurrency: 10 + concurrency: 10, + delay: 1 }, plugins: {} }; diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 373aadfe..0ce3a66e 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -179,7 +179,7 @@ function runCode(opts, problem, cb) { opts.headers.Origin = config.sys.urls.base; opts.headers.Referer = problem.link; opts.json = true; - opts._delay = opts._delay || 1; // in seconds + opts._delay = opts._delay || config.network.delay || 1; // in seconds opts.body = opts.body || {}; _.extendOwn(opts.body, { From 694c42a5a3b22334fa1744d53a5910e32ffe0644 Mon Sep 17 00:00:00 2001 From: PoppinL Date: Fri, 22 Feb 2019 12:05:19 +0800 Subject: [PATCH 38/45] add kotlin in language param --- docs/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands.md b/docs/commands.md index 2eda14a7..703f211b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -234,6 +234,7 @@ Display question details. With `-g`/`-l`/`-x`, the code template would be auto g * golang * java * javascript + * kotlin * mysql * python * python3 From 464257cdd0c242e820f1b8237e3782e89f5ff29c Mon Sep 17 00:00:00 2001 From: PoppinL Date: Tue, 26 Feb 2019 11:29:00 +0800 Subject: [PATCH 39/45] feat: add php lang --- lib/config.js | 1 + lib/helper.js | 1 + test/test_helper.js | 10 +++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/config.js b/lib/config.js index a427b8f3..373b9f0f 100644 --- a/lib/config.js +++ b/lib/config.js @@ -22,6 +22,7 @@ const DEFAULT_CONFIG = { 'javascript', 'kotlin', 'mysql', + 'php', 'python', 'python3', 'ruby', diff --git a/lib/helper.js b/lib/helper.js index c2085134..8806086e 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -39,6 +39,7 @@ const LANGS = [ {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: '#'}, diff --git a/test/test_helper.js b/test/test_helper.js index 6eb6e106..143bda9e 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -128,12 +128,13 @@ describe('helper', function() { assert.equal(h.langToExt('java'), '.java'); assert.equal(h.langToExt('javascript'), '.js'); assert.equal(h.langToExt('mysql'), '.sql'); + assert.equal(h.langToExt('php'), '.php'); assert.equal(h.langToExt('python'), '.py'); assert.equal(h.langToExt('python3'), '.py'); assert.equal(h.langToExt('ruby'), '.rb'); + assert.equal(h.langToExt('rust'), '.rs'); assert.equal(h.langToExt('scala'), '.scala'); assert.equal(h.langToExt('swift'), '.swift'); - assert.equal(h.langToExt('rust'), '.rs'); }); }); // #langToExt @@ -146,13 +147,14 @@ describe('helper', function() { assert.equal(h.extToLang('../file.go'), 'golang'); assert.equal(h.extToLang('file.java'), 'java'); assert.equal(h.extToLang('c:/file.js'), 'javascript'); + assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); + assert.equal(h.extToLang('~/leetcode/hello.php'), 'php'); assert.equal(h.extToLang('c:/Users/skygragon/file.py'), 'python'); assert.equal(h.extToLang('~/file.rb'), 'ruby'); + assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); assert.equal(h.extToLang('/tmp/file.scala'), 'scala'); assert.equal(h.extToLang('~/leetcode/file.swift'), 'swift'); - assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); assert.equal(h.extToLang('/home/skygragon/file.dat'), 'unknown'); - assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); }); }); // #extToLang @@ -170,9 +172,11 @@ describe('helper', function() { assert.deepEqual(h.langToCommentStyle('java'), C_STYLE); assert.deepEqual(h.langToCommentStyle('javascript'), C_STYLE); assert.deepEqual(h.langToCommentStyle('mysql'), SQL_STYLE); + assert.deepEqual(h.langToCommentStyle('php'), C_STYLE); assert.deepEqual(h.langToCommentStyle('python'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('python3'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('ruby'), RUBY_STYLE); + assert.deepEqual(h.langToCommentStyle('rust'), C_STYLE); assert.deepEqual(h.langToCommentStyle('scala'), C_STYLE); assert.deepEqual(h.langToCommentStyle('swift'), C_STYLE); }); From 04250608ea5d12d8eef1caf99b0d3890da15abe7 Mon Sep 17 00:00:00 2001 From: PoppinL Date: Tue, 26 Feb 2019 11:32:11 +0800 Subject: [PATCH 40/45] docs: add php in commands doc --- docs/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands.md b/docs/commands.md index 2eda14a7..c8467e14 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -235,6 +235,7 @@ Display question details. With `-g`/`-l`/`-x`, the code template would be auto g * java * javascript * mysql + * php * python * python3 * ruby From fe9a9a916ca6a7aad05db53d21dc68f5fdc30abf Mon Sep 17 00:00:00 2001 From: Le Beier Date: Sun, 7 Jul 2019 21:36:39 +0800 Subject: [PATCH 41/45] fixed the row number format for `leetcode stat -g` --- lib/commands/stat.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/commands/stat.js b/lib/commands/stat.js index 3c51ab98..772499c4 100644 --- a/lib/commands/stat.js +++ b/lib/commands/stat.js @@ -104,7 +104,8 @@ function showGraph(problems) { for (let problem of problems) graph[problem.fid] = ICONS[problem.state] || ICONS.none; - let line = [sprintf(' %03s', 0)]; + let rowNumFormat = ' %04s'; + let line = [sprintf(rowNumFormat, 0)]; for (let i = 1, n = graph.length; i <= n; ++i) { // padding before group if (i % 10 === 1) line.push(' '); @@ -114,7 +115,7 @@ function showGraph(problems) { // time to start new row if (i % (10 * groups) === 0 || i === n) { log.info(line.join(' ')); - line = [sprintf(' %03s', i)]; + line = [sprintf(rowNumFormat, i)]; } } From 9a0e155ea7f095807366f73348df8dcf69b5d1c0 Mon Sep 17 00:00:00 2001 From: Sparkles <34909275+SparklesCN@users.noreply.github.com> Date: Wed, 7 Aug 2019 08:46:02 +0800 Subject: [PATCH 42/45] Update submit.js fixed bug for "[WARN] Failed to get submission beat ratio." --- lib/commands/submit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 7376598b..56f5ed04 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -76,7 +76,7 @@ cmd.handler = function(argv) { let ratio = 0.0; for (let score of scores) { - if (parseFloat(score[0]) > myRuntime) + if (parseFloat(score[0]) >= myRuntime) ratio += parseFloat(score[1]); } From 4657db67d7834a99aa5049691d5e7d5ade3c157a Mon Sep 17 00:00:00 2001 From: Sparkles <34909275+SparklesCN@users.noreply.github.com> Date: Wed, 7 Aug 2019 08:46:45 +0800 Subject: [PATCH 43/45] Update leetcode.js fixed bug for "[WARN] Failed to get submission beat ratio." --- lib/plugins/leetcode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 0ce3a66e..6624f6d5 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -336,7 +336,7 @@ plugin.getSubmission = function(submission, cb) { let re = body.match(/submissionCode:\s('[^']*')/); if (re) submission.code = eval(re[1]); - re = body.match(/distribution_formatted:\s('[^']+')/); + re = body.match(/runtimeDistributionFormatted:\s('[^']+')/); if (re) submission.distributionChart = JSON.parse(eval(re[1])); return cb(null, submission); }); From 183d62ddd54ae8707fd7ac83829b2233fbe10af2 Mon Sep 17 00:00:00 2001 From: nnnevermore Date: Thu, 19 Sep 2019 21:55:41 +0800 Subject: [PATCH 44/45] Bug fix for Issue #177 I encountered the same bug as Issue#177, so I created this PR. --- lib/plugins/leetcode.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 0ce3a66e..19afecf4 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -261,7 +261,13 @@ function formatResult(result) { x.expected_answer = result.expected_output; x.stdout = result.std_output; } else { - x.stdout = util.inspect((result.code_output || []).join('\n')); + if (typeof(result.code_output) === 'string') { + x.stdout = util.inspect(result.code_output); + } else if (Array.isArray(result.code_output)) { + x.stdout = util.inspect(result.code_output.join('\n')); + } else { + x.stdout = util.inspect(''); + } } // make sure we pass eveything! From 5245886992ceb64bfb322256b2bdc24184b36d76 Mon Sep 17 00:00:00 2001 From: skygragon Date: Sun, 29 Sep 2019 08:13:44 +0800 Subject: [PATCH 45/45] Bump to 2.6.2 Signed-off-by: skygragon --- docs/commands.md | 4 ++-- docs/releases.md | 4 ++++ package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 0908aec1..1893dbc4 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -467,7 +467,7 @@ Display version information. Short: $ leetcode version - 2.6.1 + 2.6.2 Verbose: @@ -477,7 +477,7 @@ Verbose: | | ___ ___| |_ ___ ___ __| | ___ | |/ _ \/ _ \ __|/ __|/ _ \ / _` |/ _ \ | | __/ __/ |_ (__| (_) | (_| | __/ - |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.6.1 + |_|\___|\___|\__|\___|\___/ \__,_|\___| CLI v2.6.2 [Environment] Node v8.1.4 diff --git a/docs/releases.md b/docs/releases.md index 7b78a5f3..77cce5eb 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,10 @@ layout: default title: Release Notes --- +# 2.6.2 +* `submit` + * fixes beta ratio issue + # 2.6.1 * `submit` * fixes 500 error on windows. diff --git a/package.json b/package.json index ee8f2414..a9af9f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode-cli", - "version": "2.6.1", + "version": "2.6.2", "description": "A cli tool to enjoy leetcode!", "preferGlobal": "true", "engines": {