-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathParse.hs
312 lines (254 loc) · 9.3 KB
/
Parse.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
{-# LANGUAGE DeriveDataTypeable #-}
module Parse (parseGantt, Period(..), Gantt(..), ChartLine(..), ChartType(..), Day(..), defaultGantt, defaultDay, parseDate)
where
import Control.Monad (when)
import Text.ParserCombinators.Parsec
--import Text.ParserCombinators.Parsec.Number (int)
import System.Console.CmdArgs -- hack: defines important defaults
import Data.Char (digitToInt)
import Data.List (foldl')
import Data.Maybe (fromMaybe)
import Data.Time.Locale.Compat (defaultTimeLocale)
import Data.Time.Format (buildTime, parseTime)
import Data.Time.Calendar (Day, fromGregorian)
instance Default Day where
def = fromGregorian 1970 1 1
defaultDay :: Day
defaultDay = def
data Period = Daily | Weekly | Monthly | Quarterly | Yearly | DefaultPeriod
deriving (Data, Typeable, Show, Eq)
instance Default Period where
def = DefaultPeriod
data ChartType = GanttChart | Markdown
deriving (Data, Typeable, Show, Eq)
instance Default ChartType where
def = GanttChart
data Gantt = Gantt {
start :: Day
, dur :: Int
, windowStart :: Day
, windowDur :: Int
, inSize :: Period
, outSize :: Period
, entries :: [ChartLine]
, msg :: String
, today :: Day
-- Command line only options.
, font :: String
, labelWidth :: Int
, standalone :: Bool
, markToday :: Bool
-- , outfile :: FilePath
, verbose :: Bool
, file :: FilePath
, template :: FilePath
, chartopts :: String
, charttype :: ChartType
} deriving (Data, Typeable, Show)
defaultGantt = Gantt {
start = def
, dur = def
, windowStart = def
, windowDur = def
, inSize = def
, outSize = def
, entries = []
, msg = def
, today = def
-- Command line only options.
, font = def
, labelWidth = 15
, standalone = True
, markToday = True
-- , outfile :: FilePath
, verbose = False
-- , file :: FilePath
, template = def
, chartopts = def
, charttype = def
, file = def
}
data ConfigLine = Start Day
| Dur Int
| Today Day
| Period Period
deriving (Data, Typeable, Show)
data ChartLine = Group String Int Int
| SlippedGroup String Int Int Int Int
| Task String Int Int
| SlippedTask String Int Int Int Int
| Milestone String Int
| SlippedMilestone String Int Int
| Deliverable String Int
| SlippedDeliverable String Int Int
deriving (Data, Typeable, Show)
parseGantt :: Gantt -> String -> Either ParseError Gantt
parseGantt cfg c = runParser gantt cfg (file cfg) c
gantt :: GenParser Char Gantt Gantt
gantt =
config >>
chart >>=
(\es -> getState >>=
(\g -> (return $ g { entries = es
}) ))
-- Configuration -----------------------------------------
config :: GenParser Char Gantt [ConfigLine]
config = updateState (\cfg -> cfg {msg = (msg cfg) ++ " config;" }) >>
manyTill configline (lookAhead (try chartline))
configline :: GenParser Char Gantt ConfigLine
configline =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " configline;" }) >>
skipMany (try comment <|> blankline ) >>
(try startDate <|> try duration <|> try reportPeriodSize <|> try reportBy <|> try todayLine) >>= (\l ->
skipMany (try comment <|> blankline ) >>
return l)
startDate :: GenParser Char Gantt ConfigLine
startDate =
string "start:" >>
spaces >>
aString >>= (\v -> let t = (fromMaybe (buildTime defaultTimeLocale []) $ parseTime defaultTimeLocale "%Y-%m-%d" v) in
getState >>= (\cfg ->
(when ((start cfg) == def) $ updateState (\cfg -> cfg { start = t })) >>
newline >>
(return $ Start t) ))
duration :: GenParser Char Gantt ConfigLine
duration =
string "dur:" >>
spaces >>
int >>= (\v ->
getState >>= (\cfg ->
(when ((dur cfg) == def) $ setState cfg { dur = v }) >>
newline >>
(return $ Dur v) ))
parseDate :: String -> Day
parseDate v = (fromMaybe (buildTime defaultTimeLocale []) $ parseTime defaultTimeLocale "%Y-%m-%d" v)
todayLine :: GenParser Char Gantt ConfigLine
todayLine =
string "today:" >>
spaces >>
aString >>= (\v -> let t = parseDate v in
getState >>= (\cfg ->
(when ((today cfg) == def) $ updateState (\cfg -> cfg { today = t })) >>
newline >>
(return $ Today t) ))
reportBy :: GenParser Char Gantt ConfigLine
reportBy =
(try (string "period:") <|> try (string "report:")) >>
spaces >>
reportPeriod >>= (\p ->
getState >>= (\cfg ->
(when ((outSize cfg) == def) $ setState cfg { outSize = p }) >>
(return $ Period p)))
reportPeriod :: GenParser Char Gantt Period
reportPeriod = try daily <|> try weekly <|> try monthly <|> try quarterly <|> try yearly
daily :: GenParser Char Gantt Period
daily = string "daily" >> return Daily
weekly :: GenParser Char Gantt Period
weekly = string "weekly" >> return Weekly
monthly :: GenParser Char Gantt Period
monthly = string "monthly" >> return Monthly
quarterly :: GenParser Char Gantt Period
quarterly = string "quarterly" >> return Quarterly
yearly :: GenParser Char Gantt Period
yearly = string "yearly" >> return Yearly
reportPeriodSize :: GenParser Char Gantt ConfigLine
reportPeriodSize =
string "size:" >>
spaces >>
periodsize >>= (\p ->
getState >>= (\cfg ->
(when ((inSize cfg) == def) $ setState cfg { inSize = p }) >>
(return $ Period p)))
periodsize :: GenParser Char Gantt Period
periodsize = try days <|> try weeks <|> try months <|> try quarters <|> try years
days :: GenParser Char Gantt Period
days = string "days" >> return Daily
weeks :: GenParser Char Gantt Period
weeks = string "weeks" >> return Weekly
months :: GenParser Char Gantt Period
months = string "months" >> return Monthly
quarters :: GenParser Char Gantt Period
quarters = string "quarters" >> return Quarterly
years :: GenParser Char Gantt Period
years = string "years" >> return Yearly
-- Chart entries -----------------------------------------
chart :: GenParser Char Gantt [ChartLine]
chart = many chartline
chartline :: GenParser Char Gantt ChartLine
chartline =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " chartline: " }) >>
(try group <|> try task <|> try milestone <|> try deliverable) >>= (\l ->
skipMany (try comment <|> blankline ) >> return l)
group :: GenParser Char Gantt ChartLine
group =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " group;" }) >>
string "G" >>
space >>
spaces >>
quotedString >>= (\nm ->
range >>= (\(st, end) ->
try (newline >> (return $ Group nm st end) ) <|> slippedGroup nm st end))
slippedGroup :: String -> Int -> Int -> GenParser Char Gantt ChartLine
slippedGroup nm st end = range >>= (\(st', end') -> return $ SlippedGroup nm st end st' end')
task :: GenParser Char Gantt ChartLine
task =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " task;" }) >>
string "T" >>
space >>
spaces >>
quotedString >>= (\nm ->
range >>= (\(st, end) ->
try (newline >> (return $ Task nm st end) ) <|> slippedTask nm st end))
slippedTask :: String -> Int -> Int -> GenParser Char Gantt ChartLine
slippedTask nm st end = range >>= (\(st', end') -> return $ SlippedTask nm st end st' end')
milestone :: GenParser Char Gantt ChartLine
milestone =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " milestone;" }) >>
string "M" >>
space >>
spaces >>
quotedString >>= (\nm ->
space >>
spaces >>
int >>= (\due ->
try (newline >> (return $ Milestone nm due) ) <|> slippedMilestone nm due))
slippedMilestone :: String -> Int -> GenParser Char Gantt ChartLine
slippedMilestone nm due = space >> spaces >> int >>= (\(due') -> return $ SlippedMilestone nm due due')
deliverable :: GenParser Char Gantt ChartLine
deliverable =
updateState (\cfg -> cfg {msg = (msg cfg) ++ " deliverable;" }) >>
string "D" >>
space >>
spaces >>
quotedString >>= (\nm ->
space >>
spaces >>
int >>= (\due ->
try (newline >> (return $ Deliverable nm due) ) <|> slippedDeliverable nm due))
slippedDeliverable :: String -> Int -> GenParser Char Gantt ChartLine
slippedDeliverable nm due = space >> spaces >> int >>= (\(due') -> return $ SlippedDeliverable nm due due')
range :: GenParser Char Gantt (Int, Int)
range =
space >> spaces >>
int >>= (\st ->
spaces >>
int >>= (\end ->
return (st, end) ))
quotedString :: GenParser Char Gantt String
quotedString =
char '"' >>
many quotedChar >>= (\content ->
(char '"' <?> "quote at end of cell") >>
return content)
quotedChar = noneOf "\"" <|> try (string "\"\"" >> return '"')
aString :: GenParser Char Gantt String
aString = spaces >> many (noneOf " \t\n\r")
blankline :: GenParser Char Gantt ()
blankline = try (manyTill (oneOf " \t") (newline) >> return ())
comment :: GenParser Char Gantt ()
comment = spaces >> char '#' >> manyTill anyChar newline >> return ()
-- | Needs @foldl'@ from Data.List and
-- @digitToInt@ from Data.Char.
-- from: http://stackoverflow.com/questions/10726085/how-do-i-get-parsec-to-let-me-call-read-int/10726784#10726784
--positiveNatural :: Stream s m Char => ParsecT s u m Int
int = many1 digit >>= (\s -> return $ foldl' (\a i -> a * 10 + digitToInt i) 0 s)