Add user documentation

Add a manpage for ca-store. Additionally, modify the 'file verify'
command to behave exactly as the manpage mentions. This commit
required me to break a cycle caused by having CAStore.Type.Text
imported by CAStore.Type.
This commit is contained in:
hylodon 2025-09-22 15:18:36 +01:00
parent 1849baa588
commit d2016d1863
9 changed files with 119 additions and 32 deletions

1
.gitignore vendored
View file

@ -5,6 +5,7 @@
!flake.lock
!package.yaml
!app/Main.hs
!doc/man/*.scd
!src/CAStore/Command.hs
!src/CAStore/Command/Autocomplete.hs
!src/CAStore/Command/Type.hs

73
doc/man/ca-store.1.scd Normal file
View file

@ -0,0 +1,73 @@
ca-store(1)
# NAME
ca-store - A simple content-addressable store with tagging support
# SYNOPSIS
*ca-store* [opts] store [command..]
# OPTIONS
## File commands
*file* *add* [file..]
Adds the given files to the store and returns the ids of these files,
one per line. The ids will be returned in the same order that the files
are given on the command line. This command is idempotent.
*file* *remove* [id..]
Remove the files from the store. This command is permanent, so it is
recommended to make a backup of the file. Also consider making a copy
of its tags as well with the *tag* *show* command.
*file* *verify* [id..]
Recalculate the ids of every id given to ensure they still match. Any
corrupted file will have its id printed to standard output.
## Tag commands
*tag* *add* id tag [tag..]
Add all of the given tags to the file referenced by 'id'.
*tag* *remove* id tag [tag..]
Remove all the given tags from the file referenced by 'id'. No error
will be raised if you attempt to remove a non-existent tag.
*tag* *show* id
Show all tags assigned to the file referenced by 'id'. No error will
be raised if you attempt to reference a non-existent file.
*tag* *super* *add* supertag [subtag..]
Declare the first tag to be a supertag to all other given tags. A
supertag is a tag that is automatically implied by any of its subtags.
A supertag cannot be a subtag of any of its subtags. *ca-store* will
silently ignore any attempt to make an infinite cycle of tags.
*tag* *super* *remove* supertag [subtag..]
Remove the supertag relationship between the supertag and its subtags.
If you wish for certain files to keep their supertags and not others,
manually add the supertag with the *tag* *add* command before running
*tag* *super* *remove*.
*tag* *super* *show* supertag
Show all subtags of the given supertag. If the given tag isn't a
supertag then nothing will be displayed.
*tag* *sub* *add* subtag [supertag..]
Declare the first tag to be a subtag to all other given tags. A subtag
is a tag that automatically implies any supertags.
A subtag cannot be a supertag of any of its supertags. *ca-store* will
silently ignore any attempt to make an infinite cycle of tags.
*tag* *sub* *remove* subtag [supertag..]
Remove the subtag relationship between the subtag and its supertags.
*tag* *sub* *show* subtag
Show all supertags of the given subtag. If the given tag isn't a
subtag then nothing will be displayed.

View file

@ -17,11 +17,26 @@
);
overlays.default = final: prev: {
ca-store = final.haskellPackages.developPackage {
ca-store = prev.symlinkJoin {
pname = "ca-store";
version = final.ca-store-bin.version;
paths = [ final.ca-store-bin final.ca-store-man ];
};
ca-store-bin = prev.haskellPackages.developPackage {
root = ./.;
name = "ca-store";
withHoogle = false;
};
ca-store-man = prev.stdenv.mkDerivation {
pname = "ca-store-man";
version = "0.0.1";
src = ./.;
nativeBuildInputs = [ final.scdoc final.installShellFiles ];
buildCommand = ''
scdoc < "$src/doc/man/ca-store.1.scd" > ./ca-store.1
installManPage ./ca-store.1
'';
};
};
};
}

View file

@ -57,7 +57,6 @@ library:
- CAStore.Program.IO.Text
- CAStore.Program.Storage
- CAStore.Type
- CAStore.Type.Text
- Data.List.Extra
executables:

View file

@ -10,8 +10,8 @@ import CAStore.Program (Program, storeFile, unstoreFile, getFileLocation)
import CAStore.Program.IO
import CAStore.Program.IO.Text
import CAStore.Program.Storage
import CAStore.Type.Text
import Control.Monad (forM_)
import CAStore.Type
import Control.Monad (forM_, filterM, (<=<))
import Control.Monad.IO.Class (liftIO)
import System.Directory (doesFileExist)
@ -23,15 +23,10 @@ runCommand (FileAdd fs) = do
runCommand (FileRemove ss)
= unregisterFiles ss
*> forM_ ss unstoreFile
runCommand (FileVerify ss) = forM_ ss $ \sid -> do
file <- getFileLocation sid
liftIO (doesFileExist file) >>= \case
True -> do
sid' <- generateId file
case sid == sid' of
True -> msg $ MsgArb $ show sid ++ " is valid."
False -> err $ ErrArb $ show sid ++ " has been corrupted."
False -> err $ ErrArb $ show sid ++ " does not exist in the store."
runCommand (FileVerify ss)
= mapM_ (err . ErrCorruptStoreId)
<=< filterM (fmap not . verifyFile)
$ ss
runCommand (TagAdd sid ts) = do
registerTags ts
assignTags sid ts
@ -53,3 +48,11 @@ runCommand (TagSuperShow sup) = do
showRelationSuper sup
runCommand (TagSubShow sub) = do
showRelationSub sub
-- | Recalculate the store id of a file and check to see if it matches.
verifyFile :: StoreId -> Program Bool
verifyFile sid = do
file <- getFileLocation sid
liftIO (doesFileExist file) >>= \case
True -> ((==) sid) <$> generateId file
False -> pure True

View file

@ -7,7 +7,7 @@ where
import CAStore.Program.Internal (Program(..), getArguments, getConnectionHandle)
import CAStore.Program.IO.Text (msg)
import CAStore.Type.Text (Message(MsgArb))
import CAStore.Type (Message(MsgArb))
import Control.Monad.IO.Class (liftIO)
import Data.Foldable (for_)
import Database.SQLite.Simple (Query, query_, fromOnly)

View file

@ -6,7 +6,7 @@ module CAStore.Program.IO.Text
where
import CAStore.Program.Internal (Program(..))
import CAStore.Type.Text
import CAStore.Type
import Control.Monad.IO.Class (liftIO)
import Data.Foldable1 (intercalate1)
import Data.List.NonEmpty (NonEmpty(..))
@ -21,6 +21,8 @@ err (ErrInvalidCommand [])
= display $ "ERROR: No command specified"
err (ErrInvalidCommand (x:xs))
= display $ "ERROR: Invalid command '" ++ intercalate1 " " (x :| xs) ++ "'"
err (ErrCorruptStoreId (StoreId x))
= display x
warn :: Warning -> Program ()
warn (WarnArb x) = display $ "WARN: " ++ x

View file

@ -10,7 +10,6 @@ module CAStore.Type
)
where
import CAStore.Type.Text
import Database.SQLite.Simple.FromField (FromField, fromField)
import Database.SQLite.Simple.ToField (ToField, toField)
@ -47,3 +46,14 @@ parseStoreLocation x = pure $ StoreShort x
data OutputType = OutputId | OutputFilename
deriving (Show)
data Error
= ErrArb String
| ErrInvalidCommand [String]
| ErrCorruptStoreId StoreId
data Warning
= WarnArb String
data Message
= MsgArb String

View file

@ -1,16 +0,0 @@
module CAStore.Type.Text
( Error(..)
, Warning(..)
, Message(..)
)
where
data Error
= ErrArb String
| ErrInvalidCommand [String]
data Warning
= WarnArb String
data Message
= MsgArb String