Module: GemDocs

Defined in:
lib/gem_docs.rb,
lib/gem_docs/repo.rb,
lib/gem_docs/yard.rb,
lib/gem_docs/badge.rb,
lib/gem_docs/emacs.rb,
lib/gem_docs/tasks.rb,
lib/gem_docs/config.rb,
lib/gem_docs/header.rb,
lib/gem_docs/version.rb,
lib/gem_docs/overview.rb,
lib/gem_docs/skeleton.rb

Overview

Gem Overview (extracted from README.org by gem_docs)

  • Overview

One of the more onerous tasks when writing a ~gem~ or other ~github~ project is maintaining the documentation, and keeping it consistent and up-to-date. One of the better options for writing the ~README~ file that gets displayed by ~github~ as the main documentation is to write the file in ~org-mode~, ~README.org~ the export it to markdown ~README.md~ for display by ~github~.

Doing so gives you access to ~org-mode~‘s code blocks for writing example code to demonstrate the ~gem~ or other documents being presented. If you do so, ~github~ will render your ~README.org~ file in ~HTML~ and give you a credible result. However, ~github~ cannot handle some ~org-mode~ features, and in particular, it will not render ~#+RESULTS~ blocks showing the results of code block execution unless you wrap the results in something like a ~#+begin_example~ block and manually delete the ~#+RESULTS~ markers. Exporting to markdown eliminates that hassle.

This gem contains ~rake~ tasks to facilitate the production of documentation in other gems.

It provides tasks for:

  • ensuring that proper code block setup is included in ~README.org~ to facilitate running ~ruby~ code blocks in a session using the current version of the library,

  • running the code block examples in a ~README.org~ by invoking ~emacsclient~,

  • exporting ~README.org~ to Git-flavored markdown in ~README.md~

  • exporting ~CHANGELOG.org~ to Git-flavored markdown in ~CHANGELOG.md~

  • ensuring a workflow or ci badge is present in the ~README.md~

  • generating yard documents for your repo, and

  • copying the introductory contents of the README as a leading comment in your main gem library file so it gets picked up as an overview for ~ri~ and ~yri~

  • Usage

** Create a skeleton README.org file This is a simple task that creates a bare-bones ~README.org~ file to get started with. It does create the file with the name of the gem and other repo detains filled in. If there is already a README.org file, it does nothing.

#+begin_src ruby :eval no

rake docs:skeleton:readme

#+end_src

** Create a skeleton CHANGELOG.org file This is a simple task that creates a bare-bones ~CHANGELOG.org~ file to get started with. It only contains some tips for writing a change log and a sample heading. If there is already a CHANGELOG.org file, it does nothing.

#+begin_src ruby :eval no

rake docs:skeleton:changelog

#+end_src

** Add proper ~#+PROPERTY~ headers in ~README.org~: ~rake docs:headers~ Getting emacs code blocks to render well in your ~README.org~ takes proper configuration of the code block headers in Emacs.

#+begin_src ruby :eval no

rake docs:headers

#+end_src

By default, the ~gem_docs~ ~rake docs:headers~ task will add the following headers to the top of your ~README.org~ file. It does nothing if any ruby header args are already present, so remove them if you want these installed.

#+begin_example #+PROPERTY: header-args:ruby :results value :colnames no :hlines yes :exports both :dir “./” #+PROPERTY: header-args:ruby+ :wrap example :session gem_docs_session #+PROPERTY: header-args:ruby+ :prologue “$:.unshift(‘./lib’) unless $:.first == ‘./lib’; require ‘%n’” #+PROPERTY: header-args:sh :exports code :eval no #+PROPERTY: header-args:bash :exports code :eval no #+end_example

Here’s what the ruby headers buy you:

  • ~:results value~

    the value of the last expression in the block is rendered

    as the results of code execution. If you want the output instead for a particular block, just add the block argument ~:results output~ to the code block.

  • ~:colnames no~

    prevents org from processing the column headers in tables

    it renders. It is better for you to control column headers, and this setting allows this.

  • ~:hlines yes~

    prevents org from stripping hlines from tables, which also

    allows you to control the insertion of hlines in tables.

  • ~:exports both~

    causes both your code and the results of evaluation to be

    exported to the ~README.md~. Your example blocks should demonstrate the use of the gem, and the reader will want to see both the code and its results.

  • ~:dir “./”~

    causes each code block to execute with your gem’s root

    directory as its current directory.

  • ~:wrap example~

    this wraps the result of code block evaluation in

    ~#+begin_example~ / ~#+end_example~ so that the results are displayed literally.

  • ~:session gem_docs_session~

    causes the code blocks to execute in a

    continuous session, so that variables set up in one code block are accessible in later code blocks. Without this, you would have to build the code environment anew with each code block which obscures the readability of your ~README~. The session name is set to ‘<gem_name>_session’ automatically, where <gem_name> is the name of your gem.

  • ~:prologue “$:.unshift(‘./lib’) unless $:.first == ‘./lib’; require ‘gem_name’”~

    this prologue gets executed before each code block execution and ensures

    that the version of the gem library is you current development version; otherwise, your code blocks could be running a version from a prior installation of the gem. The ‘gem_name’ in the require is set to the name of your gem automatically.

