-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathTreeDB.hs
124 lines (102 loc) · 3.5 KB
/
TreeDB.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
{-# LANGUAGE StandaloneDeriving #-}
module TreeDB(
DirList,
dlEmpty, dlByExt, dlByExts, dlAdd, dlAddByExt,
TreeDB,
tdbEmpty, tdbByDir, tdbAdd, tdbAddDir,
tdbBuild, tdbMerge,
tdbByDirExt, tdbByDirExts
)
where
import qualified Data.ByteString.Char8 as C
import Data.List
import Data.Trie(Trie)
import qualified Data.Trie as T
import Data.Typeable
import System.FilePath
--
-- The files in a directory, partitioned by extension.
--
type DirList = [(String, [String])]
dlEmpty :: DirList
dlEmpty = []
-- Linear search for files by extension, in a single directory.
dlByExt :: String -> DirList -> [String]
dlByExt _ [] = []
dlByExt ext ((ext', names) : dirlist)
| ext' == ext = [n <.> ext' | n <- names]
| otherwise = dlByExt ext dirlist
-- Search for multiple extensions at once. 'exts' must be sorted, with no
-- duplicates.
dlByExts :: [String] -> DirList -> [String]
dlByExts _ [] = []
dlByExts [] _ = []
dlByExts (ext:exts) ((ext', names):dirlist) =
case compare ext ext' of
-- 'ext' isn't in the list.
LT -> dlByExts exts ((ext', names):dirlist)
-- 'ext' is right here.
EQ -> [n <.> ext' | n <- names] ++ dlByExts exts dirlist
-- 'ext' may be in the remainder. Nothing else can match here.
GT -> dlByExts (ext:exts) dirlist
-- Insert a file, given its extension. Again linear.
dlAdd :: FilePath -> DirList -> DirList
dlAdd file dirList =
dlAddByExt (takeExtension file) (dropExtension file) dirList
-- Keeps the list sorted by extension
dlAddByExt :: String -> String -> DirList -> DirList
dlAddByExt ext name [] = [(ext, [name])]
dlAddByExt ext name ((ext', names):dirlist) =
case compare ext ext' of
LT -> (ext, [name]):(ext', names):dirlist
EQ -> (ext', name:names):dirlist
GT -> (ext', names):(dlAddByExt ext name dirlist)
--
-- A map from directory to contents, excluding subdirectories.
--
type TreeDB = Trie DirList
deriving instance Typeable Trie
tdbEmpty :: TreeDB
tdbEmpty = T.empty
-- Get directory contents by directory path
tdbByDir :: FilePath -> TreeDB -> Maybe DirList
tdbByDir path treeDB = T.lookup (C.pack path) treeDB
-- Add a file
tdbAdd :: FilePath -> TreeDB -> TreeDB
tdbAdd path treeDB
| T.member dirS treeDB =
T.adjust (\dirList -> dlAdd file dirList) dirS treeDB
| otherwise =
T.insert dirS (dlAdd file dlEmpty) treeDB
where
dir = takeDirectory path
file = takeFileName path
dirS = C.pack dir
-- Add a directory, complete with (relative) contents
tdbAddDir :: FilePath -> [FilePath] -> TreeDB -> TreeDB
tdbAddDir dir files treeDB
| T.member dirS treeDB =
T.adjust (\dirList -> foldr dlAdd dirList files) dirS treeDB
| otherwise =
T.insert dirS (foldr dlAdd dlEmpty files) treeDB
where
dirS = C.pack dir
tdbBuild :: [FilePath] -> TreeDB
tdbBuild files = foldr tdbAdd tdbEmpty files
tdbMerge :: TreeDB -> TreeDB -> TreeDB
tdbMerge = T.unionL
--
-- Combined queries
--
-- Find files by directory and extension
tdbByDirExt :: FilePath -> String -> TreeDB -> Maybe [FilePath]
tdbByDirExt path ext treeDB = do
dirList <- tdbByDir path treeDB
let filenames = dlByExt ext dirList
return [ path </> file | file <- filenames ]
-- Look for multiple extensions. 'exts' need not be sorted.
tdbByDirExts :: FilePath -> [String] -> TreeDB -> Maybe [FilePath]
tdbByDirExts path exts treeDB = do
dirList <- tdbByDir path treeDB
let filenames = dlByExts (sort exts) dirList
return [ path </> file | file <- filenames ]