peekview 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. peekview-0.1.0/.gitignore +38 -0
  2. peekview-0.1.0/Makefile +26 -0
  3. peekview-0.1.0/PKG-INFO +171 -0
  4. peekview-0.1.0/README.md +137 -0
  5. peekview-0.1.0/peek/__init__.py +3 -0
  6. peekview-0.1.0/peek/__main__.py +6 -0
  7. peekview-0.1.0/peek/api/__init__.py +1 -0
  8. peekview-0.1.0/peek/api/entries.py +123 -0
  9. peekview-0.1.0/peek/api/files.py +114 -0
  10. peekview-0.1.0/peek/cli.py +332 -0
  11. peekview-0.1.0/peek/config.py +232 -0
  12. peekview-0.1.0/peek/database.py +304 -0
  13. peekview-0.1.0/peek/exceptions.py +127 -0
  14. peekview-0.1.0/peek/language.py +407 -0
  15. peekview-0.1.0/peek/main.py +224 -0
  16. peekview-0.1.0/peek/models.py +330 -0
  17. peekview-0.1.0/peek/services/__init__.py +1 -0
  18. peekview-0.1.0/peek/services/entry_service.py +612 -0
  19. peekview-0.1.0/peek/services/file_service.py +231 -0
  20. peekview-0.1.0/peek/static/assets/EntryDetailView-Cnhdjikj.css +1 -0
  21. peekview-0.1.0/peek/static/assets/EntryDetailView-yI3ydvJn.js +77 -0
  22. peekview-0.1.0/peek/static/assets/EntryListView-CjYcs8e1.js +1 -0
  23. peekview-0.1.0/peek/static/assets/EntryListView-DjPX64rQ.css +1 -0
  24. peekview-0.1.0/peek/static/assets/abap-DsBKuouk.js +1 -0
  25. peekview-0.1.0/peek/static/assets/actionscript-3-D_z4Izcz.js +1 -0
  26. peekview-0.1.0/peek/static/assets/ada-727ZlQH0.js +1 -0
  27. peekview-0.1.0/peek/static/assets/andromeeda-C3khCPGq.js +1 -0
  28. peekview-0.1.0/peek/static/assets/angular-html-LfdN0zeE.js +1 -0
  29. peekview-0.1.0/peek/static/assets/angular-ts-CKsD7JZE.js +1 -0
  30. peekview-0.1.0/peek/static/assets/apache-Dn00JSTd.js +1 -0
  31. peekview-0.1.0/peek/static/assets/apex-COJ4H7py.js +1 -0
  32. peekview-0.1.0/peek/static/assets/apl-BBq3IX1j.js +1 -0
  33. peekview-0.1.0/peek/static/assets/applescript-Bu5BbsvL.js +1 -0
  34. peekview-0.1.0/peek/static/assets/ara-7O62HKoU.js +1 -0
  35. peekview-0.1.0/peek/static/assets/asciidoc-BPT9niGB.js +1 -0
  36. peekview-0.1.0/peek/static/assets/asm-Dhn9LcZ4.js +1 -0
  37. peekview-0.1.0/peek/static/assets/astro-CqkE3fuf.js +1 -0
  38. peekview-0.1.0/peek/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  39. peekview-0.1.0/peek/static/assets/awk-eg146-Ew.js +1 -0
  40. peekview-0.1.0/peek/static/assets/ayu-dark-Cv9koXgw.js +1 -0
  41. peekview-0.1.0/peek/static/assets/ballerina-Du268qiB.js +1 -0
  42. peekview-0.1.0/peek/static/assets/bat-fje9CFhw.js +1 -0
  43. peekview-0.1.0/peek/static/assets/beancount-BwXTMy5W.js +1 -0
  44. peekview-0.1.0/peek/static/assets/berry-3xVqZejG.js +1 -0
  45. peekview-0.1.0/peek/static/assets/bibtex-xW4inM5L.js +1 -0
  46. peekview-0.1.0/peek/static/assets/bicep-DHo0CJ0O.js +1 -0
  47. peekview-0.1.0/peek/static/assets/blade-a8OxSdnT.js +1 -0
  48. peekview-0.1.0/peek/static/assets/bsl-Dgyn0ogV.js +1 -0
  49. peekview-0.1.0/peek/static/assets/c-C3t2pwGQ.js +1 -0
  50. peekview-0.1.0/peek/static/assets/cadence-DNquZEk8.js +1 -0
  51. peekview-0.1.0/peek/static/assets/cairo--RitsXJZ.js +1 -0
  52. peekview-0.1.0/peek/static/assets/catppuccin-frappe-CD_QflpE.js +1 -0
  53. peekview-0.1.0/peek/static/assets/catppuccin-latte-DRW-0cLl.js +1 -0
  54. peekview-0.1.0/peek/static/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
  55. peekview-0.1.0/peek/static/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
  56. peekview-0.1.0/peek/static/assets/clarity-BHOwM8T6.js +1 -0
  57. peekview-0.1.0/peek/static/assets/clojure-DxSadP1t.js +1 -0
  58. peekview-0.1.0/peek/static/assets/cmake-DbXoA79R.js +1 -0
  59. peekview-0.1.0/peek/static/assets/cobol-PTqiYgYu.js +1 -0
  60. peekview-0.1.0/peek/static/assets/codeowners-Bp6g37R7.js +1 -0
  61. peekview-0.1.0/peek/static/assets/codeql-sacFqUAJ.js +1 -0
  62. peekview-0.1.0/peek/static/assets/coffee-dyiR41kL.js +1 -0
  63. peekview-0.1.0/peek/static/assets/common-lisp-C7gG9l05.js +1 -0
  64. peekview-0.1.0/peek/static/assets/coq-Dsg_Bt_b.js +1 -0
  65. peekview-0.1.0/peek/static/assets/cpp-BksuvNSY.js +1 -0
  66. peekview-0.1.0/peek/static/assets/crystal-DtDmRg-F.js +1 -0
  67. peekview-0.1.0/peek/static/assets/csharp-D9R-vmeu.js +1 -0
  68. peekview-0.1.0/peek/static/assets/css-BPhBrDlE.js +1 -0
  69. peekview-0.1.0/peek/static/assets/csv-B0qRVHPH.js +1 -0
  70. peekview-0.1.0/peek/static/assets/cue-DtFQj3wx.js +1 -0
  71. peekview-0.1.0/peek/static/assets/cypher-m2LEI-9-.js +1 -0
  72. peekview-0.1.0/peek/static/assets/d-BoXegm-a.js +1 -0
  73. peekview-0.1.0/peek/static/assets/dark-plus-C3mMm8J8.js +1 -0
  74. peekview-0.1.0/peek/static/assets/dart-B9wLZaAG.js +1 -0
  75. peekview-0.1.0/peek/static/assets/dax-ClGRhx96.js +1 -0
  76. peekview-0.1.0/peek/static/assets/desktop-DEIpsLCJ.js +1 -0
  77. peekview-0.1.0/peek/static/assets/diff-BgYniUM_.js +1 -0
  78. peekview-0.1.0/peek/static/assets/docker-COcR7UxN.js +1 -0
  79. peekview-0.1.0/peek/static/assets/dotenv-BjQB5zDj.js +1 -0
  80. peekview-0.1.0/peek/static/assets/dracula-BzJJZx-M.js +1 -0
  81. peekview-0.1.0/peek/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  82. peekview-0.1.0/peek/static/assets/dream-maker-C-nORZOA.js +1 -0
  83. peekview-0.1.0/peek/static/assets/edge-D5gP-w-T.js +1 -0
  84. peekview-0.1.0/peek/static/assets/elixir-CLiX3zqd.js +1 -0
  85. peekview-0.1.0/peek/static/assets/elm-CmHSxxaM.js +1 -0
  86. peekview-0.1.0/peek/static/assets/emacs-lisp-BX77sIaO.js +1 -0
  87. peekview-0.1.0/peek/static/assets/erb-BYTLMnw6.js +1 -0
  88. peekview-0.1.0/peek/static/assets/erlang-B-DoSBHF.js +1 -0
  89. peekview-0.1.0/peek/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  90. peekview-0.1.0/peek/static/assets/everforest-light-C8M2exoo.js +1 -0
  91. peekview-0.1.0/peek/static/assets/fennel-bCA53EVm.js +1 -0
  92. peekview-0.1.0/peek/static/assets/fish-w-ucz2PV.js +1 -0
  93. peekview-0.1.0/peek/static/assets/fluent-Dayu4EKP.js +1 -0
  94. peekview-0.1.0/peek/static/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
  95. peekview-0.1.0/peek/static/assets/fortran-free-form-DKXYxT9g.js +1 -0
  96. peekview-0.1.0/peek/static/assets/fsharp-XplgxFYe.js +1 -0
  97. peekview-0.1.0/peek/static/assets/gdresource-BHYsBjWJ.js +1 -0
  98. peekview-0.1.0/peek/static/assets/gdscript-DfxzS6Rs.js +1 -0
  99. peekview-0.1.0/peek/static/assets/gdshader-SKMF96pI.js +1 -0
  100. peekview-0.1.0/peek/static/assets/genie-ajMbGru0.js +1 -0
  101. peekview-0.1.0/peek/static/assets/gherkin--30QC5Em.js +1 -0
  102. peekview-0.1.0/peek/static/assets/git-commit-i4q6IMui.js +1 -0
  103. peekview-0.1.0/peek/static/assets/git-rebase-B-v9cOL2.js +1 -0
  104. peekview-0.1.0/peek/static/assets/github-dark-DHJKELXO.js +1 -0
  105. peekview-0.1.0/peek/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  106. peekview-0.1.0/peek/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  107. peekview-0.1.0/peek/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  108. peekview-0.1.0/peek/static/assets/github-light-DAi9KRSo.js +1 -0
  109. peekview-0.1.0/peek/static/assets/github-light-default-D7oLnXFd.js +1 -0
  110. peekview-0.1.0/peek/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  111. peekview-0.1.0/peek/static/assets/gleam-B430Bg39.js +1 -0
  112. peekview-0.1.0/peek/static/assets/glimmer-js-D-cwc0-E.js +1 -0
  113. peekview-0.1.0/peek/static/assets/glimmer-ts-pgjy16dm.js +1 -0
  114. peekview-0.1.0/peek/static/assets/glsl-DBO2IWDn.js +1 -0
  115. peekview-0.1.0/peek/static/assets/gnuplot-CM8KxXT1.js +1 -0
  116. peekview-0.1.0/peek/static/assets/go-B1SYOhNW.js +1 -0
  117. peekview-0.1.0/peek/static/assets/graphql-cDcHW_If.js +1 -0
  118. peekview-0.1.0/peek/static/assets/groovy-DkBy-JyN.js +1 -0
  119. peekview-0.1.0/peek/static/assets/hack-D1yCygmZ.js +1 -0
  120. peekview-0.1.0/peek/static/assets/haml-B2EZWmdv.js +1 -0
  121. peekview-0.1.0/peek/static/assets/handlebars-BQGss363.js +1 -0
  122. peekview-0.1.0/peek/static/assets/haskell-BILxekzW.js +1 -0
  123. peekview-0.1.0/peek/static/assets/haxe-C5wWYbrZ.js +1 -0
  124. peekview-0.1.0/peek/static/assets/hcl-HzYwdGDm.js +1 -0
  125. peekview-0.1.0/peek/static/assets/hjson-T-Tgc4AT.js +1 -0
  126. peekview-0.1.0/peek/static/assets/hlsl-ifBTmRxC.js +1 -0
  127. peekview-0.1.0/peek/static/assets/houston-DnULxvSX.js +1 -0
  128. peekview-0.1.0/peek/static/assets/html-C2L_23MC.js +1 -0
  129. peekview-0.1.0/peek/static/assets/html-derivative-CSfWNPLT.js +1 -0
  130. peekview-0.1.0/peek/static/assets/http-FRrOvY1W.js +1 -0
  131. peekview-0.1.0/peek/static/assets/hxml-TIA70rKU.js +1 -0
  132. peekview-0.1.0/peek/static/assets/hy-BMj5Y0dO.js +1 -0
  133. peekview-0.1.0/peek/static/assets/imba-bv_oIlVt.js +1 -0
  134. peekview-0.1.0/peek/static/assets/index-CMhcFTfH.js +26 -0
  135. peekview-0.1.0/peek/static/assets/index-DvcXrXhI.css +1 -0
  136. peekview-0.1.0/peek/static/assets/ini-BjABl1g7.js +1 -0
  137. peekview-0.1.0/peek/static/assets/java-xI-RfyKK.js +1 -0
  138. peekview-0.1.0/peek/static/assets/javascript-ySlJ1b_l.js +1 -0
  139. peekview-0.1.0/peek/static/assets/jinja-DGy0s7-h.js +1 -0
  140. peekview-0.1.0/peek/static/assets/jison-BqZprYcd.js +1 -0
  141. peekview-0.1.0/peek/static/assets/json-BQoSv7ci.js +1 -0
  142. peekview-0.1.0/peek/static/assets/json5-w8dY5SsB.js +1 -0
  143. peekview-0.1.0/peek/static/assets/jsonc-TU54ms6u.js +1 -0
  144. peekview-0.1.0/peek/static/assets/jsonl-DREVFZK8.js +1 -0
  145. peekview-0.1.0/peek/static/assets/jsonnet-BfivnA6A.js +1 -0
  146. peekview-0.1.0/peek/static/assets/jssm-P4WzXJd0.js +1 -0
  147. peekview-0.1.0/peek/static/assets/jsx-BAng5TT0.js +1 -0
  148. peekview-0.1.0/peek/static/assets/julia-BBuGR-5E.js +1 -0
  149. peekview-0.1.0/peek/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  150. peekview-0.1.0/peek/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  151. peekview-0.1.0/peek/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  152. peekview-0.1.0/peek/static/assets/kotlin-B5lbUyaz.js +1 -0
  153. peekview-0.1.0/peek/static/assets/kusto-mebxcVVE.js +1 -0
  154. peekview-0.1.0/peek/static/assets/laserwave-DUszq2jm.js +1 -0
  155. peekview-0.1.0/peek/static/assets/latex-C-cWTeAZ.js +1 -0
  156. peekview-0.1.0/peek/static/assets/lean-XBlWyCtg.js +1 -0
  157. peekview-0.1.0/peek/static/assets/less-BfCpw3nA.js +1 -0
  158. peekview-0.1.0/peek/static/assets/light-plus-B7mTdjB0.js +1 -0
  159. peekview-0.1.0/peek/static/assets/liquid-D3W5UaiH.js +1 -0
  160. peekview-0.1.0/peek/static/assets/log-Cc5clBb7.js +1 -0
  161. peekview-0.1.0/peek/static/assets/logo-IuBKFhSY.js +1 -0
  162. peekview-0.1.0/peek/static/assets/lua-CvWAzNxB.js +1 -0
  163. peekview-0.1.0/peek/static/assets/luau-Du5NY7AG.js +1 -0
  164. peekview-0.1.0/peek/static/assets/make-Bvotw-X0.js +1 -0
  165. peekview-0.1.0/peek/static/assets/markdown-UIAJJxZW.js +1 -0
  166. peekview-0.1.0/peek/static/assets/marko-z0MBrx5-.js +1 -0
  167. peekview-0.1.0/peek/static/assets/material-theme-D5KoaKCx.js +1 -0
  168. peekview-0.1.0/peek/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  169. peekview-0.1.0/peek/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  170. peekview-0.1.0/peek/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  171. peekview-0.1.0/peek/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  172. peekview-0.1.0/peek/static/assets/matlab-D9-PGadD.js +1 -0
  173. peekview-0.1.0/peek/static/assets/mdc-DB_EDNY_.js +1 -0
  174. peekview-0.1.0/peek/static/assets/mdx-sdHcTMYB.js +1 -0
  175. peekview-0.1.0/peek/static/assets/mermaid-Ci6OQyBP.js +1 -0
  176. peekview-0.1.0/peek/static/assets/min-dark-CafNBF8u.js +1 -0
  177. peekview-0.1.0/peek/static/assets/min-light-CTRr51gU.js +1 -0
  178. peekview-0.1.0/peek/static/assets/mipsasm-BC5c_5Pe.js +1 -0
  179. peekview-0.1.0/peek/static/assets/mojo-Tz6hzZYG.js +1 -0
  180. peekview-0.1.0/peek/static/assets/monokai-D4h5O-jR.js +1 -0
  181. peekview-0.1.0/peek/static/assets/move-DB_GagMm.js +1 -0
  182. peekview-0.1.0/peek/static/assets/narrat-DLbgOhZU.js +1 -0
  183. peekview-0.1.0/peek/static/assets/nextflow-B0XVJmRM.js +1 -0
  184. peekview-0.1.0/peek/static/assets/nginx-D_VnBJ67.js +1 -0
  185. peekview-0.1.0/peek/static/assets/night-owl-C39BiMTA.js +1 -0
  186. peekview-0.1.0/peek/static/assets/nim-ZlGxZxc3.js +1 -0
  187. peekview-0.1.0/peek/static/assets/nix-shcSOmrb.js +1 -0
  188. peekview-0.1.0/peek/static/assets/nord-Ddv68eIx.js +1 -0
  189. peekview-0.1.0/peek/static/assets/nushell-D4Tzg5kh.js +1 -0
  190. peekview-0.1.0/peek/static/assets/objective-c-Deuh7S70.js +1 -0
  191. peekview-0.1.0/peek/static/assets/objective-cpp-BUEGK8hf.js +1 -0
  192. peekview-0.1.0/peek/static/assets/ocaml-BNioltXt.js +1 -0
  193. peekview-0.1.0/peek/static/assets/one-dark-pro-GBQ2dnAY.js +1 -0
  194. peekview-0.1.0/peek/static/assets/one-light-PoHY5YXO.js +1 -0
  195. peekview-0.1.0/peek/static/assets/pascal-JqZropPD.js +1 -0
  196. peekview-0.1.0/peek/static/assets/perl-CHQXSrWU.js +1 -0
  197. peekview-0.1.0/peek/static/assets/php-B5ebYQev.js +1 -0
  198. peekview-0.1.0/peek/static/assets/plastic-3e1v2bzS.js +1 -0
  199. peekview-0.1.0/peek/static/assets/plsql-LKU2TuZ1.js +1 -0
  200. peekview-0.1.0/peek/static/assets/po-BFLt1xDp.js +1 -0
  201. peekview-0.1.0/peek/static/assets/poimandres-CS3Unz2-.js +1 -0
  202. peekview-0.1.0/peek/static/assets/polar-DKykz6zU.js +1 -0
  203. peekview-0.1.0/peek/static/assets/postcss-B3ZDOciz.js +1 -0
  204. peekview-0.1.0/peek/static/assets/powerquery-CSHBycmS.js +1 -0
  205. peekview-0.1.0/peek/static/assets/powershell-BIEUsx6d.js +1 -0
  206. peekview-0.1.0/peek/static/assets/prisma-B48N-Iqd.js +1 -0
  207. peekview-0.1.0/peek/static/assets/prolog-BY-TUvya.js +1 -0
  208. peekview-0.1.0/peek/static/assets/proto-zocC4JxJ.js +1 -0
  209. peekview-0.1.0/peek/static/assets/pug-CM9l7STV.js +1 -0
  210. peekview-0.1.0/peek/static/assets/puppet-Cza_XSSt.js +1 -0
  211. peekview-0.1.0/peek/static/assets/purescript-Bg-kzb6g.js +1 -0
  212. peekview-0.1.0/peek/static/assets/python-DhUJRlN_.js +1 -0
  213. peekview-0.1.0/peek/static/assets/qml-D8XfuvdV.js +1 -0
  214. peekview-0.1.0/peek/static/assets/qmldir-C8lEn-DE.js +1 -0
  215. peekview-0.1.0/peek/static/assets/qss-DhMKtDLN.js +1 -0
  216. peekview-0.1.0/peek/static/assets/r-CwjWoCRV.js +1 -0
  217. peekview-0.1.0/peek/static/assets/racket-CzouJOBO.js +1 -0
  218. peekview-0.1.0/peek/static/assets/raku-B1bQXN8T.js +1 -0
  219. peekview-0.1.0/peek/static/assets/razor-CNLDkMZG.js +1 -0
  220. peekview-0.1.0/peek/static/assets/red-bN70gL4F.js +1 -0
  221. peekview-0.1.0/peek/static/assets/reg-5LuOXUq_.js +1 -0
  222. peekview-0.1.0/peek/static/assets/regexp-DWJ3fJO_.js +1 -0
  223. peekview-0.1.0/peek/static/assets/rel-DJlmqQ1C.js +1 -0
  224. peekview-0.1.0/peek/static/assets/riscv-QhoSD0DR.js +1 -0
  225. peekview-0.1.0/peek/static/assets/rose-pine-CmCqftbK.js +1 -0
  226. peekview-0.1.0/peek/static/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
  227. peekview-0.1.0/peek/static/assets/rose-pine-moon-CjDtw9vr.js +1 -0
  228. peekview-0.1.0/peek/static/assets/rst-4NLicBqY.js +1 -0
  229. peekview-0.1.0/peek/static/assets/ruby-DeZ3UC14.js +1 -0
  230. peekview-0.1.0/peek/static/assets/rust-Be6lgOlo.js +1 -0
  231. peekview-0.1.0/peek/static/assets/sas-BmTFh92c.js +1 -0
  232. peekview-0.1.0/peek/static/assets/sass-BJ4Li9vH.js +1 -0
  233. peekview-0.1.0/peek/static/assets/scala-DQVVAn-B.js +1 -0
  234. peekview-0.1.0/peek/static/assets/scheme-BJGe-b2p.js +1 -0
  235. peekview-0.1.0/peek/static/assets/scss-C31hgJw-.js +1 -0
  236. peekview-0.1.0/peek/static/assets/sdbl-BLhTXw86.js +1 -0
  237. peekview-0.1.0/peek/static/assets/shaderlab-B7qAK45m.js +1 -0
  238. peekview-0.1.0/peek/static/assets/shellscript-atvbtKCR.js +1 -0
  239. peekview-0.1.0/peek/static/assets/shellsession-C_rIy8kc.js +1 -0
  240. peekview-0.1.0/peek/static/assets/slack-dark-BthQWCQV.js +1 -0
  241. peekview-0.1.0/peek/static/assets/slack-ochin-DqwNpetd.js +1 -0
  242. peekview-0.1.0/peek/static/assets/smalltalk-DkLiglaE.js +1 -0
  243. peekview-0.1.0/peek/static/assets/snazzy-light-Bw305WKR.js +1 -0
  244. peekview-0.1.0/peek/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  245. peekview-0.1.0/peek/static/assets/solarized-light-L9t79GZl.js +1 -0
  246. peekview-0.1.0/peek/static/assets/solidity-C1w2a3ep.js +1 -0
  247. peekview-0.1.0/peek/static/assets/soy-C-lX7w71.js +1 -0
  248. peekview-0.1.0/peek/static/assets/sparql-bYkjHRlG.js +1 -0
  249. peekview-0.1.0/peek/static/assets/splunk-Cf8iN4DR.js +1 -0
  250. peekview-0.1.0/peek/static/assets/sql-COK4E0Yg.js +1 -0
  251. peekview-0.1.0/peek/static/assets/ssh-config-BknIz3MU.js +1 -0
  252. peekview-0.1.0/peek/static/assets/stata-DorPZHa4.js +1 -0
  253. peekview-0.1.0/peek/static/assets/stylus-BeQkCIfX.js +1 -0
  254. peekview-0.1.0/peek/static/assets/svelte-MSaWC3Je.js +1 -0
  255. peekview-0.1.0/peek/static/assets/swift-BSxZ-RaX.js +1 -0
  256. peekview-0.1.0/peek/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  257. peekview-0.1.0/peek/static/assets/system-verilog-C7L56vO4.js +1 -0
  258. peekview-0.1.0/peek/static/assets/systemd-CUnW07Te.js +1 -0
  259. peekview-0.1.0/peek/static/assets/talonscript-C1XDQQGZ.js +1 -0
  260. peekview-0.1.0/peek/static/assets/tasl-CQjiPCtT.js +1 -0
  261. peekview-0.1.0/peek/static/assets/tcl-DQ1-QYvQ.js +1 -0
  262. peekview-0.1.0/peek/static/assets/templ-dwX3ZSMB.js +1 -0
  263. peekview-0.1.0/peek/static/assets/terraform-BbSNqyBO.js +1 -0
  264. peekview-0.1.0/peek/static/assets/tex-rYs2v40G.js +1 -0
  265. peekview-0.1.0/peek/static/assets/tokyo-night-DBQeEorK.js +1 -0
  266. peekview-0.1.0/peek/static/assets/toml-CB2ApiWb.js +1 -0
  267. peekview-0.1.0/peek/static/assets/ts-tags-CipyTH0X.js +1 -0
  268. peekview-0.1.0/peek/static/assets/tsv-B_m7g4N7.js +1 -0
  269. peekview-0.1.0/peek/static/assets/tsx-B6W0miNI.js +1 -0
  270. peekview-0.1.0/peek/static/assets/turtle-BMR_PYu6.js +1 -0
  271. peekview-0.1.0/peek/static/assets/twig-NC5TFiHP.js +1 -0
  272. peekview-0.1.0/peek/static/assets/typescript-Dj6nwHGl.js +1 -0
  273. peekview-0.1.0/peek/static/assets/typespec-BpWG_bgh.js +1 -0
  274. peekview-0.1.0/peek/static/assets/typst-BVUVsWT6.js +1 -0
  275. peekview-0.1.0/peek/static/assets/useEntry-DSu-PUHy.js +1 -0
  276. peekview-0.1.0/peek/static/assets/useEntry-Dgrs0_hj.css +1 -0
  277. peekview-0.1.0/peek/static/assets/v-CAQ2eGtk.js +1 -0
  278. peekview-0.1.0/peek/static/assets/vala-BFOHcciG.js +1 -0
  279. peekview-0.1.0/peek/static/assets/vb-CdO5JTpU.js +1 -0
  280. peekview-0.1.0/peek/static/assets/verilog-CJaU5se_.js +1 -0
  281. peekview-0.1.0/peek/static/assets/vesper-BEBZ7ncR.js +1 -0
  282. peekview-0.1.0/peek/static/assets/vhdl-DYoNaHQp.js +1 -0
  283. peekview-0.1.0/peek/static/assets/viml-m4uW47V2.js +1 -0
  284. peekview-0.1.0/peek/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  285. peekview-0.1.0/peek/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  286. peekview-0.1.0/peek/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  287. peekview-0.1.0/peek/static/assets/vue-BuYVFjOK.js +1 -0
  288. peekview-0.1.0/peek/static/assets/vue-html-xdeiXROB.js +1 -0
  289. peekview-0.1.0/peek/static/assets/vyper-nyqBNV6O.js +1 -0
  290. peekview-0.1.0/peek/static/assets/wasm-C6j12Q_x.js +1 -0
  291. peekview-0.1.0/peek/static/assets/wasm-CG6Dc4jp.js +1 -0
  292. peekview-0.1.0/peek/static/assets/wenyan-7A4Fjokl.js +1 -0
  293. peekview-0.1.0/peek/static/assets/wgsl-CB0Krxn9.js +1 -0
  294. peekview-0.1.0/peek/static/assets/wikitext-DCE3LsBG.js +1 -0
  295. peekview-0.1.0/peek/static/assets/wolfram-C3FkfJm5.js +1 -0
  296. peekview-0.1.0/peek/static/assets/xml-e3z08dGr.js +1 -0
  297. peekview-0.1.0/peek/static/assets/xsl-Dd0NUgwM.js +1 -0
  298. peekview-0.1.0/peek/static/assets/yaml-CVw76BM1.js +1 -0
  299. peekview-0.1.0/peek/static/assets/zenscript-HnGAYVZD.js +1 -0
  300. peekview-0.1.0/peek/static/assets/zig-BVz_zdnA.js +1 -0
  301. peekview-0.1.0/peek/static/index.html +26 -0
  302. peekview-0.1.0/peek/storage.py +498 -0
  303. peekview-0.1.0/pyproject.toml +99 -0
  304. peekview-0.1.0/tests/__init__.py +1 -0
  305. peekview-0.1.0/tests/conftest.py +119 -0
  306. peekview-0.1.0/tests/factories.py +105 -0
  307. peekview-0.1.0/tests/test_api.py +226 -0
  308. peekview-0.1.0/tests/test_cli.py +441 -0
  309. peekview-0.1.0/tests/test_config.py +213 -0
  310. peekview-0.1.0/tests/test_database.py +344 -0
  311. peekview-0.1.0/tests/test_entry_service.py +207 -0
  312. peekview-0.1.0/tests/test_exceptions.py +154 -0
  313. peekview-0.1.0/tests/test_file_service.py +255 -0
  314. peekview-0.1.0/tests/test_language.py +238 -0
  315. peekview-0.1.0/tests/test_models.py +351 -0
  316. peekview-0.1.0/tests/test_security.py +601 -0
  317. peekview-0.1.0/tests/test_storage.py +480 -0
