├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── docs.yml │ ├── linter.yml │ └── tests.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app ├── .prettierrc ├── README.md ├── jest.config.js ├── nodemon.json ├── package-lock.json ├── package.json ├── src │ ├── .eslintrc │ ├── Clone │ │ ├── CCServer.ts │ │ ├── Clone.ts │ │ └── EditorCommandBuilder.ts │ ├── Config │ │ ├── Config.ts │ │ └── Types │ │ │ ├── LangAliases.ts │ │ │ ├── LangConfig.ts │ │ │ └── OnlineJudgeName.ts │ ├── Create │ │ ├── BuildLaunchCommand.ts │ │ ├── Create.ts │ │ └── SourceFileCreator.ts │ ├── GlobalConstants.ts │ ├── Init │ │ └── Init.ts │ ├── Login │ │ └── Login.ts │ ├── Stat │ │ └── Stat.ts │ ├── Submit │ │ ├── OnlineJudgeFactory │ │ │ ├── AtCoder.ts │ │ │ ├── Codeforces.ts │ │ │ ├── OmegaUp.ts │ │ │ ├── OnlineJudge.ts │ │ │ ├── OnlineJudgeFactory.ts │ │ │ ├── QOJ.ts │ │ │ ├── Szkopul.ts │ │ │ ├── TLX.ts │ │ │ ├── Ucup.ts │ │ │ ├── UniversalOJ.ts │ │ │ ├── UniversalOJGeneric.ts │ │ │ ├── Yandex.ts │ │ │ └── __tests__ │ │ │ │ └── OnlineJudge.test.ts │ │ └── Submit.ts │ ├── Test │ │ ├── Test.ts │ │ ├── TesterFactory │ │ │ ├── CompiledTester.ts │ │ │ ├── InterpretedTester.ts │ │ │ ├── MixedTester.ts │ │ │ ├── Tester.ts │ │ │ └── TesterFactory.ts │ │ └── __tests__ │ │ │ └── Tester.test.ts │ ├── Types │ │ ├── ICommandGlobalArgs.ts │ │ ├── ProblemData.ts │ │ ├── TestData.ts │ │ └── Veredict.ts │ ├── Utils │ │ ├── LangExtensions.ts │ │ ├── Util.ts │ │ └── __tests__ │ │ │ └── Util.test.ts │ └── index.ts └── tsconfig.json ├── package-lock.json └── website ├── .gitignore ├── README.md ├── babel.config.js ├── blog ├── 2019-05-28-hola.md ├── 2019-05-29-hello-world.md └── 2019-05-30-welcome.md ├── docs ├── about.mdx ├── addEditorSupport.mdx ├── addLanguageSupport.mdx ├── addOnlineJudgeSupport.mdx ├── addTestCase.mdx ├── cheatsheet.mdx ├── clone.mdx ├── colors.js ├── configuration.mdx ├── create.mdx ├── debug.mdx ├── fromv1.mdx ├── installation.mdx ├── nvm.mdx ├── setupDevEnv.mdx ├── submit.mdx ├── test.mdx └── vim.mdx ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src ├── css │ └── custom.css └── pages │ ├── index.js │ ├── markdown-page.md │ └── styles.module.css ├── static ├── .nojekyll └── img │ ├── atcoder_dropdown.png │ ├── codeforces_dropdown.png │ ├── demos │ ├── add-test-case.gif │ ├── clone.gif │ ├── create.gif │ ├── debug_file.gif │ ├── debug_keyboard.gif │ ├── demo.yml │ ├── flat-file-structure.gif │ ├── submit.gif │ ├── test_ac.gif │ ├── test_ac_2.gif │ ├── test_ce.gif │ ├── test_rte.gif │ ├── test_tle.gif │ ├── test_wa.gif │ ├── video-demo-min.gif │ ├── video-demo.gif │ ├── video-demogif.png │ ├── video_demo.mkv │ └── video_demo.mp4 │ ├── favicon.ico │ ├── inspect_right_click.png │ ├── inspect_tool.png │ ├── logo.png │ ├── logo.svg │ ├── nvim-icon.png │ ├── search_select.png │ ├── select_options.png │ ├── terminalCommandBuilder.png │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ ├── undraw_docusaurus_tree.svg │ ├── vim-icon.png │ └── vim-icon_compressed.png ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour to automatically normalize line endings. 2 | * text=auto 3 | 4 | *.json text eol=lf 5 | *.js text eol=lf 6 | *.ts text eol=lf 7 | *.md text eol=lf 8 | *.tex text eol=lf 9 | .* text eol=lf 10 | 11 | LICENSE text eol=lf 12 | 13 | *.pdf binary 14 | *.jpg binary 15 | *.jpeg binary 16 | *.gif binary 17 | *.png binary 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: searleser97 4 | custom: https://paypal.me/searleser97 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | **Please complete the following information:** 10 | cpbooster version: run `cpb -v` to get this information 11 | OS: (e.g. Windows, Arch, Ubuntu, MacOS, ...) 12 | Using Vim? : (yes, no) 13 | Using Neovim? : (yes, no) 14 | Terminal Name: (Konsole, Gnome-Terminal, CMD, ...) 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. 18 | 19 | **Steps To Reproduce it** 20 | 1. do this thing first 21 | 2. then do this 22 | 3. finally, observe the bug 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Have you read the official website to guarantee that your feature does not exist yet?** 11 | Official Website: https://searleser97.github.io/cpbooster/ 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | build-and-deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@v4 16 | 17 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 18 | run: | 19 | yarn 20 | yarn build 21 | working-directory: website/ 22 | 23 | - name: Deploy 🚀 24 | uses: JamesIves/github-pages-deploy-action@v4 25 | with: 26 | folder: website/build # The folder the action should deploy. 27 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: LINT 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: [master] 8 | pull_request: 9 | branches: [master] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | defaults: 22 | run: 23 | shell: bash 24 | working-directory: app/ 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 29 | - uses: actions/checkout@v2 30 | 31 | - name: Install modules 32 | run: npm install 33 | 34 | # Runs a single command using the runners shell 35 | - name: ESLint 36 | run: npm run lint 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: TESTS 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: [master] 8 | pull_request: 9 | branches: [master] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | defaults: 22 | run: 23 | shell: bash 24 | working-directory: app/ 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 29 | - uses: actions/checkout@v2 30 | 31 | - name: Install modules 32 | run: npm install 33 | 34 | # Runs a single command using the runners shell 35 | - name: Run tests 36 | run: npm t 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at serchgabriel97@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | app/README.md -------------------------------------------------------------------------------- /app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "printWidth": 100, 5 | "tabWidth": 2, 6 | "singleQuote": false 7 | } -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # cpbooster · [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/searleser97/cpbooster/blob/master/LICENSE) [![npm version](https://badge.fury.io/js/cpbooster.svg#)](https://badge.fury.io/js/cpbooster) ![npm](https://img.shields.io/npm/dt/cpbooster) 2 | 3 | ### Link to Website 🡲 https://searleser97.github.io/cpbooster/ 4 | 5 | ## About 6 | 7 | `cpbooster` is a cross-platform **CLI** tool designed to **boost** competitive programmer's speed during contests by automating various routine tasks like compiling and testing, debugging, cloning testcases, loading template, etc. The console command suits any coding environment (i.e. _**VSCode, Jetbrains IDEs, Vim, Emacs, Geany, Sublime Text, ...**_) and it’s very easy to use. _Vim_ / _NeoVim_ users can install [cpbooster.vim plugin](https://github.com/searleser97/cpbooster.vim) to **boost** their speed even more. 8 | 9 | #### Using **NeoVim**: 10 | ![video-demo-min](https://user-images.githubusercontent.com/5056411/112361632-3874dd80-8c99-11eb-8734-95662003b8cf.gif) 11 | 12 | 13 | ## For Installation and Setup Instructions Visit `cpbooster` [Website](https://searleser97.github.io/cpbooster/) 14 | 15 | **https://searleser97.github.io/cpbooster/** 16 | 17 | ## Features 18 | 19 | 1. `cpbooster` comes with a short alias command called `cpb` to avoid writing the long command each time 20 | 1. Automatically **clone** sample testcases files with corresponding source code files with template loaded into a desired directory 21 | - `cpb clone` waits for competitive companion plugin to send parsed data for each problem 22 | 1. **Test** your code against sample testcases quickly 23 | 24 | - `cpb test mycode.cpp` test your program against all available test cases 25 | - `cpb test mycode.cpp -t 1` test your program against the test case with the given id 26 | - `cpb test /some/path/mycode.cpp` test a program that is not located in your current location 27 | 28 | Supported results: 29 | 30 | - **AC** (Accepted) 31 | - **WA** (Wrong Answer) Shows differences between accepted output and your output beautifully 32 | - **TLE** (Time Limit Exceeded) 33 | - **RTE** (Runtime Error) 34 | - **CE** (Compilation Error) 35 | 36 | 1. Run code with your own **debugging flags** easily 37 | - `cpb test mycode.cpp -d` to use keyboard as input 38 | - `cpb test mycode.cpp -t 2 -d` to use a test case file as input 39 | - `cpb test /some/path/mycode.cpp -d` debug a program that is not located in your current location 40 | 1. **Submit** your code from the terminal really quickly. 41 | - `cpb submit mycode.cpp` submits your file to the corresponding judge. 42 | 1. open your preferred editor in the contest directory immediately after cloning it. See [Editors](https://searleser97.github.io/cpbooster/docs/configuration/#editor-string) 43 | 44 | 1. **Create** source files with corresponding template loaded 45 | - `cpb create a.py` creates single file with corresponding template loaded based on file extension 46 | - `cpb create {a..n}.cpp` creates multiple consecutive files from "a.cpp" to "n.cpp" 47 | - `cpb create {a...n}.cpp` same as previous command (Any amount of dots greater than 1 work) 48 | - `cpb create {a-n}.cpp` same as previous command (Single dash also works) 49 | - `cpb create /some/path/a.cpp` creates "a.cpp" in the specified path instead of current location 50 | - `cpb create /some/path/{a-n}.cpp` creates "a.cpp ... n.cpp" in the specified path instead of current location 51 | 52 | 1. Vim plugin [cpbooster.vim](https://github.com/searleser97/cpbooster.vim) **boosts** your speed even more 53 | 54 | 1. Flat File Structure. See [Why Flat File Structure](https://searleser97.github.io/cpbooster/docs/clone/#why-flat-file-structure) 55 | 56 | 1. Supports any programming language. 57 | 58 | ## How to Contribute 59 | 60 | 1. Fork this repository and clone it locally: `git clone https://github.com/{yourUsername}/cpbooster` 61 | 2. `cd` (change directory) to `cpbooster` repo directory 62 | 3. Install dependencies: run `npm install` 63 | 4. Install `cpbooster` from source: `npm run install:dev` 64 | 5. Make code changes 65 | 66 | ### Before making a Pull Request 67 | 68 | 1. Lint your code and fix possible linting errors: `npm run lint` 69 | 2. Verify all tests pass: `npm t` 70 | 71 | ### Recommended VSCode extensions 72 | 73 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 74 | - [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 75 | 76 | ## Future Updates (work not started yet, you can contribute by coding any of these features) 77 | 78 | - add `--here` option to `cpb clone` to clone testcases in current directory 79 | - Add Memory Limit Exceeded Veredict (using `gnu-time` as `/usr/bin/time -f '%M' ./program`, can't use `ps` due to lack of precision. 80 | See https://stackoverflow.com/a/131346/13079132) 81 | - Add the possibility to test interactive problems 82 | - Add Stress tests (using brute force solution and tests generator) 83 | - Add the possibility to test current file using test cases of another file (test --as \ or `cpb test --with \`) 84 | - Add the possibility to debug current file using test cases of another file (test --as \ -d) 85 | - Fully support windows CMD. 86 | - Add support for `NODE_ENV` in compile/debug command for debugging purposes. 87 | - Refactor `DebugOne` and `DebugWithUserInput` functions. 88 | - Open editor in created file right after executing `cpb create .` 89 | - Add option per file type/language to not prepend the `// time-limit: xxx` comment on top of a file of specific type/language. 90 | - Solve TODOs in code. 91 | 92 | 93 | ## Final Notes 94 | 95 | - I am open to feature requests. 96 | - Pull Requests are also welcome. 97 | 98 | ## License 99 | 100 | `cpbooster` is licensed under the [GNU General Public License v3.0](https://github.com/searleser97/cpbooster/blob/master/LICENSE) 101 | -------------------------------------------------------------------------------- /app/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ 2 | 3 | const { pathsToModuleNameMapper } = require('ts-jest/utils') 4 | // In the following statement, replace `./tsconfig` with the path to your `tsconfig` file 5 | // which contains the path mapping (ie the `compilerOptions.paths` option): 6 | const { compilerOptions } = require('./tsconfig') 7 | 8 | module.exports = { 9 | preset: 'ts-jest', 10 | testEnvironment: 'node', 11 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths /*, { prefix: '/' } */), 12 | modulePathIgnorePatterns: ['dist'] 13 | }; 14 | -------------------------------------------------------------------------------- /app/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts,.js", 4 | "ignore": [], 5 | "exec": "ts-node ./src/index.ts" 6 | } 7 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpbooster", 3 | "author": "searleser97", 4 | "version": "2.6.5", 5 | "description": "cpbooster is a cross-platform CLI tool designed to boost competitive programmer's speed during contests by automating various routine tasks", 6 | "homepage": "https://github.com/searleser97/cpbooster", 7 | "repository": "github:searleser97/cpbooster", 8 | "keywords": [ 9 | "competitive", 10 | "programming", 11 | "booster", 12 | "tool", 13 | "helper" 14 | ], 15 | "main": "dist/src/index.js", 16 | "bin": { 17 | "cpbooster": "dist/src/index.js", 18 | "cpb": "dist/src/index.js" 19 | }, 20 | "target": "node", 21 | "scripts": { 22 | "start": "npm run build && node dist/index.js test -h", 23 | "start:dev": "nodemon", 24 | "build": "rimraf ./dist && ttsc", 25 | "install:dev": "npm run build && npm uninstall cpbooster -g && npm install -g", 26 | "test": "jest", 27 | "lint": "eslint . --ext .ts", 28 | "lint-and-fix": "eslint . --ext .ts --fix", 29 | "prepublish": "npm run build", 30 | "version": "npm run build" 31 | }, 32 | "files": [ 33 | "*" 34 | ], 35 | "license": "GPL-3.0-only", 36 | "devDependencies": { 37 | "@types/chalk": "^2.2.0", 38 | "@types/diff": "^4.0.2", 39 | "@types/express": "^4.17.6", 40 | "@types/jest": "^26.0.24", 41 | "@types/node": "^14.0.14", 42 | "@types/update-notifier": "^4.1.0", 43 | "@types/yargs": "^15.0.5", 44 | "@typescript-eslint/eslint-plugin": "^4.28.3", 45 | "@typescript-eslint/parser": "^4.28.3", 46 | "@zerollup/ts-transform-paths": "^1.7.18", 47 | "eslint": "^7.31.0", 48 | "eslint-config-prettier": "^8.3.0", 49 | "eslint-plugin-prettier": "^3.4.0", 50 | "jest": "^27.0.6", 51 | "nodemon": "^2.0.4", 52 | "prettier": "^2.3.2", 53 | "rimraf": "^3.0.2", 54 | "ts-jest": "^27.0.4", 55 | "ts-node": "^8.10.2", 56 | "ttypescript": "^1.5.15", 57 | "typescript": "^3.9.6" 58 | }, 59 | "dependencies": { 60 | "chalk": "^4.1.0", 61 | "express": "^4.17.1", 62 | "open": "^8.0.8", 63 | "playwright-chromium": "^1.30.0", 64 | "update-notifier": "^4.1.0", 65 | "yargs": "^15.4.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "plugins": ["@typescript-eslint", "prettier"], 10 | "rules": { 11 | "prettier/prettier": "error", 12 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 13 | "@typescript-eslint/no-non-null-assertion": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/Clone/CCServer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import express from "express"; 20 | import * as fs from "fs"; 21 | import * as Path from "path"; 22 | import ProblemData from "../Types/ProblemData"; 23 | import Config from "../Config/Config"; 24 | import { exit } from "process"; 25 | import { spawn, spawnSync } from "child_process"; 26 | import Util from "../Utils/Util"; 27 | import SourceFileCreator from "../Create/SourceFileCreator"; 28 | import { getEditorCommand } from "./EditorCommandBuilder"; 29 | import chalk from "chalk"; 30 | import Tester from "../Test/TesterFactory/Tester"; 31 | 32 | /* Competitive Companion Server */ 33 | export default class CCServer { 34 | app = express(); 35 | contestName = "NO_NAME"; 36 | contestPath = ""; 37 | platform = "NO_PLATFORM"; 38 | config: Config; 39 | isActive = false; 40 | lastRequestTime = process.hrtime(); 41 | constructor(config: Config) { 42 | this.config = config; 43 | this.app.use(express.json()); 44 | this.app.post("/", (request, response) => { 45 | response.writeHead(200, { "Content-Type": "text/html" }); 46 | response.end("OK"); 47 | 48 | const problemData: ProblemData = request.body; 49 | problemData.name = Util.normalizeFileName(problemData.name); 50 | problemData.group = Util.normalizeFileName(problemData.group); 51 | this.contestName = problemData.group; 52 | this.contestPath = Util.getContestPath(this.contestName, this.config); 53 | if (!fs.existsSync(this.contestPath)) fs.mkdirSync(this.contestPath, { recursive: true }); 54 | const FilesPathNoExtension = `${Path.join(this.contestPath, problemData.name)}`; 55 | if (this.config.createContestPlatformDirectory) { 56 | let [platform, contestName] = problemData.group.split("-").map((str) => str.trim()); 57 | this.platform = platform; 58 | // removes platform name from contest name 59 | contestName = contestName.replace(new RegExp(this.platform, 'g'), ""); 60 | contestName = Util.normalizeFileName(contestName); 61 | // removes extra dots 62 | this.contestName = contestName.replace(/\./g, ""); 63 | } else { 64 | problemData.group = Util.normalizeFileName(problemData.group); 65 | this.contestName = problemData.group; 66 | } 67 | 68 | const contestPath = config.cloneInCurrentDir 69 | ? this.contestName 70 | : this.config.createContestPlatformDirectory 71 | ? Path.join(this.config.contestsDirectory, this.platform, this.contestName) 72 | : Path.join(this.config.contestsDirectory, problemData.group); 73 | if (!fs.existsSync(contestPath)) fs.mkdirSync(contestPath, { recursive: true }); 74 | const FilesPathNoExtension = `${Path.join(contestPath, problemData.name)}`; 75 | const extension = `.${config.preferredLang}`; 76 | const filePath = `${FilesPathNoExtension}${extension}`; 77 | SourceFileCreator.create(filePath, config, false, problemData.timeLimit, problemData.url); 78 | problemData.tests.forEach((testcase, idx) => { 79 | fs.writeFileSync(Tester.getInputPath(filePath, idx + 1), testcase.input); 80 | fs.writeFileSync(Tester.getAnswerPath(filePath, idx + 1), testcase.output); 81 | }); 82 | const tcLen = problemData.tests.length; 83 | console.log(`-> ${problemData.tests.length} Testcase${tcLen == 1 ? "" : "s"}`); 84 | console.log("-------------"); 85 | if (!this.isActive) this.isActive = true; 86 | this.lastRequestTime = process.hrtime(); 87 | }); 88 | } 89 | 90 | run(): void { 91 | if (!this.config.preferredLang) { 92 | console.log("Missing preferred language (preferredLang) key in configuration"); 93 | exit(0); 94 | } 95 | const serverRef = this.app.listen(this.config.port, () => { 96 | console.info("\nserver running at port:", this.config.port); 97 | console.info('\nserver waiting for "Competitive Companion Plugin" to send problems...\n'); 98 | }); 99 | 100 | const interval = setInterval(() => { 101 | if (!this.isActive) return; 102 | const elapsedTime = process.hrtime(this.lastRequestTime)[0]; 103 | const tolerance = Util.isWindows() ? 4 : 1; 104 | if (elapsedTime >= tolerance) { 105 | if (serverRef) serverRef.close(); 106 | clearInterval(interval); 107 | const contestPath = this.config.cloneInCurrentDir 108 | ? this.contestName 109 | : this.config.createContestPlatformDirectory 110 | ? Path.join(this.config.contestsDirectory, this.platform, this.contestName) 111 | : Path.join(this.config.contestsDirectory, this.contestName); 112 | console.log("\n\t DONE!\n"); 113 | console.log(`The path to your contest folder is: "${this.contestPath}"`); 114 | console.log("\n\tHappy Coding!\n"); 115 | const command = getEditorCommand(this.config.editor, this.contestPath); 116 | if (command) { 117 | const newTerminalExec = spawn(command, { shell: true, detached: true, stdio: "ignore" }); 118 | newTerminalExec.unref(); 119 | if (this.config.closeAfterClone && !Util.isWindows()) { 120 | const execution = spawnSync("ps", ["-o", "ppid=", "-p", `${process.ppid}`]); 121 | const grandParentPid = parseInt(execution.stdout.toString().trim()); 122 | if (!Number.isNaN(grandParentPid)) { 123 | process.kill(grandParentPid, "SIGKILL"); 124 | } 125 | } 126 | } else { 127 | console.log( 128 | chalk.yellow( 129 | "The terminal specified in the configuration " + 130 | "file is not fully supported yet, you will have to change your directory manually\n" 131 | ) 132 | ); 133 | } 134 | exit(0); 135 | } 136 | }, 100); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/Clone/Clone.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../Config/Config"; 20 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 21 | import CCServer from "./CCServer"; 22 | 23 | export interface ICommandCloneArgs extends ICommandGlobalArgs { 24 | here?: boolean; 25 | port?: number; 26 | } 27 | 28 | export function clone(args: ICommandCloneArgs): void { 29 | const config = Config.read(args.configPath); 30 | if (args.port) { 31 | config.port = args.port; 32 | } 33 | if (args.here) { 34 | config.cloneInCurrentDir = true; 35 | } 36 | new CCServer(config).run(); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/Clone/EditorCommandBuilder.ts: -------------------------------------------------------------------------------- 1 | export function getEditorCommand(terminalName: string, contestPath: string): string | null { 2 | switch (terminalName) { 3 | case "alacritty": 4 | return `alacritty --working-directory="${contestPath}" & disown`; 5 | case "konsole": 6 | return `konsole --workdir "${contestPath}"`; 7 | case "gnome-terminal": 8 | return `gnome-terminal --working-directory="${contestPath}"`; 9 | case "deepin-terminal": 10 | return `deepin-terminal --work-directory "${contestPath}"`; 11 | case "xterm": 12 | return `xterm -e 'cd "${contestPath}" && bash' & disown`; 13 | case "terminal": 14 | return `open -a terminal "${contestPath}"`; 15 | case "kitty": 16 | return `kitty --directory "${contestPath}"`; 17 | case "vscode": 18 | return `code "${contestPath}"`; 19 | case "neovide": 20 | return `neovide "${contestPath}"`; 21 | default: 22 | return `${terminalName} "${contestPath}"`; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/Config/Config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import * as fs from "fs"; 20 | import * as Path from "path"; 21 | import * as os from "os"; 22 | import { exit } from "process"; 23 | import { LangConfig } from "./Types/LangConfig"; 24 | import Util from "../Utils/Util"; 25 | 26 | export default class Config { 27 | static readonly defaultConfigFilePaths = [ 28 | Path.join(os.homedir(), "cpbooster-config.json"), 29 | Path.join(os.homedir(), ".cpbooster", "cpbooster-config.json"), 30 | Path.join(os.homedir(), ".config", "cpbooster", "cpbooster-config.json") 31 | ]; 32 | 33 | contestsDirectory: string; 34 | port: number; 35 | terminal: string | undefined | null; 36 | editor: string; 37 | closeAfterClone: boolean; 38 | showStatusPageOnSubmit: boolean; 39 | useUserDefaultBrowser: boolean; 40 | createContestPlatformDirectory: boolean; 41 | // preferred language extension 42 | preferredLang: string; 43 | hideTestCaseInput: boolean; 44 | maxLinesToShowFromInput: number; 45 | cloneInCurrentDir: boolean; 46 | groupContestsByJudge: boolean; 47 | executableFileExtension: string; 48 | // config for language extension 49 | languages: Record; 50 | 51 | constructor() { 52 | this.contestsDirectory = Path.join(os.homedir(), "Contests"); 53 | this.port = 1327; 54 | this.editor = "konsole"; 55 | this.closeAfterClone = false; 56 | this.showStatusPageOnSubmit = true; 57 | this.useUserDefaultBrowser = true; 58 | this.createContestPlatformDirectory = true; 59 | this.preferredLang = "cpp"; 60 | this.hideTestCaseInput = false; 61 | this.maxLinesToShowFromInput = 50; 62 | this.cloneInCurrentDir = false; 63 | this.groupContestsByJudge = false; 64 | this.executableFileExtension = "exe"; 65 | this.languages = { 66 | cpp: { 67 | template: "", 68 | command: "g++ -std=gnu++17 -O2", 69 | debugCommand: "g++ -std=gnu++17 -DDEBUG -Wshadow -Wall", 70 | aliases: { 71 | codeforces: "54", 72 | ucup: "C++23", 73 | qoj: "C++23", 74 | universaloj: "C++20", 75 | tlx: "C++20", 76 | atcoder: "5001", 77 | omegaup: "cpp17-gcc", 78 | szkopul: "C++", 79 | yandex: "gcc7_3" 80 | }, 81 | type: "compiled", 82 | commentString: "//" 83 | }, 84 | py: { 85 | template: "", 86 | command: "python3", 87 | debugCommand: "python3 -O", 88 | aliases: { 89 | codeforces: "31", 90 | ucup: "Python3", 91 | qoj: "Python3", 92 | universaloj: "Python3", 93 | tlx: "PyPy 3", 94 | atcoder: "4006", 95 | omegaup: "py3", 96 | szkopul: "Python" 97 | }, 98 | type: "interpreted", 99 | commentString: "#" 100 | }, 101 | java: { 102 | template: "", 103 | command: "javac", 104 | debugCommand: "javac", 105 | runCommand: "java", 106 | aliases: { 107 | codeforces: "36", 108 | ucup: "Java11", 109 | qoj: "Java11", 110 | universaloj: "Java17", 111 | tlx: "Java 11", 112 | atcoder: "4005" 113 | }, 114 | type: "mixed", 115 | commentString: "//" 116 | }, 117 | js: { 118 | template: "", 119 | command: "node", 120 | debugCommand: "node", 121 | aliases: { 122 | codeforces: "55", 123 | atcoder: "4030" 124 | }, 125 | type: "interpreted", 126 | commentString: "//" 127 | }, 128 | rb: { 129 | template: "", 130 | command: "ruby", 131 | debugCommand: "ruby", 132 | aliases: { 133 | codeforces: "67", 134 | atcoder: "4049" 135 | }, 136 | type: "interpreted", 137 | commentString: "#" 138 | }, 139 | cs: { 140 | template: "", 141 | command: "msc", 142 | debugCommand: "msc", 143 | aliases: { 144 | codeforces: "55", 145 | atcoder: "4030", 146 | omegaup: "cs" 147 | }, 148 | type: "compiled", 149 | commentString: "//" 150 | }, 151 | rs: { 152 | template: "", 153 | command: "rustc", 154 | debugCommand: "rustc", 155 | aliases: { 156 | codeforces: "49", 157 | ucup: "Rust", 158 | qoj: "Rust", 159 | tlx: "Rust 2021", 160 | atcoder: "4050" 161 | }, 162 | type: "compiled", 163 | commentString: "//" 164 | }, 165 | go: { 166 | template: "", 167 | command: "go build", 168 | debugCommand: "go build", 169 | aliases: { 170 | codeforces: "32", 171 | tlx: "Go", 172 | atcoder: "4026" 173 | }, 174 | type: "compiled", 175 | commentString: "//" 176 | }, 177 | kt: { 178 | template: "", 179 | command: "kotlinc", 180 | debugCommand: "kotlinc", 181 | runCommand: "kotlin", 182 | aliases: { 183 | codeforces: "48", 184 | atcoder: "4032" 185 | }, 186 | type: "mixed" 187 | }, 188 | scala: { 189 | template: "", 190 | command: "scalac", 191 | debugCommand: "scalac", 192 | runCommand: "scala", 193 | aliases: { 194 | codeforces: "20", 195 | atcoder: "4051" 196 | }, 197 | type: "mixed" 198 | } 199 | }; 200 | } 201 | 202 | private static printAlreadyExistsMsg(configFilePath: string) { 203 | console.log(`"${configFilePath}" already exists`); 204 | } 205 | 206 | private static printConfigWrittenMsg(configFilePath: string) { 207 | console.log(`Your configuration file has been written in: "${configFilePath}"`); 208 | } 209 | 210 | static write(configFilePath: string = this.defaultConfigFilePaths[0]): void { 211 | if (fs.existsSync(configFilePath)) { 212 | this.printAlreadyExistsMsg(configFilePath); 213 | } else { 214 | fs.writeFileSync(configFilePath, JSON.stringify(new Config(), null, 2)); 215 | this.printConfigWrittenMsg(configFilePath); 216 | } 217 | } 218 | 219 | static read(configFilePath: string | undefined | null): Config { 220 | const configFilePaths = configFilePath ? [configFilePath] : []; 221 | configFilePaths.push(...this.defaultConfigFilePaths); 222 | 223 | for (const configPath of configFilePaths) { 224 | if (fs.existsSync(configPath)) { 225 | // for now we are assuming that all the properties are defined in the config file 226 | const config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); 227 | if (config.terminal && !config.editor) { 228 | config.editor = config.terminal; 229 | } 230 | config.contestsDirectory = Util.replaceTildeWithAbsoluteHomePath(config.contestsDirectory); 231 | for (const langConfig of Object.values(config.languages)) { 232 | if (langConfig) { 233 | langConfig.template = Util.replaceTildeWithAbsoluteHomePath(langConfig.template); 234 | } 235 | } 236 | return config; 237 | } 238 | } 239 | 240 | console.log("\nconfiguration file not found in any of the following locations:\n"); 241 | for (const configPath of configFilePaths) { 242 | console.log("->", configPath); 243 | } 244 | console.log("\nYou can create one in your $HOME directory by running 'cpbooster init'\n"); 245 | exit(0); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /app/src/Config/Types/LangAliases.ts: -------------------------------------------------------------------------------- 1 | import { OnlineJudgeName } from "./OnlineJudgeName"; 2 | export type LangAliases = { [K in OnlineJudgeName]?: string }; 3 | -------------------------------------------------------------------------------- /app/src/Config/Types/LangConfig.ts: -------------------------------------------------------------------------------- 1 | import type { LangAliases } from "./LangAliases"; 2 | 3 | export type LangType = "compiled" | "interpreted" | "mixed"; 4 | 5 | export type LangConfig = { 6 | template: string; 7 | command: string; 8 | debugCommand: string; 9 | aliases?: LangAliases; 10 | type?: LangType; 11 | commentString?: string; 12 | runCommand?: string; // used for mixed languages (java, kotlin, ...) 13 | }; 14 | -------------------------------------------------------------------------------- /app/src/Config/Types/OnlineJudgeName.ts: -------------------------------------------------------------------------------- 1 | export enum OnlineJudgeName { 2 | codeforces = "codeforces", 3 | universaloj = "universaloj", 4 | qoj = "qoj", 5 | ucup = "ucup", 6 | tlx = "tlx", 7 | atcoder = "atcoder", 8 | omegaup = "omegaup", 9 | szkopul = "szkopul", 10 | yandex = "yandex" 11 | } 12 | -------------------------------------------------------------------------------- /app/src/Create/BuildLaunchCommand.ts: -------------------------------------------------------------------------------- 1 | import * as Path from "path"; 2 | 3 | function expandRange(filePath: string): string[] { 4 | const directory = Path.dirname(filePath); 5 | const fullFilename = Path.basename(filePath); 6 | const extension = Path.extname(fullFilename); 7 | const filenameWithoutExtension = Path.basename(fullFilename, extension); 8 | 9 | const rangePattern = /\{([a-zA-Z])(-|\.{2,})([a-zA-Z])\}/; 10 | const rangeMatch = filenameWithoutExtension.match(rangePattern); 11 | 12 | if (!rangeMatch) return [filePath]; 13 | 14 | const [ , startChar, , endChar ] = rangeMatch; 15 | let expandedPaths = []; 16 | let startCode = startChar.toLowerCase().charCodeAt(0); 17 | let endCode = endChar.toLowerCase().charCodeAt(0); 18 | 19 | if (endCode < startCode) { 20 | [startCode, endCode] = [endCode, startCode]; 21 | } 22 | 23 | for (let i = startCode; i <= endCode; i++) { 24 | expandedPaths.push( 25 | Path.join(directory, 26 | filenameWithoutExtension.replace( 27 | rangePattern, String.fromCharCode(i)) + extension 28 | ) 29 | ); 30 | } 31 | 32 | return expandedPaths; 33 | } 34 | 35 | export function buildLaunchCommand(terminalName: string, filePath: string): string | null { 36 | const fileDirectory = Path.dirname(filePath); 37 | switch (terminalName) { 38 | // Terminals 39 | case "alacritty": 40 | return `alacritty --working-directory="${fileDirectory}" & disown`; 41 | case "konsole": 42 | return `konsole --workdir "${fileDirectory}"`; 43 | case "gnome-terminal": 44 | return `gnome-terminal --working-directory="${fileDirectory}"`; 45 | case "deepin-terminal": 46 | return `deepin-terminal --work-directory "${fileDirectory}"`; 47 | case "xterm": 48 | return `xterm -e 'cd "${fileDirectory}" && bash' & disown`; 49 | case "terminal": 50 | return `open -a terminal "${fileDirectory}"`; 51 | case "kitty": 52 | return `kitty --directory "${fileDirectory}"`; 53 | 54 | // Editors 55 | case "vscode": 56 | return filePath.includes('{') 57 | ? `code ${expandRange(filePath).map(fp => `"${fp}"`).join(' ')}` 58 | : `code "${filePath}"`; 59 | 60 | default: 61 | return `${terminalName} "${filePath}"`; 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /app/src/Create/Create.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../Config/Config"; 20 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 21 | import SourceFileCreator from "./SourceFileCreator"; 22 | 23 | export interface ICommandCreateArgs extends ICommandGlobalArgs { 24 | filePath: string; 25 | } 26 | 27 | export function create(args: ICommandCreateArgs): void { 28 | SourceFileCreator.create(args.filePath, Config.read(args.configPath), true); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/Create/SourceFileCreator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | import * as fs from "fs"; 19 | import * as Path from "path"; 20 | import * as os from "os"; 21 | import Config from "../Config/Config"; 22 | import Util from "../Utils/Util"; 23 | import { buildLaunchCommand } from './BuildLaunchCommand'; 24 | import chalk from "chalk"; 25 | import { spawn, spawnSync } from "child_process"; 26 | import { exit } from "process"; 27 | 28 | export default class SourceFileCreator { 29 | // The flag determines whether to open the file right after creating it, or not 30 | static create(filePath: string, config: Config, openTerminal: boolean, timeLimitInMS = 3000, problemUrl?: string): void { 31 | const absoluteFilePath = Path.isAbsolute(filePath) 32 | ? filePath 33 | : Path.resolve(process.cwd(), filePath); 34 | const fileDirectory = Path.dirname(absoluteFilePath); 35 | if (!fs.existsSync(fileDirectory)) { 36 | console.error( 37 | chalk.red( 38 | `The specified directory does not exist: ${fileDirectory}` 39 | ) 40 | ); 41 | exit(1); 42 | } 43 | 44 | const filename = Path.basename(filePath); 45 | const match = /\{[a-zA-Z](\.{2,}|-)[a-zA-Z]\}\.[a-zA-Z0-9]+/g.exec(filename); 46 | if (match) { 47 | const idx = match[0].indexOf("}"); 48 | this.createMultiple( 49 | absoluteFilePath, 50 | config, 51 | match[0][1], 52 | match[0][idx - 1], 53 | timeLimitInMS, 54 | problemUrl 55 | ); 56 | } else { 57 | this.createSingle(absoluteFilePath, config, timeLimitInMS, problemUrl); 58 | } 59 | 60 | if (openTerminal) { 61 | const command = buildLaunchCommand(config.editor, absoluteFilePath); 62 | const isWindows = os.type() === "Windows_NT" || os.release().includes("Microsoft"); 63 | if (command) { 64 | const newTerminalExec = spawn(command, { shell: true, detached: true, stdio: "ignore" }); 65 | newTerminalExec.unref(); 66 | if (config.closeAfterClone && !isWindows) { 67 | const execution = spawnSync("ps", ["-o", "ppid=", "-p", `${process.ppid}`]); 68 | const grandParentPid = parseInt(execution.stdout.toString().trim()); 69 | if (!Number.isNaN(grandParentPid)) { 70 | process.kill(grandParentPid, "SIGKILL"); 71 | } 72 | } 73 | } else { 74 | console.log( 75 | chalk.yellow( 76 | "The terminal specified in the configuration " + 77 | "file is not fully supported yet, you will have to change your directory manually\n" 78 | ) 79 | ); 80 | } 81 | } 82 | } 83 | 84 | static createSingle( 85 | filePath: string, 86 | config: Config, 87 | timeLimitInMS = 3000, 88 | problemUrl?: string 89 | ): void { 90 | const langExtension = Util.getExtensionName(filePath); 91 | const filename = Util.normalizeFileName(Path.basename(filePath)); 92 | filePath = Path.join(Path.dirname(filePath), filename); 93 | let template = ""; 94 | const commentString = Util.getCommentString(langExtension, config); 95 | if (commentString) { 96 | template += `${commentString} time-limit: ${timeLimitInMS}\n`; 97 | if (problemUrl) { 98 | template += `${commentString} problem-url: ${problemUrl}\n`; 99 | } 100 | } 101 | const langConfig = config.languages[langExtension]; 102 | if (langConfig?.template) { 103 | template += fs.readFileSync(langConfig.template).toString(); 104 | } 105 | 106 | if (!fs.existsSync(filePath)) { 107 | fs.writeFileSync(filePath, template); 108 | console.info("Source file", filename, "created."); 109 | } else { 110 | console.info("Source file", filename, "already existed."); 111 | } 112 | } 113 | 114 | static createMultiple( 115 | filePath: string, 116 | config: Config, 117 | start: string, 118 | end: string, 119 | timeLimitInMS = 3000, 120 | problemUrl?: string 121 | ): void { 122 | if (start.length != 1 || end.length != 1) { 123 | throw new Error("incorrect format of start or end, it should be a single character"); 124 | } 125 | const dirname = Path.dirname(filePath); 126 | const extension = Path.extname(filePath); 127 | const filePaths: string[] = []; 128 | let startCode = start.toLowerCase().charCodeAt(0); 129 | let endCode = end.toLowerCase().charCodeAt(0); 130 | if (endCode < startCode) { 131 | [startCode, endCode] = [endCode, startCode]; 132 | } 133 | for (let i = startCode; i <= endCode; i++) { 134 | filePaths.push(Path.join(dirname, String.fromCharCode(i) + extension)); 135 | } 136 | for (filePath of filePaths) { 137 | SourceFileCreator.createSingle(filePath, config, timeLimitInMS, problemUrl); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/GlobalConstants.ts: -------------------------------------------------------------------------------- 1 | import * as Path from "path"; 2 | import * as os from "os"; 3 | 4 | export default class GlobalConstants { 5 | static readonly cpboosterHome = Path.join(os.homedir(), ".cpbooster"); 6 | } 7 | -------------------------------------------------------------------------------- /app/src/Init/Init.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 20 | import Config from "../Config/Config"; 21 | 22 | export function init(args: ICommandGlobalArgs): void { 23 | Config.write(args.configPath); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/Login/Login.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import OnlineJudgeFactory from "../Submit/OnlineJudgeFactory/OnlineJudgeFactory"; 20 | 21 | export interface ICommandLoginArgs { 22 | url: string; 23 | } 24 | 25 | export function login(args: ICommandLoginArgs): void { 26 | const oj = OnlineJudgeFactory.getOnlineJudge(args.url); 27 | oj.login(); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/Stat/Stat.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Tester from "../Test/TesterFactory/Tester"; 20 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 21 | 22 | export interface ICommandStatArgs extends ICommandGlobalArgs { 23 | filePath: string; 24 | nextTestCaseFilePaths?: boolean; 25 | } 26 | 27 | export function stat({ nextTestCaseFilePaths, filePath }: ICommandStatArgs): void { 28 | if (nextTestCaseFilePaths) { 29 | const nextTCId = Tester.getNextTestCaseId(filePath); 30 | console.log(Tester.getInputPath(filePath, nextTCId)); 31 | console.log(Tester.getAnswerPath(filePath, nextTCId)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/AtCoder.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class AtCoder extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.atcoder; 25 | readonly loginUrl = "https://atcoder.jp/login"; 26 | readonly blockedResourcesOnSubmit: Set = new Set(["image", "stylesheet", "font"]); 27 | 28 | async isLoggedIn(page: Page): Promise { 29 | const querySelector = "a[href*=logout]"; 30 | return (await page.$(querySelector)) !== null; 31 | } 32 | 33 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 34 | try { 35 | const inputFile = await page.$("input[type=file]"); 36 | if (inputFile) await inputFile.setInputFiles(filePath); 37 | 38 | await page.selectOption("select", { value: langAlias }); 39 | await page.click("#submit"); 40 | return true; 41 | } catch (e) { 42 | return false; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/Codeforces.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class Codeforces extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.codeforces; 25 | readonly loginUrl = "https://codeforces.com/enter"; 26 | readonly blockedResourcesOnSubmit: Set = new Set([ 27 | "image", 28 | "stylesheet", 29 | "font", 30 | "script" 31 | ]); 32 | 33 | async isLoggedIn(page: Page): Promise { 34 | const querySelector = "a[href*=logout]"; 35 | return (await page.$(querySelector)) !== null; 36 | } 37 | 38 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 39 | try { 40 | const inputFile = await page.$("input[type=file]"); 41 | if (inputFile) await inputFile.setInputFiles(filePath); 42 | await page.selectOption("select", { value: langAlias }); 43 | await page.click('input[style="width:10em;"][type=submit]'); 44 | await page.waitForLoadState("domcontentloaded"); 45 | const querySelector = 'span[class="error for__sourceFile"]'; 46 | if ((await page.$(querySelector)) !== null) { 47 | console.log("\nYou have submitted exactly the same code before\n"); 48 | return false; 49 | } else { 50 | return true; 51 | } 52 | } catch (e) { 53 | console.log(e); 54 | return false; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/OmegaUp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class Codeforces extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.omegaup; 25 | readonly loginUrl = "https://omegaup.com/login/"; 26 | readonly blockedResourcesOnSubmit: Set = new Set(["image", "font"]); 27 | 28 | async isLoggedIn(page: Page): Promise { 29 | const querySelector = "a[href*=logout]"; 30 | return (await page.$(querySelector)) !== null; 31 | } 32 | 33 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 34 | try { 35 | await page.click("a[href*=new-run]"); 36 | const inputFile = await page.$("input[type=file]"); 37 | if (inputFile) await inputFile.setInputFiles(filePath); 38 | await page.selectOption("select", { value: langAlias }); 39 | await page.click('button[type=submit][class="btn btn-primary"]'); 40 | return true; 41 | } catch (e) { 42 | console.log(e); 43 | return false; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/OnlineJudge.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { chromium, ChromiumBrowser, ChromiumBrowserContext, Page } from "playwright-chromium"; 20 | import * as fs from "fs"; 21 | import * as Path from "path"; 22 | import { exit } from "process"; 23 | import GlobalConstants from "../../GlobalConstants"; 24 | import Config from "../../Config/Config"; 25 | import { LangAliases } from "../../Config/Types/LangAliases"; 26 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 27 | import open from "open"; 28 | import Util from "../../Utils/Util"; 29 | 30 | type StorageState = { 31 | cookies: Array<{ 32 | name: string; 33 | value: string; 34 | url: string; 35 | domain: string; 36 | path: string; 37 | expires: number; 38 | httpOnly: boolean; 39 | secure: boolean; 40 | sameSite: "Strict" | "Lax" | "None"; 41 | }>; 42 | origins: Array<{ 43 | origin: string; 44 | localStorage: Array<{ 45 | name: string; 46 | value: string; 47 | }>; 48 | }>; 49 | }; 50 | 51 | const sessionFileVersion = "v2"; 52 | 53 | export default abstract class OnlineJudge { 54 | // session cookies are stored in this file 55 | readonly sessionPath = Path.join(GlobalConstants.cpboosterHome, "cpbooster-session.json"); 56 | 57 | abstract readonly onlineJudgeName: string; 58 | abstract readonly loginUrl: string; 59 | 60 | /* unnecesary resources when submitting a file, (i.e. css, script, image, font, ...) 61 | visit: https://playwright.dev/docs/api/class-request#requestresourcetype to get 62 | the list of possible resource names. Blocking these resources reduces the time 63 | required per submission significantly */ 64 | abstract readonly blockedResourcesOnSubmit: Set; 65 | 66 | abstract isLoggedIn(page: Page): Promise; 67 | 68 | /** 69 | * @param {string} filePath path to the source code file 70 | * @param {Page} page submission page, usually same as problem page 71 | * @param {string} langAlias Id of the language for the OnlineJudge. 72 | */ 73 | abstract uploadFile(filePath: string, page: Page, langAlias: string): Promise; 74 | 75 | getSession(): StorageState | undefined { 76 | const previousSession = fs.existsSync(this.sessionPath); 77 | if (!previousSession) return undefined; 78 | const sessionString = fs.readFileSync(this.sessionPath).toString(); 79 | const parsedSession = JSON.parse(sessionString); 80 | if (!parsedSession || parsedSession.version !== sessionFileVersion) { 81 | console.log( 82 | `Version of session file ${this.sessionPath} is deprecated. Please login again to all sites.` 83 | ); 84 | fs.unlinkSync(this.sessionPath); 85 | return undefined; 86 | } 87 | return parsedSession.data; 88 | } 89 | 90 | async restoreSession(browser: ChromiumBrowser): Promise { 91 | const storageState = this.getSession(); 92 | const options: { 93 | userAgent: string; 94 | viewport: null; 95 | storageState?: StorageState; 96 | } = { 97 | userAgent: "chrome", 98 | viewport: null 99 | }; 100 | if (storageState) options.storageState = storageState; 101 | const context = await browser.newContext(options); 102 | return context; 103 | } 104 | 105 | async saveSession(context: ChromiumBrowserContext): Promise { 106 | const storageState = await context.storageState(); 107 | if (!fs.existsSync(GlobalConstants.cpboosterHome)) { 108 | fs.mkdirSync(GlobalConstants.cpboosterHome, { recursive: true }); 109 | } 110 | fs.writeFile( 111 | this.sessionPath, 112 | JSON.stringify( 113 | { 114 | version: sessionFileVersion, 115 | data: storageState 116 | }, 117 | null, 118 | 2 119 | ), 120 | async (err) => { 121 | if (err) { 122 | console.log("Session information could not be written in", this.sessionPath); 123 | } 124 | } 125 | ); 126 | } 127 | 128 | async closeAllOtherTabs(context: ChromiumBrowserContext): Promise { 129 | const pages = context.pages(); 130 | for (let i = 1; i < pages.length; i++) { 131 | pages[i].close(); 132 | } 133 | } 134 | 135 | getLangAliasesObject(langExtension: string, config: Config): LangAliases | undefined { 136 | return config.languages[langExtension]?.aliases; 137 | } 138 | 139 | getLangAlias(filePath: string, config: Config): string | undefined { 140 | const lang = Util.getExtensionName(filePath); 141 | const langAliases = this.getLangAliasesObject(lang, config); 142 | return langAliases?.[this.onlineJudgeName as OnlineJudgeName]; 143 | } 144 | 145 | async openBrowserInUrl(url: string, useUserDefaultBrowser: boolean): Promise { 146 | try { 147 | if (useUserDefaultBrowser) { 148 | await open(url); 149 | } else { 150 | const browser = await chromium.launch({ headless: false }); 151 | const context = await this.restoreSession(browser); 152 | context.on("page", (_) => this.closeAllOtherTabs(context)); 153 | const pages = context.pages(); 154 | const page = pages.length > 0 ? pages[0] : await context.newPage(); 155 | page.on("close", (_) => exit(0)); 156 | await page.goto(url); 157 | } 158 | } catch (_) { 159 | // This line apparently never gets executed 160 | console.log("Browser closed"); 161 | } 162 | } 163 | 164 | async login(): Promise { 165 | const browser = await chromium.launch({ headless: false }); 166 | const context = await this.restoreSession(browser); 167 | 168 | context.on("page", (_) => this.closeAllOtherTabs(context)); 169 | const pages = context.pages(); 170 | const page = pages.length > 0 ? pages[0] : await context.newPage(); 171 | 172 | try { 173 | await page.goto(this.loginUrl); 174 | 175 | while (!(await this.isLoggedIn(page))) { 176 | if (page.url() !== this.loginUrl) { 177 | await page.goto(this.loginUrl); 178 | } 179 | await page.waitForNavigation({ timeout: 0 }); 180 | } 181 | 182 | console.log("Succesful login!"); 183 | await this.saveSession(context); 184 | await browser.close(); 185 | } catch (e) { 186 | console.log("Unsuccesful login!"); 187 | exit(0); // this helps to avoid printing any further errors 188 | } 189 | } 190 | 191 | async submit(filePath: string, url: string, config: Config, langAlias?: string): Promise { 192 | if (!langAlias) { 193 | langAlias = this.getLangAlias(filePath, config); 194 | if (!langAlias) { 195 | console.log( 196 | `${this.onlineJudgeName} alias for "${Util.getExtensionName( 197 | filePath 198 | )}" was not found in config file.` 199 | ); 200 | exit(0); 201 | } 202 | } 203 | 204 | const browser = await chromium.launch({ headless: true }); 205 | const context = await this.restoreSession(browser); 206 | 207 | const pages = context.pages(); 208 | const page = pages.length > 0 ? pages[0] : await context.newPage(); 209 | 210 | await page.route("**/*", (route) => { 211 | if (this.blockedResourcesOnSubmit.has(route.request().resourceType())) { 212 | route.abort(); 213 | } else { 214 | route.continue(); 215 | } 216 | }); 217 | 218 | await page.goto(url); 219 | 220 | if (!(await this.isLoggedIn(page))) { 221 | await browser.close(); 222 | await this.login(); 223 | return await this.submit(filePath, url, config, langAlias); 224 | } 225 | 226 | try { 227 | const result = await this.uploadFile(filePath, page, langAlias); 228 | if (result) { 229 | console.log("File submitted succesfully"); 230 | } else { 231 | console.log("Error: File was not submitted"); 232 | } 233 | await this.saveSession(context); 234 | await browser.close(); 235 | if (result && config.showStatusPageOnSubmit) { 236 | try { 237 | // waiting for half a second also fixes the error: 238 | // -> route.continue: Target page, context or browser has been closedError 239 | // when you submit for the very first time after installing cpbooster 240 | //await new Promise((resolve) => setTimeout(resolve, 500)); 241 | await this.openBrowserInUrl(page.url(), config.useUserDefaultBrowser); 242 | } catch (_) { 243 | // fixes the error: route.continue: Target page, context or browser has been closedError 244 | // when you submit for the very first time after installing cpbooster 245 | await this.openBrowserInUrl(page.url(), config.useUserDefaultBrowser); 246 | } 247 | } 248 | } catch (e) { 249 | console.log(e); 250 | console.log("Error: File was not submitted"); 251 | exit(0); 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/OnlineJudgeFactory.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { exit } from "process"; 20 | import AtCoder from "./AtCoder"; 21 | import Codeforces from "./Codeforces"; 22 | import UniversalOJ from "./UniversalOJ"; 23 | import QOJ from "./QOJ"; 24 | import Ucup from "./Ucup"; 25 | import TLX from "./TLX"; 26 | import OmegaUp from "./OmegaUp"; 27 | import Szkopul from "./Szkopul"; 28 | import Yandex from "./Yandex"; 29 | import OnlineJudge from "./OnlineJudge"; 30 | 31 | export default class OnlineJudgeFactory { 32 | static getOnlineJudge(url: string): OnlineJudge { 33 | url = url.toLowerCase(); 34 | if (url.includes("codeforces")) { 35 | return new Codeforces(); 36 | } else if (url.includes("atcoder")) { 37 | return new AtCoder(); 38 | } else if (url.includes("uoj.ac")) { 39 | return new UniversalOJ(); 40 | } else if (url.includes("qoj.ac")) { 41 | return new QOJ(); 42 | } else if (url.includes("contest.ucup.ac")) { 43 | return new Ucup(); 44 | } else if (url.includes("tlx.toki.id")) { 45 | return new TLX(); 46 | } else if (url.includes("omegaup")) { 47 | return new OmegaUp(); 48 | } else if (url.includes("szkopul.edu.pl")) { 49 | return new Szkopul(); 50 | } else if (url.includes("official.contest.yandex")) { 51 | return new Yandex(); 52 | } else { 53 | console.log("Online Judge not supported"); 54 | exit(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/QOJ.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2023 user202729 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 20 | import UniversalOJGeneric from "./UniversalOJGeneric"; 21 | 22 | export default class QOJ extends UniversalOJGeneric { 23 | readonly onlineJudgeName = OnlineJudgeName.qoj; 24 | readonly loginUrl = "https://qoj.ac/login"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/Szkopul.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2022 ??? 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class Szkopul extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.szkopul; 25 | readonly loginUrl = "https://szkopul.edu.pl/login/"; 26 | readonly blockedResourcesOnSubmit: Set = new Set([ 27 | "image", 28 | "stylesheet", 29 | "font", 30 | "script" 31 | ]); 32 | 33 | async isLoggedIn(page: Page): Promise { 34 | const querySelector = '[data-post-url="/logout/"]'; 35 | return (await page.$(querySelector)) !== null; 36 | } 37 | 38 | async uploadFile(filePath: string, page: Page, _langAlias: string): Promise { 39 | try { 40 | const inputFile = await page.$("input#id_file[type=file]"); 41 | if (inputFile) await inputFile.setInputFiles(filePath); 42 | else return false; 43 | await page.click("button[type=submit]"); 44 | await page.waitForLoadState("domcontentloaded"); 45 | return true; 46 | } catch (e) { 47 | console.log(e); 48 | return false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/TLX.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2023 user202729 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class TLX extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.tlx; 25 | readonly loginUrl = "https://tlx.toki.id/login"; 26 | readonly blockedResourcesOnSubmit: Set = new Set(["image", "font"]); 27 | 28 | async isLoggedIn(page: Page): Promise { 29 | const querySelector = "a[href='/login']"; 30 | return (await page.$(querySelector)) === null; 31 | } 32 | 33 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 34 | const inputFileOrError = await page.locator( 35 | "div.programming-problem-worksheet form input[type=file][name='sourceFiles.source'], " + 36 | "[data-key=reason-not-allowed-to-submit]" 37 | ); 38 | if ((await inputFileOrError.getAttribute("data-key")) === "reason-not-allowed-to-submit") { 39 | console.log(await inputFileOrError.innerText()); 40 | return false; 41 | } 42 | const inputFile = inputFileOrError; 43 | await inputFileOrError.setInputFiles(filePath); 44 | await page 45 | .locator("div.programming-problem-worksheet form button[data-key=gradingLanguage]") 46 | .click(); 47 | const languageSelector = page 48 | .locator( 49 | "div.programming-problem-worksheet div.bp5-popover div.bp5-popover-content a[data-key]" 50 | ) 51 | .filter({ hasText: langAlias }); 52 | await languageSelector.waitFor({ state: "visible" }); 53 | await languageSelector.click(); 54 | await languageSelector.waitFor({ state: "hidden" }); 55 | 56 | await page.locator("div.programming-problem-worksheet form button[type=submit]").click(); 57 | await page.waitForLoadState("domcontentloaded"); 58 | 59 | return true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/Ucup.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2023 user202729 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 20 | import UniversalOJGeneric from "./UniversalOJGeneric"; 21 | 22 | export default class Ucup extends UniversalOJGeneric { 23 | readonly onlineJudgeName = OnlineJudgeName.ucup; 24 | readonly loginUrl = "https://contest.ucup.ac/login"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/UniversalOJ.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2023 user202729 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 20 | import UniversalOJGeneric from "./UniversalOJGeneric"; 21 | 22 | export default class UniversalOJ extends UniversalOJGeneric { 23 | readonly onlineJudgeName = OnlineJudgeName.universaloj; 24 | readonly loginUrl = "https://uoj.ac/login"; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/UniversalOJGeneric.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2023 user202729 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | 22 | export default abstract class UniversalOJGeneric extends OnlineJudge { 23 | readonly blockedResourcesOnSubmit: Set = new Set(["image", "stylesheet", "font"]); 24 | 25 | async isLoggedIn(page: Page): Promise { 26 | return (await page.locator('a[href*="/logout"]').count()) !== 0; 27 | } 28 | 29 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 30 | try { 31 | await page.locator('a[href="#tab-submit-answer"]').click(); 32 | await page 33 | .locator('input[type=radio][value=file][name="answer_answer_upload_type"]') 34 | .setChecked(true); 35 | await page.locator("input[type=file][name=answer_answer_file]").setInputFiles(filePath); 36 | await page.locator("select[name=answer_answer_language]").selectOption(langAlias); 37 | const submitButton = page.locator('form button[type=submit][name="submit-answer"]'); 38 | await submitButton.click(); 39 | for (let i = 0; i < 5; i++) { 40 | await page.waitForLoadState("domcontentloaded"); 41 | try { 42 | await page.waitForURL("**/submissions", { timeout: 100, waitUntil: "domcontentloaded" }); 43 | return true; 44 | } catch { 45 | // the button click did not work? 46 | try { 47 | if ((await submitButton.count()) > 0) await submitButton.click(); 48 | } catch { 49 | continue; 50 | } 51 | } 52 | } 53 | return false; 54 | } catch (e) { 55 | console.log(e); 56 | return false; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/Yandex.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { Page } from "playwright-chromium"; 20 | import OnlineJudge from "./OnlineJudge"; 21 | import { OnlineJudgeName } from "../../Config/Types/OnlineJudgeName"; 22 | 23 | export default class Yandex extends OnlineJudge { 24 | readonly onlineJudgeName = OnlineJudgeName.yandex; 25 | readonly loginUrl = "https://official.contest.yandex.com/login"; 26 | readonly blockedResourcesOnSubmit: Set = new Set(["image", "stylesheet", "font"]); 27 | 28 | async isLoggedIn(page: Page): Promise { 29 | const querySelector = "form[action*=logout]"; 30 | return (await page.$(querySelector)) !== null; 31 | } 32 | 33 | async uploadFile(filePath: string, page: Page, langAlias: string): Promise { 34 | try { 35 | await page.selectOption("select", { value: langAlias }); 36 | await page.click("input[value=file]"); 37 | const inputFile = await page.$("input[type=file]"); 38 | if (inputFile) await inputFile.setInputFiles(filePath); 39 | await page.click("div.problem__send > button"); 40 | await page.waitForLoadState("load"); 41 | if ((await page.$("div[class*=msg_type_warn]")) !== null) { 42 | console.log("\nYou have submitted exactly the same code before\n"); 43 | return false; 44 | } else { 45 | return true; 46 | } 47 | } catch (e) { 48 | return false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/Submit/OnlineJudgeFactory/__tests__/OnlineJudge.test.ts: -------------------------------------------------------------------------------- 1 | import Config from "../../../Config/Config"; 2 | 3 | describe("OnlineJudge.ts", () => { 4 | describe("test languages Record", () => { 5 | it("should return undefined when key does not exists in languages", () => { 6 | const config = new Config(); 7 | expect(config.languages["unknownExtension"]).toEqual(undefined); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /app/src/Submit/Submit.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { exit } from "process"; 20 | import Config from "../Config/Config"; 21 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 22 | import OnlineJudgeFactory from "./OnlineJudgeFactory/OnlineJudgeFactory"; 23 | import * as fs from "fs"; 24 | import * as Path from "path"; 25 | import Util from "../Utils/Util"; 26 | 27 | export interface ICommandSubmitArgs extends ICommandGlobalArgs { 28 | filePath: string; 29 | url?: string; 30 | langAlias?: string; 31 | } 32 | 33 | function extractUrlFromFile(filePath: string, config: Config): string | undefined { 34 | const text = fs.readFileSync(filePath).toString(); 35 | const commentString = Util.getCommentString(Path.extname(filePath), config); 36 | const re = new RegExp(String.raw`^\s*${commentString}\s*problem-url\s*:\s*(.+)$`, "gm"); 37 | const match = re.exec(text); 38 | if (match) { 39 | return match[1].trim(); 40 | } else { 41 | return undefined; 42 | } 43 | } 44 | 45 | export function submit(args: ICommandSubmitArgs): void { 46 | const config = Config.read(args.configPath); 47 | const url = args.url ?? extractUrlFromFile(args.filePath, config); 48 | if (!url) { 49 | const commentString = Util.getCommentString(Path.extname(args.filePath), config); 50 | console.log( 51 | "Problem URL couldn't be found in file, please provide it as argument or" + 52 | " add it as a comment in your file in the following format:\n\n" + 53 | `${commentString} problem-url: ` 54 | ); 55 | exit(0); 56 | } 57 | console.log("submitting..."); 58 | const oj = OnlineJudgeFactory.getOnlineJudge(url); 59 | oj.submit(args.filePath, url, config, args.langAlias); 60 | } 61 | -------------------------------------------------------------------------------- /app/src/Test/Test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../Config/Config"; 20 | import ICommandGlobalArgs from "../Types/ICommandGlobalArgs"; 21 | import Tester from "./TesterFactory/Tester"; 22 | import TesterFactory from "./TesterFactory/TesterFactory"; 23 | 24 | export interface ICommandTestArgs extends ICommandGlobalArgs { 25 | filePath: string; 26 | testId?: number; 27 | debug?: boolean; 28 | noCompile?: boolean; 29 | add?: boolean; 30 | } 31 | 32 | export function test(args: ICommandTestArgs): void { 33 | const config = Config.read(args.configPath); 34 | if (args.add) { 35 | Tester.createTestCase(args.filePath); 36 | } else { 37 | const tester = TesterFactory.getTester(config, args.filePath); 38 | if (args.debug) { 39 | if (args.testId) tester.debugOne(args.testId, !args.noCompile); 40 | else tester.debugWithUserInput(!args.noCompile); 41 | } else { 42 | if (args.testId) tester.testOne(args.testId, !args.noCompile); 43 | else tester.testAll(!args.noCompile); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/Test/TesterFactory/CompiledTester.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../../Config/Config"; 20 | import * as Path from "path"; 21 | import * as fs from "fs"; 22 | import * as os from "os"; 23 | import chalk from "chalk"; 24 | import Util from "../../Utils/Util"; 25 | import { spawnSync } from "child_process"; 26 | import { exit } from "process"; 27 | import { Veredict } from "../../Types/Veredict"; 28 | import Tester from "./Tester"; 29 | 30 | // Todo: If the list increases significantly, creating a Set containing 31 | // the enum values would be reasonable for quick access. 32 | export enum NonStandardCompilers { 33 | mcs = "mcs", 34 | csc = "csc" 35 | } 36 | 37 | export default class CompiledTester extends Tester { 38 | private static fileNameOptionForCommand: Map = new Map([ 39 | [NonStandardCompilers.mcs, "-out:"], // csharp compiler 40 | [NonStandardCompilers.csc, "/out:"] // csharp compiler 41 | ]); 42 | 43 | constructor(config: Config, filePath: string) { 44 | super(config, filePath); 45 | } 46 | 47 | testOne(testId: number, shouldCompile: boolean): Veredict { 48 | const binaryFileName = this.getExecutableFileNameOrDefault(false); 49 | const binaryFilePath = `.${Path.sep}${binaryFileName}`; 50 | const hasValidConditions = (): { status: boolean; feedback: string } => { 51 | let result = { status: true, feedback: "" }; 52 | if (!fs.existsSync(binaryFilePath)) { 53 | result = { 54 | status: false, 55 | feedback: `${chalk.red("Error:")} Executable ${binaryFilePath} not found` 56 | }; 57 | } 58 | return result; 59 | }; 60 | const { veredict, feedback } = this.getTestVeredict( 61 | binaryFilePath, 62 | [], 63 | testId, 64 | hasValidConditions, 65 | shouldCompile, 66 | this.compile.bind(this) 67 | ); 68 | this.printTestResults(veredict, feedback, testId); 69 | return veredict; 70 | } 71 | 72 | debugOne(testId: number, compile: boolean): void { 73 | const binaryFileName = this.getExecutableFileNameOrDefault(true); 74 | const binaryFilePath = `.${Path.sep}${binaryFileName}`; 75 | if (compile) { 76 | const { status, feedback } = this.compile(true); 77 | if (!status) { 78 | this.printTestResults(Veredict.CE, feedback, testId); 79 | exit(0); 80 | } 81 | } else if (!fs.existsSync(binaryFilePath)) { 82 | console.log(chalk.red("Error:"), `Executable ${binaryFilePath} not found`); 83 | exit(0); 84 | } 85 | this.runDebug(binaryFilePath, [], testId); 86 | } 87 | 88 | debugWithUserInput(compile: boolean): void { 89 | const binaryFileName = this.getExecutableFileNameOrDefault(true); 90 | const binaryFilePath = `.${Path.sep}${binaryFileName}`; 91 | if (compile) { 92 | const { status, feedback } = this.compile(true); 93 | if (!status) { 94 | this.printTestResults(Veredict.CE, feedback, 0); 95 | exit(0); 96 | } 97 | } else if (!fs.existsSync(binaryFilePath)) { 98 | console.log(chalk.red("Error:"), `Executable ${binaryFilePath} not found`); 99 | exit(0); 100 | } 101 | this.runDebugWithUserInput(binaryFilePath); 102 | } 103 | 104 | getExecutableFileName(debug: boolean): string | undefined { 105 | const segmentedCommand = this.getSegmentedCommand(this.langExtension, debug); 106 | const compilerCommand = this.getCompilerCommand(this.langExtension, debug); 107 | const fileNameOption = CompiledTester.getFileNameOptionForCompilerCommand(compilerCommand); 108 | 109 | for (let i = 0; i < segmentedCommand.length; i++) { 110 | if (segmentedCommand[i].startsWith(fileNameOption)) { 111 | if (Object.keys(NonStandardCompilers).includes(compilerCommand)) { 112 | // using pop to reduce amount of code to return the last element 113 | return segmentedCommand[i].split(":").pop(); 114 | } else { 115 | return segmentedCommand[i + 1]; 116 | } 117 | } 118 | } 119 | return undefined; 120 | } 121 | 122 | getDefaultExecutableFileName(debug: boolean): string { 123 | let defaultName = Util.replaceAll(Path.parse(this.filePath).name, " ", ""); 124 | if (debug) defaultName += "debug"; 125 | let fileExtension = this.config.executableFileExtension; 126 | if (fileExtension) { 127 | // replace for cases where user includes unnecessary characters 128 | // ensures the option still works as expected 129 | fileExtension = fileExtension.replace(/\s+/g, '').replace(/^\./, ''); 130 | defaultName += "." + fileExtension; 131 | } 132 | return defaultName; 133 | } 134 | 135 | getExecutableFileNameOrDefault(debug: boolean): string { 136 | return this.getExecutableFileName(debug) ?? this.getDefaultExecutableFileName(debug); 137 | } 138 | 139 | static getFileNameOptionForCompilerCommand(compilerCommand: string): string { 140 | return CompiledTester.fileNameOptionForCommand.get(compilerCommand) ?? "-o"; 141 | } 142 | 143 | static printCompilingMsg(): void { 144 | console.log("Compiling...\n"); 145 | } 146 | 147 | static executeCompilation( 148 | compilerCommand: string, 149 | args: string[] 150 | ): { status: boolean; feedback: string } { 151 | const result = { status: true, feedback: "" }; 152 | const compilation = spawnSync(compilerCommand, args); 153 | 154 | if (compilation.stderr) { 155 | // if (compilation.stderr || compilation.status !== 0) { 156 | let compileStderr = Buffer.from(compilation.stderr).toString("utf8").trim(); 157 | if (compileStderr !== "") { 158 | // TODO: replace with regex instead of split and join ignoring case 159 | compileStderr = compileStderr.split("error").join(chalk.redBright("error")); 160 | compileStderr = compileStderr.split("warning").join(chalk.blueBright("warning")); 161 | // TODO: replace with regex match ignoring case 162 | if (compileStderr.includes("error")) { 163 | result.status = false; 164 | } 165 | result.feedback = compileStderr; 166 | } 167 | } 168 | return result; 169 | } 170 | 171 | compile(debug: boolean): { status: boolean; feedback: string } { 172 | CompiledTester.printCompilingMsg(); 173 | const segmentedCommand = this.getSegmentedCommand(this.langExtension, debug); 174 | 175 | const args = [...segmentedCommand.slice(1)]; 176 | const compilerCommand = this.getCompilerCommand(this.langExtension, debug); 177 | 178 | if (!this.getExecutableFileName(debug)) { 179 | if (Object.keys(NonStandardCompilers).includes(compilerCommand)) { 180 | args.push( 181 | CompiledTester.getFileNameOptionForCompilerCommand(compilerCommand) + 182 | this.getDefaultExecutableFileName(debug) 183 | ); 184 | } else { 185 | args.push( 186 | CompiledTester.getFileNameOptionForCompilerCommand(compilerCommand), 187 | this.getDefaultExecutableFileName(debug) 188 | ); 189 | } 190 | } 191 | 192 | args.push(this.filePath); 193 | 194 | return CompiledTester.executeCompilation(compilerCommand, args); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/Test/TesterFactory/InterpretedTester.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../../Config/Config"; 20 | import { Veredict } from "../../Types/Veredict"; 21 | import Tester from "./Tester"; 22 | 23 | export default class InterpretedTester extends Tester { 24 | constructor(config: Config, filePath: string) { 25 | super(config, filePath); 26 | } 27 | 28 | testOne(testId: number, _compile: boolean): Veredict { 29 | const commandAsArray = this.getSegmentedCommand(this.langExtension, false); 30 | const { veredict, feedback } = this.getTestVeredict( 31 | commandAsArray[0], 32 | [...commandAsArray.slice(1), this.filePath], 33 | testId, 34 | () => ({ status: true, feedback: "" }) 35 | ); 36 | this.printTestResults(veredict, feedback, testId); 37 | return veredict; 38 | } 39 | 40 | debugOne(testId: number, _compile: boolean): void { 41 | const debugCommandAsArray = this.getSegmentedCommand(this.langExtension, true); 42 | this.runDebug(debugCommandAsArray[0], [...debugCommandAsArray.slice(1), this.filePath], testId); 43 | } 44 | 45 | debugWithUserInput(_compile: boolean): void { 46 | const debugCommandAsArray = this.getSegmentedCommand(this.langExtension, true); 47 | this.runDebugWithUserInput(debugCommandAsArray[0], [ 48 | ...debugCommandAsArray.slice(1), 49 | this.filePath 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/Test/TesterFactory/MixedTester.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import Config from "../../Config/Config"; 20 | import { Veredict } from "../../Types/Veredict"; 21 | import Tester from "./Tester"; 22 | import chalk from "chalk"; 23 | import * as Path from "path"; 24 | import * as fs from "fs"; 25 | import { exit } from "process"; 26 | import CompiledTester from "./CompiledTester"; 27 | import { LangExtensions } from "Utils/LangExtensions"; 28 | 29 | export default class MixedTester extends Tester { 30 | constructor(config: Config, filePath: string) { 31 | super(config, filePath); 32 | } 33 | 34 | getExecutableFileName(): string { 35 | return `${this.getExecutableFileNameNoExtension()}${ 36 | this.langExtension === LangExtensions.kotlin ? "Kt" : "" 37 | }.class`; 38 | } 39 | 40 | getExecutableFileNameNoExtension(): string { 41 | return ( 42 | Path.basename(this.filePath).replace(`.${this.langExtension}`, "") + 43 | (this.langExtension === LangExtensions.kotlin ? "Kt" : "") 44 | ); 45 | } 46 | 47 | testOne(testId: number, shouldCompile: boolean): Veredict { 48 | const executableFileName = this.getExecutableFileName(); 49 | const hasValidConditions = (): { status: boolean; feedback: string } => { 50 | let result = { status: true, feedback: "" }; 51 | if (!fs.existsSync(executableFileName)) { 52 | result = { 53 | status: false, 54 | feedback: `${chalk.red( 55 | "Error:" 56 | )} Executable ${executableFileName} not found, Is your class name same as the file name ?` 57 | }; 58 | } 59 | return result; 60 | }; 61 | const langConfig = this.config.languages[this.langExtension]; 62 | if (langConfig?.runCommand) { 63 | const segmentedRunCommand = langConfig.runCommand.split(" "); 64 | segmentedRunCommand.push(this.getExecutableFileNameNoExtension()); 65 | 66 | const { veredict, feedback } = this.getTestVeredict( 67 | segmentedRunCommand[0], 68 | segmentedRunCommand.slice(1), 69 | testId, 70 | hasValidConditions, 71 | shouldCompile, 72 | this.compile.bind(this) // attaching compile function to `this` context 73 | ); 74 | this.printTestResults(veredict, feedback, testId); 75 | return veredict; 76 | } else { 77 | console.log(`runCommand not specified for ${this.langExtension} files`); 78 | exit(0); 79 | } 80 | } 81 | 82 | compile(debug: boolean): { status: boolean; feedback: string } { 83 | CompiledTester.printCompilingMsg(); 84 | const segmentedCommand = this.getSegmentedCommand(this.langExtension, debug); 85 | 86 | const args = segmentedCommand.slice(1); 87 | const compilerCommand = this.getCompilerCommand(this.langExtension, debug); 88 | 89 | args.push(this.filePath); 90 | 91 | return CompiledTester.executeCompilation(compilerCommand, args); 92 | } 93 | 94 | debugOne(testId: number, compile: boolean): void { 95 | const executableFileName = this.getExecutableFileName(); 96 | if (compile) { 97 | const { status, feedback } = this.compile(true); 98 | if (!status) { 99 | this.printTestResults(Veredict.CE, feedback, testId); 100 | exit(0); 101 | } 102 | } else if (!fs.existsSync(executableFileName)) { 103 | console.log( 104 | chalk.red("Error:"), 105 | `Executable ${executableFileName} not found, Is your class name same as the file name?` 106 | ); 107 | exit(0); 108 | } 109 | const langConfig = this.config.languages[this.langExtension]!; 110 | if (langConfig.runCommand) { 111 | const segmentedRunCommand = langConfig.runCommand.split(" "); 112 | segmentedRunCommand.push(this.getExecutableFileNameNoExtension()); 113 | return this.runDebug(segmentedRunCommand[0], segmentedRunCommand.slice(1), testId); 114 | } else { 115 | console.log(`runCommand not specified for ${this.langExtension} files`); 116 | exit(0); 117 | } 118 | } 119 | 120 | debugWithUserInput(compile: boolean): void { 121 | const executableFileName = this.getExecutableFileName(); 122 | if (compile) { 123 | const { status, feedback } = this.compile(true); 124 | if (!status) { 125 | this.printTestResults(Veredict.CE, feedback, 0); 126 | exit(0); 127 | } 128 | } else if (!fs.existsSync(executableFileName)) { 129 | console.log( 130 | chalk.red("Error:"), 131 | `Executable ${executableFileName} not found, Is your class name same as the file name?` 132 | ); 133 | exit(0); 134 | } 135 | const langConfig = this.config.languages[this.langExtension]!; 136 | if (langConfig.runCommand) { 137 | const segmentedRunCommand = langConfig.runCommand.split(" "); 138 | segmentedRunCommand.push(this.getExecutableFileNameNoExtension()); 139 | return this.runDebugWithUserInput(segmentedRunCommand[0], segmentedRunCommand.slice(1)); 140 | } else { 141 | console.log(`runCommand not specified for ${this.langExtension} files`); 142 | exit(0); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/Test/TesterFactory/TesterFactory.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | import Config from "../../Config/Config"; 19 | import CompiledTester from "./CompiledTester"; 20 | import { exit } from "process"; 21 | import * as fs from "fs"; 22 | import InterpretedTester from "./InterpretedTester"; 23 | import Tester from "./Tester"; 24 | import { LangExtensions } from "../../Utils/LangExtensions"; 25 | import Util from "../../Utils/Util"; 26 | import MixedTester from "Test/TesterFactory/MixedTester"; 27 | 28 | export default class TesterFactory { 29 | static compiledExtensions = new Set([ 30 | LangExtensions.cpp.toString(), 31 | LangExtensions.c.toString(), 32 | LangExtensions.csharp.toString(), 33 | LangExtensions.go.toString() 34 | ]); 35 | 36 | static interpretedExtensions = new Set([ 37 | LangExtensions.python.toString(), 38 | LangExtensions.javascript.toString(), 39 | LangExtensions.ruby.toString() 40 | ]); 41 | 42 | static mixedExtensions = new Set([ 43 | LangExtensions.java.toString(), 44 | LangExtensions.kotlin.toString(), 45 | LangExtensions.scala.toString() 46 | ]); 47 | 48 | static getTester(config: Config, filePath: string): Tester { 49 | if (!fs.existsSync(filePath)) { 50 | console.log("File not found:", filePath); 51 | exit(0); 52 | } 53 | 54 | const langExtension = Util.getExtensionName(filePath); 55 | 56 | if ( 57 | config.languages[langExtension]?.type === "compiled" || 58 | this.compiledExtensions.has(langExtension) 59 | ) { 60 | return new CompiledTester(config, filePath); 61 | } else if ( 62 | config.languages[langExtension]?.type === "interpreted" || 63 | this.interpretedExtensions.has(langExtension) 64 | ) { 65 | return new InterpretedTester(config, filePath); 66 | } else if ( 67 | config.languages[langExtension]?.type === "mixed" || 68 | this.mixedExtensions.has(langExtension) 69 | ) { 70 | return new MixedTester(config, filePath); 71 | } else { 72 | console.log("Language not supported"); 73 | exit(0); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/Test/__tests__/Tester.test.ts: -------------------------------------------------------------------------------- 1 | import Tester from "../TesterFactory/Tester"; 2 | import * as fs from "fs"; 3 | 4 | jest.mock("fs"); 5 | 6 | describe("Tester.ts", () => { 7 | describe("test getMaxTestCaseId", () => { 8 | const problemName = "problemName"; 9 | const problemFilePath = `${problemName}.cpp`; 10 | const readdirSyncMock = fs.readdirSync as jest.Mock; 11 | beforeEach(() => { 12 | readdirSyncMock.mockReset(); 13 | }); 14 | test("common scenario", () => { 15 | readdirSyncMock.mockReturnValue([ 16 | `${problemName}.in1`, 17 | `${problemName}.ans1`, 18 | `${problemName}.in2`, 19 | `${problemName}.ans2`, 20 | `${problemName}.in3`, 21 | `${problemName}.ans3` 22 | ]); 23 | expect(Tester.getMaxTestCaseId(problemFilePath)).toEqual(3); 24 | }); 25 | 26 | it("should return 0 when there are no testcases files", () => { 27 | readdirSyncMock.mockReturnValue([]); 28 | expect(Tester.getMaxTestCaseId(problemName)).toEqual(0); 29 | }); 30 | 31 | it("should return the max id even when the sequence is not continuous", () => { 32 | readdirSyncMock.mockReturnValue([ 33 | `${problemName}.in1`, 34 | `${problemName}.ans1`, 35 | `${problemName}.in3`, 36 | `${problemName}.ans3`, 37 | `${problemName}.in6`, 38 | `${problemName}.ans6` 39 | ]); 40 | expect(Tester.getMaxTestCaseId(problemFilePath)).toEqual(6); 41 | }); 42 | }); 43 | 44 | describe("test getNextTestCaseId", () => { 45 | const problemName = "problemName"; 46 | const problemFilePath = `${problemName}.cpp`; 47 | const readdirSyncMock = fs.readdirSync as jest.Mock; 48 | beforeEach(() => { 49 | readdirSyncMock.mockReset(); 50 | }); 51 | test("common scenario", () => { 52 | readdirSyncMock.mockReturnValue([ 53 | `${problemName}.in1`, 54 | `${problemName}.ans1`, 55 | `${problemName}.in2`, 56 | `${problemName}.ans2`, 57 | `${problemName}.in3`, 58 | `${problemName}.ans3` 59 | ]); 60 | expect(Tester.getNextTestCaseId(problemFilePath)).toEqual(4); 61 | }); 62 | 63 | it("should return 1 when there are no testcases files", () => { 64 | readdirSyncMock.mockReturnValue([]); 65 | expect(Tester.getNextTestCaseId(problemName)).toEqual(1); 66 | }); 67 | 68 | it("should return the next to the max id even when the sequence is not continuous", () => { 69 | readdirSyncMock.mockReturnValue([ 70 | `${problemName}.in1`, 71 | `${problemName}.ans1`, 72 | `${problemName}.in3`, 73 | `${problemName}.ans3`, 74 | `${problemName}.in6`, 75 | `${problemName}.ans6` 76 | ]); 77 | expect(Tester.getNextTestCaseId(problemFilePath)).toEqual(7); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /app/src/Types/ICommandGlobalArgs.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | export default interface ICommandGlobalArgs { 20 | configPath?: string; // path to configuration file 21 | } 22 | -------------------------------------------------------------------------------- /app/src/Types/ProblemData.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | import TestData from "./TestData"; 19 | 20 | export default interface ProblemData { 21 | name: string; 22 | group: string; 23 | url: string; 24 | interactive: boolean; 25 | memoryLimit: number; 26 | timeLimit: number; 27 | tests: TestData[]; 28 | testType: string; 29 | input: unknown; 30 | output: unknown; 31 | languages: unknown; 32 | } 33 | -------------------------------------------------------------------------------- /app/src/Types/TestData.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | export default interface TestData { 19 | input: string; 20 | output: string; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/Types/Veredict.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | export enum Veredict { 19 | AC, 20 | WA, 21 | TLE, 22 | RTE, 23 | CE, 24 | AC_WHEN_TRIMMED, 25 | ERROR, 26 | UNDETERMINED 27 | } 28 | -------------------------------------------------------------------------------- /app/src/Utils/LangExtensions.ts: -------------------------------------------------------------------------------- 1 | export enum LangExtensions { 2 | c = "c", 3 | cpp = "cpp", 4 | python = "py", 5 | javascript = "js", 6 | ruby = "rb", 7 | go = "go", 8 | rust = "rs", 9 | java = "java", 10 | csharp = "cs", 11 | kotlin = "kt", 12 | scala = "scala" 13 | } 14 | -------------------------------------------------------------------------------- /app/src/Utils/Util.ts: -------------------------------------------------------------------------------- 1 | /* 2 | cpbooster "Competitive Programming Booster" 3 | Copyright (C) 2020 Sergio G. Sanchez V. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | import { createInterface } from "readline"; 20 | import { once } from "events"; 21 | import * as os from "os"; 22 | import * as Path from "path"; 23 | import { LangExtensions } from "./LangExtensions"; 24 | import Config from "Config/Config"; 25 | 26 | export default class Util { 27 | static allowedSpecialChars = new Set(["_", "."]); 28 | 29 | static isAlpha(char: string): boolean { 30 | if (char.length != 1) { 31 | return false; 32 | } else { 33 | const thisCharCode = char.charCodeAt(0); 34 | if ( 35 | ("a".charCodeAt(0) <= thisCharCode && thisCharCode <= "z".charCodeAt(0)) || 36 | ("A".charCodeAt(0) <= thisCharCode && thisCharCode <= "Z".charCodeAt(0)) 37 | ) { 38 | return true; 39 | } else { 40 | return false; 41 | } 42 | } 43 | } 44 | 45 | static isNum(char: string): boolean { 46 | if (char.length != 1) { 47 | return false; 48 | } else { 49 | const thisCharCode = char.charCodeAt(0); 50 | if ("0".charCodeAt(0) <= thisCharCode && thisCharCode <= "9".charCodeAt(0)) { 51 | return true; 52 | } else { 53 | return false; 54 | } 55 | } 56 | } 57 | 58 | static isAlphaNum(char: string): boolean { 59 | if (char.length != 1) { 60 | return false; 61 | } else { 62 | if (this.isAlpha(char) || this.isNum(char)) { 63 | return true; 64 | } else { 65 | return false; 66 | } 67 | } 68 | } 69 | 70 | static replaceAll(text: string, oldString: string, newString: string): string { 71 | return text.split(oldString).join(newString); 72 | } 73 | 74 | static replaceExtraDotsWithUnderscore(fileName: string): string { 75 | const fileNameNoDots = Array.from(fileName); 76 | let extensionDotFound = false; 77 | for (let i = fileName.length - 1; i >= 0; i--) { 78 | if (!extensionDotFound && fileName[i] === ".") { 79 | extensionDotFound = true; 80 | continue; 81 | } 82 | 83 | if (fileName[i] === ".") { 84 | fileNameNoDots[i] = "_"; 85 | } 86 | } 87 | return fileNameNoDots.join(""); 88 | } 89 | 90 | static normalizeFileName(fileName: string): string { 91 | fileName = this.replaceExtraDotsWithUnderscore(fileName); 92 | let normalName = ""; 93 | for (const c of fileName) { 94 | if (this.isAlphaNum(c) || this.allowedSpecialChars.has(c)) { 95 | normalName += c; 96 | } 97 | } 98 | return normalName; 99 | } 100 | 101 | static normalizeFilePath(filePath: string): string { 102 | const dirPath = Path.dirname(filePath); 103 | return Path.join(dirPath, Util.normalizeFileName(Path.basename(filePath))); 104 | } 105 | 106 | static getCommentString(langExtension: string, config: Config): string { 107 | const langConfig = config.languages[langExtension]; 108 | if (langConfig?.commentString) { 109 | return langConfig.commentString; 110 | } 111 | langExtension = Util.replaceAll(langExtension, ".", "").toLowerCase(); 112 | const hashes = [LangExtensions.python.toString(), LangExtensions.ruby.toString()]; 113 | if (hashes.includes(langExtension)) { 114 | return "#"; 115 | } else { 116 | return "//"; 117 | } 118 | } 119 | 120 | static async readToEOF(): Promise { 121 | const rl = createInterface({ 122 | input: process.stdin, 123 | output: process.stdout 124 | }); 125 | let lines = ""; 126 | rl.on("line", (line: string) => { 127 | lines += line + "\n"; 128 | }); 129 | await once(rl, "close"); 130 | return lines; 131 | } 132 | 133 | static repeat(str: string, times: number): string { 134 | let ans = ""; 135 | for (let i = 0; i < times; i++) { 136 | ans += str; 137 | } 138 | return ans; 139 | } 140 | 141 | static padCenter(str: string, width: number): string { 142 | if (str.length >= width) { 143 | return str; 144 | } else { 145 | const remaningSpace = width - str.length; 146 | const spaceOnLeftSide = Math.floor(remaningSpace / 2); 147 | const spaceOnRightSide = remaningSpace - spaceOnLeftSide; 148 | const answer = this.repeat(" ", spaceOnLeftSide) + str + this.repeat(" ", spaceOnRightSide); 149 | return answer; 150 | } 151 | } 152 | 153 | /** 154 | * Creates a sequence of numbers from `start` to `end` 155 | * 156 | * @param {number} start The start of the sequence inclusively 157 | * @param {number} end The end of the sequence exclusively 158 | * @returns an array of size `end - start` containing the sequence of numbers in the range [start, end) 159 | */ 160 | static sequence(start: number, end: number): number[] { 161 | const n = Math.abs(end - start); 162 | const seq = new Array(n); 163 | const isDecreasing = start > end; 164 | for (let i = 0; i < n; i++, start += isDecreasing ? -1 : 1) { 165 | seq[i] = start; 166 | } 167 | return seq; 168 | } 169 | 170 | static getExtensionName(filePath: string): string { 171 | return Path.extname(filePath).substr(1).toLowerCase(); 172 | } 173 | 174 | static replaceTildeWithAbsoluteHomePath(contestsDirectory: string): string { 175 | return contestsDirectory.replace("~", os.homedir()); 176 | } 177 | 178 | static isWindows(): boolean { 179 | return os.type() === "Windows_NT" || os.release().includes("Microsoft"); 180 | } 181 | 182 | static getCodeforcesFormatedContestName(contestName: string): string { 183 | let formatedContestName = contestName; 184 | const prefix = "Codeforces"; 185 | while(formatedContestName.startsWith(prefix)) { 186 | formatedContestName = formatedContestName.substring(prefix.length); 187 | } 188 | let match = formatedContestName.match(/(EducationalCodeforcesRound)(\d+)(Ratedfor)(Div\.\d)/); 189 | if(match){ 190 | formatedContestName = `EducationalRound_${match[2]}_${match[4]}` 191 | } 192 | else{ 193 | let match = formatedContestName.match(/(\w*Round)(\d+)(Div\.\d)?/); 194 | if(match){ 195 | formatedContestName = `${match[1]}_${match[2]}${match[3]?'_'+match[3]:''}` 196 | } 197 | } 198 | return formatedContestName; 199 | } 200 | 201 | static splitOnlineJudgeName(contestName: string): Array { 202 | const onlineJudgeNames = [ 203 | "Codeforces", 204 | "CodeChef", 205 | "AtCoder", 206 | "HackerRank", 207 | "ProjectEuler", 208 | "TopCoder", 209 | "CSAcademy", 210 | "HackerEarth", 211 | "Kattis", 212 | "LeetCode", 213 | "SPOJ" 214 | ]; 215 | for (const onlineJudgeName of onlineJudgeNames) { 216 | if (contestName.startsWith(onlineJudgeName)) { 217 | if(onlineJudgeName === "Codeforces") { 218 | return [onlineJudgeName, this.getCodeforcesFormatedContestName(contestName)]; 219 | } 220 | return [onlineJudgeName, contestName.substring(onlineJudgeName.length)]; 221 | } 222 | } 223 | console.log("Online Judge not identified, saving in root directory"); 224 | return ["", contestName]; 225 | } 226 | 227 | static getContestPath(contestName: string, config: Config): string { 228 | if (config.groupContestsByJudge) { 229 | const [onlineJudgeName, splitContestName] = this.splitOnlineJudgeName(contestName); 230 | if (config.cloneInCurrentDir) { 231 | return Path.join(onlineJudgeName, splitContestName); 232 | } else { 233 | return Path.join(config.contestsDirectory, onlineJudgeName, splitContestName); 234 | } 235 | } else { 236 | if (config.cloneInCurrentDir) { 237 | return contestName; 238 | } 239 | return Path.join(config.contestsDirectory, contestName); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /app/src/Utils/__tests__/Util.test.ts: -------------------------------------------------------------------------------- 1 | import Util from "../Util"; 2 | 3 | describe("Util.test", () => { 4 | describe("sequence creation", () => { 5 | it("should return increasing sequence", () => { 6 | expect(Util.sequence(-10, -5)).toEqual([-10, -9, -8, -7, -6]); 7 | expect(Util.sequence(-5, 5)).toEqual([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]); 8 | expect(Util.sequence(0, 3)).toEqual([0, 1, 2]); 9 | }); 10 | 11 | it("should return decreasing sequence", () => { 12 | expect(Util.sequence(3, 0)).toEqual([3, 2, 1]); 13 | expect(Util.sequence(3, -3)).toEqual([3, 2, 1, 0, -1, -2]); 14 | expect(Util.sequence(-5, -10)).toEqual([-5, -6, -7, -8, -9]); 15 | }); 16 | 17 | it("should return empty array when start == end", () => { 18 | expect(Util.sequence(3, 3)).toEqual([]); 19 | expect(Util.sequence(0, 0)).toEqual([]); 20 | expect(Util.sequence(-2, -2)).toEqual([]); 21 | }); 22 | 23 | it("should return sequence of size one containing just `start` when end - start == 1", () => { 24 | expect(Util.sequence(3, 4)).toEqual([3]); 25 | expect(Util.sequence(-1, 0)).toEqual([-1]); 26 | expect(Util.sequence(-2, -1)).toEqual([-2]); 27 | }); 28 | 29 | it("should return sequence of size one containing just `start` when start - end == 1", () => { 30 | expect(Util.sequence(4, 3)).toEqual([4]); 31 | expect(Util.sequence(0, -1)).toEqual([0]); 32 | expect(Util.sequence(-1, -2)).toEqual([-1]); 33 | }); 34 | }); 35 | 36 | describe("replaceExtraDotsWithUnderscore()", () => { 37 | test("common scenario", () => { 38 | expect(Util.replaceExtraDotsWithUnderscore("A.DistanceAndAxis.cpp")).toEqual( 39 | "A_DistanceAndAxis.cpp" 40 | ); 41 | }); 42 | 43 | it("should keep the same name if no dots in it", () => { 44 | expect(Util.replaceExtraDotsWithUnderscore("A_DistanceAndAxis.cpp")).toEqual( 45 | "A_DistanceAndAxis.cpp" 46 | ); 47 | }); 48 | 49 | it("should replace all dots with underscore except extension", () => { 50 | expect(Util.replaceExtraDotsWithUnderscore("A.Distance.And.Axis.cpp")).toEqual( 51 | "A_Distance_And_Axis.cpp" 52 | ); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /app/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | cpbooster "Competitive Programming Booster" 5 | Copyright (C) 2020 Sergio G. Sanchez V. 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | import updateNotifier from "update-notifier"; 22 | import yargs from "yargs"; 23 | import * as os from "os"; 24 | import * as Path from "path"; 25 | import ICommandGlobalArgs from "./Types/ICommandGlobalArgs"; 26 | import { ICommandCloneArgs, clone } from "./Clone/Clone"; 27 | import { ICommandTestArgs, test } from "./Test/Test"; 28 | import { create, ICommandCreateArgs } from "./Create/Create"; 29 | import { init } from "./Init/Init"; 30 | import { ICommandLoginArgs, login } from "./Login/Login"; 31 | import { ICommandSubmitArgs, submit } from "./Submit/Submit"; 32 | import { ICommandStatArgs, stat } from "./Stat/Stat"; 33 | 34 | import * as pkg from "../package.json"; 35 | 36 | updateNotifier({ 37 | pkg: pkg, 38 | shouldNotifyInNpmScript: true, 39 | updateCheckInterval: 1000 * 60 * 60 * 2 // 2 hrs 40 | }).notify({ 41 | isGlobal: true, 42 | defer: false 43 | }); 44 | 45 | const descriptions = { 46 | clone: "Run cpbooster as server for competitive companion plugin.", 47 | test: "Test your code against one or all (default) available test cases.", 48 | create: 49 | "Create a new source code file with the corresponding template loaded or multiple source files if a sequence is given as file name.", 50 | init: "Create a new configuration file with default values in $HOME directory or if --configPath is specified, it writes it in the given path.", 51 | login: "Log in to the specified Online Judge (i.e. Codeforces, AtCoder, ...).", 52 | submit: 53 | "Submit a source code file as a solution to a problem in an Online Judge (i.e. Codeforces, AtCoder, ...).", 54 | stat: "Outputs useful information about the specified problem/file in a format that is easy to parse from other tools" 55 | }; 56 | 57 | yargs 58 | .usage( 59 | "\nUsage: $0 [options]\n\nRun `$0 --help` to show help for an specific command." 60 | ) 61 | .command( 62 | "clone", 63 | descriptions.clone, 64 | (serve_yargs) => { 65 | serve_yargs 66 | .usage("\n" + descriptions.clone + "\n\nUsage: $0 clone [options]") 67 | .option("port", { 68 | alias: "p", 69 | type: "number", 70 | description: "Port where competitive companion plugin will send parsed data from problems" 71 | }) 72 | .option("here", { 73 | type: "boolean", 74 | description: 75 | "Clones the contest or problem in current directory instead of your global contests directory" 76 | }); 77 | }, 78 | (argv) => clone(argv as unknown as ICommandCloneArgs) 79 | ) 80 | .command( 81 | ["test ", "t"], 82 | descriptions.test + " Run `cpb test --help` to see more usage options", 83 | (test_yargs) => { 84 | test_yargs 85 | .usage( 86 | "\n" + 87 | descriptions.test + 88 | " Refer to the options section to see all possible usages of this command." + 89 | "\n\nUsage: $0 test [options]" 90 | ) 91 | .option("debug", { 92 | alias: "d", 93 | type: "boolean", 94 | description: 95 | 'Runs program using the corresponding "Debug Command" specified in the configuration file.' + 96 | "\n* If --testId is specified, it will use the given test case as input" + 97 | "\n* Otherwise, it will read the input from keyboard" + 98 | "\n Note: If the language does not support debugging flags, this option will be ignored" 99 | }) 100 | .option("testId", { 101 | alias: "t", 102 | type: "number", 103 | description: "Specifies which testcase to use as input" 104 | }) 105 | .option("noCompile", { 106 | alias: "nc", 107 | type: "boolean", 108 | description: 109 | "Skip compilation of program (assumes there is a corresponding binary file already)." + 110 | "\n Note: If the language does not require compilation, this option will be ignored" 111 | }) 112 | .option("add", { 113 | alias: "a", 114 | type: "boolean", 115 | description: "Adds new test case for the given " 116 | }) 117 | .fail((msg: string, _, yargs) => { 118 | yargs.showHelp(); 119 | if (msg === "Not enough non-option arguments: got 0, need at least 1") { 120 | console.log("\nMissing in arguments"); 121 | } else { 122 | console.log("\n", msg); 123 | } 124 | }); 125 | }, 126 | (argv) => test(argv as unknown as ICommandTestArgs) 127 | ) 128 | .command( 129 | ["create ", "c"], 130 | descriptions.create + " Run `cpb create --help` to see usage options and examples", 131 | (create_yargs) => { 132 | create_yargs 133 | .usage( 134 | "\n" + 135 | descriptions.create + 136 | "\n\n* Usage 1: $0 create [options]\n\n" + 137 | " examples:\n" + 138 | " > $0 create sourcefile.cpp\n" + 139 | " > $0 create /home/cpbooster/sourcefile.py" + 140 | "\n\n* Usage 2: $0 create [DirectoryPath/]{from..to} [options]\n\n" + 141 | " examples:\n" + 142 | " > $0 create {a..d}.cpp (any amount of dots greater than 1 work)\n" + 143 | " > $0 create /home/cpbooster/{a..d}.cpp\n" + 144 | " > $0 create {a-d}.py (single dash also works)\n" 145 | ) 146 | .fail((msg: string, _, yargs) => { 147 | yargs.showHelp(); 148 | if (msg === "Not enough non-option arguments: got 0, need at least 1") { 149 | console.log("\nMissing in arguments"); 150 | } else { 151 | console.log("\n" + msg); 152 | } 153 | }); 154 | }, 155 | (argv) => create(argv as unknown as ICommandCreateArgs) 156 | ) 157 | .command( 158 | ["init", "i"], 159 | descriptions.init, 160 | (new_yargs) => { 161 | new_yargs 162 | .usage("\n" + descriptions.init + "\n\nUsage: $0 init [options]") 163 | .option("configPath", { 164 | type: "string", 165 | description: 166 | "Path where the JSON configuration file will be created" + 167 | `\n[default: "${Path.join(os.homedir(), "cpbooster-config.json")}"]` 168 | }); 169 | }, 170 | (argv) => init(argv as unknown as ICommandGlobalArgs) 171 | ) 172 | .command( 173 | ["login ", "l"], 174 | descriptions.login, 175 | (new_yargs) => { 176 | new_yargs 177 | .usage( 178 | "\n" + 179 | descriptions.login + 180 | " The name of the Online Judge can be given instead of ." + 181 | "\n\nUsage: $0 login " 182 | ) 183 | .fail((msg: string, _, yargs) => { 184 | yargs.showHelp(); 185 | if (msg === "Not enough non-option arguments: got 0, need at least 1") { 186 | console.log("\nMissing in arguments"); 187 | } else { 188 | console.log("\n" + msg); 189 | } 190 | }); 191 | }, 192 | (argv) => login(argv as unknown as ICommandLoginArgs) 193 | ) 194 | .command( 195 | ["submit [url]", "s"], 196 | descriptions.submit, 197 | (new_yargs) => { 198 | new_yargs 199 | .usage("\n" + descriptions.submit + "\n\nUsage: $0 submit [url]") 200 | .fail((msg: string, _, yargs) => { 201 | yargs.showHelp(); 202 | if (msg === "Not enough non-option arguments: got 0, need at least 1") { 203 | console.log("\nMissing in arguments"); 204 | } else { 205 | console.log("\n" + msg); 206 | } 207 | }); 208 | }, 209 | (argv) => submit(argv as unknown as ICommandSubmitArgs) 210 | ) 211 | .command( 212 | ["stat "], 213 | descriptions.stat, 214 | (new_yargs) => { 215 | new_yargs 216 | .usage("\n" + descriptions.stat + "\n\nUsage: $0 stat [options]") 217 | .option("nextTestCaseFilePaths", { 218 | type: "boolean", 219 | description: 220 | "Prints the paths of the next new testcase (.in and .ans files)" 221 | }) 222 | .fail((msg: string, _, yargs) => { 223 | yargs.showHelp(); 224 | if (msg === "Not enough non-option arguments: got 0, need at least 1") { 225 | console.log("\nMissing in arguments"); 226 | } else { 227 | console.log("\n" + msg); 228 | } 229 | }); 230 | }, 231 | (argv) => stat(argv as unknown as ICommandStatArgs) 232 | ) 233 | .help("help") 234 | .alias("help", "h") 235 | .alias("version", "v") 236 | .demandCommand() 237 | .strict() 238 | .option("configPath", { 239 | type: "string", 240 | description: 241 | "Path to JSON configuration file" + 242 | `\n[default: "${Path.join(os.homedir(), "cpbooster-config.json")}"]` 243 | }).argv; 244 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es6"], 6 | "allowJs": false, 7 | "outDir": "dist", 8 | "rootDir": ".", 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "esModuleInterop": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 17 | "baseUrl": "src", 18 | "paths": {} 19 | }, 20 | "exclude": ["website/*", "node_modules", "dist"], 21 | "allowSyntheticDefaultImports": true 22 | } 23 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpbooster", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/blog/2019-05-28-hola.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hola 3 | title: Hola 4 | author: Gao Wei 5 | author_title: Docusaurus Core Team 6 | author_url: https://github.com/wgao19 7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 8 | tags: [hola, docusaurus] 9 | --- 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 12 | -------------------------------------------------------------------------------- /website/blog/2019-05-29-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello 4 | author: Endilie Yacop Sucipto 5 | author_title: Maintainer of Docusaurus 6 | author_url: https://github.com/endiliey 7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 8 | tags: [hello, docusaurus] 9 | --- 10 | 11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). 12 | 13 | 14 | 15 | This is a test post. 16 | 17 | A whole bunch of other information. 18 | -------------------------------------------------------------------------------- /website/blog/2019-05-30-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | author: Yangshun Tay 5 | author_title: Front End Engineer @ Facebook 6 | author_url: https://github.com/yangshun 7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 8 | tags: [facebook, hello, docusaurus] 9 | --- 10 | 11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! 12 | 13 | Delete the whole directory if you don't want the blog features. As simple as that! 14 | -------------------------------------------------------------------------------- /website/docs/about.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: about 3 | title: What is cpbooster? 4 | --- 5 | 6 | `cpbooster` is a cross-platform **CLI** tool designed to **boost** competitive programmer's speed during contests by automating various routine tasks 7 | like compiling and testing, debugging, cloning testcases, loading template, etc. The console command suits any coding environment 8 | (i.e. _**VSCode, Jetbrains IDEs, Vim, NeoVim, Emacs, Geany, Sublime Text, ...**_) and it’s very easy to use. _Vim / NeoVim_ users can 9 | install [cpbooster.vim plugin](https://github.com/searleser97/cpbooster.vim) to **boost** their speed even more. 10 | -------------------------------------------------------------------------------- /website/docs/addEditorSupport.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: add-editor-support 3 | title: Add Editor Support 4 | --- 5 | 6 | `cpbooster` uses terminal commands to open editors, which can be either other terminals 7 | or common editors like _vscode_ or _sublime text_. You can find an example of the supported 8 | editors in the following file 9 | https://github.com/searleser97/cpbooster/blob/master/app/src/Clone/EditorCommandBuilder.ts 10 | 11 | 12 | 13 | To add another editor you need to investigate how to launch it specifying the working directory 14 | from a terminal command, then just add the proper command in the `switch` statement as shown in the image. 15 | -------------------------------------------------------------------------------- /website/docs/addLanguageSupport.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: add-language-support 3 | title: Add Language Support 4 | --- 5 | 6 | A good place to start looking at is here: https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/TesterFactory.ts 7 | 8 | where you will find that we have three main clases that represent `compiled`, `interpreted` and `mixed` languages. 9 | 10 | ### Interpreted 11 | 12 | Usually, all interpreted languages have the same syntax to run a file: 13 | 14 | ```shell 15 | interpreter filename.[extension] 16 | ``` 17 | 18 | e.g. 19 | 20 | ```shell 21 | $ python filename.py 22 | $ ruby filename.rb 23 | $ node filname.js 24 | ... 25 | ``` 26 | 27 | therefore, `cpbooster` should support absolutely all `interpreted` languages. 28 | 29 | The issue comes when we use either `compiled` or `interpreted` languages, specifically 30 | when it comes to indicate the name for the executable (binary) file. 31 | 32 | ### Compiled 33 | 34 | Compilers like `gcc`, `clang`, `rustc`, `go`, ... use the `-o` argument to indicate 35 | the name for the executable file. 36 | 37 | e.g. 38 | 39 | ```shell 40 | gcc filename.cpp -o myexecutable.exe 41 | go build filename.go -o myexecutable.exe 42 | rustc filename.rs -o myexecutable.exe 43 | ... 44 | ``` 45 | 46 | However, there might exist some other compilers that do not follow this convention and they 47 | probably use a different argument, to solve this we have the following function 48 | 49 | ```ts 50 | static getFileNameOptionForCompilerCommand(compilerCommand: string): string { 51 | return CompiledTester.fileNameOptionForCommand.get(compilerCommand) ?? "-o"; 52 | } 53 | ``` 54 | 55 | https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/CompiledTester.ts#L111-L113 56 | 57 | which uses a `Map` (hash table) 58 | to retrieve the correct argument name depending on the compiler command (`gcc`, `g++`, `csc`, `go`, ...), 59 | if the compiler does not exist in the hash table (`Map`) it defaults to the `-o` argument name. 60 | 61 | ```ts 62 | private static fileNameOptionForCommand: Map = new Map([ 63 | [NonStandardCompilers.mcs, "-out:"], // csharp compiler 64 | [NonStandardCompilers.csc, "/out:"] // csharp compiler 65 | ]); 66 | ``` 67 | 68 | https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/CompiledTester.ts#L37-L40 69 | 70 | notice that in the hash table (`Map`) we just have `msc` and `csc` which are `C#` compilers, this is because 71 | all the tested languages except for `C#` use the `-o` argument therefore there is no need to insert them into the hash table. 72 | 73 | Now let's actually talk about `C#` compilers and how they conflict with the standard format. It turns out that these compilers 74 | not just use a different argument name to sepecify the executable name, but they also force us to write the name of the executable 75 | together with the argument name (i.e. not separated by a space) 76 | 77 | e.g. 78 | 79 | ```shell 80 | mcs -out:myexecutable.exe filename.cs 81 | csc /out:myexecutable.exe filename.cs 82 | ``` 83 | 84 | to solve this when we compile we have an special condition, which basically checks if the compiler is NOT standard (i.e. `csc` and `mcs`) 85 | then join/concatenate the argument name with the executable name. 86 | 87 | ```ts 88 | if (Object.keys(NonStandardCompilers).includes(compilerCommand)) { 89 | args.push( 90 | CompiledTester.getFileNameOptionForCompilerCommand(compilerCommand) + 91 | this.getDefaultExecutableFileName(debug) 92 | ); 93 | } else { 94 | args.push( 95 | CompiledTester.getFileNameOptionForCompilerCommand(compilerCommand), 96 | this.getDefaultExecutableFileName(debug) 97 | ); 98 | } 99 | ``` 100 | 101 | https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/CompiledTester.ts#L144-L154 102 | 103 | ```ts 104 | export enum NonStandardCompilers { 105 | mcs = "mcs", 106 | csc = "csc", 107 | } 108 | ``` 109 | 110 | https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/CompiledTester.ts#L31-L34 111 | 112 | ### Mixed 113 | 114 | `mixed` languages are the ones that are compiled with one command but executed with another one (not counting `./executable.exe` format as command) 115 | e.g. programs written in `Java` are compiled using the `javac` command and then executed using the `java` command, something similar 116 | happens with languages like `Kotlin` and `Scala`. 117 | 118 | Usually, these languages generate a `.class` file after compilation, this `.class` file usually has the same name as the source file name or 119 | the same name as the `class` declared inside it. `cpbooster` takes advantage of this behavior and therefore, no extra code is written to name 120 | the executables (`.class` files), except for `kotlin` which adds `Kt` to the executable name, we have that case covered with a simple `if` 121 | condition here: 122 | 123 | ```ts 124 | getExecutableFileName(): string { 125 | return `${this.getExecutableFileNameNoExtension()}${ 126 | this.langExtension === LangExtensions.kotlin ? "Kt" : "" 127 | }.class`; 128 | } 129 | ``` 130 | 131 | https://github.com/searleser97/cpbooster/blob/33f274c030015bb555824d5ba7a4c1b11776257a/app/src/Test/TesterFactory/MixedTester.ts#L36 132 | 133 | :::note 134 | Currently, to guarantee the correct behavior of `cpbooster`, the `class` name should match with the source file name, since 135 | some languages use the `class` name as the executable (`.class`) name instead of the source file name. 136 | 137 | Which leads to a possible **contribution** related with the "templates" for languages that require the declaration of a class. 138 | We can add the concept of `cpbooster-variables` which will be formatted strings that will get replaced with some information, 139 | like filename or date, etc 140 | 141 | e.g. 142 | 143 | ###### `template.java` 144 | 145 | ```java 146 | /** 147 | * Date Time: $${DateTime}$$ 148 | */ 149 | 150 | import java.util.*; 151 | 152 | public class $${FileName}$$ <-- just a suggested syntax not sure if its the best 153 | { 154 | 155 | public static void main(String[] args) 156 | { 157 | 158 | } 159 | } 160 | ``` 161 | 162 | then if we create a file `ProblemC.java` with `cpbooster` the file will look like this: 163 | 164 | ```java 165 | /** 166 | * DateTime: 13-07-2022 13:30 <-- $${DateTime}$$ was replaced with the "current" date time 167 | */ 168 | 169 | import java.util.*; 170 | 171 | public class ProblemC <-- $${FileName}$$ was replaced with ProblemC 172 | { 173 | 174 | public static void main(String[] args) 175 | { 176 | 177 | } 178 | } 179 | ``` 180 | 181 | one thing to note here is that we will also need to force the file name to have the first letter capitalized (in uppercase), 182 | since some `mixed` languages do not allow to create classes that do not have the first letter in uppercase. 183 | ::: 184 | -------------------------------------------------------------------------------- /website/docs/addOnlineJudgeSupport.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: add-online-judge-support 3 | title: Add Online Judge Support 4 | --- 5 | 6 | Follow the changes explained in this [**Pull Request**](https://github.com/searleser97/cpbooster/pull/22/files) 7 | where we add the [**omegaUp**](https://omegaup.com/) judge. 8 | -------------------------------------------------------------------------------- /website/docs/addTestCase.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: add-test-case 3 | title: Add Test Case 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | You can add a test case associated with the give source file 9 | by passing the `--add` flag or its alias `-a` to the `test` command. 10 | See [Test](/docs/test). 11 | 12 | ```txt 13 | cpb t A.DistanceAndAxis.cpp -a 14 | ``` 15 | 16 | ## Demo 17 | 18 | 19 |
20 | test 21 |
22 | 23 | ## How Does It Work? 24 | 25 | This Command will create the corresponding `.inX` and `.ansX` files 26 | with the same name as the source file. 27 | This is because `cpbooster` uses filenames to associate test cases and 28 | source files. See [File Structure](/docs/clone#file-structure) for better understanding. 29 | 30 | :::note 31 | The id `X` will be equal to the maximum test case ID that already exists plus 1. 32 | ::: 33 | -------------------------------------------------------------------------------- /website/docs/cheatsheet.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: cheatsheet 3 | title: Cheatsheet 4 | --- 5 | 6 | 1. `cpbooster` comes with a short alias command called `cpb` to avoid writing the long command each time 7 | 1. Automatically clone sample testcases files with corresponding source code files with template loaded into a desired directory 8 | - `cpb clone` waits for competitive companion plugin to send parsed data for each problem 9 | 1. Create source files with corresponding template loaded 10 | - `cpb create a.py` creates single file with corresponding template loaded based on file extension 11 | - `cpb create {a..n}.cpp` creates multiple consecutive files from "a.cpp" to "n.cpp" 12 | - `cpb create {a...n}.cpp` same as previous command (Any amount of dots greater than 1 work) 13 | - `cpb create {a-n}.cpp` same as previous command (Single dash also works) 14 | - `cpb create /some/path/a.cpp` creates "a.cpp" in the specified path instead of current location 15 | - `cpb create /some/path/{a-n}.cpp` creates "a.cpp ... n.cpp" in the specified path instead of current location 16 | 1. Test your code against sample testcases quickly 17 | 18 | - `cpb test mycode.cpp` test your program against all available test cases 19 | - `cpb test mycode.cpp -t 1` test your program against the test case with the given id 20 | - `cpb test /some/path/mycode.cpp` test a program that is not located in your current location 21 | 22 | Supported results: 23 | 24 | - **AC** (Accepted) 25 | - **WA** (Wrong Answer) Shows differences between accepted output and your output beautifully 26 | - **TLE** (Time Limit Exceeded) 27 | - **RTE** (Runtime Error) 28 | - **CE** (Compilation Error) 29 | 30 | 1. Run code with your own debugging flags easily 31 | - `cpb test mycode.cpp -d` to use keyboard as input 32 | - `cpb test mycode.cpp -t 2 -d` to use a test case file as input 33 | - `cpb test /some/path/mycode.cpp -d` debug a program that is not located in your current location 34 | 1. Submit your code from the terminal really quickly. 35 | - `cpb submit mycode.cpp` submits your file to the corresponding judge. 36 | 1. open a new terminal in the contest directory immediately after cloning it 37 | 38 | - List of **supported terminals** for this feature: 39 | - konsole 40 | - xterm 41 | - gnome-terminal 42 | - deepin-terminal 43 | - terminal (MacOS) 44 | - kitty 45 | - vscode 46 | - I recommend adding this setting to your vscode `settings.json` so that green doesn't look to bright: 47 | `"workbench.colorCustomizations" : { "terminal.ansiGreen":"#5b8a3a" }` 48 | 49 | -------------------------------------------------------------------------------- /website/docs/clone.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: clone 3 | title: Clone Contest 4 | --- 5 | import useBaseUrl from '@docusaurus/useBaseUrl'; 6 | 7 | `cpbooster` can clone a contest to your computer with the following command: 8 | 9 | ```shell 10 | cpb clone 11 | ``` 12 | 13 | 14 | This command does 5 things: 15 | 16 | 1. Waits for [competitive companion extension](https://github.com/jmerle/competitive-companion) to send the data from the contest. 17 | 1. Creates a folder inside your [contests directory](/docs/configuration#contestsdirectory-string) with the same name as the contest that you are cloning. 18 | 1. Creates source files for all the problems in the contest. Using the name of the corresponding problem as file name. 19 | 1. Loads the template that corresponds to your [preferred language](/docs/configuration#preferredlang-string) to each source file. 20 | 1. Downloads test cases as `.in` and `.ans` files. Using the name of the corresponding problem as file name as well. 21 | 22 | 23 | 24 | ## Demo 25 |
26 | test 27 |
28 | 29 | ## File Structure 30 | 31 | Running `ls` in the contest directory will show you something like the following. 32 | 33 | ```shell 34 | Codeforces-CodeforcesRound665Div.2$ ls 35 | A_DistanceandAxis.ans1 C_MereArray.ans1 E_DivideSquare.cpp 36 | A_DistanceandAxis.cpp C_MereArray.cpp E_DivideSquare.in1 37 | A_DistanceandAxis.in1 C_MereArray.in1 F_ReverseandSwap.ans1 38 | A_DistanceandAxis.out1 D_MaximumDistributedTree.ans1 F_ReverseandSwap.ans2 39 | B_TernarySequence.ans1 D_MaximumDistributedTree.cpp F_ReverseandSwap.cpp 40 | B_TernarySequence.cpp D_MaximumDistributedTree.in1 F_ReverseandSwap.in1 41 | B_TernarySequence.in1 E_DivideSquare.ans1 F_ReverseandSwap.in2 42 | ``` 43 | 44 | The first thing to notice is that every file that corresponds to the `X` problem has the same name as `X`, 45 | except for the extension. This is how `cpbooster` identifies which testcases correspond to a certain source file. 46 | This means that you could create a new test case attached to the problem `A.DistanceandAxis` 47 | just by creating the corresponding `A.DistanceandAxis.in2` and `A.DistanceandAxis.ans2` 48 | files, however, `cpbooster` has a way to automate this task. See [Add Test Case](/docs/add-test-case). The same applies for executable files, 49 | they will use the same name except for the extension, which will be `.exe`. 50 | 51 | The second thing to notice is that there are **no subfolders**!, the file structure is **flat**!, which is just amazing for several 52 | reasons. See [Why Flat File Structure?](#why-flat-file-structure) to know more. 53 | 54 | ## Why Flat File Structure? 55 | 56 | There are several reasons why a flat file structure is preferred when it comes to competitive programming contests. 57 | 58 | But definitely the main reason, is because we want **speed!**, being fast in a contest is crucial. 59 | 60 | For example, having to change the directory like this 61 | 62 | ```shell 63 | $ cd .. 64 | $ cd ProblemB 65 | ``` 66 | each time you switch to another problem is just so annoying and slow. 67 | 68 | Having a flat file structure enables you to make every single file operation easier and faster, 69 | opening them, creating them, if you are a [vim](https://www.vim.org/about.php) or [neovim](https://neovim.io/) user you could just do 70 | 71 | ```shell 72 | $ vim *.cpp 73 | ``` 74 | to open all your source files. 75 | 76 | or let's say you want to see or modify the contents of some test case, you could just do `:e ProblemName.in1` 77 | to open the file, without changing the directory or using long relative paths. 78 | 79 | or imagine the situation where for some reason you just want to run your code with any of the available test cases manually 80 | (without using `cpbooster`), due to the fact that you will have a flat file structure you will be able to do it 81 | without changing the directory or using long relative paths. Just `./Program.exe < Program.in1` or `python Program.py < Program.in1`, etc. 82 | as usual. 83 | 84 | This simplicity will also allow you to use `cpbooster` with any source file you already have, not just for cloned contests. 85 | 86 | Also, do not forget that folders **do** use space, even when they are empty, Why would you like to use more space just for a 87 | competitive programming contest **???** 88 | 89 | #### So, leave folders and organization for more complex projects! Here you definitely DON'T NEED that, it makes you slow. 90 | 91 | -------------------------------------------------------------------------------- /website/docs/colors.js: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | green: "#44853a", 3 | red: "red", 4 | yellow: "#f67400", 5 | purple: "#9842f5", 6 | blue: "#0767ff" 7 | }; 8 | -------------------------------------------------------------------------------- /website/docs/create.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: create 3 | title: Create Files 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | `cpbooster` provides the `create` command or its alias `c`, to create 9 | one or several source files with your template preloaded in each 10 | of them. 11 | 12 | ```text 13 | cpb create 14 | ``` 15 | 16 | or to create multiple files (using alias `c` to shorten the length of the command) 17 | 18 | ```text 19 | cpb c [path/]{start-end}. 20 | ``` 21 | 22 | ## Demo 23 | 24 |
25 | test 26 |
27 | 28 | 29 | ## Usage Examples 30 | 31 | - `cpb create a.py` creates single file with the corresponding template loaded based on file extension 32 | - `cpb create {a..n}.cpp` creates multiple consecutive files from "a.cpp" to "n.cpp" 33 | - `cpb create {a...n}.cpp` same as previous command (Any amount of dots greater than 1 work) 34 | - `cpb create {a-n}.cpp` same as previous command (Single dash also works) 35 | - `cpb create /some/path/a.cpp` creates "a.cpp" in the specified path instead of current location 36 | - `cpb create /some/path/{a-n}.cpp` creates "a.cpp ... n.cpp" in the specified path instead of current location 37 | 38 | -------------------------------------------------------------------------------- /website/docs/debug.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: debug 3 | title: Debug 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | `cpbooster` provides a way for you to run your program using your debugging 9 | flags (`languages..debugCommand`). 10 | You can pass the `--debug` flag (or its alias which is just `-d`) to the `test` command for this. 11 | 12 | ```txt 13 | cpb t -d 14 | ``` 15 | 16 | By default, this command will request keyboard input, just like if you executed `./program.exe` or `python program.py`. 17 | If you wish to use a file as input See [Debug With Input File](#debug-with-input-file). 18 | 19 | #### Demo 20 | 21 |
22 | test 23 |
24 | 25 |
26 | 27 | :::note NOTES 28 | 1. This command will **automatically compile** your program everytime you run it. 29 | See [Debug Without Recompiling](#debug-without-recompiling) for details on how to avoid recompiling your program each time. 30 | 1. `cpbooster` does not print your debugging lines in red, this should be your code's behavior when running in debug mode. 31 | See [Debug Tips & Tricks](#debug-tips--tricks). 32 | ::: 33 | 34 | ### Executable File Details 35 | 36 | The executable file will be located in your current directory and its name will 37 | be the concatenation of the source file name and the word **"debug"** followed by the extension 38 | which will be `.exe`. For example, if your source file name is `ProblemA.cpp` then the executable file 39 | name will be `ProblemAdebug.exe`. This is because `cpbooster` uses filenames to associate everything 40 | (See [**File Structure**](/docs/clone#file-structure) for better understanding). 41 | If you wish to save the executable file with a different name, 42 | you can specify it in your `languages..debugCommand`. 43 | 44 | 45 | ## Debug With Input File 46 | 47 | You can specify a test case to run in debugging mode using the `--testId` flag or its alias `-t`. 48 | 49 | ```txt 50 | cpb t -t -d 51 | ``` 52 | 53 | :::note 54 | The only difference between the command used to [Test With A Single Test Case](/docs/test#test-with-a-single-test-case) and this one, is the 55 | `-d` flag at the end, which tells` cpbooster` to run in **debug** mode. 56 | ::: 57 | 58 | #### Demo 59 | 60 |
61 | test 62 |
63 | 64 | ## Debug Without Recompiling 65 | 66 | You can tell `cpbooster` to run in **debug** mode using the last compiled version of your program 67 | by passing the flag `--noCompile` or its alias `--nc`. 68 | 69 | ```txt 70 | cpb t -d --nc 71 | ``` 72 | 73 | :::caution 74 | By using this flag, `cpbooster` will assume that there is a corresponding executable file 75 | for your program. Remember that the name of the executable file must be the same as the 76 | source file plus the word **"debug"**, followed by the extension which must be `.exe`. Unless you specified otherwise 77 | in `languages..debugCommand`. 78 | ::: 79 | 80 | ## Debug Tips & Tricks 81 | 82 | ### Improve RTE Feedback In C++ 83 | 84 | You can use the following [debug command](/docs/configuration#languageslangdebugcommand-string) to get much better feedback in case of runtime errors. 85 | 86 | ##### `cpbooster-config.json` 87 | ```json 88 | { 89 | "debugCommand": "g++ -std=gnu++17 -O3 -DDEBUG -g -fsanitize=signed-integer-overflow -fsanitize=bounds-strict -fsanitize=address -fsanitize=integer-divide-by-zero -fsanitize=float-divide-by-zero -fsanitize=pointer-overflow -fsanitize=shift-exponent -fsplit-stack -Wshadow -Wall -fconcepts" 90 | } 91 | ``` 92 | 93 | ### Print Debug Lines In Red In C++ 94 | 95 | ##### `cpbooster-config.json` 96 | ```json 97 | { 98 | "debugCommand": "g++ -DDEBUG" 99 | } 100 | ``` 101 | 102 | ##### `program.cpp` 103 | ```cpp 104 | #include 105 | using namespace std; 106 | 107 | #define coutc cout << "\033[48;5;196m\033[38;5;15m" // red color code 108 | #define endlc "\033[0m" << endl; // this resets the coloring 109 | 110 | int main() { 111 | int a, b; 112 | cin >> a >> b; 113 | #ifdef DEBUG 114 | coutc << a << " " << b << endlc; 115 | #endif 116 | cout << a + b << endl; 117 | return 0; 118 | } 119 | ``` 120 | 121 | :::note 122 | Here we used the `-DDEBUG` flag in the debug command and therefore our `#ifdef` statement 123 | should check for the existence of `DEBUG`. If we had specified a different flag name, 124 | for example, `-DHOLA`, then the `#ifdef` statement should check the existence of `HOLA`. 125 | ::: 126 | 127 | :::tip 128 | You can avoid writing `#ifdef` every time if you use a well done template like [**THIS**](https://gitlab.com/searleser97/competitive-programming-reference/-/blob/master/Reference/Coding%20Resources/C++/Competitive%20Programming%20Template.cpp) one, 129 | which allows you to use a `debug(...)` function that can print **anything** that you pass to it. 130 | See [Print Anything Like In Python With C++](#print-anything-like-in-python-with-c). 131 | ::: 132 | 133 | ### Debug Command In Python 134 | 135 | In `Python` you can use the `-O` flag to print statements just when this flag is used. 136 | 137 | ##### `cpbooster-config.json` 138 | ```json 139 | { 140 | "debugCommand": "python3.9 -O" 141 | } 142 | ``` 143 | ##### `program.py` 144 | ```python 145 | if __debug__: 146 | print("This line will be printed just when -O flag is used") 147 | ``` 148 | 149 | ### Print Anything Like In Python With C++ 150 | 151 | Placing the following code on top of your source file, will enable you 152 | to use a `debug(...)` function, which will work almost like the `print(...)` function 153 | in python and it will just work if we pass the `-DDEBUG` flag 154 | to the compilation command. This function can receive any amount of parameters and 155 | they can be of almost any type, to be strict, it supports all primitive types 156 | (`bool, int, char, ...`), all iterable types (`vector, map, set, deque, ...`) and some 157 | other types like `pair` and `tuple`. 158 | 159 | 160 | ##### `cpbooster-config.json` 161 | ```json 162 | { 163 | "debugCommand": "g++ -std=gnu++17 -DDEBUG" 164 | } 165 | ``` 166 | 167 | ##### `program.cpp` 168 | ```cpp 169 | #include 170 | using namespace std; 171 | 172 | #ifdef DEBUG 173 | string to_string(char c) { return string({c}); } 174 | // 7 175 | template 176 | ostream& operator<<(ostream& o, tuple t) { 177 | string s = "("; 178 | apply([&](auto&&... r) { 179 | ((s += to_string(r) + ", "), ...); }, t); 180 | return o << s.substr(0, len(s) - 2) + ")"; 181 | } 182 | // 3 183 | ostream& operator<<(ostream &o, pair p) { 184 | return o << "(" << p.fi << ", " << p.se << ")"; 185 | } 186 | // 7 187 | template 189 | ::value>::type* = nullptr> 190 | ostream& operator<<(ostream &o, C c) { 191 | for (auto e : c) o << setw(7) << right << e; 192 | return o << endc << endl << coutc; 193 | } 194 | // 7 195 | void debug(const auto &e, const auto &... r) { 196 | cout << coutc << e; 197 | ((cout << " " << r), ..., (cout << endc << endl)); 198 | } 199 | #else 200 | #define debug(...) 201 | #endif 202 | ``` 203 | -------------------------------------------------------------------------------- /website/docs/fromv1.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: fromv1 3 | title: Coming From V1 4 | --- 5 | 6 | - `cpb serve` is now `cpb clone`. See [clone](/docs/clone). 7 | - `cpb new` is now `cpb init`. See [configuration](/docs/configuration). 8 | - There is a new [submit command](/docs/submit). 9 | - The configuration file had some changes, check them out [here](/docs/configuration). 10 | -------------------------------------------------------------------------------- /website/docs/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: installation 3 | title: Installation 4 | --- 5 | 6 | You can install `cpbooster` with a **single** comand, either using `npm` or `yarn`. 7 | 8 | ## Requirements 9 | 10 | - [nvm](/docs/nvm) (optional, but strongly recommended). 11 | - Node.js >= v12.18.2 12 | - npm 13 | - [Competitive Companion Extension (Chrome or Firefox)](https://github.com/jmerle/competitive-companion) 14 | 15 | ## Install using `npm` 16 | 17 | ```shell 18 | npm install cpbooster -g 19 | ``` 20 | 21 | **Note:** In some cases you may need to run above command with `sudo`. However, it is recommended to 22 | install `node` and `npm` with `nvm` to avoid this. 23 | 24 | ## Install using `yarn` 25 | 26 | ```shell 27 | yarn install cpbooster -g 28 | ``` 29 | 30 | ## Verify Installation 31 | 32 | ```shell 33 | cpbooster --help 34 | ``` 35 | 36 | You should see the description of each of the available commands. Read it to know what you can do with each of them. 37 | 38 | ```txt 39 | 40 | Usage: cpb [options] 41 | 42 | Run `cpb --help` to show help for an specific command. 43 | 44 | Commands: 45 | cpb clone Run cpbooster as server for competitive companion 46 | plugin. 47 | cpb test Test your code against one or all (default) available 48 | test cases. Run `cpb test --help` to see more usage 49 | options [aliases: t] 50 | cpb create Create a new source code file with the corresponding 51 | template loaded or multiple source files if a sequence 52 | is given as file name. Run `cpb create --help` to see 53 | usage options and examples [aliases: c] 54 | cpb init Create a new configuration file with default values in 55 | $HOME directory or if --configPath is specified, it 56 | writes it in the given path. [aliases: i] 57 | cpb login Log in to the specified Online Judge (i.e. Codeforces, 58 | AtCoder, ...). [aliases: l] 59 | cpb submit Submit a source code file as a solution to a problem in 60 | an Online Judge (i.e. Codeforces, AtCoder, ...). 61 | [aliases: s] 62 | cpb stat Outputs useful information about the specified 63 | problem/file in a format that is easy to parse from 64 | other tools 65 | 66 | Options: 67 | --version, -v Show version number [boolean] 68 | --help, -h Show help [boolean] 69 | --configPath Path to JSON configuration file 70 | [default: "/home/san/cpbooster-config.json"] [string] 71 | ``` 72 | 73 | ### The next step is to create your configuration file. Continue to the [next](/docs/configuration) section, to see how to do this. 74 | -------------------------------------------------------------------------------- /website/docs/nvm.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: nvm 3 | title: Install NVM 4 | --- 5 | 6 | ### About 7 | 8 | > nvm is a version manager for node.js, designed to be installed per-user, and invoked per-shell. 9 | > nvm works on any POSIX-compliant shell (sh, dash, ksh, zsh, bash), in particular on these platforms: 10 | > unix, macOS, and windows WSL. 11 | 12 | ### Installation 13 | 14 | Run any of the following commands in your terminal: 15 | 16 | ```shell 17 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash 18 | ``` 19 | 20 | ```shell 21 | wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash 22 | ``` 23 | 24 | Running either of the above commands downloads a script and runs it. The script clones the nvm repository to 25 | `~/.nvm`, and attempts to add the source lines from the snippet below to the correct profile file 26 | (`~/.bash_profile`, `~/.zshrc`, `~/.profile`, or `~/.bashrc`). 27 | 28 | For updated instructions visit:https://github.com/nvm-sh/nvm#installing-and-updating 29 | 30 | ### Verify Installation 31 | 32 | To verify that nvm has been installed, do: 33 | 34 | ```sh 35 | command -v nvm 36 | ``` 37 | 38 | which should output `nvm` if the installation was successful. 39 | Please note that `which nvm` will not work, since `nvm` is a 40 | sourced shell function, not an executable binary. 41 | 42 | ### Usage 43 | 44 | To download, compile, and install the latest release of node, do this: 45 | 46 | ```sh 47 | nvm install node # "node" is an alias for the latest version 48 | ``` 49 | 50 | And then in any new shell just use the installed version: 51 | 52 | ```sh 53 | nvm use node 54 | ``` 55 | -------------------------------------------------------------------------------- /website/docs/setupDevEnv.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: setup-dev-env 3 | title: Setup Dev Environment 4 | --- 5 | 6 | 1. Fork this repository and clone it locally: `git clone https://github.com/{yourUsername}/cpbooster` 7 | 2. `cd` (change directory) to `cpbooster/app` 8 | 3. Install dependencies: run `npm install` 9 | 4. Install `cpbooster` from source: `npm run install:dev` 10 | 5. Make code changes 11 | 12 | ### Before making a Pull Request 13 | 14 | 1. Lint your code and fix possible linting errors: `npm run lint` 15 | 2. Verify all tests pass: `npm t` 16 | 17 | ### Recommended VSCode extensions 18 | 19 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 20 | - [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 21 | -------------------------------------------------------------------------------- /website/docs/submit.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: submit 3 | title: Submit 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | `cpbooster` provides the following command to automatically 9 | **submit** your code to any supported Online Judge. See [Supported Online Judges](#supported-online-judges). 10 | 11 | ```txt 12 | cpb submit [url] 13 | ``` 14 | 15 | or its alias which is just `s`. 16 | 17 | ```txt 18 | cpb s [url] 19 | ``` 20 | 21 | #### Demo 22 | 23 |
24 | test 25 |
26 | 27 | ## Requirements 28 | 29 | - [Login](#login) with `cpb login ` 30 | - [Specify URL](#how-to-specify-the-url) in file or as argument. 31 | - [Configure Language Aliases](#how-to-configure-language-aliases) for each platform and language. 32 | 33 | :::note 34 | `cpbooster` uses a specific chromium binary that gets installed automatically in OS-specific cache 35 | folders: 36 | 37 | - `%USERPROFILE%\AppData\Local\ms-playwright` on Windows 38 | - `~/Library/Caches/ms-playwright` on MacOS 39 | - `~/.cache/ms-playwright` on Linux 40 | 41 | according to [playwright docs](https://playwright.dev/docs/installation/) 42 | ::: 43 | 44 | ## Login 45 | 46 | Before you can use the `submit` command you will need to login 47 | to the Online Judge where you will submit your code. 48 | 49 | ```txt 50 | cpb login 51 | ``` 52 | 53 | 54 | For example, to login to AtCoder I would execute the following command 55 | 56 | ```txt 57 | cpb login atcoder 58 | ``` 59 | 60 | The name can be written in upper or lowercase or mixed, it doesn't matter. 61 | 62 | :::note 63 | The `submit` command will automatically open the login page 64 | if it detects that you are not logged in, then it will continue 65 | to submit your code. So, the `submit` command will execute at most 2 66 | actions. 67 | ::: 68 | 69 | :::note information 70 | `cpbooster` will NOT send any account information 71 | to anyone else, all it does is, save your cookies 72 | and session information like any browser does locally (in 73 | `$HOME/.cpbooster/cpbooster-session.json`). 74 | ::: 75 | 76 | ## How To Specify The URL 77 | 78 | When you run `cpb submit` you can provide the `url` in **2** different ways: 79 | 80 | 81 | ### 1. As A Command Argument 82 | 83 | You can specify the `url` after the `` in the `submit` command. 84 | 85 | For example: 86 | 87 | ```txt 88 | cpb s A.DistanceAndAxis.cpp "https://codeforces.com/contest/1401/problem/A" 89 | ``` 90 | 91 | ### 2. As A Commented Line In The Source File 92 | 93 | Another way to specify the `url` is by writing the string `problem-url:` followed by 94 | the `url` as a commented line somewhere in your source file. In this way 95 | you will be able to submit your code without adding extra argument in the 96 | command, just like in the [demo](#demo). 97 | 98 | For example: 99 | 100 | ##### `A.DistanceAndAxis.cpp` 101 | 102 | ```cpp 103 | // problem-url: https://codeforces.com/contest/1401/problem/A 104 | int main() { 105 | ... 106 | } 107 | ``` 108 | :::important 109 | This line will be automatically added when you clone a contest with `cpb clone`. 110 | So, no need to do anything manually during a contest. 111 | ::: 112 | 113 | :::note 114 | If you specify the url in both ways (argument and commented line), the one 115 | specified as argument will be used. 116 | ::: 117 | 118 | ## How To Configure Language Aliases 119 | 120 | The language alias is the **ID** of the language for each Online Judge. 121 | See [How To Find Language IDs](#how-to-find-language-ids). For example, in Codeforces, 122 | the alias for `cpp` can be any of: 123 | 124 | |ID| Name| 125 | |--|-----| 126 | |42|GNU G++11 5.1.0| 127 | |50|GNU G++14 6.4.0| 128 | |54|GNU G++17 7.3.0| 129 | 130 | Let's say we want to submit our `cpp` file using G++17, then 131 | we should specify it 132 | in `languages.cpp.aliases.codeforces` 133 | like this: 134 | 135 | ```json 136 | { 137 | "languages": { 138 | "cpp": { 139 | "aliases": { 140 | "codeforces": "54" 141 | } 142 | } 143 | } 144 | ``` 145 | 146 | ### How To Find Language IDs 147 | 148 | To find language IDs follow the next steps: 149 | 150 | #### 1. Navigate to the online judge page where the language dropdown is located 151 | 152 | Some online judges use group of radio buttons or some other type of selectors for languages, 153 | like Uva Online Judge. 154 | 155 | For example, in Codeforces and AtCoder you could go to any problem's page like: 156 | 157 | - https://atcoder.jp/contests/abc196/tasks/abc196_a 158 |
159 | test 160 |
161 | - https://codeforces.com/contest/1401/problem/A 162 |
163 | test 164 |
165 | 166 | #### 2. Open the inspection tool 167 | 168 | Usually to open the inspection tool, you can `right click` in the page, 169 | and then click on `Inspect`. 170 | 171 |
172 | test 173 |
174 |
175 |
176 | test 177 |
178 | 179 | #### 3. Locate the `` tag to see 196 | the options. This is done by clicking on the small arrow to the left. 197 | 198 |
199 | test 200 |
201 | 202 | The content of the `value` property of each option is what `cpbooster` uses as **Alias**. 203 | So, now you can just copy and paste the value into your configuration file. 204 | 205 | For example, if you want to use `C++ (Clang 10.0.0)` for `cpp` files in AtCoder, you could write 206 | your configuration file as follows. 207 | 208 | ```json 209 | { 210 | "languages": { 211 | "cpp": { 212 | "aliases": { 213 | "atcoder": "4004" 214 | } 215 | } 216 | } 217 | } 218 | ``` 219 | 220 | ## Supported Online Judges 221 | 222 | - [Codeforces](https://codeforces.com/) 223 | - [AtCoder](https://atcoder.jp/) 224 | - [OmegaUp](https://omegaup.com/) 225 | 226 | :::note 227 | If you wish to add support for another Online Judge you can 228 | create a Pull Request in `cpbooster`'s github repository. 229 | See [Add Online Judge Support](/docs/add-online-judge-support) for guidance on how to do it. 230 | ::: 231 | -------------------------------------------------------------------------------- /website/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: test 3 | title: Test 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | import { colors } from './colors' 8 | 9 | `cpbooster` provides the following command for you to easily test your program against 10 | **all** available test cases. 11 | 12 | ```txt 13 | cpb test 14 | ``` 15 | 16 | or the alias for `test` which is just `t`. 17 | 18 | ```txt 19 | cpb t 20 | ``` 21 | 22 | This command will **automatically compile** your program everytime you run it. 23 | See [Test Without Recompiling](#test-without-recompiling) for details on how to avoid recompiling your program each time. 24 | 25 | ### Executable File Details 26 | 27 | The executable file will be located in your current directory and its name will 28 | match with the name of the source file except for the extension, which will be `.exe`. 29 | For example, if your source file name is `ProblemA.cpp` then the executable file 30 | name will be `ProblemA.exe`.This is because `cpbooster` uses filenames to associate 31 | everything (See [**File Structure**](/docs/clone#file-structure) for better understanding). 32 | If you wish to save the executable file with a different name, 33 | you can specify it in your `languages..command`. 34 | 35 | 36 | ## Test With A Single Test Case 37 | 38 | You can specify the test case, using the `--testId` flag or its alias `-t`. 39 | 40 | ```txt 41 | cpb t -t 42 | ``` 43 | 44 | :::important 45 | `` should match with the numeric suffix of the corresponding `.in` and `.ans` files 46 | See [**File Structure**](/docs/clone#file-structure) for better understanding. 47 | ::: 48 | 49 | ## Test Without Recompiling 50 | 51 | You can tell `cpbooster` to run the tests using the last compiled version of your program 52 | by passing the flag `--noCompile` or its alias `--nc`. 53 | 54 | ```txt 55 | cpb t --nc 56 | ``` 57 | 58 | :::caution 59 | By using this flag, `cpbooster` will assume that there is a corresponding executable file 60 | for your program. Remember that the name of the executable file must be the same as the 61 | source file, except for the extension which must be `.exe`. Unless you specified otherwise 62 | in `languages..command`. 63 | ::: 64 | 65 | 66 | ## Supported Veredicts 67 | 68 | -  AC  (Accepted Solution) 69 | -  WA  (Wrong Answer) 70 | -  CE  (Compilation Error) 71 | -  TLE  (Time Limit Exceeded) 72 | -  RTE  (Runtime Error) 73 | 74 | ## How does it work? 75 | 76 | This sections explains How each possible output of the `test` command work. 77 | 78 | ### Compilation Error 79 | 80 | If your code needs compilation (`cpp`, `java`, ...), this will be the first thing that `cpbooster` will 81 | try to do, unless the `--noCompile` flag was used. 82 | 83 | #### Demo 84 | 85 |
86 | test 87 |
88 | 89 | ### Runtime Error 90 | 91 | `cpbooster` detects that there was a runtime error if your program exited with a [status code](https://en.wikipedia.org/wiki/Exit_status) different from **0** 92 | or if there were no corresponding `.in` and `.ans` files (See [File Structure](/docs/clone#file-structure) for better understanding). 93 | 94 | #### Demo 95 | 96 |
97 | test 98 |
99 | 100 | ### Time Limit Exceeded 101 | 102 | To detect that the execution of a certain program has exceeded the time limit, `cpbooster` requires to know 103 | which is this time limit beforehand, the default value is **3000** milliseconds which equivalent to **3** seconds. 104 | To use a different value, the string `time-limit:` followed by the time limit in numeric format as **milliseconds** 105 | should be written somewhere in the source file as a commented line. 106 | 107 | #### Examples: 108 | 109 |
110 |
111 | 112 | 113 | 114 | 117 | 120 | 121 | 122 | 123 | 124 | 136 | 145 | 146 | 147 |
115 | C++ 116 | 118 | Python 119 |
125 | 126 | ```cpp 127 | // time-limit: 2000 128 | int main() { 129 | int a, b; 130 | cin >> a >> b; 131 | cout << a + b << endl; 132 | } 133 | ``` 134 | 135 | 137 | 138 | ```py 139 | # time-limit: 2000 140 | 141 | a, b = map(int, input().split()) 142 | print(a + b) 143 | ``` 144 |
148 |
149 |
150 | 151 | :::note 152 | This line will be automatically added at the top of your source file with the default value 153 | when running `cpb clone` or `cpb create`. 154 | ::: 155 | 156 | #### Demo 157 | 158 |
159 | test 160 |
161 | 162 | 163 | ### Wrong Answer 164 | 165 | Each time you run a test, `cpbooster` will create one or more `.outX` files (in the same directory as the 166 | source file) which will correspond to your program's output when using the correponding `.inX` file as 167 | input. 168 | Each `.outX` file will be compared against the corresponding `.ansX` file and if 169 | there are differences, they will be printed beautifully. 170 | 171 | 172 | :::important IMPORTANT CLARIFICATIONS 173 | 174 | 1. Each `.in`, `.ans` and `.out` file will and must have the same name as the source file associated with them. 175 | This is because `cpbooster` uses file names to make associations. See [**File Structure**](/docs/clone#file-structure). 176 | 177 | 1. `X` should be an integer number and every `.in`, `.ans` and `.out` file associated with a certain test case, 178 | must have the same numeric suffix `X`. 179 | ::: 180 | 181 | #### Demo 182 | 183 |
184 | test 185 |
186 | 187 | 188 | ### Accepted Solution 189 | 190 | If there were no errors or differences between the `.out` and `.ans` files, `cpbooster` will tell you that you have an 191 | **Accepted Solution**. 192 | 193 | :::tip 194 | `cpbooster` will also tell you if your solution has extra leading or trailing blank spaces. 195 |
196 | test 197 |
198 | ::: 199 | 200 | #### Demo 201 | 202 |
203 | test 204 |
205 | 206 | -------------------------------------------------------------------------------- /website/docs/vim.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: vim 3 | title: Vim / Neovim 4 | --- 5 | 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | `cpbooster` has a plugin for vim / neovim, to make you even faster, you can find it in [cpbooster.vim](https://github.com/searleser97/cpbooster.vim). 9 | 10 | ## Demo 11 | 12 |
13 | test 14 |
15 | 16 | ## Installation 17 | 18 | - Install [**cpbooster**](https://github.com/searleser97/cpbooster) 19 | 20 | ``` 21 | npm install cpbooster -g 22 | ``` 23 | 24 | - Install **cpbooster.vim** using your preferred plugin manager 25 | 26 | - Using [vim-plug](https://github.com/junegunn/vim-plug): 27 | 28 | ```vim 29 | Plug 'searleser97/cpbooster.vim' 30 | ``` 31 | 32 | ## Commands 33 | 34 | | Command | Description | 35 | | -------------------- | ------------------------------------------------------------------------------------------------------- | 36 | | `:Test [id]` | Test your current code against all available testcases for it,
or just one testcase if [id] is set. | 37 | | `:Debug [id]` | Run your current code with your debugging flags,
or run it against one testcase if [id] is set. | 38 | | `:Create ` | Create source file loading the corresponding
template for the file extension. | 39 | | `:Rtest [id]` | Test the last compiled version of your code.
Same as `:Test` but without recompiling. | 40 | | `:Rdebug [id]` | Debug the last compiled version of your code.
Sames as `:Debug` but without recompiling. | 41 | | `:Addtc` | Add new test case for your current code. | 42 | | `:Submit` | Submit your code to an Online Judge. (Codeforces, AtCoder, ...) | 43 | 44 | ## Future Updates 45 | 46 | - Add `:TestAs ` command 47 | - Add `:DebugAs ` command 48 | 49 | ## License 50 | 51 | `cpbooster.vim` is licensed under the [GNU General Public License v3.0](https://github.com/searleser97/cpbooster.vim/blob/master/LICENSE) 52 | 53 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 2 | module.exports = { 3 | title: "cpbooster", 4 | tagline: "Competitive Programming Booster", 5 | url: "https://searleser97.io", 6 | baseUrl: "/cpbooster/", 7 | onBrokenLinks: "throw", 8 | onBrokenMarkdownLinks: "warn", 9 | favicon: "img/favicon.ico", 10 | organizationName: "searleser97", // Usually your GitHub org/user name. 11 | projectName: "cpbooster", // Usually your repo name. 12 | themeConfig: { 13 | colorMode: { 14 | defaultMode: "dark" 15 | }, 16 | navbar: { 17 | title: "cpbooster", 18 | items: [ 19 | { 20 | type: "doc", 21 | position: "left", 22 | docId: "about", 23 | label: "Docs" 24 | }, 25 | { 26 | href: "https://github.com/searleser97/cpbooster", 27 | position: "right", 28 | className: "header-github-link" 29 | } 30 | ] 31 | }, 32 | footer: { 33 | style: "dark", 34 | copyright: `Copyright © ${new Date().getFullYear()} cpbooster. Built with Docusaurus.` 35 | } 36 | }, 37 | presets: [ 38 | [ 39 | "@docusaurus/preset-classic", 40 | { 41 | docs: { 42 | sidebarPath: require.resolve("./sidebars.js"), 43 | // Please change this to your repo. 44 | editUrl: "https://github.com/facebook/docusaurus/edit/master/website/" 45 | }, 46 | blog: { 47 | showReadingTime: true, 48 | // Please change this to your repo. 49 | editUrl: "https://github.com/facebook/docusaurus/edit/master/website/blog/" 50 | }, 51 | theme: { 52 | customCss: require.resolve("./src/css/custom.css") 53 | } 54 | } 55 | ] 56 | ], 57 | headTags: [ 58 | { 59 | tagName: "script", 60 | attributes: { 61 | async: "1", 62 | src: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5705416960449352", 63 | crossorigin: "anonymous" 64 | } 65 | }, 66 | { 67 | tagName: "meta", 68 | attributes: { 69 | name: "google-adsense-account", 70 | content: "ca-pub-5705416960449352" 71 | } 72 | } 73 | ] 74 | }; 75 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^3.7.0", 19 | "@docusaurus/preset-classic": "^3.7.0", 20 | "@mdx-js/react": "^3.0.0", 21 | "clsx": "^2.0.0", 22 | "prism-react-renderer": "^2.3.0", 23 | "react": "^19.0.0", 24 | "react-dom": "^19.0.0" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "^3.7.0", 28 | "@docusaurus/tsconfig": "^3.7.0", 29 | "@docusaurus/types": "^3.7.0", 30 | "typescript": "~5.6.2" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.5%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "engines": { 45 | "node": ">=18.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | docs: [ 3 | { 4 | type: "category", 5 | label: "Introduction", 6 | items: ["about", "cheatsheet"], 7 | collapsed: false 8 | }, 9 | { 10 | type: "category", 11 | label: "Getting Started", 12 | items: ["installation", "configuration"], 13 | collapsed: false 14 | }, 15 | { 16 | type: "category", 17 | label: "Usage", 18 | items: ["clone", "test", "debug", "submit", "add-test-case", "create"], 19 | collapsed: false 20 | }, 21 | { 22 | type: "category", 23 | label: "Contributing", 24 | items: ["setup-dev-env", "add-editor-support", "add-language-support", "add-online-judge-support"], 25 | collapsed: false 26 | }, 27 | { 28 | type: "category", 29 | label: "Extras", 30 | items: ["vim", "fromv1", "nvm"], 31 | collapsed: false 32 | } 33 | ] 34 | }; 35 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | /* :root { */ 10 | /* --ifm-color-primary: #25c2a0; */ 11 | /* --ifm-color-primary: #2b3137; */ 12 | /* --ifm-color-primary-dark: rgb(33, 175, 144); */ 13 | /* --ifm-color-primary-darker: rgb(31, 165, 136); */ 14 | /* --ifm-color-primary-darkest: rgb(26, 136, 112); */ 15 | /* --ifm-color-primary-light: rgb(70, 203, 174); */ 16 | /* --ifm-color-primary-lighter: rgb(102, 212, 189); */ 17 | /* --ifm-color-primary-lightest: rgb(146, 224, 208); */ 18 | /* --ifm-code-font-size: 95%; */ 19 | /* } */ 20 | 21 | :root { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: rgb(33, 175, 144); 24 | --ifm-color-primary-darker: rgb(31, 165, 136); 25 | --ifm-color-primary-darkest: rgb(26, 136, 112); 26 | --ifm-color-primary-light: rgb(70, 203, 174); 27 | --ifm-color-primary-lighter: rgb(102, 212, 189); 28 | --ifm-color-primary-lightest: rgb(146, 224, 208); 29 | --ifm-color-feedback-background: #fff; 30 | } 31 | 32 | .docusaurus-highlight-code-line { 33 | background-color: rgb(72, 77, 91); 34 | display: block; 35 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 36 | padding: 0 var(--ifm-pre-padding); 37 | } 38 | 39 | .hero { 40 | background-color: #2b3137; 41 | padding: 80px; 42 | color: #ffffff; 43 | } 44 | 45 | .admonition-tip { 46 | color: white; 47 | } 48 | 49 | .admonition-tip .admonition-icon svg { 50 | fill: white; 51 | stroke: white; 52 | } 53 | 54 | .admonition-tip a { 55 | color: white; 56 | } 57 | 58 | .header-github-link:hover { 59 | opacity: 0.6; 60 | } 61 | 62 | .header-github-link:before { 63 | content: ""; 64 | width: 24px; 65 | height: 24px; 66 | display: flex; 67 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 68 | no-repeat; 69 | } 70 | 71 | html[data-theme="dark"] .header-github-link:before { 72 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 73 | no-repeat; 74 | } 75 | -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import clsx from "clsx"; 3 | import Layout from "@theme/Layout"; 4 | import Link from "@docusaurus/Link"; 5 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 6 | import useBaseUrl from "@docusaurus/useBaseUrl"; 7 | import styles from "./styles.module.css"; 8 | 9 | const features = [ 10 | { 11 | title: "Clone Contests", 12 | imageUrl: "img/demos/clone.gif", 13 | description: ( 14 | <> 15 | Automatically clone sample testcases and create source files preloaded with your favorite 16 | template, 17 | 18 | ), 19 | to: "docs/clone" 20 | }, 21 | { 22 | title: "Test", 23 | imageUrl: "img/demos/test_wa.gif", 24 | description: ( 25 | <> 26 | Test your code against sample testcases quickly and with a pretty output. 27 |
28 | Supported Veredicts: AC, WA, TLE, RTE, CE 29 | 30 | ), 31 | to: "docs/test" 32 | }, 33 | { 34 | title: "Debug", 35 | imageUrl: "img/demos/debug_keyboard.gif", 36 | description: ( 37 | <> 38 | Run code with your own debugging flags easily. Input can be from the keyboard or from a test 39 | case file. 40 | 41 | ), 42 | to: "docs/debug" 43 | }, 44 | { 45 | title: "Add Test Cases", 46 | imageUrl: "img/demos/add-test-case.gif", 47 | description: ( 48 | <> 49 | Add a test case for the specified source code file. It will prompt you for the input and the 50 | expected output 51 | 52 | ), 53 | to: "docs/add-test-case" 54 | }, 55 | { 56 | title: "Submit", 57 | imageUrl: "img/demos/submit.gif", 58 | description: ( 59 | <>Submit your code to online judges pretty fast by just running a single command. 60 | ), 61 | to: "docs/submit" 62 | }, 63 | { 64 | title: "Create Files With Template", 65 | imageUrl: "img/demos/create.gif", 66 | description: <>Create source files with the corresponding template loaded, 67 | to: "docs/create" 68 | }, 69 | { 70 | title: "Flat File Structure", 71 | imageUrl: "img/demos/flat-file-structure.gif", 72 | description: ( 73 | <> 74 | Having a flat file structure gives us speed!. See{" "} 75 | Why Flat File Structure?. 76 | 77 | ), 78 | to: "docs/clone#file-structure" 79 | }, 80 | { 81 | title: "Vim / Neovim Plugin", 82 | imageUrl: "img/vim-icon.png", 83 | description: ( 84 | <> 85 | Vim / Neovim users can install{" "} 86 | cpbooster.vim plugin to boost 87 | their speed even more. 88 | 89 | ), 90 | to: "docs/vim", 91 | className: styles.vim_img 92 | } 93 | ]; 94 | 95 | function Feature({ imageUrl, title, description, to, className }) { 96 | const imgUrl = useBaseUrl(imageUrl); 97 | const toUrl = useBaseUrl(to); 98 | return ( 99 |
100 | {imgUrl && ( 101 |
102 | 103 | {title} 104 | 105 |
106 | )} 107 |

{title}

108 |

{description}

109 |
110 | ); 111 | } 112 | 113 | function useTitle(title) { 114 | useEffect(() => { 115 | const prevTitle = document.title 116 | document.title = title 117 | return () => { 118 | document.title = prevTitle 119 | } 120 | }) 121 | } 122 | 123 | export default function Home() { 124 | useTitle("cpbooster"); 125 | const context = useDocusaurusContext(); 126 | const { siteConfig = {} } = context; 127 | return ( 128 | 129 |
130 |
131 |
132 |

133 | cpbooster 134 |

135 |

136 | Competitive 137 |
138 | Programming 139 |
140 | Booster 141 |

142 |
143 | 144 |