The ~docs:headers~ task also turns off evaluation of shell code blocks since these will often be such things as demonstrating the shell commands to install the gem, etc. Of course, you can override this for particular code blocks.

Those headers are in fact what I am using in this ~README~, and here is how they work.

*** Output Tables You can build table for ~org~ to display in the output by returning an array of arrays, which org-mode renders as a table in the output. You can add an hline to the output table by simply adding ~nil~ to the outer array where you want the hline to occur.

#+begin_src ruby

result = []
result << ['N', 'exp(N)']
result << nil
0.upto(10) do |n|
  result << [n/3.0, Math.exp(n/3.0)]
end
result

#+end_src

#+RESULTS: #+begin_example | N | exp(N) | |——————–+——————–| | 0.0 | 1.0 | | 0.3333333333333333 | 1.3956124250860895 | | 0.6666666666666666 | 1.9477340410546757 | | 1.0 | 2.718281828459045 | | 1.3333333333333333 | 3.7936678946831774 | | 1.6666666666666667 | 5.29449005047003 | | 2.0 | 7.38905609893065 | | 2.3333333333333335 | 10.312258501325767 | | 2.6666666666666665 | 14.391916095149892 | | 3.0 | 20.085536923187668 | | 3.3333333333333335 | 28.03162489452614 | #+end_example

*** Output Values Sometimes, however, you just want the result of the code block evaluated without building a table. To do so, just set the block header to ~:results value raw~.

To compute the value of an $1,000 asset gaining 5% continuously compounding interest over four years, you might do this:

#+begin_src ruby :results value raw

rate = 0.05
time = 4
principal = 1000
principal * Math.exp(rate * time)

#+end_src

#+RESULTS: #+begin_example 1221.40275816017 #+end_example

Apart from all the convenient markup that ~org-mode~ allows, the ability to easily demonstrate your gem’s code in this way is the real killer feature of writing your ~README~ in ~org-mode~ then exporting to markdown.

** Run the Code Blocks in README.org: ~rake docs:tangle~ You can invoke ~emacsclient~ to run all the example code blocks in your ~README.org~ that are set for evaluation:

Note that the ~tangle~ task relies on ~emacsclient~ to evaluate the code blocks in ~README.org~, so your Emacs ~init~ files should start [[info:emacs#Emacs Server][the Emacs server]] in order to work properly.

I use the following snippet in my Emacs init file:

#+begin_src emacs-lisp :eval no

(require 'server)
(unless (server-running-p)
  (message "Starting Emacs server")
  (server-start))

#+end_src

Then, you can evaluate all the code blocks in your ~README.org~ like this:

#+begin_src ruby :eval no

rake docs:tangle

#+end_src

With the default headers provided by ‘rake docs:headers`, the buffer is evaluated in a session so that code blocks can build on one another. However, `docs:tangle` kills any existing session buffer before it runs so that each buffer evaluation is independent of earlier runs.

** Ensure that a Badge is Present in ~README.md~: ~rake docs:badge~ It is reassuring to consumers of your gem that your gem passes its workflow tests on github. This task checks to see if a “badge” indicating success or failure is present and, if not, inserts one at the top of the ~README.org~ such that it will get exported to ~README.md~ when =rake docs:export= is run.

If you want to place the badge somewhere else in you ~README.org~, place the special comment ~#badge~ where you want the badge located and the task will place it there.

If there is already a badge present, the task will not modify the ~README.org~ file.

** Export ~README.org~ and ~CHANGELOG.org~ to Markdown: ~rake docs:export~ You can write the ~README~ in Emacs org-mode, using all its features including the execution of code blocks, and then export to git-flavored markdown.

If your repo contains both ~README.org~ and ~README.md~, github (and gitlab) will render the markdown version.

Github renders markdown better than it renders org files, so this helps with the readability of the ~README~ on github. For example, if you write the ~README~ in org mode without exporting to markdown, ~github~ will not render the ~#+RESULTS~ blocks unless you manually delete the ~#+RESULTS~ tag from the output. This is tedious and error-prone, so it is best that you write the ~README~ in ~org-mode~ and export to ~markdown~. That’s what this task enables.

Also note that when ~github~ renders your ~README.md~, it automatically adds a table of contents, so putting one in the ~README.org~ file is redundant. If you want to have one for your own purposes, just set the ~:noexport~ tag on it so it doesn’t get put into the ~README.md~

Less important, but still handy, you can also write the CHANGELOG in org mode and this task will convert it to markdown for display on github and rubygems.org.

#+begin_src ruby :eval no

rake docs:export

#+end_src

** Generate Yard Documents: ~rake docs:yard~ This task generates a suitable ~.yardopts~ file if none exists and then generates ~yard~ documents into the gem’s ~doc~ directory. It also makes sure that ~yard~ knows about your ~README.md~ file so user’s of your gem will be able to get an overview of how to use your gem.

#+begin_src ruby :eval no

rake docs:yard

#+end_src

** Generate an Overview Comment for the Main gem File: ~rake docs:overview~ Gem’s typically gather into a central library file all the require’s and other setup needed for the gem and the file is given the same name as the gem. For example, this gem uses the file =lib/gem_docs.rb= for this purpose. Since this =lib= directory is placed in the user’s =LOADPATH=, a =require ‘gem_docs’= or =require ‘<gemname>’= effectively initializes the gem.

By convention the comment immediately above the ‘module’ definition in your main library file is used by =yard= and =ri= as the overview for the gem.

#+begin_src ruby :eval no

rake docs:overview

#+end_src

This extracts the “Introduction” section from ~README.org~ and makes it the overview comment in the gem’s main library file. If it already exists, it replaces it with any newer version of the “Introduction” section, otherwise, it does not change the file.

Defined Under Namespace

Modules: Badge, Emacs, Header, Overview, Skeleton, Yard Classes: Config, Repo

Constant Summary collapse

README_ORG =
"README.org"
README_MD =
"README.md"
CHANGELOG_ORG =
"CHANGELOG.org"
CHANGELOG_MD =
"CHANGELOG.md"
VERSION =
"0.3.1"

Class Method Summary collapse

Class Method Details

.configObject



46
47
48
# File 'lib/gem_docs/config.rb', line 46

def self.config
  @config ||= Config.new
end

.configure {|config| ... } ⇒ Object

Yields:



42
43
44
# File 'lib/gem_docs/config.rb', line 42

def self.configure
  yield(config)
end

.installObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/gem_docs/tasks.rb', line 4

def self.install
  extend Rake::DSL

  file README_MD => README_ORG do
    print "Exporting \"#{README_ORG}\" → "
    GemDocs::Emacs.export_readme
  end

  file CHANGELOG_MD => CHANGELOG_ORG do
    print "Exporting \"#{CHANGELOG_ORG}\" → "
    GemDocs::Emacs.export_changelog
  end

  namespace :docs do
    desc "Evaluate code blocks in README.org"
    task :tangle => ["docs:skeleton:readme"] do
      print "Executing code blocks in #{README_ORG} ... "
      GemDocs::Emacs.tangle
    end

    desc "Export README.org → README.md"
    task :export => [:badge, README_MD, CHANGELOG_MD]

    desc "Extract overview from README.org and embed in lib/<gem>.rb for ri/yard"
    task :overview => ["docs:skeleton:readme", README_ORG] do
      print "Embedding overview extracted from #{GemDocs::README_ORG} into main gem file ... "
      if GemDocs::Overview.write_overview?
        puts "added"
      else
        puts "already present"
      end
    end

    namespace :skeleton do
      desc "Create skeleton README.org if one does not exist"
      task :readme do
        if GemDocs::Skeleton.make_readme?
          puts "README.org added"
        else
          puts "README.org already present"
        end
      end

      desc "Create skeleton CHANGELOG.org if one does not exist"
      task :changelog do
        if GemDocs::Skeleton.make_changelog?
          puts "CHANGELOG.org added"
        else
          puts "CHANGELOG.org already present"
        end
      end
    end

    desc "Insert #+PROPERTY headers at top of README.org for code blocks"
    task :header => "docs:skeleton:readme" do
      print "Inserting headers ... "
      if GemDocs::Header.write_header?
        puts "added"
      else
        puts "already present"
      end
    end

    desc "Generate YARD HTML documentation"
    task :yard => [:overview] do
      puts "Generating YARD documentation ... "
      GemDocs::Yard.generate
    end

    desc "Ensure GitHub Actions badge exists in README.org"
    task :badge => "docs:skeleton:readme" do
      print "Ensuring badges are in README.org ... "
      if GemDocs::Badge.ensure!
        puts "added"
      else
        puts "already present"
      end
    end

    desc "Run all documentation tasks (examples, readme, overview, yard, ri)"
    task :all => ["docs:skeleton:readme", "docs:skeleton:changelog", :header, :tangle, :export, :overview, :yard]
  end
end

.project_rootObject

Auto-detect project root (handles being run from subdirs)



296
297
298
299
300
# File 'lib/gem_docs.rb', line 296

def self.project_root
  here = Dir.pwd
  here = File.dirname(here) until !Dir['*.gemspec', 'Gemfile'].empty? || here == "/"
  here
end