Last active
November 11, 2021 16:14
-
-
Save wenLiangcan/78e1d4ca26aaed045bd469ad83147a3d to your computer and use it in GitHub Desktop.
todo.fsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#r "nuget:Argu" | |
open System | |
open System.IO | |
open System.Text.RegularExpressions | |
open Argu | |
type TaskData = { | |
Note: string | |
} | |
type Task = | |
| DoneTask of TaskData | |
| TodoTask of TaskData | |
override x.ToString() = | |
match x with | |
| TodoTask {Note = note} -> $"- [ ] {note}" | |
| DoneTask {Note = note} -> $"- [x] {note}" | |
module Task = | |
let create note = TodoTask({Note = note}) | |
let check task = | |
match task with | |
| TodoTask(data) -> DoneTask(data) | |
| _ -> task | |
let undo task = | |
match task with | |
| DoneTask(data) -> TodoTask(data) | |
| _ -> task | |
let (|Parse|) str = | |
let matches = Regex.Match(str, @"^- \[([\sx])\] (.*)$") | |
if matches.Success then | |
match [for i in matches.Groups -> i.Value] with | |
| [_; " "; note] -> Some (create note) | |
| [_; "x"; note] -> Some (DoneTask {Note = note}) | |
| _ -> None | |
else | |
None | |
type TodoList = { | |
FilePath: string | |
Todos: Task list | |
} | |
let tryDo (acion: (unit -> 'r)) () = | |
try | |
Ok (acion()) | |
with | |
| ex -> Error ex | |
type StreamReader with | |
member x.ReadAllLines() = | |
[while not x.EndOfStream do x.ReadLine()] | |
module TodoList = | |
let load path = | |
let file = File.Open(path, FileMode.OpenOrCreate) | |
use reader = new StreamReader(file) | |
let todos = | |
reader.ReadAllLines() | |
|> List.map ((function | Task.Parse (Some t) -> t | _ -> failwith "failed to parse tasks")) | |
{FilePath = path; Todos = todos} | |
let private save todos = | |
let lines = | |
todos.Todos | |
|> List.map (fun t -> t.ToString()) | |
File.WriteAllLines(todos.FilePath, lines) | |
let private trySave todos = | |
tryDo (fun () -> | |
save todos | |
todos) | |
let add todos note = | |
let added = {todos with Todos = todos.Todos @ [Task.create note]} | |
trySave added | |
let private update todos index acion = | |
let idx = index - 1 | |
let updated = acion todos.Todos[idx] | |
let results = {todos with Todos = todos.Todos |> List.updateAt idx updated} | |
trySave results | |
let check todos index = | |
update todos index Task.check | |
let undo todos index = | |
update todos index Task.undo | |
let remove todos index = | |
let idx = index - 1 | |
let results = {todos with Todos = todos.Todos |> List.removeAt idx} | |
trySave results | |
let cleanup todos = | |
let results = | |
todos.Todos | |
|> List.filter (function | TodoTask(_) -> true | _ -> false) | |
|> fun ts -> {todos with Todos = ts} | |
trySave results | |
let clear todos = | |
let results = {todos with Todos = []} | |
trySave results | |
let private print = | |
function | |
| TodoTask {Note = note} -> | |
printf $"\u001b[31m✖\u001b[0m {note}" | |
| DoneTask {Note = note} -> | |
printf $"\u001b[32m✓\u001b[0m {note}" | |
let printAll todos = | |
todos.Todos | |
|> List.iteri (fun i t -> | |
printf $"{i + 1} " | |
print t | |
printfn "") | |
type Args = | |
| [<MainCommand; ExactlyOnce; First>] Note of note: string | |
| [<CliPrefix(CliPrefix.None)>] Ls | |
| [<CliPrefix(CliPrefix.None)>] Remove of index: int | |
| [<CliPrefix(CliPrefix.None)>] Check of index: int | |
| [<CliPrefix(CliPrefix.None)>] Undo of index: int | |
| [<CliPrefix(CliPrefix.None)>] Cleanup | |
| [<CliPrefix(CliPrefix.None)>] Clear | |
interface IArgParserTemplate with | |
member x.Usage = | |
match x with | |
| Note _ -> "Task to do" | |
| Ls -> "List unchecked tasks" | |
| Remove _ -> "Remove a task by index" | |
| Check _ -> "Check a task by index" | |
| Undo _ -> "Undo a task by index" | |
| Cleanup -> "Clear checked tasks" | |
| Clear -> "Clear all tasks" | |
let argParser = ArgumentParser.Create<Args>(programName = "todo") | |
let results = argParser.ParseCommandLine(inputs = fsi.CommandLineArgs[1..], ignoreMissing = true) | |
let home = Environment.SpecialFolder.Personal |> Environment.GetFolderPath | |
let todos = TodoList.load (Path.Combine(home, "todo.txt")) | |
match results.GetAllResults() with | |
| Ls :: _ -> TodoList.printAll todos | |
| cmd :: _ -> | |
let action = | |
match cmd with | |
| Remove i -> TodoList.remove todos i | |
| Check i -> TodoList.check todos i | |
| Undo i -> TodoList.undo todos i | |
| Cleanup -> TodoList.cleanup todos | |
| Clear -> TodoList.clear todos | |
| Note note -> TodoList.add todos note | |
| _ -> fun _ -> Error (Failure "Unkown command") | |
match action () with | |
| Ok tds -> TodoList.printAll tds | |
| Error e -> printfn "%A" e | |
| _ -> printf "%s" (argParser.PrintUsage()) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment