diff --git a/.gitignore b/.gitignore index ebcaaa3..001b9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /node_modules -/dist +# /build /tests /*.cpp diff --git a/README.md b/README.md index 516bc09..c39cd36 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,66 @@ # Codeforces project generator -**Warning:** This is a dev version, might be unstable or have errors. +**Warning:** This is a dev version - it might be unstable or have errors, because it has not been tested properly. Feel free to test it yourself and report errors. ## Instalation - ``` npm install -g https://github.com/tao24/gcg.git#dev ``` -## Local building and testing - -1. Clone repository: +## Usage instruction +### Directory management ``` -git clone -b dev https://github.com/tao24/gcg.git gcg-dev -cd gcg-dev +gcg init [--overwrite] [--input-only] ``` -2. Intall, build and run project locally: +Initializes directory with template `.cpp` file and tests (you should start copying tests to clipboard). -``` -npm install -npm run build -npm start -``` +Flag `--input-only` makes it so it creates only `*.in` files, omitting `*.out` files. Make sure to create `[task-name]_out.exe` validator file working as specified below. -3. (optional) Before publishing (pushing to repository), remember to build in production mode first: +### Testing ``` -npm run build:prod +gcg run [-f ] [-t ] [--no-compile] ``` +Runs specified task on its tests (tests in folder `tests` which begin with `` and all test in folder `tests/`). +#### Available options: +- `-f, --folder ` runs program on all tests in specified folder, +- `-t, --test ` runs program only on specified test (but it must be in either `tests` folder, in `tests/` or in folder specified by `-f `), +- `--no-compile` turns off compiling program before testing. +If no corresponding `.out` or `.ans` files are found, then it tries to use `_out[.exe]` executable to validate program. Validator should work as follows: -## Usage instruction +1. read from stdin test specification (`.in` file) +2. read from stdin your programs' output +3. validate output +4. if result is ok, return with no result. If not, it should print non-empty string on standard output (it will be displayed in console during testing). + +**Note**: Validator runs ```g++ -std=c++17 .cpp -o ```. Make sure you have a c++ compiler. + + +## Local building and testing + +1. Clone repository: -### Directory management ``` -gcg init [task-name] [--overwrite] +git clone -b dev https://github.com/tao24/gcg.git gcg-dev +cd gcg-dev ``` -Initializes directory with template ```.cpp``` files and tests (you should start copying tests to clipboard). If [task-name] is specified, then it only initializes this task. - -### Testing +2. To install and run project locally: ``` -gcg run [--no-compile] +npm install +npm start -- ``` -Runs specified task on its tests (any test that begins with ``````). Flag ```--no-compile``` turns off compiling. +3. (optional) To install CLI from local folder: + +``` +npm run build +npm install -g +``` -**Note**: Validator runs ```g++ -std=c++17 .cpp -o ```. Make sure it works. + diff --git a/app/index.js b/app/index.js deleted file mode 100644 index 970d799..0000000 --- a/app/index.js +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env node -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const minimist_1 = __importDefault(require("minimist")); -const init_1 = require("./src/init"); -const validate_1 = require("./src/validate"); -const interactive_1 = require("./src/interactive"); -const fs_1 = require("fs"); -const args = minimist_1.default(process.argv.slice(2), { - default: { - compile: true, - log: true, - interactive: true, - tasks: 5, - tests: 2 - } -}); -if (args._[0] === 'init' || args._[0] === 'i') { - if (args.interactive) { - const interactive = new interactive_1.Interactive(args); - interactive.start(); - } - else { - const initializer = new init_1.Initializer(args); - initializer.start(); - } -} -else if (args._[0] === 'run' || args._[0] === 'r') { - const validator = new validate_1.Validator(args); - validator.start(); -} -else if (args._[0] === 'help' || args._[0] === 'h' || !args._[0]) { - fs_1.readFile('./res/help', (err, data) => { - console.log(data.toString()); - }); -} diff --git a/app/res/templates/main.cpp b/app/res/templates/main.cpp deleted file mode 100644 index c13afbc..0000000 --- a/app/res/templates/main.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -using namespace std; - -int main() { - ios_base::sync_with_stdio(false); - cin.tie(0); - - - - return 0; -} \ No newline at end of file diff --git a/app/src/init.js b/app/src/init.js deleted file mode 100644 index 9c36678..0000000 --- a/app/src/init.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = require("fs"); -const logger_1 = require("./logger"); -class Initializer { - constructor(args) { - this.args = args; - this.sampleProgram = fs_1.readFileSync(__dirname + '/../res/templates/main.cpp'); - this.logger = new logger_1.Logger(args); - this.tasks = +args.tasks; - this.tests = +args.tests; - this.overwrite = args.overwrite; - } - start() { - if (this.tasks < 0 || this.tests < 0) { - this.logger.error('Please specify valid number of tasks and tests!'); - } - else { - this.generateDirectory(); - } - } - generateTests() { - for (let t = 0; t < this.tasks; t++) { - for (let i = 0; i < this.tests; i++) { - var inPath = './tests/' + String.fromCharCode(97 + t) + i + '.in'; - var outPath = './tests/' + String.fromCharCode(97 + t) + i + '.out'; - if (this.overwrite || !fs_1.existsSync(inPath)) { - fs_1.writeFile(inPath, '', this.logger.callback('Created file: ' + inPath)); - } - if (this.overwrite || !fs_1.existsSync(outPath)) { - fs_1.writeFile(outPath, '', this.logger.callback('Created file: ' + outPath)); - } - } - } - } - generateDirectory() { - fs_1.exists('./tests', (exists) => { - if (!exists) { - fs_1.mkdir('./tests', (err) => { - if (err) { - this.logger.error(err.message); - } - else { - this.logger.log("Created directory: ./tests"); - this.generateTests(); - } - }); - } - else { - this.generateTests(); - } - }); - for (let i = 0; i < this.tasks; i++) { - var a = './' + String.fromCharCode(97 + i) + '.cpp'; - if (this.overwrite || !fs_1.existsSync(a)) { - fs_1.writeFile(a, this.sampleProgram, this.logger.callback('Created file: ' + a)); - } - } - } -} -exports.Initializer = Initializer; diff --git a/app/src/input.js b/app/src/input.js deleted file mode 100644 index 6667958..0000000 --- a/app/src/input.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function input(args) { -} -exports.input = input; diff --git a/app/src/interactive.js b/app/src/interactive.js deleted file mode 100644 index ad78a9e..0000000 --- a/app/src/interactive.js +++ /dev/null @@ -1,106 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const clipboardy_1 = __importDefault(require("clipboardy")); -const logger_1 = require("./logger"); -const fs_1 = require("fs"); -class Test { - constructor(input, output) { - this.input = input; - this.output = output; - } -} -class Interactive { - constructor(args) { - this.args = args; - this.tests = []; - this.listen = true; - this.sampleProgram = fs_1.readFileSync(__dirname + '/../res/templates/main.cpp'); - this.logger = new logger_1.Logger(args); - } - start() { - if (this.args._[1]) { // Single task mode - console.log(`Copy tests or type 'q' to quit`); - clipboardy_1.default.writeSync(""); // reset clipboard - this.clipContent = clipboardy_1.default.readSync(); - this.listenClipboard(); - process.stdin.addListener("data", (data) => { - if (data.toString()[0] == 'q') { - this.createTask(this.args._[1]); - this.tests = []; - process.exit(0); - } - else { - console.log(`Copy tests or type 'q' to quit.`); - } - }); - } - else { // Multiple tasks mode - console.log(`Copy tests or type 's' to skip to next task. Type 'q' to quit.`); - var taskc = 0; - clipboardy_1.default.writeSync(""); // reset clipboard - this.clipContent = clipboardy_1.default.readSync(); - this.listenClipboard(); - process.stdin.addListener("data", (data) => { - if (data.toString()[0] == 's') { - this.createTask(String.fromCharCode(97 + taskc++)); - this.tests = []; - this.logger.log("Skipping to next task"); - } - else if (data.toString()[0] == 'q') { - this.createTask(String.fromCharCode(97 + taskc++)); - this.tests = []; - process.exit(0); - } - else { - console.log(`Copy tests or type 's' to skip to next task. Type 'q' to quit.`); - } - }); - } - } - createTask(name) { - const filename = './' + name + '.cpp'; - if (this.args.overwrite || !fs_1.existsSync(filename)) { - fs_1.writeFileSync(filename, this.sampleProgram); - this.logger.log('Created file:', filename); - } - if (!fs_1.existsSync('./tests')) { - fs_1.mkdirSync('./tests'); - this.logger.log("Created directory: ./tests"); - } - var testc = 1; - this.tests.forEach(test => { - var inPath = './tests/' + name + testc + '.in'; - var outPath = './tests/' + name + testc + '.out'; - testc++; - if (this.args.overwrite || !fs_1.existsSync(inPath)) { - fs_1.writeFileSync(inPath, test.input); - this.logger.log('Created file:', inPath); - } - if (this.args.overwrite || !fs_1.existsSync(outPath)) { - fs_1.writeFileSync(outPath, test.output); - this.logger.log('Created file:', outPath); - } - }); - } - listenClipboard() { - let newClip = clipboardy_1.default.readSync(); - if (newClip != this.clipContent) { - if (this.input) { - this.tests.push({ input: this.input, output: newClip }); - this.input = undefined; - } - else { - this.input = newClip; - } - this.clipContent = newClip; - } - if (this.listen) { - var _this = this; - setTimeout(function () { _this.listenClipboard(); }, 100); - } - } -} -exports.Interactive = Interactive; diff --git a/app/src/validate.js b/app/src/validate.js deleted file mode 100644 index 6b3a6aa..0000000 --- a/app/src/validate.js +++ /dev/null @@ -1,67 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -class Validator { - constructor(args) { - this.args = args; - this.programName = args._[1]; - this.sourcePath = this.programName + '.cpp'; - this.programPath = './' + this.programName; - } - start() { - if (!fs_1.existsSync(this.sourcePath)) { - console.error("\x1b[31m%s\x1b[0m%s", 'ERROR:', "Could not find file: " + this.sourcePath); - process.exit(); - } - if (this.args.compile) { - child_process_1.execFile('g++', ['-std=c++17', this.sourcePath, '-o', this.programName], (error, stdout, stderr) => { - if (error) { - console.error("\x1b[31m%s\x1b[0m %s", "COMPILE ERROR:", stderr); - process.exit(); - } - this.checkFile(); - }); - } - else { - if (!fs_1.existsSync(this.programName)) { - console.error("\x1b[31m%s\x1b[0m %s %s", 'ERROR:', "Could not find file: " + this.programName + '.', "Maybe remove 'no-compile' flag?"); - process.exit(); - } - this.checkFile(); - } - } - checkFile() { - fs_1.readdir('./tests', (err, items) => { - if (!items) { - console.error("\x1b[31m%s\x1b[0m %s", 'ERROR:', "Could not find directory: './tests' or it is empty"); - process.exit(); - } - items.forEach(item => { - if (item.endsWith('.in') && item.startsWith(this.programName)) { - this.validateProgram(item.replace(/\.[^/.]+$/, ""), 'tests/' + item, 'tests/' + item.replace(/\.[^/.]+$/, "") + '.out'); - } - }); - }); - } - validateProgram(testName, inputPath, outputPath) { - var child = child_process_1.execFile(this.programPath, (error, stdout, stderr) => { - if (error) { - console.error("\x1b[31m%s\x1b[0m %s", 'ERROR:', stderr ? stderr : "Could not execute file: " + this.programPath); - return; - } - const out = fs_1.readFileSync(outputPath); - if (stdout.trim() == out.toString().trim()) { - console.log("\x1b[33mTest %s:\x1b[0m \x1b[32m%s\x1b[0m", testName, "SUCCESS"); - } - else { - console.log("\x1b[33mTest %s:\x1b[0m \x1b[31m%s\x1b[0m", testName, "INVALID ANSWER"); - console.log("\x1b[33m### Expected:\x1b[0m\n%s", out.toString().trim()); - console.log('\x1b[33m### Got: \x1b[0m\n%s', stdout.trim()); - } - }); - const streamIn = fs_1.createReadStream(inputPath); - streamIn.pipe(child.stdin); - } -} -exports.Validator = Validator; diff --git a/bin/gcg b/bin/gcg new file mode 100644 index 0000000..a2f2779 --- /dev/null +++ b/bin/gcg @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../build/index.js'); \ No newline at end of file diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..23a66c6 --- /dev/null +++ b/build/index.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const commander_1 = __importDefault(require("commander")); +const initializer_1 = require("./src/initializer"); +const validate_1 = require("./src/validate"); +commander_1.default.version('2.1.1', '-v, --version'); +commander_1.default + .command('init ') + .alias('i') + .description('initialize task') + .option('-o, --overwrite', 'overwrite existing files') + .option('-i, --input-only', 'only create .in tests, skip .out') + .option('-s, --slient', 'run silenty (no logging)') + .action((task, cmd) => { + const interactive = new initializer_1.Initializer(cmd, task); + interactive.start(); +}); +commander_1.default + .command('run ') + .alias('r') + .description('run task on it\'s tests. if no custom folder or test are specified, it runs on all tests in \'tests\' directory that start with and on all tests in \'tests\\\' directory.') + .option('--no-compile', 'disable compiling before running on tests') + .option('-f, --folder ', 'set test folder path. defaults to \'tests\\\'') + .option('-t, --test ', 'run on chosen test only') + .action((task, cmd) => { + const validator = new validate_1.Validator(cmd, task); + validator.start(); +}); +commander_1.default + .on('command:*', function () { + console.error('Invalid command: %s\nSee --help for a list of available commands.', commander_1.default.args.join(' ')); + process.exit(1); +}); +commander_1.default + .parse(process.argv); diff --git a/app/res/help b/build/res/help similarity index 54% rename from app/res/help rename to build/res/help index c5066aa..277d947 100644 --- a/app/res/help +++ b/build/res/help @@ -2,15 +2,17 @@ gcg - Codeforces' contest file structure generator. Usage: gcg init initialize tasks, or specified task by clipping tests - gcg run compile and run task on tests + gcg run compile and run task on tests in folder 'tests' gcg help print this text Flags and parameters: - run: + r, run: + -t run program on specified test + -f run program on all tests in folder --no-compile disable compilation before running tests - init: + i, init: --overwrite overwrite existing files --no-interactive create tasks and blank tests for each task, where: - -tasks - -tests - --no-log disable logging \ No newline at end of file + --tasks + --tests + --no-log disable logging \ No newline at end of file diff --git a/build/res/templates/artihmetic.cpp b/build/res/templates/artihmetic.cpp new file mode 100644 index 0000000..9cadea9 --- /dev/null +++ b/build/res/templates/artihmetic.cpp @@ -0,0 +1,58 @@ +#include + +#define forn(i, n) for(int i = 0; i < n; ++i) + +using namespace std; + +typedef long long LL; +typedef vector VI; +typedef vector VVI; +typedef vector VLL; +typedef vector VVLL; + +const int md = 998244353; + +inline void add(int &a, int b) { + a += b; + if (a >= md) a -= md; +} + +inline void sub(int &a, int b) { + a -= b; + if (a < 0) a += md; +} + +inline int mul(int a, int b) { +#if !defined(_WIN32) || defined(_WIN64) + return (int) ((long long) a * b % md); +#endif + unsigned long long x = (long long) a * b; + unsigned xh = (unsigned) (x >> 32), xl = (unsigned) x, d, m; + asm( + "divl %4; \n\t" + : "=a" (d), "=d" (m) + : "d" (xh), "a" (xl), "r" (md) + ); + return m; +} + +inline int power(int a, long long b) { + int res = 1; + while (b > 0) { + if (b & 1) { + res = mul(res, a); + } + a = mul(a, a); + b >>= 1; + } + return res; +} + +int main() { + ios_base::sync_with_stdio(false); + cin.tie(0); + + + + return 0; +} \ No newline at end of file diff --git a/build/res/templates/main.cpp b/build/res/templates/main.cpp new file mode 100644 index 0000000..d814c5f --- /dev/null +++ b/build/res/templates/main.cpp @@ -0,0 +1,22 @@ +#include + +#define forn(i, n) for(int i = 0; i < (n); ++i) +#define pb push_back +#define mp make_pair + +using namespace std; + +typedef long long ll; +typedef vector vll; +typedef vector vi; +typedef pair pint; +typedef pair pll; + +int main() { + ios_base::sync_with_stdio(false); + cin.tie(0); + + + + return 0; +} \ No newline at end of file diff --git a/build/src/initializer.js b/build/src/initializer.js new file mode 100644 index 0000000..73e9f62 --- /dev/null +++ b/build/src/initializer.js @@ -0,0 +1,92 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const clipboardy_1 = __importDefault(require("clipboardy")); +const fs_1 = require("fs"); +const logger_1 = require("./logger"); +class Test { + constructor(input, output) { + this.input = input; + this.output = output; + } +} +class Initializer { + constructor(commander, taskname) { + this.commander = commander; + this.taskname = taskname; + this.tests = []; + this.listen = true; + this.sampleProgram = fs_1.readFileSync(__dirname + '/../res/templates/main.cpp'); + this.logger = new logger_1.Logger(commander.log); + } + start() { + console.log(`Copy tests or type 'q' to quit`); + clipboardy_1.default.writeSync(""); + this.clipContent = clipboardy_1.default.readSync(); + this.listenClipboard(); + process.stdin.addListener("data", (data) => { + if (data.toString()[0] == 'q') { + this.createTask(); + this.tests = []; + process.exit(0); + } + else { + console.log(`Copy tests or type 'q' to quit.`); + } + }); + } + createTask() { + const filename = './' + this.taskname + '.cpp'; + if (this.commander.overwrite || !fs_1.existsSync(filename)) { + fs_1.writeFileSync(filename, this.sampleProgram); + this.logger.log('Created file:', filename); + } + if (!fs_1.existsSync('./tests')) { + fs_1.mkdirSync('./tests'); + this.logger.log("Created directory: ./tests"); + } + if (!fs_1.existsSync('./tests/' + this.taskname)) { + fs_1.mkdirSync('./tests/' + this.taskname); + this.logger.log("Created directory: ./tests/" + this.taskname); + } + var testc = 1; + this.tests.forEach(test => { + var inPath = './tests/' + this.taskname + '/' + this.taskname + testc + '.in'; + var outPath = './tests/' + this.taskname + '/' + this.taskname + testc + '.out'; + testc++; + if (this.commander.overwrite || !fs_1.existsSync(inPath)) { + fs_1.writeFileSync(inPath, test.input); + this.logger.log('Created file:', inPath); + } + if (test.output && (this.commander.overwrite || !fs_1.existsSync(outPath))) { + fs_1.writeFileSync(outPath, test.output); + this.logger.log('Created file:', outPath); + } + }); + } + listenClipboard() { + let newClip = clipboardy_1.default.readSync(); + if (newClip != this.clipContent) { + if (this.commander.inputOnly) { + this.tests.push({ input: newClip, output: undefined }); + } + else { + if (this.input) { + this.tests.push({ input: this.input, output: newClip }); + this.input = undefined; + } + else { + this.input = newClip; + } + } + this.clipContent = newClip; + } + if (this.listen) { + var _this = this; + setTimeout(function () { _this.listenClipboard(); }, 100); + } + } +} +exports.Initializer = Initializer; diff --git a/app/src/logger.js b/build/src/logger.js similarity index 88% rename from app/src/logger.js rename to build/src/logger.js index f6a8e63..a2d6136 100644 --- a/app/src/logger.js +++ b/build/src/logger.js @@ -1,11 +1,11 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class Logger { - constructor(args) { - this.args = args; + constructor(silent) { + this.silent = silent; } log(...messages) { - if (this.args.log) { + if (!this.silent) { console.log(...messages); } } diff --git a/build/src/validate.js b/build/src/validate.js new file mode 100644 index 0000000..ec3a4cf --- /dev/null +++ b/build/src/validate.js @@ -0,0 +1,159 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const stream_1 = require("stream"); +const chalk_1 = __importDefault(require("chalk")); +class Validator { + constructor(cmd, programName) { + this.cmd = cmd; + this.programName = programName; + this.sourcePath = this.programName + '.cpp'; + this.programPath = './' + this.programName; + this.successes = this.failures = 0; + this.taskTestsFolderPath = cmd.folder || `tests/${this.programName}`; + } + start() { + if (!fs_1.existsSync(this.sourcePath)) { + console.error(chalk_1.default.red('ERROR:'), "Could not find file: " + this.sourcePath); + process.exit(); + } + if (this.cmd.compile) { + child_process_1.execFile('g++', ['-std=c++17', this.sourcePath, '-o', this.programName], (error, stdout, stderr) => { + if (error) { + console.error(chalk_1.default.red("COMPILE ERROR:"), stderr); + process.exit(); + } + this.checkFile(); + }); + } + else { + if (!fs_1.existsSync(this.programName) && !fs_1.existsSync(this.programName + '.exe')) { + console.error(chalk_1.default.red('ERROR:'), "Could not find file: " + this.programName + '.', "Maybe remove 'no-compile' flag?"); + process.exit(); + } + this.checkFile(); + } + } + checkFile() { + if (this.cmd.folder) { + fs_1.readdir(`./${this.cmd.folder}`, (err, items) => { + if (!items) { + console.error(chalk_1.default.red('ERROR:'), `Could not find directory: './${this.cmd.folder}`); + process.exit(); + } + if (this.cmd.test) { + if (!fs_1.existsSync(`./${this.cmd.folder}/${this.cmd.test}.in`)) { + console.error(chalk_1.default.red('ERROR:'), `Could not find test: "${this.cmd.test}.in".`); + process.exit(); + } + this.validateProgram(this.cmd.test, `./${this.cmd.folder}`); + } + else { + items.forEach(item => { + if (item.endsWith('.in')) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./${this.cmd.folder}`); + } + }); + } + }); + } + else { + fs_1.readdir(`./tests`, (err, items) => { + if (err || !items || items.length == 0) { + return; + } + if (this.cmd.test) { + if (!fs_1.existsSync(`./tests/${this.cmd.test}.in`)) + return; + this.validateProgram(this.cmd.test, `./tests`); + } + else { + items.forEach(item => { + if (item.endsWith('.in') && item.startsWith(this.programName)) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./tests`); + } + }); + } + }); + fs_1.readdir(`./tests/${this.programName}`, (err, items) => { + if (err || !items || items.length == 0) { + return; + } + if (this.cmd.test) { + if (!fs_1.existsSync(`./tests/${this.programName}/${this.cmd.test}.in`)) + return; + this.validateProgram(this.cmd.test, `tests/${this.programName}`); + } + else { + items.forEach(item => { + if (item.endsWith('.in')) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./tests/${this.programName}`); + } + }); + } + }); + } + } + validateProgram(testName, testPath) { + var child = child_process_1.execFile(this.programPath, (error, stdout, stderr) => { + if (error) { + console.error(chalk_1.default.red('RUNTIME ERROR'), '\n', error.message); + return; + } + var testOutExt; + if (fs_1.existsSync(`${testPath}/${testName}.out`)) { + testOutExt = '.out'; + } + else { + testOutExt = '.ans'; + } + const testTxtPath = `${testPath}/${testName}${testOutExt}`; + const testValName = this.programName + '_out'; + const testValPath = `${testPath}/${testValName}`; + if (fs_1.existsSync(testTxtPath)) { + var destout = fs_1.readFileSync(testTxtPath).toString(); + if (stdout.trim() == destout.trim()) { + console.log(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.green('SUCCESS')); + this.successes++; + } + else { + console.log(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.red('INVALID ANSWER'), chalk_1.default.yellow('\n### Expected:'), `\n${destout.trim()}`, chalk_1.default.yellow('\n### Got:'), `\n${stdout.trim()}`); + this.failures++; + } + } + else if (fs_1.existsSync(testValPath + '.exe')) { + var validator = child_process_1.execFile(testValPath, (error, stdout, stderr) => { + if (error) { + console.error(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.red('ERROR:'), stderr ? stderr : `Could not execute file: ${testValPath}`); + this.failures++; + } + else if (stdout.trim() == '') { + console.log(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.green('SUCCESS')); + this.successes++; + } + else { + console.log(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.red('INVALID ANSWER'), chalk_1.default.yellow('\n### Checker result:'), `\n${stdout.trim()}`); + this.failures++; + } + }); + const valStream = new stream_1.Readable(); + valStream._read = () => { }; + valStream.push(fs_1.readFileSync(`${testPath}/${testName}.in`).toString()); + valStream.push(' '); + valStream.push(stdout); + valStream.push(null); + valStream.pipe(validator.stdin); + } + else { + console.log(chalk_1.default.yellow(`Test ${testName}:`), chalk_1.default.blue('NO CHECKER'), chalk_1.default.yellow(`\n### Answer:`), `\n${stdout.trim()}`); + } + }); + const streamIn = fs_1.createReadStream(`${testPath}/${testName}.in`); + streamIn.pipe(child.stdin); + } +} +exports.Validator = Validator; diff --git a/index.ts b/index.ts index cf5909f..39f685d 100644 --- a/index.ts +++ b/index.ts @@ -1,36 +1,44 @@ #!/usr/bin/env node -import minimist, { ParsedArgs } from 'minimist'; -import { Initializer } from './src/init'; +import commander from 'commander'; +import { Initializer } from './src/initializer'; import { Validator } from './src/validate'; -import { Interactive } from './src/interactive'; -import { readFile } from 'fs'; - -const args: ParsedArgs = minimist(process.argv.slice(2), { - default: { - compile: true, - log: true, - interactive: true, - tasks: 5, - tests: 2 - } -}); - -if(args._[0] === 'init' || args._[0] === 'i') { - if(args.interactive) { - const interactive = new Interactive(args); + +commander.version('2.1.1', '-v, --version') + // .option('--overwrite', 'overwrite existing files') + + +commander + .command('init ') + .alias('i') + .description('initialize task') + .option('-o, --overwrite', 'overwrite existing files') + .option('-i, --input-only', 'only create .in tests, skip .out') + .option('-s, --slient', 'run silenty (no logging)') + .action((task, cmd) => { + const interactive = new Initializer(cmd, task); interactive.start(); - } else { - const initializer = new Initializer(args); - initializer.start(); - } -} else if(args._[0] === 'run' || args._[0] === 'r') { - const validator = new Validator(args); - validator.start(); -} else if(args._[0] === 'help' || args._[0] === 'h' || !args._[0]){ - readFile('./res/help', (err, data) => { - console.log(data.toString()); }); -} + +commander + .command('run ') + .alias('r') + .description('run task on it\'s tests. if no custom folder or test are specified, it runs on all tests in \'tests\' directory that start with and on all tests in \'tests\\\' directory.') + .option('--no-compile', 'disable compiling before running on tests') + .option('-f, --folder ', 'set test folder path. defaults to \'tests\\\'') + .option('-t, --test ', 'run on chosen test only') + .action((task, cmd) => { + const validator = new Validator(cmd, task); + validator.start(); + }); + +commander + .on('command:*', function () { + console.error('Invalid command: %s\nSee --help for a list of available commands.', commander.args.join(' ')); + process.exit(1); + }); + +commander + .parse(process.argv); diff --git a/package-lock.json b/package-lock.json index c540204..ee5f895 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gcg", - "version": "2.0.0", + "version": "2.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,11 +10,14 @@ "integrity": "sha512-KOxf4ah9diZWmREM5jCupx2+pZaBPwKk5d5jeNK2+TY6IgEO35uhG55NnDT4cdXeRX8irDSHQPtdRrr0JOTQIw==", "dev": true }, - "@types/minimist": { - "version": "1.2.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", - "dev": true + "@types/commander": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", + "dev": true, + "requires": { + "commander": "*" + } }, "@types/node": { "version": "10.5.7", @@ -22,23 +25,59 @@ "integrity": "sha512-VkKcfuitP+Nc/TaTFH0B8qNmn+6NbI6crLkQonbedViVz7O2w8QV/GERPlkJ4bg42VGHiEWa31CoTOPs1q6z1w==", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, "arch": { "version": "2.1.1", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/arch/-/arch-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "clipboardy": { "version": "1.2.3", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/clipboardy/-/clipboardy-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", "requires": { "arch": "^2.1.0", "execa": "^0.8.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, "cross-spawn": { "version": "5.1.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/cross-spawn/-/cross-spawn-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", @@ -46,9 +85,14 @@ "which": "^1.2.9" } }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "execa": { "version": "0.8.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/execa/-/execa-0.8.0.tgz", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", "requires": { "cross-spawn": "^5.0.1", @@ -62,41 +106,41 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, "is-stream": { "version": "1.1.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/is-stream/-/is-stream-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "isexe": { "version": "2.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/isexe/-/isexe-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "lru-cache": { "version": "4.1.3", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/lru-cache/-/lru-cache-4.1.3.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, - "minimist": { - "version": "1.2.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, "ncp": { "version": "2.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/ncp/-/ncp-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" }, "npm-run-path": { "version": "2.0.2", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/npm-run-path/-/npm-run-path-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" @@ -104,22 +148,22 @@ }, "p-finally": { "version": "1.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/p-finally/-/p-finally-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "path-key": { "version": "2.0.1", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/path-key/-/path-key-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "pseudomap": { "version": "1.0.2", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/pseudomap/-/pseudomap-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "shebang-command": { "version": "1.2.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/shebang-command/-/shebang-command-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" @@ -127,28 +171,36 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/shebang-regex/-/shebang-regex-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "signal-exit": { "version": "3.0.2", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "strip-eof": { "version": "1.0.0", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, "typescript": { - "version": "3.0.1", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/typescript/-/typescript-3.0.1.tgz", - "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", "dev": true }, "which": { "version": "1.3.1", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/which/-/which-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" @@ -156,7 +208,7 @@ }, "yallist": { "version": "2.1.2", - "resolved": "http://172.29.12.183:8088/repository/npm-ksi/yallist/-/yallist-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } diff --git a/package.json b/package.json index 0e1a3d6..73d3707 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "name": "gcg", - "version": "2.0.0", + "version": "2.1.1", "description": "Codeforces contest helper", - "main": "index.js", + "main": "build/index.js", + "module": "build/index.js", "bin": { - "gcg": "./app/index.js" + "gcg": "./bin/gcg" }, "scripts": { - "build": "tsc && ncp ./res ./dist/res", - "build:prod": "tsc --outDir ./app --sourceMap false && ncp ./res ./app/res", - "start": "node ./dist/index.js", + "build": "tsc -p tsconfig.json && ncp ./res ./build/res", + "start": "node ./bin/gcg", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -23,14 +23,15 @@ }, "homepage": "https://github.com/tao24/gcg#readme", "dependencies": { + "chalk": "^2.4.1", "clipboardy": "^1.2.3", - "minimist": "^1.2.0", + "commander": "^2.19.0", "ncp": "^2.0.0" }, "devDependencies": { "@types/clipboardy": "^1.1.0", - "@types/minimist": "^1.2.0", + "@types/commander": "^2.12.2", "@types/node": "^10.5.7", - "typescript": "^3.0.1" + "typescript": "^3.1.6" } } diff --git a/res/help b/res/help index c5066aa..277d947 100644 --- a/res/help +++ b/res/help @@ -2,15 +2,17 @@ gcg - Codeforces' contest file structure generator. Usage: gcg init initialize tasks, or specified task by clipping tests - gcg run compile and run task on tests + gcg run compile and run task on tests in folder 'tests' gcg help print this text Flags and parameters: - run: + r, run: + -t run program on specified test + -f run program on all tests in folder --no-compile disable compilation before running tests - init: + i, init: --overwrite overwrite existing files --no-interactive create tasks and blank tests for each task, where: - -tasks - -tests - --no-log disable logging \ No newline at end of file + --tasks + --tests + --no-log disable logging \ No newline at end of file diff --git a/res/templates/artihmetic.cpp b/res/templates/artihmetic.cpp new file mode 100644 index 0000000..9cadea9 --- /dev/null +++ b/res/templates/artihmetic.cpp @@ -0,0 +1,58 @@ +#include + +#define forn(i, n) for(int i = 0; i < n; ++i) + +using namespace std; + +typedef long long LL; +typedef vector VI; +typedef vector VVI; +typedef vector VLL; +typedef vector VVLL; + +const int md = 998244353; + +inline void add(int &a, int b) { + a += b; + if (a >= md) a -= md; +} + +inline void sub(int &a, int b) { + a -= b; + if (a < 0) a += md; +} + +inline int mul(int a, int b) { +#if !defined(_WIN32) || defined(_WIN64) + return (int) ((long long) a * b % md); +#endif + unsigned long long x = (long long) a * b; + unsigned xh = (unsigned) (x >> 32), xl = (unsigned) x, d, m; + asm( + "divl %4; \n\t" + : "=a" (d), "=d" (m) + : "d" (xh), "a" (xl), "r" (md) + ); + return m; +} + +inline int power(int a, long long b) { + int res = 1; + while (b > 0) { + if (b & 1) { + res = mul(res, a); + } + a = mul(a, a); + b >>= 1; + } + return res; +} + +int main() { + ios_base::sync_with_stdio(false); + cin.tie(0); + + + + return 0; +} \ No newline at end of file diff --git a/res/templates/main.cpp b/res/templates/main.cpp index c13afbc..d814c5f 100644 --- a/res/templates/main.cpp +++ b/res/templates/main.cpp @@ -1,12 +1,22 @@ #include +#define forn(i, n) for(int i = 0; i < (n); ++i) +#define pb push_back +#define mp make_pair + using namespace std; +typedef long long ll; +typedef vector vll; +typedef vector vi; +typedef pair pint; +typedef pair pll; + int main() { ios_base::sync_with_stdio(false); cin.tie(0); - - + + return 0; } \ No newline at end of file diff --git a/src/init.ts b/src/init.ts deleted file mode 100644 index 8c060af..0000000 --- a/src/init.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { exists, existsSync, mkdir, readFileSync, writeFile } from 'fs'; -import { ParsedArgs } from 'minimist'; -import { Logger } from './logger'; - -export class Initializer { - private logger: Logger; - private tasks: number; - private tests: number; - private overwrite: boolean; - private sampleProgram = readFileSync(__dirname + '/../res/templates/main.cpp'); - - constructor(private args: ParsedArgs) { - this.logger = new Logger(args); - this.tasks = +args.tasks; - this.tests = +args.tests; - this.overwrite = args.overwrite; - } - - start(): void { - if(this.tasks < 0 || this.tests < 0) { - this.logger.error('Please specify valid number of tasks and tests!'); - } else { - this.generateDirectory(); - } - } - - generateTests(): void { - for (let t = 0; t < this.tasks; t++) { - for (let i = 0; i < this.tests; i++) { - var inPath = './tests/' + String.fromCharCode(97 + t) + i + '.in'; - var outPath = './tests/' + String.fromCharCode(97 + t) + i + '.out'; - - if(this.overwrite || !existsSync(inPath)) { - writeFile(inPath, '', this.logger.callback('Created file: ' + inPath)); - } - - if(this.overwrite || !existsSync(outPath)) { - writeFile(outPath, '', this.logger.callback('Created file: ' + outPath)); - } - } - } - } - - generateDirectory(): void { - - exists('./tests', (exists) => { - if(!exists) { - mkdir('./tests', (err) => { - if(err) { - this.logger.error(err.message); - } else { - this.logger.log("Created directory: ./tests"); - this.generateTests(); - } - }); - } else { - this.generateTests(); - } - }); - - for (let i = 0; i < this.tasks; i++) { - var a = './' + String.fromCharCode(97 + i) + '.cpp'; - if(this.overwrite || !existsSync(a)) { - writeFile(a, this.sampleProgram, this.logger.callback('Created file: ' + a)); - } - } - } -} diff --git a/src/interactive.ts b/src/initializer.ts similarity index 65% rename from src/interactive.ts rename to src/initializer.ts index 790f156..73ff3d3 100644 --- a/src/interactive.ts +++ b/src/initializer.ts @@ -1,13 +1,13 @@ -import { ParsedArgs } from "minimist"; import clip from "clipboardy"; +import { CommanderStatic } from "commander"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { Logger } from "./logger"; -import { existsSync, readFileSync, writeFile, writeFileSync, mkdirSync } from "fs"; class Test { - constructor(public input: string, public output: string) { } + constructor(public input: string, public output?: string) { } } -export class Interactive { +export class Initializer { private logger: Logger; private tests: Array = []; private input?: string; @@ -15,12 +15,12 @@ export class Interactive { private listen: boolean = true; private sampleProgram = readFileSync(__dirname + '/../res/templates/main.cpp'); - constructor(private args: ParsedArgs) { - this.logger = new Logger(args); + constructor(private commander: CommanderStatic, private taskname: string) { + this.logger = new Logger(commander.log); } start(): void { - if(this.args._[1]) { // Single task mode + // if(this.args._[1]) { // Single task mode console.log(`Copy tests or type 'q' to quit`); clip.writeSync(""); // reset clipboard @@ -29,15 +29,14 @@ export class Interactive { process.stdin.addListener("data", (data: Buffer) => { if (data.toString()[0] == 'q') { - this.createTask(this.args._[1]); + this.createTask(); this.tests = []; process.exit(0); } else { console.log(`Copy tests or type 'q' to quit.`); } }); - - } else { // Multiple tasks mode +/* } else { // Multiple tasks mode console.log(`Copy tests or type 's' to skip to next task. Type 'q' to quit.`); var taskc = 0; @@ -58,12 +57,12 @@ export class Interactive { console.log(`Copy tests or type 's' to skip to next task. Type 'q' to quit.`); } }); - } + } */ } - createTask(name: string) { - const filename = './' + name + '.cpp'; - if(this.args.overwrite || !existsSync(filename)) { + createTask() { + const filename = './' + this.taskname + '.cpp'; + if(this.commander.overwrite || !existsSync(filename)) { writeFileSync(filename, this.sampleProgram); this.logger.log('Created file:', filename); } @@ -71,18 +70,22 @@ export class Interactive { mkdirSync('./tests'); this.logger.log("Created directory: ./tests"); } + if(!existsSync('./tests/' + this.taskname)) { + mkdirSync('./tests/' + this.taskname); + this.logger.log("Created directory: ./tests/" + this.taskname); + } var testc = 1; this.tests.forEach(test => { - var inPath = './tests/' + name + testc + '.in'; - var outPath = './tests/' + name + testc + '.out'; + var inPath = './tests/' + this.taskname + '/' + this.taskname + testc + '.in'; + var outPath = './tests/' + this.taskname + '/' + this.taskname + testc + '.out'; testc++; - if(this.args.overwrite || !existsSync(inPath)) { + if(this.commander.overwrite || !existsSync(inPath)) { writeFileSync(inPath, test.input); this.logger.log('Created file:', inPath) } - if(this.args.overwrite || !existsSync(outPath)) { + if(test.output && (this.commander.overwrite || !existsSync(outPath))) { writeFileSync(outPath, test.output); this.logger.log('Created file:', outPath) } @@ -93,11 +96,15 @@ export class Interactive { listenClipboard() { let newClip: string | undefined = clip.readSync(); if (newClip != this.clipContent) { - if(this.input) { - this.tests.push({input: this.input, output: newClip}); - this.input = undefined; + if(this.commander.inputOnly) { + this.tests.push({input: newClip, output: undefined}); } else { - this.input = newClip; + if(this.input) { + this.tests.push({input: this.input, output: newClip}); + this.input = undefined; + } else { + this.input = newClip; + } } this.clipContent = newClip; } diff --git a/src/input.ts b/src/input.ts deleted file mode 100644 index 1da0b64..0000000 --- a/src/input.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ParsedArgs } from 'minimist'; - -export function input(args: ParsedArgs): void { - -} \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index 0577f41..5e095d6 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,11 +1,9 @@ -import { ParsedArgs } from 'minimist'; - export class Logger { - constructor(private args: ParsedArgs) { } + constructor(private silent: boolean) { } log(...messages: string[]) { - if(this.args.log) { + if(!this.silent) { console.log(...messages); } } diff --git a/src/validate.ts b/src/validate.ts index e289d6f..f288504 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -1,35 +1,40 @@ +import { CommanderStatic } from 'commander'; import { execFile } from 'child_process'; -import { createReadStream, readdir, readFileSync, existsSync } from 'fs'; -import { ParsedArgs } from 'minimist'; +import { createReadStream, readdir, readFileSync, existsSync, exists } from 'fs'; +import { Readable } from 'stream'; +import chalk from 'chalk'; export class Validator { - programName: string; programPath: string; sourcePath: string; + taskTestsFolderPath: string; + successes: number; + failures: number; - constructor(private args: ParsedArgs) { - this.programName = args._[1]; + constructor(private cmd: CommanderStatic, private programName: string) { this.sourcePath = this.programName + '.cpp'; this.programPath = './' + this.programName; + this.successes = this.failures = 0; + this.taskTestsFolderPath = cmd.folder || `tests/${this.programName}`; } start() { if(!existsSync(this.sourcePath)) { - console.error("\x1b[31m%s\x1b[0m%s", 'ERROR:', "Could not find file: " + this.sourcePath); + console.error(chalk.red('ERROR:'), "Could not find file: " + this.sourcePath); process.exit(); } - if(this.args.compile) { + if(this.cmd.compile) { execFile('g++', ['-std=c++17', this.sourcePath, '-o', this.programName], (error: Error | null, stdout: string, stderr: string) => { if(error) { - console.error("\x1b[31m%s\x1b[0m %s", "COMPILE ERROR:", stderr); + console.error(chalk.red("COMPILE ERROR:"), stderr); process.exit(); } this.checkFile(); }); } else { - if(!existsSync(this.programName)) { - console.error("\x1b[31m%s\x1b[0m %s %s", 'ERROR:', "Could not find file: " + this.programName + '.', "Maybe remove 'no-compile' flag?"); + if(!existsSync(this.programName) && !existsSync(this.programName + '.exe')) { // FIXME: Check OS and resolve correct file + console.error(chalk.red('ERROR:'), "Could not find file: " + this.programName + '.', "Maybe remove 'no-compile' flag?"); process.exit(); } this.checkFile(); @@ -37,36 +42,114 @@ export class Validator { } checkFile() { - readdir('./tests', (err, items) => { - if(!items) { - console.error("\x1b[31m%s\x1b[0m %s", 'ERROR:', "Could not find directory: './tests' or it is empty"); - process.exit(); - } - items.forEach(item => { - if(item.endsWith('.in') && item.startsWith(this.programName)) { - this.validateProgram(item.replace(/\.[^/.]+$/, ""), 'tests/' + item, 'tests/' + item.replace(/\.[^/.]+$/, "") + '.out'); + if(this.cmd.folder) { + readdir(`./${this.cmd.folder}`, (err, items) => { + if(!items) { + console.error(chalk.red('ERROR:'), `Could not find directory: './${this.cmd.folder}`); + process.exit(); + } + if(this.cmd.test) { + if(!existsSync(`./${this.cmd.folder}/${this.cmd.test}.in`)) { + console.error(chalk.red('ERROR:'), `Could not find test: "${this.cmd.test}.in".`); + process.exit(); + } + this.validateProgram(this.cmd.test, `./${this.cmd.folder}`); + } else { + items.forEach(item => { + if(item.endsWith('.in')) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./${this.cmd.folder}`); + } + }); } }); - }); + } else { + readdir(`./tests`, (err, items) => { + if(err || !items || items.length == 0) { + return; + } + if(this.cmd.test) { + if(!existsSync(`./tests/${this.cmd.test}.in`)) return; + this.validateProgram(this.cmd.test, `./tests`); + } else { + items.forEach(item => { + if(item.endsWith('.in') && item.startsWith(this.programName)) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./tests`); + } + }); + } + }); + readdir(`./tests/${this.programName}`, (err, items) => { + if(err || !items || items.length == 0) { + return; + } + if(this.cmd.test) { + if(!existsSync(`./tests/${this.programName}/${this.cmd.test}.in`)) return; + this.validateProgram(this.cmd.test, `tests/${this.programName}`); + } else { + items.forEach(item => { + if(item.endsWith('.in')) { + this.validateProgram(item.replace(/\.[^/.]+$/, ""), `./tests/${this.programName}`); + } + }); + } + }); + } + } - validateProgram(testName: string, inputPath: string, outputPath: string) { + validateProgram(testName: string, testPath: string) { var child = execFile(this.programPath, (error, stdout, stderr) => { if (error) { - console.error("\x1b[31m%s\x1b[0m %s", 'ERROR:', stderr ? stderr : "Could not execute file: " + this.programPath); + console.error(chalk.red('RUNTIME ERROR'), '\n', error.message); return; } - const out = readFileSync(outputPath); - if(stdout.trim() == out.toString().trim()) { - console.log("\x1b[33mTest %s:\x1b[0m \x1b[32m%s\x1b[0m", testName, "SUCCESS"); + + var testOutExt: string; + if(existsSync(`${testPath}/${testName}.out`)) { + testOutExt = '.out'; + } else { + testOutExt = '.ans'; + } + const testTxtPath = `${testPath}/${testName}${testOutExt}`; + const testValName = this.programName + '_out'; + const testValPath = `${testPath}/${testValName}`; + + if(existsSync(testTxtPath)) { + var destout = readFileSync(testTxtPath).toString(); + if(stdout.trim() == destout.trim()) { + console.log(chalk.yellow(`Test ${testName}:`), chalk.green('SUCCESS')); + this.successes++; + } else { + console.log(chalk.yellow(`Test ${testName}:`), chalk.red('INVALID ANSWER'), chalk.yellow('\n### Expected:'), `\n${destout.trim()}`, chalk.yellow('\n### Got:'), `\n${stdout.trim()}`); + this.failures++; + } + } else if(existsSync(testValPath + '.exe') || existsSync(testValPath)) { + var validator = execFile(testValPath, (error, stdout, stderr) => { + if (error) { + console.error(chalk.yellow(`Test ${testName}:`), chalk.red('ERROR:'), stderr ? stderr : `Could not execute file: ${testValPath}`); + this.failures++; + } else if(stdout.trim() == '') { + console.log(chalk.yellow(`Test ${testName}:`), chalk.green('SUCCESS')); + this.successes++; + } else { + console.log(chalk.yellow(`Test ${testName}:`), chalk.red('INVALID ANSWER'), chalk.yellow('\n### Checker result:'), `\n${stdout.trim()}`); + this.failures++; + } + }); + const valStream = new Readable(); + valStream._read = () => {}; + valStream.push(readFileSync(`${testPath}/${testName}.in`).toString()); + valStream.push(' '); + valStream.push(stdout); + valStream.push(null); + valStream.pipe(validator.stdin); } else { - console.log("\x1b[33mTest %s:\x1b[0m \x1b[31m%s\x1b[0m", testName, "INVALID ANSWER"); - console.log("\x1b[33m### Expected:\x1b[0m\n%s", out.toString().trim()); - console.log('\x1b[33m### Got: \x1b[0m\n%s', stdout.trim()); + console.log(chalk.yellow(`Test ${testName}:`), chalk.blue('NO CHECKER'), chalk.yellow(`\n### Answer:`), `\n${stdout.trim()}`); + // console.error(`\x1b[31mERROR:\x1b[0m There is no '${testName}.out' or '${testName}.ans' file and no '${testValName}' validator in '${testPath}' folder.`); } }); - const streamIn = createReadStream(inputPath); + const streamIn = createReadStream(`${testPath}/${testName}.in`); streamIn.pipe(child.stdin); } } diff --git a/tsconfig.json b/tsconfig.json index 000b261..0f088ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,12 +9,12 @@ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./bin/gcg.js", /* Concatenate and emit output to single file. */ + "outDir": "./build", /* Redirect output structure to the directory. */ "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ - // "removeComments": true, /* Do not emit comments to output. */ + "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ @@ -22,7 +22,7 @@ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ @@ -56,7 +56,5 @@ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, - "exclude": [ - "src/validate.ts" - ] + "exclude": [ ] } \ No newline at end of file