srcly 0.1.14__tar.gz → 0.1.16__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.16}/PKG-INFO +4 -1
- {srcly-0.1.14 → srcly-0.1.16}/app/models.py +2 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/routers/analysis.py +8 -1
- {srcly-0.1.14 → srcly-0.1.16}/app/services/analysis.py +91 -6
- srcly-0.1.16/app/services/css_analysis.py +232 -0
- srcly-0.1.16/app/services/ipynb_analysis.py +89 -0
- srcly-0.1.16/app/services/markdown_analysis.py +414 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/services/tree_sitter_analysis.py +6 -0
- srcly-0.1.16/app/static/assets/index-CX6VqYPz.js +37 -0
- srcly-0.1.16/app/static/assets/index-DXVBnNom.css +1 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/index.html +2 -2
- {srcly-0.1.14 → srcly-0.1.16}/pyproject.toml +4 -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.16}/.gitignore +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/README.md +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/.gitignore +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/__init__.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/config.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/main.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/routers/files.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/run.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/services/cache.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/services/data_flow_analysis.py +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.14 → srcly-0.1.16}/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.16
|
|
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,9 @@ 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-scss==1.0.0
|
|
12
15
|
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
13
16
|
Requires-Dist: tree-sitter>=0.25.2
|
|
14
17
|
Requires-Dist: uvicorn>=0.38.0
|
|
@@ -570,7 +570,14 @@ async def get_analysis_context():
|
|
|
570
570
|
load, for both the current working directory and the repository root.
|
|
571
571
|
"""
|
|
572
572
|
current_root = ROOT_PATH
|
|
573
|
-
|
|
573
|
+
# Use the same repository root detection logic as the analysis layer so that
|
|
574
|
+
# the client sees a consistent "repo root" regardless of how the server was
|
|
575
|
+
# started (uvx, direct CLI, dev mode, etc.).
|
|
576
|
+
#
|
|
577
|
+
# This mirrors the behaviour of the CLI helper that walks upwards from the
|
|
578
|
+
# current directory to the enclosing `.git` root, falling back to the
|
|
579
|
+
# starting directory when no repository is found.
|
|
580
|
+
repo_root = analysis.find_repo_root(current_root)
|
|
574
581
|
|
|
575
582
|
current_file_count, current_folder_count = _estimate_counts(current_root)
|
|
576
583
|
|
|
@@ -6,9 +6,19 @@ from pathlib import Path
|
|
|
6
6
|
from app.models import Node, Metrics
|
|
7
7
|
from app.config import IGNORE_DIRS, IGNORE_FILES, IGNORE_EXTENSIONS
|
|
8
8
|
from app.services.tree_sitter_analysis import TreeSitterAnalyzer
|
|
9
|
+
from app.services.markdown_analysis import MarkdownTreeSitterAnalyzer
|
|
10
|
+
from app.services.ipynb_analysis import NotebookAnalyzer
|
|
11
|
+
from app.services.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,27 @@ 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
|
+
|
|
19
50
|
def find_repo_root(start_path: Path) -> Path:
|
|
20
51
|
current = start_path.resolve()
|
|
21
52
|
for parent in [current, *current.parents]:
|
|
@@ -54,6 +85,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
54
85
|
node.metrics.tsx_duplicated_string_count = file_info.tsx_duplicated_string_count
|
|
55
86
|
node.metrics.ts_type_interface_count = file_info.ts_type_interface_count
|
|
56
87
|
node.metrics.ts_export_count = file_info.ts_export_count
|
|
88
|
+
# Markdown-specific metrics
|
|
89
|
+
if hasattr(file_info, "md_data_url_count"):
|
|
90
|
+
node.metrics.md_data_url_count = file_info.md_data_url_count
|
|
57
91
|
|
|
58
92
|
# New metrics
|
|
59
93
|
if hasattr(file_info, 'comment_lines'):
|
|
@@ -125,6 +159,7 @@ def attach_file_metrics(node: Node, file_info) -> None:
|
|
|
125
159
|
func_node.metrics.comment_lines = getattr(func, 'comment_lines', 0)
|
|
126
160
|
func_node.metrics.todo_count = getattr(func, 'todo_count', 0)
|
|
127
161
|
func_node.metrics.ts_type_interface_count = getattr(func, 'ts_type_interface_count', 0)
|
|
162
|
+
func_node.metrics.md_data_url_count = getattr(func, 'md_data_url_count', 0)
|
|
128
163
|
|
|
129
164
|
# Density for function
|
|
130
165
|
comment_lines = getattr(func, 'comment_lines', 0)
|
|
@@ -266,6 +301,36 @@ def _is_gitignored(path: Path, ignore_root: Path, spec: PathSpec | None) -> bool
|
|
|
266
301
|
rel_str = rel.as_posix()
|
|
267
302
|
return spec.match_file(rel_str)
|
|
268
303
|
|
|
304
|
+
|
|
305
|
+
def _should_log_file_progress(completed_count: int, total_count: int) -> bool:
|
|
306
|
+
"""
|
|
307
|
+
Decide whether to emit a per-file progress log.
|
|
308
|
+
|
|
309
|
+
Rules:
|
|
310
|
+
- If there are fewer than 10 files, log every file.
|
|
311
|
+
- Otherwise:
|
|
312
|
+
- Log the first 10 files.
|
|
313
|
+
- Log at 15 and 20.
|
|
314
|
+
- Log every 25th file after that (25, 50, 75, ...).
|
|
315
|
+
- Always log the final file when done.
|
|
316
|
+
"""
|
|
317
|
+
if total_count < 10:
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
if completed_count <= 10:
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
if completed_count in (15, 20):
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
if completed_count % 25 == 0:
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
if completed_count == total_count:
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
return False
|
|
333
|
+
|
|
269
334
|
def aggregate_metrics(node: Node) -> Metrics:
|
|
270
335
|
if not node.children: return node.metrics
|
|
271
336
|
|
|
@@ -281,6 +346,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
281
346
|
total_classes_count = 0
|
|
282
347
|
total_type_interface_count = 0
|
|
283
348
|
total_export_count = 0
|
|
349
|
+
total_md_data_url_count = 0
|
|
284
350
|
|
|
285
351
|
# For average function length, we need total function loc and total functions (already have total_funcs)
|
|
286
352
|
# But we need to sum function locs from children.
|
|
@@ -301,6 +367,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
301
367
|
total_classes_count += child_metrics.classes_count
|
|
302
368
|
total_type_interface_count += child_metrics.ts_type_interface_count
|
|
303
369
|
total_export_count += child_metrics.ts_export_count
|
|
370
|
+
total_md_data_url_count += child_metrics.md_data_url_count
|
|
304
371
|
|
|
305
372
|
# Reconstruct total function loc from average * count
|
|
306
373
|
total_function_loc += (child_metrics.average_function_length * child_metrics.function_count)
|
|
@@ -327,6 +394,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
327
394
|
node.metrics.average_function_length = total_function_loc / total_funcs if total_funcs > 0 else 0.0
|
|
328
395
|
node.metrics.ts_type_interface_count = total_type_interface_count
|
|
329
396
|
node.metrics.ts_export_count = total_export_count
|
|
397
|
+
node.metrics.md_data_url_count = total_md_data_url_count
|
|
330
398
|
|
|
331
399
|
return node.metrics
|
|
332
400
|
|
|
@@ -336,11 +404,19 @@ def analyze_single_file(file_path: str):
|
|
|
336
404
|
Must be top-level for multiprocessing pickling.
|
|
337
405
|
"""
|
|
338
406
|
try:
|
|
339
|
-
if file_path.endswith(
|
|
407
|
+
if file_path.endswith(".ts") or file_path.endswith(".tsx"):
|
|
340
408
|
analyzer = get_ts_analyzer()
|
|
341
409
|
return analyzer.analyze_file(file_path)
|
|
342
|
-
|
|
343
|
-
|
|
410
|
+
if file_path.endswith(".css") or file_path.endswith(".scss"):
|
|
411
|
+
analyzer = get_css_analyzer()
|
|
412
|
+
return analyzer.analyze_file(file_path)
|
|
413
|
+
if file_path.endswith(".md") or file_path.endswith(".markdown"):
|
|
414
|
+
analyzer = get_md_analyzer()
|
|
415
|
+
return analyzer.analyze_file(file_path)
|
|
416
|
+
if file_path.endswith(".ipynb"):
|
|
417
|
+
analyzer = get_ipynb_analyzer()
|
|
418
|
+
return analyzer.analyze_file(file_path)
|
|
419
|
+
return lizard.analyze_file(file_path)
|
|
344
420
|
except Exception as e:
|
|
345
421
|
# Return error info instead of crashing
|
|
346
422
|
return {"error": str(e), "filename": file_path}
|
|
@@ -405,12 +481,13 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
405
481
|
completed_count += 1
|
|
406
482
|
try:
|
|
407
483
|
# Add a timeout to prevent hanging on single files
|
|
408
|
-
result = future.result(timeout=
|
|
484
|
+
result = future.result(timeout=PER_FILE_ANALYSIS_TIMEOUT_SECONDS)
|
|
409
485
|
if isinstance(result, dict) and "error" in result:
|
|
410
486
|
print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
|
|
411
487
|
else:
|
|
412
488
|
# Success
|
|
413
|
-
|
|
489
|
+
if _should_log_file_progress(completed_count, total_count):
|
|
490
|
+
print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
|
|
414
491
|
analysis_results.append(result)
|
|
415
492
|
except concurrent.futures.TimeoutError:
|
|
416
493
|
print(f"❌ [{completed_count}/{total_count}] Timeout analyzing {file} (skipped)", flush=True)
|
|
@@ -421,7 +498,15 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
421
498
|
node_map = {str(root_path): tree_root}
|
|
422
499
|
|
|
423
500
|
for file_info in analysis_results:
|
|
424
|
-
|
|
501
|
+
# Be defensive: if we ever get back an unexpected object from the
|
|
502
|
+
# worker (e.g. a dict without "error", or something without a
|
|
503
|
+
# ``filename`` attribute), skip it instead of crashing the whole scan.
|
|
504
|
+
filename = getattr(file_info, "filename", None)
|
|
505
|
+
if not filename:
|
|
506
|
+
print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
|
|
507
|
+
continue
|
|
508
|
+
|
|
509
|
+
path_obj = Path(filename)
|
|
425
510
|
try: rel_path = path_obj.relative_to(root_path)
|
|
426
511
|
except ValueError: continue
|
|
427
512
|
|
|
@@ -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.tree_sitter_analysis 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
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from app.services.tree_sitter_analysis import FileMetrics, FunctionMetrics
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NotebookAnalyzer:
|
|
8
|
+
"""
|
|
9
|
+
Lightweight analysis pipeline for Jupyter notebooks.
|
|
10
|
+
|
|
11
|
+
The goal is to:
|
|
12
|
+
- Treat each cell as a "scope" so it can appear as a child in the treemap.
|
|
13
|
+
- Count LOC **only** from the `source` field of each cell (code/markdown/raw).
|
|
14
|
+
We explicitly ignore any `outputs`/execution metadata so that large output-
|
|
15
|
+
heavy notebooks don't dominate the treemap.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def analyze_file(self, file_path: str) -> FileMetrics:
|
|
19
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
20
|
+
data = json.load(f)
|
|
21
|
+
|
|
22
|
+
cells = data.get("cells") or []
|
|
23
|
+
|
|
24
|
+
function_list: List[FunctionMetrics] = []
|
|
25
|
+
current_line = 1
|
|
26
|
+
total_nloc = 0
|
|
27
|
+
|
|
28
|
+
for idx, cell in enumerate(cells):
|
|
29
|
+
cell_type = cell.get("cell_type", "cell")
|
|
30
|
+
source = cell.get("source", "")
|
|
31
|
+
|
|
32
|
+
# `source` can be a list of lines or a single string.
|
|
33
|
+
if isinstance(source, list):
|
|
34
|
+
text = "".join(source)
|
|
35
|
+
else:
|
|
36
|
+
text = str(source)
|
|
37
|
+
|
|
38
|
+
lines = text.splitlines()
|
|
39
|
+
# Count only non-empty lines of actual content.
|
|
40
|
+
non_empty_lines = [ln for ln in lines if ln.strip()]
|
|
41
|
+
loc = len(non_empty_lines)
|
|
42
|
+
|
|
43
|
+
if loc <= 0:
|
|
44
|
+
# Skip zero-LOC cells entirely for treemap sizing purposes.
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
start_line = current_line
|
|
48
|
+
end_line = current_line + loc - 1
|
|
49
|
+
current_line = end_line + 1
|
|
50
|
+
total_nloc += loc
|
|
51
|
+
|
|
52
|
+
name = f"[{cell_type}] cell {idx + 1}"
|
|
53
|
+
|
|
54
|
+
cell_metrics = FunctionMetrics(
|
|
55
|
+
name=name,
|
|
56
|
+
cyclomatic_complexity=0,
|
|
57
|
+
nloc=loc,
|
|
58
|
+
start_line=start_line,
|
|
59
|
+
end_line=end_line,
|
|
60
|
+
)
|
|
61
|
+
function_list.append(cell_metrics)
|
|
62
|
+
|
|
63
|
+
# Aggregate simple file-level metrics.
|
|
64
|
+
average_complexity = (
|
|
65
|
+
sum(f.cyclomatic_complexity for f in function_list) / len(function_list)
|
|
66
|
+
if function_list
|
|
67
|
+
else 0.0
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
average_function_length = (
|
|
71
|
+
total_nloc / len(function_list) if function_list else 0.0
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return FileMetrics(
|
|
75
|
+
nloc=total_nloc,
|
|
76
|
+
average_cyclomatic_complexity=average_complexity,
|
|
77
|
+
function_list=function_list,
|
|
78
|
+
filename=file_path,
|
|
79
|
+
comment_lines=0,
|
|
80
|
+
comment_density=0.0,
|
|
81
|
+
max_nesting_depth=0,
|
|
82
|
+
average_function_length=average_function_length,
|
|
83
|
+
parameter_count=0,
|
|
84
|
+
todo_count=0,
|
|
85
|
+
classes_count=0,
|
|
86
|
+
# TS/TSX-specific metrics remain at their defaults (all zeros) for notebooks.
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|