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.
- peekview-0.1.0/.gitignore +38 -0
- peekview-0.1.0/Makefile +26 -0
- peekview-0.1.0/PKG-INFO +171 -0
- peekview-0.1.0/README.md +137 -0
- peekview-0.1.0/peek/__init__.py +3 -0
- peekview-0.1.0/peek/__main__.py +6 -0
- peekview-0.1.0/peek/api/__init__.py +1 -0
- peekview-0.1.0/peek/api/entries.py +123 -0
- peekview-0.1.0/peek/api/files.py +114 -0
- peekview-0.1.0/peek/cli.py +332 -0
- peekview-0.1.0/peek/config.py +232 -0
- peekview-0.1.0/peek/database.py +304 -0
- peekview-0.1.0/peek/exceptions.py +127 -0
- peekview-0.1.0/peek/language.py +407 -0
- peekview-0.1.0/peek/main.py +224 -0
- peekview-0.1.0/peek/models.py +330 -0
- peekview-0.1.0/peek/services/__init__.py +1 -0
- peekview-0.1.0/peek/services/entry_service.py +612 -0
- peekview-0.1.0/peek/services/file_service.py +231 -0
- peekview-0.1.0/peek/static/assets/EntryDetailView-Cnhdjikj.css +1 -0
- peekview-0.1.0/peek/static/assets/EntryDetailView-yI3ydvJn.js +77 -0
- peekview-0.1.0/peek/static/assets/EntryListView-CjYcs8e1.js +1 -0
- peekview-0.1.0/peek/static/assets/EntryListView-DjPX64rQ.css +1 -0
- peekview-0.1.0/peek/static/assets/abap-DsBKuouk.js +1 -0
- peekview-0.1.0/peek/static/assets/actionscript-3-D_z4Izcz.js +1 -0
- peekview-0.1.0/peek/static/assets/ada-727ZlQH0.js +1 -0
- peekview-0.1.0/peek/static/assets/andromeeda-C3khCPGq.js +1 -0
- peekview-0.1.0/peek/static/assets/angular-html-LfdN0zeE.js +1 -0
- peekview-0.1.0/peek/static/assets/angular-ts-CKsD7JZE.js +1 -0
- peekview-0.1.0/peek/static/assets/apache-Dn00JSTd.js +1 -0
- peekview-0.1.0/peek/static/assets/apex-COJ4H7py.js +1 -0
- peekview-0.1.0/peek/static/assets/apl-BBq3IX1j.js +1 -0
- peekview-0.1.0/peek/static/assets/applescript-Bu5BbsvL.js +1 -0
- peekview-0.1.0/peek/static/assets/ara-7O62HKoU.js +1 -0
- peekview-0.1.0/peek/static/assets/asciidoc-BPT9niGB.js +1 -0
- peekview-0.1.0/peek/static/assets/asm-Dhn9LcZ4.js +1 -0
- peekview-0.1.0/peek/static/assets/astro-CqkE3fuf.js +1 -0
- peekview-0.1.0/peek/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- peekview-0.1.0/peek/static/assets/awk-eg146-Ew.js +1 -0
- peekview-0.1.0/peek/static/assets/ayu-dark-Cv9koXgw.js +1 -0
- peekview-0.1.0/peek/static/assets/ballerina-Du268qiB.js +1 -0
- peekview-0.1.0/peek/static/assets/bat-fje9CFhw.js +1 -0
- peekview-0.1.0/peek/static/assets/beancount-BwXTMy5W.js +1 -0
- peekview-0.1.0/peek/static/assets/berry-3xVqZejG.js +1 -0
- peekview-0.1.0/peek/static/assets/bibtex-xW4inM5L.js +1 -0
- peekview-0.1.0/peek/static/assets/bicep-DHo0CJ0O.js +1 -0
- peekview-0.1.0/peek/static/assets/blade-a8OxSdnT.js +1 -0
- peekview-0.1.0/peek/static/assets/bsl-Dgyn0ogV.js +1 -0
- peekview-0.1.0/peek/static/assets/c-C3t2pwGQ.js +1 -0
- peekview-0.1.0/peek/static/assets/cadence-DNquZEk8.js +1 -0
- peekview-0.1.0/peek/static/assets/cairo--RitsXJZ.js +1 -0
- peekview-0.1.0/peek/static/assets/catppuccin-frappe-CD_QflpE.js +1 -0
- peekview-0.1.0/peek/static/assets/catppuccin-latte-DRW-0cLl.js +1 -0
- peekview-0.1.0/peek/static/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
- peekview-0.1.0/peek/static/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
- peekview-0.1.0/peek/static/assets/clarity-BHOwM8T6.js +1 -0
- peekview-0.1.0/peek/static/assets/clojure-DxSadP1t.js +1 -0
- peekview-0.1.0/peek/static/assets/cmake-DbXoA79R.js +1 -0
- peekview-0.1.0/peek/static/assets/cobol-PTqiYgYu.js +1 -0
- peekview-0.1.0/peek/static/assets/codeowners-Bp6g37R7.js +1 -0
- peekview-0.1.0/peek/static/assets/codeql-sacFqUAJ.js +1 -0
- peekview-0.1.0/peek/static/assets/coffee-dyiR41kL.js +1 -0
- peekview-0.1.0/peek/static/assets/common-lisp-C7gG9l05.js +1 -0
- peekview-0.1.0/peek/static/assets/coq-Dsg_Bt_b.js +1 -0
- peekview-0.1.0/peek/static/assets/cpp-BksuvNSY.js +1 -0
- peekview-0.1.0/peek/static/assets/crystal-DtDmRg-F.js +1 -0
- peekview-0.1.0/peek/static/assets/csharp-D9R-vmeu.js +1 -0
- peekview-0.1.0/peek/static/assets/css-BPhBrDlE.js +1 -0
- peekview-0.1.0/peek/static/assets/csv-B0qRVHPH.js +1 -0
- peekview-0.1.0/peek/static/assets/cue-DtFQj3wx.js +1 -0
- peekview-0.1.0/peek/static/assets/cypher-m2LEI-9-.js +1 -0
- peekview-0.1.0/peek/static/assets/d-BoXegm-a.js +1 -0
- peekview-0.1.0/peek/static/assets/dark-plus-C3mMm8J8.js +1 -0
- peekview-0.1.0/peek/static/assets/dart-B9wLZaAG.js +1 -0
- peekview-0.1.0/peek/static/assets/dax-ClGRhx96.js +1 -0
- peekview-0.1.0/peek/static/assets/desktop-DEIpsLCJ.js +1 -0
- peekview-0.1.0/peek/static/assets/diff-BgYniUM_.js +1 -0
- peekview-0.1.0/peek/static/assets/docker-COcR7UxN.js +1 -0
- peekview-0.1.0/peek/static/assets/dotenv-BjQB5zDj.js +1 -0
- peekview-0.1.0/peek/static/assets/dracula-BzJJZx-M.js +1 -0
- peekview-0.1.0/peek/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- peekview-0.1.0/peek/static/assets/dream-maker-C-nORZOA.js +1 -0
- peekview-0.1.0/peek/static/assets/edge-D5gP-w-T.js +1 -0
- peekview-0.1.0/peek/static/assets/elixir-CLiX3zqd.js +1 -0
- peekview-0.1.0/peek/static/assets/elm-CmHSxxaM.js +1 -0
- peekview-0.1.0/peek/static/assets/emacs-lisp-BX77sIaO.js +1 -0
- peekview-0.1.0/peek/static/assets/erb-BYTLMnw6.js +1 -0
- peekview-0.1.0/peek/static/assets/erlang-B-DoSBHF.js +1 -0
- peekview-0.1.0/peek/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- peekview-0.1.0/peek/static/assets/everforest-light-C8M2exoo.js +1 -0
- peekview-0.1.0/peek/static/assets/fennel-bCA53EVm.js +1 -0
- peekview-0.1.0/peek/static/assets/fish-w-ucz2PV.js +1 -0
- peekview-0.1.0/peek/static/assets/fluent-Dayu4EKP.js +1 -0
- peekview-0.1.0/peek/static/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
- peekview-0.1.0/peek/static/assets/fortran-free-form-DKXYxT9g.js +1 -0
- peekview-0.1.0/peek/static/assets/fsharp-XplgxFYe.js +1 -0
- peekview-0.1.0/peek/static/assets/gdresource-BHYsBjWJ.js +1 -0
- peekview-0.1.0/peek/static/assets/gdscript-DfxzS6Rs.js +1 -0
- peekview-0.1.0/peek/static/assets/gdshader-SKMF96pI.js +1 -0
- peekview-0.1.0/peek/static/assets/genie-ajMbGru0.js +1 -0
- peekview-0.1.0/peek/static/assets/gherkin--30QC5Em.js +1 -0
- peekview-0.1.0/peek/static/assets/git-commit-i4q6IMui.js +1 -0
- peekview-0.1.0/peek/static/assets/git-rebase-B-v9cOL2.js +1 -0
- peekview-0.1.0/peek/static/assets/github-dark-DHJKELXO.js +1 -0
- peekview-0.1.0/peek/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- peekview-0.1.0/peek/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- peekview-0.1.0/peek/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- peekview-0.1.0/peek/static/assets/github-light-DAi9KRSo.js +1 -0
- peekview-0.1.0/peek/static/assets/github-light-default-D7oLnXFd.js +1 -0
- peekview-0.1.0/peek/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- peekview-0.1.0/peek/static/assets/gleam-B430Bg39.js +1 -0
- peekview-0.1.0/peek/static/assets/glimmer-js-D-cwc0-E.js +1 -0
- peekview-0.1.0/peek/static/assets/glimmer-ts-pgjy16dm.js +1 -0
- peekview-0.1.0/peek/static/assets/glsl-DBO2IWDn.js +1 -0
- peekview-0.1.0/peek/static/assets/gnuplot-CM8KxXT1.js +1 -0
- peekview-0.1.0/peek/static/assets/go-B1SYOhNW.js +1 -0
- peekview-0.1.0/peek/static/assets/graphql-cDcHW_If.js +1 -0
- peekview-0.1.0/peek/static/assets/groovy-DkBy-JyN.js +1 -0
- peekview-0.1.0/peek/static/assets/hack-D1yCygmZ.js +1 -0
- peekview-0.1.0/peek/static/assets/haml-B2EZWmdv.js +1 -0
- peekview-0.1.0/peek/static/assets/handlebars-BQGss363.js +1 -0
- peekview-0.1.0/peek/static/assets/haskell-BILxekzW.js +1 -0
- peekview-0.1.0/peek/static/assets/haxe-C5wWYbrZ.js +1 -0
- peekview-0.1.0/peek/static/assets/hcl-HzYwdGDm.js +1 -0
- peekview-0.1.0/peek/static/assets/hjson-T-Tgc4AT.js +1 -0
- peekview-0.1.0/peek/static/assets/hlsl-ifBTmRxC.js +1 -0
- peekview-0.1.0/peek/static/assets/houston-DnULxvSX.js +1 -0
- peekview-0.1.0/peek/static/assets/html-C2L_23MC.js +1 -0
- peekview-0.1.0/peek/static/assets/html-derivative-CSfWNPLT.js +1 -0
- peekview-0.1.0/peek/static/assets/http-FRrOvY1W.js +1 -0
- peekview-0.1.0/peek/static/assets/hxml-TIA70rKU.js +1 -0
- peekview-0.1.0/peek/static/assets/hy-BMj5Y0dO.js +1 -0
- peekview-0.1.0/peek/static/assets/imba-bv_oIlVt.js +1 -0
- peekview-0.1.0/peek/static/assets/index-CMhcFTfH.js +26 -0
- peekview-0.1.0/peek/static/assets/index-DvcXrXhI.css +1 -0
- peekview-0.1.0/peek/static/assets/ini-BjABl1g7.js +1 -0
- peekview-0.1.0/peek/static/assets/java-xI-RfyKK.js +1 -0
- peekview-0.1.0/peek/static/assets/javascript-ySlJ1b_l.js +1 -0
- peekview-0.1.0/peek/static/assets/jinja-DGy0s7-h.js +1 -0
- peekview-0.1.0/peek/static/assets/jison-BqZprYcd.js +1 -0
- peekview-0.1.0/peek/static/assets/json-BQoSv7ci.js +1 -0
- peekview-0.1.0/peek/static/assets/json5-w8dY5SsB.js +1 -0
- peekview-0.1.0/peek/static/assets/jsonc-TU54ms6u.js +1 -0
- peekview-0.1.0/peek/static/assets/jsonl-DREVFZK8.js +1 -0
- peekview-0.1.0/peek/static/assets/jsonnet-BfivnA6A.js +1 -0
- peekview-0.1.0/peek/static/assets/jssm-P4WzXJd0.js +1 -0
- peekview-0.1.0/peek/static/assets/jsx-BAng5TT0.js +1 -0
- peekview-0.1.0/peek/static/assets/julia-BBuGR-5E.js +1 -0
- peekview-0.1.0/peek/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- peekview-0.1.0/peek/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- peekview-0.1.0/peek/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- peekview-0.1.0/peek/static/assets/kotlin-B5lbUyaz.js +1 -0
- peekview-0.1.0/peek/static/assets/kusto-mebxcVVE.js +1 -0
- peekview-0.1.0/peek/static/assets/laserwave-DUszq2jm.js +1 -0
- peekview-0.1.0/peek/static/assets/latex-C-cWTeAZ.js +1 -0
- peekview-0.1.0/peek/static/assets/lean-XBlWyCtg.js +1 -0
- peekview-0.1.0/peek/static/assets/less-BfCpw3nA.js +1 -0
- peekview-0.1.0/peek/static/assets/light-plus-B7mTdjB0.js +1 -0
- peekview-0.1.0/peek/static/assets/liquid-D3W5UaiH.js +1 -0
- peekview-0.1.0/peek/static/assets/log-Cc5clBb7.js +1 -0
- peekview-0.1.0/peek/static/assets/logo-IuBKFhSY.js +1 -0
- peekview-0.1.0/peek/static/assets/lua-CvWAzNxB.js +1 -0
- peekview-0.1.0/peek/static/assets/luau-Du5NY7AG.js +1 -0
- peekview-0.1.0/peek/static/assets/make-Bvotw-X0.js +1 -0
- peekview-0.1.0/peek/static/assets/markdown-UIAJJxZW.js +1 -0
- peekview-0.1.0/peek/static/assets/marko-z0MBrx5-.js +1 -0
- peekview-0.1.0/peek/static/assets/material-theme-D5KoaKCx.js +1 -0
- peekview-0.1.0/peek/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- peekview-0.1.0/peek/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- peekview-0.1.0/peek/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- peekview-0.1.0/peek/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- peekview-0.1.0/peek/static/assets/matlab-D9-PGadD.js +1 -0
- peekview-0.1.0/peek/static/assets/mdc-DB_EDNY_.js +1 -0
- peekview-0.1.0/peek/static/assets/mdx-sdHcTMYB.js +1 -0
- peekview-0.1.0/peek/static/assets/mermaid-Ci6OQyBP.js +1 -0
- peekview-0.1.0/peek/static/assets/min-dark-CafNBF8u.js +1 -0
- peekview-0.1.0/peek/static/assets/min-light-CTRr51gU.js +1 -0
- peekview-0.1.0/peek/static/assets/mipsasm-BC5c_5Pe.js +1 -0
- peekview-0.1.0/peek/static/assets/mojo-Tz6hzZYG.js +1 -0
- peekview-0.1.0/peek/static/assets/monokai-D4h5O-jR.js +1 -0
- peekview-0.1.0/peek/static/assets/move-DB_GagMm.js +1 -0
- peekview-0.1.0/peek/static/assets/narrat-DLbgOhZU.js +1 -0
- peekview-0.1.0/peek/static/assets/nextflow-B0XVJmRM.js +1 -0
- peekview-0.1.0/peek/static/assets/nginx-D_VnBJ67.js +1 -0
- peekview-0.1.0/peek/static/assets/night-owl-C39BiMTA.js +1 -0
- peekview-0.1.0/peek/static/assets/nim-ZlGxZxc3.js +1 -0
- peekview-0.1.0/peek/static/assets/nix-shcSOmrb.js +1 -0
- peekview-0.1.0/peek/static/assets/nord-Ddv68eIx.js +1 -0
- peekview-0.1.0/peek/static/assets/nushell-D4Tzg5kh.js +1 -0
- peekview-0.1.0/peek/static/assets/objective-c-Deuh7S70.js +1 -0
- peekview-0.1.0/peek/static/assets/objective-cpp-BUEGK8hf.js +1 -0
- peekview-0.1.0/peek/static/assets/ocaml-BNioltXt.js +1 -0
- peekview-0.1.0/peek/static/assets/one-dark-pro-GBQ2dnAY.js +1 -0
- peekview-0.1.0/peek/static/assets/one-light-PoHY5YXO.js +1 -0
- peekview-0.1.0/peek/static/assets/pascal-JqZropPD.js +1 -0
- peekview-0.1.0/peek/static/assets/perl-CHQXSrWU.js +1 -0
- peekview-0.1.0/peek/static/assets/php-B5ebYQev.js +1 -0
- peekview-0.1.0/peek/static/assets/plastic-3e1v2bzS.js +1 -0
- peekview-0.1.0/peek/static/assets/plsql-LKU2TuZ1.js +1 -0
- peekview-0.1.0/peek/static/assets/po-BFLt1xDp.js +1 -0
- peekview-0.1.0/peek/static/assets/poimandres-CS3Unz2-.js +1 -0
- peekview-0.1.0/peek/static/assets/polar-DKykz6zU.js +1 -0
- peekview-0.1.0/peek/static/assets/postcss-B3ZDOciz.js +1 -0
- peekview-0.1.0/peek/static/assets/powerquery-CSHBycmS.js +1 -0
- peekview-0.1.0/peek/static/assets/powershell-BIEUsx6d.js +1 -0
- peekview-0.1.0/peek/static/assets/prisma-B48N-Iqd.js +1 -0
- peekview-0.1.0/peek/static/assets/prolog-BY-TUvya.js +1 -0
- peekview-0.1.0/peek/static/assets/proto-zocC4JxJ.js +1 -0
- peekview-0.1.0/peek/static/assets/pug-CM9l7STV.js +1 -0
- peekview-0.1.0/peek/static/assets/puppet-Cza_XSSt.js +1 -0
- peekview-0.1.0/peek/static/assets/purescript-Bg-kzb6g.js +1 -0
- peekview-0.1.0/peek/static/assets/python-DhUJRlN_.js +1 -0
- peekview-0.1.0/peek/static/assets/qml-D8XfuvdV.js +1 -0
- peekview-0.1.0/peek/static/assets/qmldir-C8lEn-DE.js +1 -0
- peekview-0.1.0/peek/static/assets/qss-DhMKtDLN.js +1 -0
- peekview-0.1.0/peek/static/assets/r-CwjWoCRV.js +1 -0
- peekview-0.1.0/peek/static/assets/racket-CzouJOBO.js +1 -0
- peekview-0.1.0/peek/static/assets/raku-B1bQXN8T.js +1 -0
- peekview-0.1.0/peek/static/assets/razor-CNLDkMZG.js +1 -0
- peekview-0.1.0/peek/static/assets/red-bN70gL4F.js +1 -0
- peekview-0.1.0/peek/static/assets/reg-5LuOXUq_.js +1 -0
- peekview-0.1.0/peek/static/assets/regexp-DWJ3fJO_.js +1 -0
- peekview-0.1.0/peek/static/assets/rel-DJlmqQ1C.js +1 -0
- peekview-0.1.0/peek/static/assets/riscv-QhoSD0DR.js +1 -0
- peekview-0.1.0/peek/static/assets/rose-pine-CmCqftbK.js +1 -0
- peekview-0.1.0/peek/static/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
- peekview-0.1.0/peek/static/assets/rose-pine-moon-CjDtw9vr.js +1 -0
- peekview-0.1.0/peek/static/assets/rst-4NLicBqY.js +1 -0
- peekview-0.1.0/peek/static/assets/ruby-DeZ3UC14.js +1 -0
- peekview-0.1.0/peek/static/assets/rust-Be6lgOlo.js +1 -0
- peekview-0.1.0/peek/static/assets/sas-BmTFh92c.js +1 -0
- peekview-0.1.0/peek/static/assets/sass-BJ4Li9vH.js +1 -0
- peekview-0.1.0/peek/static/assets/scala-DQVVAn-B.js +1 -0
- peekview-0.1.0/peek/static/assets/scheme-BJGe-b2p.js +1 -0
- peekview-0.1.0/peek/static/assets/scss-C31hgJw-.js +1 -0
- peekview-0.1.0/peek/static/assets/sdbl-BLhTXw86.js +1 -0
- peekview-0.1.0/peek/static/assets/shaderlab-B7qAK45m.js +1 -0
- peekview-0.1.0/peek/static/assets/shellscript-atvbtKCR.js +1 -0
- peekview-0.1.0/peek/static/assets/shellsession-C_rIy8kc.js +1 -0
- peekview-0.1.0/peek/static/assets/slack-dark-BthQWCQV.js +1 -0
- peekview-0.1.0/peek/static/assets/slack-ochin-DqwNpetd.js +1 -0
- peekview-0.1.0/peek/static/assets/smalltalk-DkLiglaE.js +1 -0
- peekview-0.1.0/peek/static/assets/snazzy-light-Bw305WKR.js +1 -0
- peekview-0.1.0/peek/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- peekview-0.1.0/peek/static/assets/solarized-light-L9t79GZl.js +1 -0
- peekview-0.1.0/peek/static/assets/solidity-C1w2a3ep.js +1 -0
- peekview-0.1.0/peek/static/assets/soy-C-lX7w71.js +1 -0
- peekview-0.1.0/peek/static/assets/sparql-bYkjHRlG.js +1 -0
- peekview-0.1.0/peek/static/assets/splunk-Cf8iN4DR.js +1 -0
- peekview-0.1.0/peek/static/assets/sql-COK4E0Yg.js +1 -0
- peekview-0.1.0/peek/static/assets/ssh-config-BknIz3MU.js +1 -0
- peekview-0.1.0/peek/static/assets/stata-DorPZHa4.js +1 -0
- peekview-0.1.0/peek/static/assets/stylus-BeQkCIfX.js +1 -0
- peekview-0.1.0/peek/static/assets/svelte-MSaWC3Je.js +1 -0
- peekview-0.1.0/peek/static/assets/swift-BSxZ-RaX.js +1 -0
- peekview-0.1.0/peek/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- peekview-0.1.0/peek/static/assets/system-verilog-C7L56vO4.js +1 -0
- peekview-0.1.0/peek/static/assets/systemd-CUnW07Te.js +1 -0
- peekview-0.1.0/peek/static/assets/talonscript-C1XDQQGZ.js +1 -0
- peekview-0.1.0/peek/static/assets/tasl-CQjiPCtT.js +1 -0
- peekview-0.1.0/peek/static/assets/tcl-DQ1-QYvQ.js +1 -0
- peekview-0.1.0/peek/static/assets/templ-dwX3ZSMB.js +1 -0
- peekview-0.1.0/peek/static/assets/terraform-BbSNqyBO.js +1 -0
- peekview-0.1.0/peek/static/assets/tex-rYs2v40G.js +1 -0
- peekview-0.1.0/peek/static/assets/tokyo-night-DBQeEorK.js +1 -0
- peekview-0.1.0/peek/static/assets/toml-CB2ApiWb.js +1 -0
- peekview-0.1.0/peek/static/assets/ts-tags-CipyTH0X.js +1 -0
- peekview-0.1.0/peek/static/assets/tsv-B_m7g4N7.js +1 -0
- peekview-0.1.0/peek/static/assets/tsx-B6W0miNI.js +1 -0
- peekview-0.1.0/peek/static/assets/turtle-BMR_PYu6.js +1 -0
- peekview-0.1.0/peek/static/assets/twig-NC5TFiHP.js +1 -0
- peekview-0.1.0/peek/static/assets/typescript-Dj6nwHGl.js +1 -0
- peekview-0.1.0/peek/static/assets/typespec-BpWG_bgh.js +1 -0
- peekview-0.1.0/peek/static/assets/typst-BVUVsWT6.js +1 -0
- peekview-0.1.0/peek/static/assets/useEntry-DSu-PUHy.js +1 -0
- peekview-0.1.0/peek/static/assets/useEntry-Dgrs0_hj.css +1 -0
- peekview-0.1.0/peek/static/assets/v-CAQ2eGtk.js +1 -0
- peekview-0.1.0/peek/static/assets/vala-BFOHcciG.js +1 -0
- peekview-0.1.0/peek/static/assets/vb-CdO5JTpU.js +1 -0
- peekview-0.1.0/peek/static/assets/verilog-CJaU5se_.js +1 -0
- peekview-0.1.0/peek/static/assets/vesper-BEBZ7ncR.js +1 -0
- peekview-0.1.0/peek/static/assets/vhdl-DYoNaHQp.js +1 -0
- peekview-0.1.0/peek/static/assets/viml-m4uW47V2.js +1 -0
- peekview-0.1.0/peek/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- peekview-0.1.0/peek/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- peekview-0.1.0/peek/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- peekview-0.1.0/peek/static/assets/vue-BuYVFjOK.js +1 -0
- peekview-0.1.0/peek/static/assets/vue-html-xdeiXROB.js +1 -0
- peekview-0.1.0/peek/static/assets/vyper-nyqBNV6O.js +1 -0
- peekview-0.1.0/peek/static/assets/wasm-C6j12Q_x.js +1 -0
- peekview-0.1.0/peek/static/assets/wasm-CG6Dc4jp.js +1 -0
- peekview-0.1.0/peek/static/assets/wenyan-7A4Fjokl.js +1 -0
- peekview-0.1.0/peek/static/assets/wgsl-CB0Krxn9.js +1 -0
- peekview-0.1.0/peek/static/assets/wikitext-DCE3LsBG.js +1 -0
- peekview-0.1.0/peek/static/assets/wolfram-C3FkfJm5.js +1 -0
- peekview-0.1.0/peek/static/assets/xml-e3z08dGr.js +1 -0
- peekview-0.1.0/peek/static/assets/xsl-Dd0NUgwM.js +1 -0
- peekview-0.1.0/peek/static/assets/yaml-CVw76BM1.js +1 -0
- peekview-0.1.0/peek/static/assets/zenscript-HnGAYVZD.js +1 -0
- peekview-0.1.0/peek/static/assets/zig-BVz_zdnA.js +1 -0
- peekview-0.1.0/peek/static/index.html +26 -0
- peekview-0.1.0/peek/storage.py +498 -0
- peekview-0.1.0/pyproject.toml +99 -0
- peekview-0.1.0/tests/__init__.py +1 -0
- peekview-0.1.0/tests/conftest.py +119 -0
- peekview-0.1.0/tests/factories.py +105 -0
- peekview-0.1.0/tests/test_api.py +226 -0
- peekview-0.1.0/tests/test_cli.py +441 -0
- peekview-0.1.0/tests/test_config.py +213 -0
- peekview-0.1.0/tests/test_database.py +344 -0
- peekview-0.1.0/tests/test_entry_service.py +207 -0
- peekview-0.1.0/tests/test_exceptions.py +154 -0
- peekview-0.1.0/tests/test_file_service.py +255 -0
- peekview-0.1.0/tests/test_language.py +238 -0
- peekview-0.1.0/tests/test_models.py +351 -0
- peekview-0.1.0/tests/test_security.py +601 -0
- 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
|
peekview-0.1.0/Makefile
ADDED
|
@@ -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
|
peekview-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
peekview-0.1.0/README.md
ADDED
|
@@ -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 @@
|
|
|
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
|
+
)
|