watercooler-labs / toggl-cli

🕐 Command Line App for Toggl Track
MIT License
31 stars 7 forks source link

Track folders with templates #45

Closed shantanuraj closed 1 year ago

shantanuraj commented 2 years ago

toggl: Folder tracking proposal

Progress

Goal

Introduce the ability to track arbitary folder paths.

toggl . # current path
toggl ~/dev/some_org/some_repo

The command would then lookup it's index of configs[^1] for given path.

On successfully parsing the config the result would be effectively the same as executing[^2]

toggl start \
    --workspace $(config.workspace) \
    --description $(config.description) \
    --project $(config.project) \
    --task $(config.task) \
    --tags $(config.tags) \
    --billable $(config.billable)

Configuration

Schema

A standard default configuration could have these options.

['*']
workspace = "{{default}}"
description = "🔨 {{branch}}"
project = "{{base_dir}}"
task = "Some task"
tags = []
billable = true

With comments

# Branch (required)
# Regex to choose which branch(-es) this block applies to, some examples
# ['will/.*'] applies to branches like`will/release-workflows`
# ['\d+/.*'] applies to branches starting with numbers `123/feat-some-issue`
# [main] applies only to the branch called `main`
# ['*'] is the reserved default that applies if no block of higher precedence is present.
# ['*'] It also applies if current folder is not tracked under source control
['*']

# Workspace (optional, default=null)
# in this context would resolve to the user's default workspace
# https://support.toggl.com/en/articles/2452474-introduction-to-workspaces#switching-and-creating-workspaces
# workspace = "My workspace"

# Description (optional, default=null)
# Accepts any string template with our marcros in them
description = "🔨 {{branch}}"

# Project (optional, default=null)
project = "{{base_dir}}"

# Task (optional, default=null)
task = "Some task"

# Tags (optional, default=[])
tags = ["Foo", "Bar"]

# Billable (optional, default=false)
billable = true

Storage

Configuration can be defined either globally or on a per folder basis.

toggl config init # Set configuration for current folder path and descendants
toggl config init --global # Set global configuration is the same as invoking (cd /; toggl config init);

Configuration files are stored in the $TOGGL_DIR folder. Possible locations on different platforms are

linux windows macOS
$XDG_CONFIG_HOME/toggl-cli or $HOME/.config/toggl-cli {FOLDERID_RoamingAppData}/labs/toggl-cli/config $HOME/Library/Application Support/studio.watercooler.labs.toggl-cli

We store store configs outside directory to avoid adding noise to source control.

Initialization

toggl config init

Lookup

Parsing

struct StartCommandOptions {
  workspace_id: Option<u64>,
  project_id: Option<u64>,
  task_id: Option<u64>,
  description: String,
  tags: Vec<String>,
  billable: bool,
}

Supported macros

macro value
{{branch}} Current branch name from source control
{{base_dir}} Directory name from path registered when invoking toggl config init
{{current_dir}} Current directory name
{{parent_base_dir}} Parent directory name of {{base_dir}}
{{parent_dir}} Parent directory name of {{current_dir}}
{{git_root}} Current git root, flexible alternative to {{base_dir}}
{{parent_git_root}} Parent of git root, flexible alternative to {{parent_dir}}
{{$[shell_expression]}} Executes shell_expression and uses its result

Sample scenarios

Inbuilt macros only

Tracked Directory /Users/shantanu/dev/watercooler-labs/toggl-cli

Branch add-folder-tracking

API State

{
  "default_workspace": 120,
  "workspaces": {
    "120": {
      "name": "watercooler-labs",
      "projects": {
        "410": {
          "name": "toggl-cli",
          "tasks": {
            "555": {
              "name": "review"
            }
          }
        }
      }
    }
  }
}

Configuration

['*']
description = "🔨 {{branch}}"
project = "{{base_dir}}"
task = "Toggl CLI"
tags = ["code"]
billable = true

Output

StartCommandOptions {
  workspace_id: None,
  project_id: Some(410),
  task_id: Some(556),
  description: String::from("🔨 add-folder-tracking"),
  tags: vec![String::from("code")],
  billable: true,
}

A new task is created since the API does not have a task with the name Toggl CLI.

With Custom macros

Tracked Directory /Users/shantanu/dev/watercooler-labs/UnityGame

Current directory /Users/shantanu/dev/watercooler-labs/UnityGame/android

Branch Not tracked under source control

API State

{
  "default_workspace": 120,
  "workspaces": {
    "120": {
      "name": "watercooler-labs",
      "projects": {
        "320": {
          "name": "UnityGame",
          "tasks": {
            "600": {
              "name": "iOS"
            },
            "601": {
              "name": "Android"
            }
          }
        }
      }
    }
  }
}

Configuration

['*']
workspace = "{{parent_base_dir}}"
description = "Side hustle 🎮"
project = "{{base_dir}}"
task = """
{{$if [[ "$PWD" == *"android"* ]]; then echo "Android"; else echo "iOS"; fi}}
"""

Output

StartCommandOptions {
  workspace_id: None,
  project_id: Some(320),
  task_id: Some(601),
  description: String::from("Side hustle 🎮"),
  billable: false,
}

Questions

[^1]: See config proposal. [^2]: At the time of writing this issue, we do not support configuring workspace, project, task or tags, this is a prequisite for supporting all features of the proposed configuration schema.

heytherewill commented 2 years ago

Love this proposal! Like we spoke in the call, I have no objections to it. You summarized everything correctly and we can go on about implementing it as it is.

Do you think there are better solutions regarding storing the config? Sqlite or some other markup language?

I like the idea of config being just files that the user can mess around. This allows them to add these to whatever source control they use and set them up again in a different computer. SQL databases are clumsier in this sense.

Regarding the format, I think toml has the perfect signal to noise ratio and we should stick to it!

Instead of actual regex should we just support a wildcard subset ["will/."] becomes ["will/"].

I think regex is simpler (what a sentence, eh) because we then don't need to implement our own parser and can instead rely on whatever library we use for it.