Case Study: Building 'mdd'

A real-world example: building a Markdown tool ("mdd") to manage blog posts. Because hugo new is great, but we want more.

Step 1: Initialize the Project

We start by creating a full project structure with a subcommand article.

>
parseArger project mdd \
  --description "markdown tools for my blog" \
  --git-repo "DimitriGilbert/mdd" \
  --project-subcommand article

This creates the mdd folder, bin/article, utils/install, and more.

Step 2: Define the Arguments

We need a lot of metadata for our blog posts: title, categories, tags, series, date, etc.

>
parseArger parse bin/article \
  --pos 'title "article title"' \
  --opt 'folder "article folder name" --alias directory --alias dir' \
  --opt 'categories "article parent categories" --short c --alias cat --alias parent --repeat' \
  --opt 'tags "article tags" --repeat --short t --alias tag' \
  --opt 'series "article belongs to this series" --alias group --short g' \
  --opt 'date "publication date meta" --alias publication --alias publish-at --short d' \
  --opt 'summary "summary meta" --short s --alias description --alias desc' \
  --opt 'template "template file to use" --alias tpl' \
  --opt 'headings "add headings to your file" --repeat --alias part --alias h2' \
  --opt 'headings-level "heading level" --default-value 2 --alias hl' \
  --flag 'draft "is it a draft" --on --no-alias publish' \
  --nested-options 'meta "add any meta you want"' \
  --in-place

Step 3: The Implementation

Now we write the logic in bin/article using the variables ParseArger generated for us.

Handling Folders

_container_dir="$_arg_title";

if [ "$_arg_folder" != "" ]; then
    _container_dir="$_arg_folder";
fi

# Handle categories as path
if [ "${#_arg_categories[@]}" -gt 0 ]; then
    _cat_dir="";
    for _cat in "${_arg_categories[@]}"; do
        _cat_dir+="$_cat/";
    done
    _container_dir="$_cat_dir$_container_dir";
fi

if [ ! -d "$_container_dir" ]; then
    mkdir -p "$_container_dir";
fi

Generating Frontmatter

_metas_str="title: $_arg_title\n";

if [ "$_arg_date" != "" ]; then
    _metas_str+="date: $_arg_date\n";
fi

if [ "${#_arg_tags[@]}" -gt 0  ]; then
    _metas_str+="tags: \n";
    for _tg in "${_arg_tags[@]}"; do
        _metas_str+="\t- $_tg\n";
    done
fi

if [ "$_arg_draft" == "on" ]; then
    _metas_str+="draft: true\n";
fi

# Handle nested options
if [ "${#_arg_ns_meta[@]}" -gt 0 ]; then
    for _tmp_k_meta in "${!_arg_ns_meta[@]}"; do
        _metas_str+="$_tmp_k_meta: ${_arg_ns_meta[$_tmp_k_meta]}\n";
    done
fi

Generating Headings

if [ "${#_arg_headings[@]}" -gt 0 ]; then
    _hd_level_str="";
    for (( i=0; i<_arg_headings_level; i++ )); do
        _hd_level_str+="#";
    done

    for _hd in "${_arg_headings[@]}"; do
        _contents_str+="\n$_hd_level_str $_hd\n\n\n";
    done
fi

Step 4: Refinement (The "Oops" moment)

We forgot a way to overwrite existing files. Let's add a force flag.

>
parseArger parse bin/article -i --flag 'force "erase if exists"'

And update our logic:

if [ "$_arg_force" == "on" ] && [ -f "${_container_dir}/index.md" ]; then 
  rm "${_container_dir}/index.md" -f; 
fi

Step 5: Polish

Generate documentation and completion scripts so we don't hate ourselves later.

>
parseArger document --file ./mdd  --directory ./bin --title "MarkDown for Didi" > documentation.md
>
parseArger completely "mdd" ./mdd --subcommand-directory ./bin --no-run-completely > completely.yaml
completely preview > completely.bash

Setting up the Environment

Finally, we create an mdd.rc file to source our new tool and its completion into our shell.

if [ "${MDD_DIR}" != "" ]; then
  alias mdd="${MDD_DIR}/mdd";
  [ -f "${MDD_DIR}/completely.bash" ] && source "${MDD_DIR}/completely.bash";
fi