RubyGems.org

A CLI for journaling to structured data, Markdown, and Day One

Description

The journal command reads a journal definition and provides command line prompts to fill it out. The results are stored in a JSON database for each journal, and can optionally output to Markdown (individual files per entry, daily digest, or one large file for the journal).

Installation

Use RubyGems to install journal:

$ gem install journal-cli

If you run into errors, try running with the --user-install flag:

$ gem install --user-install journal-cli

I've noticed lately with asdf that I have to run asdf reshim after installing gems containing binaries.

If Gum is installed, it will be used for prettier input prompts and editing. The easiest way is with Homebrew:

$ brew install gum

If you want to use Day One with Journal, you'll need to install the Day One CLI. It's just one command:

$ sudo bash /Applications/Day\ One.app/Contents/Resources/install_cli.sh

Configuration

A config must be created at ~/.config/journal/journals.yaml:

$ mkdir -p ~/.config/journal
$ touch ~/.config/journal/journals.yaml

A skeleton file will be written the first time Journal is run if the config file doesn't exist.

This file contains a YAML definition of your journal. Each journal gets a top-level key, which is what you'll specify it with on the command line. It gets a few settings, and then you define sections containing questions.

Weather

You can include weather data automatically by setting a question type to 'weather'. In order for this to work, you'll need to define zip and weather_api keys. zip is just your zip code, and weather_api is a key from WeatherAPI.com. Sign up here for a free plan, and then visit the profile page to see your API key at the top.

Zip codes beginning with zero (0) must be quoted. Use:

zip: '01001'

You can optionally set the key temp_in: to f or c to control what scale is used for temperatures.

If a question type is set to weather.forecast, the moon phase and predicted condition, high, and low will be included in the JSON data for the question. A full printout of hourly temps will be included in the Markdown/Day One output.

If the question type is weather.current, only the current condition and temperature will be recorded to the JSON, and a string containing "[TEMP] and [CONDITION]" (e.g. "64 and Sunny") will be recorded to Markdown/Day One for the question.

If the question type is weather.moon, only the moon phase will be output. Moon phase is also included in weather.forecast JSON and Markdown output.

Journal Configuration

Edit the file at ~/.config/journal/journals.yaml following this structure:

# Where to save all journal entries (unless this key is defined inside the journal). 
# The journal key will be appended to this to keep each journal separate
entries_folder: ~/.local/share/journal/ 
journals:
  daily: # journal key, will be used on the command line as `journal daily`
    dayone: true # Enable or disable Day One integration
    journal: Journal # Day One journal to add to (if using Day One integration)
    markdown: daily # Type of Markdown file to create, false to skip (can be daily, individual, or digest)
    title: Daily Journal # Title for every entry, date will be appended where needed
    sections: # Required key
      - title: null # The title for the section. If null, no section header will be created
        key: journal # The key for the data collected, must be one word, alphanumeric characters and _ only
        questions: # Required key
          - prompt: How are you feeling? # The question to ask
            key: journal # alphanumeric characters and _ only, will be nested in section key
            type: multiline # The type of entry expected (numeric, string, or multiline)

Keys must be alphanumeric characters and _ (underscore) only. Titles and questions can be anything, but if they contain a colon (:), you'll need to quote the string.

The entries_folder key can be set to save JSON and Markdown files to a custom, non-default location. The default is ~/.local/share/journal. This key can also be used within a journal definition to offer custom save locations on a per-journal basis.

A more complex configuration file can contain multiple journals with multiple questions defined:

zip: 55987 # Your zip code for weather integration
weather_api: XXXXXXXXXXXX # Your weatherapi.com API key
journals: # required key
  mood: # name of the journal
    entries_folder: ~/Desktop/Journal/mood # Where to save this specific journal's entries
    journal: Mood Journal # Optional, Day One journal to add to
    tags: [checkin] # Optional, array of tags to add to Day One entries
    markdown: individual # Can be daily or individual, any other value will create a single file
    dayone: true # true to log entries to Day One, false to skip
    title: "Mood checkin %M" # The title of the entry. Use %M to insert AM or PM
    sections: # required key
      - title: Weather # Title of the section (will create template sections in Day One)
        key: weather # the key to use in the structured data, will contain all of the answers
        questions: # required key
          - prompt: Current Weather
            key: weather.current
            type: weather.current
          - prompt: Weather Forecast # The prompt shown on the command line, will also become a header in the journal entries (Markdown, Day One)
            key: weather.forecast # if a key contains a dot, it will create nested data, e.g. `{ 'weather': { 'forecast': data } }`
            type: weather.forecast # Set this to weather for weather data
      - title: Health # New section
        key: health 
        questions:
          - prompt: Health rating
            key: health.rating
            type: numeric # type can be numeric, string, or multiline
            min: 1 # Only need min/max definitions on numeric types (defaults 1-5)
            max: 5
          - prompt: Health notes
            key: health.notes
            type: multiline
      - title: Journal # New section
        key: journal
        questions:
          - prompt: Daily notes
            key: notes
            type: multiline
  daily: # New journal
    journal: Journal
    markdown: daily
    dayone: true
    title: Daily Journal
    sections:
      - title: null
        key: journal
        questions:
          - prompt: How are you feeling?
            key: journal
            type: multiline

