Skip to content

Instantly share code, notes, and snippets.

@wenLiangcan
Last active November 11, 2021 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wenLiangcan/78e1d4ca26aaed045bd469ad83147a3d to your computer and use it in GitHub Desktop.
Save wenLiangcan/78e1d4ca26aaed045bd469ad83147a3d to your computer and use it in GitHub Desktop.
todo.fsx
#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