diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f592a4b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,27 @@ +name: Build & cache +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= + substituters = https://cache.iog.io/ https://cache.nixos.org/ + allow-import-from-derivation = true + accept-flake-config = true + download-buffer-size = 524288000 + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v15 + with: + name: inguncache + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - run: | + # nix run "github:input-output-hk/haskell.nix#hix" -- init + # echo "------ Content of flake.nix ------" + # cat flake.nix + # echo "----- Content of nix/hix.nix -----" + # cat nix/hix.nix + nix build .#answers-script:exe:answers-script diff --git a/.gitignore b/.gitignore index df50a24..189a004 100644 --- a/.gitignore +++ b/.gitignore @@ -1,85 +1,3 @@ -.cabal-sandbox/ -cabal.sandbox.config -cabal.project.local -.ghc.environment.* -cabal-dev/ -.hpc/ -*.hi -*.o -*.p_hi -*.prof -*.tix -dist -dist-* -register.sh -./cabal.config -cabal-tests.log -bootstrap/*.plan.json - -/Cabal/dist/ -/Cabal/tests/Setup -/Cabal/Setup -/Cabal/source-file-list - -/cabal-install/dist/ -/cabal-install/Setup -/cabal-install/source-file-list - -.stylish-haskell.yaml -.stylish-haskell.yml -.ghci -.ghcid - -# Output of release and bootstrap -_build - -# editor temp files - -*# -.#* -*~ -.*.swp -*.bak - -# GHC build - -Cabal/GNUmakefile -Cabal/dist-boot/ -Cabal/dist-install/ -Cabal/ghc.mk - - -# TAGS files -TAGS -tags -ctags - -# stack artifacts -/.stack-work/ -stack.yaml.lock - -# Shake artifacts -.shake* -progress.txt - -# test files -register.sh - -# windows test artifacts -cabal-testsuite/**/*.exe -cabal-testsuite/**/*.bat - -# python artifacts from documentation builds -*.pyc -.python-sphinx-virtualenv/ -/doc/.skjold_cache/ - -# macOS folder metadata -.DS_Store - -# benchmarks -bench.html - -# Emacs -.projectile -/test/dst/ \ No newline at end of file +/dist-newstyle +/test/dst +/cabal.project.freeze \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d2f848d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "test/answers-db"] + path = test/answers-db + url = https://github.com/ingun37/answers-db.git + branch = test-data diff --git a/ChangeLog.md b/CHANGELOG.md similarity index 100% rename from ChangeLog.md rename to CHANGELOG.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e48f0a9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2025, Ingun Jon + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 93334bf..00dfce4 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,72 @@ -# Answers Script +# Build MacOS -## Generate default.nix +```sh +# for building gitlib packages +brew install pkgconf +brew install icu4c +brew install openssl@3 -```shell -nix-shell -p cabal2nix -cabal2nix --no-check ./. > default.nix -``` - -## Generate shell.nix +# /usr/local/opt/openssl is hard coded in the gitlib build setting. +ln -s $(brew --prefix openssl@3)/3.5.2 /usr/local/opt/openssl -```shell -nix-shell -p cabal2nix -cabal2nix --shell --no-check ./. > shell.nix +echo "export PKG_CONFIG_PATH=\"$(brew --prefix)/opt/icu4c/lib/pkgconfig\"" >> ~/.zprofile ``` -## Build +```sh +cabal build +``` -**Don't just build using Cabal!!** it will take forever because of Pandoc. +# Data flow -Build in Nix environment +```mermaid +flowchart LR + n1["src"] --> n2(("directory-tree
readDirectoryWithL")) + n3["myReader"] --> n2 + n2 --> n4["AnchoredDirTree FileType"] + n4 --> n5(("directory-tree
filterDir")) + n5 --> n8(("unfoldTree")) + n9["myUnfolder"] --> n8 + n8 --> n10["Tree Item"] + n11["myFilter"] --> n5 + n10 --> n12(("recurse")) + n13["myWriter"] --> n12 + n12 --> n14["[Effect]"] -```shell -# Enter Nix environment defined in shell.nix -nix-shell -# Use executable -answers-script ... + n1@{ shape: rect} + n9@{ shape: rect} ``` -## Test +# CI/CD -```shell -nix-shell -cabal --enable-nix test +Check the sha1 of gitlib like this + +```sh +nix-shell -p nix-prefetch-git +nix-prefetch-git https://github.com/jwiegley/gitlib.git bf256617179d853bdbc12e9283b3f570ebb9d9d7 --fetch-submodules ``` -## Install from other machines +Output is like -```shell -TAR="https://github.com/ingun37/answers-script/archive/refs/tags/1.0.1.tar.gz" -# sandboxing -nix-shell -p "with import {}; let f = import (fetchTarball $TAR); in haskellPackages.callPackage f {}" -# no sandboxing -nix-env --install -E "with import {}; let f = import (fetchTarball $TAR); in _: (haskellPackages.callPackage f {})" ``` +git revision is bf256617179d853bdbc12e9283b3f570ebb9d9d7 +path is /nix/store/63bx4k5nwjqwk7gv0a0k8adq796bjbpr-gitlib-bf25661 +git human-readable version is -- none -- +Commit date is 2025-09-04 11:17:27 -0700 +hash is 13k3aymqwzpcijnjjka820nv6rkgakzbvh13glw98p1c4yhqwcbf +{ + "url": "https://github.com/jwiegley/gitlib.git", + "rev": "bf256617179d853bdbc12e9283b3f570ebb9d9d7", + "date": "2025-09-04T11:17:27-07:00", + "path": "/nix/store/63bx4k5nwjqwk7gv0a0k8adq796bjbpr-gitlib-bf25661", + "sha256": "13k3aymqwzpcijnjjka820nv6rkgakzbvh13glw98p1c4yhqwcbf", + "hash": "sha256-bjGOoScsXJQ4fSPAvf5Ub2azLRBITSmtjOx+jqtXY44=", + "fetchLFS": false, + "fetchSubmodules": true, + "deepClone": false, + "fetchTags": false, + "leaveDotGit": false, + "rootDir": "" +} +``` + +Use the "sha256" for the `--sha256` field in the `cabal.project`. \ No newline at end of file diff --git a/answers-script.cabal b/answers-script.cabal index 2817267..fdd1c02 100644 --- a/answers-script.cabal +++ b/answers-script.cabal @@ -1,6 +1,26 @@ -cabal-version: 3.0 +cabal-version: 3.4 +-- The cabal-version field refers to the version of the .cabal specification, +-- and can be different from the cabal-install (the tool) version and the +-- Cabal (the library) version you are using. As such, the Cabal (the library) +-- version used must be equal or greater than the version stated in this field. +-- Starting from the specification version 2.2, the cabal-version field must be +-- the first thing in the cabal file. + +-- Initial package description 'answers-script' generated by +-- 'cabal init'. For further documentation, see: +-- http://haskell.org/cabal/users-guide/ +-- +-- The name of the package. name: answers-script -version: 0.1.0.0 + +-- The package version. +-- See the Haskell package versioning policy (PVP) for standards +-- guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 1.0.0.0 -- A short (one-line) description of the package. -- synopsis: @@ -8,48 +28,76 @@ version: 0.1.0.0 -- A longer description of the package. -- description: --- A URL where users can report bugs. --- bug-reports: - -- The license under which the package is released. --- license: +license: BSD-3-Clause + +-- The file containing the license text. +license-file: LICENSE + +-- The package author(s). author: Ingun Jon + +-- An email address to which users can send suggestions, bug reports, and patches. maintainer: ingun37@gmail.com -- A copyright notice. -- copyright: --- category: -extra-source-files: CHANGELOG.md +build-type: Simple + +-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. +extra-doc-files: CHANGELOG.md + +-- Extra source files to be distributed with the package, such as examples, or a tutorial module. +-- extra-source-files: + +common warnings + ghc-options: -Wall library - exposed-modules: Lib, MyGit + -- Import common warning flags. + import: warnings + + -- Modules exported by the library. + exposed-modules: MyLib, MyGit, MyMark -- Modules included in this library but not exported. - -- other-modules: + -- other-modules: MyGit -- LANGUAGE extensions used by modules in this package. -- other-extensions: - build-depends: - base ^>=4.16.4.0, - aeson, - hlibgit2, - containers, - lens, - cryptohash-sha1, - directory, + + -- Other library packages from which modules are imported. + build-depends: + base ^>=4.21.0.0, directory-tree, - pandoc, - pcre-heavy, - pcre-light, filepath, + lens, text, + cmark, + containers, + directory, + cryptohash-sha1, bytestring, base16-bytestring, - aeson-pretty + gitlib, + gitlib-libgit2, + time, + tagged, + transformers, + monad-loops, + aeson + + -- Directories containing source files. hs-source-dirs: src - default-language: Haskell2010 - + + -- Base language which the package is written in. + default-language: GHC2024 + executable answers-script + -- Import common warning flags. + import: warnings + + -- .hs or .lhs file containing the Main module. main-is: Main.hs -- Modules included in this executable, other than Main. @@ -57,25 +105,44 @@ executable answers-script -- LANGUAGE extensions used by modules in this package. -- other-extensions: + + -- Other library packages from which modules are imported. build-depends: - base ^>=4.16.4.0, + base ^>=4.21.0.0, answers-script, optparse-applicative + -- Directories containing source files. hs-source-dirs: app - default-language: Haskell2010 -Test-Suite test + -- Base language which the package is written in. + default-language: GHC2024 + +test-suite answers-script-test + -- Import common warning flags. + import: warnings + + -- Base language which the package is written in. + default-language: GHC2024 + + -- Modules included in this executable, other than Main. + -- other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + + -- The interface type and version of the test suite. type: exitcode-stdio-1.0 - main-is: Spec.hs + + -- Directories containing source files. hs-source-dirs: test - default-language: Haskell2010 + + -- The entrypoint to the test suite. + main-is: Main.hs + + -- Test dependencies. build-depends: - base ^>=4.16.4.0, + base ^>=4.21.0.0, answers-script, - hspec, - QuickCheck, - directory, filepath, - bytestring, - directory-tree \ No newline at end of file + containers \ No newline at end of file diff --git a/app/Main.hs b/app/Main.hs index 50bbe09..3242a69 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,10 +1,10 @@ module Main where -import Lib (someFunc) +import MyLib (someFunc) import Options.Applicative data Sample = Sample - { prefixPath :: String, + { prefix :: String, src :: String, dst :: String } @@ -13,7 +13,7 @@ sample :: Parser Sample sample = Sample <$> strOption - ( long "prefixpath" + ( long "prefix" <> value "" <> help "prefix path of webserver" ) @@ -27,7 +27,9 @@ sample = ) greet :: Sample -> IO () -greet (Sample prefixPath src dst) = someFunc prefixPath src dst +greet (Sample prefix src dst) = do + _ <- someFunc prefix src dst + return () main :: IO () main = greet =<< execParser opts @@ -38,4 +40,4 @@ main = greet =<< execParser opts ( fullDesc <> progDesc "import answers-db into answers static asset" <> header "what is header?" - ) + ) \ No newline at end of file diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..d0964f4 --- /dev/null +++ b/cabal.project @@ -0,0 +1,22 @@ +packages: . + +source-repository-package + type: git + location: https://github.com/jwiegley/gitlib.git + subdir: gitlib + tag: bf256617179d853bdbc12e9283b3f570ebb9d9d7 + --sha256: 13k3aymqwzpcijnjjka820nv6rkgakzbvh13glw98p1c4yhqwcbf + +source-repository-package + type: git + location: https://github.com/jwiegley/gitlib.git + subdir: gitlib-libgit2 + tag: bf256617179d853bdbc12e9283b3f570ebb9d9d7 + --sha256: 13k3aymqwzpcijnjjka820nv6rkgakzbvh13glw98p1c4yhqwcbf + +source-repository-package + type: git + location: https://github.com/jwiegley/gitlib.git + subdir: hlibgit2 + tag: bf256617179d853bdbc12e9283b3f570ebb9d9d7 + --sha256: 13k3aymqwzpcijnjjka820nv6rkgakzbvh13glw98p1c4yhqwcbf \ No newline at end of file diff --git a/default.nix b/default.nix deleted file mode 100644 index b248047..0000000 --- a/default.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ mkDerivation, aeson, aeson-pretty, base, base16-bytestring -, bytestring, containers, cryptohash-sha1, directory -, directory-tree, filepath, hlibgit2, hspec, lens, lib -, optparse-applicative, pandoc, pcre-heavy, pcre-light, QuickCheck -, text -}: -mkDerivation { - pname = "answers-script"; - version = "0.1.0.0"; - src = ./.; - isLibrary = true; - isExecutable = true; - libraryHaskellDepends = [ - aeson aeson-pretty base base16-bytestring bytestring containers - cryptohash-sha1 directory directory-tree filepath hlibgit2 lens - pandoc pcre-heavy pcre-light text - ]; - executableHaskellDepends = [ base optparse-applicative ]; - testHaskellDepends = [ - base bytestring directory directory-tree filepath hspec QuickCheck - ]; - doCheck = false; - license = "unknown"; - mainProgram = "answers-script"; -} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e046ecd --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + # This is a template created by `hix init` + inputs.haskellNix.url = "github:input-output-hk/haskell.nix"; + inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + outputs = { self, nixpkgs, flake-utils, haskellNix }: + let + supportedSystems = [ + "x86_64-linux" + # "x86_64-darwin" + # "aarch64-linux" + # "aarch64-darwin" + ]; + in + flake-utils.lib.eachSystem supportedSystems (system: + let + overlays = [ haskellNix.overlay + (final: _prev: { + hixProject = + final.haskell-nix.hix.project { + src = ./.; + # uncomment with your current system for `nix flake show` to work: + #evalSystem = "x86_64-linux"; + }; + }) + ]; + pkgs = import nixpkgs { inherit system overlays; inherit (haskellNix) config; }; + flake = pkgs.hixProject.flake {}; + in flake // { + legacyPackages = pkgs; + }); + # --- Flake Local Nix Configuration ---------------------------- + nixConfig = { + # This sets the flake to use the IOG nix cache. + # Nix should ask for permission before using it, + # but remove it here if you do not want it to. + extra-substituters = ["https://cache.iog.io"]; + extra-trusted-public-keys = ["hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ="]; + allow-import-from-derivation = "true"; + }; +} \ No newline at end of file diff --git a/nix/hix.nix b/nix/hix.nix new file mode 100644 index 0000000..bf6637e --- /dev/null +++ b/nix/hix.nix @@ -0,0 +1,15 @@ +{pkgs, ...}: { + # name = "project-name"; + compiler-nix-name = "ghc912"; # Version of GHC to use + # Cross compilation support: + # crossPlatforms = p: pkgs.lib.optionals pkgs.stdenv.hostPlatform.isx86_64 ([ + # p.mingwW64 + # p.ghcjs + # ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ + # p.musl64 + # ]); + # Tools to include in the development shell + shell.tools.cabal = "latest"; + # shell.tools.hlint = "latest"; + # shell.tools.haskell-language-server = "latest"; +} \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 0ee0bd5..0000000 --- a/shell.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: - -let - - inherit (nixpkgs) pkgs; - - f = { mkDerivation, aeson, aeson-pretty, base, base16-bytestring - , bytestring, containers, cryptohash-sha1, directory - , directory-tree, filepath, hlibgit2, hspec, lens, lib - , optparse-applicative, pandoc, pcre-heavy, pcre-light, QuickCheck - , text - }: - mkDerivation { - pname = "answers-script"; - version = "0.1.0.0"; - src = ./.; - isLibrary = true; - isExecutable = true; - libraryHaskellDepends = [ - aeson aeson-pretty base base16-bytestring bytestring containers - cryptohash-sha1 directory directory-tree filepath hlibgit2 lens - pandoc pcre-heavy pcre-light text - ]; - executableHaskellDepends = [ base optparse-applicative ]; - testHaskellDepends = [ - base bytestring directory directory-tree filepath hspec QuickCheck - ]; - doCheck = false; - license = "unknown"; - mainProgram = "answers-script"; - }; - - haskellPackages = if compiler == "default" - then pkgs.haskellPackages - else pkgs.haskell.packages.${compiler}; - - variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id; - - drv = variant (haskellPackages.callPackage f {}); - -in - - if pkgs.lib.inNixShell then drv.env else drv diff --git a/src/Lib.hs b/src/Lib.hs deleted file mode 100644 index 781b259..0000000 --- a/src/Lib.hs +++ /dev/null @@ -1,198 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} - -module Lib - ( someFunc, - ) -where - -import Control.Lens (over, (^.)) -import Control.Monad (forM, when, (<=<)) -import qualified Crypto.Hash.SHA1 as SHA (hash) -import Data.Aeson (ToJSON) -import Data.Aeson.Encode.Pretty (encodePretty) -import Data.ByteString (readFile) -import qualified Data.ByteString.Base16 as B16 (encode) -import qualified Data.ByteString.Char8 as C8 (pack, unpack) -import Data.ByteString.Lazy (writeFile) -import Data.Map (Map, fromList, keys, lookup, mapKeys) -import Data.Monoid (Sum (Sum), getSum) -import Data.Text (Text, pack, strip, unpack) -import Data.Text.Encoding (decodeUtf8) -import Data.Tree (Tree (Node), flatten, rootLabel) -import GHC.Generics (Generic) -import MyGit (creationTime) -import System.Directory - ( canonicalizePath, - copyFile, - createDirectoryIfMissing, - removePathForcibly, - ) -import System.Directory.Tree - ( DirTree (Dir, File), - filterDir, - readDirectoryWithL, - _dirTree, - ) -import System.FilePath - ( FilePath, - dropFileName, - isExtensionOf, - joinPath, - makeRelative, - splitDirectories, - takeBaseName, - takeExtension, - takeFileName, - (-<.>), - (), - ) -import qualified System.FilePath as FilePath -import System.FilePath.Posix (joinPath, ()) -import Text.Pandoc - ( Extension (Ext_pipe_tables, Ext_tex_math_double_backslash), - HTMLMathMethod (KaTeX), - def, - defaultKaTeXURL, - extensionsFromList, - githubMarkdownExtensions, - readMarkdown, - readerExtensions, - runIOorExplode, - writeHtml5String, - writerHTMLMathMethod, - ) -import Text.Regex.PCRE.Heavy (gsub, scan) -import Text.Regex.PCRE.Light (compile, dotall, multiline) -import Prelude hiding (lookup, readFile, writeFile) - -data AttributeFile = AttributeFile - { posixTime :: Integer, - content :: Text - } - deriving (Generic, Show) - -instance ToJSON AttributeFile - -data Item = Item - { title :: String, - sha1 :: String, - attr :: Map String AttributeFile, - numAnswer :: Int - } - deriving (Generic, Show) - -data TemTree = TemTree - { path :: String, - item :: Item, - kids :: [Item], - parentSha1 :: String - } - deriving (Generic, Show) - -instance ToJSON Item - -instance ToJSON TemTree - -sha1InHex :: String -> [Char] -sha1InHex = C8.unpack . B16.encode . SHA.hash . C8.pack - -theReader :: String -> String -> String -> FilePath -> IO Text -theReader sitePrefix srcDir dstDir fp = - let ext = takeExtension fp - readWorth = ext == ".md" || ext == ".txt" - content = if readWorth then decodeUtf8 <$> readFile fp else return "" - func = - if ext == ".md" - then mdToHTML <=< copyMDMedia sitePrefix srcDir (dropFileName fp) dstDir . subInlineMathBlock . subDisplayMathBlock - else return - in content >>= func - -theFilter :: DirTree a -> DirTree a -theFilter = - filterDir - ( \case - Dir name _ -> head name /= '.' - File name _ -> ".md" `isExtensionOf` name || ".txt" `isExtensionOf` name - _ -> False - ) - -writeJson :: String -> TemTree -> IO () -writeJson dst tt = writeFile (dst (sha1 (item tt) -<.> ".json")) (encodePretty tt) - -someFunc :: String -> FilePath -> FilePath -> IO () -someFunc prefixPath src'' dst = do - src <- canonicalizePath src'' - anchored <- readDirectoryWithL (theReader prefixPath src dst) src - let anchored' = over _dirTree theFilter anchored - let dbDst = dst "db" - removePathForcibly dbDst >> createDirectoryIfMissing True dbDst - let (Dir name entries) = anchored' ^. _dirTree - putStrLn "===============================" - putStrLn $ "canonical src :" ++ src - putStrLn $ "dst :" ++ dst - putStrLn $ "Prefix :" ++ prefixPath - putStrLn $ "Name of src dir :" ++ name - putStrLn "===============================" - creationTimeForFiles <- mapKeys (name ) <$> creationTime src - mapM_ (writeJson dbDst) (flatten (makeTr creationTimeForFiles name "" entries)) - -subInlineMathBlock :: Text -> Text -subInlineMathBlock = - let imgRegex = compile "\\$`(.+?)`\\$" [] - in gsub imgRegex (\(d : _) -> "\\\\(" ++ d ++ "\\\\)" :: String) - -stripString :: String -> String -stripString = unpack . strip . pack - -subDisplayMathBlock :: Text -> Text -subDisplayMathBlock = - let imgRegex = compile "^```math$(.+?)^```$" [multiline, dotall] - in gsub imgRegex (\(d : _) -> "\\\\[" ++ stripString d ++ "\\\\]" :: String) - -copyMDMedia :: String -> FilePath -> FilePath -> FilePath -> Text -> IO Text -copyMDMedia sitePrefix srcDir mdDir dstDir content = do - let imgRegex = compile "!\\[\\]\\((?!http)(.+?)\\)" [] - let imgNames = map (unpack . head . snd) $ scan imgRegex content - if null imgNames - then return content - else do - let rel = makeRelative srcDir mdDir - let relToDst = dstDir rel - createDirectoryIfMissing True relToDst - mapM_ (\x -> copyFile (mdDir x) (relToDst x)) imgNames - let g x = joinPath $ filter (/= "/") ([sitePrefix, rel, x] >>= splitDirectories) - let f x = mconcat ["![](/", g x, ")"] - return $ gsub imgRegex (f . head) content - -mdToHTML :: Text -> IO Text -mdToHTML txt = - runIOorExplode $ - readMarkdown - def - { readerExtensions = githubMarkdownExtensions <> extensionsFromList [Ext_tex_math_double_backslash, Ext_pipe_tables] - } - txt - >>= writeHtml5String - def - { writerHTMLMathMethod = KaTeX defaultKaTeXURL - } - -countAnswer :: Item -> Sum Int -countAnswer = maybe 0 (const 1) . lookup "a" . attr - -makeTr :: Map FilePath Integer -> String -> String -> [DirTree Text] -> Tree TemTree -makeTr time path parentSha1 entries = - let sha1 = sha1InHex path - kidTrs = [makeTr time (path title) sha1 entries' | Dir title entries' <- entries] - kidItems = map (item . rootLabel) kidTrs - answerNumber = getSum $ countAnswer thisItem <> foldMap (Sum . numAnswer) kidItems - f filename = - let entry = path filename - in case lookup entry time of - Just x -> x - Nothing -> error $ "Failed to read entry : " ++ entry - thisItem = Item (takeFileName path) sha1 (fromList [(takeBaseName name', AttributeFile (f name') file) | File name' file <- entries]) answerNumber - thisNode = TemTree path thisItem kidItems parentSha1 - in Node thisNode kidTrs diff --git a/src/MyGit.hs b/src/MyGit.hs index b446c15..8bc9653 100644 --- a/src/MyGit.hs +++ b/src/MyGit.hs @@ -1,119 +1,59 @@ -{-# LANGUAGE TemplateHaskell #-} - -module MyGit - ( creationTime, - ) -where - -import Bindings.Libgit2 -import Control.Lens (makeLenses, (<&>), (^.)) -import Control.Monad (when, (>=>)) -import Data.List (isPrefixOf) -import Data.Map (Map) -import qualified Data.Map as Map -import Foreign (Ptr, Storable (peek), alloca) -import Foreign.C.String (CString, peekCString, withCString) -import System.Directory (canonicalizePath, doesDirectoryExist) -import System.FilePath (makeRelative, takeDirectory, ()) -import System.FilePath.Posix (takeDirectory, ()) - --- import Data.Set (Set) --- import qualified Data.Set as Set -data Pointers = Pointers - { _repoP :: Ptr (Ptr C'git_repository), - _headP :: Ptr (Ptr C'git_reference), - _commitP :: Ptr (Ptr C'git_commit) - } - -makeLenses ''Pointers - -withPointers f = alloca $ \x -> do - alloca $ \y -> do - alloca $ \z -> do - f (Pointers x y z) - -creationTime :: FilePath -> IO (Map FilePath Integer) -creationTime src = withLibGitDo $ do - dotGitPath <- canonicalizePath =<< getDotGitPath src - repoPath <- canonicalizePath $ takeDirectory dotGitPath - let require = if repoPath == src then "" else makeRelative repoPath src - withCString dotGitPath $ \csrc -> do - putStrLn "============================" - putStrLn $ "src :" ++ src - putStrLn $ "require :" ++ require - putStrLn $ "repoPath :" ++ repoPath - putStrLn "============================" - withPointers (func require csrc) - -getDotGitPath :: FilePath -> IO FilePath -getDotGitPath fp = do - putStrLn $ "testing .git: " ++ fp - e <- doesDirectoryExist (fp ".git") - let dirName = takeDirectory fp - when (dirName == fp) $ error "Failed to find .git" - if e - then return $ fp ".git" - else getDotGitPath dirName - -func :: String -> CString -> Pointers -> IO (Map FilePath Integer) -func matchPrefix repoPath pointers = do - c'git_repository_open (pointers ^. repoP) repoPath >>= errorCheck - repo <- peek $ pointers ^. repoP - c'git_repository_head (pointers ^. headP) repo >>= errorCheck - headOid <- peek (pointers ^. headP) >>= c'git_reference_target - c'git_commit_lookup (pointers ^. commitP) repo headOid >>= errorCheck - headCommit <- peek (pointers ^. commitP) - lineage <- unfoldCommits headCommit - let constructEntryMap' = constructEntrymap matchPrefix repo - maps <- mapM (getRootAndTime >=> uncurry constructEntryMap') lineage - c'git_repository_free repo - return $ Map.unionsWith min maps - -getRootAndTime :: Ptr C'git_commit -> IO (Ptr C'git_tree, Integer) -getRootAndTime commit = do - alloca $ \rootP -> do - c'git_commit_tree rootP commit >>= errorCheck - root <- peek rootP - time <- c'git_commit_time commit - return (root, toInteger time) - -unfoldCommits :: Ptr C'git_commit -> IO [Ptr C'git_commit] -unfoldCommits commit = alloca $ \parentP -> do - result <- c'git_commit_parent parentP commit 0 - if result == 0 - then peek parentP >>= unfoldCommits <&> (++ [commit]) - else return [commit] - -constructEntrymap :: String -> Ptr C'git_repository -> Ptr C'git_tree -> Integer -> IO (Map FilePath Integer) -constructEntrymap matchPrefix repo root time = - let makeEntryMap'' :: String -> Ptr C'git_tree_entry -> IO (Map FilePath Integer) - makeEntryMap'' parentDir entry = do - entryType <- c'git_tree_entry_type entry - name <- c'git_tree_entry_name entry >>= peekCString - let next = parentDir ++ name ++ "/" - if entryType == c'GIT_OBJ_TREE - then do - eoid <- c'git_tree_entry_id entry - alloca $ \subTreeP -> do - c'git_tree_lookup subTreeP repo eoid >>= errorCheck - subTree <- peek subTreeP - if next `isPrefixOf` matchPrefix || matchPrefix `isPrefixOf` next - then makeEntryMap' (parentDir ++ name ++ "/") subTree - else return Map.empty - else do - if matchPrefix `isPrefixOf` parentDir - then do - let relPath = makeRelative matchPrefix (parentDir ++ name) - return $ Map.singleton relPath time - else return Map.empty - - makeEntryMap' :: String -> Ptr C'git_tree -> IO (Map FilePath Integer) - makeEntryMap' parentDir tree = do - entryCountC <- c'git_tree_entrycount tree - let f = c'git_tree_entry_byindex tree - foldMap (f >=> makeEntryMap'' parentDir) [0 .. (entryCountC - 1)] - in makeEntryMap' "" root - -errorCheck r = when (r /= 0) $ error "fail" - --- printMessage = c'git_commit_message >=> peekCString >=> print \ No newline at end of file +{-# LANGUAGE OverloadedStrings #-} + +module MyGit (myGit) where + +import Control.Monad qualified as Monad +import Control.Monad.IO.Class qualified as MonadIOClass +import Control.Monad.Loops qualified as MonadLoops +import Control.Monad.Trans.Reader qualified as Reader +import Data.ByteString.Char8 qualified as C8 +import Data.Map qualified as Map +import Data.Maybe qualified as Maybe +import Data.Tagged qualified as Tagged +import Data.Time qualified as Time +import Git qualified +import Git.Libgit2 qualified as LG + +myGit :: FilePath -> IO (Map.Map FilePath Time.ZonedTime) +myGit repoPath = do + let repoOpts = + Git.RepositoryOptions + { repoPath, + repoWorkingDir = Nothing, + repoIsBare = False, + repoAutoCreate = False + } + Git.withRepository' LG.lgFactory repoOpts myGit_ + +myGit_ :: Reader.ReaderT LG.LgRepo IO (Map.Map FilePath Time.ZonedTime) +myGit_ = do + maybeObjID <- Git.resolveReference "HEAD" + let commitID = Maybe.fromJust maybeObjID + headCommit <- Git.lookupCommit (Tagged.Tagged commitID) + let clone x = (x, x) + tailCommits <- + MonadLoops.unfoldrM + (fmap (fmap clone . Maybe.listToMaybe) . Git.lookupCommitParents) + headCommit + + MonadIOClass.liftIO $ putStrLn $ "Total commits : " ++ show (length tailCommits) + MonadIOClass.liftIO $ putStrLn $ "Last commit : " ++ show commitID + MonadIOClass.liftIO $ putStrLn $ "First commit : " ++ show (Git.commitOid $ last tailCommits) + + seed <- constructEntryTimeMap headCommit + + timeTable' <- + Monad.foldM + ( \xMap y -> do + yMap <- constructEntryTimeMap y + return $ Map.differenceWith (\_ b -> Just b) xMap yMap + ) + seed + tailCommits + return $ Map.mapKeys (C8.unpack . fst) timeTable' + +constructEntryTimeMap :: Git.Commit LG.LgRepo -> Reader.ReaderT LG.LgRepo IO (Map.Map (Git.TreeFilePath, Git.Oid LG.LgRepo) Time.ZonedTime) +constructEntryTimeMap commit = do + let time = Git.signatureWhen (Git.commitAuthor commit) + entries <- Git.listTreeEntries True =<< Git.lookupTree (Git.commitTree commit) + return $ Map.fromList [((x, Tagged.untag oid), time) | (x, Git.BlobEntry oid _) <- entries] diff --git a/src/MyLib.hs b/src/MyLib.hs new file mode 100644 index 0000000..38b5895 --- /dev/null +++ b/src/MyLib.hs @@ -0,0 +1,214 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TemplateHaskell #-} + +module MyLib (someFunc) where + +import CMark qualified +import Control.Lens +import Control.Monad qualified as Monad +import Crypto.Hash.SHA1 qualified as SHA (hash) +import Data.Aeson as Json +import Data.ByteString.Base16 qualified as B16 (encode) +import Data.ByteString.Char8 qualified as C8 +import Data.Foldable qualified as Foldable +import Data.List qualified as List +import Data.List.NonEmpty qualified as NE +import Data.Map qualified as Map +import Data.Maybe qualified as Maybe +import Data.Text qualified as T +import Data.Text.IO qualified as TIO +import Data.Time qualified +import Data.Tree qualified as Tree +import Data.Tree.Lens qualified as TreeLens +import GHC.Generics qualified as Generics +import MyGit qualified +import MyMark qualified +import System.Directory qualified as Dir +import System.Directory.Tree qualified as DirTree +import System.FilePath qualified as File + +sha1InHex :: String -> [Char] +sha1InHex = C8.unpack . B16.encode . SHA.hash . C8.pack + +data AttributeFile = AttributeFile + { _content :: T.Text + } + deriving (Generics.Generic) + +makeLenses ''AttributeFile + +instance Show AttributeFile where + show (AttributeFile c) = "(" ++ show (T.length c) ++ " long text)" + +instance Json.ToJSON AttributeFile where + toEncoding = Json.genericToEncoding Json.defaultOptions + +data PageAttribute = PageAttribute {_time :: Data.Time.ZonedTime, _attributeFile :: AttributeFile} deriving (Generics.Generic) + +instance Show PageAttribute where + show (PageAttribute t af) = show af ++ ", time : " ++ show t + +instance Json.ToJSON PageAttribute where + toEncoding = Json.genericToEncoding Json.defaultOptions + +data PageContent = PageContent + { _pageTitle :: String, + _hash :: String, + _attributes :: Map.Map FilePath PageAttribute, + _answers :: Word + } + deriving (Generics.Generic) + +makeLenses ''PageContent + +instance Json.ToJSON PageContent where + toEncoding = Json.genericToEncoding Json.defaultOptions + +data PageData = PageData + { _pageContent :: PageContent, + _parentHash :: String, + _childPageContents :: [PageContent] + } + deriving (Generics.Generic) + +makeLenses ''PageData + +instance Json.ToJSON PageData where + toEncoding = Json.genericToEncoding Json.defaultOptions + +instance Show PageData where + show pg = + let printEntry (k, v) = k ++ ": " ++ show v + attribs = NE.nonEmpty (map printEntry (Map.toList (pg ^. pageContent . attributes))) + attribsStr = + Maybe.maybe + "" + ( \(x NE.:| xs) -> + "\nattributes : " + ++ x + ++ Monad.join (map ("\n " ++) xs) + ) + attribs + in "page title : " + ++ pg ^. pageContent . pageTitle + ++ "\nhash : " + ++ pg ^. pageContent . hash + ++ "\nparent hash : " + ++ pg ^. parentHash + ++ "\nanswers : " + ++ show (pg ^. pageContent . answers) + ++ "\nchildren : " + ++ show (length (pg ^. childPageContents)) + ++ attribsStr + +data FileType = Resource | Attribute AttributeFile deriving (Generics.Generic, Show) + +data Item = Item + { _title :: String, + _files :: Map.Map String FileType + } + deriving (Generics.Generic, Show) + +makeLenses ''Item + +myUnfolder :: DirTree.DirTree FileType -> (Item, [DirTree.DirTree FileType]) +myUnfolder dt = + let _title = dt ^. DirTree._name + contents = dt ^. DirTree._contents + in ( Item + { _title, + _files = Map.fromList [(x, y) | DirTree.File x y <- contents] + }, + [DirTree.Dir x y | DirTree.Dir x y <- contents] + ) + +zipPath :: Tree.Tree Item -> Tree.Tree ([FilePath], Item) +zipPath t = + let item = t ^. TreeLens.root + branches = t ^. TreeLens.branches + prepend = (item ^. title :) + mapPrepend = over (mapped . _1) prepend + branches' = over mapped (mapPrepend . zipPath) branches + in Tree.Node ([], item) branches' + +theWriter :: FilePath -> FilePath -> String -> ([FilePath], Item) -> IO () +theWriter source destination prefix (parentPathComponents, item) = do + let _pathComponents = drop 1 parentPathComponents ++ [item ^. title] + putStrLn $ "Creating hash with " ++ List.intercalate "/" _pathComponents ++ " ..." + let _hash = sha1InHex $ List.intercalate "/" _pathComponents + + putStrLn $ "Processing: " ++ take 7 _hash ++ "... " ++ File.joinPath _pathComponents + + let hashDir = destination File. "resources" File. _hash + + let copyResource key = do + let src = File.joinPath $ [source] ++ _pathComponents ++ [key] + let dst = hashDir File. key + putStrLn $ " Copying " ++ src ++ " -> " ++ dst + Dir.copyFile src dst + + let compileMarkdown key _content = do + let src = File.joinPath $ [source] ++ _pathComponents ++ [key] + let dst = hashDir File. key ++ ".html" + putStrLn $ " Compiling " ++ src ++ " -> " ++ dst + let safePrefix = List.dropWhileEnd (== '/') $ dropWhile (== '/') prefix + let finalPrefix = List.intercalate "/" (filter (not . null) [safePrefix, "resources", _hash]) + TIO.writeFile dst (CMark.nodeToHtml [] $ MyMark.prefixImageUrl finalPrefix $ CMark.commonmarkToNode [] _content) + + let writeFileType key = + \case + Resource -> copyResource key + Attribute (AttributeFile {_content}) -> + Monad.when (File.takeExtension key == ".md") $ compileMarkdown key _content + _ <- Dir.createDirectoryIfMissing True hashDir + _ <- Map.traverseWithKey writeFileType (item ^. files) + return () + +someFunc :: String -> FilePath -> FilePath -> IO [PageData] +someFunc prefixPath source destination = do + root <- DirTree.readDirectoryWithL myReader source + + let tree = zipPath $ Tree.unfoldTree myUnfolder (DirTree.filterDir myFilter $ root ^. DirTree._dirTree) + + Foldable.traverse_ (theWriter source destination prefixPath) tree + timeTable <- MyGit.myGit source + + let folder (parentPathComponents, item) children = + let parentPath = List.intercalate "/" (drop 1 parentPathComponents) + path = if null parentPath then item ^. title else parentPath ++ "/" ++ item ^. title + getTime k = Maybe.fromJust $ Map.lookup (path ++ "/" ++ k) timeTable + _attributes = Map.fromList [(key, PageAttribute {_time = getTime key, _attributeFile}) | (key, Attribute _attributeFile) <- Map.toList (item ^. files)] + in PageData + { _pageContent = + PageContent + { _pageTitle = item ^. title, + _hash = sha1InHex path, + _attributes, + _answers = maybe 0 (const 1) (item ^. files . at "a.md") + sumOf (folded . _head . pageContent . answers) children + }, + _parentHash = sha1InHex parentPath, + _childPageContents = children ^.. folded . _head . pageContent + } + : Monad.join children + let pageDatas = Tree.foldTree folder tree + let pagesDir = destination File. "pages" + Dir.createDirectoryIfMissing True pagesDir + let writePageData pg = Json.encodeFile (pagesDir File. (pg ^. pageContent . hash) ++ ".json") pg + Monad.forM_ pageDatas writePageData + return pageDatas + +myReader :: FilePath -> IO FileType +myReader path = do + let ext = File.takeExtension path + if ext `elem` [".md", ".txt"] + then do + _content <- TIO.readFile path + return $ Attribute $ AttributeFile {_content} + else return Resource + +myFilter :: DirTree.DirTree a -> Bool +myFilter = + \case + DirTree.Dir name _ -> head name /= '.' + DirTree.File name _ -> head name /= '.' + _ -> False diff --git a/src/MyMark.hs b/src/MyMark.hs new file mode 100644 index 0000000..fe21117 --- /dev/null +++ b/src/MyMark.hs @@ -0,0 +1,32 @@ +module MyMark (prefixImageUrl) where + +import CMark +import Data.Text qualified as T + +prefixImageUrl :: String -> Node -> Node +prefixImageUrl prefix node = + let safePrefix = T.pack ("/" ++ prefix ++ "/") + replaceUrl url = if T.isPrefixOf (T.pack "http") url then url else safePrefix <> T.dropWhile (== '/') url + recurse (Node posInfo nodeType nodes) = + case nodeType of + IMAGE url title -> Node Nothing (IMAGE (replaceUrl url) title) nodes + PARAGRAPH -> Node Nothing PARAGRAPH $ workOnInlineMath (map (prefixImageUrl prefix) nodes) + CODE_BLOCK info text -> if info == T.pack "math" then mathBlock text else Node Nothing nodeType nodes + _ -> Node Nothing nodeType (recurse <$> nodes) + in recurse node + +workOnInlineMath :: [Node] -> [Node] +workOnInlineMath (x : y : z : tail) = + case (x, y, z) of + (Node _ (TEXT l) [], Node _ (CODE m) [], Node _ (TEXT r) []) -> + if T.isSuffixOf (T.pack "$") l && T.isPrefixOf (T.pack "$") r + then x : Node Nothing (TEXT (T.pack "`" <> m <> T.pack "`")) [] : workOnInlineMath (z : tail) + else x : y : workOnInlineMath (z : tail) + _ -> x : workOnInlineMath (y : z : tail) +workOnInlineMath (x : xs) = x : workOnInlineMath xs +workOnInlineMath [] = [] + +mathBlock :: T.Text -> Node +mathBlock text = + let t = T.pack "\\\\(\n" <> text <> T.pack "\n)\\\\" + in Node Nothing PARAGRAPH [Node Nothing (TEXT t) []] \ No newline at end of file diff --git a/test/Main.hs b/test/Main.hs new file mode 100644 index 0000000..09b3e15 --- /dev/null +++ b/test/Main.hs @@ -0,0 +1,20 @@ +module Main (main) where +import MyLib qualified +import MyGit qualified +import System.FilePath +import Control.Monad +import Data.Map + +src :: FilePath +src = "test" "answers-db" + +dst :: FilePath +dst = "test" "dst" + +prefix :: String +prefix = "prefix" + +main :: IO () +main = do + pageDatas <- MyLib.someFunc prefix src dst + forM_ pageDatas print \ No newline at end of file diff --git a/test/Spec.hs b/test/Spec.hs deleted file mode 100644 index ff9900b..0000000 --- a/test/Spec.hs +++ /dev/null @@ -1,38 +0,0 @@ -import Lib - -import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing, doesDirectoryExist) -import Test.Hspec -import Test.QuickCheck -import qualified System.Directory.Tree as DT -import qualified Data.ByteString as B -import Control.Monad (when) -import System.FilePath (takeExtension, takeFileName) -src :: FilePath -src = "test/src" - -dst :: FilePath -dst = "test/dst" - -prefix :: String -prefix = "prefix" - -expect :: FilePath -expect = "test/expect" - -simpleReader :: FilePath -> IO String -simpleReader fp = case takeExtension fp of - ".json" -> readFile fp - _ -> return (takeFileName fp) - -main :: IO () -main = hspec $ do - describe "Prelude.head" $ do - it "returns the first element of a list" $ do - dstExist <- doesDirectoryExist dst - when dstExist (removeDirectoryRecursive dst) - createDirectoryIfMissing True dst - someFunc prefix src dst - -- Top directory names are different, hence (_ :/ (Dir _ toDT)) - (_ DT.:/ (DT.Dir _ toDT)) <- DT.readDirectoryWith simpleReader dst - (_ DT.:/ (DT.Dir _ beDT)) <- DT.readDirectoryWith simpleReader expect - toDT `shouldBe` beDT \ No newline at end of file diff --git a/test/answers-db b/test/answers-db new file mode 160000 index 0000000..ba858fd --- /dev/null +++ b/test/answers-db @@ -0,0 +1 @@ +Subproject commit ba858fd9a8f220d6e4001a7c37a3895cefb4b66a diff --git a/test/expect/db/22c0547dca2710d76a885c5ee667ad981dbe31db.json b/test/expect/db/22c0547dca2710d76a885c5ee667ad981dbe31db.json deleted file mode 100644 index 96d4d77..0000000 --- a/test/expect/db/22c0547dca2710d76a885c5ee667ad981dbe31db.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "item": { - "attr": { - "author": { - "content": "Ingun Jon", - "posixTime": 1668403786 - }, - "q": { - "content": "

