PyReprism 0.2.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyreprism-1.0.0/.dockerignore +18 -0
- pyreprism-1.0.0/.github/pyreprism-action.sh +58 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.gitignore +1 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/CHANGELOG.md +93 -1
- pyreprism-1.0.0/Dockerfile +36 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/PKG-INFO +43 -11
- {pyreprism-0.2.0 → pyreprism-1.0.0}/README.md +184 -6
- pyreprism-1.0.0/action.yml +79 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/docs/intro.rst +34 -8
- {pyreprism-0.2.0 → pyreprism-1.0.0}/pyproject.toml +9 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/__init__.py +10 -9
- pyreprism-1.0.0/src/PyReprism/_parallel.py +47 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/_tokenops.py +28 -4
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/batch.py +66 -25
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/cli.py +112 -16
- pyreprism-1.0.0/src/PyReprism/clones.py +308 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/engines/pygments_engine.py +24 -8
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/__init__.py +64 -19
- pyreprism-1.0.0/src/PyReprism/languages/_scanners.py +137 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/actionscript.py +5 -10
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/ada.py +3 -11
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/apacheconf.py +5 -9
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/applescript.py +5 -3
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/arduino.py +0 -23
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/arff.py +0 -18
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/asm6502.py +5 -6
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/aspnet.py +3 -31
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/autohotkey.py +5 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/autoit.py +5 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/base.py +203 -38
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/bash.py +2 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/bro.py +5 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/c.py +1 -1
- pyreprism-1.0.0/src/PyReprism/languages/cobol.py +69 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/coffeescript.py +5 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/cpp.py +1 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/crystal.py +4 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/css.py +2 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/docker.py +5 -11
- pyreprism-1.0.0/src/PyReprism/languages/dynamic.py +153 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/elixir.py +4 -15
- pyreprism-1.0.0/src/PyReprism/languages/erb.py +29 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/erlang.py +0 -11
- pyreprism-1.0.0/src/PyReprism/languages/flow.py +23 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/fortran.py +0 -11
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/fsharp.py +0 -11
- pyreprism-1.0.0/src/PyReprism/languages/generated.py +158 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/glsl.py +5 -3
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/go.py +7 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/graphql.py +4 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/groovy.py +0 -11
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/hpkp.py +4 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/hsts.py +4 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/ichigojam.py +4 -9
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/icon.py +4 -9
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/java.py +1 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/javascript.py +6 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/jsx.py +6 -21
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/less.py +1 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/livescript.py +5 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/lua.py +5 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/nginx.py +5 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/nsis.py +1 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/objectivec.py +0 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/plsql.py +3 -5
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/properties.py +3 -4
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/pug.py +3 -4
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/python.py +22 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/qore.py +4 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/roboconf.py +3 -4
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/scheme.py +4 -13
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/scss.py +3 -4
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/smalltalk.py +0 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/sql.py +1 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/stylus.py +0 -11
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/tcl.py +4 -2
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/tsx.py +6 -12
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/typescript.py +6 -1
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/yaml.py +3 -4
- pyreprism-1.0.0/src/PyReprism/reporters.py +449 -0
- pyreprism-1.0.0/src/PyReprism/semantic.py +147 -0
- pyreprism-1.0.0/src/PyReprism/syntactic.py +163 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/tokens.py +6 -3
- pyreprism-0.2.0/src/PyReprism/languages/erb.py +0 -46
- pyreprism-0.2.0/src/PyReprism/languages/flow.py +0 -30
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.flake8 +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/FUNDING.yaml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/scripts/changelog_notes.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/scripts/package_version.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/workflows/ci.yml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.github/workflows/publish.yml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.pre-commit-hooks.yaml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/.readthedocs.yaml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/CONTRIBUTING.md +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/LICENSE +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/RELEASING.md +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/codecov.yml +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/__main__.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/diffs.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/engines/__init__.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/engines/base.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/engines/regex_engine.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/fingerprints.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/abap.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/apl.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/asciidoc.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/basic.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/batch.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/bison.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/brainfuck.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/clike.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/clojure.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/csharp.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/csp.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/css_extras.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/d.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/dart.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/diff.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/django.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/eiffel.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/gedcom.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/gherkin.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/git.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/haml.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/handlebars.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/haskell.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/haxe.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/html.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/inform7.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/ini.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/io.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/j.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/jolie.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/json.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/julia.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/keyman.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/kotlin.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/latex.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/liquid.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/lolcode.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/makefile.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/markdown.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/markup.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/markup_templating.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/matlab.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/mel.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/mizar.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/monkey.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/n4js.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/nasm.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/nim.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/nix.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/ocaml.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/opencl.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/oz.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/parigp.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/parser.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/pascal.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/perl.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/php.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/php_extras.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/powershell.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/processing.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/prolog.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/protobuf.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/puppet.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/pure.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/q.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/r.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/reason.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/registry.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/renpy.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/rest.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/rip.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/ruby.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/rust.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/sas.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/sass.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/scala.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/smarty.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/soy.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/swift.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/textile.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/twig.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/vbnet.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/velocity.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/verilog.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/vhdl.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/vim.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/visual_basic.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/wasam.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/xeora.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/languages/xojo.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/metrics.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/ngrams.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/py.typed +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/utils/__init__.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/utils/extension.py +0 -0
- {pyreprism-0.2.0 → pyreprism-1.0.0}/src/PyReprism/utils/normalizer.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Keep the build context small: only pyproject.toml, LICENSE, docs/intro.rst,
|
|
2
|
+
# and src/ are needed to build the wheel (see Dockerfile).
|
|
3
|
+
.git
|
|
4
|
+
.github
|
|
5
|
+
manuscript
|
|
6
|
+
tests
|
|
7
|
+
venv
|
|
8
|
+
.venv
|
|
9
|
+
docs/*
|
|
10
|
+
!docs/intro.rst
|
|
11
|
+
*.egg-info
|
|
12
|
+
__pycache__
|
|
13
|
+
.pytest_cache
|
|
14
|
+
.mypy_cache
|
|
15
|
+
dist
|
|
16
|
+
build
|
|
17
|
+
*.pyc
|
|
18
|
+
.DS_Store
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Entry point for the "PyReprism Clone Gate" composite action (see action.yml).
|
|
3
|
+
# Installs PyReprism from the action's own checked-out source (so the CLI matches
|
|
4
|
+
# the pinned ref), runs `pyreprism clones` with the configured inputs, writes the
|
|
5
|
+
# report, and propagates the duplication-gate exit code.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
action_path="${GITHUB_ACTION_PATH:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
9
|
+
|
|
10
|
+
extras="accurate"
|
|
11
|
+
if [ "${INPUT_SEMANTIC:-false}" = "true" ]; then
|
|
12
|
+
extras="accurate,ast,semantic"
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
venv="${RUNNER_TEMP:-/tmp}/pyreprism-venv"
|
|
16
|
+
echo "::group::Install PyReprism [${extras}]"
|
|
17
|
+
python3 -m venv "$venv"
|
|
18
|
+
"$venv/bin/python" -m pip install --quiet --upgrade pip
|
|
19
|
+
"$venv/bin/python" -m pip install --quiet "${action_path}[${extras}]"
|
|
20
|
+
"$venv/bin/pyreprism" --version
|
|
21
|
+
echo "::endgroup::"
|
|
22
|
+
|
|
23
|
+
# Build the `pyreprism clones` argument list from the inputs.
|
|
24
|
+
args=(clones)
|
|
25
|
+
# Intentionally word-split PATHS and EXTRA_ARGS (space-separated inputs).
|
|
26
|
+
# shellcheck disable=SC2206
|
|
27
|
+
args+=(${INPUT_PATHS:-.})
|
|
28
|
+
args+=(--reporter "${INPUT_REPORTER:-sarif}")
|
|
29
|
+
args+=(--min-tokens "${INPUT_MIN_TOKENS:-50}" --min-lines "${INPUT_MIN_LINES:-5}")
|
|
30
|
+
[ -n "${INPUT_LANG:-}" ] && args+=(--lang "$INPUT_LANG")
|
|
31
|
+
[ "${INPUT_NEAR:-false}" = "true" ] && args+=(--near)
|
|
32
|
+
[ "${INPUT_SEMANTIC:-false}" = "true" ] && args+=(--semantic)
|
|
33
|
+
[ -n "${INPUT_MAX_DUPLICATION:-}" ] && args+=(--max-duplication "$INPUT_MAX_DUPLICATION")
|
|
34
|
+
# shellcheck disable=SC2206
|
|
35
|
+
[ -n "${INPUT_EXTRA_ARGS:-}" ] && args+=(${INPUT_EXTRA_ARGS})
|
|
36
|
+
|
|
37
|
+
out="${INPUT_OUTPUT:-}"
|
|
38
|
+
echo "::group::pyreprism ${args[*]}"
|
|
39
|
+
set +e
|
|
40
|
+
if [ -n "$out" ]; then
|
|
41
|
+
"$venv/bin/pyreprism" "${args[@]}" > "$out"
|
|
42
|
+
code=$?
|
|
43
|
+
cat "$out"
|
|
44
|
+
else
|
|
45
|
+
"$venv/bin/pyreprism" "${args[@]}"
|
|
46
|
+
code=$?
|
|
47
|
+
fi
|
|
48
|
+
set -e
|
|
49
|
+
echo "::endgroup::"
|
|
50
|
+
|
|
51
|
+
if [ -n "$out" ]; then
|
|
52
|
+
echo "report=$out" >> "${GITHUB_OUTPUT:-/dev/null}"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [ "$code" -ne 0 ]; then
|
|
56
|
+
echo "::error::PyReprism duplication gate failed (exit ${code})."
|
|
57
|
+
fi
|
|
58
|
+
exit "$code"
|
|
@@ -7,6 +7,97 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.0] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
The first stable release. The public API (the top-level functions, the
|
|
13
|
+
language classes, `Token`/`TokenType`, `CodeStats`, and the `batch`, `diffs`,
|
|
14
|
+
`fingerprints`, `ngrams`, `clones`, `reporters`, `syntactic`, and `semantic`
|
|
15
|
+
modules) and the `pyreprism` command line now follow semantic versioning:
|
|
16
|
+
breaking changes only in a new major version. This release adds located clone
|
|
17
|
+
detection with CI reports, optional syntactic and semantic tiers, and parallel
|
|
18
|
+
processing; makes tokenization several times faster; and fixes the lexer
|
|
19
|
+
defects found by validating token classes against tree-sitter parses on a
|
|
20
|
+
project-stratified sample of open-source code.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Located clones and reports** (`PyReprism.clones`, `PyReprism.reporters`):
|
|
24
|
+
`clones.detect()` finds which line ranges are duplicated between files
|
|
25
|
+
(Rabin-Karp over the normalized token stream, with jscpd-style
|
|
26
|
+
`min_tokens`/`min_lines` floors), optionally adds Type-3 near-duplicate files
|
|
27
|
+
(`near=True`) and Type-4 semantic clones (`semantic=True`), and reports the
|
|
28
|
+
percentage of duplicated lines. Reporters render console, JSON, SARIF (one
|
|
29
|
+
rule id per clone kind), Markdown, and a self-contained HTML page. CLI:
|
|
30
|
+
`pyreprism clones --reporter ... --max-duplication PCT` (non-zero exit above
|
|
31
|
+
the threshold, for CI).
|
|
32
|
+
- **Syntactic tier** (`PyReprism.syntactic`, `pip install "PyReprism[ast]"`):
|
|
33
|
+
tree-sitter ASTs for exact cyclomatic complexity and per-function metrics
|
|
34
|
+
(`cyclomatic()`, `functions()`).
|
|
35
|
+
- **Semantic tier** (`PyReprism.semantic`, `pip install "PyReprism[semantic]"`):
|
|
36
|
+
code embeddings for Type-4 clone detection (`embed()`, `similarity()`, and an
|
|
37
|
+
`EmbeddingIndex` mirroring `FingerprintIndex`).
|
|
38
|
+
- **GitHub Action** (`action.yml`, "PyReprism Clone Gate") and a `Dockerfile`
|
|
39
|
+
for the CLI, to gate pull requests on duplication.
|
|
40
|
+
- Parallel processing: `jobs=` on `batch.analyze`, `batch.transform`, and
|
|
41
|
+
`clones.detect`, and a new `batch.map_files(paths, fn, jobs=...)` that runs any
|
|
42
|
+
per-file function on all cores. `pyreprism scan` and `pyreprism clones` take
|
|
43
|
+
`-j/--jobs` and use one process per CPU by default (`-j 1` for one process).
|
|
44
|
+
Reports are identical for any number of jobs.
|
|
45
|
+
- Secondary file extensions map to their language: `.h` (C); `.cc`, `.cxx`,
|
|
46
|
+
`.hpp`, `.hh`, `.hxx` (C++); `.mjs`, `.cjs`, `.mts`, `.cts`; `.pyi`; `.bash`,
|
|
47
|
+
`.zsh`, `.ksh`; `.yml`; and others. These files were skipped before.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Faster tokenization (about 2.7x on one core) with identical tokens: each
|
|
51
|
+
language's string, number, identifier, and operator patterns are joined into
|
|
52
|
+
one pattern and compiled once, instead of being tried one by one at every
|
|
53
|
+
position; line numbers are tracked incrementally. `stats` (and `scan`)
|
|
54
|
+
count tokens as they are classified instead of building the token list
|
|
55
|
+
(about 4x faster than before on one core; same results). `code_metrics` computes
|
|
56
|
+
Halstead measures and complexity once instead of twice. Extension lookup no
|
|
57
|
+
longer re-imports every language module (1.4 ms to under 1 µs per file).
|
|
58
|
+
- The Pygments engine is about 3x faster on large files with the same tokens:
|
|
59
|
+
it merged tokens by repeated string concatenation and counted each token's
|
|
60
|
+
line from the start of the file, both quadratic in file size.
|
|
61
|
+
- `Token` is now an immutable `NamedTuple` instead of a frozen dataclass, which
|
|
62
|
+
makes tokens cheaper to create. Fields, attribute access, and immutability
|
|
63
|
+
are unchanged; a `Token` now also compares equal to a plain tuple with the
|
|
64
|
+
same values.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
- `pyreprism --help` stated "160+ built-in languages"; it now says 187, matching
|
|
68
|
+
`pyreprism languages`. A test keeps the two in sync.
|
|
69
|
+
- `pyreprism clones --max-duplication`: when stdout and stderr go to the same log
|
|
70
|
+
(as in CI), the gate message could be written into the middle of the report's
|
|
71
|
+
summary line. stdout is now flushed before the gate message.
|
|
72
|
+
- Defects found by validating token classes against tree-sitter parses:
|
|
73
|
+
- `remove_comments` dropped text that a language's comment regex skips (e.g.
|
|
74
|
+
the `/` in `#include "api/z3.h"`, a `{` in Scala). The base implementation
|
|
75
|
+
now keeps it, and 20 modules that duplicated the old loop inherit it.
|
|
76
|
+
- C, C++, Java, and CSS strings containing `/` were split, so `//` or `/*`
|
|
77
|
+
inside them started a comment (Java text blocks are kept whole too).
|
|
78
|
+
- Python: only a triple-quoted string that starts a line is a docstring
|
|
79
|
+
(comment); `x = """..."""` and call arguments are strings.
|
|
80
|
+
- JavaScript, TypeScript, JSX, TSX: template literals are strings, and
|
|
81
|
+
regular-expression literals no longer start comments.
|
|
82
|
+
- Pygments engine: C preprocessor directives were typed as comments, and
|
|
83
|
+
Python docstrings as strings; both now follow the regex engine's policies.
|
|
84
|
+
- Strings containing comment delimiters (`"a//b"`, `"a#b"`, `'--'`) were split
|
|
85
|
+
in 36 more languages, starting a comment inside the string: ActionScript,
|
|
86
|
+
Ada, AppleScript, Asm6502, ASP.NET, AutoHotkey, AutoIt, Bro, CoffeeScript,
|
|
87
|
+
Crystal, Elixir, Flow, GLSL, GraphQL, HPKP, HSTS, IchigoJam, Icon, Less,
|
|
88
|
+
LiveScript, Lua (including `[[...]]` long strings), Nginx, NSIS, PL/SQL, Qore,
|
|
89
|
+
Roboconf, Scheme (including `#\;` characters), SCSS (including unquoted
|
|
90
|
+
`url(...)`), and SQL. Comments now follow each language's rules in Apache
|
|
91
|
+
config, Dockerfiles, `.properties`, and Pug (own line only), Tcl (where a
|
|
92
|
+
command starts), YAML (after whitespace), and ERB (`<%# %>` tags, not `#` in
|
|
93
|
+
markup). A test checks every language with such a string.
|
|
94
|
+
- Defects found on the project-stratified evaluation sample: Go raw strings
|
|
95
|
+
(`` `...` ``) containing `//` or `/*` became comments; in Bash, `#` inside a
|
|
96
|
+
word (`$#`, `${#a}`, `a#b`) started a comment; a UTF-8 byte-order mark at the
|
|
97
|
+
start of a file was counted as code, so the first line could not be a comment
|
|
98
|
+
line (both engines); and a Python triple-quoted string on the line after an
|
|
99
|
+
opening bracket, comma, or `=` was taken for a docstring.
|
|
100
|
+
|
|
10
101
|
## [0.2.0] - 2026-07-08
|
|
11
102
|
|
|
12
103
|
This release adds a static code-analysis toolkit on top of the tokenizer:
|
|
@@ -112,7 +203,8 @@ ML-oriented normalization, an optional accurate backend, and batch processing.
|
|
|
112
203
|
- Early beta releases: comment removal for an initial set of languages and the
|
|
113
204
|
`Normalizer` whitespace helper.
|
|
114
205
|
|
|
115
|
-
[Unreleased]: https://github.com/unlv-evol/PyReprism/compare/
|
|
206
|
+
[Unreleased]: https://github.com/unlv-evol/PyReprism/compare/v1.0.0...HEAD
|
|
207
|
+
[1.0.0]: https://github.com/unlv-evol/PyReprism/compare/v0.2.0...v1.0.0
|
|
116
208
|
[0.2.0]: https://github.com/unlv-evol/PyReprism/compare/v0.1.0...v0.2.0
|
|
117
209
|
[0.1.0]: https://github.com/unlv-evol/PyReprism/releases/tag/v0.1.0
|
|
118
210
|
[0.0.4]: https://github.com/unlv-evol/PyReprism/releases/tag/v0.0.4
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# PyReprism CLI, containerized.
|
|
2
|
+
#
|
|
3
|
+
# docker build -t pyreprism .
|
|
4
|
+
# docker run --rm -v "$PWD:/work" pyreprism clones . --reporter sarif
|
|
5
|
+
# docker run --rm -v "$PWD:/work" pyreprism scan . --json
|
|
6
|
+
#
|
|
7
|
+
# The image ships the 187-language, zero-dependency lexical core plus the
|
|
8
|
+
# optional Pygments backend ([accurate], pure Python). The heavier [ast]
|
|
9
|
+
# (tree-sitter) and [semantic] (torch/transformers) tiers are intentionally
|
|
10
|
+
# left out to keep the image small; derive your own image if you need them.
|
|
11
|
+
|
|
12
|
+
# ---- build stage: produce a wheel from the source tree ----
|
|
13
|
+
FROM python:3.12-slim AS build
|
|
14
|
+
WORKDIR /src
|
|
15
|
+
# Only the files hatchling needs to build the wheel and read metadata.
|
|
16
|
+
COPY pyproject.toml LICENSE ./
|
|
17
|
+
COPY docs/intro.rst ./docs/intro.rst
|
|
18
|
+
COPY src ./src
|
|
19
|
+
RUN pip install --no-cache-dir build \
|
|
20
|
+
&& python -m build --wheel --outdir /dist
|
|
21
|
+
|
|
22
|
+
# ---- runtime stage: install the wheel, drop the build tooling ----
|
|
23
|
+
FROM python:3.12-slim
|
|
24
|
+
LABEL org.opencontainers.image.title="PyReprism" \
|
|
25
|
+
org.opencontainers.image.description="Lossless token substrate for polyglot code analysis (CLI)" \
|
|
26
|
+
org.opencontainers.image.source="https://github.com/unlv-evol/PyReprism" \
|
|
27
|
+
org.opencontainers.image.licenses="MIT"
|
|
28
|
+
COPY --from=build /dist/*.whl /tmp/
|
|
29
|
+
RUN pip install --no-cache-dir "$(ls /tmp/*.whl)[accurate]" \
|
|
30
|
+
&& rm -rf /tmp/*.whl /root/.cache/pip
|
|
31
|
+
# Run as an unprivileged user; /work is where the caller mounts their code.
|
|
32
|
+
RUN useradd --create-home --uid 1000 app
|
|
33
|
+
USER app
|
|
34
|
+
WORKDIR /work
|
|
35
|
+
ENTRYPOINT ["pyreprism"]
|
|
36
|
+
CMD ["--help"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: PyReprism
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Framework for Source Code Preprocessing
|
|
5
5
|
Project-URL: Homepage, https://github.com/unlv-evol/PyReprism
|
|
6
6
|
Project-URL: Documentation, https://pyreprism.readthedocs.io
|
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
|
28
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
29
|
SOFTWARE.
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Classifier: Development Status ::
|
|
31
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
32
32
|
Classifier: Environment :: Console
|
|
33
33
|
Classifier: Intended Audience :: Developers
|
|
34
34
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -44,6 +44,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
44
44
|
Requires-Python: >=3.8
|
|
45
45
|
Provides-Extra: accurate
|
|
46
46
|
Requires-Dist: pygments; extra == 'accurate'
|
|
47
|
+
Provides-Extra: ast
|
|
48
|
+
Requires-Dist: tree-sitter; extra == 'ast'
|
|
49
|
+
Requires-Dist: tree-sitter-language-pack; extra == 'ast'
|
|
47
50
|
Provides-Extra: dev
|
|
48
51
|
Requires-Dist: build; extra == 'dev'
|
|
49
52
|
Requires-Dist: flake8; extra == 'dev'
|
|
@@ -54,6 +57,9 @@ Requires-Dist: twine; extra == 'dev'
|
|
|
54
57
|
Provides-Extra: docs
|
|
55
58
|
Requires-Dist: sphinx; extra == 'docs'
|
|
56
59
|
Requires-Dist: sphinx-rtd-theme; extra == 'docs'
|
|
60
|
+
Provides-Extra: semantic
|
|
61
|
+
Requires-Dist: torch; extra == 'semantic'
|
|
62
|
+
Requires-Dist: transformers; extra == 'semantic'
|
|
57
63
|
Description-Content-Type: text/x-rst
|
|
58
64
|
|
|
59
65
|
.. _intro_toplevel:
|
|
@@ -64,9 +70,17 @@ Overview
|
|
|
64
70
|
|
|
65
71
|
**PyReprism** is a Python framework for source-code preprocessing. It lets you
|
|
66
72
|
**match, extract, count, and remove** comments, strings, numbers, operators,
|
|
67
|
-
keywords and other language-specific constructs across **
|
|
68
|
-
languages and file formats**
|
|
69
|
-
tool, code metrics, ML-oriented
|
|
73
|
+
keywords and other language-specific constructs across **187+ programming
|
|
74
|
+
languages and file formats** (500+ with the optional Pygments backend) — through a
|
|
75
|
+
small high-level API, a command-line tool, code metrics, ML-oriented
|
|
76
|
+
normalization, and diff/pull-request analysis.
|
|
77
|
+
|
|
78
|
+
Everything is derived from one lossless token stream, and the same substrate
|
|
79
|
+
anchors a **fidelity ladder** of optional-cost tiers: a zero-dependency
|
|
80
|
+
**lexical** tier (default), an optional **syntactic** tier (`tree-sitter ASTs
|
|
81
|
+
<https://pyreprism.readthedocs.io/en/latest/syntactic.html>`__ for precise per-function metrics),
|
|
82
|
+
and an optional **semantic** tier (`code embeddings
|
|
83
|
+
<https://pyreprism.readthedocs.io/en/latest/semantic.html>`__ for Type-4 clone detection).
|
|
70
84
|
|
|
71
85
|
Highlights
|
|
72
86
|
==========
|
|
@@ -75,11 +89,25 @@ Highlights
|
|
|
75
89
|
``count_*`` / ``match_*`` for each construct, plus a lossless ``tokenize()``.
|
|
76
90
|
* Code metrics (``stats``) and ML canonicalization (``normalize``) for
|
|
77
91
|
clone/plagiarism detection and code-embedding pipelines.
|
|
78
|
-
* Optional high-accuracy `Pygments`_ backend
|
|
79
|
-
dependencies** by default.
|
|
80
|
-
*
|
|
92
|
+
* Optional high-accuracy `Pygments`_ backend and an on-demand Pygments fallback
|
|
93
|
+
for 500+ languages — with **zero required dependencies** by default.
|
|
94
|
+
* Optional `syntactic (AST) <https://pyreprism.readthedocs.io/en/latest/syntactic.html>`__ and
|
|
95
|
+
`semantic (embedding) <https://pyreprism.readthedocs.io/en/latest/semantic.html>`__ tiers for
|
|
96
|
+
precise per-function metrics and Type-4 clone detection.
|
|
97
|
+
* Located clone detection with console, JSON, SARIF, Markdown, and HTML
|
|
98
|
+
reports and a duplication gate for CI (also as a GitHub Action).
|
|
99
|
+
* Batch/corpus analysis on every core, and unified/``git`` diff processing.
|
|
81
100
|
* A ``pyreprism`` command-line tool (also ``python -m PyReprism``).
|
|
82
101
|
|
|
102
|
+
Stability
|
|
103
|
+
=========
|
|
104
|
+
|
|
105
|
+
PyReprism 1.0 is the first stable release. From 1.0 on, releases follow
|
|
106
|
+
`semantic versioning`_: the public API and the ``pyreprism`` command line change
|
|
107
|
+
incompatibly only in a new major version. Modules whose names start with an
|
|
108
|
+
underscore are internal. Token classifications may still change in minor
|
|
109
|
+
releases when they fix a lexer defect; the changelog lists every such change.
|
|
110
|
+
|
|
83
111
|
Requirements
|
|
84
112
|
============
|
|
85
113
|
|
|
@@ -92,11 +120,13 @@ Install
|
|
|
92
120
|
|
|
93
121
|
pip install PyReprism
|
|
94
122
|
|
|
95
|
-
|
|
123
|
+
The optional tiers are opt-in extras (install only what you need):
|
|
96
124
|
|
|
97
125
|
.. code-block:: shell
|
|
98
126
|
|
|
99
|
-
pip install "PyReprism[accurate]"
|
|
127
|
+
pip install "PyReprism[accurate]" # Pygments backend (higher lexical fidelity)
|
|
128
|
+
pip install "PyReprism[ast]" # syntactic tier (tree-sitter)
|
|
129
|
+
pip install "PyReprism[semantic]" # semantic tier (code embeddings)
|
|
100
130
|
|
|
101
131
|
Quick start
|
|
102
132
|
===========
|
|
@@ -131,6 +161,7 @@ Command line
|
|
|
131
161
|
pyreprism remove comments file.py
|
|
132
162
|
pyreprism scan myproject/ --csv # aggregate code metrics over a tree
|
|
133
163
|
git diff | pyreprism diff --cosmetic # flag comment/whitespace-only changes
|
|
164
|
+
pyreprism clones src/ --max-duplication 5 # located clones; fail above 5%
|
|
134
165
|
|
|
135
166
|
Documentation
|
|
136
167
|
=============
|
|
@@ -156,3 +187,4 @@ See ``CONTRIBUTING.md`` for contribution guidelines.
|
|
|
156
187
|
|
|
157
188
|
.. _Python: https://www.python.org
|
|
158
189
|
.. _Pygments: https://pygments.org
|
|
190
|
+
.. _semantic versioning: https://semver.org
|
|
@@ -13,12 +13,68 @@
|
|
|
13
13
|
|
|
14
14
|
# PyReprism
|
|
15
15
|
|
|
16
|
-
PyReprism is a Python framework that helps researchers and developers the task of source code preprocessing. With PyReprism, you can easily match, extract, count, and remove comments, whitespaces, operators, numbers and other language specific constructs from over **
|
|
16
|
+
PyReprism is a Python framework that helps researchers and developers the task of source code preprocessing. With PyReprism, you can easily match, extract, count, and remove comments, whitespaces, operators, numbers and other language specific constructs from over **187 programming languages and file extensions** — and **500+** with the optional Pygments backend.
|
|
17
|
+
|
|
18
|
+
Everything is derived from one **lossless token stream**, and the same substrate
|
|
19
|
+
anchors a **fidelity ladder** of three optional-cost tiers: a zero-dependency
|
|
20
|
+
**lexical** tier (default), an optional **syntactic** tier (tree-sitter ASTs for
|
|
21
|
+
precise per-function metrics), and an optional **semantic** tier (code embeddings
|
|
22
|
+
for Type-4 clone detection). The core stays dependency-free; you climb the ladder
|
|
23
|
+
only when a task needs it.
|
|
17
24
|
|
|
18
25
|
## Install
|
|
19
26
|
```shell
|
|
20
|
-
pip install PyReprism
|
|
27
|
+
pip install PyReprism # zero-dependency lexical core
|
|
28
|
+
|
|
29
|
+
# optional tiers (install only what you need):
|
|
30
|
+
pip install "PyReprism[accurate]" # Pygments backend (higher lexical fidelity)
|
|
31
|
+
pip install "PyReprism[ast]" # syntactic tier (tree-sitter)
|
|
32
|
+
pip install "PyReprism[semantic]" # semantic tier (code embeddings)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Run with Docker
|
|
36
|
+
|
|
37
|
+
No Python install required. The image ships the 187-language lexical core plus
|
|
38
|
+
the Pygments backend (the heavier `[ast]`/`[semantic]` tiers are omitted to keep
|
|
39
|
+
it small). Mount your code at `/work`:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
docker build -t pyreprism .
|
|
43
|
+
|
|
44
|
+
# fail CI above 5% duplication, emit SARIF for code scanning
|
|
45
|
+
docker run --rm -v "$PWD:/work" pyreprism clones . --reporter sarif --max-duplication 5
|
|
46
|
+
|
|
47
|
+
# per-language metrics as JSON
|
|
48
|
+
docker run --rm -v "$PWD:/work" pyreprism scan . --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### GitHub Action
|
|
52
|
+
|
|
53
|
+
Gate pull requests on code duplication in a few lines. The action runs
|
|
54
|
+
`pyreprism clones`, writes a report, and fails the job when duplication exceeds
|
|
55
|
+
your threshold:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- uses: unlv-evol/PyReprism@v1.0.0 # pin to a release tag or commit SHA
|
|
60
|
+
with:
|
|
61
|
+
paths: .
|
|
62
|
+
reporter: sarif
|
|
63
|
+
output: pyreprism-clones.sarif
|
|
64
|
+
max_duplication: 5 # fail the job above 5% duplicated lines
|
|
65
|
+
near: true # also flag Type-3 near-duplicate files
|
|
66
|
+
|
|
67
|
+
# optional: surface the clones in the repo's Security > Code scanning tab
|
|
68
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
69
|
+
if: always()
|
|
70
|
+
with:
|
|
71
|
+
sarif_file: pyreprism-clones.sarif
|
|
21
72
|
```
|
|
73
|
+
|
|
74
|
+
Inputs: `paths`, `reporter` (`console`/`json`/`sarif`/`markdown`/`html`),
|
|
75
|
+
`output`, `max_duplication`, `min_tokens`, `min_lines`, `near`, `semantic`,
|
|
76
|
+
`lang`, and `extra_args`.
|
|
77
|
+
|
|
22
78
|
## Quick Usage
|
|
23
79
|
|
|
24
80
|
The simplest entry point is the top-level API. Pass `lang` as a language name
|
|
@@ -117,6 +173,52 @@ index.similar_pairs(threshold=0.7) # -> [(file_a, file_b, score), ...]
|
|
|
117
173
|
On the CLI: `pyreprism similarity a.py b.py` and
|
|
118
174
|
`pyreprism clones submissions/ --threshold 0.7`.
|
|
119
175
|
|
|
176
|
+
#### Located clones & reports (a jscpd-style duplication detector)
|
|
177
|
+
|
|
178
|
+
Beyond a similarity score, `PyReprism.clones` reports **which line ranges are
|
|
179
|
+
duplicated** between files (Rabin-Karp over the normalized token stream), and
|
|
180
|
+
renders a **unified report** across all three rungs of the fidelity ladder:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from PyReprism import clones, reporters
|
|
184
|
+
|
|
185
|
+
report = clones.detect("src/",
|
|
186
|
+
min_tokens=50, min_lines=5, # clone-size floor (jscpd-style)
|
|
187
|
+
near=True, # + Type-3 near-duplicate files
|
|
188
|
+
semantic=True) # + Type-4 semantic clones
|
|
189
|
+
report.duplication_pct # % duplicated lines
|
|
190
|
+
for c in report.clones:
|
|
191
|
+
print(c.kind, c.a.path, c.a.start_line, c.b.path, c.b.start_line)
|
|
192
|
+
# kind is 'exact' (T1/2, located), 'near' (T3), or 'semantic' (T4)
|
|
193
|
+
|
|
194
|
+
print(reporters.to_sarif(report)) # GitHub code scanning
|
|
195
|
+
print(reporters.to_markdown(report)) # PR comment
|
|
196
|
+
print(reporters.to_json(report))
|
|
197
|
+
print(reporters.to_html(report)) # self-contained HTML report page
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Every clone — **including semantic (Type-4)** ones — flows through the same
|
|
201
|
+
reporters, each kind tagged with its own SARIF rule id
|
|
202
|
+
(`pyreprism/exact-clone`, `.../near-clone`, `.../semantic-clone`), so CI can treat
|
|
203
|
+
lexical and semantic duplication separately. The **HTML reporter** renders a
|
|
204
|
+
self-contained, theme-aware page — a duplication meter with a CI-gate marker,
|
|
205
|
+
per-kind counts, and a filterable card per clone with located line ranges,
|
|
206
|
+
score, rule id, and a highlighted side-by-side snippet. On the CLI:
|
|
207
|
+
|
|
208
|
+
```shell
|
|
209
|
+
pyreprism clones src/ # located exact clones (console)
|
|
210
|
+
pyreprism clones src/ --near --semantic # add Type-3 and Type-4
|
|
211
|
+
pyreprism clones src/ --reporter sarif > clones.sarif # for GitHub code scanning
|
|
212
|
+
pyreprism clones src/ --reporter html --max-duplication 5 > clones.html # HTML report + CI gate
|
|
213
|
+
pyreprism clones src/ --max-duplication 5 # exit non-zero above 5% (CI gate)
|
|
214
|
+
pyreprism clones src/ -j 1 # one process (default: one per CPU)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Fingerprints and located clones detect Type-1/2/3. For **Type-4** clones (same
|
|
218
|
+
behavior, different code), `--semantic` embeds functions with the optional
|
|
219
|
+
[semantic tier](#higher-fidelity-tiers-syntactic-ast--semantic-embeddings) and
|
|
220
|
+
adds them to the same report.
|
|
221
|
+
|
|
120
222
|
### N-grams & code "naturalness"
|
|
121
223
|
|
|
122
224
|
Token n-grams (over token text or, structurally, over token *types*) and an
|
|
@@ -173,6 +275,47 @@ installed, else regex). It works on every operation — `remove_*`, `extract_*`,
|
|
|
173
275
|
`count_*`, `tokenize`, `normalize`, `stats`, `preprocess` — and on the CLI via
|
|
174
276
|
`--engine`.
|
|
175
277
|
|
|
278
|
+
When Pygments is installed, a language or extension that has no built-in module
|
|
279
|
+
also resolves through an on-demand Pygments fallback, extending coverage to
|
|
280
|
+
**500+** languages (`pr.get_language(".zig")`, `pr.remove_comments(src, lang="solidity")`).
|
|
281
|
+
List what is reachable with `pyreprism languages` (built-in) and
|
|
282
|
+
`pyreprism languages --pygments` (the fallback tier).
|
|
283
|
+
|
|
284
|
+
### Higher-fidelity tiers: syntactic (AST) & semantic (embeddings)
|
|
285
|
+
|
|
286
|
+
The lexical core is fast and broad but approximate. For tasks that need more, the
|
|
287
|
+
same substrate feeds two optional tiers — install only what you need.
|
|
288
|
+
|
|
289
|
+
**Syntactic tier** (`pip install "PyReprism[ast]"`) parses a real tree-sitter AST
|
|
290
|
+
for exact, **per-function** metrics that a token stream cannot provide:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from PyReprism import syntactic as ast
|
|
294
|
+
|
|
295
|
+
ast.cyclomatic(src, "java") # precise McCabe complexity (decision nodes)
|
|
296
|
+
ast.functions(src, "python") # -> [FunctionInfo(name, start_line, cyclomatic), ...]
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Semantic tier** (`pip install "PyReprism[semantic]"`) embeds code with a
|
|
300
|
+
code-search transformer to catch **Type-4** clones (same behavior, different code)
|
|
301
|
+
that token fingerprints cannot — it mirrors the `FingerprintIndex` API:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
from PyReprism import semantic
|
|
305
|
+
|
|
306
|
+
semantic.similarity(code_a, code_b) # cosine similarity in embedding space
|
|
307
|
+
|
|
308
|
+
idx = semantic.EmbeddingIndex()
|
|
309
|
+
idx.add_paths("submissions/")
|
|
310
|
+
idx.similar_pairs(threshold=0.8) # semantic near-duplicate pairs
|
|
311
|
+
idx.query(code, k=5) # k most similar indexed snippets
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Both tiers degrade gracefully: if the extra isn't installed, `available()` returns
|
|
315
|
+
`False` and the lexical core is unaffected. Use the lexical tier for screening,
|
|
316
|
+
the syntactic tier for precise/per-function metrics, and the semantic tier for
|
|
317
|
+
Type-4 clones.
|
|
318
|
+
|
|
176
319
|
### Batch / whole-directory processing
|
|
177
320
|
|
|
178
321
|
Analyze or transform an entire source tree (junk folders like `.git`,
|
|
@@ -191,6 +334,20 @@ batch.transform("myproject/", lambda text, lang: lang.remove_comments(text),
|
|
|
191
334
|
output="stripped/")
|
|
192
335
|
```
|
|
193
336
|
|
|
337
|
+
Files are independent, so these calls can use every core: pass `jobs=0` (one
|
|
338
|
+
process per CPU) or `jobs=N` to `batch.analyze`, `batch.transform`,
|
|
339
|
+
`batch.map_files`, and `clones.detect`; results are identical to a serial run.
|
|
340
|
+
With `jobs` other than 1, functions you pass must be picklable (defined at
|
|
341
|
+
module level, not lambdas):
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
def metrics_and_copy(text, lang): # any per-file analysis
|
|
345
|
+
return lang.code_metrics(text), lang.normalize(text)
|
|
346
|
+
|
|
347
|
+
for path, lang, (metrics, normalized) in batch.map_files("myproject/", metrics_and_copy, jobs=0):
|
|
348
|
+
...
|
|
349
|
+
```
|
|
350
|
+
|
|
194
351
|
### Process diffs / pull requests
|
|
195
352
|
|
|
196
353
|
Parse a unified (`git`) diff and analyze the *changed code* per file, language-aware:
|
|
@@ -258,7 +415,8 @@ pyreprism normalize file.py # canonicalize for ML
|
|
|
258
415
|
pyreprism remove comments file.py --in-place # rewrite in place
|
|
259
416
|
pyreprism scan myproject/ --csv # aggregate metrics over a tree
|
|
260
417
|
pyreprism remove comments src/ --output out/ # bulk-transform a directory
|
|
261
|
-
pyreprism languages # list
|
|
418
|
+
pyreprism languages # list built-in languages
|
|
419
|
+
pyreprism languages --pygments # list the Pygments fallback tier
|
|
262
420
|
```
|
|
263
421
|
|
|
264
422
|
Equivalently: `python -m PyReprism ...`.
|
|
@@ -267,9 +425,29 @@ It can also be wired into [pre-commit](https://pre-commit.com) via the bundled
|
|
|
267
425
|
`.pre-commit-hooks.yaml` (`pyreprism-strip-comments`, `pyreprism-normalize-whitespace`).
|
|
268
426
|
These hooks rewrite files, so scope them with `files:`/`exclude:`.
|
|
269
427
|
|
|
270
|
-
Read the [docs](https://pyreprism.readthedocs.io) for more usage examples.
|
|
271
|
-
|
|
272
|
-
|
|
428
|
+
Read the [docs](https://pyreprism.readthedocs.io) for more usage examples.
|
|
429
|
+
|
|
430
|
+
## Stability and versioning
|
|
431
|
+
|
|
432
|
+
PyReprism 1.0 is the first stable release. From 1.0 on, releases follow
|
|
433
|
+
[semantic versioning](https://semver.org): the public API (the top-level
|
|
434
|
+
functions, the language classes, `Token`/`TokenType`, `CodeStats`, the
|
|
435
|
+
`batch`, `diffs`, `fingerprints`, `ngrams`, `clones`, `reporters`, `syntactic`,
|
|
436
|
+
and `semantic` modules) and the `pyreprism` command line change incompatibly
|
|
437
|
+
only in a new major version. Modules whose names start with an underscore are
|
|
438
|
+
internal. Token classifications may still change in minor releases when they
|
|
439
|
+
fix a lexer defect; the [changelog](CHANGELOG.md) lists every such change.
|
|
440
|
+
|
|
441
|
+
## Performance
|
|
442
|
+
|
|
443
|
+
Tokenization is a single compiled pattern per language, and directory-level
|
|
444
|
+
work can use every core: `pyreprism scan` and `pyreprism clones` do so by
|
|
445
|
+
default (`-j N` to change it), and the Python API takes `jobs=` (`jobs=0` for
|
|
446
|
+
one process per CPU).
|
|
447
|
+
On an 8-core Apple M3, `pyreprism scan` processes about 2,000 files per second;
|
|
448
|
+
a lexical workflow that replaces cloc, lizard, and jscpd (line counts,
|
|
449
|
+
complexity, and clones over the same files) runs 3.0x faster on one core
|
|
450
|
+
and 5.7x faster on 8 cores than the three tools together.
|
|
273
451
|
|
|
274
452
|
## How to Contribute
|
|
275
453
|
We invite you to help us build this tool and make it more extensive. Contribution is open to OSS community.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: 'PyReprism Clone Gate'
|
|
2
|
+
description: >-
|
|
3
|
+
Detect duplicate/clone code across 187 languages and fail CI above a
|
|
4
|
+
duplication threshold. Emits console/JSON/SARIF/Markdown/HTML reports.
|
|
5
|
+
author: 'UNLV EVOL Lab'
|
|
6
|
+
branding:
|
|
7
|
+
icon: 'copy'
|
|
8
|
+
color: 'orange'
|
|
9
|
+
|
|
10
|
+
inputs:
|
|
11
|
+
paths:
|
|
12
|
+
description: 'Paths to scan (space-separated).'
|
|
13
|
+
required: false
|
|
14
|
+
default: '.'
|
|
15
|
+
reporter:
|
|
16
|
+
description: 'Output format: console, json, sarif, markdown, or html.'
|
|
17
|
+
required: false
|
|
18
|
+
default: 'sarif'
|
|
19
|
+
output:
|
|
20
|
+
description: >-
|
|
21
|
+
File (relative to the workspace) to write the report to. Leave empty to
|
|
22
|
+
print to the job log only.
|
|
23
|
+
required: false
|
|
24
|
+
default: 'pyreprism-clones.sarif'
|
|
25
|
+
max_duplication:
|
|
26
|
+
description: >-
|
|
27
|
+
Fail the job if the percentage of duplicated lines exceeds this value.
|
|
28
|
+
Empty disables the gate.
|
|
29
|
+
required: false
|
|
30
|
+
default: ''
|
|
31
|
+
min_tokens:
|
|
32
|
+
description: 'Minimum clone size in tokens.'
|
|
33
|
+
required: false
|
|
34
|
+
default: '50'
|
|
35
|
+
min_lines:
|
|
36
|
+
description: 'Minimum clone size in lines.'
|
|
37
|
+
required: false
|
|
38
|
+
default: '5'
|
|
39
|
+
near:
|
|
40
|
+
description: 'Also report Type-3 near-duplicate files.'
|
|
41
|
+
required: false
|
|
42
|
+
default: 'false'
|
|
43
|
+
semantic:
|
|
44
|
+
description: >-
|
|
45
|
+
Also report Type-4 semantic clones. Installs the heavier semantic/ast
|
|
46
|
+
tiers, which take longer to set up.
|
|
47
|
+
required: false
|
|
48
|
+
default: 'false'
|
|
49
|
+
lang:
|
|
50
|
+
description: 'Force a single language instead of auto-detecting per file.'
|
|
51
|
+
required: false
|
|
52
|
+
default: ''
|
|
53
|
+
extra_args:
|
|
54
|
+
description: 'Additional raw arguments appended to `pyreprism clones`.'
|
|
55
|
+
required: false
|
|
56
|
+
default: ''
|
|
57
|
+
|
|
58
|
+
outputs:
|
|
59
|
+
report:
|
|
60
|
+
description: 'Path to the written report file (empty if none).'
|
|
61
|
+
value: ${{ steps.pyreprism.outputs.report }}
|
|
62
|
+
|
|
63
|
+
runs:
|
|
64
|
+
using: 'composite'
|
|
65
|
+
steps:
|
|
66
|
+
- id: pyreprism
|
|
67
|
+
shell: bash
|
|
68
|
+
run: bash "$GITHUB_ACTION_PATH/.github/pyreprism-action.sh"
|
|
69
|
+
env:
|
|
70
|
+
INPUT_PATHS: ${{ inputs.paths }}
|
|
71
|
+
INPUT_REPORTER: ${{ inputs.reporter }}
|
|
72
|
+
INPUT_OUTPUT: ${{ inputs.output }}
|
|
73
|
+
INPUT_MAX_DUPLICATION: ${{ inputs.max_duplication }}
|
|
74
|
+
INPUT_MIN_TOKENS: ${{ inputs.min_tokens }}
|
|
75
|
+
INPUT_MIN_LINES: ${{ inputs.min_lines }}
|
|
76
|
+
INPUT_NEAR: ${{ inputs.near }}
|
|
77
|
+
INPUT_SEMANTIC: ${{ inputs.semantic }}
|
|
78
|
+
INPUT_LANG: ${{ inputs.lang }}
|
|
79
|
+
INPUT_EXTRA_ARGS: ${{ inputs.extra_args }}
|