├── .github └── workflows │ └── publish-to-pypi.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── rime └── rime_init ├── docs ├── Makefile ├── conf.py └── index.rst ├── example ├── PROJECT ├── a+b │ ├── PROBLEM │ ├── cpp-TLE │ │ ├── SOLUTION │ │ └── main.cc │ ├── cpp-WA-multiply │ │ ├── SOLUTION │ │ └── main.cc │ ├── cpp-correct │ │ ├── SOLUTION │ │ └── main.cc │ ├── go-WA │ │ ├── SOLUTION │ │ └── main.go │ ├── go-correct │ │ ├── SOLUTION │ │ └── main.go │ ├── kotlin-WA │ │ ├── SOLUTION │ │ └── main.kt │ ├── kotlin-correct │ │ ├── Main.kt │ │ └── SOLUTION │ ├── python-correct │ │ ├── SOLUTION │ │ └── main.py │ └── tests │ │ ├── 00-sample1.diff │ │ ├── 00-sample1.in │ │ ├── 00-sample2.in │ │ ├── 10-minimum.diff │ │ ├── 10-minimum.in │ │ ├── 11-maximum.diff │ │ ├── 11-maximum.in │ │ ├── TESTSET │ │ ├── generator.py │ │ └── validator.py ├── rime └── rime.py ├── rime.py ├── rime ├── __init__.py ├── basic │ ├── __init__.py │ ├── codes.py │ ├── commands.py │ ├── consts.py │ ├── targets │ │ ├── __init__.py │ │ ├── problem.py │ │ ├── project.py │ │ ├── solution.py │ │ └── testset.py │ ├── test.py │ └── util │ │ ├── __init__.py │ │ └── test_summary.py ├── core │ ├── __init__.py │ ├── codes.py │ ├── commands.py │ ├── hooks.py │ ├── main.py │ ├── targets.py │ ├── taskgraph.py │ └── ui.py ├── plugins │ ├── __init__.py │ ├── example.py │ ├── htmlify_full.py │ ├── judge_system │ │ ├── __init__.py │ │ ├── aoj.py │ │ ├── atcoder.py │ │ ├── domjudge.py │ │ └── hacker_rank.py │ ├── markdownify_full.py │ ├── merged_test.py │ ├── plus │ │ ├── __init__.py │ │ ├── basic_patch.py │ │ ├── commands.py │ │ ├── flexible_judge.py │ │ ├── merged_test.py │ │ └── subtask.py │ ├── rime_plus.py │ ├── summary │ │ ├── __init__.py │ │ ├── html.ninja │ │ ├── md.ninja │ │ ├── pukiwiki.ninja │ │ ├── pukiwiki_full.ninja │ │ └── summary.py │ ├── testlib_checker.py │ ├── wikify.py │ └── wikify_full.py └── util │ ├── __init__.py │ ├── class_registry.py │ ├── console.py │ ├── files.py │ ├── module_loader.py │ └── struct.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── core_tests ├── __init__.py └── test_main.py ├── plugins_merged_test_test.py ├── plugins_test ├── __init__.py ├── test_htmlify_full.py ├── test_markdownify_full.py ├── test_wikify.py └── test_wikify_full.py └── util_tests ├── __init__.py ├── test_class_registry.py ├── test_console.py ├── test_module_loader.py ├── test_package ├── __init__.py ├── test_module1.py └── test_subpackage │ ├── __init__.py │ └── test_module2.py └── test_struct.py /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python distributions to PyPI and TestPyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-n-publish: 9 | name: Build and publish Python distributions to PyPI and TestPyPI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Set up Python 3.8 14 | uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.8 17 | - name: Install pypa/build 18 | run: >- 19 | python -m 20 | pip install 21 | build 22 | --user 23 | - name: Build a binary wheel and a source tarball 24 | run: >- 25 | python -m 26 | build 27 | --sdist 28 | --wheel 29 | --outdir dist/ 30 | . 31 | - name: Publish distribution to Test PyPI 32 | uses: pypa/gh-action-pypi-publish@release/v1 33 | with: 34 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 35 | repository-url: https://test.pypi.org/legacy/ 36 | - name: Publish distribution to PyPI 37 | uses: pypa/gh-action-pypi-publish@release/v1 38 | with: 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | .\#* 4 | *.o 5 | *.pyc 6 | *.orig 7 | *.rej 8 | *.swp 9 | .DS_Store 10 | *.egg-info 11 | dist 12 | build 13 | rime-out 14 | .eggs 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: xenial 3 | python: 4 | - 2.7 5 | - 3.5 6 | - 3.6 7 | - 3.7 8 | - 3.8 9 | before_install: 10 | - curl -sL https://get.sdkman.io | bash 11 | - source "${HOME}/.sdkman/bin/sdkman-init.sh" 12 | install: 13 | - sdk install kotlin 14 | - pip install . 15 | - pip install flake8 hacking pytest mock 16 | script: 17 | - flake8 18 | - pytest 19 | - rime 20 | - rime help test 21 | - mkdir tmp && cd tmp && rime_init --git && cd .. 22 | - cd example && rime help 23 | - rime test && rime clean 24 | - rime test -j 2 && rime clean 25 | - rime htmlify_full && rime clean 26 | - rime markdownify_full && rime clean 27 | notifications: 28 | email: 29 | on_success: never 30 | on_failure: never 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011 Rime Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rime 2 | ==== 3 | 4 | ![status](https://travis-ci.org/icpc-jag/rime.svg?branch=master) 5 | 6 | Rime is a tool for programming contest organizers to automate usual, boring and error-prone process of problem set preparation. 7 | It supports various programming contest styles like ACM-ICPC, TopCoder, etc. by plugins. 8 | 9 | Detailed documentations (in Japanese) are found at documentation site: 10 | 11 | https://rime.readthedocs.io/ja/latest/ 12 | 13 | 14 | Cheat sheet 15 | ----------- 16 | 17 | #### Install Rime 18 | 19 | ``` 20 | $ pip install rime 21 | ``` 22 | 23 | #### Upgrade Rime 24 | 25 | ``` 26 | $ pip install -U rime 27 | ``` 28 | 29 | #### Uninstall Rime 30 | 31 | ``` 32 | $ pip uninstall rime 33 | ``` 34 | 35 | #### Initialize a project 36 | 37 | ``` 38 | $ rime_init --git/--mercurial 39 | ``` 40 | 41 | #### Add a problem 42 | 43 | ``` 44 | $ rime add . problem 45 | ``` 46 | 47 | #### Add a solution 48 | 49 | ``` 50 | $ rime add solution 51 | ``` 52 | 53 | #### Add a testset 54 | 55 | ``` 56 | $ rime add testset 57 | ``` 58 | 59 | #### Build a target (project/problem/solution/testset) 60 | 61 | ``` 62 | $ rime build -j <#workers> 63 | ``` 64 | 65 | #### Test a target (project/problem/solution/testset) 66 | 67 | ``` 68 | $ rime test -C -j <#workers> 69 | ``` 70 | 71 | #### Pack a target for an online judge (project/problem/testset) 72 | 73 | ``` 74 | $ rime pack 75 | ``` 76 | 77 | #### Upload a target to an online judge (project/problem/testset) 78 | 79 | ``` 80 | $ rime upload 81 | ``` 82 | 83 | #### Submit a target to an online judge (project/problem/solution) 84 | 85 | ``` 86 | $ rime submit 87 | ``` 88 | 89 | #### Edit a configuration file (project/problem/solution/testset) 90 | 91 | ``` 92 | $ vi/emacs/nano / 93 | ``` 94 | 95 | 96 | New features from Rime Plus 97 | --------------------------- 98 | 99 | * -O2, -std=c++11 as a default 100 | * Faster parallel test 101 | * native testlib.h support 102 | * subtask / partial scoring 103 | * reactive checker (partially support) 104 | * gcj-styled merged test 105 | * additional commands 106 | * pip support 107 | * judge system deployment 108 | * test result cache 109 | * some bug fix 110 | * JS / CSharp / Haskell codes 111 | * etc. 112 | 113 | 114 | For developers 115 | -------------- 116 | 117 | #### How to run unit tests 118 | 119 | ``` 120 | $ python setup.py test 121 | ``` 122 | -------------------------------------------------------------------------------- /bin/rime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from rime.core import main 6 | 7 | if __name__ == '__main__': 8 | sys.exit(main.Main(sys.argv)) 9 | -------------------------------------------------------------------------------- /bin/rime_init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | Initialize a rime project. 5 | """ 6 | 7 | import os 8 | import os.path 9 | import sys 10 | 11 | from six.moves import urllib 12 | 13 | from rime.plugins.plus import rime_plus_version 14 | 15 | 16 | def make_file(path, content): 17 | with open(path, 'w') as f: 18 | f.write(content) 19 | 20 | 21 | if __name__ == '__main__': 22 | if os.path.exists('PROJECT'): 23 | print('This directory is already a rime project root.') 24 | sys.exit(1) 25 | 26 | if len(sys.argv) < 2: 27 | print('Usage: rime_init.py --git / rime_init.py --mercurial') 28 | sys.exit(1) 29 | 30 | if sys.argv[1] == '--git': 31 | isgit = True 32 | elif sys.argv[1] == '--mercurial': 33 | isgit = False 34 | else: 35 | print('Usage: rime_init.py --git / rime_init.py --mercurial') 36 | sys.exit(1) 37 | 38 | content = '''\ 39 | rime-out 40 | *.pyc 41 | Icon* 42 | *.swp 43 | .DS_Store 44 | Thumbs.db 45 | *~ 46 | *.out 47 | target 48 | ''' 49 | if isgit: 50 | make_file('.gitignore', content) 51 | else: 52 | make_file('.hgignore', 'syntax: glob\n' + content) 53 | 54 | if not os.path.exists('common'): 55 | os.makedirs('common') 56 | try: 57 | urllib.request.urlretrieve( 58 | 'https://raw.githubusercontent.com/' 59 | 'MikeMirzayanov/testlib/master/testlib.h', 60 | 'common/testlib.h') 61 | except Exception: 62 | os.remove('PROJECT') 63 | print('Some error while downloading testlib.h.') 64 | sys.exit(1) 65 | 66 | content = '''\ 67 | # -*- coding: utf-8; mode: python -*- 68 | 69 | ## You can load plugins here. 70 | use_plugin('rime_plus') 71 | use_plugin('judge_system.atcoder') 72 | #use_plugin('judge_system.aoj') 73 | #use_plugin('judge_system.hacker_rank') 74 | #use_plugin('wikify') 75 | #use_plugin('wikify_full') 76 | 77 | project(library_dir='common', required_rime_plus_version='{0}') 78 | 79 | #wikify_config( 80 | # url="http://example.com/pukiwiki/", 81 | # page="***", 82 | # encoding="utf-8", 83 | # auth_realm="***", 84 | # auth_username="***", 85 | # auth_password="***") 86 | 87 | atcoder_config( 88 | upload_script='***', 89 | contest_url='https://***.contest.atcoder.jp/', 90 | username='***', password='***', 91 | lang_ids={{'c': 13, 'cxx': 10, 'java': 3}} 92 | ) 93 | '''.format(rime_plus_version) 94 | make_file('PROJECT', content) 95 | 96 | if isgit: 97 | os.system('git init') 98 | os.system('git add PROJECT .gitignore common/testlib.h') 99 | os.system('git commit -m "Initial commit"') 100 | else: 101 | os.system('hg init') 102 | os.system('hg add PROJECT .hgignore common/testlib.h') 103 | os.system('hg commit -m "Initial commit"') 104 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/*/* 43 | -cd $(BUILDDIR)/html && git checkout HEAD . 44 | 45 | html: 46 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 47 | @echo 48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 49 | 50 | dirhtml: 51 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 52 | @echo 53 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 54 | 55 | singlehtml: 56 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 57 | @echo 58 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 59 | 60 | pickle: 61 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 62 | @echo 63 | @echo "Build finished; now you can process the pickle files." 64 | 65 | json: 66 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 67 | @echo 68 | @echo "Build finished; now you can process the JSON files." 69 | 70 | htmlhelp: 71 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 72 | @echo 73 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 74 | ".hhp project file in $(BUILDDIR)/htmlhelp." 75 | 76 | qthelp: 77 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 78 | @echo 79 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 80 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 81 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Rime.qhcp" 82 | @echo "To view the help file:" 83 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Rime.qhc" 84 | 85 | devhelp: 86 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 87 | @echo 88 | @echo "Build finished." 89 | @echo "To view the help file:" 90 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Rime" 91 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Rime" 92 | @echo "# devhelp" 93 | 94 | epub: 95 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 96 | @echo 97 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 98 | 99 | latex: 100 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 101 | @echo 102 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 103 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 104 | "(use \`make latexpdf' here to do that automatically)." 105 | 106 | latexpdf: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo "Running LaTeX files through pdflatex..." 109 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 110 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 111 | 112 | text: 113 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 114 | @echo 115 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 116 | 117 | man: 118 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 119 | @echo 120 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 121 | 122 | texinfo: 123 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 124 | @echo 125 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 126 | @echo "Run \`make' in that directory to run these through makeinfo" \ 127 | "(use \`make info' here to do that automatically)." 128 | 129 | info: 130 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 131 | @echo "Running Texinfo files through makeinfo..." 132 | make -C $(BUILDDIR)/texinfo info 133 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 134 | 135 | gettext: 136 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 137 | @echo 138 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 139 | 140 | changes: 141 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 142 | @echo 143 | @echo "The overview file is in $(BUILDDIR)/changes." 144 | 145 | linkcheck: 146 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 147 | @echo 148 | @echo "Link check complete; look for any errors in the above output " \ 149 | "or in $(BUILDDIR)/linkcheck/output.txt." 150 | 151 | doctest: 152 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 153 | @echo "Testing of doctests in the sources finished, look at the " \ 154 | "results in $(BUILDDIR)/doctest/output.txt." 155 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Rime documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Apr 14 15:12:48 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Rime' 44 | copyright = u'2011 Rime Project' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | html_sidebars = {'**': ['localtoc.html', 'relations.html']} 136 | 137 | # Additional templates that should be rendered to pages, maps page names to 138 | # template names. 139 | #html_additional_pages = {} 140 | 141 | # If false, no module index is generated. 142 | html_domain_indices = False 143 | 144 | # If false, no index is generated. 145 | html_use_index = False 146 | 147 | # If true, the index is split into individual pages for each letter. 148 | #html_split_index = False 149 | 150 | # If true, links to the reST sources are added to the pages. 151 | html_show_sourcelink = False 152 | 153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 154 | #html_show_sphinx = True 155 | 156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 157 | #html_show_copyright = True 158 | 159 | # If true, an OpenSearch description file will be output, and all pages will 160 | # contain a tag referring to it. The value of this option must be the 161 | # base URL from which the finished HTML is served. 162 | #html_use_opensearch = '' 163 | 164 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 165 | #html_file_suffix = None 166 | 167 | # Output file base name for HTML help builder. 168 | htmlhelp_basename = 'Rimedoc' 169 | 170 | 171 | # -- Options for LaTeX output -------------------------------------------------- 172 | 173 | latex_elements = { 174 | # The paper size ('letterpaper' or 'a4paper'). 175 | #'papersize': 'letterpaper', 176 | 177 | # The font size ('10pt', '11pt' or '12pt'). 178 | #'pointsize': '10pt', 179 | 180 | # Additional stuff for the LaTeX preamble. 181 | #'preamble': '', 182 | } 183 | 184 | # Grouping the document tree into LaTeX files. List of tuples 185 | # (source start file, target name, title, author, documentclass [howto/manual]). 186 | latex_documents = [ 187 | ('index', 'Rime.tex', u'Rime Documentation', 188 | u'Rime Project', 'manual'), 189 | ] 190 | 191 | # The name of an image file (relative to this directory) to place at the top of 192 | # the title page. 193 | #latex_logo = None 194 | 195 | # For "manual" documents, if this is true, then toplevel headings are parts, 196 | # not chapters. 197 | #latex_use_parts = False 198 | 199 | # If true, show page references after internal links. 200 | #latex_show_pagerefs = False 201 | 202 | # If true, show URL addresses after external links. 203 | #latex_show_urls = False 204 | 205 | # Documents to append as an appendix to all manuals. 206 | #latex_appendices = [] 207 | 208 | # If false, no module index is generated. 209 | #latex_domain_indices = True 210 | 211 | 212 | # -- Options for manual page output -------------------------------------------- 213 | 214 | # One entry per manual page. List of tuples 215 | # (source start file, name, description, authors, manual section). 216 | man_pages = [ 217 | ('index', 'rime', u'Rime Documentation', 218 | [u'Rime Project'], 1) 219 | ] 220 | 221 | # If true, show URL addresses after external links. 222 | #man_show_urls = False 223 | 224 | 225 | # -- Options for Texinfo output ------------------------------------------------ 226 | 227 | # Grouping the document tree into Texinfo files. List of tuples 228 | # (source start file, target name, title, author, 229 | # dir menu entry, description, category) 230 | texinfo_documents = [ 231 | ('index', 'Rime', u'Rime Documentation', 232 | u'Rime Project', 'Rime', 'One line description of project.', 233 | 'Miscellaneous'), 234 | ] 235 | 236 | # Documents to append as an appendix to all manuals. 237 | #texinfo_appendices = [] 238 | 239 | # If false, no module index is generated. 240 | #texinfo_domain_indices = True 241 | 242 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 243 | #texinfo_show_urls = 'footnote' 244 | -------------------------------------------------------------------------------- /example/PROJECT: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## You can load plugins here. 4 | use_plugin('wikify') 5 | use_plugin('htmlify_full') 6 | use_plugin('markdownify_full') 7 | -------------------------------------------------------------------------------- /example/a+b/PROBLEM: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Problem definition. 4 | ## Required fields are: 5 | ## title: The problem title shown in test summary. 6 | ## id: The problem ID (typically starts from A) used to order problems. 7 | ## time_limit: The time limit of this problem in seconds. 8 | problem(title = "A+B Problem", 9 | wiki_name = 'A+B', 10 | assignees = 'assignee', 11 | need_custom_judge = False, 12 | id = "A", 13 | time_limit = 1.0, 14 | ) 15 | -------------------------------------------------------------------------------- /example/a+b/cpp-TLE/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Solution definition. 4 | ## Choose and modify one of definitions below. 5 | ## Adding a parameter challenge_cases marks this solution as a wrong solution. 6 | #c_solution(src='main.c') 7 | cxx_solution(src='main.cc', challenge_cases=[]) 8 | #java_solution(src='Main.java', encoding='UTF-8', mainclass='Main') 9 | -------------------------------------------------------------------------------- /example/a+b/cpp-TLE/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() { 6 | int a, b; 7 | cin >> a >> b; 8 | int c = 0; 9 | for (int i = 0; i < a; ++i) { 10 | c++; 11 | } 12 | for (int i = 0; i < b; ++i) { 13 | c++; 14 | } 15 | cout << c << endl; 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /example/a+b/cpp-WA-multiply/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Solution definition. 4 | ## Choose and modify one of definitions below. 5 | ## Adding a parameter challenge_cases marks this solution as a wrong solution. 6 | #c_solution(src='main.c') 7 | cxx_solution(src='main.cc', challenge_cases=['00-sample1.in']) 8 | #java_solution(src='Main.java', encoding='UTF-8', mainclass='Main') 9 | -------------------------------------------------------------------------------- /example/a+b/cpp-WA-multiply/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() { 6 | int a, b; 7 | cin >> a >> b; 8 | cout << a * b << endl; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /example/a+b/cpp-correct/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Solution definition. 4 | ## Choose and modify one of definitions below. 5 | ## Adding a parameter challenge_cases marks this solution as a wrong solution. 6 | #c_solution(src='main.c') 7 | cxx_solution(src='main.cc') 8 | #java_solution(src='Main.java', encoding='UTF-8', mainclass='Main') 9 | #script_solution(src='main.py') 10 | -------------------------------------------------------------------------------- /example/a+b/cpp-correct/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() { 6 | int a, b; 7 | cin >> a >> b; 8 | cout << a + b << endl; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /example/a+b/go-WA/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | go_solution(src='main.go', challenge_cases=[]) 4 | -------------------------------------------------------------------------------- /example/a+b/go-WA/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a, b int 7 | fmt.Scan(&a, &b) 8 | fmt.Println(a - b) 9 | } 10 | -------------------------------------------------------------------------------- /example/a+b/go-correct/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | go_solution(src='main.go') 4 | -------------------------------------------------------------------------------- /example/a+b/go-correct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a, b int 7 | fmt.Scan(&a, &b) 8 | fmt.Println(a + b) 9 | } 10 | -------------------------------------------------------------------------------- /example/a+b/kotlin-WA/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | kotlin_solution(src='main.kt', challenge_cases=[]) 4 | -------------------------------------------------------------------------------- /example/a+b/kotlin-WA/main.kt: -------------------------------------------------------------------------------- 1 | import java.util.Scanner; 2 | 3 | fun main(args: Array) { 4 | val sc = Scanner(System.`in`) 5 | val a = sc.nextInt() 6 | val b = sc.nextInt() 7 | println(a - b) 8 | } 9 | -------------------------------------------------------------------------------- /example/a+b/kotlin-correct/Main.kt: -------------------------------------------------------------------------------- 1 | import java.util.Scanner; 2 | 3 | fun main(args: Array) { 4 | val sc = Scanner(System.`in`) 5 | val a = sc.nextInt() 6 | val b = sc.nextInt() 7 | println(a + b) 8 | } 9 | -------------------------------------------------------------------------------- /example/a+b/kotlin-correct/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | kotlin_solution(src='Main.kt') 4 | -------------------------------------------------------------------------------- /example/a+b/python-correct/SOLUTION: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Solution definition. 4 | ## Choose and modify one of definitions below. 5 | ## Adding a parameter challenge_cases marks this solution as a wrong solution. 6 | #c_solution(src='main.c') 7 | #cxx_solution(src='main.cc') 8 | #java_solution(src='Main.java', encoding='UTF-8', mainclass='Main') 9 | script_solution(src='main.py') 10 | -------------------------------------------------------------------------------- /example/a+b/python-correct/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | 6 | def main(): 7 | a, b = map(int, sys.stdin.read().strip().split()) 8 | print(a + b) 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /example/a+b/tests/00-sample1.diff: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /example/a+b/tests/00-sample1.in: -------------------------------------------------------------------------------- 1 | 3 4 2 | -------------------------------------------------------------------------------- /example/a+b/tests/00-sample2.in: -------------------------------------------------------------------------------- 1 | 243 28 2 | -------------------------------------------------------------------------------- /example/a+b/tests/10-minimum.diff: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /example/a+b/tests/10-minimum.in: -------------------------------------------------------------------------------- 1 | 0 0 2 | -------------------------------------------------------------------------------- /example/a+b/tests/11-maximum.diff: -------------------------------------------------------------------------------- 1 | 2000000000 2 | -------------------------------------------------------------------------------- /example/a+b/tests/11-maximum.in: -------------------------------------------------------------------------------- 1 | 1000000000 1000000000 2 | -------------------------------------------------------------------------------- /example/a+b/tests/TESTSET: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | 3 | ## Input generators. 4 | #c_generator(src='generator.c') 5 | #cxx_generator(src='generator.cc') 6 | #java_generator(src='Generator.java', encoding='UTF-8', mainclass='Generator') 7 | script_generator(src='generator.py') 8 | 9 | ## Input validators. 10 | #c_validator(src='validator.c') 11 | #cxx_validator(src='validator.cc') 12 | #java_validator(src='Validator.java', encoding='UTF-8', mainclass='Validator') 13 | script_validator(src='validator.py') 14 | 15 | ## Output judges. 16 | #c_judge(src='judge.c') 17 | #cxx_judge(src='judge.cc') 18 | #java_judge(src='Judge.java', encoding='UTF-8', mainclass='Judge') 19 | #script_judge(src='judge.py') 20 | -------------------------------------------------------------------------------- /example/a+b/tests/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import random 4 | 5 | import six 6 | 7 | MAX = 1000000000 8 | seq = 0 9 | 10 | 11 | def Generate(a, b): 12 | global seq 13 | filename = '50-random%02d.in' % seq 14 | with open(filename, 'w') as f: 15 | f.write('{} {}\n'.format(a, b)) 16 | seq += 1 17 | 18 | 19 | def main(): 20 | for _ in six.moves.range(20): 21 | Generate(random.randrange(0, MAX), random.randrange(0, MAX)) 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /example/a+b/tests/validator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import re 4 | import sys 5 | 6 | MAX = 1000000000 7 | 8 | 9 | def main(): 10 | m = re.match(r'^(\d+) (\d+)\n$', sys.stdin.read()) 11 | assert m, 'Does not match with regexp' 12 | a, b = map(int, m.groups()) 13 | assert 0 <= a <= MAX, 'a out of range: %d' % a 14 | assert 0 <= b <= MAX, 'a out of range: %d' % b 15 | 16 | 17 | if __name__ == '__main__': 18 | main() 19 | -------------------------------------------------------------------------------- /example/rime: -------------------------------------------------------------------------------- 1 | ../rime -------------------------------------------------------------------------------- /example/rime.py: -------------------------------------------------------------------------------- 1 | ../rime.py -------------------------------------------------------------------------------- /rime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from rime.core import main 6 | 7 | 8 | if __name__ == '__main__': 9 | sys.exit(main.Main(sys.argv)) 10 | -------------------------------------------------------------------------------- /rime/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Rime: a tool for programming contest organizers.""" 4 | -------------------------------------------------------------------------------- /rime/basic/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Provides basic modules of Rime. 4 | 5 | Basic package contains implementations of interfaces provided in core package, 6 | providing standard commands/targets like build/test of problem/solution etc. 7 | """ 8 | -------------------------------------------------------------------------------- /rime/basic/codes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import optparse 4 | import os 5 | import os.path 6 | import signal 7 | import subprocess 8 | 9 | from rime.basic import consts 10 | from rime.core import codes 11 | from rime.core import taskgraph 12 | from rime.util import files 13 | 14 | 15 | class CodeBase(codes.Code): 16 | """Base class of program codes with various common methods.""" 17 | 18 | def __init__(self, src_name, src_dir, out_dir, compile_args, run_args): 19 | super(CodeBase, self).__init__(src_name, src_dir, out_dir) 20 | self.log_name = os.path.splitext(src_name)[0] + consts.LOG_EXT 21 | self.compile_args = tuple(compile_args) 22 | self.run_args = tuple(run_args) 23 | 24 | @taskgraph.task_method 25 | def Compile(self): 26 | """Compile the code and return RunResult.""" 27 | try: 28 | if not self.compile_args: 29 | result = codes.RunResult(codes.RunResult.OK, None) 30 | else: 31 | result = yield self._ExecForCompile(args=self.compile_args) 32 | except Exception as e: 33 | result = codes.RunResult('On compiling: %s' % e, None) 34 | yield result 35 | 36 | @taskgraph.task_method 37 | def Run(self, args, cwd, input, output, timeout, precise, 38 | redirect_error=False, ok_returncode=0, ng_returncode=None): 39 | """Run the code and return RunResult.""" 40 | try: 41 | result = yield self._ExecForRun( 42 | args=tuple(list(self.run_args) + list(args)), cwd=cwd, 43 | input=input, output=output, timeout=timeout, precise=precise, 44 | redirect_error=redirect_error, 45 | ok_returncode=ok_returncode, ng_returncode=ng_returncode) 46 | except Exception as e: 47 | result = codes.RunResult('On execution: %s' % e, None) 48 | yield result 49 | 50 | @taskgraph.task_method 51 | def Clean(self): 52 | """Cleans the output directory. 53 | 54 | Returns an exception object on error. 55 | """ 56 | try: 57 | files.RemoveTree(self.out_dir) 58 | except Exception as e: 59 | yield e 60 | else: 61 | yield None 62 | 63 | def ReadCompileLog(self): 64 | return files.ReadFile(os.path.join(self.out_dir, self.log_name)) 65 | 66 | @taskgraph.task_method 67 | def _ExecForCompile(self, args): 68 | with open(os.path.join(self.out_dir, self.log_name), 'w') as outfile: 69 | yield (yield self._ExecInternal( 70 | args=args, cwd=self.src_dir, 71 | stdin=files.OpenNull(), stdout=outfile, 72 | stderr=subprocess.STDOUT)) 73 | 74 | @taskgraph.task_method 75 | def _ExecForRun(self, args, cwd, input, output, timeout, precise, 76 | redirect_error=False, ok_returncode=0, ng_returncode=None): 77 | with open(input, 'r') as infile: 78 | with open(output, 'w') as outfile: 79 | if redirect_error: 80 | errfile = subprocess.STDOUT 81 | else: 82 | errfile = files.OpenNull() 83 | yield (yield self._ExecInternal( 84 | args=args, cwd=cwd, 85 | stdin=infile, stdout=outfile, stderr=errfile, 86 | timeout=timeout, precise=precise, 87 | ok_returncode=ok_returncode, ng_returncode=ng_returncode)) 88 | 89 | @taskgraph.task_method 90 | def _ExecInternal(self, args, cwd, stdin, stdout, stderr, 91 | timeout=None, precise=False, ok_returncode=0, 92 | ng_returncode=None): 93 | task = taskgraph.ExternalProcessTask( 94 | args, cwd=cwd, stdin=stdin, stdout=stdout, stderr=stderr, 95 | timeout=timeout, exclusive=precise) 96 | proc = yield task 97 | code = proc.returncode 98 | # Retry if TLE. 99 | if not precise and code == -(signal.SIGXCPU): 100 | self._ResetIO(stdin, stdout, stderr) 101 | task = taskgraph.ExternalProcessTask( 102 | args, cwd=cwd, stdin=stdin, stdout=stdout, stderr=stderr, 103 | timeout=timeout, exclusive=True) 104 | proc = yield task 105 | code = proc.returncode 106 | if code == ok_returncode: 107 | status = codes.RunResult.OK 108 | elif code == -(signal.SIGXCPU): 109 | status = codes.RunResult.TLE 110 | elif code < 0: 111 | status = codes.RunResult.RE 112 | elif ng_returncode is not None: 113 | if code == ng_returncode: 114 | status = codes.RunResult.NG 115 | else: 116 | status = codes.RunResult.RE 117 | else: 118 | status = codes.RunResult.NG 119 | yield codes.RunResult(status, task.time) 120 | 121 | def _ResetIO(self, *args): 122 | for f in args: 123 | if f is None: 124 | continue 125 | try: 126 | f.seek(0) 127 | f.truncate() 128 | except IOError: 129 | pass 130 | 131 | 132 | class CCode(CodeBase): 133 | PREFIX = 'c' 134 | EXTENSIONS = ['c'] 135 | 136 | def __init__(self, src_name, src_dir, out_dir, flags=['-lm']): 137 | exe_name = os.path.splitext(src_name)[0] + consts.EXE_EXT 138 | cc = os.getenv('CC', 'gcc') 139 | super(CCode, self).__init__( 140 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 141 | compile_args=([cc, 142 | '-o', os.path.join(out_dir, exe_name), 143 | src_name] + list(flags)), 144 | run_args=[os.path.join(out_dir, exe_name)]) 145 | 146 | 147 | class CXXCode(CodeBase): 148 | PREFIX = 'cxx' 149 | EXTENSIONS = ['cc', 'cxx'] 150 | 151 | def __init__(self, src_name, src_dir, out_dir, flags=[]): 152 | exe_name = os.path.splitext(src_name)[0] + consts.EXE_EXT 153 | cxx = os.getenv('CXX', 'g++') 154 | super(CXXCode, self).__init__( 155 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 156 | compile_args=([cxx, 157 | '-o', os.path.join(out_dir, exe_name), 158 | src_name] + list(flags)), 159 | run_args=[os.path.join(out_dir, exe_name)]) 160 | 161 | 162 | class KotlinCode(CodeBase): 163 | PREFIX = 'kotlin' 164 | EXTENSIONS = ['kt'] 165 | 166 | def __init__(self, src_name, src_dir, out_dir, 167 | compile_flags=[], run_flags=[]): 168 | kotlinc = 'kotlinc' 169 | kotlin = 'kotlin' 170 | mainclass = os.path.splitext(src_name)[0].capitalize() + 'Kt' 171 | super(KotlinCode, self).__init__( 172 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 173 | compile_args=([kotlinc, '-d', files.ConvPath(out_dir)] + 174 | compile_flags + [src_name]), 175 | run_args=([kotlin, '-Dline.separator=\n', 176 | '-cp', files.ConvPath(out_dir)] + 177 | run_flags + [mainclass])) 178 | 179 | 180 | class JavaCode(CodeBase): 181 | PREFIX = 'java' 182 | EXTENSIONS = ['java'] 183 | 184 | def __init__(self, src_name, src_dir, out_dir, 185 | compile_flags=[], run_flags=[], 186 | encoding='UTF-8', mainclass='Main'): 187 | java_home = os.getenv('JAVA_HOME') 188 | if java_home is not None: 189 | java = os.path.join(java_home, 'bin/java') 190 | javac = os.path.join(java_home, 'bin/javac') 191 | else: 192 | java = 'java' 193 | javac = 'javac' 194 | super(JavaCode, self).__init__( 195 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 196 | compile_args=([javac, '-encoding', encoding, 197 | '-d', files.ConvPath(out_dir)] + 198 | compile_flags + [src_name]), 199 | run_args=([java, '-Dline.separator=\n', 200 | '-cp', files.ConvPath(out_dir)] + 201 | run_flags + [mainclass])) 202 | 203 | 204 | class RustCode(CodeBase): 205 | PREFIX = 'rust' 206 | EXTENSIONS = ['rs'] 207 | 208 | def __init__(self, src_name, src_dir, out_dir, flags=[]): 209 | exe_name = os.path.splitext(src_name)[0] + consts.EXE_EXT 210 | rustc = 'rustc' 211 | super(RustCode, self).__init__( 212 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 213 | compile_args=([rustc, 214 | '-o', os.path.join(out_dir, exe_name), 215 | src_name] + list(flags)), 216 | run_args=[os.path.join(out_dir, exe_name)]) 217 | 218 | 219 | class GoCode(CodeBase): 220 | PREFIX = 'go' 221 | EXTENSIONS = ['go'] 222 | 223 | def __init__(self, src_name, src_dir, out_dir, flags=[]): 224 | exe_name = os.path.splitext(src_name)[0] + consts.EXE_EXT 225 | goc = 'go' 226 | super(GoCode, self).__init__( 227 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 228 | compile_args=([goc, 'build', 229 | '-o', os.path.join(out_dir, exe_name)] + 230 | flags + [src_name]), 231 | run_args=[os.path.join(out_dir, exe_name)]) 232 | 233 | 234 | class ScriptCode(CodeBase): 235 | QUIET_COMPILE = True 236 | PREFIX = 'script' 237 | EXTENSIONS = ['sh', 'pl', 'py', 'rb'] 238 | 239 | def __init__(self, src_name, src_dir, out_dir, run_flags=[]): 240 | super(ScriptCode, self).__init__( 241 | src_name=src_name, src_dir=src_dir, out_dir=out_dir, 242 | compile_args=[], 243 | run_args=['false', os.path.join(src_dir, src_name)] + run_flags) 244 | # Replace the executable with the shebang line 245 | run_args = list(self.run_args) 246 | try: 247 | run_args[0] = self._ReadAndParseShebangLine() 248 | except IOError: 249 | pass 250 | self.run_args = tuple(run_args) 251 | 252 | @taskgraph.task_method 253 | def Compile(self, *args, **kwargs): 254 | """Fail if the script is missing a shebang line.""" 255 | try: 256 | interpreter = self._ReadAndParseShebangLine() 257 | except IOError: 258 | yield codes.RunResult('File not found', None) 259 | if not interpreter: 260 | yield codes.RunResult('Script missing a shebang line', None) 261 | if not os.path.exists(interpreter): 262 | yield codes.RunResult('Interpreter not found: %s' % interpreter, 263 | None) 264 | yield (yield super(ScriptCode, self).Compile(*args, **kwargs)) 265 | 266 | def _ReadAndParseShebangLine(self): 267 | with open(os.path.join(self.src_dir, self.src_name)) as f: 268 | shebang_line = f.readline() 269 | if not shebang_line.startswith('#!'): 270 | return None 271 | return shebang_line[2:].strip() 272 | 273 | 274 | class InternalDiffCode(CodeBase): 275 | QUIET_COMPILE = True 276 | 277 | def __init__(self): 278 | super(InternalDiffCode, self).__init__( 279 | src_name='diff', 280 | src_dir='', 281 | out_dir='', 282 | compile_args=[], 283 | run_args=[]) 284 | 285 | @taskgraph.task_method 286 | def Run(self, args, cwd, input, output, timeout, precise, 287 | redirect_error=False): 288 | parser = optparse.OptionParser() 289 | parser.add_option('-i', '--infile', dest='infile') 290 | parser.add_option('-d', '--difffile', dest='difffile') 291 | parser.add_option('-o', '--outfile', dest='outfile') 292 | (options, pos_args) = parser.parse_args([''] + list(args)) 293 | run_args = ('diff', '-u', options.difffile, options.outfile) 294 | with open(input, 'r') as infile: 295 | with open(output, 'w') as outfile: 296 | if redirect_error: 297 | errfile = subprocess.STDOUT 298 | else: 299 | errfile = files.OpenNull() 300 | task = taskgraph.ExternalProcessTask( 301 | run_args, cwd=cwd, stdin=infile, stdout=outfile, 302 | stderr=errfile, timeout=timeout) 303 | try: 304 | proc = yield task 305 | except OSError: 306 | yield codes.RunResult(codes.RunResult.RE, None) 307 | ret = proc.returncode 308 | if ret == 0: 309 | yield codes.RunResult(codes.RunResult.OK, task.time) 310 | if ret > 0: 311 | yield codes.RunResult(codes.RunResult.NG, None) 312 | yield codes.RunResult(codes.RunResult.RE, None) 313 | 314 | @taskgraph.task_method 315 | def Clean(self): 316 | yield True 317 | 318 | 319 | codes.registry.Add(CCode) 320 | codes.registry.Add(CXXCode) 321 | codes.registry.Add(KotlinCode) 322 | codes.registry.Add(JavaCode) 323 | codes.registry.Add(RustCode) 324 | codes.registry.Add(GoCode) 325 | codes.registry.Add(ScriptCode) 326 | -------------------------------------------------------------------------------- /rime/basic/commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path 5 | 6 | from rime.basic import consts 7 | from rime.basic.targets import problem 8 | from rime.basic.targets import project 9 | from rime.basic.targets import solution 10 | from rime.basic.targets import testset 11 | from rime.basic.util import test_summary 12 | from rime.core import commands 13 | from rime.core import taskgraph 14 | 15 | 16 | # Register the root command and global options. 17 | class Default(commands.CommandBase): 18 | def __init__(self, parent): 19 | assert parent is None 20 | super(Default, self).__init__( 21 | None, None, '', consts.GLOBAL_HELP, parent) 22 | self.AddOptionEntry(commands.OptionEntry( 23 | 'h', 'help', 'help', bool, False, None, 24 | 'Show this help.')) 25 | self.AddOptionEntry(commands.OptionEntry( 26 | 'j', 'jobs', 'parallelism', int, 0, 'n', 27 | 'Run multiple jobs in parallel.')) 28 | self.AddOptionEntry(commands.OptionEntry( 29 | 'd', 'debug', 'debug', bool, False, None, 30 | 'Turn on debugging.')) 31 | self.AddOptionEntry(commands.OptionEntry( 32 | 'C', 'cache_tests', 'cache_tests', bool, False, None, 33 | 'Cache test results.')) 34 | self.AddOptionEntry(commands.OptionEntry( 35 | 'p', 'precise', 'precise', bool, False, None, 36 | 'Do not run timing tasks concurrently.')) 37 | self.AddOptionEntry(commands.OptionEntry( 38 | 'k', 'keep_going', 'keep_going', bool, False, None, 39 | 'Do not skip tests on failures.')) 40 | self.AddOptionEntry(commands.OptionEntry( 41 | 'q', 'quiet', 'quiet', bool, False, None, 42 | 'Skip unimportant message.')) 43 | 44 | 45 | def IsBasicTarget(obj): 46 | return isinstance(obj, (project.Project, 47 | problem.Problem, 48 | solution.Solution, 49 | testset.Testset)) 50 | 51 | 52 | def RunCommon(method_name, project, args, ui): 53 | if args: 54 | base_dir = os.path.abspath(args[0]) 55 | args = args[1:] 56 | else: 57 | base_dir = os.getcwd() 58 | 59 | obj = project.FindByBaseDir(base_dir) 60 | if not obj: 61 | ui.errors.Error(None, 62 | 'Target directory is missing or not managed by Rime.') 63 | return None 64 | 65 | if args: 66 | ui.errors.Error(None, 67 | 'Extra argument passed to %s command!' % method_name) 68 | return None 69 | 70 | if not IsBasicTarget(obj): 71 | ui.errors.Error( 72 | None, '%s is not supported for the specified target.' % 73 | method_name) 74 | return None 75 | 76 | return getattr(obj, method_name)(ui) 77 | 78 | 79 | class Build(commands.CommandBase): 80 | def __init__(self, parent): 81 | super(Build, self).__init__( 82 | 'build', 83 | '[]', 84 | 'Build a target and its dependencies.', 85 | consts.BUILD_HELP, 86 | parent) 87 | 88 | def Run(self, project, args, ui): 89 | return RunCommon('Build', project, args, ui) 90 | 91 | 92 | class Test(commands.CommandBase): 93 | def __init__(self, parent): 94 | super(Test, self).__init__( 95 | 'test', 96 | '[]', 97 | 'Run tests in a target.', 98 | consts.TEST_HELP, 99 | parent) 100 | 101 | def Run(self, project, args, ui): 102 | task = RunCommon('Test', project, args, ui) 103 | if not task: 104 | return task 105 | 106 | @taskgraph.task_method 107 | def TestWrapper(): 108 | results = yield task 109 | test_summary.PrintTestSummary(results, ui) 110 | yield results 111 | return TestWrapper() 112 | 113 | 114 | class Clean(commands.CommandBase): 115 | def __init__(self, parent): 116 | super(Clean, self).__init__( 117 | 'clean', 118 | '[]', 119 | 'Clean intermediate files.', 120 | consts.CLEAN_HELP, 121 | parent) 122 | 123 | def Run(self, project, args, ui): 124 | return RunCommon('Clean', project, args, ui) 125 | 126 | 127 | commands.registry.Add(Default) 128 | commands.registry.Add(Build) 129 | commands.registry.Add(Test) 130 | commands.registry.Add(Clean) 131 | -------------------------------------------------------------------------------- /rime/basic/consts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | RIMEROOT_FILE = 'RIMEROOT' 4 | PROBLEM_FILE = 'PROBLEM' 5 | SOLUTION_FILE = 'SOLUTION' 6 | TESTS_FILE = 'TESTS' 7 | 8 | STAMP_FILE = '.stamp' 9 | 10 | IN_EXT = '.in' 11 | DIFF_EXT = '.diff' 12 | OUT_EXT = '.out' 13 | EXE_EXT = '.exe' 14 | JUDGE_EXT = '.judge' 15 | CACHE_EXT = '.cache' 16 | LOG_EXT = '.log' 17 | VALIDATION_EXT = '.validation' 18 | 19 | RIME_OUT_DIR = 'rime-out' 20 | 21 | 22 | # Limit the width of help messages to 75 characters! 23 | 24 | GLOBAL_HELP = """\ 25 | Rime is a tool for programming contest organizers to automate usual, boring 26 | and error-prone process of problem set preparation. It supports various 27 | programming contest styles like ACM-ICPC, TopCoder, etc. by plugins. 28 | 29 | To see a brief description and available options of a command, try: 30 | 31 | rime.py help 32 | """ 33 | 34 | BUILD_HELP = """\ 35 | If the target is a project, Rime builds all problems recursively. 36 | 37 | If the target is a problem, Rime builds all solutions and a testset 38 | recursively. 39 | 40 | If the target is a solution, Rime compiles the solution program specified 41 | in SOLUTION file. 42 | 43 | If the target is a testset, Rime compiles all necessary programs including 44 | input generators, input validators, output judges, and a reference solution 45 | that is automatically selected or explicitly specified in PROBLEM file. 46 | Then it copies static input/output files (*.in, *.diff) into rime-out 47 | directory, runs input generators, runs input validators against all 48 | static/generated input files, and finally runs a reference solution over 49 | them to generate reference output files. 50 | 51 | can be omitted to imply the target in the current working 52 | directory. 53 | 54 | If -k (--keep_going) is set, build does not stop even if a compile error 55 | happens. 56 | 57 | -j (--jobs) can be used to make build faster to allow several processes to 58 | run in parallel. 59 | """ 60 | 61 | TEST_HELP = """\ 62 | If the target is a project, Rime runs tests of all problems recursively. 63 | 64 | If the target is a problem, Rime runs tests of all solutions recursively. 65 | 66 | If the target is a solution, Rime builds it and the testset of the problem, 67 | and then runs the solution against a series of tests. 68 | 69 | If the target is a testset, Rime runs tests of all solutions recursively. 70 | 71 | can be omitted to imply the target in the current working 72 | directory. 73 | 74 | If -k (--keep_going) is set, build does not stop even if a compile error 75 | or a test failure happens. 76 | 77 | -j (--jobs) can be used to make build and test faster to allow several 78 | processes to run in parallel. If a test failed by time limit exceed in 79 | parallelized tests, the same test is re-run after all other concurrent 80 | processes are finished to see if it really does not run in the specified 81 | time limit. You can always force this behavior not to run tests 82 | concurrently by -p (--precise). 83 | 84 | If -C (--cache_tests) is set, Rime skips unchanged tests which passed 85 | previously. 86 | """ 87 | 88 | CLEAN_HELP = """\ 89 | Deletes files under corresponding directory in rime-out. 90 | 91 | can be omitted to imply the target in the current working 92 | directory. 93 | """ 94 | -------------------------------------------------------------------------------- /rime/basic/targets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/rime/basic/targets/__init__.py -------------------------------------------------------------------------------- /rime/basic/targets/problem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import itertools 4 | import os.path 5 | 6 | from rime.basic import consts 7 | from rime.basic.targets import project 8 | from rime.core import targets 9 | from rime.core import taskgraph 10 | from rime.util import files 11 | 12 | 13 | class Problem(targets.TargetBase): 14 | """Problem target.""" 15 | 16 | CONFIG_FILENAME = 'PROBLEM' 17 | 18 | def __init__(self, name, base_dir, parent): 19 | assert isinstance(parent, project.Project) 20 | super(Problem, self).__init__(name, base_dir, parent) 21 | self.project = parent 22 | self.problem = self 23 | self.out_dir = os.path.join(self.problem.base_dir, consts.RIME_OUT_DIR) 24 | 25 | def PreLoad(self, ui): 26 | super(Problem, self).PreLoad(ui) 27 | self.problem_defined = False 28 | 29 | def _problem(time_limit, reference_solution=None, 30 | title=None, id=None, **kwargs): 31 | assert not self.problem_defined, 'Multiple problem definitions' 32 | self.problem_defined = True 33 | self.timeout = time_limit 34 | self.reference_solution = reference_solution 35 | self.title = title or self.name 36 | self.id = id 37 | for key in kwargs: 38 | ui.errors.Warning( 39 | self, 'Unknown parameter for problem(): %s' % key) 40 | self.exports['problem'] = _problem 41 | 42 | def PostLoad(self, ui): 43 | super(Problem, self).PostLoad(ui) 44 | assert self.problem_defined, 'No problem definition found' 45 | self._ChainLoad(ui) 46 | self._ParseSettings(ui) 47 | 48 | def _ChainLoad(self, ui): 49 | # Chain-load solutions. 50 | self.solutions = [] 51 | for name in sorted(files.ListDir(self.base_dir)): 52 | path = os.path.join(self.base_dir, name) 53 | if targets.registry.Solution.CanLoadFrom(path): 54 | solution = targets.registry.Solution(name, path, self) 55 | try: 56 | solution.Load(ui) 57 | self.solutions.append(solution) 58 | except targets.ConfigurationError: 59 | ui.errors.Exception(solution) 60 | # Chain-load testsets. 61 | self.testsets = [] 62 | for name in sorted(files.ListDir(self.base_dir)): 63 | path = os.path.join(self.base_dir, name) 64 | if targets.registry.Testset.CanLoadFrom(path): 65 | testset = targets.registry.Testset(name, path, self) 66 | try: 67 | testset.Load(ui) 68 | self.testsets.append(testset) 69 | except targets.ConfigurationError: 70 | ui.errors.Exception(testset) 71 | 72 | def _ParseSettings(self, ui): 73 | # Currently we only support one testset per problem. 74 | self.testset = None 75 | if len(self.testsets) >= 2: 76 | ui.errors.Error(self, 'Multiple testsets found') 77 | elif len(self.testsets) == 0: 78 | self.testset = targets.registry.Testset.CreateEmpty(self, ui) 79 | elif len(self.testsets) == 1: 80 | self.testset = self.testsets[0] 81 | 82 | if self.timeout is None: 83 | ui.errors.Error(self, 'Time limit is not specified') 84 | 85 | # Select a reference solution. 86 | if self.reference_solution is None: 87 | # If not explicitly specified, select one which is 88 | # not marked as incorrect. 89 | for solution in self.solutions: 90 | if solution.IsCorrect(): 91 | self.reference_solution = solution 92 | break 93 | 94 | else: 95 | # If explicitly specified, just use it. 96 | reference_solution_name = self.reference_solution 97 | self.reference_solution = None 98 | for solution in self.solutions: 99 | if solution.name == reference_solution_name: 100 | self.reference_solution = solution 101 | break 102 | if self.reference_solution is None: 103 | ui.errors.Error( 104 | self, 105 | ('Reference solution "%s" does not exist' % 106 | reference_solution_name)) 107 | 108 | def FindByBaseDir(self, base_dir): 109 | if self.base_dir == base_dir: 110 | return self 111 | for solution in self.solutions: 112 | obj = solution.FindByBaseDir(base_dir) 113 | if obj: 114 | return obj 115 | for testset in self.testsets: 116 | obj = testset.FindByBaseDir(base_dir) 117 | if obj: 118 | return obj 119 | return None 120 | 121 | @taskgraph.task_method 122 | def Build(self, ui): 123 | """Build all solutions and the testset.""" 124 | results = yield taskgraph.TaskBranch( 125 | [solution.Build(ui) for solution in self.solutions] + 126 | [self.testset.Build(ui)]) 127 | yield all(results) 128 | 129 | @taskgraph.task_method 130 | def Test(self, ui): 131 | """Run tests in the problem.""" 132 | results = yield taskgraph.TaskBranch( 133 | [testset.Test(ui) for testset in self.testsets]) 134 | yield list(itertools.chain(*results)) 135 | 136 | @taskgraph.task_method 137 | def TestSolution(self, solution, ui): 138 | """Run tests in the problem.""" 139 | results = yield taskgraph.TaskBranch( 140 | [testset.TestSolution(solution, ui) for testset in self.testsets]) 141 | yield list(itertools.chain(*results)) 142 | 143 | @taskgraph.task_method 144 | def Clean(self, ui): 145 | """Clean the problem.""" 146 | ui.console.PrintAction('CLEAN', self) 147 | success = True 148 | if success: 149 | try: 150 | files.RemoveTree(self.out_dir) 151 | except Exception: 152 | ui.errors.Exception(self) 153 | success = False 154 | yield success 155 | 156 | 157 | class ProblemComponentMixin(object): 158 | """Mix-in for components of a problem (solution, testset).""" 159 | 160 | def __init__(self): 161 | self.src_dir = self.base_dir 162 | assert self.src_dir.startswith(self.base_dir) 163 | rel_dir = self.src_dir[len(self.problem.base_dir) + 1:] 164 | self.out_dir = os.path.join(self.problem.out_dir, rel_dir) 165 | self.stamp_file = os.path.join(self.out_dir, consts.STAMP_FILE) 166 | 167 | def GetLastModified(self): 168 | """Get timestamp of this target.""" 169 | stamp = files.GetLastModifiedUnder(self.src_dir) 170 | return stamp 171 | 172 | def SetCacheStamp(self, ui): 173 | """Update the stamp file.""" 174 | try: 175 | files.CreateEmptyFile(self.stamp_file) 176 | return True 177 | except Exception: 178 | ui.errors.Exception(self) 179 | return False 180 | 181 | def GetCacheStamp(self): 182 | """Get timestamp of the stamp file. 183 | 184 | Returns datetime.datetime.min if not available. 185 | """ 186 | return files.GetModified(self.stamp_file) 187 | 188 | def IsBuildCached(self): 189 | """Check if cached build is not staled.""" 190 | src_mtime = self.GetLastModified() 191 | stamp_mtime = self.GetCacheStamp() 192 | return (src_mtime < stamp_mtime) 193 | 194 | 195 | targets.registry.Add(Problem) 196 | -------------------------------------------------------------------------------- /rime/basic/targets/project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import itertools 4 | import os.path 5 | 6 | from rime.core import targets 7 | from rime.core import taskgraph 8 | from rime.util import files 9 | 10 | 11 | class Project(targets.registry.Project): 12 | """Project target.""" 13 | 14 | CONFIG_FILENAME = 'PROJECT' 15 | 16 | def __init__(self, name, base_dir, parent): 17 | assert parent is None 18 | super(Project, self).__init__(name, base_dir, parent) 19 | self.project = self 20 | 21 | def PostLoad(self, ui): 22 | super(Project, self).PostLoad(ui) 23 | self._ChainLoad(ui) 24 | 25 | def _ChainLoad(self, ui): 26 | # Chain-load problems. 27 | self.problems = [] 28 | for name in files.ListDir(self.base_dir): 29 | path = os.path.join(self.base_dir, name) 30 | if targets.registry.Problem.CanLoadFrom(path): 31 | problem = targets.registry.Problem(name, path, self) 32 | try: 33 | problem.Load(ui) 34 | self.problems.append(problem) 35 | except targets.ConfigurationError: 36 | ui.errors.Exception(problem) 37 | self.problems.sort(key=lambda a: (a.id, a.name)) 38 | 39 | def FindByBaseDir(self, base_dir): 40 | if self.base_dir == base_dir: 41 | return self 42 | for problem in self.problems: 43 | obj = problem.FindByBaseDir(base_dir) 44 | if obj: 45 | return obj 46 | return None 47 | 48 | @taskgraph.task_method 49 | def Build(self, ui): 50 | """Build all problems.""" 51 | results = yield taskgraph.TaskBranch( 52 | [problem.Build(ui) for problem in self.problems]) 53 | yield all(results) 54 | 55 | @taskgraph.task_method 56 | def Test(self, ui): 57 | """Run tests in the project.""" 58 | results = yield taskgraph.TaskBranch( 59 | [problem.Test(ui) for problem in self.problems]) 60 | yield list(itertools.chain(*results)) 61 | 62 | @taskgraph.task_method 63 | def Clean(self, ui): 64 | """Clean the project.""" 65 | results = yield taskgraph.TaskBranch( 66 | [problem.Clean(ui) for problem in self.problems]) 67 | yield all(results) 68 | 69 | 70 | targets.registry.Override('Project', Project) 71 | -------------------------------------------------------------------------------- /rime/basic/targets/solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import itertools 4 | 5 | from rime.basic.targets import problem 6 | from rime.core import codes 7 | from rime.core import targets 8 | from rime.core import taskgraph 9 | from rime.util import files 10 | 11 | 12 | class Solution(targets.TargetBase, problem.ProblemComponentMixin): 13 | """Solution target.""" 14 | 15 | CONFIG_FILENAME = 'SOLUTION' 16 | 17 | def __init__(self, name, base_dir, parent): 18 | assert isinstance(parent, problem.Problem) 19 | super(Solution, self).__init__(name, base_dir, parent) 20 | self.project = parent.project 21 | self.problem = parent 22 | problem.ProblemComponentMixin.__init__(self) 23 | 24 | def PreLoad(self, ui): 25 | super(Solution, self).PreLoad(ui) 26 | self._codes = [] 27 | self.exports.update( 28 | codes.CreateDictionary('%s_solution', 29 | self._codes, 30 | src_dir=self.src_dir, 31 | out_dir=self.out_dir, 32 | wrapper=self._WrapSolution)) 33 | 34 | def _WrapSolution(self, code_class): 35 | def Wrapped(src_name, src_dir, out_dir, challenge_cases=None, 36 | *args, **kwargs): 37 | code = code_class(src_name, src_dir, out_dir, *args, **kwargs) 38 | self.challenge_cases = challenge_cases 39 | return code 40 | return Wrapped 41 | 42 | def PostLoad(self, ui): 43 | super(Solution, self).PostLoad(ui) 44 | if len(self._codes) == 0: 45 | self._CompatGuessSolution(ui) 46 | if len(self._codes) >= 2: 47 | raise targets.ConfigurationError('multiple solutions') 48 | if len(self._codes) == 0: 49 | raise targets.ConfigurationError('no solution definition found') 50 | self.code = self._codes[0] 51 | 52 | def _CompatGuessSolution(self, ui): 53 | wrapped_auto_code = self._WrapSolution(codes.AutoCode) 54 | for filename in files.ListDir(self.src_dir): 55 | try: 56 | code = wrapped_auto_code(filename, self.src_dir, self.out_dir) 57 | self._codes.append(code) 58 | except codes.UnknownCodeExtensionException: 59 | continue 60 | 61 | def IsCorrect(self): 62 | """Returns whether this is correct solution.""" 63 | return self.challenge_cases is None 64 | 65 | @taskgraph.task_method 66 | def Build(self, ui): 67 | """Build this solution.""" 68 | if self.IsBuildCached(): 69 | ui.console.PrintAction( 70 | 'COMPILE', self, 'up-to-date', progress=True) 71 | yield True 72 | files.MakeDir(self.out_dir) 73 | if not self.code.QUIET_COMPILE: 74 | ui.console.PrintAction('COMPILE', self) 75 | res = yield self.code.Compile() 76 | log = self.code.ReadCompileLog() 77 | if res.status != codes.RunResult.OK: 78 | ui.errors.Error(self, 'Compile Error (%s)' % res.status) 79 | ui.console.PrintLog(log) 80 | yield False 81 | if log: 82 | ui.console.Print('Compiler warnings found:') 83 | ui.console.PrintLog(log) 84 | if not self.SetCacheStamp(ui): 85 | yield False 86 | yield True 87 | 88 | @taskgraph.task_method 89 | def Run(self, args, cwd, input, output, timeout, precise): 90 | """Run this solution.""" 91 | yield (yield self.code.Run( 92 | args=args, cwd=cwd, input=input, output=output, 93 | timeout=timeout, precise=precise)) 94 | 95 | @taskgraph.task_method 96 | def Test(self, ui): 97 | """Run tests for the solution.""" 98 | results = yield taskgraph.TaskBranch( 99 | [testset.TestSolution(self, ui) for testset in 100 | self.problem.testsets]) 101 | yield list(itertools.chain(*results)) 102 | 103 | @taskgraph.task_method 104 | def Clean(self, ui): 105 | """Clean the solution.""" 106 | ui.console.PrintAction('CLEAN', self) 107 | e = yield self.code.Clean() 108 | if e: 109 | ui.errors.Exception(self, e) 110 | yield False 111 | yield True 112 | 113 | 114 | targets.registry.Add(Solution) 115 | -------------------------------------------------------------------------------- /rime/basic/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os.path 4 | 5 | from rime.basic import consts 6 | 7 | 8 | class TestVerdict(object): 9 | 10 | def __init__(self, msg): 11 | self.msg = msg 12 | 13 | def __str__(self): 14 | return self.msg 15 | 16 | 17 | class TestCase(object): 18 | 19 | def __init__(self, testset, infile, difffile=None): 20 | self.testset = testset 21 | self.infile = infile 22 | if difffile is None: 23 | self.difffile = os.path.splitext(infile)[0] + consts.DIFF_EXT 24 | else: 25 | self.difffile = difffile 26 | 27 | @property 28 | def timeout(self): 29 | return self.testset.problem.timeout 30 | 31 | 32 | class TestCaseResult(object): 33 | """Testcase result.""" 34 | 35 | NA = TestVerdict('-') 36 | AC = TestVerdict('Accepted') 37 | WA = TestVerdict('Wrong Answer') 38 | TLE = TestVerdict('Time Limit Exceeded') 39 | RE = TestVerdict('Runtime Error') 40 | ERR = TestVerdict('System Error') 41 | 42 | def __init__(self, solution=None, testcase=None, verdict=NA, time=None, cached=False): 43 | self.solution = solution 44 | self.testcase = testcase 45 | self.verdict = verdict 46 | self.time = time 47 | self.cached = cached 48 | 49 | 50 | class TestsetResult(object): 51 | """Testset result. 52 | 53 | This includes sub-results for each testcase. 54 | """ 55 | 56 | def __init__(self, testset, solution, testcases): 57 | """Construct with empty results.""" 58 | self.testset = testset 59 | self.problem = testset.problem 60 | self.solution = solution 61 | self.testcases = testcases 62 | self.results = dict( 63 | [(testcase, 64 | TestCaseResult(solution, testcase, TestCaseResult.NA, 65 | time=None, cached=False)) 66 | for testcase in testcases]) 67 | assert len(self.results) == len(testcases) 68 | self.finalized = False 69 | 70 | def IsFinalized(self): 71 | return self.finalized 72 | 73 | def Finalize(self, expected, detail, notable_testcase=None, 74 | allow_override=False): 75 | if self.finalized and not allow_override: 76 | return 77 | self.expected = expected 78 | self.detail = detail 79 | self.notable_testcase = notable_testcase 80 | self.finalized = True 81 | 82 | def IsCached(self): 83 | return any((c.cached for c in self.results.values())) 84 | 85 | def IsAccepted(self): 86 | return all((c.verdict == TestCaseResult.AC for c in 87 | self.results.values())) 88 | 89 | def IsTimingValid(self, ui): 90 | """Checks if timing stats are valid.""" 91 | return ((ui.options.precise or ui.options.parallelism <= 1) and 92 | self.results and 93 | all((c.verdict == TestCaseResult.AC 94 | for c in self.results.values()))) 95 | 96 | def GetTimeStats(self, ui): 97 | """Get time statistics.""" 98 | # TODO(nya): Concat support 99 | if not self.IsTimingValid(ui): 100 | return 'max *.**s, acc *.**s' 101 | return 'max %.2fs, acc %.2fs' % ( 102 | self.GetMaxTime(), self.GetTotalTime()) 103 | 104 | def GetMaxTime(self): 105 | """Get maximum time. 106 | 107 | All case should be accepted. 108 | """ 109 | return max([c.time for k, c in self.results.items()]) 110 | 111 | def GetTotalTime(self): 112 | """Get total time. 113 | 114 | All case should be accepted. 115 | """ 116 | return sum([c.time for k, c in self.results.items()]) 117 | -------------------------------------------------------------------------------- /rime/basic/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/rime/basic/util/__init__.py -------------------------------------------------------------------------------- /rime/basic/util/test_summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | def PrintTestSummary(results, ui): 5 | if len(results) == 0: 6 | return 7 | ui.console.Print() 8 | ui.console.Print(ui.console.BOLD, 'Test Summary:', ui.console.NORMAL) 9 | solution_name_width = max( 10 | map(lambda t: len(t.solution.name), results)) 11 | last_problem = None 12 | for result in sorted(results, key=KeyTestResultForListing): 13 | if last_problem is not result.problem: 14 | problem_row = [ 15 | ui.console.BOLD, 16 | ui.console.CYAN, 17 | result.problem.name, 18 | ui.console.NORMAL, 19 | ' ... %d solutions, %d tests' % 20 | (len(result.problem.solutions), 21 | len(result.problem.testset.ListTestCases()))] 22 | ui.console.Print(*problem_row) 23 | last_problem = result.problem 24 | status_row = [' '] 25 | status_row += [ 26 | result.solution.IsCorrect() and ui.console.GREEN or 27 | ui.console.YELLOW, 28 | result.solution.name.ljust(solution_name_width), 29 | ui.console.NORMAL, 30 | ' '] 31 | if result.expected: 32 | status_row += [ui.console.GREEN, ' OK ', ui.console.NORMAL] 33 | else: 34 | status_row += [ui.console.RED, 'FAIL', ui.console.NORMAL] 35 | status_row += [' ', result.detail] 36 | if result.IsCached(): 37 | status_row += [' ', '(cached)'] 38 | ui.console.Print(*status_row) 39 | if not (ui.options.precise or ui.options.parallelism <= 1): 40 | ui.console.Print() 41 | ui.console.Print('Note: Timings are not displayed when ' 42 | 'parallel testing is enabled.') 43 | ui.console.Print(' To show them, try -p (--precise).') 44 | 45 | 46 | def KeyTestResultForListing(a): 47 | """Key function of TestResult for display-ordering.""" 48 | return (a.problem.name, 49 | a.solution is not a.problem.reference_solution, 50 | not a.solution.IsCorrect(), 51 | a.solution.name) 52 | -------------------------------------------------------------------------------- /rime/core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Provides essential modules of Rime. 4 | 5 | Core package contains building-block modules of Rime as a parallelized 6 | task-execution framework. 7 | """ 8 | -------------------------------------------------------------------------------- /rime/core/codes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os.path 4 | 5 | from rime.util import class_registry 6 | 7 | 8 | class RunResult(object): 9 | """Result of a single run. 10 | 11 | Note that this is not judgement result but just execution status. 12 | """ 13 | 14 | OK = 'OK' 15 | NG = 'Exited Abnormally' 16 | RE = 'Runtime Error' 17 | TLE = 'Time Limit Exceeded' 18 | 19 | def __init__(self, status, time): 20 | self.status = status 21 | self.time = time 22 | 23 | 24 | class Code(object): 25 | """Interface of program codes. 26 | 27 | Supports operations such as compile, run, clean. 28 | """ 29 | 30 | # Set to True if the deriving class does not require compilation 31 | # (e.g. script language). 32 | QUIET_COMPILE = False 33 | 34 | # Prefix of exported directive. 35 | # (e.g. prefix "foo" generates foo_solution, foo_generator, etc.) 36 | PREFIX = None 37 | 38 | # Extensions of this type of source codes. Used to autodetect code types. 39 | EXTENSIONS = None 40 | 41 | def __init__(self, src_name, src_dir, out_dir): 42 | self.src_name = src_name 43 | self.src_dir = src_dir 44 | self.out_dir = out_dir 45 | 46 | def Compile(self): 47 | raise NotImplementedError() 48 | 49 | def Run(self, args, cwd, input, output, timeout, precise, 50 | redirect_error=False): 51 | raise NotImplementedError() 52 | 53 | def Clean(self): 54 | raise NotImplementedError() 55 | 56 | 57 | registry = class_registry.ClassRegistry(Code) 58 | 59 | 60 | def CreateDictionary(name_fmt, codeset, src_dir, out_dir, wrapper=None): 61 | """Creates a dictionary used for configs.""" 62 | exports = {} 63 | if wrapper is None: 64 | def wrapper(c): 65 | return c 66 | for code_class in registry.classes.values(): 67 | def Closure(code_class): 68 | def Registerer(src, *args, **kwargs): 69 | codeset.append( 70 | wrapper(code_class)(src_name=src, src_dir=src_dir, 71 | out_dir=out_dir, *args, **kwargs)) 72 | exports[name_fmt % code_class.PREFIX] = Registerer 73 | Closure(code_class) 74 | return exports 75 | 76 | 77 | class UnknownCodeExtensionException(Exception): 78 | pass 79 | 80 | 81 | class AutoCode(Code): 82 | """Code class with automatic code type detection.""" 83 | 84 | PREFIX = 'auto' 85 | EXTENSIONS = [] 86 | 87 | def __init__(self, src_name, src_dir, out_dir, *args, **kwargs): 88 | src_ext = os.path.splitext(src_name)[1][1:] 89 | for code_class in registry.classes.values(): 90 | if src_ext in code_class.EXTENSIONS: 91 | break 92 | else: 93 | code_class = None 94 | if not code_class: 95 | raise UnknownCodeExtensionException( 96 | 'Unknown code extension: %s' % src_ext) 97 | # Swap the class in runtime. 98 | self.__class__ = code_class 99 | self.__init__(src_name, src_dir, out_dir, *args, **kwargs) 100 | 101 | 102 | registry.Add(AutoCode) 103 | -------------------------------------------------------------------------------- /rime/core/commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from rime.util import class_registry 4 | from rime.util import struct 5 | 6 | 7 | class ParseError(Exception): 8 | pass 9 | 10 | 11 | class OptionEntry(object): 12 | def __init__(self, shortname, longname, varname, argtype, argdef, argname, 13 | description): 14 | assert argtype in (bool, int, str) 15 | assert isinstance(argdef, argtype) 16 | self.shortname = shortname 17 | self.longname = longname 18 | self.varname = varname 19 | self.argtype = argtype 20 | self.argdef = argdef 21 | self.argname = argname 22 | self.description = description 23 | 24 | def Match(self, name): 25 | return (name in (self.shortname, self.longname)) 26 | 27 | 28 | class Command(object): 29 | name = None 30 | description = None 31 | 32 | def __init__(self, parent): 33 | self.parent = parent 34 | 35 | def FindOptionEntry(self, name): 36 | raise NotImplementedError() 37 | 38 | def GetDefaultOptionDict(self): 39 | raise NotImplementedError() 40 | 41 | def PrintHelp(self, ui): 42 | raise NotImplementedError() 43 | 44 | def Run(self, project, args, ui): 45 | raise NotImplementedError() 46 | 47 | 48 | class CommandBase(Command): 49 | def __init__(self, name, args, oneline_summary, description, parent): 50 | super(CommandBase, self).__init__(parent) 51 | self.name = name 52 | self.args = args 53 | self.oneline_summary = oneline_summary 54 | self.description = description 55 | self.options = [] 56 | 57 | def AddOptionEntry(self, option): 58 | self.options.append(option) 59 | 60 | def FindOptionEntry(self, name): 61 | for option in self.options: 62 | if option.Match(name): 63 | return option 64 | if self.parent: 65 | return self.parent.FindOptionEntry(name) 66 | return None 67 | 68 | def GetDefaultOptionDict(self): 69 | if self.parent: 70 | options = self.parent.GetDefaultOptionDict() 71 | else: 72 | options = {} 73 | for option in self.options: 74 | assert option.varname not in options 75 | options[option.varname] = option.argdef 76 | return options 77 | 78 | def PrintHelp(self, ui): 79 | ui.console.Print('rime.py %s [...] %s' % 80 | (self.name or '', self.args or 81 | '[...]')) 82 | ui.console.Print() 83 | self._PrintCommandDescription(ui) 84 | if self.name: 85 | ui.console.Print('Options for "%s":' % self.name) 86 | ui.console.Print() 87 | self._PrintOptionDescription(ui) 88 | ui.console.Print('Global options:') 89 | ui.console.Print() 90 | ui.commands[None]._PrintOptionDescription(ui) 91 | 92 | def _PrintCommandDescription(self, ui): 93 | if self.oneline_summary: 94 | ui.console.Print(self.oneline_summary) 95 | ui.console.Print() 96 | if self.description: 97 | for line in self.description.splitlines(): 98 | ui.console.Print(' ' + line) 99 | ui.console.Print() 100 | 101 | if not self.name: 102 | rows = [] 103 | values = filter(lambda a: a.name, ui.commands.values()) 104 | for cmd in sorted(values, key=lambda a: a.name): 105 | rows.append((' %s ' % cmd.name, cmd.oneline_summary)) 106 | 107 | offset = max([len(left_col) for left_col, _ in rows]) 108 | 109 | ui.console.Print('Commands:') 110 | ui.console.Print() 111 | for left_col, right_col in rows: 112 | ui.console.Print(left_col.ljust(offset) + right_col) 113 | ui.console.Print() 114 | 115 | def _PrintOptionDescription(self, ui): 116 | rows = [] 117 | for option in sorted(self.options, key=lambda a: a.longname): 118 | longopt = '--%s' % option.longname 119 | if option.argname: 120 | longopt += ' <%s>' % option.argname 121 | if option.shortname: 122 | left_col_head = ' -%s, %s ' % (option.shortname, longopt) 123 | else: 124 | left_col_head = ' %s ' % longopt 125 | rows.append((left_col_head, option.description.splitlines())) 126 | if not rows: 127 | ui.console.Print(' No options.') 128 | else: 129 | offset = max([len(left_col) for left_col, _ in rows]) 130 | for left_col_head, right_col_lines in rows: 131 | for i, right_col_line in enumerate(right_col_lines): 132 | left_col_line = (i == 0 and left_col_head or '').ljust( 133 | offset) 134 | ui.console.Print(left_col_line + right_col_line) 135 | ui.console.Print() 136 | 137 | 138 | registry = class_registry.ClassRegistry(Command) 139 | 140 | 141 | def GetCommands(): 142 | commands = {} 143 | default = registry.Default(None) 144 | commands[None] = default 145 | for name, clazz in registry.classes.items(): 146 | if name == 'Default': 147 | continue 148 | cmd = clazz(default) 149 | commands[cmd.name] = cmd 150 | return commands 151 | 152 | 153 | def GetCommand(cmdname): 154 | return GetCommands()[cmdname] 155 | 156 | 157 | def Parse(argv, commands): 158 | """Parses the command line arguments. 159 | 160 | Arguments: 161 | argv: A list of string passed to the command. 162 | Note that this should include sys.argv[0] as well. 163 | 164 | Returns: 165 | A tuple of (cmd_name, extra_args, options) where: 166 | cmd: Command object of the main command specified by the command line. 167 | extra_args: A list of extra arguments given to the command. 168 | options: Struct containing option arguments. 169 | 170 | Raises: 171 | ParseError: When failed to parse arguments. 172 | """ 173 | default = commands[None] 174 | cmd = None 175 | extra_args = [] 176 | options = default.GetDefaultOptionDict() 177 | 178 | assert len(argv) >= 1 179 | i = 1 180 | option_finished = False 181 | 182 | while i < len(argv): 183 | arg = argv[i] 184 | i += 1 185 | 186 | if option_finished or not arg.startswith('-'): 187 | if cmd is None: 188 | arg = arg.lower() 189 | 190 | if arg not in commands: 191 | raise ParseError('Unknown command: %s' % arg) 192 | cmd = commands[arg] 193 | options.update(cmd.GetDefaultOptionDict()) 194 | 195 | else: 196 | extra_args.append(arg) 197 | 198 | else: 199 | longopt = arg.startswith('--') 200 | optvalue = None 201 | 202 | if longopt: 203 | optname = arg[2:] 204 | if optname == '': 205 | option_finished = True 206 | continue 207 | if '=' in optname: 208 | sep = optname.find('=') 209 | optvalue = optname[sep + 1:] 210 | optname = optname[:sep] 211 | optnames = [optname] 212 | 213 | else: 214 | optnames = arg[1:] 215 | 216 | for optname in optnames: 217 | optfull = '%s%s' % (longopt and '--' or '-', optname) 218 | 219 | option = (cmd and cmd.FindOptionEntry(optname) or 220 | default.FindOptionEntry(optname)) 221 | if option is None: 222 | raise ParseError('Unknown option: %s' % optfull) 223 | 224 | if option.argtype is bool: 225 | optvalue = True 226 | elif optvalue is None: 227 | if i == len(argv): 228 | raise ParseError( 229 | 'Option parameter was missing for %s' % optfull) 230 | optvalue = argv[i] 231 | i += 1 232 | 233 | try: 234 | optvalue = option.argtype(optvalue) 235 | except Exception: 236 | raise ParseError( 237 | 'Invalid option parameter for %s' % optfull) 238 | 239 | options[option.varname] = optvalue 240 | 241 | if cmd is None: 242 | cmd = commands[None] 243 | options['help'] = True 244 | 245 | return (cmd, extra_args, struct.Struct(options)) 246 | 247 | 248 | class Help(CommandBase): 249 | def __init__(self, parent): 250 | super(Help, self).__init__( 251 | 'help', 252 | '', 253 | 'Show help.', 254 | 'To see a brief description and available options of a command,' 255 | 'try:\n' 256 | '\n' 257 | 'rime.py help ', 258 | parent) 259 | 260 | def Run(self, project, args, ui): 261 | commands = GetCommands() 262 | cmd = None 263 | if len(args) > 0: 264 | cmd = commands.get(args[0]) 265 | if not cmd: 266 | cmd = self 267 | cmd.PrintHelp(ui) 268 | return None 269 | 270 | 271 | registry.Add(Help) 272 | -------------------------------------------------------------------------------- /rime/core/hooks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | class HookPoint(object): 5 | def __init__(self): 6 | self.hooks = [] 7 | 8 | def __call__(self, *args, **kwargs): 9 | for hook in self.hooks: 10 | hook(*args, **kwargs) 11 | 12 | def Register(self, hook): 13 | self.hooks.append(hook) 14 | return hook 15 | 16 | 17 | pre_command = HookPoint() 18 | post_command = HookPoint() 19 | -------------------------------------------------------------------------------- /rime/core/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path 5 | import platform 6 | import sys 7 | import traceback 8 | 9 | from rime.core import commands as commands_mod 10 | from rime.core import hooks 11 | from rime.core import targets 12 | from rime.core import taskgraph 13 | from rime.core import ui as ui_mod 14 | from rime.util import console as console_mod 15 | from rime.util import module_loader 16 | from rime.util import struct 17 | 18 | 19 | def LoadRequiredModules(): 20 | # TODO(nya): Fix this hacky implementation. 21 | module_loader.LoadPackage('rime.basic') 22 | while True: 23 | commands = commands_mod.GetCommands() 24 | default_options = struct.Struct(commands[None].GetDefaultOptionDict()) 25 | null_console = console_mod.NullConsole() 26 | graph = taskgraph.SerialTaskGraph() 27 | fake_ui = ui_mod.UiContext( 28 | default_options, null_console, commands, graph) 29 | try: 30 | LoadProject(os.getcwd(), fake_ui) 31 | break 32 | except targets.ConfigurationError: 33 | # Configuration errors should be processed later. 34 | break 35 | except targets.ReloadConfiguration: 36 | # Processed use_plugin(). Retry. 37 | pass 38 | 39 | 40 | def CheckSystem(ui): 41 | """Checks the sytem environment.""" 42 | system = platform.system() 43 | if system in ('Windows', 'Microsoft') or system.startswith('CYGWIN'): 44 | ui.console.Print('Note: Running Rime under Windows will be unstable.') 45 | return True 46 | 47 | 48 | def LoadProject(cwd, ui): 49 | """Loads configs and return Project instance. 50 | 51 | Location of root directory is searched upward from cwd. 52 | If PROJECT cannot be found, return None. 53 | """ 54 | path = cwd 55 | while not targets.registry.Project.CanLoadFrom(path): 56 | (head, tail) = os.path.split(path) 57 | if head == path: 58 | return None 59 | path = head 60 | project = targets.registry.Project(None, path, None) 61 | try: 62 | project.Load(ui) 63 | return project 64 | except targets.ConfigurationError: 65 | ui.errors.Exception(project) 66 | return None 67 | 68 | 69 | def CreateTaskGraph(options): 70 | """Creates the instance of TaskGraph to use for this session.""" 71 | if options.parallelism == 0: 72 | graph = taskgraph.SerialTaskGraph() 73 | else: 74 | graph = taskgraph.FiberTaskGraph( 75 | parallelism=options.parallelism, 76 | debug=options.debug) 77 | return graph 78 | 79 | 80 | def InternalMain(argv): 81 | """Main method called when invoked as stand-alone script.""" 82 | LoadRequiredModules() 83 | 84 | console = console_mod.TtyConsole(sys.stdout) 85 | 86 | commands = commands_mod.GetCommands() 87 | 88 | # Parse arguments. 89 | try: 90 | cmd, args, options = commands_mod.Parse(argv, commands) 91 | except commands_mod.ParseError as e: 92 | console.PrintError(str(e)) 93 | return 1 94 | 95 | if options.quiet: 96 | console.set_quiet() 97 | 98 | graph = CreateTaskGraph(options) 99 | 100 | ui = ui_mod.UiContext(options, console, commands, graph) 101 | 102 | if options.help: 103 | cmd.PrintHelp(ui) 104 | return 0 105 | 106 | # Check system. 107 | if not CheckSystem(ui): 108 | return 1 109 | 110 | # Try to load config files. 111 | project = LoadProject(os.getcwd(), ui) 112 | if ui.errors.HasError(): 113 | return 1 114 | if not project and not isinstance(cmd, commands_mod.Help): 115 | console.PrintError( 116 | 'PROJECT not found. Make sure you are in Rime subtree.') 117 | return 1 118 | 119 | # Run the task. 120 | task = None 121 | try: 122 | hooks.pre_command(ui) 123 | task = cmd.Run(project, tuple(args), ui) 124 | if task: 125 | graph.Run(task) 126 | hooks.post_command(ui) 127 | except KeyboardInterrupt: 128 | if ui.options.debug >= 1: 129 | traceback.print_exc() 130 | raise 131 | 132 | if task: 133 | console.Print() 134 | console.Print(console.BOLD, 'Error Summary:', console.NORMAL) 135 | ui.errors.PrintSummary() 136 | if ui.errors.HasError(): 137 | return 1 138 | return 0 139 | 140 | 141 | def Main(args): 142 | try: 143 | # Instanciate Rime class and call Main(). 144 | return InternalMain(args) 145 | except SystemExit: 146 | raise 147 | except KeyboardInterrupt: 148 | # Suppress stack trace when interrupted by Ctrl-C 149 | return 1 150 | except Exception: 151 | # Print stack trace for debug. 152 | exc = sys.exc_info() 153 | sys.excepthook(*exc) 154 | return 1 155 | -------------------------------------------------------------------------------- /rime/core/targets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path 5 | import sys 6 | 7 | from rime.util import class_registry 8 | from rime.util import files 9 | from rime.util import module_loader 10 | 11 | 12 | class TargetBase(object): 13 | """Base class of all target types.""" 14 | 15 | # Config filename of this target. Every subclass should override this. 16 | CONFIG_FILENAME = None 17 | 18 | def __init__(self, name, base_dir, parent): 19 | """Constructs a new unconfigured target.""" 20 | self.name = name 21 | self.base_dir = base_dir 22 | self.parent = parent 23 | 24 | # Set full name. 25 | # Full name is normally path-like string separated with "/". 26 | if name is None: 27 | self.name = '' 28 | self.fullname = None 29 | elif parent is None or parent.fullname is None: 30 | self.fullname = name 31 | else: 32 | self.fullname = parent.fullname + '/' + name 33 | 34 | # Locate config file. 35 | self.config_file = os.path.join(base_dir, self.CONFIG_FILENAME) 36 | 37 | self.exports = {} 38 | self.configs = {} 39 | 40 | self._loaded = False 41 | 42 | def Export(self, method, name=None): 43 | """Exports a method to config modules.""" 44 | if not name: 45 | name = method.__name__ 46 | assert name not in self.exports, '%s already in exports!' 47 | self.exports[name] = method 48 | 49 | def Load(self, ui): 50 | """Loads configurations and do setups. 51 | 52 | Raises: 53 | ConfigurationError: configuration is missing or incorrect. 54 | """ 55 | assert not self._loaded, 'TargetBase.Load() called twice!' 56 | self._loaded = True 57 | 58 | # Evaluate config. 59 | try: 60 | script = files.ReadFile(self.config_file) 61 | except IOError: 62 | raise ConfigurationError('cannot read file: %s' % self.config_file) 63 | try: 64 | code = compile(script, self.config_file, 'exec') 65 | self.PreLoad(ui) 66 | exec(code, self.exports, self.configs) 67 | self.PostLoad(ui) 68 | except ReloadConfiguration: 69 | raise # Passthru 70 | except Exception as e: 71 | # TODO(nya): print pretty file/lineno for debug 72 | raise ConfigurationError(e) 73 | 74 | def PreLoad(self, ui): 75 | """Called just before evaluation of configs. 76 | 77 | Subclasses should setup exported symbols by self.exports. 78 | """ 79 | pass 80 | 81 | def PostLoad(self, ui): 82 | """Called just after evaluation of configs. 83 | 84 | Subclasses can do post-processing of configs here. 85 | """ 86 | pass 87 | 88 | def FindByBaseDir(self, base_dir): 89 | """Search whole subtree and return the object with matching base_dir. 90 | 91 | Subclasses may want to override this method for recursive search. 92 | """ 93 | if self.base_dir == base_dir: 94 | return self 95 | return None 96 | 97 | @classmethod 98 | def CanLoadFrom(self, base_dir): 99 | return os.path.isfile(os.path.join(base_dir, self.CONFIG_FILENAME)) 100 | 101 | 102 | class ConfigurationError(Exception): 103 | pass 104 | 105 | 106 | class ReloadConfiguration(Exception): 107 | pass 108 | 109 | 110 | class Project(TargetBase): 111 | """Project target. 112 | 113 | Project is the only target defined in rime.core. Here, only special methods 114 | which need to be cared in the core library are defined, e.g. use_plugin(). 115 | """ 116 | 117 | CONFIG_FILENAME = 'PROJECT' 118 | 119 | def PreLoad(self, ui): 120 | # Do not use super() here because targets.Project will be overridden. 121 | TargetBase.PreLoad(self, ui) 122 | 123 | def use_plugin(name): 124 | module_name = 'rime.plugins.%s' % name 125 | if module_name not in sys.modules: 126 | if not module_loader.LoadModule(module_name): 127 | raise ConfigurationError( 128 | 'Failed to load a plugin: %s' % name) 129 | raise ReloadConfiguration('use_plugin(%s)' % repr(name)) 130 | self.exports['use_plugin'] = use_plugin 131 | 132 | 133 | registry = class_registry.ClassRegistry(TargetBase) 134 | registry.Add(Project) 135 | -------------------------------------------------------------------------------- /rime/core/ui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import traceback 5 | 6 | 7 | class UiContext(object): 8 | """UI object of Rime.""" 9 | 10 | def __init__(self, options, console, commands, graph): 11 | self.options = options 12 | self.console = console 13 | self.commands = commands 14 | self.graph = graph 15 | self.errors = ErrorRecorder(self) 16 | 17 | 18 | class ErrorRecorder(object): 19 | """Accumurates errors/warnings and print summary of them.""" 20 | 21 | def __init__(self, ui): 22 | self.ui = ui 23 | self.errors = [] 24 | self.warnings = [] 25 | 26 | def Error(self, source, reason, exc_info=None, quiet=False): 27 | """Emit an error. 28 | 29 | If quiet is True it is not printed immediately, but shown in summary. 30 | """ 31 | msg, stacktrace = self._FormatErrorMessage(source, reason, exc_info) 32 | self.errors.append(msg) 33 | if not quiet: 34 | self.ui.console.PrintError(msg) 35 | if stacktrace: 36 | self.ui.console.PrintLog(stacktrace) 37 | 38 | def Warning(self, source, reason, exc_info=None, quiet=False): 39 | """Emits an warning. 40 | 41 | If quiet is True it is not printed immediately, but shown in summary. 42 | """ 43 | msg, stacktrace = self._FormatErrorMessage(source, reason, exc_info) 44 | self.warnings.append(msg) 45 | if not quiet: 46 | self.ui.console.PrintWarning(msg) 47 | if stacktrace: 48 | self.ui.console.PrintLog(stacktrace) 49 | 50 | def Exception(self, source, exc_info=None, quiet=False): 51 | """Emits an exception without aborting. 52 | 53 | If exc_info is not given, use current context. 54 | If quiet is True it is not printed immediately, but shown in summary. 55 | """ 56 | if exc_info is None: 57 | exc_info = sys.exc_info() 58 | self.Error(source, str(exc_info[1]), exc_info=exc_info, quiet=quiet) 59 | 60 | def HasError(self): 61 | return bool(self.errors) 62 | 63 | def HasWarning(self): 64 | return bool(self.warnings) 65 | 66 | def PrintSummary(self): 67 | for e in self.errors: 68 | self.ui.console.PrintError(e) 69 | for e in self.warnings: 70 | self.ui.console.PrintWarning(e) 71 | self.ui.console.Print( 72 | 'Total %d errors, %d warnings' % 73 | (len(self.errors), len(self.warnings))) 74 | 75 | def _FormatErrorMessage(self, source, reason, exc_info): 76 | msg, stacktrace = '', None 77 | if source: 78 | msg += '%s: ' % source.fullname 79 | msg += '%s' % reason 80 | if exc_info is not None and self.ui.options.debug >= 1: 81 | stackframes = traceback.extract_tb(exc_info[2]) 82 | filename, lineno, modulename, code = stackframes[-1] 83 | msg += ' [File "%s", line %d, in %s]' % ( 84 | filename, lineno, modulename) 85 | stacktrace = ''.join(traceback.format_list(stackframes)) 86 | return (msg, stacktrace) 87 | -------------------------------------------------------------------------------- /rime/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Provides plugins of Rime. 4 | 5 | Modules in this package can be loaded with use_plugin(name) directive in 6 | PROJECT file to enhance the Rime framework. 7 | """ 8 | -------------------------------------------------------------------------------- /rime/plugins/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from rime.core import commands 4 | 5 | 6 | class Example(commands.CommandBase): 7 | def __init__(self, parent): 8 | super(Example, self).__init__( 9 | 'example', 10 | '', 11 | 'Example command.', 12 | 'Example help.', 13 | parent) 14 | 15 | def Run(self, project, args, ui): 16 | ui.console.Print('Hello, world!') 17 | ui.console.Print() 18 | ui.console.Print('Project:') 19 | ui.console.Print(' %s' % repr(project)) 20 | ui.console.Print() 21 | ui.console.Print('Parameters:') 22 | for i, arg in enumerate(args): 23 | ui.console.Print(' args[%s] = "%s"' % (i, arg)) 24 | ui.console.Print() 25 | ui.console.Print('Options:') 26 | for key, value in ui.options.items(): 27 | ui.console.Print(' options.%s = %s' % (key, value)) 28 | 29 | 30 | commands.registry.Add(Example) 31 | -------------------------------------------------------------------------------- /rime/plugins/htmlify_full.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import codecs 5 | import os.path 6 | 7 | import rime.basic.targets.project # NOQA 8 | from rime.core import commands as rime_commands # NOQA 9 | from rime.core import targets # NOQA 10 | from rime.core import taskgraph # NOQA 11 | 12 | 13 | class Project(targets.registry.Project): 14 | @taskgraph.task_method 15 | def HtmlifyFull(self, ui): 16 | if not ui.options.skip_clean: 17 | yield self.Clean(ui) 18 | 19 | results = yield self.Test(ui) 20 | template_file = os.path.join( 21 | os.path.dirname(__file__), 22 | 'summary', 23 | 'html.ninja') 24 | content = self._Summarize(results, template_file, ui) 25 | out_file = os.path.join(self.base_dir, 'summary.html') 26 | codecs.open(out_file, 'w', 'utf8').write(content) 27 | yield None 28 | 29 | 30 | class HtmlifyFull(rime_commands.CommandBase): 31 | def __init__(self, parent): 32 | super(HtmlifyFull, self).__init__( 33 | 'htmlify_full', 34 | '', 35 | 'Htmlify full plugin', 36 | '', 37 | parent) 38 | self.AddOptionEntry(rime_commands.OptionEntry( 39 | 's', 'skip_clean', 'skip_clean', bool, False, None, 40 | 'Skip cleaning generated files up.' 41 | )) 42 | 43 | def Run(self, obj, args, ui): 44 | if args: 45 | ui.console.PrintError( 46 | 'Extra argument passed to htmlify_full command!') 47 | return None 48 | 49 | if isinstance(obj, Project): 50 | return obj.HtmlifyFull(ui) 51 | 52 | ui.console.PrintError( 53 | 'Htmlify_full is not supported for the specified target.') 54 | return None 55 | 56 | 57 | targets.registry.Override('Project', Project) 58 | 59 | rime_commands.registry.Add(HtmlifyFull) 60 | -------------------------------------------------------------------------------- /rime/plugins/judge_system/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Submodules for supporting judge systems. 4 | """ 5 | -------------------------------------------------------------------------------- /rime/plugins/judge_system/aoj.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path 5 | 6 | from rime.basic import codes as basic_codes 7 | from rime.basic import consts 8 | import rime.basic.targets.testset # NOQA 9 | from rime.core import targets 10 | from rime.core import taskgraph 11 | from rime.plugins.plus import commands as plus_commands 12 | from rime.util import files 13 | 14 | 15 | class Testset(targets.registry.Testset): 16 | def __init__(self, *args, **kwargs): 17 | super(Testset, self).__init__(*args, **kwargs) 18 | self.aoj_pack_dir = os.path.join(self.problem.out_dir, 'aoj') 19 | 20 | 21 | class AOJPacker(plus_commands.PackerBase): 22 | @taskgraph.task_method 23 | def Pack(self, ui, testset): 24 | testcases = testset.ListTestCases() 25 | try: 26 | files.RemoveTree(testset.aoj_pack_dir) 27 | files.MakeDir(testset.aoj_pack_dir) 28 | except Exception: 29 | ui.errors.Exception(testset) 30 | yield False 31 | for (i, testcase) in enumerate(testcases): 32 | basename = os.path.splitext(testcase.infile)[0] 33 | difffile = basename + consts.DIFF_EXT 34 | packed_infile = 'in' + str(i + 1) + '.txt' 35 | packed_difffile = 'out' + str(i + 1) + '.txt' 36 | try: 37 | ui.console.PrintAction( 38 | 'PACK', 39 | testset, 40 | '%s -> %s' % (os.path.basename(testcase.infile), 41 | packed_infile), 42 | progress=True) 43 | files.CopyFile(os.path.join(testset.out_dir, testcase.infile), 44 | os.path.join(testset.aoj_pack_dir, 45 | packed_infile)) 46 | ui.console.PrintAction( 47 | 'PACK', 48 | testset, 49 | '%s -> %s' % (os.path.basename(difffile), packed_difffile), 50 | progress=True) 51 | files.CopyFile(os.path.join(testset.out_dir, difffile), 52 | os.path.join(testset.aoj_pack_dir, 53 | packed_difffile)) 54 | except Exception: 55 | ui.errors.Exception(testset) 56 | yield False 57 | 58 | # case.txt 59 | files.WriteFile(str(len(testcases)), 60 | os.path.join(testset.aoj_pack_dir, 'case.txt')) 61 | 62 | # build.sh 63 | # TODO(mizuno): reactive 64 | checker = testset.judges[0] 65 | if (len(testset.judges) == 1 and 66 | not isinstance(checker, basic_codes.InternalDiffCode)): 67 | ui.console.PrintAction( 68 | 'PACK', testset, 'checker files', progress=True) 69 | files.CopyFile(os.path.join(testset.src_dir, checker.src_name), 70 | os.path.join(testset.aoj_pack_dir, 'checker.cpp')) 71 | for f in checker.dependency: 72 | files.CopyFile(os.path.join(testset.project.library_dir, f), 73 | os.path.join(testset.aoj_pack_dir, f)) 74 | files.WriteFile( 75 | '#!/bin/bash\ng++ -o checker -std=c++11 checker.cpp', 76 | os.path.join(testset.aoj_pack_dir, 'build.sh')) 77 | elif len(testset.judges) > 1: 78 | ui.errors.Error( 79 | testset, "Multiple output checker is not supported!") 80 | yield False 81 | 82 | # AOJCONF 83 | aoj_conf = '''\ 84 | # -*- coding: utf-8; mode: python -*- 85 | 86 | # Problem ID 87 | PROBLEM_ID = '*' 88 | 89 | # Judge type 90 | # 'diff-validator' for a problem without special judge 91 | # 'float-validator' for a problem with floating point validator 92 | # 'special-validator' for a problem with special validator 93 | # 'reactive' for a reactive problem 94 | {0} 95 | 96 | # Language of problem description 97 | # 'ja', 'en' or 'ja-en' 98 | DOCUMENT_TYPE = '*' 99 | 100 | # Title of the problem 101 | TITLE = '{1}' 102 | 103 | # Time limit (integer, in seconds) 104 | TIME_LIMIT = 1 105 | 106 | # Memory limit (integer, in KB) 107 | MEMORY_LIMIT = 32768 108 | 109 | # Date when the problem description will be able to be seen 110 | PUBLICATION_DATE = datetime.datetime(*, *, *, *, *) 111 | ''' 112 | if not isinstance(checker, basic_codes.InternalDiffCode): 113 | files.WriteFile( 114 | aoj_conf.format( 115 | 'JUDGE_TYPE = \'special-validator\'', 116 | testset.problem.title), 117 | os.path.join(testset.aoj_pack_dir, 'AOJCONF')) 118 | else: 119 | files.WriteFile( 120 | aoj_conf.format( 121 | 'JUDGE_TYPE = \'diff-validator\'', testset.problem.title), 122 | os.path.join(testset.aoj_pack_dir, 'AOJCONF')) 123 | 124 | yield True 125 | 126 | 127 | targets.registry.Override('Testset', Testset) 128 | 129 | plus_commands.packer_registry.Add(AOJPacker) 130 | -------------------------------------------------------------------------------- /rime/plugins/judge_system/atcoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import os.path 6 | import re 7 | import time 8 | 9 | from six.moves import http_cookiejar 10 | from six.moves import urllib 11 | 12 | from rime.basic import codes as basic_codes 13 | from rime.basic import consts 14 | import rime.basic.targets.problem 15 | import rime.basic.targets.project 16 | import rime.basic.targets.solution 17 | import rime.basic.targets.testset # NOQA 18 | from rime.core import targets 19 | from rime.core import taskgraph 20 | from rime.plugins.plus import commands as plus_commands 21 | from rime.util import files 22 | 23 | 24 | # opener with cookiejar 25 | cookiejar = http_cookiejar.CookieJar() 26 | opener = urllib.request.build_opener( 27 | urllib.request.HTTPCookieProcessor(cookiejar)) 28 | 29 | 30 | class Project(targets.registry.Project): 31 | def PreLoad(self, ui): 32 | super(Project, self).PreLoad(ui) 33 | self.atcoder_config_defined = False 34 | 35 | def _atcoder_config(upload_script, contest_url, username, password, 36 | lang_ids): 37 | self.atcoder_config_defined = True 38 | self.atcoder_upload_script = upload_script 39 | self.atcoder_contest_url = contest_url 40 | self.atcoder_username = username 41 | self.atcoder_password = password 42 | self.atcoder_lang_ids = lang_ids 43 | self.exports['atcoder_config'] = _atcoder_config 44 | self.atcoder_logined = False 45 | 46 | @taskgraph.task_method 47 | def Upload(self, ui): 48 | script = os.path.join(self.atcoder_upload_script) 49 | if not os.path.exists(os.path.join(self.base_dir, script)): 50 | ui.errors.Error(self, script + ' is not found.') 51 | yield False 52 | yield super(Project, self).Upload(ui) 53 | 54 | def _Request(self, path, data=None): 55 | if type(data) is dict: 56 | data = urllib.parse.urlencode(data).encode('utf-8') 57 | req = urllib.request.Request(self.atcoder_contest_url + path, data) 58 | return opener.open(req) 59 | 60 | def _Login(self): 61 | if not self.atcoder_logined: 62 | self._Request('login', 63 | {'name': self.atcoder_username, 64 | 'password': self.atcoder_password}) 65 | self.atcoder_logined = True 66 | 67 | 68 | class Problem(targets.registry.Problem): 69 | def PreLoad(self, ui): 70 | super(Problem, self).PreLoad(ui) 71 | self.atcoder_config_defined = False 72 | 73 | def _atcoder_config(task_id=None): 74 | self.atcoder_config_defined = True 75 | self.atcoder_task_id = task_id 76 | self.exports['atcoder_config'] = _atcoder_config 77 | 78 | @taskgraph.task_method 79 | def Submit(self, ui): 80 | if not self.atcoder_config_defined: 81 | ui.errors.Error( 82 | self, 'atcoder_config() is not defined in PROBLEM.') 83 | yield False 84 | if self.atcoder_task_id is None: 85 | ui.console.PrintAction( 86 | 'SUBMIT', self, 87 | 'This problem is considered to a spare. Not submitted.') 88 | yield True 89 | yield super(Problem, self).Submit(ui) 90 | 91 | 92 | class Testset(targets.registry.Testset): 93 | def __init__(self, *args, **kwargs): 94 | super(Testset, self).__init__(*args, **kwargs) 95 | self.atcoder_pack_dir = os.path.join(self.problem.out_dir, 'atcoder') 96 | 97 | 98 | class AtCoderPacker(plus_commands.PackerBase): 99 | @taskgraph.task_method 100 | def Pack(self, ui, testset): 101 | testcases = testset.ListTestCases() 102 | try: 103 | files.RemoveTree(testset.atcoder_pack_dir) 104 | files.MakeDir(testset.atcoder_pack_dir) 105 | files.MakeDir(os.path.join(testset.atcoder_pack_dir, 'in')) 106 | files.MakeDir(os.path.join(testset.atcoder_pack_dir, 'out')) 107 | files.MakeDir(os.path.join(testset.atcoder_pack_dir, 'etc')) 108 | except Exception: 109 | ui.errors.Exception(testset) 110 | yield False 111 | for (i, testcase) in enumerate(testcases): 112 | basename = os.path.splitext(testcase.infile)[0] 113 | difffile = basename + consts.DIFF_EXT 114 | packed_infile = os.path.join('in', os.path.basename(basename)) 115 | packed_difffile = os.path.join('out', os.path.basename(basename)) 116 | try: 117 | ui.console.PrintAction( 118 | 'PACK', 119 | testset, 120 | '%s -> %s' % (os.path.basename(testcase.infile), 121 | packed_infile), 122 | progress=True) 123 | files.CopyFile(os.path.join(testset.out_dir, testcase.infile), 124 | os.path.join(testset.atcoder_pack_dir, 125 | packed_infile)) 126 | ui.console.PrintAction( 127 | 'PACK', 128 | testset, 129 | '%s -> %s' % (os.path.basename(difffile), packed_difffile), 130 | progress=True) 131 | files.CopyFile(os.path.join(testset.out_dir, difffile), 132 | os.path.join(testset.atcoder_pack_dir, 133 | packed_difffile)) 134 | except Exception: 135 | ui.errors.Exception(testset) 136 | yield False 137 | 138 | # checker 139 | checker = testset.judges[0] 140 | if (len(testset.judges) == 1 and 141 | not isinstance(checker, basic_codes.InternalDiffCode)): 142 | ui.console.PrintAction( 143 | 'PACK', testset, 'output checker files', progress=True) 144 | files.CopyFile( 145 | os.path.join(testset.src_dir, checker.src_name), 146 | os.path.join(testset.atcoder_pack_dir, 'etc', 147 | 'output_checker.cpp')) 148 | for f in checker.dependency: 149 | files.CopyFile(os.path.join(testset.project.library_dir, f), 150 | os.path.join(testset.atcoder_pack_dir, 'etc', 151 | f)) 152 | elif len(testset.judges) > 1: 153 | ui.errors.Error( 154 | testset, "Multiple output checker is not supported!") 155 | yield False 156 | 157 | # reactive 158 | if len(testset.reactives) == 1: 159 | reactive = testset.reactives[0] 160 | ui.console.PrintAction( 161 | 'PACK', testset, 'reactive checker files', progress=True) 162 | files.CopyFile( 163 | os.path.join(testset.src_dir, reactive.src_name), 164 | os.path.join(testset.atcoder_pack_dir, 'etc', 'reactive.cpp')) 165 | for f in reactive.dependency: 166 | files.CopyFile(os.path.join(testset.project.library_dir, f), 167 | os.path.join(testset.atcoder_pack_dir, 'etc', 168 | f)) 169 | # outは使わない 170 | files.RemoveTree(os.path.join(testset.atcoder_pack_dir, 'out')) 171 | elif len(testset.judges) > 1: 172 | ui.errors.Error( 173 | testset, "Multiple reactive checker is not supported!") 174 | yield False 175 | 176 | # score.txt 177 | subtasks = testset.subtask_testcases 178 | if len(subtasks) > 0: 179 | score = '\n'.join([ 180 | s.name + '(' + str(s.score) + ')' + ': ' + 181 | ','.join(s.input_patterns) 182 | for s in subtasks]) 183 | else: 184 | score = 'All(100): *' 185 | files.WriteFile( 186 | score, os.path.join(testset.atcoder_pack_dir, 'etc', 'score.txt')) 187 | 188 | yield True 189 | 190 | 191 | class AtCoderUploader(plus_commands.UploaderBase): 192 | @taskgraph.task_method 193 | def Upload(self, ui, problem, dryrun): 194 | if not problem.project.atcoder_config_defined: 195 | ui.errors.Error( 196 | problem, 'atcoder_config() is not defined in PROJECT.') 197 | yield False 198 | 199 | if not problem.atcoder_config_defined: 200 | ui.errors.Error( 201 | problem, 'atcoder_config() is not defined in PROBLEM.') 202 | yield False 203 | 204 | if problem.atcoder_task_id is None: 205 | ui.console.PrintAction( 206 | 'UPLOAD', problem, 207 | 'This problem is considered to a spare. Not uploaded.') 208 | yield True 209 | 210 | script = os.path.join(problem.project.atcoder_upload_script) 211 | if not os.path.exists(os.path.join(problem.project.base_dir, script)): 212 | ui.errors.Error(problem, script + ' is not found.') 213 | yield False 214 | 215 | stmp = files.ReadFile(script) 216 | if not stmp.startswith('#!/usr/bin/php'): 217 | ui.errors.Error(problem, script + ' is not an upload script.') 218 | yield False 219 | 220 | log = os.path.join(problem.out_dir, 'upload_log') 221 | 222 | if not dryrun: 223 | args = ('php', script, str(problem.atcoder_task_id), 224 | problem.testset.atcoder_pack_dir) 225 | else: 226 | ui.console.PrintWarning('Dry-run mode') 227 | args = ('echo', 'php', script, str(problem.atcoder_task_id), 228 | problem.testset.atcoder_pack_dir) 229 | 230 | ui.console.PrintAction( 231 | 'UPLOAD', problem, ' '.join(args), progress=True) 232 | devnull = files.OpenNull() 233 | 234 | with open(log, 'a+') as logfile: 235 | task = taskgraph.ExternalProcessTask( 236 | args, cwd=problem.project.base_dir, 237 | stdin=devnull, stdout=logfile, stderr=logfile, exclusive=True) 238 | try: 239 | proc = yield task 240 | except Exception: 241 | ui.errors.Exception(problem) 242 | yield False 243 | ret = proc.returncode 244 | if ret != 0: 245 | ui.errors.Error(problem, 'upload failed: ret = %d' % ret) 246 | yield False 247 | ui.console.PrintAction( 248 | 'UPLOAD', problem, str(problem.atcoder_task_id)) 249 | yield True 250 | 251 | 252 | class AtCoderSubmitter(plus_commands.SubmitterBase): 253 | @taskgraph.task_method 254 | def Submit(self, ui, solution): 255 | if not solution.project.atcoder_config_defined: 256 | ui.errors.Error( 257 | solution, 'atcoder_config() is not defined in PROJECT.') 258 | yield False 259 | 260 | solution.project._Login() 261 | 262 | task_id = str(solution.problem.atcoder_task_id) 263 | lang_id = solution.project.atcoder_lang_ids[solution.code.PREFIX] 264 | source_code = files.ReadFile( 265 | os.path.join(solution.src_dir, solution.code.src_name)) 266 | 267 | ui.console.PrintAction( 268 | 'SUBMIT', solution, str({'task_id': task_id, 'lang_id': lang_id}), 269 | progress=True) 270 | 271 | html = solution.project._Request('submit?task_id=%s' % task_id).read() 272 | pat = re.compile(r'name="__session" value="([^"]+)"') 273 | m = pat.search(str(html)) 274 | session = m.group(1) 275 | r = solution.project._Request('submit?task_id=%s' % task_id, { 276 | '__session': session, 277 | 'task_id': task_id, 278 | 'language_id_' + task_id: lang_id, 279 | 'source_code': source_code}) 280 | r.read() 281 | 282 | results = solution.project._Request('submissions/me').read() 283 | submit_id = str(results).split( 284 | ' input/%s' % (os.path.basename(testcase.infile), 46 | packed_infile), 47 | progress=True) 48 | files.CopyFile(os.path.join(testset.out_dir, testcase.infile), 49 | os.path.join(testset.pack_dir, 50 | 'input', 51 | packed_infile)) 52 | ui.console.PrintAction( 53 | 'PACK', 54 | testset, 55 | '%s -> output/%s' % (os.path.basename(difffile), 56 | packed_difffile), 57 | progress=True) 58 | files.CopyFile(os.path.join(testset.out_dir, difffile), 59 | os.path.join(testset.pack_dir, 60 | 'output', 61 | packed_difffile)) 62 | except Exception: 63 | ui.errors.Exception(testset) 64 | yield False 65 | 66 | # hacker_rank.zip 67 | try: 68 | shutil.make_archive( 69 | os.path.join(testset.pack_dir, 'hacker_rank'), 70 | 'zip', 71 | os.path.join(testset.pack_dir)) 72 | ui.console.PrintAction( 73 | 'PACK', testset, 'zipped to hacker_rank.zip', progress=True) 74 | except Exception: 75 | ui.errors.Exception(testset) 76 | yield False 77 | 78 | yield True 79 | 80 | 81 | targets.registry.Override('Testset', Testset) 82 | 83 | plus_commands.packer_registry.Add(HackerRankPacker) 84 | -------------------------------------------------------------------------------- /rime/plugins/markdownify_full.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import codecs 5 | import os.path 6 | 7 | import rime.basic.targets.project # NOQA 8 | from rime.core import commands as rime_commands # NOQA 9 | from rime.core import targets # NOQA 10 | from rime.core import taskgraph # NOQA 11 | 12 | 13 | class Project(targets.registry.Project): 14 | @taskgraph.task_method 15 | def MarkdownifyFull(self, ui): 16 | if not ui.options.skip_clean: 17 | yield self.Clean(ui) 18 | 19 | results = yield self.Test(ui) 20 | template_file = os.path.join( 21 | os.path.dirname(__file__), 22 | 'summary', 23 | 'md.ninja') 24 | content = self._Summarize(results, template_file, ui) 25 | out_file = os.path.join(self.base_dir, 'summary.md') 26 | codecs.open(out_file, 'w', 'utf8').write(content) 27 | yield None 28 | 29 | 30 | class MarkdownifyFull(rime_commands.CommandBase): 31 | def __init__(self, parent): 32 | super(MarkdownifyFull, self).__init__( 33 | 'markdownify_full', 34 | '', 35 | 'Markdownify full plugin', 36 | '', 37 | parent) 38 | self.AddOptionEntry(rime_commands.OptionEntry( 39 | 's', 'skip_clean', 'skip_clean', bool, False, None, 40 | 'Skip cleaning generated files up.' 41 | )) 42 | 43 | def Run(self, obj, args, ui): 44 | if args: 45 | ui.console.PrintError( 46 | 'Extra argument passed to markdownify_full command!') 47 | return None 48 | 49 | if isinstance(obj, Project): 50 | return obj.MarkdownifyFull(ui) 51 | 52 | ui.console.PrintError( 53 | 'Markdownify_full is not supported for the specified target.') 54 | return None 55 | 56 | 57 | targets.registry.Override('Project', Project) 58 | 59 | rime_commands.registry.Add(MarkdownifyFull) 60 | -------------------------------------------------------------------------------- /rime/plugins/merged_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import fnmatch 4 | import os.path 5 | 6 | from rime.basic import consts 7 | import rime.basic.targets.testset # NOQA 8 | from rime.basic import test 9 | from rime.core import targets 10 | from rime.core import taskgraph 11 | from rime.util import files 12 | 13 | 14 | class TestMerger(object): 15 | def __init__(self, name, input_pattern, 16 | input_separator, input_terminator, 17 | output_separator, output_terminator): 18 | self.name = name 19 | self.input_pattern = input_pattern 20 | self.input_separator = input_separator 21 | self.input_terminator = input_terminator 22 | self.output_separator = output_separator 23 | self.output_terminator = output_terminator 24 | 25 | def Run(self, testcases, merged_testcase, ui): 26 | infiles = [t.infile for t in testcases 27 | if fnmatch.fnmatch(os.path.basename(t.infile), 28 | self.input_pattern)] 29 | difffiles = [os.path.splitext(infile)[0] + consts.DIFF_EXT 30 | for infile in infiles] 31 | ui.console.PrintAction( 32 | 'MERGE', merged_testcase.testset, 33 | 'Generating %s' % os.path.basename(merged_testcase.infile), 34 | progress=True) 35 | self._Concatenate(infiles, merged_testcase.infile, 36 | self.input_separator, 37 | self.input_terminator) 38 | ui.console.PrintAction( 39 | 'MERGE', merged_testcase.testset, 40 | 'Generating %s' % os.path.basename(merged_testcase.difffile), 41 | progress=True) 42 | self._Concatenate(difffiles, merged_testcase.difffile, 43 | self.output_separator, 44 | self.output_terminator) 45 | 46 | @classmethod 47 | def _Concatenate(cls, srcs, dst, separator, terminator): 48 | with open(dst, 'w') as f: 49 | for i, src in enumerate(srcs): 50 | if i > 0: 51 | f.write(separator) 52 | f.write(files.ReadFile(src)) 53 | f.write(terminator) 54 | 55 | 56 | class MergedTestCase(test.TestCase): 57 | def __init__(self, testset, merger): 58 | super(MergedTestCase, self).__init__( 59 | testset, 60 | os.path.join(testset.out_dir, '%s%s' % 61 | (merger.name, consts.IN_EXT))) 62 | self.merger = merger 63 | 64 | @property 65 | def timeout(self): 66 | return None 67 | 68 | 69 | class Testset(targets.registry.Testset): 70 | def __init__(self, *args, **kwargs): 71 | super(Testset, self).__init__(*args, **kwargs) 72 | self.mergers = [] 73 | 74 | def PreLoad(self, ui): 75 | super(Testset, self).PreLoad(ui) 76 | 77 | def merged_test(name=None, input_pattern=('*' + consts.IN_EXT), 78 | input_separator='', input_terminator='', 79 | output_separator='', output_terminator=''): 80 | if name is None: 81 | name = 'M%s' % len(self.mergers) 82 | merger = TestMerger(name, input_pattern, 83 | input_separator, input_terminator, 84 | output_separator, output_terminator) 85 | for name in ('input_separator', 'input_terminator', 86 | 'output_separator', 'output_terminator'): 87 | value = locals()[name] 88 | if value and not value.endswith('\n'): 89 | ui.errors.Warning( 90 | self, 'merged_test(): %s not ending with \\n.' % name) 91 | self.mergers.append(merger) 92 | self.exports['merged_test'] = merged_test 93 | 94 | @taskgraph.task_method 95 | def _PostBuildHook(self, ui): 96 | if not (yield super(Testset, self)._PostBuildHook(ui)): 97 | yield False 98 | yield all((yield taskgraph.TaskBranch([ 99 | self._GenerateMergedTest(testcase, ui) 100 | for testcase in self.GetMergedTestCases()]))) 101 | 102 | @taskgraph.task_method 103 | def _GenerateMergedTest(self, merged_testcase, ui): 104 | merged_testcase.merger.Run(self.ListTestCases(), 105 | merged_testcase, 106 | ui) 107 | yield True 108 | 109 | @taskgraph.task_method 110 | def _TestSolutionWithAllCases(self, solution, ui): 111 | original_result = ( 112 | yield super(Testset, self)._TestSolutionWithAllCases(solution, ui)) 113 | if original_result.expected and solution.IsCorrect() and self.mergers: 114 | merged_result = ( 115 | yield self._TestSolutionWithMergedTests(solution, ui)) 116 | original_result.results.update(merged_result.results) 117 | if not merged_result.expected: 118 | original_result.Finalize( 119 | False, detail=merged_result.detail, 120 | notable_testcase=merged_result.notable_testcase, 121 | allow_override=True) 122 | else: 123 | if merged_result.IsTimingValid(ui): 124 | detail = ('%s, %s' % 125 | (original_result.detail, 126 | ', '.join(['%s %.2fs' % 127 | (os.path.basename(t.infile), 128 | merged_result.results[t].time) 129 | for t in merged_result.testcases]))) 130 | else: 131 | detail = ('%s, %s' % 132 | (original_result.detail, 133 | ', '.join(['%s *.**s' % 134 | os.path.basename(t.infile) 135 | for t in merged_result.testcases]))) 136 | original_result.Finalize( 137 | True, detail=detail, allow_override=True) 138 | yield original_result 139 | 140 | @taskgraph.task_method 141 | def _TestSolutionWithMergedTests(self, solution, ui): 142 | testcases = self.GetMergedTestCases() 143 | result = test.TestsetResult(self, solution, testcases) 144 | # Try all cases. 145 | yield taskgraph.TaskBranch([ 146 | self._TestSolutionWithAllCasesOne(solution, testcase, result, ui) 147 | for testcase in testcases], 148 | unsafe_interrupt=True) 149 | if not result.IsFinalized(): 150 | result.Finalize(True, 'okay') 151 | yield result 152 | 153 | def ListTestCases(self): 154 | merged_infiles = set([t.infile for t in self.GetMergedTestCases()]) 155 | return [t for t in super(Testset, self).ListTestCases() 156 | if t.infile not in merged_infiles] 157 | 158 | def GetMergedTestCases(self): 159 | return [MergedTestCase(self, merger) for merger in self.mergers] 160 | 161 | 162 | targets.registry.Override('Testset', Testset) 163 | -------------------------------------------------------------------------------- /rime/plugins/plus/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Submodules for bootstrap plugin. 4 | """ 5 | 6 | rime_plus_version = '1.0.0' 7 | -------------------------------------------------------------------------------- /rime/plugins/plus/flexible_judge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os.path 4 | 5 | from rime.basic import consts 6 | import rime.basic.targets.testset # NOQA 7 | from rime.basic import test 8 | from rime.core import codes as core_codes 9 | from rime.core import targets 10 | from rime.core import taskgraph 11 | from rime.util import class_registry 12 | 13 | 14 | class JudgeRunner(object): 15 | def Run(self, infile, difffile, outfile, cwd, judgefile): 16 | raise NotImplementedError() 17 | 18 | 19 | class RimeJudgeRunner(JudgeRunner): 20 | PREFIX = 'rime' 21 | 22 | def Run(self, judge, infile, difffile, outfile, cwd, judgefile): 23 | return judge.Run( 24 | args=('--infile', infile, 25 | '--difffile', difffile, 26 | '--outfile', outfile), 27 | cwd=cwd, 28 | input=os.devnull, 29 | output=judgefile, 30 | timeout=None, precise=False, 31 | redirect_error=True) # !redirect_error 32 | 33 | 34 | class TestlibJudgeRunner(JudgeRunner): 35 | PREFIX = 'testlib' 36 | 37 | def Run(self, judge, infile, difffile, outfile, cwd, judgefile): 38 | return judge.Run( 39 | args=(infile, outfile, difffile), 40 | cwd=cwd, 41 | input=os.devnull, 42 | output=judgefile, 43 | timeout=None, precise=False, 44 | redirect_error=True) # !redirect_error 45 | 46 | 47 | judge_runner_registry = class_registry.ClassRegistry(JudgeRunner) 48 | judge_runner_registry.Add(RimeJudgeRunner) 49 | judge_runner_registry.Add(TestlibJudgeRunner) 50 | 51 | 52 | class ReactiveRunner(object): 53 | def Run(self, reactive, solution, args, cwd, input, output, timeout, 54 | precise): 55 | raise NotImplementedError() 56 | 57 | 58 | class KUPCReactiveRunner(ReactiveRunner): 59 | PREFIX = 'kupc' 60 | 61 | def Run(self, reactive, args, cwd, input, output, timeout, precise): 62 | return reactive.Run( 63 | args=("'%s'" % ' '.join(args),), 64 | cwd=cwd, 65 | input=input, 66 | output=output, 67 | timeout=timeout, 68 | precise=precise, 69 | redirect_error=True) # !redirect_error 70 | 71 | 72 | class TestlibReactiveRunner(ReactiveRunner): 73 | PREFIX = 'testlib' 74 | 75 | def Run(self, reactive, solution, args, cwd, input, output, timeout, 76 | precise): 77 | raise NotImplementedError() 78 | 79 | 80 | class NEERCReactiveRunner(ReactiveRunner): 81 | PREFIX = 'neerc' 82 | 83 | def Run(self, reactive, solution, args, cwd, input, output, timeout, 84 | precise): 85 | raise NotImplementedError() 86 | 87 | 88 | reactive_runner_registry = class_registry.ClassRegistry(ReactiveRunner) 89 | reactive_runner_registry.Add(KUPCReactiveRunner) 90 | # reactive_runner_registry.Add(TestlibReactiveRunner) 91 | # reactive_runner_registry.Add(NEERCReactiveRunner) 92 | 93 | 94 | class Testset(targets.registry.Testset): 95 | def __init__(self, *args, **kwargs): 96 | super(Testset, self).__init__(*args, **kwargs) 97 | 98 | for judge_runner in judge_runner_registry.classes.values(): 99 | self.exports['{0}_judge_runner'.format( 100 | judge_runner.PREFIX)] = judge_runner() 101 | 102 | for reactive_runner in reactive_runner_registry.classes.values(): 103 | self.exports['{0}_reactive_runner'.format( 104 | reactive_runner.PREFIX)] = reactive_runner() 105 | 106 | @taskgraph.task_method 107 | def _TestOneCaseNoCache(self, solution, testcase, ui): 108 | """Test a solution with one case. 109 | 110 | Never cache results. 111 | Returns TestCaseResult. 112 | """ 113 | outfile, judgefile = [ 114 | os.path.join( 115 | solution.out_dir, 116 | os.path.splitext(os.path.basename(testcase.infile))[0] + ext) 117 | for ext in (consts.OUT_EXT, consts.JUDGE_EXT)] 118 | precise = (ui.options.precise or ui.options.parallelism <= 1) 119 | # reactive 120 | if self.reactives: 121 | if len(self.reactives) > 1: 122 | ui.errors.Error(self, "Multiple reactive checkers registered.") 123 | yield None 124 | reactive = self.reactives[0] 125 | if not reactive.variant: 126 | reactive.variant = KUPCReactiveRunner() 127 | res = yield reactive.variant.Run( 128 | reactive=reactive, 129 | args=solution.code.run_args, cwd=solution.out_dir, 130 | input=testcase.infile, 131 | output=outfile, 132 | timeout=testcase.timeout, precise=precise) 133 | # Normally, res is codes.RunResult. 134 | # Some reactive variants returns TestCaseResult 135 | if isinstance(res, test.TestCaseResult): 136 | res.solution = solution 137 | res.testcase = testcase 138 | res.cached = False 139 | yield res 140 | return 141 | else: 142 | res = yield solution.Run( 143 | args=(), cwd=solution.out_dir, 144 | input=testcase.infile, 145 | output=outfile, 146 | timeout=testcase.timeout, precise=precise) 147 | if res.status == core_codes.RunResult.TLE: 148 | yield test.TestCaseResult(solution, testcase, 149 | test.TestCaseResult.TLE, 150 | time=None, cached=False) 151 | if res.status != core_codes.RunResult.OK: 152 | yield test.TestCaseResult(solution, testcase, 153 | test.TestCaseResult.RE, 154 | time=None, cached=False) 155 | 156 | time = res.time 157 | for judge in self.judges: 158 | if not judge.variant: 159 | judge.variant = RimeJudgeRunner() 160 | res = yield judge.variant.Run( 161 | judge=judge, 162 | infile=testcase.infile, 163 | difffile=testcase.difffile, 164 | outfile=outfile, 165 | cwd=self.out_dir, 166 | judgefile=judgefile) 167 | if res.status == core_codes.RunResult.NG: 168 | yield test.TestCaseResult(solution, testcase, 169 | test.TestCaseResult.WA, 170 | time=None, cached=False) 171 | elif res.status != core_codes.RunResult.OK: 172 | yield test.TestCaseResult( 173 | solution, testcase, 174 | test.TestVerdict('Validator %s' % res.status), 175 | time=None, cached=False) 176 | yield test.TestCaseResult(solution, testcase, test.TestCaseResult.AC, 177 | time=time, cached=False) 178 | 179 | @taskgraph.task_method 180 | def _RunReferenceSolutionOne(self, reference_solution, testcase, ui): 181 | """Run the reference solution against a single input file.""" 182 | if os.path.isfile(testcase.difffile): 183 | yield True 184 | # reactive 185 | if self.reactives: 186 | if len(self.reactives) > 1: 187 | ui.errors.Error(self, "Multiple reactive checkers registered.") 188 | yield None 189 | reactive = self.reactives[0] 190 | if not reactive.variant: 191 | reactive.variant = KUPCReactiveRunner() 192 | res = yield reactive.variant.Run( 193 | reactive=reactive, 194 | args=reference_solution.code.run_args, 195 | cwd=reference_solution.out_dir, 196 | input=testcase.infile, 197 | output=testcase.difffile, 198 | timeout=None, precise=False) 199 | # Some reactive variants returns TestCaseResult 200 | if isinstance(res, test.TestCaseResult): 201 | if res.verdict != test.TestCaseResult.AC: 202 | ui.errors.Error(reference_solution, res.verdict) 203 | raise taskgraph.Bailout([False]) 204 | yield True 205 | return 206 | else: 207 | res = yield reference_solution.Run( 208 | args=(), cwd=reference_solution.out_dir, 209 | input=testcase.infile, 210 | output=testcase.difffile, 211 | timeout=None, precise=False) 212 | if res.status != core_codes.RunResult.OK: 213 | ui.errors.Error(reference_solution, res.status) 214 | raise taskgraph.Bailout([False]) 215 | ui.console.PrintAction('REFRUN', reference_solution, 216 | '%s: DONE' % os.path.basename(testcase.infile), 217 | progress=True) 218 | yield True 219 | 220 | @taskgraph.task_method 221 | def _CompileJudges(self, ui): 222 | res = (yield super(Testset, self)._CompileJudges(ui)) 223 | if not res: 224 | yield False 225 | 226 | results = yield taskgraph.TaskBranch([ 227 | self._CompileReactiveOne(reactive, ui) 228 | for reactive in self.reactives]) 229 | yield all(results) 230 | 231 | @taskgraph.task_method 232 | def _CompileReactiveOne(self, reactive, ui): 233 | """Compile a single reative.""" 234 | if not reactive.QUIET_COMPILE: 235 | ui.console.PrintAction('COMPILE', self, reactive.src_name) 236 | res = yield reactive.Compile() 237 | if res.status != core_codes.RunResult.OK: 238 | ui.errors.Error(self, '%s: Compile Error (%s)' 239 | % (reactive.src_name, res.status)) 240 | ui.console.PrintLog(reactive.ReadCompileLog()) 241 | yield False 242 | yield True 243 | 244 | 245 | targets.registry.Override('Testset', Testset) 246 | -------------------------------------------------------------------------------- /rime/plugins/plus/merged_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import fnmatch 4 | import os.path 5 | 6 | from rime.basic import consts 7 | import rime.basic.targets.testset # NOQA 8 | from rime.basic import test 9 | from rime.core import targets 10 | from rime.core import taskgraph 11 | from rime.util import class_registry 12 | from rime.util import files 13 | 14 | consts.IN_ORIGINAL_EXT = '.in_orig' 15 | 16 | 17 | class TestMerger(object): 18 | def __init__(self, output_replace=None): 19 | self.output_replace = output_replace 20 | 21 | def Run(self, testcases, merged_testcase, ui): 22 | infiles = [os.path.splitext(t.infile)[0] + consts.IN_ORIGINAL_EXT 23 | for t in testcases] 24 | difffiles = [os.path.splitext(infile)[0] + consts.DIFF_EXT 25 | for infile in infiles] 26 | ui.console.PrintAction( 27 | 'MERGE', merged_testcase.testset, 28 | 'Generating %s' % os.path.basename(merged_testcase.infile), 29 | progress=True) 30 | self._ConcatenateIn(infiles, merged_testcase.infile) 31 | ui.console.PrintAction( 32 | 'MERGE', merged_testcase.testset, 33 | 'Generating %s' % os.path.basename(merged_testcase.difffile), 34 | progress=True) 35 | self._ConcatenateDiff(difffiles, merged_testcase.difffile) 36 | 37 | def _ConcatenateIn(self, srcs, dst): 38 | raise NotImplementedError() 39 | 40 | def _ConcatenateDiff(self, srcs, dst): 41 | # avoid overwriting 42 | if any([os.path.exists(src) and src != dst for src in srcs]): 43 | with open(dst, 'w') as f: 44 | for i, src in enumerate(srcs): 45 | if self.output_replace: 46 | f.write(self.output_replace( 47 | i + 1, files.ReadFile(src))) 48 | else: 49 | f.write(files.ReadFile(src)) 50 | 51 | 52 | class ICPCMerger(TestMerger): 53 | PREFIX = 'icpc' 54 | 55 | def __init__(self, input_terminator, output_replace=None): 56 | super(ICPCMerger, self).__init__(output_replace) 57 | self.input_terminator = input_terminator 58 | if self.input_terminator and not self.input_terminator.endswith('\n'): 59 | raise RuntimeError( 60 | 'icpc_merger(): input_terminator is not ending with \\n.') 61 | 62 | def _ConcatenateIn(self, srcs, dst): 63 | with open(dst, 'w') as f: 64 | for i, src in enumerate(srcs): 65 | f.write(files.ReadFile(src)) 66 | f.write(self.input_terminator) 67 | 68 | 69 | class GCJMerger(TestMerger): 70 | PREFIX = 'gcj' 71 | 72 | def __init__(self, output_replace=None): 73 | super(GCJMerger, self).__init__(output_replace) 74 | 75 | def _ConcatenateIn(self, srcs, dst): 76 | with open(dst, 'w') as f: 77 | f.write(str(len(srcs)) + '\n') 78 | for i, src in enumerate(srcs): 79 | f.write(files.ReadFile(src)) 80 | 81 | 82 | test_merger_registry = class_registry.ClassRegistry(TestMerger) 83 | test_merger_registry.Add(ICPCMerger) 84 | test_merger_registry.Add(GCJMerger) 85 | 86 | 87 | class MergedTestCase(test.TestCase): 88 | def __init__(self, testset, name, input_pattern): 89 | super(MergedTestCase, self).__init__( 90 | testset, 91 | os.path.join(testset.out_dir, 92 | '{0}{1}'.format(name, consts.IN_EXT))) 93 | self.input_pattern = input_pattern 94 | 95 | @property 96 | def timeout(self): 97 | return None 98 | 99 | 100 | class Testset(targets.registry.Testset): 101 | def __init__(self, *args, **kwargs): 102 | super(Testset, self).__init__(*args, **kwargs) 103 | self.test_merger = None 104 | self.merged_testcases = [] 105 | 106 | def PreLoad(self, ui): 107 | super(Testset, self).PreLoad(ui) 108 | 109 | for test_merger in test_merger_registry.classes.values(): 110 | # to make a new environment 111 | def Closure(test_merger): 112 | def Registerer(*args, **kwargs): 113 | if self.test_merger: 114 | raise RuntimeError('Multiple test merger registered.') 115 | self.test_merger = test_merger(*args, **kwargs) 116 | self.exports['{0}_merger'.format( 117 | test_merger.PREFIX)] = Registerer 118 | Closure(test_merger) 119 | 120 | def casenum_replace(case_pattern, case_replace): 121 | return lambda i, src: src.replace( 122 | case_pattern, case_replace.format(i)) 123 | self.exports['casenum_replace'] = casenum_replace 124 | 125 | def merged_testset(name, input_pattern): 126 | self.merged_testcases.append( 127 | MergedTestCase(self, name, input_pattern)) 128 | self.exports['merged_testset'] = merged_testset 129 | 130 | @taskgraph.task_method 131 | def _RunGenerators(self, ui): 132 | if not (yield super(Testset, self)._RunGenerators(ui)): 133 | yield False 134 | 135 | # convert to merged case 136 | if self.test_merger: 137 | for testcase in self.ListTestCases(): 138 | src = testcase.infile 139 | dst = os.path.splitext(src)[0] + consts.IN_ORIGINAL_EXT 140 | files.CopyFile(src, dst) 141 | self.test_merger.Run([testcase], testcase, ui) 142 | 143 | yield True 144 | 145 | @taskgraph.task_method 146 | def _PostBuildHook(self, ui): 147 | if not (yield super(Testset, self)._PostBuildHook(ui)): 148 | yield False 149 | if not all((yield taskgraph.TaskBranch([ 150 | self._GenerateMergedTest(testcase, ui) 151 | for testcase in self.GetMergedTestCases()]))): 152 | yield False 153 | 154 | if not all((yield taskgraph.TaskBranch([ 155 | self._ValidateMergedTest(testcase, ui) 156 | for testcase in self.GetMergedTestCases()]))): 157 | yield False 158 | 159 | yield True 160 | 161 | @taskgraph.task_method 162 | def _GenerateMergedTest(self, merged_testcase, ui): 163 | if not self.test_merger: 164 | ui.errors.Error(self, "No merger registered!") 165 | yield False 166 | 167 | testcases = [t for t in self.ListTestCases() 168 | if fnmatch.fnmatch(os.path.basename(t.infile), 169 | merged_testcase.input_pattern)] 170 | self.test_merger.Run(testcases, merged_testcase, ui) 171 | yield True 172 | 173 | @taskgraph.task_method 174 | def _ValidateMergedTest(self, merged_testcase, ui): 175 | if not self.validators: 176 | if self.base_dir: 177 | ui.errors.Warning(self, 'Validator unavailable') 178 | yield True 179 | testcases = self.GetMergedTestCases() 180 | results = yield taskgraph.TaskBranch([ 181 | self._RunValidatorOne(validator, testcase, ui) 182 | for validator in self.validators 183 | for testcase in testcases]) 184 | if not all(results): 185 | yield False 186 | ui.console.PrintAction('VALIDATE', self, 'OK Merged Cases') 187 | yield True 188 | 189 | @taskgraph.task_method 190 | def _TestSolutionWithAllCases(self, solution, ui): 191 | original_result = ( 192 | yield super(Testset, self)._TestSolutionWithAllCases(solution, ui)) 193 | if (original_result.expected and 194 | solution.IsCorrect() and 195 | self.merged_testcases): 196 | merged_result = (yield self._TestSolutionWithMergedTests( 197 | solution, ui)) 198 | original_result.results.update(merged_result.results) 199 | if not merged_result.expected: 200 | original_result.Finalize( 201 | False, detail=merged_result.detail, 202 | notable_testcase=merged_result.notable_testcase, 203 | allow_override=True) 204 | else: 205 | if merged_result.IsTimingValid(ui): 206 | detail = ('%s, %s' % 207 | (original_result.detail, 208 | ', '.join(['%s %.2fs' % 209 | (os.path.basename(t.infile), 210 | merged_result.results[t].time) 211 | for t in merged_result.testcases]))) 212 | else: 213 | detail = ('%s, %s' % 214 | (original_result.detail, 215 | ', '.join(['%s *.**s' % 216 | os.path.basename(t.infile) 217 | for t in merged_result.testcases]))) 218 | original_result.Finalize( 219 | True, detail=detail, allow_override=True) 220 | yield original_result 221 | 222 | @taskgraph.task_method 223 | def _TestSolutionWithMergedTests(self, solution, ui): 224 | testcases = self.GetMergedTestCases() 225 | result = test.TestsetResult(self, solution, testcases) 226 | # Try all cases. 227 | yield taskgraph.TaskBranch([ 228 | self._TestSolutionWithAllCasesOne(solution, testcase, result, ui) 229 | for testcase in testcases], 230 | unsafe_interrupt=True) 231 | if not result.IsFinalized(): 232 | result.Finalize(True, 'okay') 233 | yield result 234 | 235 | def ListTestCases(self): 236 | merged_infiles = set([t.infile for t in self.GetMergedTestCases()]) 237 | return [t for t in super(Testset, self).ListTestCases() 238 | if t.infile not in merged_infiles] 239 | 240 | def GetMergedTestCases(self): 241 | return self.merged_testcases 242 | 243 | 244 | targets.registry.Override('Testset', Testset) 245 | -------------------------------------------------------------------------------- /rime/plugins/plus/subtask.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import fnmatch 4 | import os.path 5 | import re 6 | 7 | from rime.basic import consts 8 | import rime.basic.targets.solution # NOQA 9 | import rime.basic.targets.testset # NOQA 10 | from rime.basic import test 11 | from rime.core import targets 12 | from rime.core import taskgraph 13 | from rime.util import files 14 | 15 | 16 | class SubtaskTestCase(test.TestCase): 17 | def __init__(self, testset, name, score, input_patterns): 18 | super(SubtaskTestCase, self).__init__( 19 | testset, 20 | name, name) 21 | self.name = name 22 | self.score = score 23 | self.input_patterns = input_patterns 24 | 25 | @property 26 | def timeout(self): 27 | return None 28 | 29 | 30 | class Testset(targets.registry.Testset): 31 | def __init__(self, *args, **kwargs): 32 | super(Testset, self).__init__(*args, **kwargs) 33 | self.subtask_testcases = [] 34 | self.scoring_judge = False 35 | 36 | def PreLoad(self, ui): 37 | super(Testset, self).PreLoad(ui) 38 | 39 | def subtask_testset(name, score=100, input_patterns=['*']): 40 | self.subtask_testcases.append(SubtaskTestCase( 41 | self, name, score, input_patterns)) 42 | self.exports['subtask_testset'] = subtask_testset 43 | 44 | def scoring_judge(): 45 | self.scoring_judge = True 46 | self.exports['scoring_judge'] = scoring_judge 47 | 48 | @taskgraph.task_method 49 | def _TestSolutionWithAllCases(self, solution, ui): 50 | original_result = ( 51 | yield super(Testset, self)._TestSolutionWithAllCases(solution, ui)) 52 | 53 | if self.subtask_testcases: 54 | max_score = 0 55 | min_score = 0 56 | 57 | for subtask in self.subtask_testcases: 58 | subtask_results = [ 59 | r for (t, r) in original_result.results.items() 60 | if any([fnmatch.fnmatch(os.path.basename(t.infile), 61 | input_pattern) 62 | for input_pattern in subtask.input_patterns])] 63 | accepted = all([result.verdict == test.TestCaseResult.AC 64 | for result in subtask_results 65 | if result.verdict != test.TestCaseResult.NA]) 66 | unknown = any([result.verdict == test.TestCaseResult.NA 67 | for result in subtask_results]) 68 | if accepted: 69 | if not unknown: 70 | min_score += subtask.score 71 | max_score += subtask.score 72 | 73 | if min_score == max_score: 74 | detail = ('%s, score %s' % (original_result.detail, min_score)) 75 | else: 76 | detail = ('%s, score %s <= x <= %s' % 77 | (original_result.detail, min_score, max_score)) 78 | ui.errors.Warning( 79 | self, 80 | "If you want more precise score, set keep_going option.") 81 | 82 | if solution.expected_score is not None: 83 | expected_result = (min_score <= solution.expected_score and 84 | solution.expected_score <= max_score) 85 | if expected_result: 86 | original_result.Finalize( 87 | True, detail=detail, allow_override=True) 88 | else: 89 | original_result.Finalize( 90 | False, 91 | notable_testcase=test.TestCase( 92 | self, 'unexpected_score.in'), 93 | detail=detail, allow_override=True) 94 | if min_score == max_score: 95 | ui.errors.Error(self, 96 | 'expected score %s does not equal to ' 97 | '%s' % 98 | (solution.expected_score, min_score)) 99 | else: 100 | ui.errors.Error( 101 | self, 102 | 'expected score x = %s does not satisfy' 103 | '%s <= x <= %s' % 104 | (solution.expected_score, min_score, max_score)) 105 | elif original_result.expected: 106 | original_result.Finalize( 107 | True, detail=detail, allow_override=True) 108 | else: 109 | original_result.Finalize( 110 | False, 111 | notable_testcase=original_result.notable_testcase, 112 | detail=detail, allow_override=True) 113 | 114 | elif original_result.IsAccepted() and self.scoring_judge: 115 | score = 0 116 | p = re.compile("IMOJUDGE<<<(\\d+)>>>") 117 | for (testcase, result) in original_result.results.items(): 118 | judge_detail = files.ReadFile( 119 | os.path.join( 120 | solution.out_dir, 121 | os.path.splitext( 122 | os.path.basename(testcase.infile))[0] + 123 | consts.JUDGE_EXT)) 124 | if judge_detail: 125 | judge_detail = judge_detail.strip() 126 | if judge_detail.isdigit(): 127 | score += int(judge_detail) 128 | elif p.search(judge_detail): 129 | score += int(p.search(judge_detail).group(1)) 130 | else: 131 | ui.errors.Error( 132 | self, 133 | 'the judge result does not indicate a score:' 134 | '"%s"' % (judge_detail)) 135 | original_result.Finalize( 136 | False, 137 | notable_testcase=test.TestCase( 138 | self, 'judge_error.in'), 139 | detail=original_result.detail, allow_override=True) 140 | yield original_result 141 | else: 142 | ui.errors.Error(self, 'the judge is silent.') 143 | original_result.Finalize( 144 | False, 145 | notable_testcase=test.TestCase(self, 'judge_error.in'), 146 | detail=original_result.detail, allow_override=True) 147 | yield original_result 148 | score /= float(len(original_result.results)) 149 | detail = ('%s, score %s' % 150 | (original_result.detail, score)) 151 | expected_result = score == solution.expected_score 152 | if expected_result or not solution.expected_score: 153 | original_result.Finalize( 154 | True, detail=detail, allow_override=True) 155 | else: 156 | original_result.Finalize( 157 | False, 158 | notable_testcase=test.TestCase( 159 | self, 'unexpected_score.in'), 160 | detail=detail, allow_override=True) 161 | ui.errors.Error(self, 162 | 'expected score %d does not equal to %s' % 163 | (solution.expected_score, score)) 164 | original_result.Finalize(True, detail=detail, allow_override=True) 165 | yield original_result 166 | 167 | 168 | class Solution(targets.registry.Solution): 169 | def __init__(self, *args, **kwargs): 170 | super(Solution, self).__init__(*args, **kwargs) 171 | self.expected_score = None 172 | 173 | def PreLoad(self, ui): 174 | super(Solution, self).PreLoad(ui) 175 | 176 | def expected_score(score): 177 | self.expected_score = score 178 | self.exports['expected_score'] = expected_score 179 | 180 | 181 | targets.registry.Override('Solution', Solution) 182 | targets.registry.Override('Testset', Testset) 183 | -------------------------------------------------------------------------------- /rime/plugins/rime_plus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # import sub-modules 4 | import rime.plugins.plus.basic_patch # NOQA 5 | import rime.plugins.plus.commands # NOQA 6 | import rime.plugins.plus.flexible_judge # NOQA 7 | import rime.plugins.plus.merged_test # NOQA 8 | import rime.plugins.plus.subtask # NOQA 9 | -------------------------------------------------------------------------------- /rime/plugins/summary/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Submodules for generating project summary. 4 | """ 5 | -------------------------------------------------------------------------------- /rime/plugins/summary/html.ninja: -------------------------------------------------------------------------------- 1 | {%- macro bg(type) -%} 2 | {%- if type == ItemState.GOOD -%} class="success" 3 | {%- elif type == ItemState.NOTBAD -%} class="warning" 4 | {%- elif type == ItemState.BAD -%} class="danger" 5 | {%- else -%} class="info" {%- endif -%} 6 | {%- endmacro -%} 7 | 8 | {%- macro cell(type) -%} 9 | {%- if type == ItemState.GOOD -%} ○ 10 | {%- elif type == ItemState.NOTBAD -%} △ 11 | {%- elif type == ItemState.BAD -%} × 12 | {%- else -%} - {%- endif -%} 13 | {%- endmacro -%} 14 | 15 | 16 | 17 | 18 | 19 | 20 | このファイルは htmlify_full plugin により自動生成されています (rev.{{ system.rev }}, uploaded by {{ system.username }} @ {{ system.hostname }}) 21 | 22 |

