(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 type (case lastHeader ("BONUS" Bonus) ("TODO" Todo) ("DAILY" (case (line.split ":") ([noColon] (Daily // all days of week (collect (range 7)) // never done before "")) ([::&mut preColon ...afterColon] (set line (afterColon.join ":")) (Daily // Days of week specified by abbreviation: (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) ])) // Last date completed after that: (ifLet [[days date] (preColon.split " ")] date ""))) (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 [(Daily days lastDayDone) 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")))) "") " " lastDayDone ": ") "")$(.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 todayString [] (let [d (Date.now)] "$(d.getDate)-$(+ 1 (d.getMonth))-$(d.getFullYear)")) (function isActive [:Entry e] (case e.type ((Daily days lastDayDone) (and !(= lastDayDone (todayString)) (contains days (.getDay (Date.now))))) (otherwise true)))