Skip to content
A command-line tool to help with your GM-ing.
Ruby Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
exe Working on a quick and dirty config line parser Aug 8, 2019


A command-line tool to help with your GM-ing.

To install

First, you will need a working copy of Ruby. I recommend following the instructions over at rbenv's Github page.

Next, you'll want to install the gm-notepad gem.

$ gem install gm-notepad



On a commute home from work, while listening to Judd Karlman's "Daydreaming about Dragons" podcast he wondered about ways to organize notes for NPCs. And I started thinking. How might I organize my content for access while gaming? And what kind of content? More importantly, what kind of tasks do I need to complete as a GM.

  • Remember a character name
  • Lookup their passive perception
  • Lookup a random table
  • Roll on a random table
  • Create a random character name. Maybe based on a culture.
  • Record quick notes about an NPC

I thought about swnt, and command line tool for Stars without Numbers. See the github project for more information.

And I thought about Alex Schroeder's tools.

I have kicked this around for awhile. I made an attempt in Rollio. But I built that to roll on tables. I needed something more general. I will, however, begin converting the data.


By default gm-notepad interacts with $stdout and $stderr. There are three conceptual buffers:

  • interactive (defaults to $stderr)
  • output (defaults to $stderr)
  • filesystem (defaults to your file system)

When you type a line, and hit <enter>, gm-notepad will evaluate the line and render it to one or more of the buffers.


First, take a look at the help: $ gm-notepad -h

Usage: gm-notepad [options] [files]
Note taking tool with random table expansion.

	$ gm-notepad
	$ gm-notepad filename
	$ echo '{name}' | gm-notepad

    -l, --list_tables                List tables loaded and exit (Default: false)
    -r, --report_config              Dump the configuration data (Default: false)
    -p, --path=PATH                  Path(s) for {table_name}.<config.table_extension> files (Default: ["."])
    -f, --filesystem_directory=DIR   Path to dump tables (Default: ".")
    -x, --table_extension=EXT        Extension to use for selecting tables (Default: ".txt")
        --time_to_live=TTL           Per line of input, how many times to allow text expansion (Default: 100)
    -d, --delimiter=DELIM            Default column delimiter for tables (Default: "|")

Output options:
    -t, --timestamp                  Append a timestamp to the note (Default: false)

Color options:
    -i, --skip-interactive-color     Disable color rendering for interactive buffer (Default: false)
    -o, --with-output-color          Enable color rendering for output buffer (Default: true)

    -h, --help                       You're looking at it!

At it's core, gm-shell interacts with named tables. A named table is a file found amongst the specified paths and has the specified table_extension. Let's take a look at the defaults. In a new shell, type: $ gm-notepad -r

Which writes the following to the interactive buffer (eg. $stderr)::

# Configuration Parameters:
#   config[:column_delimiter] = "|"
#   config[:filesystem_directory] = "."
#   config[:include_original_command_as_comment] = true
#   config[:index_entry_prefix] = "index"
#   config[:interactive_buffer] = #<IO:<STDERR>>
#   config[:interactive_color] = :faint
#   config[:list_tables] = false
#   config[:output_buffer] = #<IO:<STDOUT>>
#   config[:output_color] = false
#   config[:paths] = ["."]
#   config[:report_config] = true
#   config[:skip_readlines] = false
#   config[:table_extension] = ".txt"
#   config[:time_to_live] = 100
#   config[:with_timestamp] = false

You'll need to exit out (CTRL+D).

By default gm-notepad will load as tables all files matching the following glob: ./**/*.txt.

Included in the gem's test suite are four files:

  • ./spec/fixtures/name.txt
  • ./spec/fixtures/first-name.txt
  • ./spec/fixtures/last-name.txt
  • ./spec/fixtures/location.csv

When I run gm-notepad -l, gm-notepad does the following:

  • load all found tables
  • puts the config (see above) to the interactive buffer
  • puts the table_names to the interactive buffer
  • exits

