+5
CHANGELOG.md
+5
CHANGELOG.md
+1
gleam.toml
+1
gleam.toml
+2
manifest.toml
+2
manifest.toml
···
3
3
4
4
packages = [
5
5
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
6
+
{ name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" },
6
7
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
7
8
{ name = "glance", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "8F3314D27773B7C3B9FB58D8C02C634290422CE531988C0394FA0DF8676B964D" },
8
9
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
···
21
22
22
23
[requirements]
23
24
argv = { version = ">= 1.0.2 and < 2.0.0" }
25
+
edit_distance = { version = ">= 2.0.1 and < 3.0.0" }
24
26
filepath = { version = ">= 1.0.0 and < 2.0.0" }
25
27
glance = { version = ">= 0.11.0 and < 1.0.0" }
26
28
gleam_community_ansi = { version = ">= 1.4.0 and < 2.0.0" }
+79
-7
src/birdie.gleam
+79
-7
src/birdie.gleam
···
2
2
import birdie/internal/diff.{type DiffLine, DiffLine}
3
3
import birdie/internal/project
4
4
import birdie/internal/titles
5
+
import edit_distance/levenshtein
5
6
import filepath
6
7
import gleam/bool
7
8
import gleam/erlang
···
730
731
731
732
// --- CLI COMMAND -------------------------------------------------------------
732
733
734
+
type Command {
735
+
Review
736
+
AcceptAll
737
+
RejectAll
738
+
Help
739
+
}
740
+
741
+
fn command_to_string(command: Command) -> String {
742
+
case command {
743
+
Review -> "review"
744
+
AcceptAll -> "accept-all"
745
+
RejectAll -> "reject-all"
746
+
Help -> "help"
747
+
}
748
+
}
749
+
733
750
/// Reviews the snapshots in the project's folder.
734
751
/// This function will behave differently depending on the command line
735
752
/// arguments provided to the program.
···
744
761
/// > and checked with the vcs you're using.
745
762
///
746
763
pub fn main() -> Nil {
747
-
case argv.load().arguments {
748
-
[] | ["review"] -> report_status(review())
749
-
["accept-all"] | ["accept", "all"] -> report_status(accept_all())
750
-
["reject-all"] | ["reject", "all"] -> report_status(reject_all())
751
-
["help"] -> help()
752
-
[subcommand] -> unexpected_subcommand(subcommand)
753
-
subcommands -> more_than_one_command(subcommands)
764
+
let args = argv.load().arguments
765
+
case parse_command(args) {
766
+
Ok(command) -> run_command(command)
767
+
Error(_) ->
768
+
case args {
769
+
[subcommand] ->
770
+
case closest_command(subcommand) {
771
+
Ok(command) -> suggest_run_command(subcommand, command)
772
+
Error(Nil) -> unexpected_subcommand(subcommand)
773
+
}
774
+
subcommands -> more_than_one_command(subcommands)
775
+
}
776
+
}
777
+
}
778
+
779
+
fn parse_command(arguments: List(String)) {
780
+
case arguments {
781
+
[] | ["review"] -> Ok(Review)
782
+
["accept-all"] | ["accept", "all"] -> Ok(AcceptAll)
783
+
["reject-all"] | ["reject", "all"] -> Ok(RejectAll)
784
+
["help"] -> Ok(Help)
785
+
_ -> Error(Nil)
786
+
}
787
+
}
788
+
789
+
fn run_command(command: Command) -> Nil {
790
+
case command {
791
+
Review -> report_status(review())
792
+
AcceptAll -> report_status(accept_all())
793
+
RejectAll -> report_status(reject_all())
794
+
Help -> help()
754
795
}
796
+
}
797
+
798
+
fn suggest_run_command(invalid: String, command: Command) -> Nil {
799
+
let error_message =
800
+
ansi.bold("Error: ") <> "\"" <> invalid <> "\" isn't a valid subcommand."
801
+
802
+
io.println(ansi.red(error_message))
803
+
let msg =
804
+
"I think you misspelled `"
805
+
<> command_to_string(command)
806
+
<> "`, would you like me to run it instead? [Y/n] "
807
+
808
+
case erlang.get_line(msg) {
809
+
Error(_) -> Nil
810
+
Ok(line) ->
811
+
case string.lowercase(line) |> string.trim {
812
+
"yes" | "y" | "" -> run_command(command)
813
+
_ -> io.println("\n" <> help_text())
814
+
}
815
+
}
816
+
}
817
+
818
+
fn closest_command(to string: String) -> Result(Command, Nil) {
819
+
let distance = fn(c) { command_to_string(c) |> levenshtein.distance(string) }
820
+
821
+
[Review, AcceptAll, RejectAll, Help]
822
+
|> list.map(fn(command) { #(command, distance(command)) })
823
+
|> list.filter(keeping: fn(command) { command.1 <= 3 })
824
+
|> list.sort(fn(one, other) { int.compare(one.1, other.1) })
825
+
|> list.first
826
+
|> result.map(fn(pair) { pair.0 })
755
827
}
756
828
757
829
fn review() -> Result(Nil, Error) {
+3
-3
test/titles_test.gleam
+3
-3
test/titles_test.gleam
···
81
81
titles
82
82
}
83
83
84
-
fn pretty_titles(titles: titles.Titles) -> String {
84
+
fn pretty_titles(ts: titles.Titles) -> String {
85
85
let pretty = fn(title, info) {
86
86
let titles.TestInfo(file: file, test_name: test_name) = info
87
87
let title = string.pad_right(title, to: 40, with: " ")
···
90
90
}
91
91
92
92
let literals =
93
-
dict.to_list(titles.literals(titles))
93
+
dict.to_list(titles.literals(ts))
94
94
|> list.map(fn(pair) { pretty(pair.0, pair.1) })
95
95
|> string.join(with: "\n")
96
96
97
97
let prefixes =
98
-
dict.to_list(titles.prefixes(titles))
98
+
dict.to_list(titles.prefixes(ts))
99
99
|> list.map(fn(pair) { pretty(pair.0, pair.1) })
100
100
|> string.join(with: "\n")
101
101