clod 0.1.2 → 0.1.3
raw patch · 6 files changed
+86/−95 lines, 6 filesdep ~bytestringdep ~dhalldep ~directoryPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: bytestring, dhall, directory, filepath, hashable, text, transformers, unix
API changes (from Hackage documentation)
- Clod.AdvancedCapability: instance GHC.Show.Show (Clod.AdvancedCapability.TypedPath p)
- Clod.FileSystem.Checksums: instance GHC.Generics.Generic Clod.FileSystem.Checksums.FileStatus
- Clod.FileSystem.Checksums: instance GHC.Show.Show Clod.FileSystem.Checksums.FileStatus
- Clod.FileSystem.Detection: instance GHC.Show.Show Clod.FileSystem.Detection.TextPatterns
- Clod.FileSystem.Processing: instance GHC.Show.Show Clod.FileSystem.Processing.ManifestEntry
- Clod.IgnorePatterns: instance GHC.Generics.Generic Clod.IgnorePatterns.ClodIgnorePatterns
- Clod.IgnorePatterns: instance GHC.Show.Show Clod.IgnorePatterns.CharClassPattern
- Clod.IgnorePatterns: instance GHC.Show.Show Clod.IgnorePatterns.ClodIgnorePatterns
- Clod.IgnorePatterns: instance GHC.Show.Show Clod.IgnorePatterns.PatternType
- Clod.Types: instance Data.String.IsString Clod.Types.Checksum
- Clod.Types: instance Data.String.IsString Clod.Types.IgnorePattern
- Clod.Types: instance Data.String.IsString Clod.Types.OptimizedName
- Clod.Types: instance Data.String.IsString Clod.Types.OriginalPath
- Clod.Types: instance GHC.Base.Monoid Clod.Types.Checksum
- Clod.Types: instance GHC.Base.Monoid Clod.Types.IgnorePattern
- Clod.Types: instance GHC.Base.Monoid Clod.Types.OptimizedName
- Clod.Types: instance GHC.Base.Monoid Clod.Types.OriginalPath
- Clod.Types: instance GHC.Base.Semigroup Clod.Types.Checksum
- Clod.Types: instance GHC.Base.Semigroup Clod.Types.IgnorePattern
- Clod.Types: instance GHC.Base.Semigroup Clod.Types.OptimizedName
- Clod.Types: instance GHC.Base.Semigroup Clod.Types.OriginalPath
- Clod.Types: instance GHC.Generics.Generic Clod.Types.Checksum
- Clod.Types: instance GHC.Generics.Generic Clod.Types.ClodConfig
- Clod.Types: instance GHC.Generics.Generic Clod.Types.ClodDatabase
- Clod.Types: instance GHC.Generics.Generic Clod.Types.ClodError
- Clod.Types: instance GHC.Generics.Generic Clod.Types.FileEntry
- Clod.Types: instance GHC.Generics.Generic Clod.Types.FileResult
- Clod.Types: instance GHC.Generics.Generic Clod.Types.OptimizedName
- Clod.Types: instance GHC.Generics.Generic Clod.Types.OriginalPath
- Clod.Types: instance GHC.Generics.Generic Clod.Types.SerializableClodDatabase
- Clod.Types: instance GHC.Show.Show Clod.Types.Checksum
- Clod.Types: instance GHC.Show.Show Clod.Types.ClodConfig
- Clod.Types: instance GHC.Show.Show Clod.Types.ClodDatabase
- Clod.Types: instance GHC.Show.Show Clod.Types.ClodError
- Clod.Types: instance GHC.Show.Show Clod.Types.FileEntry
- Clod.Types: instance GHC.Show.Show Clod.Types.FileReadCap
- Clod.Types: instance GHC.Show.Show Clod.Types.FileResult
- Clod.Types: instance GHC.Show.Show Clod.Types.FileWriteCap
- Clod.Types: instance GHC.Show.Show Clod.Types.IgnorePattern
- Clod.Types: instance GHC.Show.Show Clod.Types.OptimizedName
- Clod.Types: instance GHC.Show.Show Clod.Types.OriginalPath
- Clod.Types: instance GHC.Show.Show Clod.Types.SerializableClodDatabase
+ Clod.AdvancedCapability: instance GHC.Internal.Show.Show (Clod.AdvancedCapability.TypedPath p)
+ Clod.FileSystem.Checksums: instance GHC.Internal.Generics.Generic Clod.FileSystem.Checksums.FileStatus
+ Clod.FileSystem.Checksums: instance GHC.Internal.Show.Show Clod.FileSystem.Checksums.FileStatus
+ Clod.FileSystem.Detection: instance GHC.Internal.Show.Show Clod.FileSystem.Detection.TextPatterns
+ Clod.FileSystem.Processing: instance GHC.Internal.Show.Show Clod.FileSystem.Processing.ManifestEntry
+ Clod.IgnorePatterns: instance GHC.Internal.Generics.Generic Clod.IgnorePatterns.ClodIgnorePatterns
+ Clod.IgnorePatterns: instance GHC.Internal.Show.Show Clod.IgnorePatterns.CharClassPattern
+ Clod.IgnorePatterns: instance GHC.Internal.Show.Show Clod.IgnorePatterns.ClodIgnorePatterns
+ Clod.IgnorePatterns: instance GHC.Internal.Show.Show Clod.IgnorePatterns.PatternType
+ Clod.Types: instance GHC.Internal.Base.Monoid Clod.Types.Checksum
+ Clod.Types: instance GHC.Internal.Base.Monoid Clod.Types.IgnorePattern
+ Clod.Types: instance GHC.Internal.Base.Monoid Clod.Types.OptimizedName
+ Clod.Types: instance GHC.Internal.Base.Monoid Clod.Types.OriginalPath
+ Clod.Types: instance GHC.Internal.Base.Semigroup Clod.Types.Checksum
+ Clod.Types: instance GHC.Internal.Base.Semigroup Clod.Types.IgnorePattern
+ Clod.Types: instance GHC.Internal.Base.Semigroup Clod.Types.OptimizedName
+ Clod.Types: instance GHC.Internal.Base.Semigroup Clod.Types.OriginalPath
+ Clod.Types: instance GHC.Internal.Data.String.IsString Clod.Types.Checksum
+ Clod.Types: instance GHC.Internal.Data.String.IsString Clod.Types.IgnorePattern
+ Clod.Types: instance GHC.Internal.Data.String.IsString Clod.Types.OptimizedName
+ Clod.Types: instance GHC.Internal.Data.String.IsString Clod.Types.OriginalPath
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.Checksum
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.ClodConfig
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.ClodDatabase
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.ClodError
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.FileEntry
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.FileResult
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.OptimizedName
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.OriginalPath
+ Clod.Types: instance GHC.Internal.Generics.Generic Clod.Types.SerializableClodDatabase
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.Checksum
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.ClodConfig
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.ClodDatabase
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.ClodError
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.FileEntry
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.FileReadCap
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.FileResult
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.FileWriteCap
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.IgnorePattern
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.OptimizedName
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.OriginalPath
+ Clod.Types: instance GHC.Internal.Show.Show Clod.Types.SerializableClodDatabase
- Clod.AdvancedCapability: [TypedPath] :: FilePath -> TypedPath p
+ Clod.AdvancedCapability: [TypedPath] :: forall (p :: Permission). FilePath -> TypedPath p
- Clod.AdvancedCapability: class HasPermission cap (p :: Permission) | cap -> p
+ Clod.AdvancedCapability: class HasPermission (cap :: k) (p :: Permission) | cap -> p
- Clod.AdvancedCapability: createCapability :: forall p. [FilePath] -> Capability p
+ Clod.AdvancedCapability: createCapability :: forall (p :: Permission). [FilePath] -> Capability p
- Clod.AdvancedCapability: readFile :: forall p m. (MonadIO m, PermissionFor 'Read p) => Capability p -> TypedPath p -> m ByteString
+ Clod.AdvancedCapability: readFile :: forall (p :: Permission) m. (MonadIO m, PermissionFor 'Read p) => Capability p -> TypedPath p -> m ByteString
- Clod.AdvancedCapability: restrictCapability :: forall p p'. Capability p -> Capability p'
+ Clod.AdvancedCapability: restrictCapability :: forall (p :: Permission) (p' :: Permission). Capability p -> Capability p'
- Clod.AdvancedCapability: type PathWithPerm p = TypedPath p
+ Clod.AdvancedCapability: type PathWithPerm (p :: Permission) = TypedPath p
- Clod.AdvancedCapability: type family PermissionFor (required :: Permission) (provided :: Permission) :: Constraint
+ Clod.AdvancedCapability: type family PermissionFor (required :: Permission) (provided :: Permission)
- Clod.AdvancedCapability: unsafeAsPath :: FilePath -> TypedPath p
+ Clod.AdvancedCapability: unsafeAsPath :: forall (p :: Permission). FilePath -> TypedPath p
- Clod.AdvancedCapability: withPath :: forall p m a. MonadIO m => Capability p -> FilePath -> (Maybe (TypedPath p) -> m a) -> m a
+ Clod.AdvancedCapability: withPath :: forall (p :: Permission) m a. MonadIO m => Capability p -> FilePath -> (Maybe (TypedPath p) -> m a) -> m a
- Clod.AdvancedCapability: writeFile :: forall p m. (MonadIO m, PermissionFor 'Write p) => Capability p -> TypedPath p -> ByteString -> m ()
+ Clod.AdvancedCapability: writeFile :: forall (p :: Permission) m. (MonadIO m, PermissionFor 'Write p) => Capability p -> TypedPath p -> ByteString -> m ()
- Clod.Effects: type ClodM a = ReaderT ClodConfig (ExceptT ClodError IO) a
+ Clod.Effects: type ClodM a = ReaderT ClodConfig ExceptT ClodError IO a
- Clod.Types: type ClodM a = ReaderT ClodConfig (ExceptT ClodError IO) a
+ Clod.Types: type ClodM a = ReaderT ClodConfig ExceptT ClodError IO a
Files
- clod.cabal +36/−63
- src/Clod/FileSystem/Processing.hs +6/−3
- src/Clod/FileSystem/Transformations.hs +7/−2
- src/Clod/IgnorePatterns.hs +8/−3
- src/Clod/Types.hs +2/−7
- test/Clod/FileSystem/ChecksumsSpec.hs +27/−17
clod.cabal view
@@ -1,6 +1,6 @@-cabal-version: 2.0+cabal-version: 2.4 name: clod-version: 0.1.2+version: 0.1.3 synopsis: Project file manager for Claude AI integrations description: Clod (Claude Loader) is a utility for preparing and uploading files to Claude AI's Project Knowledge feature. It tracks file changes, respects .gitignore and .clodignore @@ -70,7 +70,31 @@ type: git location: https://github.com/fuzz/clod +common deps+ build-depends: + base >= 4.7 && < 5,+ directory >= 1.3 && < 1.5,+ filepath >= 1.4 && < 1.6,+ process >= 1.6 && < 1.7,+ text >= 1.2 && < 2.2,+ bytestring >= 0.10 && < 0.13,+ containers >= 0.6 && < 0.7,+ time >= 1.9 && < 1.13,+ hashable >= 1.3 && < 1.6++common extensions+ default-extensions: + OverloadedStrings+ TypeOperators+ FlexibleContexts+ GADTs+ DataKinds+ ScopedTypeVariables+ TypeApplications+ RecordWildCards+ library+ import: deps, extensions hs-source-dirs: src exposed-modules: Clod.Core@@ -90,70 +114,36 @@ other-modules: Paths_clod autogen-modules: Paths_clod build-depends: - base >= 4.7 && < 5,- directory >= 1.3 && < 1.4,- filepath >= 1.4 && < 1.5,- process >= 1.6 && < 1.7,- text >= 1.2 && < 1.3, aeson >= 2.0 && < 3.0, aeson-pretty >= 0.8 && < 0.9,- dhall >= 1.41 && < 1.43,- bytestring >= 0.10 && < 0.12,- containers >= 0.6 && < 0.7,- time >= 1.9 && < 1.13,+ dhall >= 1.41 && < 1.44, temporary >= 1.3 && < 1.4, mtl >= 2.2 && < 2.4,- transformers >= 0.5 && < 0.6,- unix >= 2.7 && < 2.8,+ transformers >= 0.5 && < 0.7,+ unix >= 2.7 && < 2.9, xxhash-ffi >= 0.3 && < 0.4,- hashable >= 1.3 && < 1.5, base16-bytestring >= 1.0 && < 1.1, magic >= 1.1 && < 1.2, prettyprinter >= 1.7 && < 1.8, file-embed >= 0.0.15 && < 0.1 default-language: Haskell2010- default-extensions: - OverloadedStrings- TypeOperators- FlexibleContexts- GADTs- DataKinds- ScopedTypeVariables- TypeApplications- TemplateHaskell- LambdaCase- RecordWildCards+ default-extensions: TemplateHaskell+ LambdaCase ghc-options: -Wall executable clod+ import: deps, extensions hs-source-dirs: app main-is: Main.hs build-depends: - base >= 4.7 && < 5, clod,- directory >= 1.3 && < 1.4,- filepath >= 1.4 && < 1.5,- text >= 1.2 && < 1.3, aeson >= 2.0 && < 3.0,- bytestring >= 0.10 && < 0.12,- containers >= 0.6 && < 0.7,- time >= 1.9 && < 1.13,- process >= 1.6 && < 1.7,- hashable >= 1.3 && < 1.5, optparse-applicative >= 0.16 && < 0.18 default-language: Haskell2010- default-extensions: - OverloadedStrings- TypeOperators- FlexibleContexts- GADTs- DataKinds- ScopedTypeVariables- TypeApplications- RecordWildCards ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N test-suite clod-test+ import: deps, extensions type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs@@ -177,32 +167,15 @@ Clod.ManPagesSpec Clod.TestHelpers build-depends: - base >= 4.7 && < 5, clod,- directory >= 1.3 && < 1.4,- filepath >= 1.4 && < 1.5,- process >= 1.6 && < 1.7, temporary >= 1.3 && < 1.4,- text >= 1.2 && < 1.3,- time >= 1.9 && < 1.13,+ unix >= 2.7 && < 2.9,+ optparse-applicative >= 0.16 && < 0.18, hspec >= 2.8 && < 2.12, QuickCheck >= 2.14 && < 2.16,- unix >= 2.7 && < 2.8,- bytestring >= 0.10 && < 0.12,- containers >= 0.6 && < 0.7,- optparse-applicative >= 0.16 && < 0.18, random >= 1.2 && < 1.4, mtl >= 2.2 && < 2.4 default-language: Haskell2010- default-extensions: - OverloadedStrings- TypeOperators- FlexibleContexts- GADTs- DataKinds- ScopedTypeVariables- TypeApplications- RecordWildCards ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N executable magictest@@ -211,7 +184,7 @@ build-depends: base >= 4.7 && < 5, magic >= 1.1 && < 1.2,- directory >= 1.3 && < 1.4+ directory >= 1.3 && < 1.5 default-language: Haskell2010 default-extensions: OverloadedStrings ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
src/Clod/FileSystem/Processing.hs view
@@ -334,10 +334,13 @@ -- Remove trailing slash if present let cleanDir = if L.isSuffixOf "/" dir then init dir else dir -- Apply hidden file transformation if needed- in if not (null cleanDir) && head cleanDir == '.'- then "dot--" ++ tail cleanDir+ in if not (null cleanDir) && cleanDir `startsWith` '.'+ then "dot--" ++ drop 1 cleanDir else cleanDir- + where+ startsWith :: String -> Char -> Bool+ startsWith [] _ = False+ startsWith (x:_) c = x == c -- Split a path into its directory components splitPath :: FilePath -> [String] splitPath path = filter (not . null) $ map getSegment $ L.groupBy sameGroup path
src/Clod/FileSystem/Transformations.hs view
@@ -51,6 +51,11 @@ -- transformFilename ".gitignore" -- returns "dot--gitignore" -- transformFilename "image.png" -- returns "image.png" (no change) -- @+-- | Helper function to check if a string starts with a character+startsWith :: String -> Char -> Bool+startsWith [] _ = False+startsWith (x:_) c = x == c+ transformFilename :: String -> String -> String transformFilename name original -- SVG files must be transformed to XML for Claude compatibility@@ -59,9 +64,9 @@ let baseName = take (length name - 4) name in sanitizeFilename $ baseName ++ "-svg.xml" -- Handle hidden files (those starting with a dot)- | not (null name) && head name == '.' =+ | not (null name) && name `startsWith` '.' = -- Remove the leading dot and add the "dot--" prefix- "dot--" ++ tail name+ "dot--" ++ drop 1 name -- Handle empty filename | null name = "unnamed" -- Match test case explicitly to keep compatibility with tests
src/Clod/IgnorePatterns.hs view
@@ -482,6 +482,11 @@ -- simpleGlobMatch "file[0-9].txt" "file5.txt" -- Returns True -- simpleGlobMatch "*.txt" "file.md" -- Returns False -- @++-- | Helper function to check if a string starts with a character+startsWith :: String -> Char -> Bool+startsWith [] _ = False+startsWith (x:_) c = x == c simpleGlobMatch :: String -> FilePath -> Bool simpleGlobMatch ptn = \filepath -> matchGlob ptn filepath where@@ -529,14 +534,14 @@ -- Beginning of character class: [ ((CharClassStart cs), (c:path')) -> let (classSpec, rest) = span (/= ']') cs- negated = not (null classSpec) && head classSpec == '!'- actualClass = if negated then tail classSpec else classSpec+ negated = not (null classSpec) && classSpec `startsWith` '!'+ actualClass = if negated then drop 1 classSpec else classSpec in if null rest -- Malformed pattern, no closing ] then False else let matches = matchCharacterClass actualClass c result = if negated then not matches else matches- in result && matchGlob (tail rest) path'+ in result && matchGlob (drop 1 rest) path' -- Regular character matching ((p:ps), (c:cs)) ->
src/Clod/Types.hs view
@@ -74,7 +74,6 @@ import Control.Monad.Reader (ReaderT, ask, asks, local, runReaderT) import Data.String (IsString(..)) import GHC.Generics (Generic)-import Data.Typeable (Typeable) import Data.List (isPrefixOf) import System.Directory (canonicalizePath) import Data.Time.Clock (UTCTime)@@ -125,7 +124,6 @@ , lastMode :: !Bool -- ^ Whether to use the previous staging directory , ignorePatterns :: ![IgnorePattern] -- ^ Patterns from .gitignore and .clodignore } deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable) -- | Result of processing a file -- @@ -135,7 +133,6 @@ = Success -- ^ File was successfully processed | Skipped !String -- ^ File was skipped with the given reason deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable) -- | Errors that can occur during Clod operation --@@ -149,7 +146,6 @@ | DatabaseError !String -- ^ Error related to checksums database | ChecksumError !String -- ^ Error related to checksum calculation deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable) -- | Newtype for file checksums to prevent mixing with other string types newtype Checksum = Checksum { unChecksum :: String }@@ -169,7 +165,7 @@ , entryLastModified :: !UTCTime -- ^ Last modified time , entryOptimizedName :: !OptimizedName -- ^ Name in staging directory } deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable, FromDhall, ToDhall, FromJSON, ToJSON)+ deriving anyclass (FromDhall, ToDhall, FromJSON, ToJSON) -- | Main database structure data ClodDatabase = ClodDatabase@@ -178,7 +174,6 @@ , dbLastStagingDir :: !(Maybe FilePath) -- ^ Previous staging directory , dbLastRunTime :: !UTCTime -- ^ Time of last run } deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable) -- | Serialization-friendly version of ClodDatabase data SerializableClodDatabase = SerializableClodDatabase@@ -187,7 +182,7 @@ , serializedLastStagingDir :: !(Maybe FilePath) -- ^ Previous staging directory , serializedLastRunTime :: !UTCTime -- ^ Time of last run } deriving stock (Show, Eq, Generic)- deriving anyclass (Typeable, FromDhall, ToDhall, FromJSON, ToJSON)+ deriving anyclass (FromDhall, ToDhall, FromJSON, ToJSON) -- | Convert to serializable form toSerializable :: ClodDatabase -> SerializableClodDatabase
test/Clod/FileSystem/ChecksumsSpec.hs view
@@ -241,9 +241,11 @@ -- Should have one change (just the text file) length changes `shouldBe` 1 -- The change should be for the text file only- let (path, status) = head changes- path `shouldBe` "text.txt"- status `shouldBe` New+ case changes of+ (path, status):_ -> do+ path `shouldBe` "text.txt"+ status `shouldBe` New+ [] -> expectationFailure "Expected at least one change" -- The binary file should not be present in the changes any (("binary.bin" ==) . fst) changes `shouldBe` False @@ -271,9 +273,11 @@ -- Should have one change length changes `shouldBe` 1 -- The change should be a new file- let (path, status) = head changes- path `shouldBe` "new.txt"- status `shouldBe` New+ case changes of+ (path, status):_ -> do+ path `shouldBe` "new.txt"+ status `shouldBe` New+ [] -> expectationFailure "Expected at least one change" it "identifies modified files" $ do withSystemTempDirectory "clod-test" $ \tmpDir -> do@@ -321,9 +325,11 @@ -- Should have one change length changes `shouldBe` 1 -- The change should be a modified file- let (path, status) = head changes- path `shouldBe` "test.txt"- status `shouldBe` Modified+ case changes of+ (path, status):_ -> do+ path `shouldBe` "test.txt"+ status `shouldBe` Modified+ [] -> expectationFailure "Expected at least one change" it "identifies renamed files" $ do withSystemTempDirectory "clod-test" $ \tmpDir -> do@@ -378,14 +384,18 @@ -- Should have one change length changes `shouldBe` 1 -- The change should be a renamed file- let (path, status) = head changes- path `shouldBe` "renamed.txt"- case status of- Renamed oldPath -> oldPath `shouldBe` "original.txt"- _ -> expectationFailure $ "Expected renamed status, got: " ++ show status+ case changes of+ (path, status):_ -> do+ path `shouldBe` "renamed.txt"+ case status of+ Renamed oldPath -> oldPath `shouldBe` "original.txt"+ _ -> expectationFailure $ "Expected renamed status, got: " ++ show status+ [] -> expectationFailure "Expected at least one change" -- Should include the renamed file in the renamed list length renamed `shouldBe` 1- let (newPath, oldPath) = head renamed- newPath `shouldBe` "renamed.txt"- oldPath `shouldBe` "original.txt"+ case renamed of+ (newPath, oldPath):_ -> do+ newPath `shouldBe` "renamed.txt"+ oldPath `shouldBe` "original.txt"+ [] -> expectationFailure "Expected at least one renamed file"