Below are the table names when you run the gm-notepad -l against the repository (note when you run this command you'll get a preamble of the config):


Now let's load gm-notepad for interaction. In the terminal, type: $ gm-notepad

You now have an interactive shell for gm-notepad. Type ? and hit <enter>.

gm-notepad will write the following to interactive buffer (eg. $stderr):

	?  - Help (this command)
	+  - Query table names and contents
	<table_name: - Write the results to the given table
	`  - Shell out command and write to interactive buffer
	`> - Shell out command and write to interactive AND output buffer
	! - Skip expansion
	/search/ - Grep for the given 'search' within the prefix
	[index] - Target a specific 'index'
	[][column] - Pick a random index
	{table_name} - expand_line the given 'table_name'
	{table_name[d6]} - roll a d6 and lookup that row on the given 'table_name'
	{table_name[d6][name]} - pick a random row (between 1 and 6) and select the 'name' column from the given table_name

Now, let's take look at a table. Again in an active gm-notepad session type the following: +first-name

gm-notepad will write the following to interactive buffer (eg. $stderr):

[1]	Frodo
[2]	Merry
[3]	Pippin
[4]	Sam
[5-6]	{first-name}Wise

These are the five table entries in the first-name table. "Frodo" is at index 1. "Merry", "Pippin", and "Sam" are at indices 2,3,4 respectively. For the fifth line there are two things happening. First the index spans a range. Second, notice the entry: {first-name}Wise. The {first-name} references a table named "first-name" (the same on you are looking at). Now type the following in your gm-notepad session: Hello {first-name}

gm-notepad will read the line and recursively expand the {first-name} and write the result to the interactive buffer and output buffer. The expander randomly picks a name from all entries, with ranges increasing the chance of being picked. In the above table "Frodo", "Merry", "Pippin", and "Sam" each have a 1 in 6 chance of being picked. And "{first-name}Wise" has a 2 in 6 chance.

In the session you might have something like the below:

=>	Hello SamWise
Hello SamWise

The line with starting with => is the interactive buffer. The other line is written to the output buffer.

You can also roll within a table. In the gm-notepad type the following: {first-name[1d4]}. The system will output "Frodo", "Merry", "Pippin", or "Sam". You won't get a "SamWise" or "FrodoWise" (or "FrodoWiseWise").

To wrap up our first session, let's try one more thing. In your gm-notepad session type the following: {first-name} owes {2d6}gp to {first-name}:

Frodo owes 3gp to SamWise

Let's take a look at the +character table. Your table indices need not be numbers. And you can mix numbers and text. This example introduces the idea of columns. I am still working on retrieving by column names as well as rendering column names.

[grell]	Grell	15	12D12
[jehat]	Jehat	19	14D6

You can write a new table, without exiting gm-notepad, by doing the following:


This will write to the junk.txt file the following:


You can then immediately access the junk table, by typing the following: +junk

[1]	2
[3]	4

Testing Locally

  • Clone the repository
  • Bundle the dependencies ($ bundle install)
  • Run the specs ($ bundle exec rspec)
  • Run the command from the repository ($ bundle exec exe/gm-notepad)


  • When printing tables, also print column names/indices
  • Write expected interface document
  • Raise load error if table index is a "dice" expression
  • Gracefully handle loading a malformed data file (maybe?)
  • Add concept of "journal entry"; its not a table (perhaps) but something that you could capture notes.
  • Add ability to pass a configuration file that includes parameters
  • Colorize puts to interactive buffer
  • Disable colors as a configuration option
  • Handle {critical[5]}
  • Allow {critical[{2d6+1}]} to roll the dice then lookup the value in the critical table
  • Handle {critical[{2d6}]} for {2d6} damage
  • For {critical[{2d6+1}]}, how to handle out of bounds
  • Skip table lines that begin with #
  • Skip processing input lines that begin with #
  • Allow configuration to specify table delimiter
  • Allow configuration for where to dump data
  • Normalize WriteToTableHandler to use a renderer
  • Gracefully handle requesting an entry from a table with an index that does not exist (e.g. with test data try +name[23])
  • Gracefully handle +name[], where "name" is a registered table
  • Add time to live for line expansion (to prevent infinite loops); I suspect 100 to be reasonable
  • Enable "up" and "down" to scroll through history
  • Add index name when rendering table entries
  • Add concept of history
  • When expanding tables account for line expansion (via \n and \t)
  • Separate the InputHandler into pre-amble (e.g. allow overrides to where we are writing, determine what command we are writing)
  • Create a configuration object that captures the initial input (reduce passing around parameters and persisting copies of the config)
  • Add column handling {table[][]}
  • Gracefully handle cell lookup when named cell for entry is not found
  • Support \{\{table}-name} You should be able to do \{\{culture}-name} and first evaluate to {arabic-name} and then get a value from the arabic-name table
  • Ensure index names are lower-case
  • Hit 100% spec coverage
  • Create a "To Render Object"; When you parse the input, you push relevant lines to that "To Render Object". When you look at a table, you want to know what the column names are.
  • Remove "defer" printing concept
  • Add ability to shell out; I would love to leverage the swnt command line tool
  • Refine row/column grep behavior, as it is pre-dates the idea of a table having columns

Stretch TODO

  • Handle a .gm-notepadrc to inject default configurations
  • Extract exe/gm-notepad logic into a Runner, to provide better file configuration
  • Allow configuration to specify colors
  • Aspiration: Enable \{\{monster}[ac]} to pick a random monster and then fetch that monster's AC
  • Allow option to add a table to memory (instead of writing the table)
  • Add auto table expansion for "{}"
  • Add auto table expansion for "+"
  • Add auto index expansion for "["
  • Determine feasibility of adding dice to the {} expansion syntax (instead of the [] syntax)
  • Add force write results to output
  • Add option to dump all tables to the given directory
  • Add config that expands dice results while including the requested roll
  • Normalize WriteToTableHandler to deliver on grep and index behavior
You can’t perform that action at this time.