(prop :Array dailyEntries []) (prop :Array bonusEntries []) (prop :Array todoEntries []) (prop :Array rewardFiles []) (defNew [&prop :String textFile] (let [s (Stream.fromFile textFile) &mut lastHeader ""] // TODO could be whileLet (loop (case (s.takeLine) ((Some "DAILY") (set lastHeader "DAILY")) ((Some "BONUS") (set lastHeader "BONUS")) ((Some "TODO") (set lastHeader "TODO")) ((Some "FILES") (set lastHeader "FILES")) ((when (apply = (concat ["-"] (line.split ""))) (Some line)) (continue)) ((Some "") (continue)) // Types won't unify with the next case, so this is its own: ((when (= lastHeader "FILES") (Some line)) (rewardFiles.push (let [parts (line.split " ") startingPoints (Std.parseInt (parts.pop)) path (parts.join " ")] (objectWith path startingPoints)))) ((Some line) (.push (case lastHeader ("DAILY" dailyEntries) ("BONUS" bonusEntries) ("TODO" todoEntries) (otherwise (throw "bad header"))) (object doneToday false type (case lastHeader ("BONUS" Bonus) ("TODO" Todo) ("DAILY" (Daily (case (line.split ":") ([noColon] (collect (range 7))) ([::&mut preColon ...afterColon] (set line (afterColon.join ":")) (sort (filter [ // disambiguate Th from T and Su from S: (when (contains preColon "Th") {(set preColon (StringTools.replace preColon "Th" "")) 4}) (when (contains preColon "Su") {(set preColon (StringTools.replace preColon "Su" "")) 0}) (when (contains preColon "M") 1) (when (contains preColon "T") 2) (when (contains preColon "W") 3) (when (contains preColon "F") 5) (when (contains preColon "S") 6) ]))) (otherwise (throw "bad line"))))) (otherwise (throw "bad header"))) labels (for l (line.split "/") (object label (StringTools.trim (StringTools.replace l "|" "")) points (count (l.split "") ->c (= c "|"))))))) (otherwise (break)))))) (method :Int totalPoints [] (apply + (for l (flatten (for e (the Array (concat dailyEntries bonusEntries todoEntries)) e.labels)) l.points))) (function :String stringify [:Entry e] "$(ifLet [(when !(= days.length 7) (Daily days)) e.type] (+ (.join (for day days (case day (0 "Su") (1 "M") (2 "T") (3 "W") (4 "Th") (5 "F") (6 "S") (otherwise (throw "bad day")))) "") ": ") "")$(.join (for label e.labels "${label.label} $(* "|" label.points)") "/")") (function :String stringifyRewardFile [:RewardFile rewardFile] "${rewardFile.path} ${rewardFile.startingPoints}") (method :Void save [] (localVar &mut content "DAILY\n-----\n") (+= content (.join (map dailyEntries stringify) "\n") "\n") (+= content "\nBONUS\n-----\n") (+= content (.join (map bonusEntries stringify) "\n") "\n") (+= content "\nTODO\n----\n") (+= content (.join (map todoEntries stringify) "\n") "\n") (+= content "\nFILES\n-----\n") (+= content (.join (map rewardFiles stringifyRewardFile) "\n") "\n") (File.saveContent textFile content)) // With rotating entries, the active one is the first one with the lowest score: (function :EntryLabel activeLabel [:Entry e] (let [lowScore (apply min (for label e.labels label.points))] (doFor label e.labels (when (= lowScore label.points) (return label))) (throw "no active?!"))) (function isActive [:Entry e] (case e.type ((Daily days) (and !e.doneToday (contains days (.getDay (Date.now))))) (otherwise true)))