@@ -0,0 +1,38 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ *.egg
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Peek data
17
+ .peek/
18
+ *.db
19
+ *.db-wal
20
+ *.db-shm
21
+
22
+ # IDE
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ *.swo
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # Testing
33
+ .coverage
34
+ htmlcov/
35
+ .pytest_cache/
36
+
37
+ # Logs
38
+ *.log
@@ -0,0 +1,26 @@
1
+ .PHONY: dev test test-cov lint build clean
2
+
3
+ dev:
4
+ uvicorn peek.main:app --host 127.0.0.1 --port 8080 --reload
5
+
6
+ test:
7
+ python -m pytest tests/ -v --tb=short
8
+
9
+ test-cov:
10
+ python -m pytest tests/ -v --tb=short --cov=peek --cov-report=term-missing
11
+
12
+ lint:
13
+ ruff check peek/ tests/
14
+ ruff format --check peek/ tests/
15
+
16
+ format:
17
+ ruff check --fix peek/ tests/
18
+ ruff format peek/ tests/
19
+
20
+ build:
21
+ pip install -e ".[test]"
22
+
23
+ clean:
24
+ find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
25
+ find . -type f -name "*.pyc" -delete
26
+ rm -rf .pytest_cache .coverage htmlcov
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: peekview
3
+ Version: 0.1.0
4
+ Summary: A lightweight code & document formatting display service
5
+ Project-URL: Homepage, https://github.com/randomgitsrc/peek
6
+ Project-URL: Repository, https://github.com/randomgitsrc/peek
7
+ Author: Peek Team
8
+ License: MIT
9
+ Keywords: agent,code,documentation,mcp,viewer
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Python: >=3.12
17
+ Requires-Dist: aiofiles>=23.2.0
18
+ Requires-Dist: click>=8.1.0
19
+ Requires-Dist: fastapi>=0.109.0
20
+ Requires-Dist: pydantic-settings>=2.1.0
21
+ Requires-Dist: pydantic>=2.5.0
22
+ Requires-Dist: python-multipart>=0.0.6
23
+ Requires-Dist: sqlmodel>=0.0.14
24
+ Requires-Dist: uvicorn[standard]>=0.27.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
27
+ Requires-Dist: ruff>=0.2.0; extra == 'dev'
28
+ Provides-Extra: test
29
+ Requires-Dist: httpx>=0.26.0; extra == 'test'
30
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
31
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
32
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Peek
36
+
37
+ A lightweight code & document formatting display service.
38
+
39
+ > Agent(AI)产出 → Peek 格式化 → 人类友好查看
40
+
41
+ ## 快速开始
42
+
43
+ ### 安装
44
+
45
+ ```bash
46
+ pip install peek
47
+ ```
48
+
49
+ ### 启动服务
50
+
51
+ ```bash
52
+ # 本地开发
53
+ peek serve
54
+
55
+ # 生产部署(指定端口和主机)
56
+ peek serve --host 0.0.0.0 --port 8080
57
+ ```
58
+
59
+ 服务启动后,访问 http://localhost:8080 即可使用 Web 界面。
60
+
61
+ ## 命令行用法
62
+
63
+ ### 创建条目
64
+
65
+ ```bash
66
+ # 从文件创建
67
+ peek create file.txt -s "My document"
68
+
69
+ # 从多文件创建
70
+ peek create src/*.py -s "Python project" -t python -t cli
71
+
72
+ # 从标准输入创建
73
+ echo "print('hello')" | peek create -s "From stdin" --from-stdin
74
+
75
+ # 指定自定义 slug
76
+ peek create README.md -s "Documentation" --slug docs
77
+ ```
78
+
79
+ ### 查看条目
80
+
81
+ ```bash
82
+ # 查看条目详情
83
+ peek get my-entry
84
+
85
+ # 列出入库(支持分页)
86
+ peek list
87
+ peek list --page 2 --per-page 50
88
+
89
+ # 搜索条目(FTS5 全文搜索)
90
+ peek list -q "python function"
91
+
92
+ # 按标签过滤
93
+ peek list -t python -t cli
94
+ ```
95
+
96
+ ### 删除条目
97
+
98
+ ```bash
99
+ # 删除条目(会要求确认)
100
+ peek delete my-entry
101
+
102
+ # 强制删除(无需确认)
103
+ peek delete my-entry --force
104
+ ```
105
+
106
+ ## 配置
107
+
108
+ 通过环境变量配置:
109
+
110
+ | 变量 | 默认值 | 说明 |
111
+ |------|--------|------|
112
+ | `PEEK_DATA_DIR` | `~/.peek/data` | 文件存储目录 |
113
+ | `PEEK_DB_PATH` | `~/.peek/peek.db` | SQLite 数据库路径 |
114
+ | `PEEK_ALLOWED_PATHS` | `[]` | 允许读取的本地路径列表 |
115
+ | `PEEK_HOST` | `127.0.0.1` | 服务绑定地址 |
116
+ | `PEEK_PORT` | `8080` | 服务端口 |
117
+ | `PEEK_API_KEY` | `` | API 认证密钥(可选) |
118
+ | `PEEK_CORS_ORIGINS` | `http://localhost:5173` | CORS 允许来源 |
119
+
120
+ ### 配置文件
121
+
122
+ 也可以将配置写入 `.env` 文件:
123
+
124
+ ```bash
125
+ PEEK_DATA_DIR=/var/peek/data
126
+ PEEK_DB_PATH=/var/peek/peek.db
127
+ PEEK_HOST=0.0.0.0
128
+ PEEK_PORT=8080
129
+ PEEK_API_KEY=your-secret-key
130
+ ```
131
+
132
+ ## 特性
133
+
134
+ - 🎨 **代码高亮** - 基于 Shiki 的语法高亮,支持 100+ 语言
135
+ - 📝 **Markdown 渲染** - 支持 GitHub 风格 Markdown
136
+ - 🔍 **全文搜索** - 基于 SQLite FTS5 的高性能搜索
137
+ - 📂 **多文件支持** - 单条目支持多文件,树形展示
138
+ - 🌓 **主题切换** - 深色/浅色模式,自动跟随系统
139
+ - 📱 **移动端适配** - 响应式设计,底部工具栏
140
+ - 🔗 **URL 友好** - 支持 slug 和文件路径参数
141
+ - 🔒 **安全防护** - 路径遍历防护、API 认证、XSS 过滤
142
+
143
+ ## 技术栈
144
+
145
+ - **后端**: FastAPI + SQLModel + SQLite (FTS5)
146
+ - **前端**: Vue 3 + Vite + Shiki + TypeScript
147
+ - **CLI**: Click + Rich
148
+
149
+ ## 开发
150
+
151
+ ```bash
152
+ # 克隆仓库
153
+ git clone https://github.com/randomgitsrc/peek.git
154
+ cd peek/backend
155
+
156
+ # 安装开发依赖
157
+ pip install -e ".[test,dev]"
158
+
159
+ # 运行测试
160
+ make test
161
+
162
+ # 格式化代码
163
+ make format
164
+
165
+ # 启动开发服务器
166
+ make dev
167
+ ```
168
+
169
+ ## 许可证
170
+
171
+ MIT License
@@ -0,0 +1,137 @@
1
+ # Peek
2
+
3
+ A lightweight code & document formatting display service.
4
+
5
+ > Agent(AI)产出 → Peek 格式化 → 人类友好查看
6
+
7
+ ## 快速开始
8
+
9
+ ### 安装
10
+
11
+ ```bash
12
+ pip install peek
13
+ ```
14
+
15
+ ### 启动服务
16
+
17
+ ```bash
18
+ # 本地开发
19
+ peek serve
20
+
21
+ # 生产部署(指定端口和主机)
22
+ peek serve --host 0.0.0.0 --port 8080
23
+ ```
24
+
25
+ 服务启动后,访问 http://localhost:8080 即可使用 Web 界面。
26
+
27
+ ## 命令行用法
28
+
29
+ ### 创建条目
30
+
31
+ ```bash
32
+ # 从文件创建
33
+ peek create file.txt -s "My document"
34
+
35
+ # 从多文件创建
36
+ peek create src/*.py -s "Python project" -t python -t cli
37
+
38
+ # 从标准输入创建
39
+ echo "print('hello')" | peek create -s "From stdin" --from-stdin
40
+
41
+ # 指定自定义 slug
42
+ peek create README.md -s "Documentation" --slug docs
43
+ ```
44
+
45
+ ### 查看条目
46
+
47
+ ```bash
48
+ # 查看条目详情
49
+ peek get my-entry
50
+
51
+ # 列出入库(支持分页)
52
+ peek list
53
+ peek list --page 2 --per-page 50
54
+
55
+ # 搜索条目(FTS5 全文搜索)
56
+ peek list -q "python function"
57
+
58
+ # 按标签过滤
59
+ peek list -t python -t cli
60
+ ```
61
+
62
+ ### 删除条目
63
+
64
+ ```bash
65
+ # 删除条目(会要求确认)
66
+ peek delete my-entry
67
+
68
+ # 强制删除(无需确认)
69
+ peek delete my-entry --force
70
+ ```
71
+
72
+ ## 配置
73
+
74
+ 通过环境变量配置:
75
+
76
+ | 变量 | 默认值 | 说明 |
77
+ |------|--------|------|
78
+ | `PEEK_DATA_DIR` | `~/.peek/data` | 文件存储目录 |
79
+ | `PEEK_DB_PATH` | `~/.peek/peek.db` | SQLite 数据库路径 |
80
+ | `PEEK_ALLOWED_PATHS` | `[]` | 允许读取的本地路径列表 |
81
+ | `PEEK_HOST` | `127.0.0.1` | 服务绑定地址 |
82
+ | `PEEK_PORT` | `8080` | 服务端口 |
83
+ | `PEEK_API_KEY` | `` | API 认证密钥(可选) |
84
+ | `PEEK_CORS_ORIGINS` | `http://localhost:5173` | CORS 允许来源 |
85
+
86
+ ### 配置文件
87
+
88
+ 也可以将配置写入 `.env` 文件:
89
+
90
+ ```bash
91
+ PEEK_DATA_DIR=/var/peek/data
92
+ PEEK_DB_PATH=/var/peek/peek.db
93
+ PEEK_HOST=0.0.0.0
94
+ PEEK_PORT=8080
95
+ PEEK_API_KEY=your-secret-key
96
+ ```
97
+
98
+ ## 特性
99
+
100
+ - 🎨 **代码高亮** - 基于 Shiki 的语法高亮,支持 100+ 语言
101
+ - 📝 **Markdown 渲染** - 支持 GitHub 风格 Markdown
102
+ - 🔍 **全文搜索** - 基于 SQLite FTS5 的高性能搜索
103
+ - 📂 **多文件支持** - 单条目支持多文件,树形展示
104
+ - 🌓 **主题切换** - 深色/浅色模式,自动跟随系统
105
+ - 📱 **移动端适配** - 响应式设计,底部工具栏
106
+ - 🔗 **URL 友好** - 支持 slug 和文件路径参数
107
+ - 🔒 **安全防护** - 路径遍历防护、API 认证、XSS 过滤
108
+
109
+ ## 技术栈
110
+
111
+ - **后端**: FastAPI + SQLModel + SQLite (FTS5)
112
+ - **前端**: Vue 3 + Vite + Shiki + TypeScript
113
+ - **CLI**: Click + Rich
114
+
115
+ ## 开发
116
+
117
+ ```bash
118
+ # 克隆仓库
119
+ git clone https://github.com/randomgitsrc/peek.git
120
+ cd peek/backend
121
+
122
+ # 安装开发依赖
123
+ pip install -e ".[test,dev]"
124
+
125
+ # 运行测试
126
+ make test
127
+
128
+ # 格式化代码
129
+ make format
130
+
131
+ # 启动开发服务器
132
+ make dev
133
+ ```
134
+
135
+ ## 许可证
136
+
137
+ MIT License
@@ -0,0 +1,3 @@
1
+ """Peek - A lightweight code & document formatting display service."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m peek."""
2
+
3
+ from peek.cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -0,0 +1 @@
1
+ """Peek API routes."""
@@ -0,0 +1,123 @@
1
+ """Entry CRUD API routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter, Depends, Query, Request
6
+ from fastapi.responses import JSONResponse
7
+
8
+ from peek.models import CreateEntryRequest, EntryUpdate
9
+ from peek.services.entry_service import EntryService, get_entry_service
10
+
11
+ router = APIRouter(prefix="/api/v1/entries", tags=["entries"])
12
+
13
+
14
+ def _get_service(request: Request) -> EntryService:
15
+ """Get EntryService from app state."""
16
+ return get_entry_service(request.app)
17
+
18
+
19
+ @router.post("", status_code=201)
20
+ async def create_entry(
21
+ data: CreateEntryRequest,
22
+ request: Request,
23
+ service: EntryService = Depends(_get_service),
24
+ ):
25
+ """Create a new entry. Returns 201 Created."""
26
+ # Convert files and dirs to dicts
27
+ files_data = []
28
+ for f in data.files:
29
+ file_dict = {}
30
+ if f.path is not None:
31
+ file_dict["path"] = f.path
32
+ if f.content is not None:
33
+ file_dict["content"] = f.content
34
+ if f.local_path is not None:
35
+ file_dict["local_path"] = f.local_path
36
+ files_data.append(file_dict)
37
+
38
+ dirs_data = []
39
+ for d in data.dirs:
40
+ dirs_data.append({"path": d.path})
41
+
42
+ return service.create_entry(
43
+ summary=data.summary,
44
+ slug=data.slug,
45
+ tags=data.tags,
46
+ files_data=files_data if files_data else None,
47
+ dirs_data=dirs_data if dirs_data else None,
48
+ expires_in=data.expires_in,
49
+ )
50
+
51
+
52
+ @router.get("")
53
+ async def list_entries(
54
+ request: Request,
55
+ q: str | None = Query(None),
56
+ tags: str | None = Query(None),
57
+ status: str | None = Query(None),
58
+ page: int = Query(1, ge=1),
59
+ per_page: int = Query(20, ge=1, le=100),
60
+ service: EntryService = Depends(_get_service),
61
+ ):
62
+ """List entries with search, filter, and pagination."""
63
+ tag_list = tags.split(",") if tags else None
64
+ return service.list_entries(q=q, tags=tag_list, status=status, page=page, per_page=per_page)
65
+
66
+
67
+ @router.get("/{slug}")
68
+ async def get_entry(
69
+ slug: str,
70
+ request: Request,
71
+ service: EntryService = Depends(_get_service),
72
+ ):
73
+ """Get entry details by slug."""
74
+ return service.get_entry(slug)
75
+
76
+
77
+ @router.patch("/{slug}")
78
+ async def update_entry(
79
+ slug: str,
80
+ data: EntryUpdate,
81
+ request: Request,
82
+ service: EntryService = Depends(_get_service),
83
+ ):
84
+ """Update an entry."""
85
+ # Convert add_files to dicts
86
+ add_files = None
87
+ if data.add_files:
88
+ add_files = []
89
+ for f in data.add_files:
90
+ file_dict = {}
91
+ if f.path is not None:
92
+ file_dict["path"] = f.path
93
+ if f.content is not None:
94
+ file_dict["content"] = f.content
95
+ if f.local_path is not None:
96
+ file_dict["local_path"] = f.local_path
97
+ add_files.append(file_dict)
98
+
99
+ # Convert add_dirs to dicts
100
+ add_dirs = None
101
+ if data.add_dirs:
102
+ add_dirs = [{"path": d.path} for d in data.add_dirs]
103
+
104
+ return service.update_entry(
105
+ slug=slug,
106
+ summary=data.summary,
107
+ status=data.status,
108
+ tags=data.tags,
109
+ add_files=add_files,
110
+ remove_file_ids=data.remove_file_ids,
111
+ add_dirs=add_dirs,
112
+ )
113
+
114
+
115
+ @router.delete("/{slug}")
116
+ async def delete_entry(
117
+ slug: str,
118
+ request: Request,
119
+ service: EntryService = Depends(_get_service),
120
+ ):
121
+ """Delete entry by slug."""
122
+ service.delete_entry(slug)
123
+ return {"ok": True}
@@ -0,0 +1,114 @@
1
+ """File download and content API routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from fastapi import APIRouter, Request
8
+ from fastapi.responses import Response
9
+ from sqlmodel import Session, select
10
+
11
+ from peek.database import get_engine
12
+ from peek.exceptions import NotFoundError
13
+ from peek.models import Entry, File
14
+ from peek.storage import StorageManager
15
+
16
+ router = APIRouter(prefix="/api/v1/entries", tags=["files"])
17
+
18
+
19
+ def _sanitize_filename(filename: str) -> str:
20
+ """Sanitize filename for Content-Disposition header to prevent injection.
21
+
22
+ Removes quotes, semicolons, and newlines that could break the header.
23
+ """
24
+ # Remove characters that could inject additional headers
25
+ sanitized = re.sub(r'[";\r\n]', "", filename)
26
+ # Limit length
27
+ if len(sanitized) > 200:
28
+ sanitized = sanitized[:200]
29
+ return sanitized
30
+
31
+
32
+ def _language_to_content_type(language: str | None) -> str:
33
+ """Map language ID to Content-Type for inline display."""
34
+ _TYPE_MAP = {
35
+ "python": "text/x-python",
36
+ "javascript": "text/javascript",
37
+ "typescript": "text/typescript",
38
+ "html": "text/html",
39
+ "css": "text/css",
40
+ "json": "application/json",
41
+ "yaml": "text/yaml",
42
+ "xml": "text/xml",
43
+ "markdown": "text/markdown",
44
+ "sql": "text/x-sql",
45
+ "bash": "text/x-shellscript",
46
+ "go": "text/x-go",
47
+ "rust": "text/x-rust",
48
+ "java": "text/x-java",
49
+ "cpp": "text/x-c++src",
50
+ "text": "text/plain",
51
+ }
52
+ if language and language in _TYPE_MAP:
53
+ return _TYPE_MAP[language]
54
+ return "text/plain; charset=utf-8"
55
+
56
+
57
+ @router.get("/{slug}/files/{file_id}")
58
+ async def download_file(slug: str, file_id: int, request: Request):
59
+ """Download a single file (with Content-Disposition: attachment)."""
60
+ config = request.app.state.config
61
+ engine = get_engine(config)
62
+ storage = StorageManager(config=config)
63
+
64
+ with Session(engine) as session:
65
+ entry = session.exec(select(Entry).where(Entry.slug == slug)).first()
66
+ if not entry:
67
+ raise NotFoundError(f"Entry not found: {slug}")
68
+
69
+ file_record = session.exec(
70
+ select(File).where(File.id == file_id, File.entry_id == entry.id)
71
+ ).first()
72
+ if not file_record:
73
+ raise NotFoundError(f"File not found: {file_id}")
74
+
75
+ content = storage.read_file(entry.id, file_record.filename, file_record.path)
76
+ safe_name = _sanitize_filename(file_record.filename)
77
+ return Response(
78
+ content=content,
79
+ media_type="application/octet-stream",
80
+ headers={"Content-Disposition": f'attachment; filename="{safe_name}"'},
81
+ )
82
+
83
+
84
+ @router.get("/{slug}/files/{file_id}/content")
85
+ async def get_file_content(slug: str, file_id: int, request: Request):
86
+ """Get file content inline (raw text, no Content-Disposition).
87
+
88
+ Returns the file content with an appropriate Content-Type based on
89
+ language. No Content-Disposition header — suitable for inline display.
90
+ """
91
+ config = request.app.state.config
92
+ engine = get_engine(config)
93
+ storage = StorageManager(config=config)
94
+
95
+ with Session(engine) as session:
96
+ entry = session.exec(select(Entry).where(Entry.slug == slug)).first()
97
+ if not entry:
98
+ raise NotFoundError(f"Entry not found: {slug}")
99
+
100
+ file_record = session.exec(
101
+ select(File).where(File.id == file_id, File.entry_id == entry.id)
102
+ ).first()
103
+ if not file_record:
104
+ raise NotFoundError(f"File not found: {file_id}")
105
+
106
+ content = storage.read_file(entry.id, file_record.filename, file_record.path)
107
+
108
+ # Determine Content-Type from language
109
+ content_type = _language_to_content_type(file_record.language)
110
+ return Response(
111
+ content=content,
112
+ media_type=content_type,
113
+ # No Content-Disposition — inline display
114
+ )