+37
-27
src/birdie.gleam
+37
-27
src/birdie.gleam
···
64
64
65
65
/// Performs a snapshot test with the given title, saving the content to a new
66
66
/// snapshot file. All your snapshots will be stored in a folder called
67
-
/// `birdie_snapshots` in the project's root.
68
-
///
67
+
/// `birdie_snapshots` in the project's root.
68
+
///
69
69
/// The test will fail if there already is an accepted snapshot with the same
70
70
/// title and a different content.
71
71
/// The test will also fail if there's no accepted snapshot with the same title
72
72
/// to make sure you will review new snapshots as well.
73
-
///
73
+
///
74
74
/// > 🚨 A snapshot is saved to a file named after its title, so all titles
75
75
/// > should be unique! Otherwise you'd end up comparing unrelated snapshots.
76
-
///
76
+
///
77
77
/// > 🐦⬛ To review all your snapshots interactively you can run
78
78
/// > `gleam run -m birdie`.
79
-
/// >
79
+
/// >
80
80
/// > To get an help text and all the available options you can run
81
81
/// > `gleam run -m birdie help`.
82
-
///
82
+
///
83
83
pub fn snap(content content: String, title title: String) -> Nil {
84
84
case do_snap(content, title) {
85
85
Ok(Same) -> Nil
···
209
209
// --- FILE SYSTEM OPERATIONS --------------------------------------------------
210
210
211
211
/// Save a new snapshot to a given path.
212
-
///
212
+
///
213
213
fn save(snapshot: Snapshot(New), to destination: String) -> Result(Nil, Error) {
214
214
// Just to make sure I'm not messing up something anywhere else in the code
215
215
// base: a new snapshot's destination MUST always end with a `.new` extension.
···
229
229
}
230
230
231
231
/// Read an accepted snapshot which might be missing.
232
-
///
232
+
///
233
233
fn read_accepted(source: String) -> Result(Option(Snapshot(Accepted)), Error) {
234
234
case simplifile.read(source) {
235
235
Ok(content) ->
···
245
245
}
246
246
247
247
/// Read a new snapshot.
248
-
///
248
+
///
249
249
/// > ℹ️ Notice the different return type compared to `read_accepted`: when we
250
250
/// > try to read a new snapshot we are sure it's there (because we've listed
251
251
/// > the directory or something else) so if it's not present that's an error
252
252
/// > and we don't return an `Ok(None)`.
253
-
///
253
+
///
254
254
fn read_new(source: String) -> Result(Snapshot(New), Error) {
255
255
case simplifile.read(source) {
256
256
Ok(content) ->
···
262
262
263
263
/// List all the new snapshots in a folder. Every file is automatically
264
264
/// prepended with the folder so you get the full path of each file.
265
-
///
265
+
///
266
266
fn list_new_snapshots(in folder: String) -> Result(List(String), Error) {
267
267
case simplifile.read_directory(folder) {
268
268
Error(reason) -> Error(CannotReadSnapshots(reason: reason, folder: folder))
···
281
281
282
282
/// Finds the snapshots folder at the root of the project the command is run
283
283
/// into. If it's not present the folder is created automatically.
284
-
///
284
+
///
285
285
fn find_snapshots_folder() -> Result(String, Error) {
286
286
let result = result.map_error(find_project_root("."), CannotFindProjectRoot)
287
287
use project_root <- result.try(result)
···
294
294
}
295
295
296
296
/// Returns the path to the project's root.
297
-
///
297
+
///
298
298
/// > ⚠️ This assumes that this is only ever run inside a Gleam's project and
299
299
/// > sooner or later it will reach a `gleam.toml` file.
300
300
/// > Otherwise this will end up in an infinite loop, I think.
301
-
///
301
+
///
302
302
fn find_project_root(path: String) -> Result(String, simplifile.FileError) {
303
303
let manifest = filepath.join(path, "gleam.toml")
304
304
case simplifile.verify_is_file(manifest) {
···
323
323
324
324
/// Turns a snapshot's title into a file name stripping it of all dangerous
325
325
/// characters (or at least those I could think ok 😁).
326
-
///
326
+
///
327
327
fn file_name(title: String) -> String {
328
328
string.replace(each: "/", with: " ", in: title)
329
329
|> string.replace(each: "\\", with: " ")
···
336
336
}
337
337
338
338
/// Returns the path where a new snapshot should be saved.
339
-
///
339
+
///
340
340
fn new_destination(snapshot: Snapshot(New), folder: String) -> String {
341
341
filepath.join(folder, file_name(snapshot.title)) <> ".new"
342
342
}
343
343
344
344
/// Strips the extension of a file (if it has one).
345
-
///
345
+
///
346
346
fn strip_extension(file: String) -> String {
347
347
case filepath.extension(file) {
348
348
Ok(extension) -> string.drop_right(file, string.length(extension) + 1)
···
352
352
353
353
/// Turns a new snapshot path into the path of the corresponding accepted
354
354
/// snapshot.
355
-
///
355
+
///
356
356
fn to_accepted_path(file: String) -> String {
357
357
// This just replaces the `.new` extension with the `.accepted` one.
358
358
strip_extension(file) <> ".accepted"
···
542
542
}
543
543
544
544
// This is an ugly hack that I need because `glam` currently doesn't take into
545
-
// account color codes.
545
+
// account color codes.
546
546
// Those are invisible but still contribute to the length of a string, so I
547
547
// have to artifically set the width to a higher limit to take into account
548
548
// the length of the color codes added to the lines' titles.
···
591
591
592
592
// --- CLI COMMAND -------------------------------------------------------------
593
593
594
-
@deprecated("🚨 This is the entry point of the CLI tool.
595
-
You should never call this function yourself, you should run `gleam run -m birdie` instead.
596
-
Expect this function to disappear from the public API on future releases!")
594
+
/// Reviews the snapshots in the project's folder.
595
+
/// This function will behave differently depending on the command line
596
+
/// arguments provided to the program.
597
+
/// To have a look at all the available options you can run
598
+
/// `gleam run -m birdie help`.
599
+
///
600
+
/// > 🐦⬛ The recommended workflow is to first run your gleeunit tests with
601
+
/// > `gleam test` and then review any new/failing snapshot manually running
602
+
/// > `gleam run -m birdie`.
603
+
/// >
604
+
/// > And don't forget to commit your snapshots! Those should be treated as code
605
+
/// > and checked with the vcs you're using.
606
+
///
597
607
pub fn main() -> Nil {
598
608
case argv.load().arguments {
599
609
[] | ["review"] -> report_status(review())
···
678
688
}
679
689
680
690
/// The choice the user can make when reviewing a snapshot.
681
-
///
691
+
///
682
692
type ReviewChoice {
683
693
AcceptSnapshot
684
694
RejectSnapshot
···
688
698
/// Asks the user to make a choice: it first prints a reminder of the options
689
699
/// and waits for the user to choose one.
690
700
/// Will prompt again if the choice is not amongst the possible options.
691
-
///
701
+
///
692
702
fn ask_choice() -> Result(ReviewChoice, Error) {
693
703
io.println(
694
704
ansi.bold(ansi.green(" a"))
···
792
802
// --- FFI ---------------------------------------------------------------------
793
803
794
804
/// Clear the screen.
795
-
///
805
+
///
796
806
@external(erlang, "birdie_ffi_erl", "clear")
797
807
fn clear() -> Nil
798
808
799
809
/// Move the cursor up a given number of lines.
800
-
///
810
+
///
801
811
@external(erlang, "birdie_ffi_erl", "cursor_up")
802
812
fn cursor_up(n: Int) -> Nil
803
813
804
814
/// Clear the line the cursor is currently on.
805
-
///
815
+
///
806
816
@external(erlang, "birdie_ffi_erl", "clear_line")
807
817
fn clear_line() -> Nil
808
818