some book's q.md

\n

\\cdots

\n

", - "posixTime": 1668403786 - } - }, - "numAnswer": 2, - "sha1": "22c0547dca2710d76a885c5ee667ad981dbe31db", - "title": "some book" - }, - "kids": [ - { - "attr": { - "a": { - "content": "

it's 3

", - "posixTime": 1668403786 - }, - "q": { - "content": "

What is 1 + 1?

", - "posixTime": 1668403786 - } - }, - "numAnswer": 1, - "sha1": "262d26ddc4b6cf08bd3542ce232fc43acf6004d7", - "title": "chapter 1" - }, - { - "attr": { - "a": { - "content": "

it's 5

", - "posixTime": 1674358503 - }, - "q": { - "content": "

What is 2+2?

", - "posixTime": 1674358503 - } - }, - "numAnswer": 1, - "sha1": "d2786f0cdc45005f4cd9e4c3bd67870fa7c4c3a1", - "title": "chapter2" - } - ], - "parentSha1": "f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9", - "path": "src/some book" -} \ No newline at end of file diff --git a/test/expect/db/262d26ddc4b6cf08bd3542ce232fc43acf6004d7.json b/test/expect/db/262d26ddc4b6cf08bd3542ce232fc43acf6004d7.json deleted file mode 100644 index 1b3d7d2..0000000 --- a/test/expect/db/262d26ddc4b6cf08bd3542ce232fc43acf6004d7.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "item": { - "attr": { - "a": { - "content": "

