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.
Files changed (313) hide show
  1. {srcly-0.1.14 → srcly-0.1.17}/PKG-INFO +5 -1
  2. {srcly-0.1.14 → srcly-0.1.17}/app/models.py +6 -0
  3. {srcly-0.1.14 → srcly-0.1.17}/app/routers/analysis.py +11 -3
  4. {srcly-0.1.14 → srcly-0.1.17}/app/routers/files.py +4 -0
  5. {srcly-0.1.14 → srcly-0.1.17}/app/services/analysis.py +117 -7
  6. srcly-0.1.17/app/services/analysis_types.py +61 -0
  7. srcly-0.1.17/app/services/css/css_analysis.py +232 -0
  8. {srcly-0.1.14 → srcly-0.1.17}/app/services/data_flow_analysis.py +1 -1
  9. srcly-0.1.17/app/services/ipynb/ipynb_analysis.py +129 -0
  10. srcly-0.1.17/app/services/markdown/markdown_analysis.py +414 -0
  11. srcly-0.1.17/app/services/python/python_analysis.py +249 -0
  12. srcly-0.1.14/app/services/tree_sitter_analysis.py → srcly-0.1.17/app/services/typescript/typescript_analysis.py +2 -52
  13. srcly-0.1.17/app/static/assets/index-BuiLB-f3.js +37 -0
  14. srcly-0.1.17/app/static/assets/index-lgAeJI5G.css +1 -0
  15. {srcly-0.1.14 → srcly-0.1.17}/app/static/index.html +2 -2
  16. {srcly-0.1.14 → srcly-0.1.17}/pyproject.toml +5 -1
  17. srcly-0.1.14/app/static/assets/index-CTNf0zED.css +0 -1
  18. srcly-0.1.14/app/static/assets/index-DzyR2Ke2.js +0 -37
  19. {srcly-0.1.14 → srcly-0.1.17}/.gitignore +0 -0
  20. {srcly-0.1.14 → srcly-0.1.17}/README.md +0 -0
  21. {srcly-0.1.14 → srcly-0.1.17}/app/.gitignore +0 -0
  22. {srcly-0.1.14 → srcly-0.1.17}/app/__init__.py +0 -0
  23. {srcly-0.1.14 → srcly-0.1.17}/app/config.py +0 -0
  24. {srcly-0.1.14 → srcly-0.1.17}/app/main.py +0 -0
  25. {srcly-0.1.14 → srcly-0.1.17}/app/run.py +0 -0
  26. {srcly-0.1.14 → srcly-0.1.17}/app/services/cache.py +0 -0
  27. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/abap-BdImnpbu.js +0 -0
  28. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  29. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ada-bCR0ucgS.js +0 -0
  30. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  31. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  32. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  33. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apache-Pmp26Uib.js +0 -0
  34. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apex-DhZLUxFE.js +0 -0
  35. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/apl-dKokRX4l.js +0 -0
  36. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  37. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ara-BRHolxvo.js +0 -0
  38. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  39. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  40. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/astro-CbQHKStN.js +0 -0
  41. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  42. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/awk-DMzUqQB5.js +0 -0
  43. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  44. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  45. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bat-BkioyH1T.js +0 -0
  46. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  47. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/berry-D08WgyRC.js +0 -0
  48. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  49. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  50. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/blade-DVc8C-J4.js +0 -0
  51. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  52. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/c-BIGW1oBm.js +0 -0
  53. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  54. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  55. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  56. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  57. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  58. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  59. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/clarity-D53aC0YG.js +0 -0
  60. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/clojure-P80f7IUj.js +0 -0
  61. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  62. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cobol-nwyudZeR.js +0 -0
  63. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  64. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  65. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  66. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  67. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/coq-DkFqJrB1.js +0 -0
  68. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cpp-CofmeUqb.js +0 -0
  69. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  70. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  71. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/css-DPfMkruS.js +0 -0
  72. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/csv-fuZLfV_i.js +0 -0
  73. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cue-D82EKSYY.js +0 -0
  74. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/cypher-COkxafJQ.js +0 -0
  75. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/d-85-TOEBH.js +0 -0
  76. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  77. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dart-CF10PKvl.js +0 -0
  78. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dax-CEL-wOlO.js +0 -0
  79. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  80. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/diff-D97Zzqfu.js +0 -0
  81. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/docker-BcOcwvcX.js +0 -0
  82. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  83. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  84. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  85. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  86. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/edge-BkV0erSs.js +0 -0
  87. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/elixir-CDX3lj18.js +0 -0
  88. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/elm-DbKCFpqz.js +0 -0
  89. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  90. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/erb-BOJIQeun.js +0 -0
  91. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  92. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  93. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  94. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fennel-BYunw83y.js +0 -0
  95. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fish-BvzEVeQv.js +0 -0
  96. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  97. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  98. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  99. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  100. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  101. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  102. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  103. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/genie-D0YGMca9.js +0 -0
  104. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  105. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  106. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  107. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  108. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  109. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  110. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  111. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  112. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  113. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  114. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gleam-BspZqrRM.js +0 -0
  115. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  116. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  117. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/glsl-DplSGwfg.js +0 -0
  118. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  119. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/go-Dn2_MT6a.js +0 -0
  120. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  121. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  122. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  123. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  124. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  125. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  126. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  127. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  128. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hack-CaT9iCJl.js +0 -0
  129. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haml-B8DHNrY2.js +0 -0
  130. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  131. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  132. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  133. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  134. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hjson-D5-asLiD.js +0 -0
  135. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  136. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/houston-DnULxvSX.js +0 -0
  137. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/html-GMplVEZG.js +0 -0
  138. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  139. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/http-jrhK8wxY.js +0 -0
  140. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hurl-irOxFIW8.js +0 -0
  141. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  142. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/hy-DFXneXwc.js +0 -0
  143. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/imba-DGztddWO.js +0 -0
  144. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ini-BEwlwnbL.js +0 -0
  145. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/java-CylS5w8V.js +0 -0
  146. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  147. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  148. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jison-wvAkD_A8.js +0 -0
  149. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/json-Cp-IABpG.js +0 -0
  150. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/json5-C9tS-k6U.js +0 -0
  151. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  152. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  153. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  154. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  155. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  156. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/julia-C8NyazO9.js +0 -0
  157. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  158. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  159. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  160. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kdl-DV7GczEv.js +0 -0
  161. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  162. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  163. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  164. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/latex-BUKiar2Z.js +0 -0
  165. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/lean-DP1Csr6i.js +0 -0
  166. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/less-B1dDrJ26.js +0 -0
  167. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  168. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/liquid-DYVedYrR.js +0 -0
  169. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/llvm-BtvRca6l.js +0 -0
  170. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/log-2UxHyX5q.js +0 -0
  171. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/logo-BtOb2qkB.js +0 -0
  172. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/lua-BbnMAYS6.js +0 -0
  173. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/luau-CXu1NL6O.js +0 -0
  174. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/make-CHLpvVh8.js +0 -0
  175. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  176. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/marko-CPi9NSCl.js +0 -0
  177. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  178. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  179. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  180. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  181. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  182. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/matlab-D7o27uSR.js +0 -0
  183. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mdc-DUICxH0z.js +0 -0
  184. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  185. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  186. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  187. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/min-light-CTRr51gU.js +0 -0
  188. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  189. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/mojo-1DNp92w6.js +0 -0
  190. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  191. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/move-Bu9oaDYs.js +0 -0
  192. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  193. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  194. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nginx-DknmC5AR.js +0 -0
  195. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  196. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nim-CVrawwO9.js +0 -0
  197. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nix-BbRYJGeE.js +0 -0
  198. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nord-Ddv68eIx.js +0 -0
  199. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/nushell-C-sUppwS.js +0 -0
  200. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  201. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  202. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  203. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  204. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  205. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  206. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/perl-C0TMdlhV.js +0 -0
  207. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/php-CDn_0X-4.js +0 -0
  208. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  209. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  210. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  211. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/po-BTJTHyun.js +0 -0
  212. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  213. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/polar-C0HS_06l.js +0 -0
  214. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/postcss-CXtECtnM.js +0 -0
  215. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  216. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  217. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  218. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  219. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/proto-DyJlTyXw.js +0 -0
  220. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/pug-CGlum2m_.js +0 -0
  221. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/puppet-BMWR74SV.js +0 -0
  222. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/purescript-CklMAg4u.js +0 -0
  223. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/python-B6aJPvgy.js +0 -0
  224. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qml-3beO22l8.js +0 -0
  225. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  226. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/qss-IeuSbFQv.js +0 -0
  227. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/r-DiinP2Uv.js +0 -0
  228. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/racket-BqYA7rlc.js +0 -0
  229. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/raku-DXvB9xmW.js +0 -0
  230. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/razor-WgofotgN.js +0 -0
  231. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/red-bN70gL4F.js +0 -0
  232. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/reg-C-SQnVFl.js +0 -0
  233. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  234. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rel-C3B-1QV4.js +0 -0
  235. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  236. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  237. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  238. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  239. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  240. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rst-B0xPkSld.js +0 -0
  241. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  242. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/rust-B1yitclQ.js +0 -0
  243. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sas-cz2c8ADy.js +0 -0
  244. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  245. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scala-C151Ov-r.js +0 -0
  246. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scheme-C98Dy4si.js +0 -0
  247. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/scss-OYdSNvt2.js +0 -0
  248. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  249. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  250. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  251. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  252. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  253. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  254. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  255. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  256. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  257. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  258. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  259. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/soy-Brmx7dQM.js +0 -0
  260. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  261. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  262. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/sql-BLtJtn59.js +0 -0
  263. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  264. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/stata-BH5u7GGu.js +0 -0
  265. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  266. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  267. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/swift-Dg5xB15N.js +0 -0
  268. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  269. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  270. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  271. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  272. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  273. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  274. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/templ-W15q3VgB.js +0 -0
  275. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/terraform-BETggiCN.js +0 -0
  276. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tex-Cppo0RY3.js +0 -0
  277. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  278. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/toml-vGWfd6FD.js +0 -0
  279. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  280. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  281. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/tsx-COt5Ahok.js +0 -0
  282. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/turtle-BsS91CYL.js +0 -0
  283. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/twig-CO9l9SDP.js +0 -0
  284. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  285. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typespec-Df68jz8_.js +0 -0
  286. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/typst-DHCkPAjA.js +0 -0
  287. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/v-BcVCzyr7.js +0 -0
  288. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vala-CsfeWuGM.js +0 -0
  289. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vb-D17OF-Vu.js +0 -0
  290. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  291. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vesper-DU1UobuO.js +0 -0
  292. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  293. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/viml-CJc9bBzg.js +0 -0
  294. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  295. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  296. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  297. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-CCoi5OLL.js +0 -0
  298. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  299. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  300. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  301. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  302. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  303. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  304. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  305. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  306. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wit-5i3qLPDT.js +0 -0
  307. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  308. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  309. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  310. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/yaml-Buea-lGh.js +0 -0
  311. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  312. {srcly-0.1.14 → srcly-0.1.17}/app/static/assets/zig-VOosw3JB.js +0 -0
  313. {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.14
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, tree_sitter_analysis
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 = tree_sitter_analysis.TreeSitterAnalyzer()
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
- repo_root = current_root.parent
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.tree_sitter_analysis import TreeSitterAnalyzer
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('.ts') or file_path.endswith('.tsx'):
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
- else:
343
- return lizard.analyze_file(file_path)
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=5)
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
- print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
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
- path_obj = Path(file_info.filename)
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.tree_sitter_analysis import TreeSitterAnalyzer
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())