packages feed

tagtree (empty) → 0.1.0.0

raw patch · 5 files changed

+290/−0 lines, 5 filesdep +aesondep +basedep +bytestring

Dependencies added: aeson, base, bytestring, containers, data-default, filepattern, megaparsec, parser-combinators, relude, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2021, Sridhar Ratnakumar++All rights reserved.++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 Sridhar Ratnakumar nor the names of other+      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+OWNER 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.
+ README.md view
@@ -0,0 +1,15 @@+# tagtree++Haskell library for representing hierarchical tags, such as `foo/bar/qux`, as well as a tree of such tags and then searching them based on path patterns.++## Developing++- [Install Nix](https://nixos.org/download.html) & [enable Flakes](https://nixos.wiki/wiki/Flakes)+- Run `nix-shell --run haskell-language-server` to sanity check your environment +- [Open as single-folder workspace](https://code.visualstudio.com/docs/editor/workspaces#_singlefolder-workspaces) in Visual Studio Code+    - Install the [workspace recommended](https://code.visualstudio.com/docs/editor/extension-marketplace#_workspace-recommended-extensions) extensions+    - <kbd>Ctrl+Shift+P</kbd> to run command "Nix-Env: Select Environment" and select `shell.nix`. The extension will ask you to reload VSCode at the end.+- Press <kbd>Ctrl+Shift+B</kbd> in VSCode, or run `bin/run` (`bin/run-via-tmux` if you have tmux installed) in terminal, to launch Ghcid running your program.++All but the final step need to be done only once.+
+ src/Data/TagTree.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}++module Data.TagTree+  ( -- * Types+    Tag (..),+    TagPattern (unTagPattern),+    TagNode (..),++    -- * Create Tags+    constructTag,+    deconstructTag,++    -- * Creating Tag Trees+    tagTree,++    -- * Searching Tags+    mkTagPattern,+    mkTagPatternFromTag,+    tagMatch,++    -- * Working with Tag Trees+    foldTagTree,+  )+where++import Control.Monad.Combinators.NonEmpty (sepBy1)+import Data.Aeson (FromJSON, ToJSON, ToJSONKey)+import Data.Default (Default (def))+import qualified Data.Map.Strict as Map+import Data.TagTree.PathTree (annotatePathsWith, foldSingleParentsWith, mkTreeFromPaths)+import qualified Data.Text as T+import Data.Tree (Forest)+import System.FilePattern (FilePattern, (?==))+import qualified Text.Megaparsec as M+import qualified Text.Megaparsec.Char as M++-- | A hierarchical tag+--+-- Tag nodes are separated by @/@+newtype Tag = Tag {unTag :: Text}+  deriving (Eq, Ord, Show, Generic)+  deriving newtype+    ( ToJSON,+      FromJSON,+      ToJSONKey+    )++--------------+-- Tag Pattern+---------------++-- | A glob-based pattern to match hierarchical tags+--+-- For example, the pattern+--+-- > foo/**+--+-- matches both the following+--+-- > foo/bar/baz+-- > foo/baz+newtype TagPattern = TagPattern {unTagPattern :: FilePattern}+  deriving+    ( Eq,+      Ord,+      Show,+      Generic+    )+  deriving newtype+    ( ToJSON,+      FromJSON+    )++mkTagPattern :: Text -> TagPattern+mkTagPattern =+  TagPattern . toString++mkTagPatternFromTag :: Tag -> TagPattern+mkTagPatternFromTag (Tag t) =+  TagPattern $ toString t++tagMatch :: TagPattern -> Tag -> Bool+tagMatch (TagPattern pat) (Tag tag) =+  pat ?== toString tag++-----------+-- Tag Tree+-----------++-- | An individual component of a hierarchical tag+--+-- The following hierarchical tag,+--+-- > foo/bar/baz+--+-- has three tag nodes: @foo@, @bar@ and @baz@+newtype TagNode = TagNode {unTagNode :: Text}+  deriving (Eq, Show, Ord, Generic)+  deriving newtype (ToJSON)++deconstructTag :: HasCallStack => Tag -> NonEmpty TagNode+deconstructTag (Tag s) =+  either error id $ parse tagParser (toString s) s+  where+    tagParser :: Parser (NonEmpty TagNode)+    tagParser =+      nodeParser `sepBy1` M.char '/'+    nodeParser :: Parser TagNode+    nodeParser =+      TagNode . toText <$> M.some (M.anySingleBut '/')++constructTag :: NonEmpty TagNode -> Tag+constructTag (fmap unTagNode . toList -> nodes) =+  Tag $ T.intercalate "/" nodes++-- | Construct the tree from a list of hierarchical tags+tagTree :: (Eq a, Default a) => Map Tag a -> Forest (TagNode, a)+tagTree tags =+  fmap (annotatePathsWith $ countFor tags) $+    mkTreeFromPaths $+      toList . deconstructTag+        <$> Map.keys tags+  where+    countFor tags' path =+      fromMaybe def $ Map.lookup (constructTag path) tags'++foldTagTree :: (Eq a, Default a) => Forest (TagNode, a) -> Forest (NonEmpty TagNode, a)+foldTagTree tree =+  foldSingleParentsWith foldNodes <$> fmap (fmap (first (:| []))) tree+  where+    foldNodes (parent, x) (child, y) = do+      guard (x == def)+      Just (parent <> child, y)++type Parser a = M.Parsec Void Text a++parse :: Parser a -> String -> Text -> Either Text a+parse p fn s =+  first (toText . M.errorBundlePretty) $+    M.parse (p <* M.eof) fn s
+ src/Data/TagTree/PathTree.hs view
@@ -0,0 +1,38 @@+module Data.TagTree.PathTree+  ( mkTreeFromPaths,+    annotatePathsWith,+    foldSingleParentsWith,+  )+where++import qualified Data.List.NonEmpty as NE+import qualified Data.Map as Map+import Data.Tree (Forest, Tree (Node))+import Relude.Extra.Group (groupBy)++mkTreeFromPaths :: Ord a => [[a]] -> Forest a+mkTreeFromPaths paths = uncurry mkNode <$> Map.assocs groups+  where+    groups = fmap tail <$> groupBy head (mapMaybe nonEmpty paths)+    mkNode label children =+      Node label $ mkTreeFromPaths $ toList children++annotatePathsWith :: (NonEmpty a -> ann) -> Tree a -> Tree (a, ann)+annotatePathsWith f = go []+  where+    go ancestors (Node rel children) =+      let path = rel :| ancestors+       in Node (rel, f $ NE.reverse path) $ fmap (go $ toList path) children++-- | Fold nodes with one child using the given function+--+-- The function is called with the parent and the only child. If a Just value is+-- returned, folding happens with that value, otherwise there is no effect.+foldSingleParentsWith :: (a -> a -> Maybe a) -> Tree a -> Tree a+foldSingleParentsWith f = go+  where+    go (Node parent children) =+      case fmap go children of+        [Node child grandChildren]+          | Just new <- f parent child -> Node new grandChildren+        xs -> Node parent xs
+ tagtree.cabal view
@@ -0,0 +1,64 @@+cabal-version:      2.4+name:               tagtree+version:            0.1.0.0+license:            BSD-3-Clause+copyright:          2021 Sridhar Ratnakumar+maintainer:         srid@srid.ca+author:             Sridhar Ratnakumar+category:           Web++-- TODO: Before hackage release.+-- A short (one-line) description of the package.+synopsis:           Hierarchical Tags & Tag Trees++-- A longer description of the package.+description:+  Haskell library for representing hierarchical tags (eg: @foo\/bar\/qux@) and trees of such tags, as well as for searching them based on filepath patterns.++-- A URL where users can report bugs.+-- bug-reports:++extra-source-files:+  LICENSE+  README.md++library+  build-depends:+    , aeson+    , base >= 4.13.0.0 && <=4.17.0.0+    , bytestring+    , containers+    , data-default+    , filepattern+    , megaparsec+    , parser-combinators+    , relude+    , text++  mixins:+    base hiding (Prelude),+    relude (Relude as Prelude, Relude.Container.One),+    relude++  ghc-options:+    -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns++  exposed-modules:+    Data.TagTree+    Data.TagTree.PathTree++  default-extensions:+    DerivingStrategies+    FlexibleContexts+    FlexibleInstances+    KindSignatures+    LambdaCase+    MultiParamTypeClasses+    MultiWayIf+    OverloadedStrings+    ScopedTypeVariables+    TupleSections+    ViewPatterns++  hs-source-dirs:     src+  default-language:   Haskell2010