Summary

23 | 24 | 25 | 26 | 27 | {%- for problem in problems %} 28 | 43 | {% endfor %} 44 |
問題担当解答入力出力入検出検
{{ 29 | problem.title 30 | }} {{ 31 | problem.assignees 32 | }} {{ 33 | problem.solution_state.detail 34 | }} {{ 35 | problem.input_state.detail 36 | }} {{ 37 | problem.output_state.detail 38 | }} {{ 39 | cell(problem.validator) 40 | }} {{ 41 | cell(problem.judge) 42 | }}
45 | 46 |

Environments

47 |
48 | {% for env in environments %} 49 |
{{ env.type }}:
50 |
{{ env.detail }}
51 | {% endfor %} 52 |
53 | 54 | {% if errors|length > 0 or warnings|length > 0 %} 55 |

Error Messages

56 |
57 | 58 | {% if errors|length > 0 %} 59 |
ERROR:
60 |
    61 | {%- for error in errors %} 62 |
  • {{ error }}
  • 63 | {%- endfor %} 64 |
65 | {% endif %} 66 | 67 | {% if warnings|length > 0 %} 68 |
WARNING:
69 |
    70 | {%- for warning in warnings %} 71 |
  • {{ warning }}
  • 72 | {%- endfor %} 73 |
