srcly 0.1.14__tar.gz → 0.1.17__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.
- {srcly-0.1.14 → srcly-0.1.17}/PKG-INFO +5 -1
- {srcly-0.1.14 → srcly-0.1.17}/app/models.py +6 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/routers/analysis.py +11 -3
- {srcly-0.1.14 → srcly-0.1.17}/app/routers/files.py +4 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/services/analysis.py +117 -7
- srcly-0.1.17/app/services/analysis_types.py +61 -0
- srcly-0.1.17/app/services/css/css_analysis.py +232 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/services/data_flow_analysis.py +1 -1
- srcly-0.1.17/app/services/ipynb/ipynb_analysis.py +129 -0
- srcly-0.1.17/app/services/markdown/markdown_analysis.py +414 -0
- srcly-0.1.17/app/services/python/python_analysis.py +249 -0
- srcly-0.1.14/app/services/tree_sitter_analysis.py → srcly-0.1.17/app/services/typescript/typescript_analysis.py +2 -52
- srcly-0.1.17/app/static/assets/index-BuiLB-f3.js +37 -0
- srcly-0.1.17/app/static/assets/index-lgAeJI5G.css +1 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/index.html +2 -2
- {srcly-0.1.14 → srcly-0.1.17}/pyproject.toml +5 -1
- srcly-0.1.14/app/static/assets/index-CTNf0zED.css +0 -1
- srcly-0.1.14/app/static/assets/index-DzyR2Ke2.js +0 -37
- {srcly-0.1.14 → srcly-0.1.17}/.gitignore +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/README.md +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/.gitignore +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/__init__.py +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/config.py +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/main.py +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/run.py +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/services/cache.py +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.17}/app/static/vite.svg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: srcly
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: Interactive codebase treemap and metrics viewer.
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: fastapi>=0.121.3
|
|
@@ -9,6 +9,10 @@ Requires-Dist: lizard>=1.19.0
|
|
|
9
9
|
Requires-Dist: pathspec>=0.12.1
|
|
10
10
|
Requires-Dist: pydantic>=2.12.4
|
|
11
11
|
Requires-Dist: pytest>=9.0.1
|
|
12
|
+
Requires-Dist: tree-sitter-css==0.25.0
|
|
13
|
+
Requires-Dist: tree-sitter-markdown<0.6.0,>=0.5.1
|
|
14
|
+
Requires-Dist: tree-sitter-python==0.25.0
|
|
15
|
+
Requires-Dist: tree-sitter-scss==1.0.0
|
|
12
16
|
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
13
17
|
Requires-Dist: tree-sitter>=0.25.2
|
|
14
18
|
Requires-Dist: uvicorn>=0.38.0
|
|
@@ -34,6 +34,12 @@ class Metrics(BaseModel):
|
|
|
34
34
|
tsx_duplicated_string_count: int = 0
|
|
35
35
|
ts_type_interface_count: int = 0
|
|
36
36
|
ts_export_count: int = 0
|
|
37
|
+
|
|
38
|
+
# Python-specific metrics
|
|
39
|
+
python_import_count: int = 0
|
|
40
|
+
|
|
41
|
+
# Markdown-specific metrics
|
|
42
|
+
md_data_url_count: int = 0
|
|
37
43
|
|
|
38
44
|
class Node(BaseModel):
|
|
39
45
|
name: str
|
|
@@ -5,7 +5,8 @@ import os
|
|
|
5
5
|
from typing import Dict, List, Optional, Tuple
|
|
6
6
|
|
|
7
7
|
from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
|
|
8
|
-
from app.services import analysis, cache
|
|
8
|
+
from app.services import analysis, cache
|
|
9
|
+
from app.services.typescript import typescript_analysis
|
|
9
10
|
from app.config import IGNORE_DIRS
|
|
10
11
|
|
|
11
12
|
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
|
|
@@ -333,7 +334,7 @@ async def get_dependencies(path: str = None):
|
|
|
333
334
|
if not target_path.exists():
|
|
334
335
|
raise HTTPException(status_code=404, detail="Path not found")
|
|
335
336
|
|
|
336
|
-
analyzer =
|
|
337
|
+
analyzer = typescript_analysis.TreeSitterAnalyzer()
|
|
337
338
|
nodes = []
|
|
338
339
|
edges = []
|
|
339
340
|
|
|
@@ -570,7 +571,14 @@ async def get_analysis_context():
|
|
|
570
571
|
load, for both the current working directory and the repository root.
|
|
571
572
|
"""
|
|
572
573
|
current_root = ROOT_PATH
|
|
573
|
-
|
|
574
|
+
# Use the same repository root detection logic as the analysis layer so that
|
|
575
|
+
# the client sees a consistent "repo root" regardless of how the server was
|
|
576
|
+
# started (uvx, direct CLI, dev mode, etc.).
|
|
577
|
+
#
|
|
578
|
+
# This mirrors the behaviour of the CLI helper that walks upwards from the
|
|
579
|
+
# current directory to the enclosing `.git` root, falling back to the
|
|
580
|
+
# starting directory when no repository is found.
|
|
581
|
+
repo_root = analysis.find_repo_root(current_root)
|
|
574
582
|
|
|
575
583
|
current_file_count, current_folder_count = _estimate_counts(current_root)
|
|
576
584
|
|
|
@@ -2,6 +2,8 @@ from fastapi import APIRouter, HTTPException, Query
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from fastapi.responses import PlainTextResponse
|
|
4
4
|
|
|
5
|
+
from app.services.analysis import get_ipynb_analyzer
|
|
6
|
+
|
|
5
7
|
router = APIRouter(prefix="/api/files", tags=["files"])
|
|
6
8
|
|
|
7
9
|
@router.get("/content", response_class=PlainTextResponse)
|
|
@@ -21,6 +23,8 @@ async def get_file_content(path: str = Query(..., description="Absolute path to
|
|
|
21
23
|
# For this local tool, we'll allow reading any file as requested, but maybe warn?
|
|
22
24
|
|
|
23
25
|
try:
|
|
26
|
+
if file_path.suffix == ".ipynb":
|
|
27
|
+
return get_ipynb_analyzer().get_virtual_content(str(file_path))
|
|
24
28
|
return file_path.read_text(encoding="utf-8")
|
|
25
29
|
except Exception as e:
|
|
26
30
|
raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}")
|
|
@@ -5,10 +5,20 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
from app.models import Node, Metrics
|
|
7
7
|
from app.config import IGNORE_DIRS, IGNORE_FILES, IGNORE_EXTENSIONS
|
|
8
|
-
from app.services.
|
|
8
|
+
from app.services.typescript.typescript_analysis import TreeSitterAnalyzer
|
|
9
|
+
from app.services.markdown.markdown_analysis import MarkdownTreeSitterAnalyzer
|
|
10
|
+
from app.services.ipynb.ipynb_analysis import NotebookAnalyzer
|
|
11
|
+
from app.services.css.css_analysis import CssTreeSitterAnalyzer
|
|
9
12
|
from pathspec import PathSpec
|
|
10
13
|
|
|
14
|
+
# Maximum time allowed for analyzing a single file in a worker process.
|
|
15
|
+
PER_FILE_ANALYSIS_TIMEOUT_SECONDS: float = 10.0
|
|
16
|
+
|
|
11
17
|
_ts_analyzer = None
|
|
18
|
+
_md_analyzer = None
|
|
19
|
+
_ipynb_analyzer = None
|
|
20
|
+
_css_analyzer = None
|
|
21
|
+
|
|
12
22
|
|
|
13
23
|
def get_ts_analyzer():
|
|
14
24
|
global _ts_analyzer
|
|
@@ -16,6 +26,35 @@ def get_ts_analyzer():
|
|
|
16
26
|
_ts_analyzer = TreeSitterAnalyzer()
|
|
17
27
|
return _ts_analyzer
|
|
18
28
|
|
|
29
|
+
|
|
30
|
+
def get_md_analyzer():
|
|
31
|
+
global _md_analyzer
|
|
32
|
+
if _md_analyzer is None:
|
|
33
|
+
_md_analyzer = MarkdownTreeSitterAnalyzer()
|
|
34
|
+
return _md_analyzer
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_ipynb_analyzer():
|
|
38
|
+
global _ipynb_analyzer
|
|
39
|
+
if _ipynb_analyzer is None:
|
|
40
|
+
_ipynb_analyzer = NotebookAnalyzer()
|
|
41
|
+
return _ipynb_analyzer
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_css_analyzer():
|
|
45
|
+
global _css_analyzer
|
|
46
|
+
if _css_analyzer is None:
|
|
47
|
+
_css_analyzer = CssTreeSitterAnalyzer()
|
|
48
|
+
return _css_analyzer
|
|
49
|
+
|
|
50
|
+
_python_analyzer = None
|
|
51
|
+
def get_python_analyzer():
|
|
52
|
+
global _python_analyzer
|
|
53
|
+
if _python_analyzer is None:
|
|
54
|
+
from app.services.python.python_analysis import PythonTreeSitterAnalyzer
|
|
55
|
+
_python_analyzer = PythonTreeSitterAnalyzer()
|
|
56
|
+
return _python_analyzer
|
|
57
|
+
|
|
19
58
|
def find_repo_root(start_path: Path) -> Path:
|
|
20
59
|
current = start_path.resolve()
|
|
21
60
|
for parent in [current, *current.parents]:
|
|
@@ -54,6 +93,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
54
93
|
node.metrics.tsx_duplicated_string_count = file_info.tsx_duplicated_string_count
|
|
55
94
|
node.metrics.ts_type_interface_count = file_info.ts_type_interface_count
|
|
56
95
|
node.metrics.ts_export_count = file_info.ts_export_count
|
|
96
|
+
# Markdown-specific metrics
|
|
97
|
+
if hasattr(file_info, "md_data_url_count"):
|
|
98
|
+
node.metrics.md_data_url_count = file_info.md_data_url_count
|
|
57
99
|
|
|
58
100
|
# New metrics
|
|
59
101
|
if hasattr(file_info, 'comment_lines'):
|
|
@@ -65,6 +107,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
65
107
|
node.metrics.todo_count = file_info.todo_count
|
|
66
108
|
node.metrics.classes_count = file_info.classes_count
|
|
67
109
|
|
|
110
|
+
if hasattr(file_info, 'python_import_count'):
|
|
111
|
+
node.metrics.python_import_count = file_info.python_import_count
|
|
112
|
+
|
|
68
113
|
# Set start/end line for the file (approximate, 1 to total lines)
|
|
69
114
|
# We don't strictly have this from lizard always, but we can infer or leave 0.
|
|
70
115
|
# For now, let's leave file start/end as 0 unless we want to read the file.
|
|
@@ -125,6 +170,7 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
125
170
|
func_node.metrics.comment_lines = getattr(func, 'comment_lines', 0)
|
|
126
171
|
func_node.metrics.todo_count = getattr(func, 'todo_count', 0)
|
|
127
172
|
func_node.metrics.ts_type_interface_count = getattr(func, 'ts_type_interface_count', 0)
|
|
173
|
+
func_node.metrics.md_data_url_count = getattr(func, 'md_data_url_count', 0)
|
|
128
174
|
|
|
129
175
|
# Density for function
|
|
130
176
|
comment_lines = getattr(func, 'comment_lines', 0)
|
|
@@ -266,6 +312,36 @@ def _is_gitignored(path: Path, ignore_root: Path, spec: PathSpec | None) -> bool
|
|
|
266
312
|
rel_str = rel.as_posix()
|
|
267
313
|
return spec.match_file(rel_str)
|
|
268
314
|
|
|
315
|
+
|
|
316
|
+
def _should_log_file_progress(completed_count: int, total_count: int) -> bool:
|
|
317
|
+
"""
|
|
318
|
+
Decide whether to emit a per-file progress log.
|
|
319
|
+
|
|
320
|
+
Rules:
|
|
321
|
+
- If there are fewer than 10 files, log every file.
|
|
322
|
+
- Otherwise:
|
|
323
|
+
- Log the first 10 files.
|
|
324
|
+
- Log at 15 and 20.
|
|
325
|
+
- Log every 25th file after that (25, 50, 75, ...).
|
|
326
|
+
- Always log the final file when done.
|
|
327
|
+
"""
|
|
328
|
+
if total_count < 10:
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
if completed_count <= 10:
|
|
332
|
+
return True
|
|
333
|
+
|
|
334
|
+
if completed_count in (15, 20):
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
if completed_count % 25 == 0:
|
|
338
|
+
return True
|
|
339
|
+
|
|
340
|
+
if completed_count == total_count:
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
return False
|
|
344
|
+
|
|
269
345
|
def aggregate_metrics(node: Node) -> Metrics:
|
|
270
346
|
if not node.children: return node.metrics
|
|
271
347
|
|
|
@@ -281,6 +357,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
281
357
|
total_classes_count = 0
|
|
282
358
|
total_type_interface_count = 0
|
|
283
359
|
total_export_count = 0
|
|
360
|
+
total_md_data_url_count = 0
|
|
284
361
|
|
|
285
362
|
# For average function length, we need total function loc and total functions (already have total_funcs)
|
|
286
363
|
# But we need to sum function locs from children.
|
|
@@ -301,6 +378,13 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
301
378
|
total_classes_count += child_metrics.classes_count
|
|
302
379
|
total_type_interface_count += child_metrics.ts_type_interface_count
|
|
303
380
|
total_export_count += child_metrics.ts_export_count
|
|
381
|
+
total_md_data_url_count += child_metrics.md_data_url_count
|
|
382
|
+
if hasattr(child_metrics, 'python_import_count'):
|
|
383
|
+
# Metric model has it, but child_metrics is a Metrics object so it should have it
|
|
384
|
+
total_python_import_count = getattr(child_metrics, 'python_import_count', 0)
|
|
385
|
+
# Should we add a local var for this? Yes
|
|
386
|
+
pass
|
|
387
|
+
|
|
304
388
|
|
|
305
389
|
# Reconstruct total function loc from average * count
|
|
306
390
|
total_function_loc += (child_metrics.average_function_length * child_metrics.function_count)
|
|
@@ -326,7 +410,10 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
326
410
|
node.metrics.classes_count = total_classes_count
|
|
327
411
|
node.metrics.average_function_length = total_function_loc / total_funcs if total_funcs > 0 else 0.0
|
|
328
412
|
node.metrics.ts_type_interface_count = total_type_interface_count
|
|
413
|
+
node.metrics.ts_type_interface_count = total_type_interface_count
|
|
329
414
|
node.metrics.ts_export_count = total_export_count
|
|
415
|
+
node.metrics.md_data_url_count = total_md_data_url_count
|
|
416
|
+
node.metrics.python_import_count = total_python_import_count
|
|
330
417
|
|
|
331
418
|
return node.metrics
|
|
332
419
|
|
|
@@ -336,11 +423,25 @@ def analyze_single_file(file_path: str):
|
|
|
336
423
|
Must be top-level for multiprocessing pickling.
|
|
337
424
|
"""
|
|
338
425
|
try:
|
|
339
|
-
if file_path.endswith(
|
|
426
|
+
if file_path.endswith(".ts") or file_path.endswith(".tsx"):
|
|
340
427
|
analyzer = get_ts_analyzer()
|
|
341
428
|
return analyzer.analyze_file(file_path)
|
|
342
|
-
|
|
343
|
-
|
|
429
|
+
if file_path.endswith(".css") or file_path.endswith(".scss"):
|
|
430
|
+
analyzer = get_css_analyzer()
|
|
431
|
+
return analyzer.analyze_file(file_path)
|
|
432
|
+
if file_path.endswith(".md") or file_path.endswith(".markdown"):
|
|
433
|
+
analyzer = get_md_analyzer()
|
|
434
|
+
return analyzer.analyze_file(file_path)
|
|
435
|
+
if file_path.endswith(".ipynb"):
|
|
436
|
+
analyzer = get_ipynb_analyzer()
|
|
437
|
+
return analyzer.analyze_file(file_path)
|
|
438
|
+
if file_path.endswith(".py"):
|
|
439
|
+
# We can cache it similarly if we want, or just instantiate.
|
|
440
|
+
# For consistency let's add a getter or just instantiate for now to avoid circular imports / global clutter
|
|
441
|
+
# actually lets follow pattern
|
|
442
|
+
return get_python_analyzer().analyze_file(file_path)
|
|
443
|
+
|
|
444
|
+
return lizard.analyze_file(file_path)
|
|
344
445
|
except Exception as e:
|
|
345
446
|
# Return error info instead of crashing
|
|
346
447
|
return {"error": str(e), "filename": file_path}
|
|
@@ -405,12 +506,13 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
405
506
|
completed_count += 1
|
|
406
507
|
try:
|
|
407
508
|
# Add a timeout to prevent hanging on single files
|
|
408
|
-
result = future.result(timeout=
|
|
509
|
+
result = future.result(timeout=PER_FILE_ANALYSIS_TIMEOUT_SECONDS)
|
|
409
510
|
if isinstance(result, dict) and "error" in result:
|
|
410
511
|
print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
|
|
411
512
|
else:
|
|
412
513
|
# Success
|
|
413
|
-
|
|
514
|
+
if _should_log_file_progress(completed_count, total_count):
|
|
515
|
+
print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
|
|
414
516
|
analysis_results.append(result)
|
|
415
517
|
except concurrent.futures.TimeoutError:
|
|
416
518
|
print(f"❌ [{completed_count}/{total_count}] Timeout analyzing {file} (skipped)", flush=True)
|
|
@@ -421,7 +523,15 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
421
523
|
node_map = {str(root_path): tree_root}
|
|
422
524
|
|
|
423
525
|
for file_info in analysis_results:
|
|
424
|
-
|
|
526
|
+
# Be defensive: if we ever get back an unexpected object from the
|
|
527
|
+
# worker (e.g. a dict without "error", or something without a
|
|
528
|
+
# ``filename`` attribute), skip it instead of crashing the whole scan.
|
|
529
|
+
filename = getattr(file_info, "filename", None)
|
|
530
|
+
if not filename:
|
|
531
|
+
print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
path_obj = Path(filename)
|
|
425
535
|
try: rel_path = path_obj.relative_to(root_path)
|
|
426
536
|
except ValueError: continue
|
|
427
537
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class FunctionMetrics:
|
|
6
|
+
name: str
|
|
7
|
+
cyclomatic_complexity: int
|
|
8
|
+
nloc: int
|
|
9
|
+
start_line: int
|
|
10
|
+
end_line: int
|
|
11
|
+
parameter_count: int = 0
|
|
12
|
+
max_nesting_depth: int = 0
|
|
13
|
+
comment_lines: int = 0
|
|
14
|
+
todo_count: int = 0
|
|
15
|
+
# TS/TSX Specific Metrics (per-function)
|
|
16
|
+
ts_type_interface_count: int = 0
|
|
17
|
+
# Markdown-specific metrics
|
|
18
|
+
md_data_url_count: int = 0
|
|
19
|
+
# TSX structure helpers
|
|
20
|
+
contains_tsx: bool = False
|
|
21
|
+
tsx_start_line: int = 0
|
|
22
|
+
tsx_end_line: int = 0
|
|
23
|
+
tsx_root_name: str = ""
|
|
24
|
+
tsx_root_is_fragment: bool = False
|
|
25
|
+
origin_type: str = ""
|
|
26
|
+
is_jsx_container: bool = False
|
|
27
|
+
children: List["FunctionMetrics"] = field(default_factory=list)
|
|
28
|
+
# We store children to represent nested functions.
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class FileMetrics:
|
|
32
|
+
nloc: int
|
|
33
|
+
average_cyclomatic_complexity: float
|
|
34
|
+
function_list: List[FunctionMetrics] = field(default_factory=list)
|
|
35
|
+
filename: str = ""
|
|
36
|
+
comment_lines: int = 0
|
|
37
|
+
comment_density: float = 0.0
|
|
38
|
+
max_nesting_depth: int = 0
|
|
39
|
+
average_function_length: float = 0.0
|
|
40
|
+
parameter_count: int = 0
|
|
41
|
+
todo_count: int = 0
|
|
42
|
+
classes_count: int = 0
|
|
43
|
+
|
|
44
|
+
# TS/TSX Specific Metrics
|
|
45
|
+
tsx_nesting_depth: int = 0
|
|
46
|
+
tsx_render_branching_count: int = 0
|
|
47
|
+
tsx_react_use_effect_count: int = 0
|
|
48
|
+
tsx_anonymous_handler_count: int = 0
|
|
49
|
+
tsx_prop_count: int = 0
|
|
50
|
+
ts_any_usage_count: int = 0
|
|
51
|
+
ts_ignore_count: int = 0
|
|
52
|
+
ts_import_coupling_count: int = 0
|
|
53
|
+
tsx_hardcoded_string_volume: int = 0
|
|
54
|
+
tsx_duplicated_string_count: int = 0
|
|
55
|
+
ts_type_interface_count: int = 0
|
|
56
|
+
ts_type_interface_count: int = 0
|
|
57
|
+
ts_export_count: int = 0
|
|
58
|
+
# Python-specific metrics
|
|
59
|
+
python_import_count: int = 0
|
|
60
|
+
# Markdown-specific metrics (per-file)
|
|
61
|
+
md_data_url_count: int = 0
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import tree_sitter_css as tscss
|
|
5
|
+
import tree_sitter_scss as tsscss
|
|
6
|
+
from tree_sitter import Language, Node, Parser
|
|
7
|
+
|
|
8
|
+
from app.services.analysis_types import FileMetrics, FunctionMetrics
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
CSS_LANGUAGE = Language(tscss.language())
|
|
12
|
+
SCSS_LANGUAGE = Language(tsscss.language())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CssTreeSitterAnalyzer:
|
|
16
|
+
"""
|
|
17
|
+
Analyze CSS and SCSS files using tree-sitter and emit a hierarchy of
|
|
18
|
+
"scopes" compatible with the existing FunctionMetrics / FileMetrics
|
|
19
|
+
pipeline.
|
|
20
|
+
|
|
21
|
+
Goals:
|
|
22
|
+
- For CSS: treat each rule / at-rule block as a scope.
|
|
23
|
+
- For SCSS: in addition to CSS rules, create scopes for:
|
|
24
|
+
- nested rules (selectors inside selectors / at-rules)
|
|
25
|
+
- mixin definitions
|
|
26
|
+
- function definitions
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self.css_parser = Parser(CSS_LANGUAGE)
|
|
31
|
+
self.scss_parser = Parser(SCSS_LANGUAGE)
|
|
32
|
+
|
|
33
|
+
def analyze_file(self, file_path: str) -> FileMetrics:
|
|
34
|
+
with open(file_path, "rb") as f:
|
|
35
|
+
content = f.read()
|
|
36
|
+
|
|
37
|
+
is_scss = file_path.endswith(".scss")
|
|
38
|
+
parser = self.scss_parser if is_scss else self.css_parser
|
|
39
|
+
tree = parser.parse(content)
|
|
40
|
+
|
|
41
|
+
# Simple non-empty line count for LOC
|
|
42
|
+
lines = content.splitlines()
|
|
43
|
+
nloc = len([l for l in lines if l.strip()])
|
|
44
|
+
|
|
45
|
+
scopes = self._extract_scopes(tree.root_node, content, is_scss=is_scss)
|
|
46
|
+
|
|
47
|
+
average_complexity = (
|
|
48
|
+
sum(f.cyclomatic_complexity for f in scopes) / len(scopes)
|
|
49
|
+
if scopes
|
|
50
|
+
else 0.0
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
total_scope_loc = sum(f.nloc for f in scopes)
|
|
54
|
+
average_scope_length = total_scope_loc / len(scopes) if scopes else 0.0
|
|
55
|
+
|
|
56
|
+
# For now we do not compute any CSS/SCSS-specific metrics; everything
|
|
57
|
+
# other than LOC and the scope hierarchy stays at 0 so the rest of the
|
|
58
|
+
# pipeline can aggregate safely.
|
|
59
|
+
return FileMetrics(
|
|
60
|
+
nloc=nloc,
|
|
61
|
+
average_cyclomatic_complexity=average_complexity,
|
|
62
|
+
function_list=scopes,
|
|
63
|
+
filename=file_path,
|
|
64
|
+
comment_lines=0,
|
|
65
|
+
comment_density=0.0,
|
|
66
|
+
max_nesting_depth=0,
|
|
67
|
+
average_function_length=average_scope_length,
|
|
68
|
+
parameter_count=0,
|
|
69
|
+
todo_count=0,
|
|
70
|
+
classes_count=0,
|
|
71
|
+
tsx_nesting_depth=0,
|
|
72
|
+
tsx_render_branching_count=0,
|
|
73
|
+
tsx_react_use_effect_count=0,
|
|
74
|
+
tsx_anonymous_handler_count=0,
|
|
75
|
+
tsx_prop_count=0,
|
|
76
|
+
ts_any_usage_count=0,
|
|
77
|
+
ts_ignore_count=0,
|
|
78
|
+
ts_import_coupling_count=0,
|
|
79
|
+
tsx_hardcoded_string_volume=0,
|
|
80
|
+
tsx_duplicated_string_count=0,
|
|
81
|
+
ts_type_interface_count=0,
|
|
82
|
+
ts_export_count=0,
|
|
83
|
+
md_data_url_count=0,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# ---- Scope extraction ----------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def _extract_scopes(
|
|
89
|
+
self, root: Node, content: bytes, is_scss: bool
|
|
90
|
+
) -> List[FunctionMetrics]:
|
|
91
|
+
"""
|
|
92
|
+
Walk the CSS / SCSS syntax tree and build a hierarchy of scopes.
|
|
93
|
+
|
|
94
|
+
We treat any node that looks like a "rule" / "block" container (rule
|
|
95
|
+
sets, at-rules, etc.) as a scope. For SCSS we also treat mixin and
|
|
96
|
+
function definitions as scopes.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def walk(node: Node) -> List[FunctionMetrics]:
|
|
100
|
+
results: List[FunctionMetrics] = []
|
|
101
|
+
for child in node.children:
|
|
102
|
+
if self._node_is_scope(child, is_scss=is_scss):
|
|
103
|
+
scope = self._build_scope_metrics(child, content, is_scss=is_scss)
|
|
104
|
+
scope.children = walk(child)
|
|
105
|
+
results.append(scope)
|
|
106
|
+
else:
|
|
107
|
+
results.extend(walk(child))
|
|
108
|
+
return results
|
|
109
|
+
|
|
110
|
+
return walk(root)
|
|
111
|
+
|
|
112
|
+
def _node_is_scope(self, node: Node, is_scss: bool) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Heuristic: identify rule / at-rule / mixin / function nodes.
|
|
115
|
+
|
|
116
|
+
The exact node type names differ slightly between CSS and SCSS
|
|
117
|
+
grammars, so we rely on a mix of explicit names and substring checks.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
t = node.type
|
|
121
|
+
|
|
122
|
+
# Common CSS rule / at-rule containers.
|
|
123
|
+
rule_like_types = {
|
|
124
|
+
"rule_set",
|
|
125
|
+
"ruleset",
|
|
126
|
+
"style_rule",
|
|
127
|
+
"qualified_rule",
|
|
128
|
+
"media_rule",
|
|
129
|
+
"supports_rule",
|
|
130
|
+
"keyframes_rule",
|
|
131
|
+
"font_face_rule",
|
|
132
|
+
"page_rule",
|
|
133
|
+
}
|
|
134
|
+
if t in rule_like_types:
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
# Generic heuristic: anything with "rule" in the name (but not the
|
|
138
|
+
# root stylesheet) is probably a rule-like container.
|
|
139
|
+
if "rule" in t and t not in {"stylesheet"}:
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
if is_scss:
|
|
143
|
+
# SCSS-specific constructs for functions / mixins and control flow
|
|
144
|
+
# that introduce nested blocks.
|
|
145
|
+
scss_scope_types = {
|
|
146
|
+
"mixin_declaration",
|
|
147
|
+
"mixin_definition",
|
|
148
|
+
"function_declaration",
|
|
149
|
+
"function_definition",
|
|
150
|
+
"if_statement",
|
|
151
|
+
"each_statement",
|
|
152
|
+
"for_statement",
|
|
153
|
+
"while_statement",
|
|
154
|
+
}
|
|
155
|
+
if t in scss_scope_types:
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
if any(
|
|
159
|
+
key in t
|
|
160
|
+
for key in (
|
|
161
|
+
"mixin",
|
|
162
|
+
"function",
|
|
163
|
+
)
|
|
164
|
+
):
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
def _build_scope_metrics(
|
|
170
|
+
self, node: Node, content: bytes, is_scss: bool
|
|
171
|
+
) -> FunctionMetrics:
|
|
172
|
+
start_line = node.start_point.row + 1
|
|
173
|
+
end_line = node.end_point.row + 1
|
|
174
|
+
nloc = max(0, end_line - start_line + 1)
|
|
175
|
+
|
|
176
|
+
name = self._get_scope_name(node, content)
|
|
177
|
+
origin_type = "scss_scope" if is_scss else "css_scope"
|
|
178
|
+
|
|
179
|
+
return FunctionMetrics(
|
|
180
|
+
name=name,
|
|
181
|
+
cyclomatic_complexity=0,
|
|
182
|
+
nloc=nloc,
|
|
183
|
+
start_line=start_line,
|
|
184
|
+
end_line=end_line,
|
|
185
|
+
parameter_count=0,
|
|
186
|
+
max_nesting_depth=0,
|
|
187
|
+
comment_lines=0,
|
|
188
|
+
todo_count=0,
|
|
189
|
+
ts_type_interface_count=0,
|
|
190
|
+
md_data_url_count=0,
|
|
191
|
+
origin_type=origin_type,
|
|
192
|
+
is_jsx_container=False,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# ---- Helpers -------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
def _get_scope_name(self, node: Node, content: bytes, max_len: int = 80) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Derive a human-friendly name for a CSS / SCSS scope by taking the
|
|
200
|
+
first line of the node text up to the opening '{', if present.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
# Use byte offsets so we don't depend on child layout details.
|
|
204
|
+
src = content[node.start_byte : node.end_byte].decode(
|
|
205
|
+
"utf-8", errors="ignore"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Take the first non-empty line.
|
|
209
|
+
first_line = ""
|
|
210
|
+
for line in src.splitlines():
|
|
211
|
+
stripped = line.strip()
|
|
212
|
+
if stripped:
|
|
213
|
+
first_line = stripped
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
if not first_line:
|
|
217
|
+
first_line = node.type
|
|
218
|
+
|
|
219
|
+
# Trim everything after '{' so selectors / rule headers look clean.
|
|
220
|
+
brace_index = first_line.find("{")
|
|
221
|
+
if brace_index != -1:
|
|
222
|
+
first_line = first_line[:brace_index].rstrip()
|
|
223
|
+
|
|
224
|
+
# Collapse internal whitespace to keep long selectors readable.
|
|
225
|
+
first_line = re.sub(r"\s+", " ", first_line).strip()
|
|
226
|
+
|
|
227
|
+
if len(first_line) > max_len:
|
|
228
|
+
first_line = first_line[: max_len - 1] + "…"
|
|
229
|
+
|
|
230
|
+
return first_line or node.type
|
|
231
|
+
|
|
232
|
+
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import List, Dict, Optional, Any
|
|
5
5
|
import uuid
|
|
6
6
|
|
|
7
|
-
from app.services.
|
|
7
|
+
from app.services.typescript.typescript_analysis import TreeSitterAnalyzer
|
|
8
8
|
|
|
9
9
|
# Load TypeScript and TSX grammars
|
|
10
10
|
TYPESCRIPT_LANGUAGE = Language(tstypescript.language_typescript())
|