it's 3

", - "posixTime": 1668403786 - }, - "q": { - "content": "

What is 1 + 1?

", - "posixTime": 1668403786 - } - }, - "numAnswer": 1, - "sha1": "262d26ddc4b6cf08bd3542ce232fc43acf6004d7", - "title": "chapter 1" - }, - "kids": [], - "parentSha1": "22c0547dca2710d76a885c5ee667ad981dbe31db", - "path": "src/some book/chapter 1" -} \ No newline at end of file diff --git a/test/expect/db/d2786f0cdc45005f4cd9e4c3bd67870fa7c4c3a1.json b/test/expect/db/d2786f0cdc45005f4cd9e4c3bd67870fa7c4c3a1.json deleted file mode 100644 index ec0bc31..0000000 --- a/test/expect/db/d2786f0cdc45005f4cd9e4c3bd67870fa7c4c3a1.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "item": { - "attr": { - "a": { - "content": "

it's 5

", - "posixTime": 1674358503 - }, - "q": { - "content": "

What is 2+2?

", - "posixTime": 1674358503 - } - }, - "numAnswer": 1, - "sha1": "d2786f0cdc45005f4cd9e4c3bd67870fa7c4c3a1", - "title": "chapter2" - }, - "kids": [], - "parentSha1": "22c0547dca2710d76a885c5ee667ad981dbe31db", - "path": "src/some book/chapter2" -} \ No newline at end of file diff --git a/test/expect/db/f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9.json b/test/expect/db/f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9.json deleted file mode 100644 index 6ff4d52..0000000 --- a/test/expect/db/f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "item": { - "attr": { - "q": { - "content": "

top q.md

", - "posixTime": 1668403786 - } - }, - "numAnswer": 2, - "sha1": "f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9", - "title": "src" - }, - "kids": [ - { - "attr": { - "author": { - "content": "Ingun Jon", - "posixTime": 1668403786 - }, - "q": { - "content": "

some book's q.md

\n

\\cdots

\n

", - "posixTime": 1668403786 - } - }, - "numAnswer": 2, - "sha1": "22c0547dca2710d76a885c5ee667ad981dbe31db", - "title": "some book" - } - ], - "parentSha1": "", - "path": "src" -} \ No newline at end of file diff --git a/test/expect/some book/img.png b/test/expect/some book/img.png deleted file mode 100644 index bb5ea35..0000000 Binary files a/test/expect/some book/img.png and /dev/null differ