74 | {% endif %} 75 | 76 |
77 | {% endif %} 78 | 79 | 80 |

Detail

81 | {% for problem in problems %} 82 |

{{ problem.title }}

83 | 84 | 85 | 88 | 89 | 90 | {%- for case in problem.testcases %} 91 | {% for sol in problem.solutions -%} {% endfor -%} 104 | {%- endfor -%} 105 |
testcaseindiffmd5 {% for sol in problem.solutions -%} {{ 86 | sol.name | replace('-', ' ') | replace('_', ' ') 87 | }} {% endfor -%} comments
{{ 92 | case.name | replace('-', ' ') | replace('_', ' ') 93 | }} {{ 94 | case.insize 95 | }} {{ 96 | case.outsize 97 | }} {{ 98 | case.md5 99 | }} {{ 100 | sol.verdicts[case.name].detail 101 | }} {{ 102 | case.comment | replace('\n', '
') 103 | }}
106 | {% endfor %} 107 | -------------------------------------------------------------------------------- /rime/plugins/summary/md.ninja: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | {%- macro emoji(type) -%} 4 | {%- if type == ItemState.GOOD -%} :white_check_mark: 5 | {%- elif type == ItemState.NOTBAD -%} :large_blue_diamond: 6 | {%- elif type == ItemState.BAD -%} :x: 7 | {%- else -%} :wavy_dash: {%- endif -%} 8 | {%- endmacro -%} 9 | 10 | 11 | # Project Status 12 | 13 | このファイルは markdownify_full plugin により自動生成されています (rev.{{ system.rev }}, uploaded by {{ system.username }} @ {{ system.hostname }}) 14 | 15 | 16 | ## Summary 17 | 18 | 問題|担当|解答|入力|出力|入検|出検 19 | :---|:---|:---|:---|:---|:---|:--- 20 | {%- for problem in problems %} 21 | {{ 22 | problem.title 23 | }} | {{ 24 | problem.assignees 25 | }} | {{ 26 | emoji(problem.solution_state.status) }} {{ problem.solution_state.detail 27 | }} | {{ 28 | emoji(problem.input_state.status) }} {{ problem.input_state.detail 29 | }} | {{ 30 | emoji(problem.output_state.status) }} {{ problem.output_state.detailo 31 | }} | {{ 32 | emoji(problem.validator) 33 | }} | {{ 34 | emoji(problem.judge) 35 | }} 36 | {%- endfor %} 37 | 38 | 39 | ## Environments 40 | 41 | {% for env in environments %} 42 | - {{ env.type }} 43 | - {{ env.detail }} 44 | {% endfor %} 45 | 46 | 47 | {% if errors|length > 0 or warnings|length > 0 %} 48 | ## Error Messages 49 | 50 | {% if errors|length > 0 %} 51 | - ERROR: 52 | {%- for error in errors %} 53 | - {{ error }} 54 | {%- endfor %} 55 | {% endif %} 56 | 57 | {% if warnings|length > 0 %} 58 | - WARNING: 59 | {%- for warning in warnings %} 60 | - {{ warning }} 61 | {%- endfor %} 62 | {% endif %} 63 | 64 | {% endif %} 65 | 66 | 67 | ## Detail 68 | {% for problem in problems %} 69 | ### {{ problem.title }} 70 | 71 | testcase | in | diff | md5 | {% for sol in problem.solutions -%} {{ 72 | sol.name | replace('-', ' ') | replace('_', ' ') 73 | }} | {% endfor -%} comments 74 | {% for _ in range(problem.solutions|length + 5) -%} |:--- {%- endfor %} 75 | {%- for case in problem.testcases %} 76 | {{ 77 | case.name | replace('-', ' ') | replace('_', ' ') 78 | }} | {{ 79 | case.insize 80 | }} | {{ 81 | case.outsize 82 | }} | `{{ 83 | case.md5 84 | }}` | {% for sol in problem.solutions -%} {{ 85 | emoji(sol.verdicts[case.name].status) }} {{ sol.verdicts[case.name].detail 86 | }} | {% endfor -%} | {{ 87 | case.comment | replace('\n', '
') 88 | }} 89 | {%- endfor -%} 90 | {% endfor %} 91 | 92 | {% endautoescape %} 93 | -------------------------------------------------------------------------------- /rime/plugins/summary/pukiwiki.ninja: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | {%- macro bg(type) -%} 4 | {%- if type == ItemState.GOOD -%} BGCOLOR(#ccffcc): 5 | {%- elif type == ItemState.NOTBAD -%} BGCOLOR(#ffffcc): 6 | {%- elif type == ItemState.BAD -%} BGCOLOR(#ffcccc): 7 | {%- else -%} BGCOLOR(#cccccc): {%- endif -%} 8 | {%- endmacro -%} 9 | 10 | {%- macro cell(type) -%} 11 | {%- if type == ItemState.GOOD -%} ○ 12 | {%- elif type == ItemState.NOTBAD -%} △ 13 | {%- elif type == ItemState.BAD -%} × 14 | {%- else -%} - {%- endif -%} 15 | {%- endmacro -%} 16 | 17 | 18 | #contents 19 | このセクションは wikify plugin により自動生成されています (rev.{{ system.rev }}, uploaded by {{ system.username }} @ {{ system.hostname }}) 20 | 21 | ** Summary 22 | |||CENTER:|CENTER:|CENTER:|CENTER:|CENTER:|c 23 | |~問題|~担当|~解答|~入力|~出力|~入検|~出検| 24 | {%- for problem in problems %} 25 | |[[{{ 26 | problem.title }}>{{ problem.wiki_name 27 | }}]]| {{ 28 | problem.assignees 29 | }} |{{ 30 | bg(problem.solution_state.status) }} {{ problem.solution_state.detail 31 | }} |{{ 32 | bg(problem.input_state.status) }} {{ problem.input_state.detail 33 | }} |{{ 34 | bg(problem.output_state.status) }} {{ problem.output_state.detail 35 | }} |{{ 36 | bg(problem.validator) }} {{ cell(problem.validator) 37 | }} |{{ 38 | bg(problem.judge) }} {{ cell(problem.judge) 39 | }} | 40 | {%- endfor %} 41 | 42 | ** Environments 43 | {% for env in environments %} 44 | :{{ env.type }}:|{{ env.detail }} 45 | {% endfor %} 46 | 47 | {% if errors|length > 0 or warnings|length > 0 %} 48 | ** Error Messages 49 | {% if errors|length > 0 %} 50 | :COLOR(red):ERROR:| 51 | {%- for error in errors %} 52 | --{{ error }} 53 | {%- endfor %} 54 | {% endif %} 55 | 56 | {% if warnings|length > 0 %} 57 | :COLOR(yellow):WARNING:| 58 | {%- for warning in warnings %} 59 | --{{ warning }} 60 | {%- endfor %} 61 | {% endif %} 62 | {% endif %} 63 | 64 | {% endautoescape %} 65 | -------------------------------------------------------------------------------- /rime/plugins/summary/pukiwiki_full.ninja: -------------------------------------------------------------------------------- 1 | {% include "./pukiwiki.ninja" %} 2 | 3 | {% autoescape off %} 4 | 5 | {%- macro bg(type) -%} 6 | {%- if type == ItemState.GOOD -%} BGCOLOR(#ccffcc): 7 | {%- elif type == ItemState.NOTBAD -%} BGCOLOR(#ffffcc): 8 | {%- elif type == ItemState.BAD -%} BGCOLOR(#ffcccc): 9 | {%- else -%} BGCOLOR(#cccccc): {%- endif -%} 10 | {%- endmacro -%} 11 | 12 | {%- macro cell(type) -%} 13 | {%- if type == ItemState.GOOD -%} ○ 14 | {%- elif type == ItemState.NOTBAD -%} △ 15 | {%- elif type == ItemState.BAD -%} × 16 | {%- else -%} - {%- endif -%} 17 | {%- endmacro -%} 18 | 19 | ** Detail 20 | {% for problem in problems %} 21 | ***{{ problem.title }} 22 | |CENTER:~testcase|CENTER:~in|CENTER:~diff|CENTER:~md5{% for sol in problem.solutions -%}|CENTER:~{{ 23 | sol.name | replace('-', ' ') | replace('_', ' ') 24 | }} {% endfor -%}|CENTER:~Comments|h 25 | |LEFT:|RIGHT:|RIGHT:|LEFT:{% for _ in range(problem.solutions|length) -%} |RIGHT: {%- endfor %}|LEFT:|c 26 | {%- for case in problem.testcases %} 27 | |{{ 28 | case.name | replace('-', ' ') | replace('_', ' ') 29 | }} | {{ 30 | case.insize 31 | }} | {{ 32 | case.outsize 33 | }} | {{ 34 | case.md5 35 | }} {% for sol in problem.solutions -%} |{{ bg(sol.verdicts[case.name].status) }} {{ 36 | sol.verdicts[case.name].detail 37 | }} {% endfor -%} | {{ 38 | case.comment | replace('\n', '&br;') | replace('|', '|') 39 | }}| 40 | {%- endfor %} 41 | {% endfor %} 42 | 43 | 44 | {% endautoescape %} 45 | -------------------------------------------------------------------------------- /rime/plugins/summary/summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import getpass 5 | import hashlib 6 | import os 7 | import socket 8 | import sys 9 | 10 | from enum import Enum 11 | from itertools import groupby 12 | 13 | from jinja2 import Environment 14 | from jinja2 import FileSystemLoader 15 | 16 | if sys.version_info[0] == 2: 17 | import commands as builtin_commands # NOQA 18 | else: 19 | import subprocess as builtin_commands 20 | 21 | from rime.basic import consts # NOQA 22 | from rime.basic import codes as basic_codes # NOQA 23 | from rime.basic import test # NOQA 24 | 25 | 26 | class ItemState(Enum): 27 | GOOD, NOTBAD, BAD, NA = range(4) 28 | 29 | 30 | def SafeUnicode(s): 31 | if sys.version_info.major == 2 and not isinstance(s, unicode): # NOQA 32 | s = s.decode('utf-8') 33 | return s 34 | 35 | 36 | def GetFileSize(dir, filename): 37 | filepath = os.path.join(dir, filename) 38 | if os.path.exists(filepath): 39 | return '%dB' % os.path.getsize(filepath) 40 | else: 41 | return '-' 42 | 43 | 44 | def GetFileHash(dir, filename): 45 | filepath = os.path.join(dir, filename) 46 | if os.path.exists(filepath): 47 | f = open(filepath) 48 | r = f.read() 49 | f.close() 50 | return hashlib.md5(SafeUnicode(r).encode('utf-8')).hexdigest() 51 | else: 52 | return '' 53 | 54 | 55 | def GetTestcaseComment(dir, filename): 56 | filepath = os.path.join(dir, filename) 57 | if os.path.exists(filepath): 58 | f = open(filepath) 59 | r = f.read().strip() 60 | f.close() 61 | return SafeUnicode(r) 62 | else: 63 | return '' 64 | 65 | 66 | def GetTestCaseState(result): 67 | """Generate per testcase result for summary 68 | 69 | Arguments: 70 | result (rime.basic.test.TestCaseResult) 71 | """ 72 | if result.verdict is test.TestCaseResult.NA: 73 | return {'status': ItemState.NA, 'detail': str(result.verdict)} 74 | elif result.verdict is test.TestCaseResult.AC: 75 | return {'status': ItemState.GOOD, 'detail': '%.2fs' % (result.time)} 76 | else: 77 | return {'status': ItemState.BAD, 'detail': str(result.verdict)} 78 | 79 | 80 | def GenerateSummary(results, template_file, ui): 81 | """Generate a project summary. 82 | 83 | Arguments: 84 | results (array of rime.basic.test.TestCaseResult): 85 | data used to generate a summary. 86 | template_file (string): path to a jinja template file. 87 | 88 | Returns: 89 | Project summary as a string. 90 | """ 91 | (dirname, basename) = os.path.split(template_file) 92 | jinja_env = Environment(loader=FileSystemLoader(dirname, encoding='utf8')) 93 | template = jinja_env.get_template(basename) 94 | template.globals['ItemState'] = ItemState 95 | 96 | summ = GenerateProjectSummary(results, ui) 97 | return template.render(**summ) 98 | 99 | 100 | def GenerateProjectSummary(results, ui): 101 | """Generate an object for project summary. 102 | 103 | Arguments: 104 | results (array of rime.basic.test.TestsetResult): 105 | data used to generate a summary. 106 | """ 107 | system = { 108 | 'rev': builtin_commands.getoutput( 109 | 'git show -s --oneline').replace('\n', ' ').replace('\r', ' '), 110 | 'username': getpass.getuser(), 111 | 'hostname': socket.gethostname(), 112 | } 113 | 114 | cc = os.getenv('CC', 'gcc') 115 | cxx = os.getenv('CXX', 'g++') 116 | java_home = os.getenv('JAVA_HOME') 117 | if java_home is not None: 118 | java = os.path.join(java_home, 'bin/java') 119 | javac = os.path.join(java_home, 'bin/javac') 120 | else: 121 | java = 'java' 122 | javac = 'javac' 123 | 124 | environments = [ 125 | { 126 | 'type': 'gcc', 127 | 'detail': builtin_commands.getoutput( 128 | '{0} --version'.format(cc)).strip(), 129 | }, { 130 | 'type': 'g++', 131 | 'detail': builtin_commands.getoutput( 132 | '{0} --version'.format(cxx)).strip(), 133 | }, { 134 | 'type': 'javac', 135 | 'detail': builtin_commands.getoutput( 136 | '{0} --version'.format(javac)).strip(), 137 | }, { 138 | 'type': 'java', 139 | 'detail': builtin_commands.getoutput( 140 | '{0} --version'.format(java)).strip(), 141 | }] 142 | 143 | # Generate content for each problem. 144 | problems = [GenerateProblemSummary(k, g) 145 | for k, g in groupby(results, lambda k: k.problem)] 146 | 147 | return { 148 | 'system': system, 149 | 'environments': environments, 150 | 'problems': list(problems), 151 | 'errors': ui.errors.errors if ui.errors.HasError() else [], 152 | 'warnings': ui.errors.warnings if ui.errors.HasWarning() else [] 153 | } 154 | 155 | 156 | def GenerateProblemSummary(problem, testset_results): 157 | """Generate an object for project summary for paticular problem. 158 | 159 | Arguments: 160 | problem (rime.basic.targets.problem.Problem): 161 | A problem to summarize. 162 | testset_results (array of rime.basic.test.TestsetResult): 163 | An array of testset result of the specified problem. 164 | """ 165 | testset_results = list( 166 | sorted( 167 | testset_results, 168 | key=lambda x: x.solution.name)) 169 | 170 | # Get test results from each testset (i.e. per solution) 171 | solutions = [] 172 | testnames = set() 173 | for testset in testset_results: 174 | verdicts = {} 175 | for (testcase, result) in testset.results.items(): 176 | testname = os.path.splitext( 177 | os.path.basename(testcase.infile))[0] 178 | testnames.add(testname) 179 | verdicts[testname] = GetTestCaseState(result) 180 | solutions.append({ 181 | 'name': testset.solution.name, 182 | 'verdicts': verdicts, 183 | }) 184 | 185 | # Populate missing results with NA. 186 | empty_verdict = test.TestCaseResult( 187 | None, None, test.TestCaseResult.NA, None, None) 188 | for solution in solutions: 189 | for testname in testnames: 190 | if testname not in solution['verdicts']: 191 | solution['verdicts'][testname] = empty_verdict 192 | 193 | # Test case informations. 194 | out_dir = problem.testset.out_dir 195 | testcases = [{ 196 | 'name': testname, 197 | 'insize': GetFileSize(out_dir, testname + consts.IN_EXT), 198 | 'outsize': GetFileSize(out_dir, testname + consts.DIFF_EXT), 199 | 'md5': GetFileHash(out_dir, testname + consts.IN_EXT)[:7], 200 | 'comment': GetTestcaseComment(out_dir, testname + '.comment'), 201 | } for testname in sorted(testnames)] 202 | 203 | # Get summary about the problem. 204 | assignees = problem.assignees 205 | if isinstance(assignees, list): 206 | assignees = ','.join(assignees) 207 | 208 | num_solutions = len(solutions) 209 | num_tests = len(problem.testset.ListTestCases()) 210 | correct_solution_results = [result for result in testset_results 211 | if result.solution.IsCorrect()] 212 | num_corrects = len(correct_solution_results) 213 | num_incorrects = num_solutions - num_corrects 214 | num_agreed = len([result for result in correct_solution_results 215 | if result.expected]) 216 | need_custom_judge = problem.need_custom_judge 217 | 218 | # Solutions: 219 | if num_corrects >= 2: 220 | solutions_state = ItemState.GOOD 221 | elif num_corrects >= 1: 222 | solutions_state = ItemState.NOTBAD 223 | else: 224 | solutions_state = ItemState.BAD 225 | 226 | # Input: 227 | if num_tests >= 20: 228 | inputs_state = ItemState.GOOD 229 | else: 230 | inputs_state = ItemState.BAD 231 | 232 | # Output: 233 | if num_corrects >= 2 and num_agreed == num_corrects: 234 | outputs_state = ItemState.GOOD 235 | elif num_agreed >= 2: 236 | outputs_state = ItemState.NOTBAD 237 | else: 238 | outputs_state = ItemState.BAD 239 | 240 | # Validator: 241 | if problem.testset.validators: 242 | validator_state = ItemState.GOOD 243 | else: 244 | validator_state = ItemState.BAD 245 | 246 | # Judge: 247 | if need_custom_judge: 248 | custom_judges = [ 249 | judge for judge in problem.testset.judges 250 | if judge.__class__ != basic_codes.InternalDiffCode] 251 | if custom_judges: 252 | judge_state = ItemState.GOOD 253 | else: 254 | judge_state = ItemState.BAD 255 | else: 256 | judge_state = ItemState.NA 257 | 258 | # Done. 259 | return { 260 | 'title': SafeUnicode(problem.title) or 'No Title', 261 | 'solutions': solutions, 262 | 'testcases': testcases, 263 | 'assignees': SafeUnicode(assignees), 264 | 'solution_state': { 265 | 'status': solutions_state, 266 | 'detail': '%d+%d' % (num_corrects, num_incorrects), 267 | }, 268 | 'input_state': { 269 | 'status': inputs_state, 270 | 'detail': str(num_tests), 271 | }, 272 | 'output_state': { 273 | 'status': outputs_state, 274 | 'detail': '%d/%d' % (num_agreed, num_corrects), 275 | }, 276 | 'validator': validator_state, 277 | 'judge': judge_state, 278 | 'wiki_name': SafeUnicode(problem.wiki_name) or 'No Wiki Name', 279 | } 280 | -------------------------------------------------------------------------------- /rime/plugins/testlib_checker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # using testlib 4 | 5 | import os.path 6 | import subprocess 7 | 8 | from rime.core import codes 9 | from rime.core import taskgraph 10 | from rime.util import files 11 | 12 | 13 | class TestlibCode(codes.registry.CXXCode): 14 | PREFIX = 'testlib' 15 | EXTENSIONS = [] 16 | 17 | def __init__(self, src_name, src_dir, out_dir, flags=[], testlib=None): 18 | super(TestlibCode, self).__init__(src_name, src_dir, out_dir, flags) 19 | self.testlib = testlib 20 | 21 | @taskgraph.task_method 22 | def Compile(self): 23 | """Compile the code and return RunResult.""" 24 | if self.testlib is not None: 25 | files.CopyFile( 26 | os.path.join(self.src_dir, self.testlib), 27 | os.path.join(self.out_dir, 'testlib.h')) 28 | try: 29 | if not self.compile_args: 30 | result = codes.RunResult(codes.RunResult.OK, None) 31 | else: 32 | result = yield self._ExecForCompile(args=self.compile_args) 33 | except Exception as e: 34 | result = codes.RunResult('On compiling: %s' % e, None) 35 | yield result 36 | 37 | @taskgraph.task_method 38 | def _ExecForCompile(self, args): 39 | with open(os.path.join(self.out_dir, self.log_name), 'w') as outfile: 40 | yield (yield self._ExecInternal( 41 | args=args, cwd=self.out_dir, 42 | stdin=files.OpenNull(), stdout=outfile, 43 | stderr=subprocess.STDOUT)) 44 | 45 | @taskgraph.task_method 46 | def Run(self, args, cwd, input, output, timeout, precise, 47 | redirect_error=False): 48 | """Run the code and return RunResult.""" 49 | try: 50 | # reorder 51 | result = yield self._ExecForRun( 52 | args=tuple(list(self.run_args) + [args[1], args[5], args[3]]), 53 | cwd=cwd, input=input, output=output, timeout=timeout, 54 | precise=precise, redirect_error=redirect_error) 55 | except Exception as e: 56 | result = codes.RunResult('On execution: %s' % e, None) 57 | yield result 58 | 59 | 60 | codes.registry.Add(TestlibCode) 61 | -------------------------------------------------------------------------------- /rime/plugins/wikify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import re 6 | import sys 7 | 8 | from six.moves import urllib 9 | 10 | import rime.basic.targets.problem 11 | import rime.basic.targets.project # NOQA 12 | from rime.core import commands as rime_commands 13 | from rime.core import targets 14 | from rime.core import taskgraph 15 | from rime.plugins.summary import summary 16 | 17 | 18 | def SafeUnicode(s): 19 | if sys.version_info.major == 2 and not isinstance(s, unicode): # NOQA 20 | s = s.decode('utf-8') 21 | return s 22 | 23 | 24 | class Project(targets.registry.Project): 25 | def PreLoad(self, ui): 26 | super(Project, self).PreLoad(ui) 27 | self.wikify_config_defined = False 28 | self._Summarize = summary.GenerateSummary 29 | 30 | def _wikify_config(url, page, encoding="utf-8", auth_realm=None, 31 | auth_username=None, auth_password=None): 32 | self.wikify_config_defined = True 33 | self.wikify_url = url 34 | self.wikify_page = page 35 | self.wikify_encoding = encoding 36 | self.wikify_auth_realm = auth_realm 37 | self.wikify_auth_username = auth_username 38 | self.wikify_auth_password = auth_password 39 | self.exports['wikify_config'] = _wikify_config 40 | 41 | @taskgraph.task_method 42 | def Wikify(self, ui): 43 | if not self.wikify_config_defined: 44 | ui.errors.Error(self, 'wikify_config() is not defined.') 45 | yield None 46 | 47 | if not ui.options.skip_clean: 48 | yield self.Clean(ui) 49 | 50 | results = yield self.Test(ui) 51 | template_file = os.path.join( 52 | os.path.dirname(__file__), 53 | 'summary', 54 | 'pukiwiki.ninja') 55 | content = self._Summarize(results, template_file, ui) 56 | self._UploadWiki(content, ui) 57 | yield None 58 | 59 | def _UploadWiki(self, wiki, ui): 60 | url = self.wikify_url 61 | page = SafeUnicode(self.wikify_page) 62 | encoding = self.wikify_encoding 63 | auth_realm = SafeUnicode(self.wikify_auth_realm) 64 | auth_username = self.wikify_auth_username 65 | auth_password = self.wikify_auth_password 66 | auth_hostname = urllib.parse.urlparse(url).hostname 67 | 68 | native_page = page.encode(encoding) 69 | native_wiki = wiki.encode(encoding) 70 | 71 | if self.wikify_auth_realm: 72 | auth_handler = urllib.request.HTTPBasicAuthHandler() 73 | auth_handler.add_password( 74 | auth_realm, auth_hostname, auth_username, auth_password) 75 | opener = urllib.request.build_opener(auth_handler) 76 | urllib.request.install_opener(opener) 77 | 78 | ui.console.PrintAction('UPLOAD', None, url) 79 | 80 | edit_params = { 81 | 'cmd': 'edit', 82 | 'page': native_page, 83 | } 84 | edit_page_content = urllib.request.urlopen( 85 | '%s?%s' % (url, urllib.parse.urlencode(edit_params))).read() 86 | 87 | digest = re.search( 88 | r'value="([0-9a-f]{32})"', 89 | edit_page_content.decode(encoding)).group(1) 90 | 91 | update_params = { 92 | 'cmd': 'edit', 93 | 'page': native_page, 94 | 'digest': digest, 95 | 'msg': native_wiki, 96 | 'write': u'ページの更新'.encode(encoding), 97 | 'encode_hint': u'ぷ'.encode(encoding), 98 | } 99 | urllib.request.urlopen( 100 | url, urllib.parse.urlencode(update_params).encode(encoding)) 101 | 102 | 103 | class Problem(targets.registry.Problem): 104 | def PreLoad(self, ui): 105 | super(Problem, self).PreLoad(ui) 106 | base_problem = self.exports['problem'] 107 | 108 | def _problem(wiki_name, assignees, need_custom_judge, **kwargs): 109 | self.wiki_name = wiki_name 110 | self.assignees = assignees 111 | self.need_custom_judge = need_custom_judge 112 | return base_problem(**kwargs) 113 | self.exports['problem'] = _problem 114 | 115 | 116 | class Wikify(rime_commands.CommandBase): 117 | def __init__(self, parent): 118 | super(Wikify, self).__init__( 119 | 'wikify', 120 | '', 121 | 'Upload test results to Pukiwiki. (wikify plugin)', 122 | '', 123 | parent) 124 | self.AddOptionEntry(rime_commands.OptionEntry( 125 | 's', 'skip_clean', 'skip_clean', bool, False, None, 126 | 'Skip cleaning generated files up.' 127 | )) 128 | 129 | def Run(self, obj, args, ui): 130 | if args: 131 | ui.console.PrintError('Extra argument passed to wikify command!') 132 | return None 133 | 134 | if isinstance(obj, Project): 135 | return obj.Wikify(ui) 136 | 137 | ui.console.PrintError( 138 | 'Wikify is not supported for the specified target.') 139 | return None 140 | 141 | 142 | targets.registry.Override('Project', Project) 143 | targets.registry.Override('Problem', Problem) 144 | 145 | rime_commands.registry.Add(Wikify) 146 | -------------------------------------------------------------------------------- /rime/plugins/wikify_full.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | 6 | import rime.basic.targets.project # NOQA 7 | from rime.core import commands as rime_commands # NOQA 8 | from rime.core import targets # NOQA 9 | from rime.core import taskgraph # NOQA 10 | 11 | 12 | class Project(targets.registry.Project): 13 | @taskgraph.task_method 14 | def WikifyFull(self, ui): 15 | if not self.wikify_config_defined: 16 | ui.errors.Error(self, 'wikify_config() is not defined.') 17 | yield None 18 | 19 | if not ui.options.skip_clean: 20 | yield self.Clean(ui) 21 | 22 | results = yield self.Test(ui) 23 | template_file = os.path.join( 24 | os.path.dirname(__file__), 25 | 'summary', 26 | 'pukiwiki_full.ninja') 27 | content = self._Summarize(results, template_file, ui) 28 | self._UploadWiki(content, ui) 29 | yield None 30 | 31 | 32 | class WikifyFull(rime_commands.CommandBase): 33 | def __init__(self, parent): 34 | super(WikifyFull, self).__init__( 35 | 'wikify_full', 36 | '', 37 | 'Upload all test results to Pukiwiki. (wikify_full plugin)', 38 | '', 39 | parent) 40 | self.AddOptionEntry(rime_commands.OptionEntry( 41 | 's', 'skip_clean', 'skip_clean', bool, False, None, 42 | 'Skip cleaning generated files up.' 43 | )) 44 | 45 | def Run(self, obj, args, ui): 46 | if args: 47 | ui.console.PrintError( 48 | 'Extra argument passed to wikify_full command!') 49 | return None 50 | 51 | if isinstance(obj, Project): 52 | return obj.WikifyFull(ui) 53 | 54 | ui.console.PrintError( 55 | 'Wikify_full is not supported for the specified target.') 56 | return None 57 | 58 | 59 | targets.registry.Override('Project', Project) 60 | 61 | rime_commands.registry.Add(WikifyFull) 62 | -------------------------------------------------------------------------------- /rime/util/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Provides utility modules of Rime. 4 | 5 | Util package contains utility modules not specific to Rime and reusable in 6 | other softwares. 7 | """ 8 | -------------------------------------------------------------------------------- /rime/util/class_registry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | class ClassRegistry(object): 5 | def __init__(self, base_class=object): 6 | self.classes = {} 7 | self.base_class = base_class 8 | 9 | def Get(self, name): 10 | return self.classes.get(name) 11 | 12 | def Add(self, clazz, name=None): 13 | if name is None: 14 | name = clazz.__name__ 15 | assert name not in self.classes 16 | assert issubclass(clazz, self.base_class) 17 | self.classes[name] = clazz 18 | 19 | def Override(self, name, clazz): 20 | assert name in self.classes 21 | assert issubclass(clazz, self.classes[name]) 22 | self.classes[name] = clazz 23 | 24 | def __getattribute__(self, name): 25 | try: 26 | return super(ClassRegistry, self).__getattribute__(name) 27 | except AttributeError as e: 28 | try: 29 | return self.classes[name] 30 | except KeyError: 31 | pass 32 | raise e 33 | -------------------------------------------------------------------------------- /rime/util/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | 5 | from rime.util import files 6 | from rime.util import struct 7 | 8 | 9 | # Escape sequences. 10 | _COLOR_ESCAPE_SEQUENCES = { 11 | 'BOLD': '\x1b[1m', 12 | 'RED': '\x1b[31m', 13 | 'GREEN': '\x1b[32m', 14 | 'YELLOW': '\x1b[33m', 15 | 'BLUE': '\x1b[34m', 16 | 'MAGENTA': '\x1b[35m', 17 | 'CYAN': '\x1b[36m', 18 | 'WHITE': '\x1b[37m', 19 | 'NORMAL': '\x1b[0m', 20 | } 21 | _CONTROL_ESCAPE_SEQUENCES = { 22 | 'UP': '\x1b[1A', 23 | 'KILL': '\x1b[K', 24 | } 25 | 26 | 27 | class ConsoleBase(object): 28 | """Base of Console classes.""" 29 | 30 | def __init__(self, out, caps): 31 | self.out = out 32 | self.caps = caps 33 | self.quiet = False 34 | # Whether the last print is marked "progress". 35 | self._last_progress = False 36 | for name, value in _COLOR_ESCAPE_SEQUENCES.items(): 37 | setattr(self, name, (self.caps.color and value or '')) 38 | for name, value in _CONTROL_ESCAPE_SEQUENCES.items(): 39 | setattr(self, name, (self.caps.overwrite and value or '')) 40 | 41 | # TODO(mizuno): Move to constructor. 42 | def set_quiet(self): 43 | self.quiet = True 44 | 45 | def Print(self, *args, **kwargs): 46 | """Print one line. 47 | 48 | Each argument is either ordinal string or control code. 49 | """ 50 | progress = bool(kwargs.get('progress')) 51 | 52 | if self.quiet and progress: 53 | return 54 | 55 | msg = ''.join(args) 56 | if self._last_progress and self.caps.overwrite: 57 | self.out.write(self.UP + '\r' + msg + self.KILL + '\n') 58 | else: 59 | self.out.write(msg + '\n') 60 | self._last_progress = progress 61 | 62 | def PrintAction(self, action, obj, *args, **kwargs): 63 | """Utility function to print actions.""" 64 | real_args = [self.GREEN, '[' + action.center(10) + ']', self.NORMAL] 65 | if obj: 66 | real_args += [' ', obj.fullname] 67 | if args: 68 | if obj: 69 | real_args += [':'] 70 | real_args += [' '] + list(args) 71 | self.Print(*real_args, **kwargs) 72 | 73 | def PrintError(self, msg): 74 | """Utility function to print errors.""" 75 | self.Print(self.RED, 'ERROR:', self.NORMAL, ' ', msg) 76 | 77 | def PrintWarning(self, msg): 78 | """Utility function to print warnings.""" 79 | self.Print(self.YELLOW, 'WARNING:', self.NORMAL, ' ', msg) 80 | 81 | def PrintLog(self, log): 82 | """Print bare messages. 83 | 84 | Used to print logs such as compiler's output. 85 | """ 86 | if log is None: 87 | return 88 | for line in log.splitlines(): 89 | self.Print(line) 90 | 91 | 92 | class TtyConsole(ConsoleBase): 93 | """Console output to tty.""" 94 | 95 | def __init__(self, out): 96 | super(TtyConsole, self).__init__(out, self._GetCaps()) 97 | 98 | @classmethod 99 | def _GetCaps(cls): 100 | caps = struct.Struct() 101 | caps.overwrite = False 102 | caps.color = False 103 | if sys.stdout.isatty(): 104 | try: 105 | import curses 106 | curses.setupterm() 107 | caps.overwrite = bool(curses.tigetstr('cuu1')) 108 | caps.color = bool(curses.tigetstr('setaf')) 109 | except Exception: 110 | pass 111 | return caps 112 | 113 | 114 | class NullConsole(ConsoleBase): 115 | """Null console output.""" 116 | 117 | def __init__(self): 118 | null_caps = struct.Struct(color=False, overwrite=False) 119 | super(NullConsole, self).__init__(files.OpenNull(), null_caps) 120 | -------------------------------------------------------------------------------- /rime/util/files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import with_statement 4 | import datetime 5 | import os 6 | import os.path 7 | import pickle 8 | import platform 9 | import shutil 10 | import subprocess 11 | 12 | 13 | _devnull = open(os.devnull, 'r+') 14 | 15 | 16 | def CopyFile(src, dst): 17 | shutil.copy(src, dst) 18 | 19 | 20 | def MakeDir(dir): 21 | if not os.path.isdir(dir): 22 | os.makedirs(dir) 23 | 24 | 25 | def CopyTree(src, dst): 26 | MakeDir(dst) 27 | files = ListDir(src, True) 28 | for f in files: 29 | srcpath = os.path.join(src, f) 30 | dstpath = os.path.join(dst, f) 31 | if os.path.isdir(srcpath): 32 | MakeDir(dstpath) 33 | else: 34 | CopyFile(srcpath, dstpath) 35 | 36 | 37 | def RemoveTree(dir): 38 | if os.path.exists(dir): 39 | shutil.rmtree(dir) 40 | 41 | 42 | def GetModified(file): 43 | try: 44 | return datetime.datetime.fromtimestamp(os.path.getmtime(file)) 45 | except Exception: 46 | return datetime.datetime.min 47 | 48 | 49 | def GetLastModifiedUnder(dir): 50 | file_list = [f for f in ListDir(dir, True)] + [dir] 51 | return max([GetModified(os.path.join(dir, name)) for name in file_list]) 52 | 53 | 54 | def CreateEmptyFile(file): 55 | open(file, 'w').close() 56 | 57 | 58 | def ListDir(dir, recursive=False): 59 | files = [] 60 | try: 61 | files = filter(lambda x: not x.startswith('.'), 62 | os.listdir(dir)) 63 | if recursive: 64 | for subfile in files[:]: 65 | subdir = os.path.join(dir, subfile) 66 | if os.path.isdir(subdir): 67 | files += [os.path.join(subfile, s) 68 | for s in ListDir(subdir, True)] 69 | except Exception: 70 | pass 71 | return files 72 | 73 | 74 | def PickleSave(obj, file): 75 | with open(file, 'w') as f: 76 | pickle.dump(obj, f) 77 | 78 | 79 | def PickleLoad(file): 80 | with open(file, 'r') as f: 81 | obj = pickle.load(f) 82 | return obj 83 | 84 | 85 | def ConvPath(path): 86 | if not platform.uname()[0].lower().startswith('cygwin'): 87 | return path 88 | try: 89 | p = subprocess.Popen(['cygpath', '-wp', path], stdout=subprocess.PIPE) 90 | newpath = p.communicate()[0].rstrip('\r\n') 91 | if p.returncode == 0: 92 | return newpath 93 | except Exception: 94 | pass 95 | return path 96 | 97 | 98 | def LocateBinary(name): 99 | if 'PATH' in os.environ: 100 | paths = os.environ['PATH'] 101 | else: 102 | paths = os.defpath 103 | for path in paths.split(os.pathsep): 104 | bin = os.path.join(path, name) 105 | if os.path.isfile(bin) and os.access(bin, os.X_OK): 106 | return bin 107 | return None 108 | 109 | 110 | def OpenNull(): 111 | return _devnull 112 | 113 | 114 | def ReadFile(name): 115 | try: 116 | with open(name, 'r') as f: 117 | return f.read() 118 | except Exception: 119 | return None 120 | 121 | 122 | def WriteFile(content, name): 123 | try: 124 | with open(name, 'w') as f: 125 | f.write(content) 126 | return True 127 | except Exception: 128 | return False 129 | 130 | 131 | def AppendFile(content, name): 132 | try: 133 | with open(name, 'a') as f: 134 | f.write(content) 135 | return True 136 | except Exception: 137 | return False 138 | -------------------------------------------------------------------------------- /rime/util/module_loader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path 5 | 6 | from rime.util import files 7 | 8 | 9 | def LoadModule(module_fullname): 10 | module_name = module_fullname.split('.')[-1] 11 | package_name = '.'.join(module_fullname.split('.')[:-1]) 12 | package = __import__(package_name, globals(), locals(), [module_name]) 13 | return hasattr(package, module_name) 14 | 15 | 16 | def LoadPackage(package_name): 17 | assert package_name 18 | package_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 19 | *package_name.split('.')) 20 | for filename in files.ListDir(package_dir): 21 | if (os.path.isdir(os.path.join(package_dir, filename)) and 22 | os.path.isfile(os.path.join( 23 | package_dir, filename, '__init__.py'))): 24 | LoadPackage('%s.%s' % (package_name, filename)) 25 | elif filename.endswith('.py'): 26 | module_name = os.path.splitext(filename)[0] 27 | if module_name != '__init__': 28 | LoadModule('%s.%s' % (package_name, module_name)) 29 | -------------------------------------------------------------------------------- /rime/util/struct.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | class Struct(dict): 5 | """Dictionary-like object allowing attribute access.""" 6 | 7 | def __getattribute__(self, name): 8 | try: 9 | return super(Struct, self).__getattribute__(name) 10 | except AttributeError as e: 11 | try: 12 | return self[name] 13 | except KeyError: 14 | pass 15 | raise e 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | filename = *.py ./bin/* 3 | ignore = W503, W504, H216 4 | exclude = docs/conf.py 5 | 6 | [pep8] 7 | exclude = ./docs/conf.py 8 | 9 | [tool:pytest] 10 | testpaths = tests 11 | filterwarnings = error::DeprecationWarning 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | 5 | def get_long_description() -> str: 6 | readme = os.path.join(os.path.dirname(__file__), "README.md") 7 | with open(readme) as f: 8 | return f.read() 9 | 10 | 11 | setuptools.setup( 12 | name='rime', 13 | version='3.0.0.dev', 14 | description="An automation tool for programming contest organizers", 15 | long_description=get_long_description(), 16 | long_description_content_type="text/markdown", 17 | scripts=['bin/rime', 'bin/rime_init'], 18 | packages=['rime', 'rime.basic', 'rime.basic.targets', 'rime.basic.util', 19 | 'rime.core', 'rime.plugins', 'rime.plugins.judge_system', 20 | 'rime.plugins.plus', 'rime.plugins.summary', 'rime.util'], 21 | package_dir={'rime': 'rime'}, 22 | install_requires=['six', 'Jinja2', 'requests'], 23 | tests_require=['pytest', 'mock'], 24 | package_data={'rime': ['plugins/summary/*.ninja']}, 25 | ) 26 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/__init__.py -------------------------------------------------------------------------------- /tests/core_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/core_tests/__init__.py -------------------------------------------------------------------------------- /tests/core_tests/test_main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | import mock 5 | 6 | from rime.core import commands 7 | from rime.core import main 8 | from rime.core import ui as ui_mod 9 | from rime.util import console as console_mod 10 | from rime.util.struct import Struct 11 | 12 | 13 | class TestInternalMain(unittest.TestCase): 14 | def test_no_args(self): 15 | cmd = mock.MagicMock() 16 | argv = ['rime'] 17 | args = argv[2:] 18 | options = Struct({'quiet': False, 'parallelism': 0, 'help': True}) 19 | commands.Parse = mock.MagicMock() 20 | commands.Parse.return_value = (cmd, args, options) 21 | 22 | result = main.InternalMain(argv) 23 | 24 | self.assertEqual(result, 0) 25 | cmd.PrintHelp.assert_called() 26 | 27 | def test_help(self): 28 | argv = ['rime', 'help', 'clean'] 29 | 30 | console = console_mod.TtyConsole(sys.stdout) 31 | console_mod.TtyConsole = mock.MagicMock() 32 | console_mod.TtyConsole.return_value = console 33 | 34 | cmd = mock.MagicMock() 35 | cmd.__class__ = commands.Help 36 | args = argv[2:] 37 | options = Struct({'quiet': False, 'parallelism': 0, 'help': False}) 38 | commands.Parse = mock.MagicMock() 39 | commands.Parse.return_value = (cmd, args, options) 40 | 41 | ui = ui_mod.UiContext(options, console, cmd, None) 42 | ui_mod.UiContext = mock.MagicMock() 43 | ui_mod.UiContext.return_value = ui 44 | 45 | main.CheckSystem = mock.MagicMock() 46 | main.CheckSystem.return_value = True 47 | 48 | project = None 49 | main.LoadProject = mock.MagicMock() 50 | main.LoadProject.return_value = project 51 | 52 | result = main.InternalMain(argv) 53 | 54 | self.assertEqual(result, 0) 55 | cmd.Run.assert_called() 56 | -------------------------------------------------------------------------------- /tests/plugins_merged_test_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import unittest 4 | 5 | from rime.core import targets 6 | from rime.plugins import merged_test 7 | 8 | 9 | class MergedTestTest(unittest.TestCase): 10 | def testTestsetOverridden(self): 11 | self.assertTrue(targets.registry.Testset is merged_test.Testset) 12 | 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /tests/plugins_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/plugins_test/__init__.py -------------------------------------------------------------------------------- /tests/plugins_test/test_htmlify_full.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | import shutil 5 | import tempfile 6 | 7 | from rime.plugins import htmlify_full 8 | from rime.util import struct 9 | 10 | 11 | class TestHtmlifyProject(unittest.TestCase): 12 | def setUp(self): 13 | self.tmpdir = tempfile.mkdtemp() 14 | project = htmlify_full.Project('project', self.tmpdir, None) 15 | project.Clean = mock.MagicMock() 16 | project.Test = mock.MagicMock() 17 | project._Summarize = mock.MagicMock(return_value='result') 18 | self.project = project 19 | 20 | def tearDown(self): 21 | shutil.rmtree(self.tmpdir) 22 | 23 | def test_do_clean(self): 24 | ui = mock.MagicMock() 25 | ui.options = struct.Struct({'skip_clean': False}) 26 | 27 | task = self.project.HtmlifyFull(ui) 28 | while task.Continue(): 29 | pass 30 | 31 | self.project.Test.assert_called_once_with(ui) 32 | self.project.Clean.assert_called_once_with(ui) 33 | 34 | def test_skip_clean(self): 35 | ui = mock.MagicMock() 36 | ui.options = struct.Struct({'skip_clean': True}) 37 | 38 | task = self.project.HtmlifyFull(ui) 39 | while task.Continue(): 40 | pass 41 | 42 | self.project.Test.assert_called_once_with(ui) 43 | self.project.Clean.assert_not_called() 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /tests/plugins_test/test_markdownify_full.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | import shutil 5 | import tempfile 6 | 7 | from rime.plugins import markdownify_full 8 | from rime.util import struct 9 | 10 | 11 | class TestMarkdownifyProject(unittest.TestCase): 12 | def setUp(self): 13 | self.tmpdir = tempfile.mkdtemp() 14 | project = markdownify_full.Project('project', self.tmpdir, None) 15 | project.Clean = mock.MagicMock() 16 | project.Test = mock.MagicMock() 17 | project._Summarize = mock.MagicMock(return_value='result') 18 | self.project = project 19 | 20 | def tearDown(self): 21 | shutil.rmtree(self.tmpdir) 22 | 23 | def test_do_clean(self): 24 | ui = mock.MagicMock() 25 | ui.options = struct.Struct({'skip_clean': False}) 26 | 27 | task = self.project.MarkdownifyFull(ui) 28 | while task.Continue(): 29 | pass 30 | 31 | self.project.Test.assert_called_once_with(ui) 32 | self.project.Clean.assert_called_once_with(ui) 33 | 34 | def test_skip_clean(self): 35 | ui = mock.MagicMock() 36 | ui.options = struct.Struct({'skip_clean': True}) 37 | 38 | task = self.project.MarkdownifyFull(ui) 39 | while task.Continue(): 40 | pass 41 | 42 | self.project.Test.assert_called_once_with(ui) 43 | self.project.Clean.assert_not_called() 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /tests/plugins_test/test_wikify.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | 5 | from rime.plugins import wikify 6 | from rime.util import struct 7 | 8 | 9 | class TestWikify(unittest.TestCase): 10 | def setUp(self): 11 | project = wikify.Project('project', 'base_dir', None) 12 | project.wikify_config_defined = True 13 | project.Clean = mock.MagicMock() 14 | project.Test = mock.MagicMock() 15 | project._Summarize = mock.MagicMock() 16 | project._UploadWiki = mock.MagicMock() 17 | self.project = project 18 | 19 | def test_do_clean(self): 20 | ui = mock.MagicMock() 21 | ui.options = struct.Struct({'skip_clean': False}) 22 | 23 | task = self.project.Wikify(ui) 24 | while task.Continue(): 25 | pass 26 | 27 | self.project.Clean.assert_called_once_with(ui) 28 | self.project.Test.assert_called_once_with(ui) 29 | 30 | def test_skip_clean(self): 31 | ui = mock.MagicMock() 32 | ui.options = struct.Struct({'skip_clean': True}) 33 | 34 | task = self.project.Wikify(ui) 35 | while task.Continue(): 36 | pass 37 | 38 | self.project.Clean.assert_not_called() 39 | self.project.Test.assert_called_once_with(ui) 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/plugins_test/test_wikify_full.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | 5 | from rime.plugins import wikify_full 6 | from rime.util import struct 7 | 8 | 9 | class TestWikifyFull(unittest.TestCase): 10 | def setUp(self): 11 | project = wikify_full.Project('project', 'base_dir', None) 12 | project.wikify_config_defined = True 13 | project.Clean = mock.MagicMock() 14 | project.Test = mock.MagicMock() 15 | project._Summarize = mock.MagicMock() 16 | project._UploadWiki = mock.MagicMock() 17 | self.project = project 18 | 19 | def test_do_clean(self): 20 | ui = mock.MagicMock() 21 | ui.options = struct.Struct({'skip_clean': False}) 22 | 23 | task = self.project.WikifyFull(ui) 24 | while task.Continue(): 25 | pass 26 | 27 | self.project.Clean.assert_called_once_with(ui) 28 | self.project.Test.assert_called_once_with(ui) 29 | 30 | def test_skip_clean(self): 31 | ui = mock.MagicMock() 32 | ui.options = struct.Struct({'skip_clean': True}) 33 | 34 | task = self.project.WikifyFull(ui) 35 | while task.Continue(): 36 | pass 37 | 38 | self.project.Clean.assert_not_called() 39 | self.project.Test.assert_called_once_with(ui) 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/util_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/util_tests/__init__.py -------------------------------------------------------------------------------- /tests/util_tests/test_class_registry.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from rime.util import class_registry 4 | 5 | 6 | class HogeBase(object): 7 | pass 8 | 9 | 10 | class HogeImpl(HogeBase): 11 | pass 12 | 13 | 14 | class Piyo(object): 15 | pass 16 | 17 | 18 | class TestClassRegistry(unittest.TestCase): 19 | def test_add(self): 20 | registry = class_registry.ClassRegistry(HogeBase) 21 | 22 | registry.Add(HogeBase, 'Hoge') 23 | self.assertIs(registry.classes['Hoge'], HogeBase) 24 | self.assertIs(registry.Hoge, HogeBase) 25 | self.assertIs(registry.Get('Hoge'), HogeBase) 26 | 27 | def test_add_without_name(self): 28 | registry = class_registry.ClassRegistry(HogeBase) 29 | 30 | registry.Add(HogeBase) 31 | self.assertIs(registry.classes['HogeBase'], HogeBase) 32 | self.assertIs(registry.HogeBase, HogeBase) 33 | self.assertIs(registry.Get('HogeBase'), HogeBase) 34 | 35 | def test_override(self): 36 | registry = class_registry.ClassRegistry(HogeBase) 37 | 38 | registry.Add(HogeBase, 'Hoge') 39 | registry.Override('Hoge', HogeImpl) 40 | self.assertIs(registry.classes['Hoge'], HogeImpl) 41 | self.assertIs(registry.Hoge, HogeImpl) 42 | self.assertIs(registry.Get('Hoge'), HogeImpl) 43 | 44 | def test_add_duplication(self): 45 | registry = class_registry.ClassRegistry(HogeBase) 46 | registry.Add(HogeBase, 'Hoge') 47 | with self.assertRaises(AssertionError): 48 | registry.Add(HogeImpl, 'Hoge') 49 | 50 | def test_base_class_not_subclass(self): 51 | registry = class_registry.ClassRegistry(HogeBase) 52 | registry.Add(HogeBase, 'Hoge') 53 | with self.assertRaises(AssertionError): 54 | registry.Override('Hoge', Piyo) 55 | 56 | def test_base_class_no_override(self): 57 | registry = class_registry.ClassRegistry(HogeBase) 58 | with self.assertRaises(AssertionError): 59 | registry.Override('Hoge', HogeImpl) 60 | 61 | def test_attribute_error(self): 62 | registry = class_registry.ClassRegistry(HogeBase) 63 | with self.assertRaises(AttributeError): 64 | registry.Hoge 65 | -------------------------------------------------------------------------------- /tests/util_tests/test_console.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | import mock 5 | 6 | from rime.util import console 7 | from rime.util import files 8 | from rime.util import struct 9 | 10 | if sys.version_info[0] == 2: 11 | from StringIO import StringIO 12 | else: 13 | from io import StringIO 14 | 15 | 16 | class TestConsole(unittest.TestCase): 17 | def create_stringio_console(self): 18 | return console.ConsoleBase( 19 | out=StringIO(), caps=struct.Struct(color=True, overwrite=True)) 20 | 21 | def test_console_base(self): 22 | c = console.ConsoleBase( 23 | out=None, caps=struct.Struct(color=False, overwrite=False)) 24 | self.assertEqual(c.BOLD, '') 25 | self.assertEqual(c.UP, '') 26 | c = console.ConsoleBase( 27 | out=None, caps=struct.Struct(color=True, overwrite=False)) 28 | self.assertEqual(c.BOLD, '\x1b[1m') 29 | self.assertEqual(c.UP, '') 30 | c = console.ConsoleBase( 31 | out=None, caps=struct.Struct(color=False, overwrite=True)) 32 | self.assertEqual(c.BOLD, '') 33 | self.assertEqual(c.UP, '\x1b[1A') 34 | c = console.ConsoleBase( 35 | out=None, caps=struct.Struct(color=True, overwrite=True)) 36 | self.assertEqual(c.BOLD, '\x1b[1m') 37 | self.assertEqual(c.UP, '\x1b[1A') 38 | 39 | def test_print(self): 40 | c = self.create_stringio_console() 41 | c.Print('hoge') 42 | self.assertEqual(c.out.getvalue(), 'hoge\n') 43 | 44 | def test_print_with_progress(self): 45 | c = self.create_stringio_console() 46 | c.Print('a') 47 | c.Print('b', progress=True) 48 | c.Print('c') 49 | c.Print('d', progress=True) 50 | c.Print('e', progress=True) 51 | c.Print('f') 52 | c.Print('g') 53 | self.assertEqual(c.out.getvalue(), 54 | 'a\n' 55 | 'b\n' 56 | '\x1b[1A\rc\x1b[K\n' 57 | 'd\n' 58 | '\x1b[1A\re\x1b[K\n' 59 | '\x1b[1A\rf\x1b[K\n' 60 | 'g\n') 61 | 62 | def test_quiet(self): 63 | c = self.create_stringio_console() 64 | c.set_quiet() 65 | c.Print('hoge', progress=True) 66 | self.assertEqual(c.out.getvalue(), '') 67 | 68 | def test_print_action_without_obj(self): 69 | c = self.create_stringio_console() 70 | c.Print = mock.MagicMock() 71 | c.PrintAction('HOGE', None, 'hoge', 'piyo', foo='bar') 72 | c.Print.assert_called_once_with( 73 | '\x1b[32m', '[ HOGE ]', '\x1b[0m', ' ', 'hoge', 'piyo', 74 | foo='bar') 75 | 76 | def test_print_action_with_obj(self): 77 | c = self.create_stringio_console() 78 | c.Print = mock.MagicMock() 79 | c.PrintAction('HOGE', struct.Struct(fullname='name'), 80 | 'hoge', 'piyo', foo='bar') 81 | c.Print.assert_called_once_with( 82 | '\x1b[32m', '[ HOGE ]', '\x1b[0m', ' ', 'name', ':', ' ', 83 | 'hoge', 'piyo', foo='bar') 84 | 85 | def test_print_action_with_obj_no_arg(self): 86 | c = self.create_stringio_console() 87 | c.Print = mock.MagicMock() 88 | c.PrintAction('HOGE', struct.Struct(fullname='name'), foo='bar') 89 | c.Print.assert_called_once_with( 90 | '\x1b[32m', '[ HOGE ]', '\x1b[0m', ' ', 'name', foo='bar') 91 | 92 | def test_print_error(self): 93 | c = self.create_stringio_console() 94 | c.Print = mock.MagicMock() 95 | c.PrintError('hoge') 96 | c.Print.assert_called_once_with( 97 | '\x1b[31m', 'ERROR:', '\x1b[0m', ' ', 'hoge') 98 | 99 | def test_print_warning(self): 100 | c = self.create_stringio_console() 101 | c.Print = mock.MagicMock() 102 | c.PrintWarning('hoge') 103 | c.Print.assert_called_once_with( 104 | '\x1b[33m', 'WARNING:', '\x1b[0m', ' ', 'hoge') 105 | 106 | def test_print_log(self): 107 | c = self.create_stringio_console() 108 | c.Print = mock.MagicMock() 109 | c.PrintLog('\nhoge\n\npiyo\n\n') 110 | expected = [ 111 | mock.call(''), 112 | mock.call('hoge'), 113 | mock.call(''), 114 | mock.call('piyo'), 115 | mock.call('') 116 | ] 117 | self.assertEqual(c.Print.mock_calls, expected) 118 | 119 | def test_print_none_log(self): 120 | c = self.create_stringio_console() 121 | c.Print = mock.MagicMock() 122 | c.PrintLog(None) 123 | self.assertEqual(c.Print.mock_calls, []) 124 | 125 | def test_tty_console(self): 126 | c = console.TtyConsole(sys.stdout) 127 | self.assertEqual(c.BOLD, '') 128 | self.assertEqual(c.UP, '') 129 | self.assertEqual(c.out, sys.stdout) 130 | 131 | def test_null_console(self): 132 | c = console.NullConsole() 133 | self.assertEqual(c.BOLD, '') 134 | self.assertEqual(c.UP, '') 135 | self.assertEqual(c.out, files.OpenNull()) 136 | -------------------------------------------------------------------------------- /tests/util_tests/test_module_loader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from rime.util import class_registry 4 | from rime.util import module_loader 5 | 6 | from . import test_package 7 | 8 | 9 | class TestModuleLoader(unittest.TestCase): 10 | def test_load_module(self): 11 | res = module_loader.LoadModule( 12 | 'tests.util_tests.test_package.test_module1') 13 | self.assertTrue(res) 14 | self.assertIs(registry.test_module1, test_package.test_module1.Test1) 15 | 16 | def test_load_package(self): 17 | module_loader.LoadPackage('tests.util_tests.test_package') 18 | self.assertIs(registry.test_module1, test_package.test_module1.Test1) 19 | self.assertIs(registry.test_module2, 20 | test_package.test_subpackage.test_module2.Test2) 21 | 22 | 23 | registry = class_registry.ClassRegistry() 24 | -------------------------------------------------------------------------------- /tests/util_tests/test_package/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/util_tests/test_package/__init__.py -------------------------------------------------------------------------------- /tests/util_tests/test_package/test_module1.py: -------------------------------------------------------------------------------- 1 | from tests.util_tests import test_module_loader 2 | 3 | 4 | class Test1(object): 5 | pass 6 | 7 | 8 | test_module_loader.registry.Add(Test1, 'test_module1') 9 | -------------------------------------------------------------------------------- /tests/util_tests/test_package/test_subpackage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icpc-jag/rime/1194c9dc2d210bf1da4281c144c692f46181cdbf/tests/util_tests/test_package/test_subpackage/__init__.py -------------------------------------------------------------------------------- /tests/util_tests/test_package/test_subpackage/test_module2.py: -------------------------------------------------------------------------------- 1 | from tests.util_tests import test_module_loader 2 | 3 | 4 | class Test2(object): 5 | pass 6 | 7 | 8 | test_module_loader.registry.Add(Test2, 'test_module2') 9 | -------------------------------------------------------------------------------- /tests/util_tests/test_struct.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from rime.util import struct 4 | 5 | 6 | class TestStruct(unittest.TestCase): 7 | def test_dict_attr(self): 8 | self.assertEqual(struct.Struct.items, dict.items) 9 | 10 | def test_constructor(self): 11 | s = struct.Struct(test_attr='test_obj') 12 | self.assertEqual(s.test_attr, 'test_obj') 13 | self.assertEqual(s['test_attr'], 'test_obj') 14 | 15 | def test_add_attr(self): 16 | s = struct.Struct() 17 | s.test_attr = 'test_obj' 18 | self.assertEqual(s.test_attr, 'test_obj') 19 | 20 | def test_add_key(self): 21 | s = struct.Struct() 22 | s['test_attr'] = 'test_obj' 23 | self.assertEqual(s.test_attr, 'test_obj') 24 | self.assertEqual(s['test_attr'], 'test_obj') 25 | 26 | def test_attribute_error(self): 27 | s = struct.Struct() 28 | with self.assertRaises(AttributeError): 29 | s.test_attr 30 | --------------------------------------------------------------------------------