Liking cljdoc? Tell your friends :D

Quick summary
Setup
API
Changelog
Introduction
Ideas
Usage
Possibilities
Critique
Alternatives
Examples
Glossary
Contact

Chlog

A Clojure library for maintaining an edn changelog

Quick summary

  1. A version number is merely a label; it conveys no other meaning.

  2. The changelog contains any and all information concerning switching from one version to another.

Setup

Leiningen/Boot

[com.sagevisuals/chlog "4"]

Clojure CLI/deps.edn

com.sagevisuals/chlog {:mvn/version "4"}

Require

(require '[chlog.core :refer [-main]])

Introduction

Q: What will happen if we switch from this version to that  version?

A: Consult the changelog, not the version number.

We ask too much of our software version numbers, and we should expect more  from our changelogs. A major.minor.patch sequence of numbers simply doesn't have enough bandwidth to tell us how to  make good decisions about switching versions. On the other hand, html/markdown changelogs could potentially convey that information, but require  a person to read and interpret it.

Ideally, a changelog would contain a compact representation of what  changed from one version to the next, and operational implications of those  changes. If we use functions foo and bar, but only function baz changed, it's safe to switch. Or, function baz changed and we use it, but the change is non-breaking, so, again, it's safe  to switch. Or, maybe baz was changed in a way that does break the way we use it, but the changelog tells us why it changed and how  to manage the switch.

Changelogs are often served as html or markdown files, intended for people to read. But to convey the depth of information  we're talking about requires a high-level of discipline by the authors to  comprehensively write all that out. Mistakes and omissions are bound to happen  with free-form text, not to mention irregularities that thwart parsing, and in  the end, a person has to synthesize a lot of bits to make a decision. html/markdown is ill-suited for this purpose.

Storing a changelog in Clojure data structures offers many potential  benefits. The data can be written, stored, retrieved, and manipulated  programmatically. It can be validated for correctness and completeness. And we could write utilities that answer detailed questions about what it would be like to switch from  one version to another.

Chlog is a library that tests these ideas. It encompasses several parts.  First, it promotes a set of ideas, some of which we've already mentioned. Second, Chlog proposes a set of  specifications for such a changelog. Third, Chlog offers an experimental  implementation that maintains an extensible data notation (edn) changelog specification, validates it, and generates easily-readable html and markdown webpages based upon the changelog data.

The resulting changelog looks like this.

Ideas

A version number is just a number

Tagging software with major.minor.patch numbers attempts to convey the notion Yes, we can safely upgrade to such-and-such version. But the granularity is poor. What if a dependency does have a breaking  change, but the breaking change is in a portion of the dependency that we don't  use. Version numbers ought to merely be a label to differentiate one release  from another.

If a version number is merely a label without semantics, how would someone  judge whether to switch from one version to another? A detailed, concise,  regularly-formatted changelog could convey all the information necessary to  make an informed decision about if there is any benefit to changing versions,  if changing versions will require updates on the consuming side, and if so,  what updates are necessary.

A later version is not promised to be better, merely different. The changelog authors will provide dispassionate  information about the changes, and the people using the software can decide  whether it is worth switching.

Chlog is an experiment to detangle version numbers from changelog  information. A version number n makes no claim other than it was released some time later than version n-1.

A changelog is data

The changelog edn files are the canonical sources of information. All other representations (html/markdown, etc) are derived from that, and are merely conveniences.

A human- and machine-readable edn file will accompany each version. Each file contains a hashmap detailing  that version. The global changelog is a sequence constructed by concatenating  those hashmaps from all previous releases, i.e.,  the per-version hashmaps contained in changelog-vN.edn files located in a designated sub-directory.

A low threshold for breakage

The Chlog experiment advocates the changelog being the sole source of  information on what will happen when switching versions. For that to succeed,  the entries must accurately communicate whether a change is breaking. Not every  change can be objectively categorized as either breaking or non-breaking (more  on that in a moment). To have empathy for other people is tricky. If all  changes are claimed as breaking, the concept loses its meaning and purpose. But  if a supposedly safe change ends up breaking for someone else, trust is lost.

A changelog that declares :breaking? false stipulates that switching to that version will work as it worked before with  zero other changes (including changes in dependencies). Otherwise, the change  is a breaking change, explicitly indicated by :breaking? true.

As a rough starting guideline, the following kinds of changes are probably breaking.

  • all regressions (performance, memory, network)
  • added or changed dependencies (see note below)
  • removed or renamed namespaces
  • moved, renamed, or removed functions
  • stricter input requirements
  • decreased return
  • different default

Likewise, the following kinds of changes are probably non-breaking.

  • all improvements (performance, memory, network)
  • removed dependencies
  • added or deprecated namespaces
  • added or deprecated functions
  • relaxed input requirements
  • increased returns
  • implementation
  • source code formatting
  • documentation

These are just starting guidelines. Careful judgment may say that a change  in a function's defaults will in all cases be a non-breaking change. Or, a  change in the documentation might be so severe that it's elevated to a breaking  change.

One important kind of change that sorta defies categorization is  bug-fixes. According to the notion that a non-breaking change must be a perfect  drop-in replacement, a bug fix would classify as a breaking change. Tentative  policy: Bug fixes are non-breaking changes, but it depends on the scenario.

Formal specifications state required information

Each version has required information that is explicitly delineated in  the specifications. Correctness of a changelog, or any sub-component of the changelog, may be  verified by validating the changelog against those specifications.

Et cetera

  • A changelog is mutable. Corrections are encouraged and additions are  okay. The changelog itself is versioned-controlled data, and the html/markdown documents that are generated from the changelog data are also  under version-control.

  • Yanked or retracted releases can simply be noted by revising the  changelog data.

  • Much of the changelog data is objective (e.g., dates, email), but  some is merely the changelog author's opinions. That's okay. The changelog  author is communicating that opinion to the person considering switching  versions. The changelog author may consider a particular bug-fix :high urgency, but the person using the software may not.

Usage

There are four steps to using Chlog.

  1. Maintain changelog data in edn files.

  2. Declare and require the dependency.

  3. Create an options file.

  4. Generate the changelog documents.

Changelog information is Clojure data

Changelog information is built by concatenating hashmaps contained in edn files located in the changelog entries directory, one file per version. Every version is represented by the following hashmap.

{:version ___
 :date {:year ___
        :month ___
        :day ___ }
 :responsible {:name ___
               :email ___ }
 :project-status ___
 :breaking? ___
 :urgency ___
 :comment ___
 :changes [...]}

This hashmap (and all the following) is formally and canonically specified with a Speculoos style specification.

Briefly, the hashmap values:

  • version is an integer.
  • date is a nested hashmap of integer year, string  month, and integer day.
  • responsible is a nested hashmap of a :name string and an :email string.
  • project-status is one of enumerated keywords borrowed from  the Metosin description.
  • breaking? is a boolean or nil (the later is only valid for the initial release).
  • urgency is one of :low, :medium, or :high.
  • comment is a free-form string.
  • changes is a nested vector of change hashmaps (discussed soon).

A changelog is a tail-appended sequence of one or more such version hashmaps. Furthermore, a version hashmap may have zero or more change hashmaps associated to the :changes key. A change hashmap looks like this.

{:description ___
 :reference {:source ___
             :url ___}
 :change-type ___
 :breaking? ___
 :altered-functions []
 :date {:year ___
        :month ___
        :day ___ }
 :responsible {:name ___
               :email ___ }}

Besides the sequence of :altered-functions seen above, a change hashmap may also contain sequences of :added-functions, :deprecated-functions, :moved-functions, :removed-functions, and :renamed-functions.

The parts of a change hashmap are:

  • date analogous to the date of a version.
  • reference (optional) a nested hashmap of :source string (e.g., a GitHub Issue, JIRA ticket, etc.), and :url string (creates a line-leading hyperlink).
  • breaking? a boolean.
  • altered-functions a nested vector of symbols that were  altered in this change.
  • responsible a nested hashmap of a :name string and an :email string.
  • change-type a keyword from this enumerated list.
  • description a free-form string.

The changelog data may be manipulated and queried with any suitable  Clojure function, such as get-in, assoc-in, update-in, etc. The Chlog library includes specifications for changelog data and  utilities for performing those validations, but any Clojure validation  facility, such as clojure.spec.alpha, may be used once suitable specification are written.

Note: Chlog consults every file in the changelog entries directory whose filename begins with changelog_v and ends with .edn. Between those required bookends, files may be named according to whatever  may be convenient, e.g., integers padded with zeros for human-readable sorting  in the local filesystem or desktop environment.

Creating an options file

The options file is an edn file (example) that contains a hashmap which supplies required information for generating a  changelog. It also declares preferences for other optional settings.

Required keys:

  • :project-name-formatted Project name (string) to display on changelog html/markdown  documents.

  • :copyright-holder Name displayed in the copyright statement in the footer of the  changelog.

  • :UUID Version 4 Universally Unique Identifier. Suggestion: eval-and-replace (random-uuid). Default nil.

Optional keys (defaults supplied by chlog_defaults.clj):

  • :changelog-entries-directory Alternative directory to find changelog edn files. Include trailing '/'. Defaults to resources/changelog_entries/.

  • :changelog-html-directory Alternative output html directory (string). Include trailing '/'. Defaults to doc/.

  • :changelog-html-filename Alternative output html filename (string). Defaults to changelog.html.

  • :changelog-markdown-directory Alternative output markdown directory (string). Include trailing `/`.  Defaults to '' (i.e., project's root directory).

  • :changelog-markdown-filename Alternative output markdown filename (string). Defaults to changelog.md.

  • :tidy-html? Indent and wrap html and markdown files. Defaults to false.

Generating the changelog documents

The Chlog library generates html and markdown changelog files from hiccup source. To generate the html and markdown files. We could evaluate…

(-main "resources/chlog_options.edn")

…in our repl-attached editor. Or, we could run the -main function from command line something like this.

$ lein run -m chlog.core

Chlog produces two files. The first is a 'markdown' file that's actually plain old html, abusing the fact that html passes through the markdown converter. By default, this markdown file is written to the project's root directory where GitHub can find and display the changelog. We don't need a dedicated markdown converter to view this file; copy it to a GitHub gist and it'll display similarly to when we view it on GitHub. The second file, by default written to the resources/ directory, is a proper html document with a <head>, etc., that is viewable in any browser as-is. Copy over the css file for some lightweight styling.

Possibilities

When changelog information is stored as Clojure data, it opens many intriguing possibilities.

  • Changelog data could be used to generate formatted html or markdown webpages for casual reading. Chlog currently implements this.

  • A js/cljs widget embedded in a webpage that presents a current version selector and a target version selector. Then, based on each selection, the utility would collapse all the intervening versions and list the breaking and non-breaking changes. Someone considering switching versions could quickly click around and compare the available versions. Switching from version 3 to version 4 introduces one breaking change whereas Switching from version 3 to version 5 involves that same breaking change, plus another breaking change in a function we don't use. Therefore, switching to version 4 or version 5 is equivalent.

  • A utility that could scan the codebase and list all the functions used from a particular dependency. If we were curious about switching versions of that dependency, that list of functions would be compared to the list of functions with breaking changes. If there was no intersection of the lists, it's safe to switch versions. If the codebase did use a function with a breaking change, the changelog would communicate what changed, and how involved it would be to switch versions.

Critique

On the one hand, making the changelog normalized data lends itself to straightforward analysis and display on the web. On the other hand, is it a strategic mistake to require that every change be a member of an enumerated set? Is having a change-kind :other a red flag? It's fairly typical for one of my commits to include an update to the source code, accompanied by some additional unit tests, and some kind of change in the documentation. At the moment, I would categorize it as a :altered-function, with the implicit understanding that someone reading the changelog would care about the changed function the most, and that the accompanying unit tests and docs are subordinate.

But the rigidity causes concern, and perhaps only extended use will reveal if it's a deal-breaker.

Alternatives

  • Classic markdown/html files Discussed above.

  • Changelog generated from version control commit log At first glance, a changelog generated from a version control commit log seems natural. It is certainly easy, and there are many concepts in common. However, the purpose of a changelog is different enough to merit its own focus.

    Version control logs communicate information about the development history to developers of that software. Changelogs communicate the details of the released versions to people who consume the software. Related, but subtly different.

    A commit message notes fine-grained changes to the software, somewhat like a diary of software development. It would feel a bit oppressive to have to consider how every commit message would appear in a public changelog. The evolution of software is noisy. Commit messages may involve false starts, mistakes, and dead ends. They are meant to be read by people developing the software itself. Plus, writing commit messages that also serve as a changelog entry would require some kind of standards or specifications, or heroic discipline by the authors. Finally, commit messages typically do not concern themselves about whether the change is breaking for the people using the software.

    Changelogs, on the other hand, should clearly and concisely communicate, to people using the software, the differences between one version and another. It could be fine- or coarse-grained, but the freedom to decide should be independent of the version control commit log. Authoring a changelog requires care, judgment, and empathy for people ultimately using the software, and is a task somewhat different from wrangling version control commit messages.

Even at this early stage of its life, Chlog can alleviate most of that labor. Keep the changelog as edn data, and Chlog will take care of the html/markdown.

Examples

Changelog for a specification library.

Changelog for a collection manipulation library.

Changelog for a ReadMe generator library.

Changelog for a changelog generator library.

Changelog for a fictitious library.

Glossary

breaking

Any change that is not non-breaking.

changelog

A sequence of notable versions of project. Concretely, a tail-appended sequence containing one or more version hashmaps. Typically organized into multiple edn files located in a known directory where Chlog can locate it. The changelog contains information about the software that helps people understand what will happen when switching from one version of the software to another.

change

Within a version, a report of some facet of the software that is different. Concretely, a hashmap containing information about the change kind (e.g., added function, deprecated function, implementation change, performance improvement, bug-fix, etc.), person responsible, namespaced function symbols involved, and an issue reference (e.g., GitHub issue number, JIRA ticket, etc.).

non-breaking

A change that can be installed with zero other adjustments and the consuming software will work exactly as before. Otherwise, the change is breaking.

version

A notable release of software, labeled by a version number. Concretely, a hashmap containing information about the release date, the person responsible, the urgency of switching, breakage, free-form description, and a listing of zero or more detailed changes.


License

This program and the accompanying materials are made available under the terms of the MIT License.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close