Commits: 4

Fix indentation in the spec

Looks like TBB is really tripped off by misaligned backticks. It doesn't produce a readable error, sometimes fails to recognize steps beyond the borked region. This is really bad, because it may lead to positive evaluation result, while in reality some steps (or even whole scenarios) are not evaluated.

index 9a1451b..e61e1af 100644
--- a/spec/BASIC.md
+++ b/spec/BASIC.md
@@ -71,11 +71,11 @@ IT should eventually be possible to evaluate this spec using TBB. The difficulty
=    # v1.0.0
=    
=    * Now it's working
-   ```
+    ```
=
=  * On `2026-03-28 03:06` commit as `Alice <alice@example.com>`
=  
-      Notice that it's logically the same day - any work done until 04:00 AM is considered to belong to the previous day.
+    Notice that it's logically the same day - any work done until 04:00 AM is considered to belong to the previous day.
=      
=    ``` text
=    Make Project Alpha work
@@ -85,9 +85,9 @@ IT should eventually be possible to evaluate this spec using TBB. The difficulty
=
=  * Write `LICENSE`
=  
-  ``` text
-  All rights reserved! You monkeys better not even look at my pro codes.
-  ```
+    ``` text
+    All rights reserved! You monkeys better not even look at my pro codes.
+    ```
=  
=  
=  * On `2026-04-01 09:03` commit as `Bob <bob@example.com>`

Implement the listing spec

Also modify it in some respects. I decided not to have a sub-command for listing, but instead use the main command with --dry-run flag for the purpose of listing.

Some parts of devlog code are dead now and will most likely be removed. The logic around getting the log is completely overhauled.

index d27f57c..6677495 100755
--- a/devlog.nu
+++ b/devlog.nu
@@ -1,5 +1,100 @@
=#!/usr/bin/env nu
=
+use std/log
+
+def main [
+    --since: datetime = 1970-01-01          # How far back should we look?
+    --until: datetime                       # When did you last committed anything good?
+    --projects-dir: path = "~/Projects",    # Where do you keep your projects?
+    --day-start: string = "04:00"           # Until how late can you code?
+    --out-dir: path = "drafts"              # Where to write excavated posts
+    --config-file: path = "devlog.toml"     # The configuration file
+    --dry-run                               # Just list the commits to excavate
+] {
+    # Read the config file, process and set some defaults
+    # TODO: DRY with `main list`
+    open $config_file
+    | update projects { update path { path expand } } # Resolve all project paths
+    | upsert frontmatter.projects_cell_path { default { [ extra projects ] } | into cell-path }
+    | let config
+
+    $until | default (date now) | let until
+    
+    $projects_dir
+    | projects
+    | each { project log $since $until }
+    | flatten
+    | update time { |commit| $commit.time | into datetime }
+    | insert day { 
+        get time
+        | if ($in | format date %T) < $day_start {
+           $in - 1day
+        } else {
+          $in
+        }
+        | format date %F
+    }
+    | insert project { |commit|
+        $config.projects
+        | where path == $commit.path
+        | first
+        | get name
+    }
+    | move day project author email subject time sha path --first
+    | let commits
+
+    if $dry_run {
+        $commits
+        | update time { format date }
+        | print 
+        exit 0
+    }
+    
+    # TODO: Allow setting in a config file
+    mkdir $out_dir
+
+    # for project_path in ($projects_dir | projects) {
+    #     let project = 
+    #     if ($project | is-empty) {
+    #         continue
+    #     }
+
+    #     let project_slug = $project.name | str kebab-case
+
+    #     if ($project | get --optional ignore | default false ) {
+    #         continue
+    #     }
+
+    #     # TODO: Iterate over days and write to a file
+    #     for day in (main days $since) {
+    #         let date = $day.start | format date %Y-%m-%d
+    #         let out_path = ($out_dir | path join $"($date)-($project_slug).md")
+
+    #         try {
+    #             # TODO: Catch and print errors
+    #             $project_path
+    #             | project log $day.start $day.end
+    #             | reverse
+    #             | let log
+
+    #             if ($log | is-empty) {
+    #                 "No commits on that day"
+    #                 continue
+    #             }
+
+    #             $log
+    #             | format log $project.name $config
+    #             | save --force $out_path
+    #         } catch { |error|
+    #             print --stderr $"Project path: ($project_path)"
+    #             print --stderr $"Date: ($date)"
+    #             print --stderr $"Output path: ($out_path)"
+    #             print --stderr $error.rendered
+    #         }
+    #     }
+        
+    # }
+}
=def "main days" [
=    since: datetime,
=    --day-start: string = "04:00"
@@ -17,38 +112,54 @@ def "main days" [
=    | take while { |day| $day.start < $day.end }
=}
=
-def "main projects" [
-    path: path
-]: nothing -> list<path> {
-    $path
+def "projects" []: path -> list<path> {
+    $in
=    | path join "**/.git"
=    | glob $in
=    | path dirname
=}
=
-def "main project log" [
-    path: path,
+def "project log" [
=    since: datetime,
=    until: datetime,
-] {
-    let log_format = "<--- commit --->%n%s%n<--- part --->%n%b%n<--- part --->%n"
-    cd $path
-
-    # Debugging:
-    # print $env.PWD
-    # let git_command = $'git log --since ($since | format date %+) --until ($until | format date %+) --unified --format="($log_format)"'
-    # print $git_command
-
-    # TODO: Handle binary stream somehow coming from Erna's log
-    # This is a workaround for `skip` swallowing errors
-    let chunks = git log --since=($since | format date %+) --until=($until | format date %+) --patch --format=($log_format)
-    | split row "\n<--- commit --->\n"
-
-    $chunks
-    | skip 1 # First is empty
-    | split column "\n<--- part --->\n"
-    | rename "subject" "body" "diff" 
-    | update "diff" { | row | $row.diff | split row --regex "\ndiff --git .+" | skip 1 }
+]: path -> list {
+    let project_path = $in
+
+    cd $project_path
+
+    let format = {
+      "%s": "subject",
+      "%ad": "time",
+      "%an": "author",
+      "%ae": "email",
+      "%H": "sha",
+    }
+
+    $format
+    | columns
+    | each { $"⸨($in)⸩" } # Wrap in a double quote
+    | str join " "
+    | let log_format
+
+    
+    $format
+    | values
+    | each { $"⸨{($in)}⸩" } # Wrap in a double quote and a curly brace
+    | str join " "
+    | let parse_format
+
+    log debug $"Log format ($log_format)"
+    log debug $"Parse format ($parse_format)"
+
+    $log_format
+    | $"git log --date=iso-strict --since=($since | format date %+) --until=($until | format date %+) --format=($in)"
+    | log info $in
+
+    $log_format
+    | git log --date=iso-strict --since=($since | format date %+) --until=($until | format date %+) --format=($in)
+    | parse $parse_format
+    | reverse
+    | insert path $project_path
=}
=
=def "format commit" [] {
@@ -84,61 +195,3 @@ def "format log" [
=        ($in | each { | commit | format commit } | str join "\n\n")
=    ] | str join "\n"
=}
-
-def main [
-    since: datetime,
-    --projects-dir: path = "~/Projects",
-    --day-start: string = "04:00"
-    --out-dir: path = "drafts"
-    --config-file: path = "devlog.toml"
-] {
-    # Read the config file, process and set some defaults
-    let config = open $config_file
-    | update projects { update path { path expand } } # Resolve all project paths
-    | upsert frontmatter.projects_cell_path { default { [ extra projects ] } | into cell-path } 
-
-    # TODO: Allow setting in a config file
-    mkdir $out_dir
-
-    for project_path in (main projects $projects_dir) {
-        let project = $config.projects
-        | where path == $project_path
-        | first
-
-        if ($project | is-empty) {
-            continue
-        }
-
-        let project_slug = $project.name | str kebab-case
-
-        if ($project | get --optional ignore | default false ) {
-            continue
-        }
-
-        # TODO: Iterate over days and write to a file
-        for day in (main days $since) {
-            let date = $day.start | format date %Y-%m-%d
-            let out_path = ($out_dir | path join $"($date)-($project_slug).md")
-
-            try {
-                # TODO: Catch and print errors
-                let log = main project log $project_path $day.start $day.end | reverse
-
-                if ($log | is-empty) {
-                    "No commits on that day"
-                    continue
-                }
-
-                $log
-                | format log $project.name $config
-                | save --force $out_path
-            } catch { |error|
-                print --stderr $"Project path: ($project_path)"
-                print --stderr $"Date: ($date)"
-                print --stderr $"Output path: ($out_path)"
-                print --stderr $error.rendered
-            }
-        }
-        
-    }
-}
index e61e1af..cd8528d 100644
--- a/spec/BASIC.md
+++ b/spec/BASIC.md
@@ -7,15 +7,15 @@ interpreter: nu --stdin spec/interpreters/basic.nu
=This program excavates a devlog (i.e. a bunch of .md files) from commit messages in multiple repositories.
=
=
-## The `list` Subcommand
+## Listing Commits to Excavate
=
=``` yaml tbb
=tags: [ focus ]
=```
=
-IT should eventually be possible to evaluate this spec using TBB. The difficulty is, that any sample data to work on needs to be a git repository, while this project itself is version controlled using git. One way would be to have TBB make commits with fabricated dates, using `git commit --date`.
+Before you excavate, it might be smart to list the commits. You can use `--dry-run` flag for that. Consider the following scenario.
=
-  * Write `weblog.toml`
+  * Write `devlog.toml`
=    
=    ``` toml
=    [[projects]]
@@ -80,7 +80,7 @@ IT should eventually be possible to evaluate this spec using TBB. The difficulty
=    ``` text
=    Make Project Alpha work
=    
-    Coding hard. I should have used Tad Better Behavior to test my stuff.
+    Coding is hard for Bob. He should have used Tad Better Behavior to test his stuff before pushing it to prod!
=    ```
=
=  * Write `LICENSE`
@@ -109,8 +109,6 @@ IT should eventually be possible to evaluate this spec using TBB. The difficulty
=    ```
=
=  * On `2026-03-28 14:18` commit as `Alice <alice@example.com>`
-  
-
=      
=    ``` text
=    I quit
@@ -118,39 +116,44 @@ IT should eventually be possible to evaluate this spec using TBB. The difficulty
=    Bob is an asshole.
=    ```
=
+TODO: Setup a second project.
+
=  * Change directory to `..`
=  
=    Leave the repository and go back to where the config file is.
=
-  * Run the `devlog.nu list --projects-dir .` command
+  * Run the `devlog.nu --projects-dir . --dry-run` command
=  
=    TODO: Make it `devlog list`, without the extension,
=  
-  * Expect the output to contain `the heading row`
+  * Expect the output to contain `the header row`
=
=    It contains column names, as produced by Nushell. The output format should be configurable.
=    
=    ``` regexp
-    │ day +│ time +│ sha +│ author +│ email +│ title +│ project +│ path +│
+    │ +# +│ ++day +│ +project +│ +author +│ +email +│ +subject +│ +time +│ +sha +│ +path +│
=    ```
+
=  * Expect the output to contain `the first commit from Alice`
-  
+
=    ``` regex
-    │ 2026-04-26 +│ 2026-04-27 13:22:00 +│ [0-9a-f]+ +│ Alice +│ alice@example.com +│ Make Project Alpha work +│ Project Alpha +│ .+/project-alpha/ +│
+    │ +0 +│ +2026-03-27 +│ +Project Alpha +│ +Alice +│ +alice@example.com +│ +Write a readme +│ +Fri, 27 Mar 2026 13:22:00 \+\d{4} │ +[0-9a-f]+ +│ +.+/project-alpha +│
=    ```
=
+      > NOTE: To make the spec portable, the timezone, sha and part of the path has to be expressed as wildcard.
+
=  * Expect the output to contain `the late night commit from Alice`
=  
-    Notice that the logical day is one before the actual day (in the second column), because work done at night is still counted toward the previous day. This can be controlled with `start-of-day` configuration parameter.
+    Notice that the logical day (in the second column) is one before the actual day (in the seventh column). That's because work done at night is still counted toward the previous day. This can be controlled with `start-of-day` configuration parameter.
=    
=    ``` regex
-    │ 2026-03-27 │ 2026-03-28 03:06 +│ [0-9a-f]+ +│ Alice +│ alice@example.com +│ Write a readme +│ Project Alpha +│ .+/project-alpha/ +│
+    │ +2 +│ +2026-03-27 +│ +Project Alpha +│ +Alice +│ +alice@example.com +│ +Make Project Alpha work +│ +Sat, 28 Mar 2026 03:06:00 \+\d{4} │ +[0-9a-f]+ +│ +.+/project-alpha +│
=    ```
=
=  * Expect the output to contain `the silly commit from Bob`
=  
=    ``` regex
-    │ 2026-04-01 │ 2026-04-01 09:03 +│ [0-9a-f]+ +│ Bob +│ bob@example.com +│ I've lawyered up +│ Project Alpha +│ .+/project-alpha/ +│
+    │ +3 +│ +2026-04-01 +│ +Project Alpha +│ +Bob +│ +bob@example.com +│ +I've lawyered up +│ +Wed, 1 Apr 2026 09:03:00 \+\d{4} +│ +[0-9a-f]+ +│ +.+/project-alpha +│
=    ```
=
=## The `excavate` Subcommand

Write some thoughts about limiting time span and grouping

index cd8528d..c0ffaca 100644
--- a/spec/BASIC.md
+++ b/spec/BASIC.md
@@ -156,6 +156,16 @@ TODO: Setup a second project.
=    │ +3 +│ +2026-04-01 +│ +Project Alpha +│ +Bob +│ +bob@example.com +│ +I've lawyered up +│ +Wed, 1 Apr 2026 09:03:00 \+\d{4} +│ +[0-9a-f]+ +│ +.+/project-alpha +│
=    ```
=
+## Grouped view
+
+When using `--dry-run` it might be useful to see the commits grouped by project and day. Maybe there should be another flag? Or value?
+
+
+## Limiting the time span
+
+With `--since` and `--until` flags you can select a span of time from which commits are excavated.
+
+
=## The `excavate` Subcommand
=
=For each day run `git log` in each project. If there is any output, dump it to `devlog/<date>/<project-name>.md`. So each file should contain a log from a single project that was developed on a given day.

Export the main function, let it return data

Now it's possible to do something like:

❯ use devlog.nu ❯ devlog --projects-dir . --dry-run | group-by project day --to-table

Helpful in development, but also can be useful in advanced use cases.

index 6677495..f276b89 100755
--- a/devlog.nu
+++ b/devlog.nu
@@ -2,7 +2,7 @@
=
=use std/log
=
-def main [
+export def main [
=    --since: datetime = 1970-01-01          # How far back should we look?
=    --until: datetime                       # When did you last committed anything good?
=    --projects-dir: path = "~/Projects",    # Where do you keep your projects?
@@ -46,8 +46,7 @@ def main [
=    if $dry_run {
=        $commits
=        | update time { format date }
-        | print 
-        exit 0
+        | return $in
=    }
=    
=    # TODO: Allow setting in a config file
@@ -95,6 +94,7 @@ def main [
=        
=    # }
=}
+
=def "main days" [
=    since: datetime,
=    --day-start: string = "04:00"