A journal must contain a sections key, and each section must contain a questions key with an array of questions. Each question must (at minimum) have a prompt, key, and type.

If a question has a key secondary_question, the prompt will be repeated with the secondary question until it's returned empty, answers will be joined together.

Question Types

A question type can be one of:

  • text or string will request a single-line string, submitted on return
  • multiline for multiline strings (opens a readline editor, use ctrl-d to save)
  • weather will just insert current weather data with no prompt
    • weather.forecast will insert just the forecast (using weather history for backdated entries)
    • weather.current will insert just the current temperature and condition (using weather history for backdated entries)
    • weather.moon will insert the current moon phase for the entry date
  • number or float will request numeric input, stored as a float (decimal)
  • integer will convert numeric input to the nearest integer
  • date will request a natural language date which will be parsed into a date object

Conditional Questions

You can have a question only show up based on conditions. Currently the only condition is time based. Just add a key called condition to the question definition, then include a natural language string like before noon or after 3pm. If the condition is matched, then the question will be displayed, otherwise it will be skipped and its data entry in the JSON will be null.

Conditions can be applied to individual questions, or to entire sections, depending on where the condition key is placed.

Naming Keys

If you want data stored in a nested object, you can set a question type to dictionary and set the prompt to null (or just leave the key out), but give it a key that will serve as the parent in the object. Then in the nested questions, give them a key in the dot format [PARENT_KEY].[CHILD_KEY]. Section keys automatically nest their questions, but if you want to go deeper, you could have a question with the key health and type dictionary, then have questions with keys like health.rating and health.notes. If the section key was status, the resulting dictionary would look like this in the JSON:

{
  "date": "2023-09-08 12:19:40 UTC",
  "data": {
    "status": {
      "health": {
        "rating": 4,
        "notes": "Feeling much better today. Still a bit groggy."
      }
    }
  }
}

If a question has the same key as its parent section, it will be moved up the chain so that you don't get { 'journal': { 'journal': 'Journal notes' } }. You'll just get { 'journal': 'Journal notes' }. This offers a way to organize data with fewer levels of nesting in the output.

Usage

Once your configuration file is set up, you can just run journal JOURNAL_KEY to begin prompting for the answers to the configured questions.

If a second argument contains a natural language date, the journal entry will be set to that date instead of the current time. For example, journal mood "yesterday 5pm" will create a new entry (in the journal configured for mood) for yesterday at 5pm.

Answers will always be written to ~/.local/share/journal/[KEY].json (where [KEY] is the journal key, one data file for each journal). If you've specified a top-level custom path with entries_folder in the config, entries will be written to [top level folder]/[KEY].json. If you've specified a custom path using entries_folder within the journal, entries will be written to [custom folder]/[KEY].json.

If you've specified daily or individual Markdown formats, entries will be written to Markdown files in ~/.local/share/journal/[KEY]/entries, either in a [KEY]-%Y-%m-%d.md file (daily), or in timestamped individual files. If digest is specified for the markdown key, a single file will be created at ~/.local/share/journal/[KEY]/entries/[KEY].md (or a folder defined by entries_folder).

At present there's no tool for querying the dataset created. You just need to parse the JSON and use your language of choice to extract the data. Numeric entries are stored as numbers, and every entry is timestamped, so you should be able to do some advanced analysis once you have enough data.

Answering prompts

Questions with numeric answers will have a valid range assigned. Enter just a number within the range and hit return.

Questions with type 'string' or 'text' will save when you hit return. Pressing return without typing anything will leave that answer blank, and it will be ignored when exporting to Markdown or Day One (an empty value will exist in the JSON database).

When using the mutiline type, you'll get an edit field that responds to most control-key navigation and allows insertion and movement. To save a multiline field, type CTRL-d.