├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── 0000_introduction.md ├── 0099_conventions.md ├── 0100_dependencies.md ├── 0199_talk.md ├── 0200_basic_exercises.md ├── 0201_querying.md ├── 0205_querying.md ├── 0250_basic_automation.md ├── 0251_make_intro.md ├── 0252_make_for_ledger.md ├── 0295_other_reports.md ├── 0300_tracking_commodities.md ├── 0600_ledger-autosync.md ├── 0700_fava.md ├── 0800_timetracking.md ├── 0899_seealso.md ├── 0900_appendix.md ├── 0901_installing_ledger2beancount.md ├── 0910_example_c_program.md ├── 0912_finance_accounts.md ├── 0915_time_accounts.md ├── 0999_package_it_up.md ├── 0999_references.md ├── Brewfile ├── LICENSE.md ├── Makefile ├── README.md ├── REPO.md ├── config.yaml ├── examples.ledger ├── filter-pipe.lua ├── headers.tex ├── pandoc-crossref.yaml ├── refs.bibtex └── simple.csv /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve the workshop 4 | title: '' 5 | labels: bug 6 | assignees: colindean 7 | 8 | --- 9 | 10 | ## In what Chapter/Section is the bug? 11 | 12 | 13 | ## Describe the bug 14 | 15 | 16 | ## Expected behavior 17 | 18 | 19 | ## Screenshots 20 | 21 | 22 | ## What version do you have? 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[ENHANCEMENT]: " 5 | labels: enhancement 6 | assignees: colindean 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.html 3 | LICENSE.tex 4 | REPO.tex 5 | test.md 6 | build 7 | *.tar.gz 8 | -------------------------------------------------------------------------------- /0000_introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This workshop aims to teach accounting basics and to teach how to use the core tools of the Plain Text Accounting ecosystem in a workflow familiar to software developers who prefer command line tools, build systems, and easily transported report formats. 4 | 5 | ## What is Plain Text Accounting? 6 | 7 | Plain Text Accounting is the practice of maintaining an accounting ledger in a format that values human readability, accountant auditability, and version control. The ecosystem of PTA tools includes programs categorized as "ledger-like" which enable recording of purchases and transfers and investments, versioning of ledger-files to provide an audit trail, and performing analysis to produce registers, balance sheets, profit and loss statements, track billable time and paid time off, and lots of other reports. 8 | 9 | ## For whom was this workshop designed? 10 | 11 | This workshop is geared toward folks with the following skills: 12 | 13 | 1. Comfortability with basic algebra 14 | 2. Comfortability using a computer with a suitable keyboard or another input method for typing 15 | 3. Comfortability using a text editor program, such as Atom, Visual Studio Code, vim, or Emacs 16 | 4. Comfortability installing and using command line software on the operating system of their choice 17 | 18 | and the following things available to them: 19 | 20 | 1. A computer running a UNIX-like operating system or having standard UNIX tools available to it. That explicitly includes **Apple macOS** or virtually any modern **Linux** distribution, such as **Ubuntu Linux**. **Microsoft Windows** can be used when using UNIX tools via _Windows Subsystem for Linux_ or some third-party package managers. For more information, see @tbl:os-pkgman. 21 | 1. Internet access, to install tools required to follow the workshop activities. 22 | 23 | ## Why should you care? 24 | 25 | Developers throughout the world are often highly-compensated professionals. By carefully capturing and documenting the flow of one's money, these professionals can avoid mismanaging their personal finances and squandering the assets at their disposal, preserving more funds for important things like debt repayment, retirement investment, vacations, and charitable contributions. This talk introduces personal finance accounting in general and moves into teaching how to use the open source plain text accounting ecosystem to manage one's money. 26 | 27 | The majority of tools in this ecosystem and free and open-source software, meaning you can rely on them to always be freely available to you for your use, modification, and, in most cases, redistribution. The absence of lock-in means you own your data 28 | without concern for being locked out of your hard work because of a price increase or service outage. 29 | 30 | ## Workshop goals 31 | 32 | In this presentation, you'll learn 33 | 34 | * basic generally-accepted accounting principals (GAAP) and double-entry accounting, 35 | * `ledger` command line tool usage, 36 | * alternatives to and similar programs that complement `ledger` 37 | * why you might choose Plain Text Accounting over other tools such as Quicken, QuickBooks, Mint.com, or You Need a Budget 38 | * how to track daily transactions as well as investments, from brokerage accounts to cryptocurrencies 39 | * how to automate some entry of data 40 | 41 | 42 | 43 | Anti-goals, or things you won't learn or things specifically avoided in this 44 | workshop: 45 | 46 | * how to compile from source most of the tools used, since most are available 47 | in binary form through a package manager. Only those unavailable are 48 | explained in this workshop. 49 | * how to use _everything_ that `ledger` has to offer, since `ledger` is an 50 | in-depth program with a variety of modes that not even the author uses 51 | frequently, and this should not take the place of the 52 | [`ledger` documentation](https://www.ledger-cli.org/3.0/doc/ledger3.html). 53 | 54 | ## Workshop agenda 55 | 56 | This workshop will take approximately 150 minutes. 57 | 58 | Rough time breakdown: 59 | 60 | * 45 minutes: Chapter 3 - Presentation of GAAP, double-entry accounting basics, `ledger` format basics, and why `ledger` 61 | * 10 minutes: Q&A and break 62 | * 15 minutes: Chapter 4-6: Writing `ledger` format logs and querying them 63 | * 25 minutes: Chapter 7-8: Report automation 64 | * 5 minutes: break 65 | * 10 minutes: Chapter 9: Tracking commodities 66 | * 30 minutes: Chapter 10: Importing data from a bank 67 | * 10 minutes: Chapter 11-14: Web-based visualization, time keeping 68 | 69 | ## Things You'll Have When You Are Done 70 | 71 | 1. A basic understanding of accounting principals 72 | 1. The knowledge to read and write `ledger` transaction records to represent 73 | daily transactions, payments, stocks, and inventory 74 | 1. The knowledge to query transaction logs with `ledger` 75 | 1. A simple script to run `ledger` 76 | 1. A better way than that script to run `ledger`: `make` 77 | 1. A `make` configuration that you could use to be immediately productive 78 | tracking your own finances if you build transaction records from your 79 | financial statements 80 | 1. A way to convert your bank statement exports to `ledger` transaction records 81 | 1. A way to track your time using `ledger` 82 | 83 | ## About the author 84 | 85 | Colin Dean has been using Plain Text Accounting tools since the early 2010s for his work time tracking, his personal finances, and managing the finances of the for-profit and non-profit organizations he administers. He runs the [/r/plaintextaccounting subreddit](https://reddit.com/r/plaintextaccounting) and has contributed to the Plain Text Accounting ecosystem through educational presentations, bug reporting, and software development. 86 | -------------------------------------------------------------------------------- /0099_conventions.md: -------------------------------------------------------------------------------- 1 | ## Conventions used in this document 2 | 3 | This document follows some conventions for convey information. 4 | You've probably already noticed the numbered section headers. 5 | Many clickable elements are highlighted in a different color. 6 | **External links** are blue. 7 | Most links in the prose are noted by red footnote links. 8 | The actual link is at the bottom of the page in the footnotes. 9 | **Internal links** are red and lead to the relevant section, table, figure, or listing. 10 | For example, this is a link to the listing below: @lst:conventions. 11 | If you link on it, the listing will scroll to the top of your view, or whatever 12 | your document reader is configured to do if not that. 13 | 14 | `Monospace` text indicates a **command** or a **file name**. 15 | If it follows an imperative verb such as "run" or "execute," then it is 16 | a command that you should execute. 17 | For example, run `date` to see the current date in your terminal. 18 | 19 | Some call-out sections will look like this or begin with certain words: 20 | 21 | ::: tryit 22 | 23 | **TRY IT:** This paragraph contains something that you could do for 24 | experimentation. It'll probably be open-ended. 25 | 26 | ::: 27 | 28 | ::: protip 29 | 30 | **PROTIP:** This paragraph contains something that this author has learned 31 | through experience that will make your life easier knowing ahead of time. 32 | 33 | ::: 34 | 35 | **Listings** contain code snippets or the expected output of a command. 36 | This document is automatically laid out, so some listings will be follow the 37 | relevant text significantly, sometimes several pages later. 38 | This may be confusing at first, but using the internal links will expedite 39 | navigation. 40 | It may be useful to have two side-by-side copies of the document open: one for 41 | reading the prose and one for copying text from the referenced listing. 42 | 43 | There are three ways to use the examples provided in listings: 44 | 45 | 1. You should have received along with this PDF a compressed archive containing 46 | some supplementary files. 47 | See @sec:artifacts for what files are in it and how to verify that you have 48 | the correct archive file for this version of the workshop. 49 | Unpack that archive. 50 | Each listing connected to a file in that archive contains a filename in 51 | parentheses at the end of the caption. 52 | You can use the content of this file as if you'd copied the file by hand out 53 | of the workshop text! 54 | 1. Some listings can be copied out of the PDF by highlighting them, copying the 55 | text to your clipboard, and pasting into a text editor. Some document readers 56 | may capture the line numbers in the process. You can either highlight 57 | line-by-line or refer to the companion files for this workshop to find the 58 | text. Apple Preview seems to work as expected while Google Drive PDF Viewer 59 | copies the line numbers all the time – don't use it! 60 | 1. You could type each example by hand directly out of the PDF. This is a bit 61 | laborious but it can help you learn along the way by doing, approximately 62 | the same value as copying notes from a teacher's chalkboard. 63 | 64 | Listing: An example listing (`example_listing.sh`) {#lst:conventions} 65 | 66 | ```{.bash pipe="tee example_listing.sh"} 67 | #!/usr/bin/env bash 68 | # run me with: 69 | # bash example_listing.sh 70 | echo "It contains some shell script." 71 | for i in `seq 1 4`; do 72 | echo "This is line $i." 73 | done 74 | echo "You can find this file at example_listing.sh," 75 | echo "you can copy and paste this into a file," 76 | echo "or you can retype it by hand…" 77 | ``` 78 | 79 | 80 | ```{pipe="bash example_listing.sh > /dev/null"} 81 | ``` 82 | -------------------------------------------------------------------------------- /0100_dependencies.md: -------------------------------------------------------------------------------- 1 | # Install Dependencies {#sec:dependencies} 2 | 3 | Let's install some software that you'll need in order to work through this workshop. It's easiest to install the basics upfront so that you can continue the workshop even if you may be offline later. 4 | 5 | There are many dependencies across a few language ecosystems: C++, C, Rust, Python, Perl, and more. A strength of the Plain Text Accounting ecosystem is how easy it is to create tooling for it. While writing a parser takes some time, _producing_ Ledger-format transaction logs is an easy task for even new programers: it's a matter of outputting some text with appropriate whitespace between elements. 6 | 7 | Table: Great tools for exploring the Plain Text Accounting ecosystem {#tbl:tool-list} 8 | 9 | ----------------------------------------------------------------------------------------------------- 10 | Software Utility Website 11 | ------------------ ------------------------------------ --------------------------------------------- 12 | `ledger` The original plain text https://ledger-cli.org 13 | accounting program 14 | 15 | GNU `make` Task- and file-based build system https://www.gnu.org/software/make/ 16 | standard across multiple ecosystems 17 | 18 | `gnuplot` Renders graphs from data files http://www.gnuplot.info/ 19 | 20 | `pandoc` Universal document converter https://pandoc.org 21 | 22 | `xsv` CSV querying and manipulation tool https://github.com/BurntSushi/xsv 23 | 24 | `entr` Runs a command when any in https://eradman.com/entrproject/ 25 | a set of files change 26 | 27 | `ledger-autosync` Converts CSV and QFX/OFX https://github.com/egh/ledger-autosync 28 | to Ledger format 29 | 30 | `ledger2beancount` Converts Ledger-format transaction https://github.com/beancount/ledger2beancount 31 | records to Beancount format 32 | 33 | `fava` Web-based transaction exploration https://beancount.github.io/fava/ 34 | tool for Beancount ledgers 35 | 36 | ----------------------------------------------------------------------------------------------------- 37 | 38 | ## A Note before Installing 39 | 40 | It may be tempting to install each of these packages one-by-one, or in groups, 41 | but it's a good practice to use per-project package manifests. Those are 42 | provided in @sec:install_homebrew and @sec:install_pip. 43 | 44 | @Sec:install_basics and @sec:install_advanced provide one-liners to install 45 | their packages if you don't want to wait and use the other method or want to 46 | install just enough to get through the basics of the workshop. 47 | 48 | ## Installing Using a Package Manager 49 | 50 | While you could install each of these programs in @tbl:tool-list separately, it's easiest to do that in a package manager. [Homebrew](https://brew.sh) is the package manager used in examples in this section and the remainder of the workshop. Other package managers described in @tbl:os-pkgman may contain the software necessary to complete this workshop. 51 | 52 | Table: Operating system package manager support {#tbl:os-pkgman} 53 | 54 | |Operating System|Supported for Workshop?[^os-support-notice]|Recommendation | 55 | |----------------|---------------|-----------------------------------------------------------------| 56 | |macOS|Yes|Homebrew| 57 | |Linux|Partially|Homebrew or your distro's package manager plus Cargo| 58 | | ChromeOS | Partially | Use [Linux (Beta) mode](https://support.google.com/chromebook/answer/9145439) to install Linux packages and then use [Homebrew](https://brew.sh) or [Chromebrew](https://skycocker.github.io/chromebrew/) to access other packages. | 59 | |Windows | Partially | Install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) or use a Linux VM, then follow Ubuntu Linux instructions. You can [Chocolatey](https://chocolatey.org/) or [Scoop](https://scoop.sh) for some programs. | 60 | | Android | No | [Termux](https://termux.com/) | 61 | |iOS | No | Please use a desktop operating system for this workshop. | 62 | | Others| No | You probably know what you're doing 63 | 64 | [^os-support-notice]: Operating systems that partially-supported are supported when using Homebrew. 65 | 66 | Not all dependencies may be available through each package manager, and dependencies with more extensive installation instructions may be detailed at the time of their use. 67 | 68 | ### The Basics {#sec:install_basics} 69 | 70 | In order to complete the most meaningful parts of this workshop, you will need 71 | to install `ledger` for @sec:reports_balance, GNU `make` for @sec:make_intro, 72 | and `gnuplot` for @sec:gnuplot. @lst:install_basics provides a quick one-liner 73 | to install these if you don't want to use a Homebrew manifest in 74 | @sec:install_homebrew. 75 | 76 | Listing: Package installer lines for the basics {#lst:install_basics} 77 | 78 | ```bash 79 | # macOS and Linux with Homebrew 80 | brew install ledger make gnuplot pandoc 81 | # Windows with Chocolatey 82 | choco install ledger make gnuplot pandoc 83 | # Windows with Scoop 84 | # - ledger unavailable, use choco for it 85 | scoop install make gnuplot pandoc 86 | # Ubuntu/Debian Linux 87 | apt install ledger make gnuplot pandoc 88 | # Alpine Linux 89 | apk add ledger make gnuplot pandoc 90 | ``` 91 | 92 | ### The Advanced Dependencies {#sec:install_advanced} 93 | 94 | In order to complete the entire workshop, you will need to additionally install 95 | `xsv` for @sec:sync_clean, 96 | `python` and some Python packages from the Python Package Index (PyPI) starting 97 | in @sec:autosync_updates, 98 | and 99 | `entr` for @sec:categorizing. 100 | @lst:install_advanced provides a quick one-liner 101 | to install these if you don't want to use a Homebrew manifest in 102 | @sec:install_homebrew. 103 | 104 | Listing: Package installer lines for the tools used later in the workshop {#lst:install_advanced} 105 | 106 | ```bash 107 | # macOS and Linux with Homebrew 108 | brew install python xsv entr 109 | # Windows with Chocolatey 110 | # - Use Cargo for xsv 111 | # - entr unavailable natively, use WSL 112 | choco install python 113 | # Windows with Scoop 114 | # - entr unavailable natively, use WSL 115 | scoop install xsv python 116 | # Ubuntu/Debian Linux 117 | # - Use Cargo for xsv 118 | apt install entr python3 119 | # Alpine Linux 120 | # - Use Cargo for xsv 121 | apk add entr python3 122 | # Cargo+Crates.io for Rust programs, https://crates.io/install 123 | cargo install xsv 124 | ``` 125 | 126 | ### Homebrew `Brewfile` for most dependencies {#sec:install_homebrew} 127 | 128 | If you've not already installed Homebrew, follow the instructions at to install it. 129 | 130 | @Lst:brewfile contains `Brewfile` for use with Homebrew. Write its contents to a file called `Brewfile` or use that file from the supplementary artifacts and then run `brew bundle` to install the software. 131 | 132 | ```{#lst:brewfile .ruby caption="Brewfile" pipe="tee Brewfile"} 133 | # Brewfile for macOS and Linux 134 | brew 'ledger' 135 | brew 'make' 136 | brew 'gnuplot' 137 | brew 'entr' 138 | brew 'xsv' 139 | brew 'python' 140 | brew 'pandoc' 141 | ``` 142 | 143 | ### Python dependencies with `pip` {#sec:install_pip} 144 | 145 | After you've installed those packages, you'll need to install some Python packages with [`pip`](https://pypi.org/project/pip/), the package installer for the Python Package Index. Write the contents of @lst:requirements-txt to a file named `requirements.txt` or use that file from the supplementary artifacts and follow the instruction in the comments to execute the installation process. 146 | 147 | Note that you may already have a Python 2.x environment installed and set to 148 | default even if you install Python 3.x in an earlier step. Be sure that when 149 | you run `pip --version`, you see `python 3.x` like what's in @lst:versions for 150 | `pip`. 151 | If you don't, use `pip3` to install the packages in @lst:requirements-txt. 152 | 153 | Listing: `requirements.txt` {#lst:requirements-txt} 154 | 155 | ```{pipe="tee requirements.txt"} 156 | # put this into requirements.txt then run 157 | # pip install -r requirements.txt -U 158 | # 159 | ledger-autosync 160 | fava 161 | ``` 162 | 163 | ### Other Advanced Dependencies to be Manually Installed 164 | 165 | Most of the dependencies listed in @tbl:manually-installed are optional. They are required only for one activity and _may_ be omitted or installed when reaching that relevant module of the workshop. 166 | 167 | Table: Manually installed dependencies {#tbl:manually-installed} 168 | 169 | | Dependency | Required for | How to install | 170 | |--------------------|--------------|---------------------| 171 | | `ledger2beancount` | @Sec:use_ledger2beancount | @Sec:install_ledger2beancount | 172 | 173 | ## Versions 174 | 175 | Aspects of this workshop description are built against certain versions of the dependencies. 176 | The listings below reflect the versions of the software used. Most of the dependencies should 177 | work at any version available as of the date of this workshop on the title page, though. 178 | 179 | Listing: Versions of software used in this workshop {#lst:versions} 180 | 181 | ```{pipe="sh"} 182 | echo "# ledger" 183 | ledger --version | head -n 1 184 | echo "# entr" 185 | entr 2>&1 | head -n 1 186 | echo "# xsv" 187 | xsv --version 188 | echo "# python" 189 | python --version 190 | echo "# pip" 191 | pip --version 192 | echo "# make" 193 | make -v | head -n 1 194 | echo "# gnuplot" 195 | gnuplot --version 196 | echo "# pandoc" 197 | pandoc --version | head -n 1 198 | ``` 199 | 200 | -------------------------------------------------------------------------------- /0199_talk.md: -------------------------------------------------------------------------------- 1 | # Introduction to Accounting and Ledger {#sec:intro_talk} 2 | 3 | This workshop is paired with the presentation "Plain Text Accounting with the 4 | Ledger Ecosystem". 5 | 6 | If you're working on this workshop as a part of a conference, there's a pretty good chance that you've just listened to this presentation live! 7 | Thanks for listening, and now you'll get a great chance to interact with the examples provided in that presentation. 8 | 9 | That said, if you've not already done so, please consume the presentation in some way before proceeding in order to learn: 10 | 11 | * Double-entry accounting basics 12 | * Account naming conventions 13 | * Reporting types 14 | * Terminology of `ledger` and the rest of the Plain Text Accounting ecosystem 15 | * The "whys" of the Plain Text Accounting ecosystem 16 | 17 | ## Where to view the presentation 18 | 19 | It's on [YouTube](https://www.youtube.com/watch?v=FJtaM43PgXQ). You can see the slides on [SpeakerDeck](https://speakerdeck.com/colindean/plain-text-accounting-with-the-ledger-ecosystem-ohio-linuxfest-2017) or the raw presentation files on [GitHub](https://github.com/colindean/talks/tree/master/intro_to_plaintextaccounting). 20 | 21 | -------------------------------------------------------------------------------- /0200_basic_exercises.md: -------------------------------------------------------------------------------- 1 | # Maintaining your Transaction Record in `ledger` format 2 | 3 | Tracking your transactions for analysis with `ledger` is as easy as writing some text to a file in a very human-readable format. 4 | The format is _structured_ but appears _unstructured_ to many because it doesn't use curly brackets, key-value pairs, or other special characters to model transaction data. 5 | Instead, the things that matter are just having enough whitespace between 6 | certain elements in order for the `ledger` parser to understand the difference 7 | between dates, amounts, and so on. 8 | 9 | Start your favorite text editor and terminal emulator [^terminals] and you'll get started on the path to personal finance greatness. 10 | 11 | [^terminals]: Command Prompt, Terminal, iTerm2, Gnome Terminal, Alacritty, etc. 12 | 13 | ## Basic transaction format {#sec:basic_tx_format} 14 | 15 | Recall from the introductory presentation in @sec:intro_talk the basic format 16 | of a `ledger` transaction, shown in @lst:basics_basic. 17 | 18 | Listing: A basic transaction (`1.ledger`) {#lst:basics_basic} 19 | 20 | ```{.ledger pipe="tee 1.ledger" .numberLines} 21 | 2017-06-26 Commonplace Coffee 22 | Expenses:Restaurants:Coffee 3.00 23 | Assets:Cash:Wallet -3.00 24 | 25 | ``` 26 | 27 | In @lst:basics_basic, line 1 shows the _transaction date_ and _payee_. 28 | Lines 2 and 3 shows a _posting_ comprised of an _account_ and an _amount_. 29 | 30 | All transactions must balance. That is, the amount credited must 31 | equal the amount debited: credits minus debits must equal zero. 32 | 33 | Note the _accounts_ used in this example. 34 | One begins with `Expenses` and the other begins with `Assets`. 35 | Expenses are _credited_ because the money flows _toward_ them. 36 | Assets are credited when you add funds and debited when you move money to something else. 37 | In this transaction, you're deducting money from an account representing your wallet and adding it to an expense representing your coffee spending. 38 | 39 | `ledger` has some great conveniences that ease entry. 40 | One such convenience is that `ledger` allows transactions to omit the _amount_ on a single _posting_, as shown in @lst:basics_basic_omitamount. 41 | The missing amount is calculated and is equal to whatever amount is necessary 42 | to balance the transaction. 43 | 44 | Listing: A basic transaction with an automatically-balanced posting amount (`2.ledger`) {#lst:basics_basic_omitamount} 45 | 46 | ```{.ledger pipe="tee 2.ledger" .numberLines} 47 | 2017-06-26 Commonplace Coffee 48 | Expenses:Restaurants:Coffee 3.00 49 | Assets:Cash:Wallet 50 | 51 | ``` 52 | 53 | You can also supply comments for a transaction or posting. 54 | Postings can only have one comment line but transactions can have as many as 55 | you want. 56 | A comment in the format `key: value` creates a transaction _tag_ accessible in 57 | queries. You'll learn more about that in @sec:tx_walkthrough and @sec:querying_tagged_transactions. 58 | 59 | Listing: A basic transaction with a comment (`3.ledger`) {#lst:basics_basic_comment} 60 | 61 | ```{.ledger pipe="tee 3.ledger" .numberLines} 62 | 2017-06-26 Commonplace Coffee 63 | ; cold brew 64 | Expenses:Restaurants:Coffee 3.00 65 | Assets:Cash:Wallet -3.00 66 | 67 | ``` 68 | 69 | ## Walkthrough of some common basic transactions {#sec:tx_walkthrough} 70 | 71 | There are some other important things to highlight about transaction formatting. 72 | @lst:common_transactions lists a few common example transactions worth explaining. 73 | 74 | Listing: Common transaction examples (`walkthrough.ledger`) {#lst:common_transactions} 75 | 76 | ```{.ledger pipe="ledger -f - print | tee walkthrough.ledger" .numberLines} 77 | 2020-07-15 ! PA Department of Revenue Income Tax Due 78 | ; check_number: 1701 79 | Expenses:Taxes:EarnedIncome:Pennsylvania 64.00 USD 80 | Assets:Cash:Banks:Dollar -64.00 USD 81 | 82 | 2020-07-17 * Code and Supply Heartifacts Tickets 83 | ; ticket_reference: 0xDECAFBAD 84 | Expenses:Conferences:Registration 50.00 USD 85 | Liabilities:CreditCard:Visa -50.00 USD 86 | 87 | 2020-07-15=2020-07-23 IRS Tax Due 88 | ; check_number: 1700 89 | * Expenses:Taxes:EarnedIncome:Federal 64.00 USD 90 | ! Assets:Cash:Banks:Dollar -64.00 USD = -128.00 USD 91 | 92 | ``` 93 | 94 | Transactions have a state noting that they are uncleared, pending, or cleared. 95 | This is useful to note transactions that you may need to revisit and amend, 96 | such as outstanding checks or other similar payments. 97 | 98 | * Line 1: The presence of `!` marks a transaction as pending. 99 | * Line 6: The presence of `*` marks a transaction as cleared. 100 | * Line 11: The absence of a mark leaves a transaction in uncleared state. 101 | 102 | These can apply to individual postings, too, as is shown on line 14 to indicate 103 | that payment was made on July 15 but it's not expected to hit your bank account 104 | until July 23. 105 | 106 | Note the comments on lines 2, 7, and 12 are in the `key: value` format 107 | mentioned in @sec:basic_tx_format. 108 | 109 | Lastly, note the presence of `USD` after the amounts in each posting. 110 | This denotes the currency by creating a commodity implicitly. 111 | `ledger` tracks all currencies, like US Dollar, the Euro, Haitian gourde, 112 | Bitcoin, or Japanese yen, and things like stocks, property, and more with a 113 | commodity. 114 | It is essentially a label to distinguish balances in different systems and 115 | different amounts of a different object! 116 | You'll learn more on commodities in @sec:commodities. 117 | 118 | Line 11 in @lst:common_transactions notes an effective date relationship. 119 | This says that the transaction started on `2020/07/15` but it wasn't effective 120 | until `2020/07/23`. This is useful for tracking when a posting was debited 121 | and when the other was credited. You can sort by the effective date, too. 122 | See the [`ledger` docs on Effective Dates](https://www.ledger-cli.org/3.0/doc/ledger3.html#Effective-Dates) for a more in-depth explanation. 123 | 124 | The last line in @lst:common_transactions includes a _balance assertion_. 125 | This is a powerful way to assert that, after this posting and transaction are processed, the balance of this particular account should be a known amount. 126 | This is tremendously useful for reconciliation with statements but can also be a source of pain during manual entry. 127 | Treat balance assertions like regression tests – tests that warn when fixed bugs come back – and use them when you are really certain about a balance. 128 | This workshop doesn't use balance assertions beyond this because of the 129 | inherent difficulty in tracking them down. 130 | Learning to use them is an exercise left to the reader! 131 | 132 | -------------------------------------------------------------------------------- /0201_querying.md: -------------------------------------------------------------------------------- 1 | # Querying your Transaction Records with Reports 2 | 3 | There are two types of basic reports in `ledger`: 4 | 5 | * `balance` - applies all transactions and displays the current balance 6 | * `register` - applies all transaction and displays each transaction in a list 7 | 8 | Write the contents of @lst:basics_basic to a file `1.ledger`. 9 | 10 | ## Balance reports {#sec:reports_balance} 11 | 12 | Balance reports display balances in a a convenient account tree format. 13 | This format enables you to see what amounts are associated with what accounts 14 | in a clear summary view. 15 | 16 | Run `ledger --file 1.ledger balance` to see a balance report. 17 | It should look like the contents of @lst:basics_balance. 18 | 19 | ::: protip 20 | 21 | **PROTIP**: Note that `-f` and `--file` are synonymous and used interchangeably throughout 22 | this workshop. 23 | There may be other short options, i.e. one character preceded by one dash, used in place of 24 | other long option, i.e. two dashed preceding a word or a couple of words. 25 | It's a good practice to use long options when writing a script and short 26 | options when when typing a command in a terminal. 27 | Not all programs offer both for each option, so keep your eyes out! 28 | Fortunately, most CLI options in `ledger` do have a short and long version. 29 | 30 | ::: 31 | 32 | Listing: Balance report for @lst:basics_basic {#lst:basics_balance} 33 | 34 | ```{pipe="sh"} 35 | ledger -f root/examples.ledger balance --begin 2017/06/26 --end 2017/06/27 36 | ``` 37 | 38 | @Lst:examples shows a more realistic transaction log. Write it to a file 39 | `ex.ledger` so that you can use it for some querying experimentation. 40 | 41 | Listing: A fuller example (`ex.ledger`) {#lst:examples} 42 | 43 | ```{pipe="sh | ledger -f - print | tee ex.ledger"} 44 | cat root/examples.ledger 45 | ``` 46 | 47 | Run `ledger -f ex.ledger balance` to see the balance report. 48 | @Lst:examples_bal reflects what you'll see. 49 | 50 | Listing: Examples basic balance report {#lst:examples_bal} 51 | ```{pipe="sh" } 52 | ledger -f ex.ledger balance 53 | ``` 54 | 55 | Notice the structure of the output in @lst:examples_bal. 56 | This account tree structure helps the reader understand the sum of each account 57 | at the level shown. Leaves on the tree show the amount just for the account, 58 | while higher-level accounts sum all of the leaves and may include postings at 59 | that particular level. 60 | 61 | To see this more in action, check out the transactions in 62 | @lst:account_tree_effects. Notice that the posting on line 5 is to an account 63 | that other postings have used a subaccount. 64 | 65 | Listing: Account tree effects (`account_tree_effects.ledger`) {#lst:account_tree_effects} 66 | ```{.ledger pipe="tee account_tree_effects.ledger" .numberLines} 67 | 2020/07/01 Black Forge Coffee 68 | Expenses:Restaurants:Coffee 5 69 | Liabilities:CreditCard:Visa 70 | 71 | 2020/07/02 Leon's Caribbean 72 | Expenses:Restaurants 20 73 | Liabilities:CreditCard:Amex 74 | 75 | 2020/07/04 Lorelei 76 | Expenses:Restaurants:Alcohol 10 77 | Liabilities:CreditCard:Visa 78 | ``` 79 | 80 | @Lst:account_tree_effects_balance shows the balance report on these transactions. 81 | Note that the `Expenses` on line 1 reflects credits of 35 and while the sum of the leaf 82 | accounts is only 25. Note that the sum of the leaf accounts in `Liabilities` on 83 | line 4 is 35. 84 | 85 | ::: protip 86 | 87 | **PROTIP:** It's a good idea to post transactions to the leaf accounts or subaccounts. 88 | 89 | ::: 90 | 91 | Listing: Balance report on `account_tree_effects.ledger` {#lst:account_tree_effects_balance} 92 | ```{pipe="ledger -f account_tree_effects.ledger bal" .numberLines} 93 | ``` 94 | 95 | Note that when an account has only one subaccount, that subaccount is displayed 96 | inline, but multiple subaccounts are broken out into multiple lines. 97 | 98 | The zero at the bottom of a balance report indicates a zero net balance across all of the queries accounts. 99 | If you were tracking price changes on a commodity like a stock, this could be positive or negative. 100 | More on that in @sec:commodities. 101 | 102 | ## Register report 103 | 104 | The register report lists all transactions that match your query. 105 | This is useful for comparing against a bank statement or other list of 106 | transactions when reconciling your records against those provided by a third party. 107 | Bank transaction errors seem to be a rarity these days, but manually reviewing 108 | even after importing transactions manually is an excellent way to ensure that 109 | no transactions were omitted or have incorrect information. 110 | The register is a great way to see all transactions involving a certain account 111 | or payee or date. You'll learn more about this in @sec:querying_accounts and @sec:querying_payees. 112 | 113 | To see a register report for the transasction shown in @lst:basics_basic, 114 | run `ledger --file 1.ledger register`. 115 | It should look like the contents of @lst:basics_register. 116 | 117 | Listing: Register report for @lst:basics_basic {#lst:basics_register} 118 | 119 | ```{pipe="sh"} 120 | ledger -f 1.ledger register 121 | ``` 122 | 123 | Register reports on a single transaction are pretty boring, so 124 | run `ledger -f ex.ledger register` to see the register report for @lst:examples 125 | and it should match @lst:examples_reg. 126 | 127 | Listing: Examples basic register report {#lst:examples_reg} 128 | ```{pipe="sh"} 129 | ledger -f ex.ledger register 130 | ``` 131 | 132 | You'll notice this minimal output in @lst:examples_reg is more simplified than 133 | what you're likely seeing in your terminal. `ledger` elegantly determines the 134 | width of your terminal and makes an attempt to size the display accordingly. 135 | You can add `--wide` to the command invocation in order to force a wide view. 136 | This is useful for generating reports that will be on HTML documents or other 137 | hypermedia that is less sensitive to width. 138 | 139 | Notice the dates, the payees, and the postings. The first column of numbers is 140 | the amount for each posting while the second number reflects a running sum 141 | _within the query_ of each account shown. Notice also that the account names 142 | are abbreviated: this is a result of the aforementioned width limitation. 143 | It really is a good idea to use `ledger` on a wide terminal, at least 120 144 | columns. Run `echo $COLUMNS` and you'll likely see your current column width. 145 | 146 | ::: protip 147 | 148 | **PROTIP:** Now is a great time to widen your terminal window to at least 120 columns. 149 | 150 | ::: 151 | -------------------------------------------------------------------------------- /0205_querying.md: -------------------------------------------------------------------------------- 1 | # Querying Your Transactions 2 | 3 | ## Querying Accounts {#sec:querying_accounts} 4 | 5 | A bare balance or register report is great for seeing all data. However, it's 6 | more realistic to limit output in some way in order to see transactions related to other 7 | transations or accounts related to others in some way. `ledger` has a very 8 | powerful query language, the key tenets of which you'll learn in these next few 9 | sections 10 | 11 | Perhaps you want to only see your assets on hand. Run a balance report and 12 | specify `Assets` with `ledger -f ex.ledger balance Assets` to see only Assets. 13 | @Lst:examples_assets shows the output. 14 | 15 | Listing: Assets-only report on @lst:examples {#lst:examples_assets} 16 | ```{pipe="sh"} 17 | ledger -f ex.ledger balance Assets 18 | ``` 19 | 20 | ::: tryit 21 | 22 | **TRY IT:** Try some other accounts present in `ex.ledger` to see the balance for top-level accounts 23 | as well as subaccounts. 24 | 25 | ::: 26 | 27 | If you're not sure what accounts are available to be queried, `ledger` can 28 | help. 29 | To see the accounts present in a transaction log, 30 | use the `accounts` commmand in place of `balance` or 31 | `register`. You will see something like @lst:example_accounts. 32 | 33 | Listing: `ledger -f ex.ledger accounts` output {#lst:example_accounts} 34 | ```{pipe="sh"} 35 | ledger -f ex.ledger accounts 36 | ``` 37 | 38 | ## Important Balance Report Types 39 | 40 | In the introductory talk you watched as a part of @sec:intro_talk, you learned 41 | about two important report types: 42 | _Cashflow_ (also called _Profit and Loss_) and _Net Worth_. 43 | 44 | ### Cashflow {#sec:cashflow} 45 | 46 | Cashflow tracks Income and Expenses: money that came in from outside of your 47 | control and money that exited your control. Subtract Expenses from Income and 48 | you will know if you had a _gain_ and turned a _profit_ or experienced a _loss_. 49 | 50 | `ledger` makes a cashflow report incredible easy. The query is simply `Income 51 | Expenses`! Try it out on `ex.ledger`. See @lst:example_cashflow for the 52 | expected output of `ledger -f ex.ledger bal Income Expenses`. 53 | 54 | Listing: A cashflow report generated by `ledger -f ex.ledger bal Income Expenses` {#lst:example_cashflow} 55 | 56 | ```{pipe="sh"} 57 | ledger -f ex.ledger bal Income Expenses 58 | ``` 59 | 60 | ### Net Worth {#sec:networth} 61 | 62 | Net Worth tracks Assets and Liabilities: money that you control and money owed to someone else. 63 | Subtract Liabilities from Assets and you will ascertain your current net worth. 64 | It's OK if this number is negative: it just means that you owe more than you currently have. 65 | When tracking your net work for real, don't forget to track real property, like a house or a car. 66 | A house is an asset that has a corresponding liability that goes away over time: a mortgage! 67 | You'll see how to track house value, mortgage, and associated expenses in @sec:commodity_home. 68 | 69 | Like the cashflow report, ascertaining net worth is very easy in `ledger`. 70 | The query is simply `Assets Liabilities`. 71 | See @lst:example_networth for the expected output of `ledger -f ex.ledger bal Assets Liabilities`. 72 | 73 | Listing: A net worth report generated by `ledger -f ex.ledger bal Assets Liabilities`. {#lst:example_networth} 74 | 75 | ```{pipe="sh"} 76 | ledger -f ex.ledger bal Assets Liabilities 77 | ``` 78 | 79 | ## Querying Payees {#sec:querying_payees} 80 | 81 | When you want to find all of the transactions for a certain payee, `ledger` has 82 | a special query keyword for it: `payee`. 83 | Using `payee` is common way to find transactions with a business where you 84 | might have used different methods of payment, like cash on one visit but 85 | a debit or credit card the next. @Lst:payee_query shows this. 86 | 87 | Listing: A register of Commonplace Coffee purchases `ledger -f ex.ledger reg payee "Commonplace Coffee"` {#lst:payee_query} 88 | 89 | ```{pipe="sh"} 90 | ledger -f ex.ledger reg payee "Commonplace Coffee" 91 | ``` 92 | ::: tryit 93 | 94 | **TRY IT:** Try finding the other payees through a payee query. 95 | 96 | ::: 97 | 98 | ## Querying Tagged Transactions {#sec:querying_tagged_transactions} 99 | 100 | Tagged transactions are a powerful way to set simple key-value pairs of 101 | metadata associated with a transaction. For example, you could track the 102 | contact information of payees or maybe a warranty expiration date for something 103 | you purchased. 104 | 105 | Listing: A transaction with a tag comment (`tx_tags.ledger`) {#lst:tagged_tx} 106 | 107 | ```{.ledger pipe="ledger -f - print | tee tx_tags.ledger"} 108 | 2020-06-29 Costco 109 | ; warranty_expiration: 2023-06-29 110 | Expenses:Electronics:Computer 1,200.00 USD 111 | Expenses:Groceries 200.00 USD 112 | Liabilities:CreditCard:Visa 113 | 114 | 2020-07-01 Costco 115 | Expenses:Groceries 100.00 USD 116 | Liabilities:CreditCard:Visa 117 | 118 | ```` 119 | 120 | Using the `register` and `print` commands, we can see just the transactions 121 | that have a tag (@lst:warranty_tag) or match based on tag value 122 | (@lst:warranty_tag_value). 123 | 124 | Listing: A `register` of transactions that have the `warranty_expiration` tag with 125 | `ledger -f tx_tags.ledger reg tag warranty_expiration` {#lst:warranty_tag} 126 | 127 | ```{pipe="sh"} 128 | ledger -f tx_tags.ledger reg tag warranty_expiration 129 | ``` 130 | 131 | Listing: A `print` of transactions that have the `warranty_expiration` tag with 132 | `ledger -f tx_tags.ledger print tag warranty_expiration=2023-06-29` {#lst:warranty_tag_value} 133 | 134 | ```{pipe="sh"} 135 | ledger -f tx_tags.ledger print tag warranty_expiration=2023-06-29 136 | ``` 137 | 138 | ## Limiting by Date 139 | 140 | For the last set of basic reports, it's important to learn limiting by date: 141 | setting a beginning of the report, an end, and how to group within a specified 142 | time period. 143 | For instance, you might want to know how much you spent on coffee for June 144 | and July 2017. 145 | 146 | `--begin` and `--end` take a date-like string, such as `2017/06/30` or `June 2017`, 147 | or a relative date such as `last July`. `--begin` is inclusive and `--end` is 148 | exclusive. 149 | That is, if you specify `2020/07/24` as the end date, transactions dated 150 | `2020/07/23` will be included but not those dated `2020/07/24`. 151 | 152 | `ledger` supports a few grouping shortcuts for time periods: `--daily`, 153 | `--weekly`, `--monthly`, `--quarterly`, and `--yearly`. 154 | These are useful for showing the register for these time periods in order 155 | to see changes within that time period. 156 | 157 | Listing: A date-limited, monthly query: `ledger -f ex.ledger --begin 'June 2017' --end 'August 2017' --monthly reg 'Coffee$'` {#lst:basic_date_query} 158 | 159 | ```{pipe="sh"} 160 | ledger -f ex.ledger --begin 'June 2017' --end 'August 2017' --monthly reg 'Coffee$' 161 | ``` 162 | 163 | One more thing: notice the `$` in the query. `ledger` supports regular 164 | expressions (regex) for queries. In fact, it uses it by default. 165 | If you're familiar with regex, you'll understand this expression. 166 | Searching `Coffee` is actually matching accounts `.*Coffee.*` but the inclusion 167 | of another regex marker, such as the `$` which indicates to only look at the 168 | end of a line or `^`, which only matches from the beginning, changes that 169 | default. 170 | 171 | ::: tryit 172 | 173 | **TRY IT:** create a transaction that includes the word "Assets" in a posting's 174 | account, but not at the beginning. Try "Expenses:Assets". Now, try the cashflow 175 | report as described in @sec:cashflow. What happened? What can you do to fix 176 | the report so that it will work with this account name in your records? 177 | 178 | ::: 179 | 180 | ::: protip 181 | 182 | **PROTIP:** Avoid using the top-level accounts anywhere in a subaccount. 183 | That is, the words `Assets`, `Expenses`, `Income`, `Liabilities`, and `Equity` 184 | should only ever appear at the _beginning_ of account names. 185 | If you don't do this, you will _have to_ use `^` in your queries involving 186 | these top-level accounts. It's not a bad idea to do that anyway! 187 | 188 | ::: 189 | 190 | ## Sorting 191 | 192 | Nearly all queries can be sorted in some way. 193 | Sorting `print` or `register` with `--sort d` will sort by date. 194 | Sorting `balance` with `--sort T` sorts by the absolute value of the total for 195 | each account. 196 | 197 | `ledger`'s `--sort` option actually takes what's called a _VEXPR_, or a value 198 | expression. The [`ledger` docs on value expressions](https://www.ledger-cli.org/3.0/doc/ledger3.html#Value-Expressions) are quite in-depth, but @tbl:common_sorts contains 199 | some of the most common ones. 200 | 201 | Table: Common value expression variables used for simple sorting keys {#tbl:common_sorts} 202 | 203 | |Variable |Purpose | 204 | |:--------:|:-----------------------------------------------------------| 205 | |`d`|A posting’s date, as the number of seconds past the epoch. This is always “today” for an account. 206 | |`a`| The posting’s amount; the balance of an account, without considering children. | 207 | |`v`| The market value of a posting or an account, without its children. | 208 | |`X`| '1' if a posting’s transaction has been cleared, '0' otherwise. | 209 | 210 | 211 | -------------------------------------------------------------------------------- /0250_basic_automation.md: -------------------------------------------------------------------------------- 1 | # Quick and Easy Report Automation {#sec:automation} 2 | 3 | It's pretty clear from the previous few chapters that constantly typing out 4 | each `ledger` command would become laborious quickly. 5 | If you're working through this document, you are probably comfortable 6 | or will soon find yourself comfortable with some light programming, so you 7 | might be tempted to _script_ these executions in something like 8 | @lst:simple_script. 9 | 10 | Listing: A simple `ledger` script (`simple_script.sh`) {#lst:simple_script} 11 | 12 | ```{.bash pipe="tee simple_script.sh" .numberLines} 13 | #!/usr/bin/env bash 14 | TX_RECORD="${1}" # pass the transaction record in as the first arg 15 | LEDGER_BIN="$(command -v ledger)" # find the ledger command 16 | 17 | # Guard against a missing ledger binary 18 | if [[ -z "${LEDGER_BIN}" ]]; then 19 | echo "ledger isn't found in your PATH, please install it." 20 | exit 1 21 | fi 22 | 23 | # Assemble a convenient way to constantly invoke the program 24 | FILE_ARG="-f ${TX_RECORD}" 25 | LEDGER="${LEDGER_BIN} ${FILE_ARG}" 26 | 27 | echo "Net worth:" 28 | ${LEDGER} balance ^Assets ^Liabilities 29 | 30 | YEAR="${2:-$(date +%Y)}" 31 | echo "Cashflow for ${YEAR}:" 32 | ${LEDGER} --begin "${YEAR}" --end "$(( ++YEAR ))" balance ^Income ^Expenses 33 | ``` 34 | 35 | As @lst:simple_script_exec shows, this is a pretty cool first attempt. 36 | 37 | Listing: The simple script from @lst:simple_script, invoked with `bash simple_script.sh ex.ledger 2017` {#lst:simple_script_exec} 38 | 39 | ```{pipe="sh"} 40 | bash simple_script.sh ex.ledger 2017 41 | ``` 42 | 43 | @Lst:simple_script has a simple way to ensure that the `ledger` command 44 | exists on lines 2-9. 45 | It takes the transaction record as the first parameter on line 1. 46 | It builds a convenience variable on lines 12-13 to make executing `ledger` easier. 47 | It executes `ledger` to produce a net worth report (@sec:networth) on line 16. 48 | It takes an optional second parameter on line 18 for the year to use in the 49 | cash flow report (@sec:cashflow) that it executes on line 20. 50 | 51 | This is a good start and a rational way to automate reporting. 52 | However, you can quickly take this to the next logical step by shifting your 53 | thinking from producing output in the console or a text file to treating 54 | `ledger` as a type of compiler or conversion tool that produces objects: 55 | reports. 56 | 57 | ## Thinking in Reports 58 | 59 | When using `ledger` and other plain text accounting tools, think in reports: 60 | the results of a query of data presented in a particular format. 61 | Each report has meaning and reusability. 62 | So far, you've read about four different kinds of reports that are the most common for basic personal finance: 63 | 64 | 1. A generalized or account-scoped balance report 65 | 2. A generalized or account-scoped register report 66 | 3. A paired short-term balance report: cash flow (@sec:cashflow) 67 | 4. A paired long-term balance report: net worth (@sec:networth) 68 | 69 | Each of these is comprised of a single query and a single output. 70 | If we put the output of each of these queries into a file, we 71 | can use and reuse these files to create a really cool unified report 72 | comprised of these and other reports. 73 | 74 | -------------------------------------------------------------------------------- /0251_make_intro.md: -------------------------------------------------------------------------------- 1 | ## A Brief Introduction to `make` {#sec:make_intro} 2 | 3 | `make` is a file-oriented build automation tool. 4 | A `Makefile` describes the steps necessary to create a file in the form of 5 | a _task_. 6 | A task declares an output and several inputs, then defines the steps necessary 7 | to create the output file. 8 | While tasks are generally files, tasks can also be _phony_, in that 9 | they don't actually create the file named in the task. 10 | `make` figures out what files need to be created to accomplish the primary task 11 | and executes all of the other tasks necessary to create those files. 12 | 13 | @Lst:example_makefile shows a basic `Makefile` used to compile a C program. 14 | Programs written in C are generally comprised of several C source code files. 15 | Each C source file, e.g. `code.c`, is compiled into an object code file, e.g. `code.o`. 16 | This happens for all of the files: `main.c`, `net.c`, and `cli.c` are built 17 | into `main.o`, `net.o`, and `cli.c`. 18 | Once all of the object code files are ready, another step essentially 19 | _combines_ these files into an executable binary, e.g. `hello` or `hello.exe`. 20 | Note that this is a simple explanation and more elaborate setups are possible. 21 | If you want to try this out and have a C compiler already installed – 22 | try `gcc -v` – then write the contents of @lst:helloworld_c to `hello.c` and 23 | run `make` after writing the contents of @lst:example_makefile to `Makefile`. 24 | 25 | Listing: A basic `Makefile` used to compile a program written in C (`Makefile.helloworld`) {#lst:example_makefile} 26 | 27 | ```{.makefile pipe="tee Makefile.helloworld"} 28 | # get all of the C files in the directory 29 | C_FILES := $(wildcard *.c) 30 | # replace the 'c' with 'o', convention for compiled C object code 31 | OBJECTS := $(patsubst %.c, %.o, $(C_FILES)) 32 | # the final binary file name 33 | BINARY := hello 34 | 35 | # convenient way to run, and the first task defined is the executed when 36 | # running make without a specified task 37 | all: $(BINARY) 38 | 39 | # to produce $(BINARY), we need all $(OBJECTS) to be built 40 | $(BINARY): $(OBJECTS) 41 | # $@ means "the output file" 42 | # $^ means "all of the input files" 43 | $(CC) $(LDFLAGS) -o $@ $^ 44 | 45 | # to produce any file ending in '.o', we need a file that has the same 46 | # basename but ends with '.c'. These files already exist, so make tracks 47 | # when they've been altered and knows that it only needs to run this for 48 | # altered files. 49 | %.o: %.c 50 | # $< means "just the first input file" 51 | $(CC) $(CFLAGS) -c -o $@ $< 52 | 53 | # phony explicitly declares that this task will never create a file. 54 | .PHONY: clean 55 | clean: 56 | rm -rf $(BINARY) $(OBJECTS) 57 | ``` 58 | 59 | `make` is one of the older tools still in active use today. 60 | Designed by Stuart Feldman in 1976, the three common derivatives – BSD make, 61 | GNU make, and Microsoft nmake (@wikipedia:make). 62 | It's important to know what version of `make` you are using because there are 63 | subtle differences between these derivatives. 64 | If you are using Linux, you are likely using GNU make. 65 | If you are using macOS or a BSD, you are likely using BSD make by default, but 66 | can install GNU make easily. 67 | If you are using Windows, you likely have nmake available to you if you have 68 | installed Visual Studio or you can install GNU make through scoop. 69 | You probably installed GNU make in the process of installing the dependencies 70 | of this workshop in @sec:dependencies. 71 | Run `make -v` to see the version, and ensure that it's the same derivative, 72 | but not necessarily the same _version number_ what's shown in @lst:versions. 73 | If it's not, you'll _probably_ be OK as this author tries to write portable 74 | Makefiles [^portability], but you've been warned. 75 | 76 | [^portability]: Derivatives of `make` have different ways of handling tabs 77 | versus spaces and provide some different built-in functions. 78 | GNU make tends to be the most restrictive yet most commonly available, 79 | thus this author's preference for it in general. 80 | -------------------------------------------------------------------------------- /0252_make_for_ledger.md: -------------------------------------------------------------------------------- 1 | ## Setting Up a `Makefile` for use with `ledger` {#sec:make_for_ledger} 2 | 3 | In this section, you will read prose and in-line comments to learn about `make` 4 | and `ledger` at the same time, and maybe some other helpful utilities along the 5 | way. 6 | 7 | Note that this `Makefile` you are building relies on following a hierarchy of 8 | accounts matching what this author uses for his personal finances. This 9 | `Makefile` is nearly identical to what he uses. It has been modified only 10 | for clarity and actual useful improvements that he should have done long ago! 11 | 12 | ### Using Make Instead of a Script 13 | 14 | Revisit the simple script in @lst:simple_script. 15 | This approach is fine, but for benefits we'll _really_ see in @sec:parallelism, 16 | you must to implement some helpful tasks in in a `Makefile` instead of a 17 | simple shell script. 18 | 19 | A convenient way pass commands into `make` when running it is to use 20 | environment variables specified at invocation. 21 | For example, 22 | `make report YEAR=2019` would run the `report` task after setting the `YEAR` 23 | variable during startup. You'll use this mechanism to override some default 24 | values that you'll specify at the top of the `Makefile`. 25 | 26 | Listing: Some base variables for `Makefile` (`Makefile.01.vars.txt`) {#lst:makefile_variables} 27 | 28 | ```{.makefile pipe="tee Makefile.01.vars.txt" .numberLines} 29 | # The year for reports tied to a year, defaults to the current year. 30 | # Unlike shell scripting, where this would be $(date) or `date`, 31 | # Makefile provides a special function $(shell cmd) to run a command. 32 | YEAR = $(shell date +%Y) 33 | 34 | # The path to the ledger binary. In general, it'll be on your $PATH 35 | # but if you want to use a different ledger binary or perhaps use it 36 | # provided within a Docker container, you'll need a way to override it. 37 | LEDGER_BIN = ledger 38 | 39 | # It's a common practice to put all transactions into one file per year 40 | # and use the `equity` command from the previous year to start off the 41 | # new year file 42 | LEDGER_FILE = $(YEAR).ledger 43 | 44 | # This combined variable provides a convenient way to execute ledger 45 | # with the file already populated, plus a convenient way to inject 46 | # eXtra arguments into the command as a one-off. This is a great way 47 | # to test new commands. 48 | LEDGER = $(LEDGER_BIN) --file $(LEDGER_FILE) $(X) 49 | 50 | ``` 51 | 52 | The `Makefile` variables created in @lst:makefile_variables are a great 53 | starting point. 54 | You will inenvitably forget the meaning and utility of some of the tasks 55 | you're about to create, so let's use a fantastic way of documenting the 56 | `Makefile`: creating a "doc comment" that a task can parse out of the 57 | file and display. While you're at it, create another task that will help you 58 | open the year's file without having to remember – or think about – what year it is. 59 | The 2020s have been a long decade for all of us! 60 | 61 | Listing: Helpful tasks for your `Makefile` (`Makefile.02.help.txt`) {#lst:makefile_help} 62 | 63 | ```{.makefile pipe="tee Makefile.02.help.txt" .numberLines} 64 | ### Help Tasks 65 | 66 | help: ## Prints help for targets with comments 67 | @grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ 68 | sort | \ 69 | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 70 | 71 | edit: $(LEDGER_FILE) ## opens the transaction log for the year in your text editor 72 | $(EDITOR) $(LEDGER_FILE) 73 | 74 | ### end help tasks 75 | 76 | ``` 77 | 78 | Next, you'll add some basic tasks that generate some reports for display in the 79 | terminal. 80 | Terminal-focused reports are great for quick checks or general use. 81 | Some plain text accounting practitioners never really go past this or skip 82 | directly to a graphical tool like you will learn about in @sec:fava. 83 | Add the contents of @lst:makefile_terminal to your `Makefile`. 84 | 85 | Listing: Basic business tasks for viewing common reports (`Makefile.03.terminal.txt`) {#lst:makefile_terminal} 86 | 87 | ```{.makefile pipe="tee Makefile.03.terminal.txt" .numberLines} 88 | ### Terminal Viewing Tasks 89 | 90 | bal: $(LEDGER_FILE) ## show all balances 91 | $(LEDGER) balance 92 | 93 | networth: $(LEDGER_FILE) ## show short net worth report 94 | $(LEDGER) --depth=2 balance ^Assets ^Liabilities 95 | 96 | networth-all: $(LEDGER_FILE) ## show complete net worth report 97 | $(LEDGER) balance ^Assets ^Liabilities 98 | 99 | cashflow: $(LEDGER_FILE) ## show cashflow report 100 | $(LEDGER) balance ^Income ^Expenses 101 | 102 | expenses: $(LEDGER_FILE) ## show non-paycheck expenses (no taxes or health insurance) 103 | $(LEDGER) balance ^Expenses and not ^Expenses:Taxes and not ^Expenses:Insurance 104 | 105 | checklist: $(LEDGER_FILE) ## show a list used to check accounts 106 | $(LEDGER) accounts ^Assets:Cash ^Liabilities 107 | 108 | raw: $(LEDGER_FILE) ## run a query with make raw Q="bal" or drops into console mode 109 | $(LEDGER) $(Q) 110 | 111 | cash: $(LEDGER_FILE) ## show only cash assets 112 | $(LEDGER) balance ^Assets:Cash 113 | 114 | investments: $(LEDGER_FILE) ## show only investments 115 | $(LEDGER) balance ^Assets:Investments 116 | 117 | reimbursements: $(LEDGER_FILE) ## show only reimbursements 118 | $(LEDGER) balance ^Assets:Reimbursements 119 | 120 | 121 | ### end terminal viewing tasks 122 | 123 | ``` 124 | 125 | Now that you've got a lot of tasks in your `Makefile`, run `make help` to see 126 | the help text associated with each tasks or 127 | run `make -f Makefile.help` if you're using the supplementary files from @sec:artifacts. 128 | It will look like @lst:makefile_output_help. 129 | Notice that the doc comment, which has two octothorpes [^octothorpe] on the same line 130 | as the task declaration, becomes the help text. 131 | 132 | ::: protip 133 | 134 | **PROTIP:** Like `ledger`, `make` supports using `-f` or `--file` to specify a file to use 135 | instead of its default `Makefile`. It is convention to use `Makefile` and only 136 | use `Makefile.something` when `something` is some kind of rarely used alternate 137 | mode or it's automatically included in `Makefile` as a collection of subtasks. 138 | This workshop provides a series of Makefiles at various stages of completion. 139 | 140 | ::: 141 | 142 | Listing: The output of `make help` so far (`Makefile.help`) {#lst:makefile_output_help} 143 | 144 | ```{pipe="bash" .numberLines} 145 | cat Makefile.*.txt > Makefile.help 146 | make -f Makefile.help help | aha | pandoc -f html -t plain 147 | ``` 148 | 149 | **PROTIP:** Always remember to leave helpful comments in your code: you are helping the 150 | next person to read the code or documentation understand what you meant, and 151 | it's more than likely that the next person will be yourself. 152 | 153 | [^octothorpe]: Really, that's its real name, but it's commonly called a 154 | "hash" or "hashtag" or "pound" or "number". Musicians and Microsoft 155 | programmers call it a "sharp". 156 | 157 | Let's examine some of these tasks in @lst:makefile_terminal beyond their help text. 158 | 159 | Lines 3-4's task is the basic `balance` report: "show me all balances for all data". 160 | Lines 6-10 provide you with net worth, but a limited view on the `networth` task 161 | because listing all assets might get long if you have a lot of `Assets:Cash` 162 | subaccount accounts (`Assets:Cash:Banks`, perhaps a checking and a savings 163 | account for each bank; `Assets:Cash:Online` for things like PayPal, Venmo, 164 | Square Cash App, and so on.) or a lot of `Assets:Investments` accounts such as 165 | employer 401(k)s, IRAs, and other things that you may not care about at a finer 166 | detail than "all investments" most of the time when looking at your net worth 167 | statement. 168 | 169 | Lines 15-16 provide a way to see only expenses and only those that are not 170 | normal paycheck deductions such as income taxes and insurances. 171 | This report is useful to see expenses you actively control, be they automatic 172 | bill payments or normal daily transactions. 173 | It's important to separate these because for those with a savings mentality, 174 | taxes may be a significant portion of yearly expenses. 175 | 176 | Lines 18-19 help this author know what accounts need to be checked regularly. 177 | Note the usage of the `accounts` report. 178 | Lines 21-22 provide the ability to run a raw query by simply passing whatever 179 | is in the `Q` environment variable to `ledger`. 180 | For example, `make raw Q="balance ^Assets:Cash"` would execute the same report 181 | as what's on lines 24-25. 182 | The remaining tasks in @lst:makefile_terminal are shortcuts for investments 183 | balances and `Assets:Reimbursements`, a great account to track loans made to 184 | friends or to your employer if you covered an expenses on a personal credit 185 | card instead of a company credit card [^churning]. 186 | 187 | [^churning]: There's a whole practice to this called "churning" that not only 188 | involves charging company expenses on a personal card but also finding ways 189 | to take advantage of rewards programs to earn significant travel perks for 190 | personal use. If you will ever travel for work, this is a cool thing to look 191 | into in order to be able to spend some vacation time somewhere you want to 192 | go. 193 | 194 | Finally, your `Makefile` should look like @lst:makefile_after_script. 195 | Note that the top-level comments were removed in order to fit the entirety of 196 | the listing on one page. You can leave them in! 197 | 198 | Listing: The finalized basic `Makefile` for a `ledger` project (`Makefile.basic`) {#lst:makefile_after_script} 199 | 200 | ```{.makefile pipe="cat Makefile.*.txt | grep -v '^# ' | tee Makefile.basic" .numberLines} 201 | ``` 202 | 203 | #### Experimentation 204 | 205 | Spend some time using the `Makefile` that you've created. 206 | Copy `ex.ledger` to `2020.ledger`. 207 | Try each of the tasks to get a feel for the output of each. 208 | If you need to, add some transactions in order to generate data. 209 | 210 | ### Report File Generation 211 | 212 | Using the terminal to view reports might sit fine with you, but eventually, 213 | inevitably, you will want to produce more than a text experience for yourself. 214 | You may want to share your findings with others who prefer explanations, 215 | graphs, and more. 216 | In this section, you'll learn how to use ledger to produce files that you can 217 | use to build nice-looking reports. 218 | 219 | #### Creating Graphs with GNUplot {#sec:gnuplot} 220 | 221 | In order to create some graphs from your records, we need some scripts to help 222 | manipulate the data. Write the contents of @lst:last_entry and @lst:plotsh to 223 | the file specified in the listing caption. Read them as you do it so you can 224 | understand what they are doing. 225 | 226 | ```{pipe="sh"} 227 | mkdir scripts 228 | ``` 229 | Listing: `scripts/last-entry.sh`, which makes `ledger -j` output easier to graph {#lst:last_entry} 230 | 231 | ```{.bash pipe="tee scripts/last-entry.sh" .numberLines} 232 | #!/bin/sh 233 | # set is a great way to write safer shell scripts 234 | # -e = exit when a command fails; default is continue! 235 | # -o pipefail = use the exit status of the last executed 236 | # command in a pipe chain 237 | # -u = use of unset variables is an error 238 | set -euo pipefail 239 | # tac reads a file line by line from the end to beginning 240 | tac | \ 241 | # reverse the order of the data in the lines 242 | awk '{print $2 " " $1}' | \ 243 | # remove duplicates 244 | uniq -f 1 | \ 245 | # reverse back to normal 246 | awk '{print $2 " " $1}' | \ 247 | tac 248 | 249 | ``` 250 | 251 | Listing: `scripts/plot.sh`, which runs `gnuplot` on the data provided {#lst:plotsh} 252 | 253 | ```{.bash pipe="tee scripts/plot.sh" .numberLines} 254 | #!/usr/bin/env bash 255 | set -euo pipefail 256 | 257 | filename="${1}" 258 | 259 | case "${2}" in 260 | checking ) title="Checking balances" ;; 261 | networth ) title="Net worth" ;; 262 | * ) title="${2}" 263 | esac 264 | 265 | # gnuplot provides a DSL for graphing 266 | # here, we're saying to output a PNG image of time-series data 267 | # in a given format with a title specified and use columns 1 and 2 268 | # for the data points in X and Y series on the plot 269 | (cat < $@ 293 | 294 | LAST_ENTRY_SCRIPT = scripts/last-entry.sh 295 | 296 | $(REPORTS_DIR)/checking.balances: $(LEDGER_FILE) 297 | $(LEDGER) register --daily --total-data ^Assets:Cash:Bank:Checking | \ 298 | sh $(LAST_ENTRY_SCRIPT) > $@ 299 | 300 | $(REPORTS_DIR)/networth.balances: $(LEDGER_FILE) 301 | $(LEDGER) register --daily --total-data ^Assets ^Liabilities | \ 302 | sh $(LAST_ENTRY_SCRIPT) > $@ 303 | 304 | $(REPORTS_DIR): 305 | mkdir -p reports 306 | ``` 307 | 308 | There's a lot going on in @lst:makefile_graphs, so let's break it down. 309 | 310 | Lines 6-7 are producing PNG images from `.balances` files using 311 | `scripts/plot.sh`. The `$(*F)` is a special variable that captures what the 312 | text represented by `%` is for the particular invocation. 313 | 314 | Lines 11-17 are producing the `.balances` files, which are space-separated 315 | pairs of date and amount produced by the `ledger` command on lines 12 and 16. 316 | This `ledger` command is producing a daily register report, but outputting 317 | formatted with the date and the _total_ column – the rightmost column – to 318 | provide a running total over time. `ledger` will output multiples of the same 319 | day when there are entries on the same day, so you must manually deduplicate 320 | entries: only the last entry of the period is the correct one. 321 | If you wanted to see the amount added each day instead of a running total, 322 | you would use `--amount-data` instead. 323 | This might be useful to identify spikes in particular daily expenses instead of 324 | a running total of how much you've spent on that expense. 325 | 326 | Listing: The output of `make graphs` against `ex.ledger` renamed `2020.ledger` or using `LEDGER_FILE=ex.ledger` {#lst:makefile_graphs_run} 327 | 328 | ```{.bash pipe="bash" .numberLines} 329 | cat Makefile.*.txt > Makefile.graphs 330 | make -j 2 -f Makefile.graphs graphs LEDGER_FILE=ex.ledger 331 | mkdir -p root/build/reports 332 | cp reports/checking.png root/build/reports/checking.png 333 | cp reports/networth.png root/build/reports/networth.png 334 | ``` 335 | 336 | The graphs will look something like those in @fig:checking_balances_graph and 337 | @fig:networth_balances_graph. 338 | 339 | ![Checking balances in `ex.ledger` (`reports/checking.png`)](build/reports/checking.png){#fig:checking_balances_graph height=2in} 340 | 341 | ![Net worth balances in `ex.ledger` (`reports/networth.png`)](build/reports/networth.png){#fig:networth_balances_graph height=2in} 342 | 343 | #### Experimentation 344 | 345 | Using the tasks in @lst:makefile_graphs as a guide, create some of your own graphs. 346 | 347 | #### Parallelism {#sec:parallelism} 348 | 349 | One of the most powerful features of `make` is its ability to build an internal 350 | [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) 351 | of tasks to be executed and then execute those tasks in parallel where 352 | possible. 353 | This parallel execution is particularly useful when the production of a single 354 | build artifact – a program – is actually the result of combining several 355 | smaller products. 356 | 357 | This sounds familiar, no? It is the same as what you are doing in building 358 | reports with your `ledger` transaction log. 359 | For one business for which this author handles finances, the difference between 360 | running `make statement`, which runs all reports and builds the final document 361 | serially, and `make -j 8 statement`, which runs reports in parallel before 362 | building the final document, is dozens of seconds. 363 | As the transaction log grows over time, the savings will only get bigger! 364 | 365 | ::: tryit 366 | 367 | **TRY IT:** try running `time make clean graphs` and then try running `time 368 | make -j 4 clean graphs`. Which is faster? Almost assuredly the latter will be 369 | faster, potentially up to twice as fast! 370 | 371 | That paralellism is why programmers love having multiple CPU cores available. 372 | The more cores, the more tasks can be run simultaneously. 373 | 374 | ::: 375 | 376 | ## Building an HTML report with `pandoc` 377 | 378 | `pandoc` is a great tool for converting documents. 379 | This workshop was produced using it! 380 | It is able to convert dozens of document formats into dozens of other 381 | formats. 382 | Notably, it's able to read [Markdown](https://commonmark.org/) and 383 | produce [HTML](https://en.wikipedia.org/wiki/HTML "The language websites are built in"). 384 | In this section, you'll add a few convenient `Makefile` tasks 385 | and use a `pandoc` filter written in Lua to create a simple HTML report. 386 | 387 | First, you'll need to write @lst:filter-pipe to a file `filter-pipe.lua` or 388 | retrieve it from the supplementary artifacts archive in @sec:artifacts. 389 | 390 | Listing: A simple `pandoc` filter to execute code in a code block (`filter-pipe.lua`) {#lst:filter-pipe} 391 | 392 | ```{.lua pipe="sh"} 393 | cat root/filter-pipe.lua | tee filter-pipe.lua 394 | ``` 395 | 396 | Next, you'll need some very basic `pandoc`-flavored Markdown to form a basis of 397 | the document. 398 | Write @lst:pandoc_markdown to `statement.md`. 399 | Note that the name of your `Makefile` may be different if you didn't just copy 400 | from the artifacts archive. 401 | 402 | Listing: A simple `pandoc` Markdown document with front matter metadata (`statement.md`) {#lst:pandoc_markdown} 403 | 404 | ```{.markdown pipe="tee statement.md"} 405 | --- 406 | title: "My financial statement" 407 | --- 408 | 409 | # My financial statement 410 | 411 | ## Cashflow 412 | 413 | ~~~pipe 414 | make -f Makefile.graphs cashflow LEDGER_FILE=ex.ledger 415 | ~~~ 416 | 417 | 418 | ## Net worth 419 | 420 | ~~~pipe 421 | make -f Makefile.graphs networth LEDGER_FILE=ex.ledger 422 | ~~~ 423 | 424 | ``` 425 | 426 | Next, process @lst:pandoc_markdown with 427 | `pandoc statement.md -o statement.html --lua-filter filter-pipe.lua` 428 | 429 | ```{pipe="sh"} 430 | pandoc statement.md -o statement.html --lua-filter filter-pipe.lua 431 | ``` 432 | 433 | Open up `statement.html` in your browser. 434 | You're on your way to a great web-based way to produce your own reports. 435 | 436 | Automate it a bit by adding @lst:pandoc_makefile to your `Makefile`. 437 | 438 | Listing: A `Makefile` task for generating your statement (`Makefile.07.pandoc.txt`) {#lst:pandoc_makefile} 439 | 440 | ```{.makefile pipe="tee Makefile.07.pandoc.txt"} 441 | PANDOC=pandoc 442 | STATEMENT_TEMPLATE=statement.md 443 | STATEMENT_OUTPUT=$(STATEMENT_TEMPLATE:%.md=%.html) 444 | PANDOC_LUA_FILTERS=filter-pipe.lua 445 | 446 | .PHONY: statement 447 | statement: $(STATEMENT_OUTPUT) 448 | 449 | $(STATEMENT_OUTPUT): $(STATEMENT_TEMPLATE) $(LEDGER_FILE) 450 | $(PANDOC) $< -o $@ $(addprefix --lua-filter, $(PANDOC_LUA_FILTERS)) 451 | 452 | ``` 453 | 454 | Run `make statement` to generate your statement with the content of 455 | @lst:pandoc_makefile in your `Makefile` or run it from the artifacts archive 456 | with `make -f Makefile.pandoc statement`. 457 | 458 | ```{pipe="sh"} 459 | cat Makefile.*.txt > Makefile.pandoc 460 | ``` 461 | 462 | ### Taking report generation to the next steps 463 | 464 | There exist a nearly endless variety of strategies for producing reports in 465 | this manner. 466 | One strategy is to use `make` to produce a series of text files. These text 467 | files can be pulled into a document using a simple filter like the Lua filter 468 | in @lst:filter-pipe or with a more elaborate filter such as `panpipe` or 469 | `pandoc-include-code`. 470 | The latter is available in the Homebrew repository and can be used to read in 471 | files, while `filter-pipe.lua` and `panpipe` can execute `make` tasks directly. 472 | Writing to a file will run faster because of `make` parallelism: multiple 473 | reports can be executed simultaneously as opposed to serial execution within 474 | a `pandoc` conversion process. 475 | 476 | ::: tryit 477 | 478 | **TRY IT:** Try building more elaborate reports by creating more `make` tasks 479 | that write files to disk. 480 | Read those files using `cat filename.txt` and the pipe class on a codeblock as 481 | shown in @lst:pandoc_markdown. 482 | 483 | Note: this could be an hour's task unto itself! You might want to skip this 484 | task during the workshop and come back to it if you have time or after the 485 | guided workshop is over. 486 | 487 | ::: 488 | -------------------------------------------------------------------------------- /0295_other_reports.md: -------------------------------------------------------------------------------- 1 | # Other Reports 2 | 3 | There are some other reports and command functions that are useful but outside 4 | the scope of this workshop. 5 | 6 | ## Special balance reports 7 | 8 | * `cleared` : Shows a special `balance` view that more visibly shows balances 9 | when there are transactions that have not yet cleared, e.g. a 10 | check is in the mail. 11 | * `equity`: Prints current account balances as a single transaction. 12 | Useful for summarizing previous years as an opening balances 13 | transaction on a fresh ledger. 14 | 15 | 16 | ## Keeping a Tidy Transaction Log 17 | 18 | * `print` : Prints all transactions nicely formatted with the minimal text 19 | necessary to represent the transaction. Useful for sorting 20 | transactions that were entered out of order. 21 | 22 | ## Exporting for Other Programs 23 | 24 | 25 | While the Plain Text Ecosystem has a lot of useful tools, 26 | a few other commands facilitate interacting with other programs. 27 | 28 | * `csv` : Outputs transactions in CSV format. 29 | * `xml` : Outputs transactions in XML format. 30 | -------------------------------------------------------------------------------- /0300_tracking_commodities.md: -------------------------------------------------------------------------------- 1 | # Tracking Multiple Currencies, Stocks, and Other Commodities {#sec:commodities} 2 | 3 | ## What is a Commodity? 4 | 5 | You were first introduced to commodities in @sec:tx_walkthrough. 6 | In that section, the `USD` suddenly introduced in the examples 7 | denoted the currency by creating a commodity implicitly. 8 | Remember that `ledger` tracks all currencies, such as 9 | the US Dollar ($ - USD), 10 | the Euro (€ - EUR), 11 | 12 | the Armenian dram (֏ - AMD), 13 | Bitcoin (₿ - BTC), or 14 | the Ethiopian birr (Br - ETB), 15 | and things like stocks, property, and more with a commodity. 16 | The commodities are simply label to distinguish balances in different systems; 17 | they note different amounts of a different object! 18 | 19 | 24 | 25 | ## Explicitly Defining Commodities in `ledger` 26 | 27 | It is a good practice to explicitly note your primary commodity at the top of 28 | your `ledger` transaction record when you may have multiple commodities. 29 | `ledger` defines some _directives_ that are used to declare things. 30 | One such is a `commodity` directive, as shown in @lst:commodity_directive. 31 | These directives allow you to define aliases between commodities and to 32 | designate the format to use when displaying an amount in that particular 33 | commodity. 34 | 35 | Listing: A couple of common commodity directives {#lst:commodity_directive} 36 | 37 | ```{.ledger} 38 | commodity USD 39 | note "US Dollar" 40 | alias $ 41 | format 1,000.00 42 | default 43 | 44 | commodity VTSAX 45 | note "Vanguard Total Stock Market Index Admiral Shares" 46 | format 1,000.0000 47 | 48 | commodity BTC 49 | note "Bitcoin" 50 | format 1,000.00000000 51 | 52 | commodity mBTC 53 | note "millibitcoin" 54 | format 1,000.0000 55 | 56 | ; denotes a commodity conversion 57 | C 1000.0000 mBTC = 1.000000 BTC 58 | ``` 59 | 60 | Note that these really come in handy when using `--strict` or `--pedantic` 61 | modes. 62 | In these modes, accounts, tags, and commodities not previously declared 63 | will be declared as warnings or errors, respectively (@ledger:docs). 64 | 65 | Commodities can also have a space in them, so you can track commodities without 66 | abbreviating them, as well. For example, when [Code & Supply Scholarship 67 | Fund](https://codeandsupply.fund) was donated tickets for [Abstractions 68 | II](https://abstractions.io) software conference, this author tracked those 69 | donations as inventory in the non-profit organization's `ledger` records as 70 | shown in @lst:abstractions_tix. 71 | 72 | Listing: Tracking inventory with `ledger` (`tickets.ledger`) {#lst:abstractions_tix} 73 | 74 | ```{.ledger pipe="ledger -f - print | tee tickets.ledger"} 75 | 2019-07-24 * Ticket Donation from Numerius Negidius 76 | Income:Donations:InKind -1 "Abstractions II Ticket" 77 | Assets:Tickets 78 | 79 | 2019-07-25 * Ticket Award to Aulua Agerius 80 | Assets:Tickets -1 "Abstractions II Ticket" 81 | Expenses:Program:Tickets 82 | ``` 83 | 84 | ## Recording Commodity Transactions {#sec:commodity_recording} 85 | 86 | Recording in multiple currencies is easy when you know precisely the 87 | exchanged amounts. @lst:simple_multicurrency and its balance report in 88 | @lst:simple_multicurrency_bal show this. 89 | 90 | Listing: A simple pair of multi-currency transactions (`multicurrency.ledger`) {#lst:simple_multicurrency} 91 | 92 | ```{.ledger pipe="ledger -f - print | tee multicurrency.ledger"} 93 | 2013-01-01 * Opening Balances 94 | Assets:Cash:Bank:Checking 300.00 USD 95 | Equity:OpeningBalances 96 | 97 | 2013-01-31 * Duty Free Currency Exchange 98 | Assets:Cash:Bank:Checking -50.00 USD 99 | Assets:Cash:Wallet 250.00 CRC ; Costa Rican Colónes 100 | 101 | 2013-02-01 * Plaintains 102 | Expenses:Groceries 2 CRC 103 | Assets:Cash:Wallet 104 | 105 | ``` 106 | 107 | Listing: Balance report on @lst:simple_multicurrency {#lst:simple_multicurrency_bal} 108 | 109 | ```{pipe="sh"} 110 | ledger -f multicurrency.ledger bal 111 | 112 | ``` 113 | 114 | Recording the purchase of stock or something else that works like stock, e.g. 115 | cryptocurrency, is best done by denoting the purchase price and any commissions 116 | or other purchase fees explicitly, then showing the debit in the amount paid. 117 | This is a great check on the math of the institution from which you purchased 118 | the commodity. 119 | In fact, this author has caught small math bugs and keeps a running account 120 | called `Equity:RoundingErrors` just to capture small differences in reported 121 | and actual math. 122 | 123 | Listing: Buying some Bitcoin and tracking with `ledger` (`bitcoin.ledger`) {#lst:bitcoin} 124 | 125 | ```{.ledger pipe="tee bitcoin.ledger" .numberLines} 126 | 2016-12-17 * Coinbase 127 | Assets:Cash:Banks:Checking -1000.00 USD 128 | Expenses:Fees:Banks:Coinbase 15.00 USD 129 | Assets:Cryptocurrency 1.255000892 BTC @ 784.86 USD 130 | 131 | 2017-12-17 * Coinbase 132 | Assets:Cryptocurrency -1.255000892 BTC {784.86 USD} @ 20089.00 USD 133 | Expenses:Fees:Banks:Coinbase 10.00 USD 134 | Income:CapitalGains -24236.71 USD 135 | Assets:Cash:Banks:Checking 25211.71 USD 136 | 137 | ``` 138 | 139 | Once again, there's a lot going on in @lst:bitcoin, so let's review it in 140 | detail. 141 | 142 | Line 4 is using a syntax that effectively says "record this purchase of 1.255… 143 | BTC for 985 USD" by doing the math for you. 144 | 145 | Line 7 is where a lot of magic is happening. You're selling the entire 1.255… 146 | BTC holding for 20,089 USD[^ath]. 147 | You're specifically calling out the _lot_ [^lots] purchased for 784.86 USD. 148 | You know how much money went into your bank account from the purchase, 149 | 25,211.71 USD, and `ledger` actually helps you figure out the capital gains 150 | if you leave the capital gains posting empty. It is filled in in this example 151 | to ensure that the amount is correct. 152 | 153 | [^lots]: Lots have an effect on taxation; an explanation of this is outside the scope of 154 | this workshop. 155 | 156 | [^ath]: Fun fact: this is the date and amount of Bitcoin's all-time high as of 157 | the writing of this document. 158 | 159 | ::: protip 160 | 161 | **PROTIP**: Another way to let `ledger` help you figure out complex transactions is to 162 | purposefully omit a posting that you know is necessary. Write the contents of 163 | @lst:bitcoin to a file `bitcoin.ledger`, remove line 9, 164 | and run `ledger -f bitcoin.ledger bal`. 165 | The output should look something like @lst:unbalanced_error. 166 | Then, append `Income:CapitalGains` to that last transaction and run the 167 | `ledger` command again. 168 | Notice how it worked and showed the same amount without having to calculate it 169 | manually or rely on a reported amount from the brokerage. 170 | Note that his method is exactly how this author knew how much to put in 171 | the posting in @lst:bitcoin! 172 | 173 | ::: 174 | 175 | Listing: An example of an unbalanced transaction error {#lst:unbalanced_error} 176 | 177 | ``` 178 | While parsing file "bitcoin.ledger", line 9: 179 | While balancing transaction from streamed input: 180 | Unbalanced remainder is: 181 | 24236.71 USD 182 | Amount to balance against: 183 | 25221.71 USD 184 | Error: Transaction does not balance 185 | 186 | ``` 187 | 188 | ## Tracking Stock Prices with a Prices Database 189 | 190 | `ledger` can use a specially formatted text database containing commodity 191 | pricing information to reflect changes in the value of commodities held. 192 | @lst:stock_purchase reflects the purchase of 87 "COLIN" for $87.00 each. 193 | Run `bal` on this transaction and you'll see a negative balance of $7,569 in 194 | the checking account. 195 | 196 | Listing: A basic stock purchase (`stock.ledger`) {#lst:stock_purchase} 197 | 198 | ```{.ledger pipe="tee stock.ledger"} 199 | commodity USD 200 | alias $ 201 | format 1,000.00 USD 202 | 203 | 2020-06-01 * Investment in Colin 204 | Assets:Investments:Brokerage 87 COLIN @ $87.00 205 | Assets:Cash:Banks:Checking 206 | ``` 207 | 208 | Let's create a price database in `colin_prices.db`. See @lst:colin_prices for some 209 | content. 210 | 211 | Listing: Weekly COLIN prices in June and July 2020 (`colin_prices.db`) {#lst:colin_prices} 212 | 213 | ```{.ledger pipe="tee colin_prices.db"} 214 | ; entry format: 215 | ; P [time] 216 | P 2020-06-01 COLIN 87 USD 217 | P 2020-06-08 COLIN 90 USD 218 | P 2020-06-15 COLIN 100 USD 219 | P 2020-06-22 COLIN 97 USD 220 | P 2020-06-29 COLIN 103 USD 221 | P 2020-07-06 COLIN 110 USD 222 | P 2020-07-13 COLIN 90 USD 223 | P 2020-07-20 COLIN 95 USD 224 | P 2020-07-24 COLIN 100 USD 225 | ``` 226 | 227 | Using this prices database, you can view a balance report showing your account 228 | balance taking into account this changing price. 229 | Run `ledger -f stock.ledger --prices-db colin_prices.db --market balance` to 230 | see the current price, shown in @lst:colin_balance. 231 | 232 | Listing: Current balances with latest COLIN pricing {#lst:colin_balance} 233 | 234 | ```{pipe="sh"} 235 | ledger -f stock.ledger --price-db colin_prices.db --market balance 236 | ``` 237 | 238 | ### Experimentation 239 | 240 | What happens when you add a new entry to the prices database? 241 | What happens to the price if you change the `--end` date? 242 | 243 | ## A Home as a Commodity {#sec:commodity_home} 244 | 245 | A commodity could be a house, condominium, or other ownable real property! 246 | It's all in how you represent it in your transaction log. 247 | 248 | Listing: Example transactions for a house (`house.ledger`) {#lst:house_transactions} 249 | 250 | ```{pipe="ledger -f - print | tee house.ledger"} 251 | commodity USD 252 | format 1,000.00 USD 253 | alias $ 254 | 255 | 2014/11/14 House Purchase 123 Main Street 256 | Assets:Cash:Bank:Checking -24,000.00 USD 257 | Expenses:House:ClosingCosts 4,000.00 USD 258 | Assets:RealEstate:123MainStreet 1 HOUSE @ 100,000.00 USD 259 | Liabilities:Bank:Mortgage -80,000.00 USD 260 | 261 | 2014/12/14 Mortgage Payment 262 | Assets:Cash:Bank:Checking -600.00 USD 263 | Expenses:Interest:Mortgage 150.00 USD 264 | Liabilities:Bank:Mortgage 450.00 USD 265 | 266 | 2015/01/14 Mortgage Payment 267 | Assets:Cash:Bank:Checking -600.00 USD 268 | Expenses:Interest:Mortgage 149.00 USD 269 | Liabilities:Bank:Mortgage 451.00 USD 270 | 271 | ``` 272 | 273 | With your mortgage payments tracked like this, your house is in your Assets 274 | and your mortgage is in Liabilities. Therefore, you're tracking them in your net worth, 275 | as recommended in @sec:networth. 276 | You can easily check your mortgage interest paid to verify your records against 277 | the IRS Form 1098-INT that your American bank issues yearly to support mortgage interest 278 | deductions in the United States. 279 | 280 | ::: tryit 281 | 282 | **TRY IT:** Write a balance query against the records in @lst:house_transactions 283 | that show 284 | 285 | 1. Mortgage interest paid in 2014 286 | 2. Mortgage interest paid since the start of the loan 287 | 3. The equity [^equity] in the house as of the latest transaction 288 | 289 | ::: 290 | 291 | [^equity]: Equity is the value of the property minus the mortgage, divided by the value of the property. 292 | 293 | You can track the value of your home in a prices database, too, and account for 294 | estimated fluctuations in its value or track appraisal prices as a base when 295 | refinancing or getting a home equity loan. 296 | 297 | -------------------------------------------------------------------------------- /0600_ledger-autosync.md: -------------------------------------------------------------------------------- 1 | # Importing Data from Your Financial Institutions 2 | 3 | ## Using `ledger-autosync` to convert bank records 4 | 5 | `ledger-autosync` is a fantastic tool that drastically expedites data entry by almost entirely automating it. 6 | 7 | ### Cleaning data {#sec:sync_clean} 8 | 9 | If you're lucky, your bank will provide an export in QFX or OFX format. 10 | `ledger-autosync` handles this excellently and needs little manual preparation. 11 | However, not all do – only four of this author's seven financial institutions 12 | do – so you will inevitably need to deal with CSV. 13 | 14 | First, you need to understand CSV, character- or comma-separated value format. If you've never encountered it, you're probably early in your journey in computer science and consuming data. 15 | CSV uses a character, generally a comma (`,`) to separate fields in a record of data, and another character, generally a newline, to separate the records. 16 | Fields are optionally contained within paired quotation marks (`"`) in order to 17 | allow commas and newlines to be "escaped" and exist within the field. 18 | Other common field separators are tabs, pipes (`|`), and the actual unit separator character (ASCII 31), while the record separator is almost always a newline but is sometimes the record separator character (ASCII 30). There are a variety of [delimiters](https://en.wikipedia.org/wiki/Delimiter) in CSV, but it's important to think in commas, quotation marks, and newlines most of the time. 19 | 20 | Despite being often created by CSV libraries, sometimes CSV isn't cleanly parseable. Again, if you don't already know this, you will learn in the process of maintaining your finances that CSV is the worst format ever and that you should avoid it at all costs, both as a consumer and a producer [^bidhigh]. 21 | 22 | [^bidhigh]: If someone ever asks you how much it will cost to ship CSV as a part of a product, bid high. 23 | 24 | For example, Simple, one of my banks, emits CSV that Python's CSV library cannot reliably automatically determine its delimiter. So I use a convenient tool called `xsv` to sort it (because it comes in reverse order) and then add quotation marks explicitly in `clean_simple.sh`, shown in @lst:cleancsv. 25 | 26 | Listing: `clean_simple.sh` {#lst:cleancsv} 27 | 28 | ```{.bash pipe="tee clean_simple.sh"} 29 | #!/usr/bin/env bash 30 | INPUT="$1" 31 | xsv sort --select Date "${INPUT}" | \ 32 | xsv fmt --quote-always > \ 33 | "$(basename -s .csv "${INPUT}")-sorted.csv" 34 | 35 | ``` 36 | 37 | ```{pipe="sh"} 38 | cp root/simple.csv simple.csv 39 | ``` 40 | 41 | ::: tryit 42 | 43 | **TRY IT:** In the supplementary artifacts provided in @sec:artifacts, there is 44 | a file `simple.csv`. 45 | Run @lst:cleancsv on it with `bash clean_simple.sh 46 | simple.csv` to fix the data so that `ledger-autosync` can read it. 47 | The output will be written to `simple-sorted.csv`. 48 | 49 | ::: 50 | 51 | 52 | ### Updating a transaction record with new data {#sec:autosync_updates} 53 | 54 | `ledger-autosync` uses a _transaction ID_ to uniquely identify transactions. 55 | When importing from QFX files, this transaction ID is provided by the exporting bank and _should_ always be trustworthy. 56 | When importing from CSV files, `ledger-autosync` must derive the transaction ID from the data if the definition of the format being read does not identify a transaction ID provided in the data. 57 | The derived transactions ID is a hash of all of the data in the row concatentated together. 58 | @Lst:csvid shows an example of how `ledger-autosync` does this. 59 | 60 | Listing: Python example showing how row IDs are generated {#lst:csvid} 61 | 62 | ```python 63 | from functools import reduce 64 | import hashlib 65 | # `row` is a list containing the columns from a single row 66 | smushed_row = reduce(lambda a,b: a + b, row).encode('utf-8') 67 | csv_id = hashlib.sha256(smushed_row).hexdigest() 68 | ``` 69 | 70 | Run `ledger-autosync` once on your clean, exported CSV from @sec:sync_clean to visually check the output using the command in @lst:export_show. 71 | 72 | Listing: `ledger-autosync` running on `simple-sorted.csv` {#lst:export_show} 73 | 74 | ```bash 75 | ledger-autosync \ 76 | -a "Assets:Cash:Banks:Simple:Checking" \ 77 | -l 2020.ledger \ 78 | --unknown-account "Equity:Unknown" \ 79 | simple-sorted.csv 80 | ``` 81 | 82 | Then, run it again, but this time _append_ to your existing transaction record using output redirection `>>`, as shown in @lst:export_append. 83 | 84 | Listing: Appending the output of `ledger-autosync` to a file {#lst:export_append} 85 | 86 | ```bash 87 | ledger-autosync \ 88 | -a "Assets:Cash:Banks:Simple:Checking" \ 89 | -l 2020.ledger \ 90 | --unknown-account "Equity:Unknown" \ 91 | simple-sorted.csv >> 2020.ledger 92 | ``` 93 | 94 | If you run @lst:export_append again, you'll notice that nothing else new was added! `ledger-autosync` successfully used the CSVID it generated to keep from adding transactions it already had converted. 95 | 96 | ## Rules to keep in mind for synchronizing 97 | 98 | 1. Synchronize your primary account first. 99 | 2. Synchronize transfer or payment IDs from subsequent accounts that have transfers or payments to or from that primary account _before_ mixing in the other accounts' full records. This ensures that you are tracking both records' IDs in order to prevent duplicate entries. You can _manually_ delete the transfer or payment transactions from your initial conversion against your primary ledger file or you can re-run `ledger-autosync` and it will ignore the IDs that are already present in the ledger file passed as `--ledger`. 100 | 3. Diligently use `; AutosyncPayee: XXX` tags in transactions where you modify the payee. This will enable `ledger-autosync` to find transactions and set the counter-transaction (the expense, generally) automatically. This will save you literally hours of data entry per update session! 101 | 4. Use your editor's autocomplete functionality. [`vim-ledger`](https://github.com/ledger/vim-ledger) lends itself to this with its `Ctrl-X Ctrl-O` autocompletion trigger while in insert mode: it'll look at all account names in the current file by doing `ledger -f - accounts` and build suggestions based on partial matches. `E:E:B` could expand to `Expenses:Electronics:Batteries`. Look how much time that saves in typing alone! 102 | 103 | ## Converting with `ledger` 104 | 105 | Importing with `ledger` is possible through its `convert` command. 106 | However, it takes a lot of massaging of data to use. 107 | It lends itself to scripting, but those scripts are not as easy to share as 108 | `ledger-autosync`'s plugins. This author has contributed some of the converters 109 | available in `ledger-autosync`. 110 | 111 | If you're interested in learning how to convert using only `ledger`, check out 112 | the [`convert` command section](https://www.ledger-cli.org/3.0/doc/ledger3.html#The-convert-command) in the `ledger` docs. 113 | This section is thorough and well-explained. 114 | 115 | ## Experimentation 116 | 117 | ::: tryit 118 | 119 | **TRY IT:** 120 | Retrieve exports from your financial institution perhaps from a bank like 121 | Simple. 122 | Try different ways of converting them automatically or try entering 123 | a few transactions by hand. 124 | 125 | ::: 126 | 127 | You may find that you need to implement a converter plugin for 128 | `ledger-autosync`. See the [Plugin Support](https://github.com/egh/ledger-autosync#plugin-support) documentation for how to write in Python a new `CsvConverter` subclass that the program can load and use. 129 | Use the [built-in converters](https://github.com/egh/ledger-autosync/blob/cc6d9f61420c69d08d5b7d6d529f28c5da20cf47/ledgerautosync/converter.py#L597-L929) as inspiration or ask the author of this workshop for advice – he's implemented many converters! 130 | 131 | ## The tedium: categorizing transactions using accounts in your account tree {#sec:categorizing} 132 | 133 | Now that you've got some data, you're onto the tedious task of categorizing transactions by replacing the `Equity:Unknown` account with something meaningful. If you need an idea of some accounts to use, see @sec:fin_acct_ref, which contains a list of accounts this author has used in some form. 134 | 135 | Don't worry too much about formatting. We'll use `ledger` itself to reformat and sort the transactions you're modifying. 136 | 137 | It might be helpful to have a little program showing you what transactions remain to be categorized. [`entr`](http://eradman.com/entrproject/) is a great little tool for watching files for changes and running a command when they change. @lst:entr shows a command that runs a `ledger` register report in wide mode on `2020.ledger` showing only the `Equity:Unknown` account postings whenever `2020.ledger` changes. 138 | 139 | Listing: Automatically run ledger whenever `2020.ledger` changes {#lst:entr} 140 | 141 | ```bash 142 | echo 2020.ledger | entr -acpr ledger -w -f /_ reg Equity:Unknown 143 | ``` 144 | 145 | ## Deduplicating inter-account transfers 146 | 147 | Inevitably, you'll have a transfer that touches two exports, for example a credit card payment that appears on both your bank statement and your credit card statement. 148 | @Lst:dedup-before shows what this can look like in your transaction record. 149 | You'll have to manually deduplicate them until [egh/ledger-autosync#101](https://github.com/egh/ledger-autosync/issues/101) is in and `ledger-autosync` is smarter about detecting possible transfers! 150 | 151 | Listing: Transaction record before deduplication {#lst:dedup-before} 152 | 153 | ```ledger 154 | 2019/03/28 AUTOPAY THANK YOU 155 | ; ofxid: 2102.XXXXXXXXXXXX2321.20190328090097 156 | Liabilities:CreditCard:Citi:Costco 742.82 USD = 0.00 USD 157 | Assets:Cash:Banks:Dollar:Checking 158 | 159 | 2019/03/29 ECK CITIBANK PAYMENT 160 | ; csvid: bc0c96d0c2aae6830cbfd20d8baf6c59 161 | Assets:Cash:Banks:Dollar:Checking -742.82 USD = 1,400.23 USD 162 | Liabilities:CreditCard:Citi:Costco 163 | ``` 164 | 165 | @Lst:dedup-after shows the appropriate way to combine them. 166 | 167 | Listing: Transaction record after deduplication {#lst:dedup-after} 168 | 169 | ```ledger 170 | 2019/03/28=2019/03/29 Citibank Costco Visa Payment 171 | ; csvid: bc0c96d0c2aae6830cbfd20d8baf6c59 172 | Assets:Cash:Banks:Dollar:Checking -742.82 USD = 1,400.23 USD 173 | ; ofxid: 2102.XXXXXXXXXXXX2321.20190328090097 174 | Liabilities:CreditCard:Citi:Costco 742.82 USD = 0.00 USD 175 | ``` 176 | 177 | Note the date initialized and date cleared notation. This can be helpful to know when the transaction was started and when it actually completed. `ledger` generally knows how best to handle this internally when building its model of record. 178 | 179 | Note that the comment is in tag format and is above the posting it annotates. Dollar Bank gives me CSV while Citi gives me OFX. If I were run an import again against either export file, `ledger-autosync` _should_ catch it and not duplicate the transaction. 180 | 181 | Balance assertions are "hard mode" for `ledger`. While they can really help you checkpoint, they can be difficult to work with because of how they are processed. BAs are processed in _transaction record appearance order_, not in date order. When sorting, the transaction date is used, not the clearing date. This means that a BA might be off after sorting. I use a mental rule that as long as a BA worked before sorting, it's _probably_ safe to comment it out after sorting if it's causing problems because of transaction date ordering. 182 | 183 | ## Protips for categorizing quickly and efficiently 184 | 185 | This is the most laborious part of tracking your finances in _any_ accounting system, so you really want to find ways to optimize your workflow to _incentivize_ you to do it. Manual entry is the most rewarding yet tedious way to do this, so automating typing – especially when it comes transaction _amounts_ – is imporant to reduce the error-prone parts so you can focus your manual time on the categorization that is sometimes difficult to automate. 186 | 187 | When trying to figure out how to categorize transactions from the same vendor, try to **remember patterns in your spending**. For example, when a transaction is from a gas station and it's under $10.00, it's probably coffeee, so I categorize it as `Expenses:Coffee` because I track that separate from my `Expenses:Restaurants` when my primary reason for the purchase is, uh, go-go bean juice. If the gas station charge is between $20 and $40, it's 95% likely refueling my car with a 10 gallon tank. If the charge is over that, it's probably for my big SUV with its 25 gallon tank. 188 | 189 | **Make progress** when categorizing. If you can't figure out what a transaction is from the cryptic information your bank gives you, mark it with something and move on, e.g. `Equity:Unknown` for bank transaction records or `Expenses:Stuff` for credit card records. These two things help me mark transactions for which I need to look at receipts or go back to the bank's activity records or statement to see if I can get more context about the purchase. 190 | 191 | **Use indicators in the cents of tipped charges** to help you remember what it was at a restaurant. For example, if you want to categorize a transaction as a restaurant charge, tip such that the total is something like $31.03, where 3¢ means it's `Expenses:Restaurant`. If you want to remind yourself that it's alcohol-only, make the total something like $31.04, where 4¢ means that it should be `Expenses:Alcohol`. You can even program an autoclassification tool to handle this for you at some step in your categorization workflow. 192 | 193 | ## Reformatting with a sort 194 | 195 | `ledger` contains a simple `print` command that writes transactions out in the most storage-efficient format possible. It can also use queries to limit what's printed or it can sort the output. Since our appended transactions may be out of order, let's use the opportunity to sort them by date, which is indicated by `d` in `ledger`'s format specifier syntax. 196 | 197 | It's a good idea to ensure that you've committed before doing this in case the sort messes up. @Lst:sorting shows how to safely sort. 198 | 199 | Listing: Sorting safely with ledger {#lst:sorting} 200 | 201 | ```bash 202 | ledger -f 2020.ledger --sort d print > 2020-s.ledger 203 | mv 2020-s.ledger 2020.ledger 204 | ledger -f 2020.ledger bal 205 | ``` 206 | 207 | If the sort worked and didn't alter your output, then commit again! 208 | 209 | We have to do file rename dance because `ledger` reads in a stream and outputs immediately, so we'd risk overwriting our log file! 210 | 211 | -------------------------------------------------------------------------------- /0700_fava.md: -------------------------------------------------------------------------------- 1 | # Visualizing Reports in Fava {#sec:fava} 2 | 3 | [Fava](https://beancount.github.io/fava/) is the best visualization tool for the Plaintext Accounting ecosystem as of this writing. 4 | Fava requires input in a specific variant of Ledger format used by Beancount, a Ledger-like that uses a more explicit syntax. 5 | Fortunately, we can convert Ledger to Beancount and be able to use Fava fully. 6 | 7 | ## Converting to Beancount {#sec:use_ledger2beancount} 8 | 9 | Having installed `ledger2beancount` using the instructions in @sec:install_ledger2beancount, you can run it after making your own configuration file. 10 | 11 | Listing: Running `ledger2beancount` {#lst:running_l2b} 12 | 13 | ```bash 14 | cp ledger2beancount.yml myl2b.yml 15 | # edit myl2b.yml in your text editor! 16 | bin/ledger2beancount --config myl2b.yml 2020.ledger > 2020.beancount 17 | ``` 18 | 19 | ## Running Fava 20 | 21 | This one's simple! 22 | 23 | Listing: Running `fava` {#lst:running_fava} 24 | 25 | ```bash 26 | fava 2020.beancount 27 | ``` 28 | 29 | Fava will run a webserver accessible via [`http://localhost:5000`](http://localhost:5000). You can explore around the Fava interface, notably two views: 30 | 31 | 1. Income Statement -> Net Profit. This is a cashflow analysis, Income minus Expenses. 32 | 2. Balance Sheet -> Net Worth. This is a net worth analysis, Assets minus Liabilities. 33 | 34 | You may see some _errors_ on the left sidebar. `ledger2beancount` can't handle 100% of all things, so sometimes Beancount still works while silently failing. 35 | For example, balance assertions can be ambiguous, so `ledger2beancount` converts in a way that Beancount will continue to work but not enforce the assertions. 36 | It's OK, because any assertions you've made still guard your record in your original transaction log. 37 | 38 | ## Integrating Beancount & Fava in your `Makefile` 39 | 40 | Move your `ledger2beancount` installation into `scripts/ledger2beancount` and 41 | its configuration file into `scripts`, as well. 42 | Then, you can append @lst:makefile_fava to your `Makefile`. 43 | With that, you'll be able to run `make fava` and know that the underlying 44 | beancount-format file will be updated if you've made changes to the `ledger` 45 | file from which it is produced. 46 | That is the magic of `make`! 47 | 48 | Listing: Additional `Makefile` tasks (`Makefile.09.fava.txt`) {#lst:makefile_fava} 49 | 50 | ```{.makefile pipe="tee Makefile.09.fava.txt"} 51 | ## beancount compat & fava 52 | 53 | L2B = scripts/ledger2beancount 54 | L2B_BIN = $(L2B)/bin/ledger2beancount 55 | L2B_CONFIG = scripts/ledger2beancount.yml 56 | 57 | BEANCOUNT_FILE=reports/$(LEDGER_FILE).beancount 58 | 59 | .PHONY: beancount 60 | beancount: $(BEANCOUNT_FILE) ## creates a beancount version of the ledger file 61 | 62 | fava: $(BEANCOUNT_FILE) ## runs fava on the beancount version of the ledger file 63 | fava $(<) 64 | 65 | $(BEANCOUNT_FILE): $(LEDGER_FILE) $(L2B_CONFIG) 66 | $(L2B_BIN) --config $(L2B_CONFIG) $< > $@ 67 | 68 | ``` 69 | -------------------------------------------------------------------------------- /0800_timetracking.md: -------------------------------------------------------------------------------- 1 | # Time Keeping with `ledger` {#sec:timekeeping} 2 | 3 | `ledger` has a built-in set of directives that enable tracking time spent doing 4 | activities. 5 | These directives create transactions under the hood in commodities for seconds, 6 | minutes, hours, and so on. 7 | It establishes commodity conversions that allow ledger to report meaningfully 8 | on time spent, regardless of the unit of time. 9 | 10 | This author was introduced to `ledger` not through personal finance, but 11 | through a search for a better way to track consulting hours. As such, he's 12 | tracked time for _years_ using `ledger`. This section briefly covers these 13 | practices and links to existing, production-quality tooling enabling time 14 | keeping complete with analysis tools. 15 | 16 | @Lst:timekeeping shows a day in the life of `ledger` time keeping through its 17 | timelog mode. Interacting with these time records is easy: you can use 18 | `balance` and `register` just as if these records were normal transactions. 19 | @Lst:timekeeping_bal shows the balance report. 20 | 21 | Listing: A simple timekeeping transaction record (`time.ledger`) {#lst:timekeeping} 22 | 23 | ```{.ledger pipe="tee time.ledger"} 24 | ; My convention: 25 | ; N: non-billable time | B: billable time | A: away time; PTO, holidays 26 | i 2020/07/03 09:30:00 N:Email 27 | o 2020/07/03 10:00:00 28 | i 2020/07/03 10:00:00 N:Development:Coding 29 | o 2020/07/03 11:45:00 30 | i 2020/07/03 11:45:00 N:Meeting:Standup 31 | o 2020/07/03 12:00:00 32 | i 2020/07/03 12:00:00 N:Development:Coding 33 | o 2020/07/03 12:45:00 34 | i 2020/07/03 12:45:00 N:Development:CodeReview 35 | o 2020/07/03 13:15:00 36 | i 2020/07/03 13:15:00 N:Development:Coding 37 | o 2020/07/03 16:00:00 38 | i 2020/07/03 16:00:00 N:Meeting:Development:Pairing 39 | o 2020/07/03 17:00:00 40 | i 2020/07/03 17:00:00 N:Training:ConferenceTalks 41 | o 2020/07/03 17:30:00 42 | i 2020/07/04 10:00:00 A:Holiday 43 | o 2020/07/04 18:00:00 44 | 45 | ``` 46 | 47 | Listing: A balance report on @lst:timekeeping {#lst:timekeeping_bal} 48 | 49 | ```{pipe="sh" } 50 | ledger -f time.ledger bal 51 | 52 | ``` 53 | 54 | These directives can be kept in your financial records or a separate file. 55 | This author keeps his in a separate file on his employer-owned workstation and 56 | backs them up to a cloud file hosting service. 57 | 58 | If you want to give this time tracking system a try, check out the 59 | production-quality scripts noted in @tbl:timekeeping that are well-exercised since 60 | their origin starting around 2013. 61 | 62 | ::: tryit 63 | 64 | **TRY IT**: Think about how you spend your last two days and represent that in 65 | ledger timelog form. 66 | 67 | ::: 68 | 69 | Table: Colin Dean's time keeping scripts {#tbl:timekeeping} 70 | 71 | |Script |Purpose | 72 | |-----------------|-----------------------------------------------------------| 73 | | [`t`][script_t] | A script that automates the creation of `ledger` timelog entries | 74 | | [`_t_completion`][script_t_completion] | Provides bash shell completions for `t`] | 75 | | [`analyze_t`][script_analyze_t] | Uses `t` and `gnuplot` to produce graphs of weekly hours [^overwork] | 76 | | [`hours.1m.sh`][script_bitbar] | A plugin script for [Bitbar][bitbar] to show current task and daily/weekly hours in the macOS menubar| 77 | 78 | Listing: `t` options {#lst:t_options} 79 | 80 | ```{pipe="sh"} 81 | t help 82 | 83 | ``` 84 | 85 | [^overwork]: The graphs are focused on combating overwork by visualizing hours 86 | worked and showing when and by how much those hours worked exceed 40 hours 87 | per week. 88 | 89 | [script_t]: https://github.com/colindean/hejmo/blob/master/scripts/t 90 | [script_bitbar]: https://github.com/colindean/hejmo/blob/master/dotfiles/bitbar/work/hours.1m.sh 91 | [bitbar]: https://getbitbar.com/ 92 | [script_t_completion]: https://github.com/colindean/hejmo/blob/master/scripts/_t_completion 93 | [script_analyze_t]: https://github.com/colindean/hejmo/blob/master/scripts/analyze_t 94 | -------------------------------------------------------------------------------- /0899_seealso.md: -------------------------------------------------------------------------------- 1 | # See These Other Neat Things 2 | 3 | This is by far not an exhaustive list, but meant to be a starting point for 4 | engaging with the Plain Text Accounting community as you begin your journey. 5 | Searching through these, you'll inevitably come upon posts written by this 6 | author, generally using the handle `colindean`. 7 | 8 | ## General Resources 9 | 10 | * [`plaintextaccounting.org`](https://plaintextaccounting.org/) 11 | * [`/r/plaintextaccounting` on reddit](https://reddit.com/r/plaintextaccounting) 12 | * [\@ledgertips on Twitter](https://twitter.com/LedgerTips) 13 | * [ledger-cli on Google Groups](https://groups.google.com/forum/#!forum/ledger-cli) 14 | 15 | ## Reporting and Data Entry 16 | 17 | * [ledger-pyreport](https://yingtongli.me/git/ledger-pyreport/about/) 18 | * [Costflow](https://www.costflow.io/) - succinct DSL for quickly entering data 19 | 20 | ## Budgeting and Forecasting 21 | 22 | * [fzf + ledger CLI: interactive balance sheet forecasting](https://asciinema.org/a/343330) 23 | 24 | ## Organizational accounting 25 | 26 | * [NPO-ACCT](https://npoacct.sfconservancy.org/) - Software Freedom Conservancy 27 | project to build a non-profit accounting system around `ledger` 28 | 29 | -------------------------------------------------------------------------------- /0900_appendix.md: -------------------------------------------------------------------------------- 1 | # Appendix 2 | -------------------------------------------------------------------------------- /0901_installing_ledger2beancount.md: -------------------------------------------------------------------------------- 1 | ## Installing `ledger2beancount` {#sec:install_ledger2beancount} 2 | 3 | [`ledger2beancount`](https://github.com/beancount/ledger2beancount) is the most robust way to convert and handle all of the magic that Ledger can do, but in the Beancount world. 4 | This installation procedure is a bit hefty for macOS users just to get one little Perl script, but this app is all but required to convert Ledger records to Beancount records in order to use Fava in @sec:fava. 5 | 6 | ### Debian or Ubuntu Linux 7 | 8 | If you're on Debian or Ubuntu, you can run `apt install ledger2beancount` to install it. 9 | 10 | ### macOS 11 | 12 | If you're on macOS, we'll have to install from source. 13 | 14 | First, we'll install `cpanm` so we can install `ledger2beancount` dependencies and then install the program itself. 15 | 16 | ```{#lst:install_ledger2beancount .bash caption="Steps to install ledger2beancount with its dependencies"} 17 | brew install cpanminus 18 | curl -O ledger2beancount.zip \ 19 | https://github.com/beancount/ledger2beancount/archive/master.zip 20 | unzip ledger2beancount.zip 21 | cd ledger2beancount-master 22 | cpanm --installdeps . 23 | ``` 24 | 25 | That last step will take a while. 26 | It took about five minutes on my 2015 Macbook Pro. 27 | 28 | ### Others 29 | 30 | Read over the [installation instructions](https://github.com/beancount/ledger2beancount/blob/master/docs/installation.md) if you're not on Debian or Ubuntu or macOS. 31 | -------------------------------------------------------------------------------- /0910_example_c_program.md: -------------------------------------------------------------------------------- 1 | ## Example C program for use with @lst:example_makefile {#sec:example_c} 2 | 3 | This very small program exists to ensure the validity of @lst:example_makefile 4 | but it also serves as a cool way to see `make` in action in its original 5 | intended use environment. 6 | 7 | Listing: A simple "hello world" program in C, `hello.c` {#lst:helloworld_c} 8 | 9 | ```{.c pipe="tee hello.c"} 10 | #include 11 | 12 | int main(void) 13 | { 14 | printf("hello, world\n"); 15 | } 16 | ``` 17 | 18 | Listing: Compiling @lst:helloworld_c with @lst:example_makefile `make all` and then invoking it {#lst:helloworld_c_compile} 19 | 20 | ```{.bash pipe="sh"} 21 | echo "$ make all" 22 | make -f Makefile.helloworld 23 | echo "$ ./hello" 24 | ./hello 25 | ``` 26 | -------------------------------------------------------------------------------- /0912_finance_accounts.md: -------------------------------------------------------------------------------- 1 | ## Example Financial Accounts Reference {#sec:fin_acct_ref} 2 | 3 | Here are some ideas for how to structure your account tree for tracking your 4 | finances with `ledger`. 5 | 6 | One of the hardest parts of getting started with 7 | accounting is knowing or figuring out how to categorize a posting in 8 | a transaction. 9 | 10 | This is an extraction from this author's personal transaction logs. 11 | 12 | ```{pipe="column -c 80 -t -s '++'"} 13 | Assets:Cash:Banks::Checking 14 | Assets:Cash:Banks::Savings 15 | Assets:Cash:Banks::BackupSavings 16 | Assets:Cash:Banks::Checking 17 | Assets:Cash:Online:PayPal 18 | Assets:Cash:Online:Venmo 19 | Assets:Cash:Wallet 20 | Assets:Cryptocurrency:Coinbase++# Great for multi-currency 21 | Assets:Cryptocurrency:Personal 22 | Assets:Investments: 23 | Assets:Investments:HealthSavings 24 | Assets:Investments:401k 25 | Assets:Investments:Vanguard:Brokerage 26 | Assets:Investments:Vanguard:RolloverIRA 27 | Assets:Investments:Vanguard:RothIRA 28 | Assets:Property:House: 29 | Assets:Property: 30 | Assets:Reimbursements:CodeAndSupply 31 | Assets:Reimbursements:++# Churn on business trips 32 | Equity:Cashback:++# Track that 1% cashback 33 | Equity:Cashback:Costco++# Track that yearly Costco Exec check 34 | Equity:OpeningBalances 35 | Equity:TaxRefund:Federal 36 | Equity:Unknown++# The default for all incoming, uncategorized transactions 37 | Expenses:Alcohol 38 | Expenses:Clothing:General 39 | Expenses:Clothing:Shoes++# Use choice categories to focus on spending reduction 40 | Expenses:Coffee 41 | Expenses:Conference::Registration 42 | Expenses:Conference:Gas 43 | Expenses:Conference:Meals 44 | Expenses:Cycling 45 | Expenses:Dogs:Toys 46 | Expenses:Dogs:Food 47 | Expenses:Dogs:Medical:Checkup 48 | Expenses:Dogs:Medical:Medicine 49 | Expenses:Dogs:Medical:Seresto++# Flea and tick stuff = $$$ 50 | Expenses:Donations:Charitable:ACLU++# Support civil liberties! 51 | Expenses:Donations:Charitable:AlmaMater++# Support the next generation! 52 | Expenses:Donations:Charitable:BikePGH++# Support transit! 53 | Expenses:Donations:Charitable:EqualJusticeInitiative++# Support justice! 54 | Expenses:Donations:Charitable:CodeAndSupplyFund++# Support conferences! 55 | Expenses:Donations:Charitable:DiseaseFoundation++# Support the search for a cure! 56 | Expenses:Donations:Charitable:ElectronicFrontierFoundation++# Support digital rights! 57 | Expenses:Donations:Charitable:FreeSoftwareFoundation++# Support open source! 58 | Expenses:Donations:Charitable:FreeBSD++# Support open source! 59 | Expenses:Donations:Charitable:HackPGH++# Make stuff 60 | Expenses:Donations:Charitable:Kiva++# Pay it forward 61 | Expenses:Donations:Charitable:MetaMeshWirelessCommunities++# Internet the Earth 62 | Expenses:Donations:Charitable:NPONewsOrg++# Support journalism! 63 | Expenses:Donations:Noncharitable++# GoFundMe, etc. 64 | Expenses:Donations:Political: 65 | Expenses:Donations:Political:<501c4Org> 66 | Expenses:Electronics:Batteries++# A small fortune 67 | Expenses:Electronics:Computers:Laptop 68 | Expenses:Electronics:Computers:NAS 69 | Expenses:Electronics:Computers:Parts 70 | Expenses:Electronics:Computers:Repairs 71 | Expenses:Electronics:Headphones++# 2019 was a bad luck year for quality assurance 72 | Expenses:Electronics:Networking++# Homelab addiction 73 | Expenses:Entertainment:GoogleMusic 74 | Expenses:Entertainment:Movies++# Support your small local theater! 75 | Expenses:Entertainment:Museum++# Art and History Appreciation 101 76 | Expenses:Entertainment:Netflix++# Stay home and vege! 77 | Expenses:Entertainment:Performances++# Fine Arts 78 | Expenses:Entertainment:Records++# Vinyl addiction 79 | Expenses:Entertainment:SatelliteRadio++# Never pay full price 80 | Expenses:Entertainment:Shows 81 | Expenses:Entertainment:Waterpark 82 | Expenses:Fees:Annual::++# Track CC annual fees 83 | Expenses:Gifts 84 | Expenses:Groceries 85 | Expenses:Haircut 86 | Expenses:House:Appliances 87 | Expenses:House:Bathroom 88 | Expenses:House:Bedding 89 | Expenses:House:Gardening 90 | Expenses:House:Insurance 91 | Expenses:House:Kitchen 92 | Expenses:House:Maintenance 93 | Expenses:House:PestControl 94 | Expenses:House:Utilities:Electric 95 | Expenses:House:Utilities:Internet 96 | Expenses:House:Utilities:MobilePhone 97 | Expenses:House:Utilities:NaturalGas 98 | Expenses:House:Utilities:Sewage 99 | Expenses:House:Utilities:Water 100 | Expenses:Insurance:ADD 101 | Expenses:Insurance:CriticalIllness 102 | Expenses:Insurance:Dental 103 | Expenses:Insurance:GroupTermLife 104 | Expenses:Insurance:Hospitalization 105 | Expenses:Insurance:Legal 106 | Expenses:Insurance:Medical 107 | Expenses:Interest:Mortgage++# You're a query away from filling a 1040 quickly 108 | Expenses:Medical:Copay 109 | Expenses:Medical:Dentist 110 | Expenses:Medical:Devices 111 | Expenses:Medical:Hospitalization 112 | Expenses:Medical:Medicine 113 | Expenses:Medical:Supplements 114 | Expenses:Memberships:AAA 115 | Expenses:Memberships:AmazonPrime 116 | Expenses:Memberships:Costco 117 | Expenses:Memberships:Gym 118 | Expenses:Misc 119 | Expenses:Parking 120 | Expenses:PayrollDeductions:Unspecified 121 | Expenses:Postage 122 | Expenses:Restaurants 123 | Expenses:Software++# Apps, etc. 124 | Expenses:Stuff++# Can't describe it? Punt! 125 | Expenses:Taxes:Income:Federal:EarnedIncome 126 | Expenses:Taxes:Income:Federal:Med 127 | Expenses:Taxes:Income:Federal:Oasdi 128 | Expenses:Taxes:Income:State 129 | Expenses:Taxes:Income:State:EarnedIncome 130 | Expenses:Taxes:Income:State:Unemployment 131 | Expenses:Taxes:Income:Municipal:EarnedIncome 132 | Expenses:Taxes:RealEstate:County 133 | Expenses:Taxes:RealEstate:SchoolTax 134 | Expenses:Taxes:RealEstate:Municipal 135 | Expenses:Taxes:State:Unemployment 136 | Expenses:Taxes:TaxPreparation++# Tax-deductible 137 | Expenses:Travel:Rideshare 138 | Expenses:Travel:Tolls 139 | Expenses:Unknown++# No idea what something is? Punt! 140 | Expenses:Vehicles::Gas 141 | Expenses:Vehicles::Maintenance 142 | Expenses:Vehicles::Registration 143 | Expenses:Vehicles::Repairs 144 | Expenses:Vehicles::Tires 145 | Expenses:Webhosting:DNS++# It's not DNS / It cannot be DNS / It was DNS 146 | Expenses:Webhosting:Domains++# How much are you spending on domains? 147 | Expenses:Webhosting:Email++# Free email isn't for everyone 148 | Income:AmazonAffiliates++# Track your affiliate programs 149 | Income:Interest:++# Capture interest, makes taxes easier 150 | Income::401kMatch++# Match paid until you track the investment 151 | Income::Bonus:Performance 152 | Income::Bonus:Signon 153 | Income::GroupTermLife++# Company-paid insurance 154 | Income::HealthSavings++# HSA contributions from the company 155 | Income::Salary++# General income 156 | Income:Unemployment++# Taxable 157 | Liabilities:CreditCard::++# Issuer/Type pair for each card 158 | Liabilities:Loans:Auto::++# Keep detail for analysis 159 | Liabilities:Mortgage::++# Track against Assets:Property:House 160 | 161 | ``` 162 | -------------------------------------------------------------------------------- /0915_time_accounts.md: -------------------------------------------------------------------------------- 1 | ## Timelog Account Tree Reference 2 | 3 | Here are some ideas for how to structure your account tree for timekeeping with 4 | `ledger` timelog directives as described in @sec:timekeeping. 5 | 6 | ```{pipe="column -c 80 -t -s '++'"} 7 | N++# Non-billable tasks 8 | N:Email++# Checking email or reading articles as a result of email 9 | N:Meeting++# Any activity done with someone else 10 | N:Meeting:Development++# Any development-related meeting 11 | N:Meeting:Development:Standup++# Regular standup meeting 12 | N:Meeting:Development:Pairing++# Pair programming! 13 | N:Meeting:Development:Support++# Talking with a customer 14 | N:Meeting:HR:OneOnOne++# 1:1 meeting with your management chain 15 | N:Development++# Individual development-related tasks 16 | N:Development:Coding++# Writing code! 17 | N:Development:CodeReview++# Reviewing pull requests 18 | N:Development:Presentation++# Building a presentation 19 | N:Training++# Learning tasks 20 | N:Training:Podcasts++# Listening to podcasts 21 | N:Training:Presentation++# Attending a presentation 22 | B++# Mirror of N hierarchy but used for billing clients 23 | A++# Time away from work 24 | A:Holiday++# Company holidays 25 | A:Vacation++# Time spent not caring about work 26 | A:Sick++# Sick days 27 | A:Errand++# Errands during work time but not enough to take PTO 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /0999_package_it_up.md: -------------------------------------------------------------------------------- 1 | ## Helpful Accompanying Artifacts & Colophon {#sec:artifacts} 2 | 3 | This document was built with [pandoc](https://pandoc.org) and a host of plugins 4 | and other great software. 5 | 6 | One particularly great plugin, `panpipe` enabled the listings to contain 7 | program output generated dynamically as the document was built. @Lst:artifacts 8 | contains a list of files available in a companion archive and a SHA256 hash of 9 | that archive as it was packaged at build time to verify its authenticity. 10 | 11 | Notably, `Makefile.final` is the assembled version of all `ledger` Makefile 12 | examples in this workshop. You can use it wholesale for your next `ledger` 13 | project: your personal finances! 14 | 15 | 16 | 17 | Listing: Artifacts built in the process of compiling this document {#lst:artifacts} 18 | 19 | ```{pipe="sh"} 20 | cat Makefile.*.txt | grep -v '^# ' > Makefile.final 21 | ARCHIVE=root/pta_workshop_artifacts.tar.gz 22 | tar -czvf "${ARCHIVE}" --exclude='root' . 2>&1 | sed -e 's%a \.\/%%g' | grep -v 'a\ \.' | sort | paste - - - | column -t 23 | sha256sum "${ARCHIVE}" 24 | 25 | ``` 26 | 27 | Run `sha256sum pta_workshop_artifacts.tar.gz` to see the SHA256 hash of the artifacts archive. 28 | If the output matches the long string of letters and numbers on the last line 29 | in @lst:artifacts, you've got the correct companion archive for this version of 30 | the workshop. 31 | If it doesn't match, you can probably still use it, but there might be some 32 | small differences because of changed files. It wouldn't be a bad idea to double 33 | check the listings if the output isn't what you'd expect. 34 | 35 | Good luck! 36 | -------------------------------------------------------------------------------- /0999_references.md: -------------------------------------------------------------------------------- 1 | ## References {#sec:references} 2 | 3 | Contents from these works were used in the production of this workshop. 4 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew 'pandoc-crossref' 2 | brew 'pandoc-include-code' 3 | brew 'pandoc' 4 | brew 'aha' 5 | brew 'make' 6 | brew 'entr' 7 | brew 'gnuplot' 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | **License** 2 | 3 | © 2020 Colin Dean 4 | 5 | This work is licensed under Creative Commons BY-NC-SA 4.0. 6 | 7 | To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0. 8 | 9 | **Your rights under this license** 10 | 11 | You are free to: 12 | 13 | * Share — copy and redistribute the material in any medium or format 14 | * Adapt — remix, transform, and build upon the material 15 | 16 | The licensor cannot revoke these freedoms as long as you follow the license terms. 17 | 18 | Under the following terms: 19 | 20 | * Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 21 | * NonCommercial — You may not use the material for commercial purposes. 22 | * ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | 24 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 25 | 26 | **Notices** 27 | 28 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 29 | 30 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 31 | 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OUTPUT=pta_workshop 2 | PDF_OUTPUT=$(OUTPUT).pdf 3 | HTML_OUTPUT=$(OUTPUT).html 4 | 5 | OUTPUTS=$(PDF_OUTPUT) $(HTML_OUTPUT) 6 | 7 | CONFIG=config.yaml 8 | MD_FILES=$(sort $(wildcard 0*.md)) 9 | LEDGER_FILES=$(sort $(wildcard *.ledger)) 10 | 11 | HEADER_TEXS=LICENSE.tex REPO.tex 12 | HEADER_HTMLS=$(HEADER_TEXS:%.tex=%.html) 13 | 14 | all: $(OUTPUTS) 15 | 16 | $(OUTPUT).pdf: $(MD_FILES) $(CONFIG) $(HEADER_TEXS) 17 | pandoc \ 18 | --defaults $(CONFIG) \ 19 | $(MD_FILES) \ 20 | -o $@ \ 21 | -M "date=v$(shell date +%Y.%m.%d)$(PATCH)" \ 22 | -M "crossrefYaml=pandoc-crossref.yaml" \ 23 | $(addprefix --include-before-body=, $(HEADER_TEXS)) 24 | 25 | $(OUTPUT).html: $(MD_FILES) $(CONFIG) $(HEADER_HTMLS) 26 | pandoc \ 27 | --defaults $(CONFIG) \ 28 | $(MD_FILES) \ 29 | -o $@ \ 30 | -M "date=v$(shell date +%Y.%m.%d)$(PATCH)" \ 31 | -M "crossrefYaml=pandoc-crossref.yaml" \ 32 | $(addprefix --include-before-body=, $(HEADER_HTMLS)) 33 | 34 | .PHONY: open 35 | open: $(PDF_OUTPUT) 36 | open $(PDF_OUTPUT) 37 | 38 | %.tex: %.md 39 | pandoc --from=markdown+autolink_bare_uris --to=latex $< -o $@ 40 | 41 | %.html: %.md 42 | pandoc --from=markdown+autolink_bare_uris --to=html $< -o $@ 43 | 44 | WATCHABLES=Makefile $(MD_FILES) $(CONFIG) $(LEDGER_FILES) $(HEADER_TEXS:%.tex=%.md) refs.bibtex 45 | 46 | .PHONY: watch 47 | watch: 48 | ls $(WATCHABLES) $(HEADER_TEXS) $(HEADER_HTMLS) | entr -napr make all PATCH=-wip 49 | 50 | .PHONY: gitadd 51 | gitadd: 52 | git add $(WATCHABLES) 53 | 54 | .PHONY: clean 55 | clean: 56 | rm -rf $(HEADER_TEXS) $(HEADER_HTMLS) $(OUTPUTS) pta_workshop_artifacts.tar.gz 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plain Text Accounting Workshop 2 | 3 | :warning: :warning: 4 | If you're wanting to follow the workshop, 5 | download the PDF (or the HTML) and the tar.gz from the 6 | [releases page](https://github.com/colindean/plaintextaccounting_workshop/releases/latest). 7 | 8 | If you're reading the this README on the linked repo, you're not reading the right thing: 9 | the instructions below are for _building_ the workshop distributable files. 10 | 11 | ## Contributing to the Workshop 12 | 13 | The Markdown files in this directory are [`pandoc`-flavored 14 | Markdown](https://pandoc.org) so they may not correctly render on GitHub. This 15 | is especially true of the tables. 16 | 17 | Run `brew bundle` to install the easily available dependencies to build this 18 | workshop document. 19 | 20 | And then there's the no so easily available: 21 | 22 | * [`panpipe`](https://hackage.haskell.org/package/panpipe "warning: takes forever to compile all dependencies and install") ([git](http://chriswarbo.net/git/panpipe/git/index.html)) ([my custom fork](https://github.com/colindean/panpipe)) 23 | 24 | Run `make open` to compile the PDF and open it in a PDF viewer. 25 | 26 | Run `make watch` to start an edit-compile loop. 27 | 28 | The № 1 rule? _Leave it better than you found it._ 29 | 30 | ## License 31 | 32 | This work is licensed under Creative Commons BY-NC-SA 4.0. 33 | You are free to share and adapt this workshop but you must provide attribution 34 | to Colin Dean, you must share your changes under the same license, and you may 35 | not use this workshop for commercial purposes. 36 | See [LICENSE.md](LICENSE.md) for more infromation. 37 | -------------------------------------------------------------------------------- /REPO.md: -------------------------------------------------------------------------------- 1 | **Where can I report a problem with the workshop?** 2 | 3 | You can file bugs and feature requests online at: 4 | 5 | 6 | 7 | **This is awesome, how can I support you?** 8 | 9 | I do pretty well myself, so please don't feel obligated to give me something. 10 | If you are interested in [hiring me](https://linkedin.com/in/colindean), let's talk. 11 | I'm not actively looking for work but I keep my mind open for new and 12 | interesting opportunities. I'm easy enough to find online, too. 13 | 14 | If you strongly prefer to donate something, please consider donating to one of 15 | my two favorite non-profits that I helped to start: 16 | 17 | * [Code & Supply Scholarship Fund](https://codeandsupply.fund) - Provides 18 | scholarships to people in need so that they can attend and speak at tech 19 | conferences throughout the United States. One award can turn into a new 20 | career. 21 | * [Meta Mesh Wireless Communities](https://metamesh.org) - Builds WiFi networks 22 | for people, businesses, and governments in low-income communities and is 23 | working on becoming a low-cost, non-profit Wireless ISP in 2020. 24 | 25 | If you _really_ want to reward _me_, you can drop me some coffee money at https://venmo.com/colindean 26 | or ask me for a Bitcoin address. 27 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | from: markdown+autolink_bare_uris 3 | 4 | metadata: 5 | # document metadata 6 | title: "Command-Line Finances Using the Plain Text Accounting Ecosystem" 7 | subtitle: "A workshop meant to improve personal finance confidence using software skills" 8 | author: ["Colin Dean","@colindean","#plaintextaccounting"] 9 | subject: "Plain Text Accounting" 10 | keywords: 11 | - plain text accounting 12 | - ledger 13 | - personal finance 14 | - timekeeping 15 | - retirement 16 | - investing 17 | - trading 18 | - accounting 19 | - bookkeeping 20 | thanks: | 21 | Thank you to John Wiegley, author of Ledger, for starting a revolution 22 | freeing personal and business finances from the walled garden of 23 | proprietary software. 24 | 25 | Thank you to Simon Michael, author of hledger and maintainer of 26 | plaintextaccounting.org, for turning a single tool into an ecosystem 27 | with a standard. 28 | 29 | Thank you to my parents, who involved me in financial decisions from 30 | an early age and who taught me how to save, track, and do good with my money 31 | even when theirs was exceptionally tight. 32 | 33 | And lastly, thank you to you who are seeking to improve how you live your 34 | life. May your thirst for knowledge be quenched by this workshop, and I 35 | hope that you will contribute back to the Plain Text Accounting ecosystem 36 | by sharing your knowledge as I have shared mine. 37 | # filter metadata 38 | # crossrefYaml: pandoc-crossref.yaml 39 | 40 | # include-before-body: ['LICENSE.tex','REPO.tex'] 41 | 42 | variables: 43 | # physical paper and layout settings 44 | documentclass: report 45 | papersize: letter 46 | geometry: 47 | - margin=1in 48 | lof: true 49 | lot: true 50 | toc: true 51 | fontfamily: 'sourceserifpro' 52 | colorlinks: true 53 | links-as-notes: true 54 | # page stylings 55 | pagestyle: headings 56 | 57 | #include-in-header: 58 | # - headers.tex 59 | 60 | bibliography: 61 | - refs.bibtex 62 | 63 | number-sections: true 64 | 65 | pdf-engine: xelatex 66 | filters: 67 | - pandoc-crossref 68 | - pandoc-citeproc 69 | - pandoc-include-code 70 | # - filter-pipe.lua 71 | - panpipe 72 | 73 | preserve-tabs: true 74 | -------------------------------------------------------------------------------- /examples.ledger: -------------------------------------------------------------------------------- 1 | 2017-06-20 Opening Balances 2 | Assets:Cash:Wallet 3.50 3 | Assets:Cash:Bank:Checking 536.35 4 | Assets:Cash:Bank:Savings 3014.12 5 | Liabilities:CreditCards:Costco -235.13 6 | Equity:OpeningBalances 7 | 8 | 2017-06-26 Commonplace Coffee 9 | Expenses:Restaurants:Coffee 3.00 10 | Assets:Cash:Wallet -3.00 11 | 12 | 2017-06-27 Commonplace Coffee 13 | Expenses:Restaurants:Coffee 3.00 14 | Assets:Cash:Wallet 15 | 16 | 2017-06-27 Commonplace Coffee 17 | ; cold brew 18 | Expenses:Restaurants:Coffee 3.00 19 | Liabilities:CreditCards:Costco 20 | 21 | 2017-06-30 Spacely Sprockets 22 | ; payday! 23 | Income:SpacelySprockets -2000.00 24 | Assets:Cash:Bank:Checking 2000.00 25 | 26 | 2017-07-15 Spacely Sprockets 27 | ; payday! 28 | Income:SpacelySprockets -2000.00 29 | Assets:Cash:Bank:Checking 2000.00 30 | 31 | 2017-07-15 Commonplace Coffee 32 | ; type: cold brew 33 | Expenses:Restaurants:Coffee 3.00 34 | Liabilities:CreditCards:Costco 35 | 36 | 2017-07-15 Transfer to Savings 37 | Assets:Cash:Bank:Checking -100.00 38 | Assets:Cash:Bank:Savings 100.00 39 | 40 | 2017-07-30 Spacely Sprockets 41 | ; payday with taxes 42 | Income:SpacelySprockets -2735.00 43 | Expenses:Taxes:Federal:EIT 500.00 44 | Expenses:Taxes:Federal:FICA 200.00 45 | Expenses:Taxes:State:EIT 35.00 46 | Assets:Cash:Bank:Checking 2000.00 47 | 48 | 2017-07-30 Commonplace Coffee 49 | ; type: cappucino 50 | Expenses:Restaurants:Coffee 3.00 51 | Liabilities:CreditCards:Costco 52 | -------------------------------------------------------------------------------- /filter-pipe.lua: -------------------------------------------------------------------------------- 1 | function execute_shell(code) 2 | return pandoc.pipe("bash", {"--"}, code) 3 | end 4 | 5 | 6 | function CodeBlock(block) 7 | if block.classes[1] == "pipe" then 8 | block.text = execute_shell(block.text) 9 | end 10 | 11 | return block 12 | end 13 | -------------------------------------------------------------------------------- /headers.tex: -------------------------------------------------------------------------------- 1 | \usepackage{fancyhdr} 2 | \pagestyle{fancy} 3 | \fancyhead[LO,RE]{\leftmark} 4 | \fancyhead[LE,RO]{} 5 | \fancyfoot[C]{\thepage} 6 | \fancyfoot[LO,RE]{\tiny{\textcopyright{} Colin Dean. Licensed under CC BY-NC-SA 4.0.}} 7 | \fancyfoot[LE,RO]{\tiny{\thetitle}} 8 | \renewcommand{\headrulewidth}{1pt} % non-default, 0.4 is default 9 | \renewcommand{\footrulewidth}{1pt} % non-default 10 | 11 | 12 | -------------------------------------------------------------------------------- /pandoc-crossref.yaml: -------------------------------------------------------------------------------- 1 | # move into config.yaml when this is fixed: 2 | # https://github.com/lierdakil/pandoc-crossref/issues/259 3 | codeBlockCaptions: true 4 | linkReferences: true 5 | nameInLink: true 6 | -------------------------------------------------------------------------------- /refs.bibtex: -------------------------------------------------------------------------------- 1 | @misc{wikipedia:make, 2 | author = "Wikipedia", 3 | title = "{Make (software)} --- {W}ikipedia{,} The Free Encyclopedia", 4 | year = "2020", 5 | howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Make\%20(software)&oldid=966907354}}, 6 | note = "[Online; accessed 17-July-2020]" 7 | } 8 | 9 | @misc{ledger:docs, 10 | author = "John Wiegley", 11 | title = "Ledger-CLI 3.0 Documentation", 12 | year = 2020, 13 | howpublished = {\url{https://www.ledger-cli.org/3.0/doc/ledger3.html}}, 14 | note = "[Online; accessed 19-July 2020]" 15 | } 16 | -------------------------------------------------------------------------------- /simple.csv: -------------------------------------------------------------------------------- 1 | Date,Recorded at,Scheduled for,Amount,Activity,Pending,Raw description,Description,Category folder,Category,Street address,City,State,Zip,Latitude,Longitude,Memo 2 | 2020/07/01,,,0.01,Interest credit,FALSE,IOD INTEREST PAID,Interest Credit,Income,Interest,,,,,,, 3 | 2020/06/30,,,-3600.00,Direct debit,FALSE,DIRECT DEBIT,Mortgage,Expenses,Mortgage,,,,,,,Mortgage payment 4 | 2020/06/18,,,-16.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 5 | 2020/06/08,,,200.00,Check deposit,FALSE,MOBILE DEPOSIT,Check Deposit,Income,Other Income,,,,,,,Elections Payment 6 | 2020/06/04,,,-4.87,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 7 | 2020/06/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 8 | 2020/05/30,,,0.01,Interest credit,FALSE,IOD INTEREST PAID,Interest Credit,Income,Interest,,,,,,, 9 | 2020/05/24,,,200.00,C2c,FALSE,Instant Transfer from Partner,Partner,Financial,Money Transfers,,,,,,,Oops 10 | 2020/05/24,,,-16.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 11 | 2020/05/23,,,-250.00,C2c,FALSE,Instant Transfer to Partner,Partner,Financial,Money Transfers,,,,,,,Dinner at Dijlah 12 | 2020/05/07,,,100.00,Check deposit,FALSE,MOBILE DEPOSIT,Check Deposit,Income,Other Income,,,,,,,Birthday card 13 | 2020/05/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 14 | 2020/05/01,,,0.01,Interest credit,FALSE,IOD INTEREST PAID,Interest Credit,Income,Interest,,,,,,, 15 | 2020/04/18,,,-16.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 16 | 2020/04/14,,,-5.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 17 | 2020/04/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 18 | 2020/04/01,,,0.01,Interest credit,FALSE,IOD INTEREST PAID,Interest Credit,Income,Interest,,,,,,, 19 | 2020/03/30,,,-3600.00,Direct debit,FALSE,DIRECT DEBIT,Mortgage,Expenses,Mortgage,,,,,,,Mortgage payment 20 | 2020/03/28,,,-4.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 21 | 2020/03/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 22 | 2020/02/29,,,0.01,Interest credit,FALSE,IOD INTEREST PAID,Interest Credit,Income,Interest,,,,,,, 23 | 2020/02/10,,,493.05,Check deposit,FALSE,MOBILE DEPOSIT,Check Deposit,Income,Other Income,,,,,,,Costco Cashback 24 | 2020/02/08,,,-16.67,Debit,FALSE,DEBIT PURCHASE,Black Forge Coffee,Expenses,Coffee,,,,,,,Coffee at Black Forge 25 | 2020/02/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 26 | 2020/01/18,,,175.00,Check deposit,FALSE,MOBILE DEPOSIT,Check Deposit,Income,Other Income,,,,,,, 27 | 2020/01/09,,,-75.00,C2c,FALSE,Instant Transfer to Partner,Partner,Financial,Money Transfers,,,,,,,Truck gas 28 | 2020/01/02,,,5000.00,Check deposit,FALSE,DIRECT DEPOSIT,Code & Supply,Income,Salary,,,,,,,Payday 29 | --------------------------------------------------------------------------------