├── .gitignore
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── UPGRADE.md
├── bin
└── um
├── doc
├── man1
│ ├── um-config.1
│ ├── um-edit.1
│ ├── um-help.1
│ ├── um-list.1
│ ├── um-read.1
│ ├── um-rm.1
│ ├── um-topic.1
│ ├── um-topics.1
│ └── um.1
├── um-config.md
├── um-edit.md
├── um-help.md
├── um-list.md
├── um-read.md
├── um-rm.md
├── um-topic.md
├── um-topics.md
└── um.md
├── lib
├── um.rb
└── um
│ ├── commands.rb
│ ├── options.rb
│ ├── preprocessor.rb
│ ├── topic.rb
│ └── umconfig.rb
├── libexec
├── um-config.rb
├── um-edit.rb
├── um-help.rb
├── um-list.rb
├── um-read.rb
├── um-rm.rb
├── um-topic.rb
└── um-topics.rb
├── templates
├── template.md
└── template.txt
├── um-completion.sh
├── um-completion.zsh
├── um.gemspec
└── version.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | notes.txt
2 | man_examples
3 | Gemfile.lock
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Sinclair Target
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # um
2 | `um` is a command-line utility for creating and maintaining your own set of
3 | `man`-like help pages. It is available for MacOS (via
4 | [Homebrew](https://brew.sh/)) and Linux (via AUR in Arch, otherwise via
5 | Homebrew, which is [now on Linux](https://docs.brew.sh/Homebrew-on-Linux)).
6 |
7 | ### Why?
8 | Have you seen how long `curl`'s man page is? How many times have you gone
9 | through it trying to figure out how to make a POST request?
10 |
11 | Man pages are written to be comprehensive, but what humans really need are the
12 | bullet points. Use `um` to write your own `man`-like help pages that reflect
13 | what you've learned about a command so far. That way you have an easy
14 | reference for the things you already know are useful.
15 |
16 | ### An Example
17 | Say you've just reminded yourself how `grep` works for the third time this
18 | month. You'd like to hold on to that precious knowledge so you don't have to go
19 | digging through the `grep` man page again. You can do that with `um`:
20 | ```
21 | $ um edit grep
22 | ```
23 | This will open your text editor, allowing you to record everything you want to
24 | remember about `grep`. Once you've saved what you've written, you can pull it
25 | up again as easily as you would any man page:
26 |
27 | ```
28 | $ um grep
29 | ```
30 | This will open your pager with whatever you might have for `grep`, say:
31 | ```
32 | GREP(shell) GREP(shell)
33 |
34 |
35 | NAME
36 | grep -- Print lines matching a pattern
37 |
38 | SYNOPSIS
39 | grep [OPTIONS...] pattern [FILE...]
40 |
41 | REGEX SYNTAX
42 | . Matches any character.
43 |
44 | ^ Anchors pattern to beginning of line.
45 |
46 | $ Anchors pattern to end of line.
47 |
48 | [] Character set. ^ for negation, - for range.
49 |
50 | OPTIONS
51 | -r Recursively search listed directories.
52 |
53 | -E Force grep to behave as egrep, accepting extended REGEXes.
54 |
55 |
56 |
57 | Um Pages September 26, 2017 GREP(shell)
58 | ```
59 |
60 | `um` supports several additional sub-commands. Among them are:
61 | * `um list`, which lists all the um pages you already have.
62 | * `um rm`, which removes an existing um page.
63 | * `um topic`, which switches between topic namespaces for your pages, allowing
64 | you to keep a separate set of um pages for css properties, for example.
65 |
66 | ### Um Page Format
67 | Man pages were [historically typeset using the `roff` typesetting
68 | system](http://twobithistory.org/2017/09/28/the-lineage-of-man.html). `roff`
69 | was basically an early LaTeX. Writing man pages using `roff` today is not very
70 | fun or intuitive.
71 |
72 | Happily, the Kramdown library can be used to convert Markdown documents to
73 | `roff`-like man pages. (Previously, `um` used Pandoc. See
74 | [UPGRADE.md](/UPGRADE.md) if the switch to Kramdown has broken your um pages.)
75 | By default, `um` expects you to write your um pages in Markdown so that it can
76 | convert them and pass them to the `man` program to view. You can, however,
77 | elect to just write your um pages as `.txt` files and view them without going
78 | through the `man` program.
79 |
80 | Below is the Markdown source that produced the `grep` listing above. Except for
81 | the Kramdown-specific attribute syntax (all the fiddly curly brace bits), it's
82 | all just Markdown:
83 | ```markdown
84 | # grep -- Print lines matching a pattern
85 | {:data-section="shell"}
86 | {:data-date="September 26, 2017"}
87 | {:data-extra="Um Pages"}
88 | {::comment}
89 | ^ The Kramdown "attribute list" which provides metadata for the page.
90 | The first heading must include the name of the command and a summary.
91 | {:/}
92 |
93 | ## SYNOPSIS
94 | {::comment}Top level Markdown headings become man section headings.{:/}
95 | **grep** [OPTIONS...] *pattern* [FILE...]
96 |
97 | ## REGEX SYNTAX
98 | {::comment}Here we're using a "definition list" to get that man page look.{:/}
99 |
100 | `.`
101 | : Matches any character.
102 |
103 | `^`
104 | : Anchors pattern to beginning of line.
105 |
106 | `$`
107 | : Anchors pattern to end of line.
108 |
109 | `[]`
110 | : Character set. ^ for negation, - for range.
111 |
112 | ## OPTIONS
113 | `-r`
114 | : Recursively search listed directories.
115 |
116 | `-E`
117 | : Force grep to behave as egrep, accepting extended REGEXes.
118 | ```
119 |
120 | See [Configuration](#config) below for more information on changing the default
121 | um page format. See the [Kramdown Man Converter
122 | Documentation](https://kramdown.gettalong.org/converter/man.html) for more
123 | information about Kramdown's flavor of Markdown and the formatting options
124 | available to you when you are writing a man page.
125 |
126 | `um`'s own [man pages](/doc) are written in Markdown and converted using
127 | Kramdown, so they could also make a good reference.
128 |
129 | ## Installation
130 |
131 |
132 |
133 |
134 |
135 | * **MacOS/Linux:** `um` is available via [Homebrew](http://brew.sh/):
136 | ```
137 | $ brew install um
138 | ```
139 | * **Arch Linux:** `um` is available via the AUR in two versions: the release version [`um`](https://aur.archlinux.org/packages/um/) and the latest master [`um-git`](https://aur.archlinux.org/packages/um-git/)
140 |
141 | ### Post-Installation
142 | A bash completion script for `um` is installed to
143 | `/usr/local/etc/bash_completion.d`, assuming you're using the default `brew`
144 | prefix. You may need to add the following lines to your `~/.bash_profile` to
145 | enable the completion:
146 | ```
147 | if [ -f $(brew --prefix)/etc/bash_completion.d/um-completion.sh ]; then
148 | . $(brew --prefix)/etc/bash_completion.d/um-completion.sh
149 | fi
150 | ```
151 |
152 | ## Help
153 | Refer to `um help` for comprehensive documentation of the sub-commands and
154 | options available for `um`. Man pages are also available.
155 |
156 |
157 | ## Configuration
158 | You can configure `um` using a file called `umconfig` placed in a folder called
159 | `.um` in your home directory. The syntax for setting an option is as follows:
160 | ```
161 | =
162 | e.g.
163 | pager = less
164 | ```
165 |
166 | You can set values for `pager`, `editor`, `default_topic`, `pages_directory`,
167 | and `pages_ext`. The defaults for these options are `less`, `vi`, `shell`,
168 | `~/.um`, and `.md` respectively. Before falling back to the defaults, `um` will
169 | attempt to read the values for `pager` and `editor` from the shell environment
170 | (i.e. the `PAGER` and `EDITOR` environment variables) if they are not
171 | specified in `umconfig`.
172 |
173 | Option | Default | Meaning
174 | --- | --- | ---
175 | `pager` | `less` | "Use this pager to view um pages."
176 | `editor` | `vi` | "Use this editor to edit um pages."
177 | `default_topic` | `shell` | Current topic if none is set.
178 | `pages_directory` | `~/.um` | Where to store um pages.
179 | `pages_ext` | `.md` | Unless `.md`, just the extension for your um pages.
180 |
181 | The `pager` configuration option is only used when `pages_ext` is not `.md`
182 | (the default). When `pages_ext` is `.md`, then `um` runs the pages through
183 | Kramdown before passing them to `man`. The pager used by `man` is determined by
184 | the `PAGER` and `MANPAGER` environment variables. See the man page for `man`
185 | for more information.
186 |
187 | So, if you wanted to store your um pages in your Dropbox folder, and you prefer
188 | emacs to vim, your config file might look like the following:
189 | ```
190 | editor = emacs
191 | pages_directory = /Users/myusername/Dropbox/um
192 | ```
193 |
194 | You can print the current configuration using `um config`.
195 |
196 | Finally, if you want to store your umconfig file in a different location, you
197 | can specify a new `.um` directory using the `UMCONFIG_HOME` environment
198 | variable. Adding `export UMCONFIG_HOME = ~/foo/bar` to your `.bash_profile`,
199 | for example, will cause `um` to look for a file called `umconfig` under
200 | `~/foo/bar` instead of the default `~/.um`.
201 |
202 | Specifying `UMCONFIG_HOME` also changes where `um` looks for template files
203 | (see next section).
204 |
205 | ## Page Templating
206 | If you place a file called `template.md` in `~/.um`, that file will serve as
207 | the basis for any new um pages you create (when `pages_ext` is set to `.md`).
208 | If you have `pages_ext` set to something else, perhaps `.txt`, then you should
209 | create a template file called `template.txt`.
210 |
211 | The template file is preprocessed so that the following variables are replaced
212 | before the file is used to create a new um page:
213 |
214 | Variable | Substitution
215 | --- | ---
216 | `$name` | The name of the page, which you specify when you call `um edit `.
217 | `$NAME` | The same as above, but uppercase.
218 | `$topic` | The name of the current topic.
219 | `$time` | The current time in RFC2822 format.
220 | `$date` | The current date as _Month_ _Day_, _Year_.
221 |
222 | If you do not have an appropriate template in your `~/.um` directory, `um`
223 | falls back to using its default templates. `um` ships with a default template
224 | for `.md` um pages and `.txt` um pages.
225 |
226 | ## Tips
227 | If you want to reset the topic to its default whenever you start a new shell,
228 | you can place the following line in your `.bash_profile` or `.bashrc`:
229 | ```
230 | um topic -d
231 | ```
232 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake/clean'
2 | require 'kramdown'
3 |
4 | SOURCE_DIR = 'doc'.freeze
5 | SOURCE_EXT = '.md'.freeze
6 |
7 | OUTPUT_DIR = 'doc/man1'.freeze
8 | OUTPUT_EXT = '.1'.freeze
9 |
10 | def source_to_out(pathmapable)
11 | pathmapable.pathmap("#{OUTPUT_DIR}/%f").ext(OUTPUT_EXT)
12 | end
13 |
14 | def out_to_source(pathmapable)
15 | pathmapable.pathmap("#{SOURCE_DIR}/%f").ext(SOURCE_EXT)
16 | end
17 |
18 | SOURCE_FILES = FileList["#{SOURCE_DIR}/*#{SOURCE_EXT}"]
19 | OUTPUT_FILES = source_to_out(SOURCE_FILES)
20 |
21 | task :default => [:man]
22 |
23 | desc 'Converts Markdown man pages to troff man files.'
24 | task :man => OUTPUT_FILES
25 | CLOBBER.include(OUTPUT_DIR)
26 |
27 | rule OUTPUT_EXT => [-> (name) { out_to_source(name) }, OUTPUT_DIR] do |t|
28 | doc = Kramdown::Document.new(File.read(t.source))
29 | File.write(t.name, doc.to_man)
30 | end
31 |
32 | directory OUTPUT_DIR
33 |
34 | desc 'Install gem under GEM_HOME'
35 | task :install do
36 | sh 'gem build um.gemspec'
37 | sh 'gem install --ignore-dependencies --no-document um*.gem'
38 | end
39 | CLEAN.include('um*.gem')
40 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Upgrading to 4.0
2 | `um` now uses the Kramdown gem to convert Markdown files into roff man pages.
3 | On the one hand, this is great, because Pandoc was a heavy external dependency.
4 | On the other hand, the Kramdown flavor of Markdown is not the same as the
5 | Pandoc flavor.
6 |
7 | If you have a lot of um pages written in Markdown and you are happy using
8 | Pandoc, **I encourage you not to upgrade to 4.0.** You will have to revise your
9 | um pages to make them work with Kramdown. `um` is an extrememly simple program
10 | and is unlikely to change in the future, so you aren't missing anything. The
11 | switch to Kramdown just makes `um` easier to package.
12 |
13 | If you want to learn more about how Kramdown expects a Markdown man page to
14 | look, see [this guide](https://kramdown.gettalong.org/converter/man.html).
15 |
16 | The old Pandoc format is documented in the Pandoc user's manual, [available
17 | here](https://pandoc.org/MANUAL.html#pandocs-markdown).
18 |
19 | If you really want to upgrade to 4.0 and migrate your Pandoc um pages, you can
20 | take a look at [the
21 | changes](https://github.com/sinclairtarget/um/commit/0158f32d65de822afc3660d82eb020b9ef3deccb#diff-be728250639dc5786a3e7dc2a5f89d92)
22 | made to `um`'s own man pages for an example of how to do that.
23 |
24 | You might also want to look at [these
25 | changes](https://github.com/sinclairtarget/um/commit/b701f6dd8459e48e36d6e27a431f7186dae4c282)
26 | to the README.
27 |
--------------------------------------------------------------------------------
/bin/um:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env ruby
2 |
3 | require 'um'
4 | require 'pathname'
5 |
6 | usage = %{usage: um
7 | um [ARGS...]
8 |
9 | The first form is equivalent to `um read `.
10 |
11 | Subcommands:
12 | um (l)ist List the available pages for the current topic.
13 | um (r)ead Read the given page under the current topic.
14 | um (e)dit Create or edit the given page under the current topic.
15 | um rm Remove the given page.
16 | um (t)opic [topic] Get or set the current topic.
17 | um topics List all topics.
18 | um (c)onfig [config key] Display configuration environment.
19 | um (h)elp [sub-command] Display this help message, or the help message for a sub-command.}
20 |
21 | command = ARGV.first
22 |
23 | if command.to_s.empty?
24 | $stderr.puts usage
25 | exit 1
26 | end
27 |
28 | script_path = Pathname.new(__FILE__).realpath
29 |
30 | if ARGV.length == 1
31 | case command
32 | when 'help', '--help', '-h'
33 | puts usage
34 | exit 0
35 | when 'version', '--version', '-v'
36 | version_path =
37 | File.expand_path('../version.txt', File.dirname(script_path))
38 |
39 | if File.exist?(version_path)
40 | puts File.read(version_path).strip
41 | else
42 | puts 'unknown'
43 | end
44 |
45 | exit 0
46 | end
47 | end
48 |
49 | libexec_dir = File.expand_path('../libexec', File.dirname(script_path))
50 | Commands.libexec(libexec_dir, command)
51 |
--------------------------------------------------------------------------------
/doc/man1/um-config.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-CONFIG" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-config \- View the current um configuration
5 | .SH "SYNOPSIS"
6 | \fBum config\fP [\-h | \(emhelp] [\fIoption_key\fP]
7 | .SH "DESCRIPTION"
8 | When run without arguments, this subcommand prints the entire configuration environment\.
9 | .P
10 | You can supply the name of a particular option to get only the value set for that option\.
11 | .P
12 | See README\.md for more information about the configuration options that can be set and how to set them\.
13 | .SH "OPTIONS"
14 | .TP
15 | \-h, \(emhelp
16 | Display help information for this subcommand\.
17 | .SH "ENVIRONMENT"
18 | When loading the configuration environment, \fBum\fP will check the PAGER and EDITOR environment variables before falling back to defaults for the \fBpager\fP and \fBeditor\fP configuration options\.
19 | .P
20 | If the \fBpager\fP and \fBeditor\fP configuration options are set explicitly in the umconfig file, then the PAGER and EDITOR environment variables are ignored\.
21 | .P
22 | \fBum\fP uses the \fBman\fP program to view markdown files that have been converted to the \fBtroff\fP man format\. In this case the \fBpager\fP configuration option is not consulted\. See man(1) for information about how \fBman\fP chooses a pager\.
23 | .SH "SEE ALSO"
24 | um(1), um\-help(1), um\-list(1), um\-edit(1), um\-read(1), um\-topic(1), um\-topics(1), um\-rm(1)
25 | .SH "AUTHORS"
26 | Sinclair Target \fB\fP\&\.
27 |
--------------------------------------------------------------------------------
/doc/man1/um-edit.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-EDIT" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-edit \- Edit an um page
5 | .SH "SYNOPSIS"
6 | \fBum edit\fP [\-t \fItopic\fP | \(emtopic \fItopic\fP] \fIpagename\fP
7 | .br
8 | \fBum edit\fP [\-h | \(emhelp]
9 | .SH "DESCRIPTION"
10 | This subcommand searches for an um page matching \fIpagename\fP and opens it in the configured editor\. See um\-config(1) for more information about configuring the editor\.
11 | .P
12 | If no page already exists matching \fIpagename\fP, then this command creates a new file based on the template for the currently configured \fBpages_ext\fP\&\. See um\-config(1) for more information about this configuration option\. See README\.md for more information on page templating\.
13 | .SH "OPTIONS"
14 | .TP
15 | \-h, \(emhelp
16 | Displays help information for this subcommand\.
17 | .TP
18 | \-t, \(emtopic
19 | Override the current topic for this invocation of \fBum\fP\&\. \fBum\fP will then assume that \fIpagename\fP refers to a page under this topic\.
20 | .SH "SEE ALSO"
21 | um(1), um\-config(1), um\-help(1), um\-list(1), um\-read(1), um\-topic(1), um\-topics(1), um\-rm(1)
22 | .SH "AUTHORS"
23 | Sinclair Target \fB\fP\&\.
24 |
--------------------------------------------------------------------------------
/doc/man1/um-help.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-HELP" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-help \- Get help about um subcommands
5 | .SH "SYNOPSIS"
6 | \fBum help\fP [\-h | \(emhelp] [\fIsubcommand\fP]
7 | .SH "DESCRIPTION"
8 | When run without providing a subcommand, \fBum help\fP is equivalent to \fBum \(emhelp\fP\&\.
9 | .P
10 | Otherwise, \fBum help\fP \fIsubcommand\fP is equivalent to running \fBum\fP \fIsubcommand\fP \fB\(emhelp\fP\&\.
11 | .SH "OPTIONS"
12 | .TP
13 | \-h, \(emhelp
14 | Display help about the help command\. (This one is for bad days\.)
15 | .SH "SEE ALSO"
16 | um(1), um\-config(1), um\-list(1), um\-edit(1), um\-read(1), um\-topic(1), um\-topics(1), um\-rm(1)
17 | .SH "AUTHORS"
18 | Sinclair Target \fB\fP\&\.
19 |
--------------------------------------------------------------------------------
/doc/man1/um-list.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-LIST" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-list \- List all um pages under the current topic
5 | .SH "SYNOPSIS"
6 | \fBum list\fP [\-h | \(emhelp] [\-t \fItopic\fP | \(emtopic \fItopic\fP]
7 | .SH "DESCRIPTION"
8 | This subcommand lists all the available um pages under the current topic\.
9 | .SH "OPTIONS"
10 | .TP
11 | \-h, \(emhelp
12 | Displays help information for this subcommand\.
13 | .TP
14 | \-t, \(emtopic
15 | Override the current topic for this invocation of \fBum\fP\&\. \fBum list\fP will then list the um pages under \fItopic\fP\&\.
16 | .SH "SEE ALSO"
17 | um(1), um\-config(1), um\-help(1), um\-read(1), um\-edit(1), um\-topic(1), um\-topics(1), um\-rm(1)
18 | .SH "AUTHORS"
19 | Sinclair Target \fB\fP\&\.
20 |
--------------------------------------------------------------------------------
/doc/man1/um-read.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-READ" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-read \- Read an um page
5 | .SH "SYNOPSIS"
6 | \fBum read\fP [\-t \fItopic\fP | \(emtopic \fItopic\fP] \fIpagename\fP
7 | .br
8 | \fBum read\fP [\-h | \(emhelp]
9 | .SH "DESCRIPTION"
10 | This subcommand searches for an um page matching \fIpagename\fP and displays it\.
11 | .P
12 | If the um page is a markdown file with the \fB\&\.md\fP extension, then \fBum\fP will first convert the markdown file to \fBtroff\fP man format before displaying the page using the \fBman\fP command\.
13 | .P
14 | Otherwise \fBum\fP will simply display the file using the configured pager\. See um\-config(1)\.
15 | .SH "OPTIONS"
16 | .TP
17 | \-h, \(emhelp
18 | Displays help information for this subcommand\.
19 | .TP
20 | \-t, \(emtopic
21 | Override the current topic for this invocation of \fBum\fP\&\. \fBum\fP will then assume that \fIpagename\fP refers to a page under this topic\.
22 | .SH "SEE ALSO"
23 | um(1), um\-config(1), um\-help(1), um\-list(1), um\-edit(1), um\-topic(1), um\-topics(1), um\-rm(1)
24 | .SH "AUTHORS"
25 | Sinclair Target \fB\fP\&\.
26 |
--------------------------------------------------------------------------------
/doc/man1/um-rm.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-RM" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-rm \- Remove um pages
5 | .SH "SYNOPSIS"
6 | \fBum rm\fP [\-t \fItopic\fP | \(emtopic \fItopic\fP] \fIpagename\fP
7 | .br
8 | \fBum rm\fP [\-h | \(emhelp]
9 | .SH "DESCRIPTION"
10 | This subcommand deletes um pages\. It will ask you for confirmation before actually deleting any files\.
11 | .SH "OPTIONS"
12 | .TP
13 | \-h, \(emhelp
14 | Displays help information for this subcommand\.
15 | .TP
16 | \-t, \(emtopic
17 | Override the current topic for this invocation of \fBum\fP\&\. \fBum\fP will then assume that \fIpagename\fP refers to a page under this topic\.
18 | .SH "SEE ALSO"
19 | um(1), um\-config(1), um\-help(1), um\-list(1), um\-read(1), um\-edit(1), um\-topic(1), um\-topics(1)
20 | .SH "AUTHORS"
21 | Sinclair Target \fB\fP\&\.
22 |
--------------------------------------------------------------------------------
/doc/man1/um-topic.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-TOPIC" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-topic \- Get and set the current um topic
5 | .SH "SYNOPSIS"
6 | \fBum topic\fP [\-h | \(emhelp] [\-d | \(emdefault] [\fItopic\fP]
7 | .SH "DESCRIPTION"
8 | The current topic determines which set of um pages are in use\.
9 | .P
10 | When run without any options or arguments, \fBum topic\fP prints the current um topic\.
11 | .P
12 | When a topic is provided as an argument, the current topic is changed to the provided topic\.
13 | .SH "OPTIONS"
14 | .TP
15 | \-h, \(emhelp
16 | Displays help information for this subcommand\.
17 | .TP
18 | \-d, \(emdefault
19 | Sets the topic to the default topic (\[u201c]shell\[u201d])\.
20 | .SH "SEE ALSO"
21 | um(1), um\-config(1), um\-help(1), um\-list(1), um\-edit(1), um\-read(1), um\-topics(1), um\-rm(1)
22 | .SH "AUTHORS"
23 | Sinclair Target \fB\fP\&\.
24 |
--------------------------------------------------------------------------------
/doc/man1/um-topics.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM\-TOPICS" "1" "September 26, 2017"
3 | .SH NAME
4 | um\-topics \- List all um topics
5 | .SH "SYNOPSIS"
6 | \fBum topics\fP [\-h | \(emhelp]
7 | .SH "DESCRIPTION"
8 | This subcommand lists all the topics that um knows about\.
9 | .P
10 | You can create a new topic by setting the current topic to something new\. See um\-topic(1)\.
11 | .SH "OPTIONS"
12 | .TP
13 | \-h, \(emhelp
14 | Displays help information for this subcommand\.
15 | .SH "SEE ALSO"
16 | um(1), um\-config(1), um\-help(1), um\-list(1), um\-edit(1), um\-read(1), um\-topic(1), um\-rm(1)
17 | .SH "AUTHORS"
18 | Sinclair Target \fB\fP\&\.
19 |
--------------------------------------------------------------------------------
/doc/man1/um.1:
--------------------------------------------------------------------------------
1 | .\" generated by kramdown
2 | .TH "UM" "1" "September 26, 2017"
3 | .SH NAME
4 | um \- Create your own man pages
5 | .SH "SYNOPSIS"
6 | \fBum\fP [\-t \fItopic\fP | \(emtopic \fItopic\fP] \fIpagename\fP
7 | .br
8 | \fBum\fP [\-h | \(emhelp] [\-v | \(emversion]
9 | .br
10 | \fBum\fP \fIsubcommand\fP [OPTIONS \.\.\.]
11 | .SH "DESCRIPTION"
12 | \fBum\fP helps you create and manage your own personal collection of man pages\. These custom man pages are called um pages, though by default they look exactly like man pages and are indeed viewed using the \fBman\fP utility\. You can record what you know about a command in an um page so that you can refer back to it later without wading through all the information in the offical man page for that command\.
13 | .P
14 | \fBum\fP can be invoked on its own or in conjunction with a subcommand\. Invoking \fBum\fP on its own is equivalent to invoking \fBum read\fP\&\. In this case, you must provide a \fIpagename\fP argument that tells \fBum\fP which page you want to display\.
15 | .P
16 | For more information about the available subcommands, refer to the SEE ALSO section below\.
17 | .SH "OPTIONS"
18 | These options are accepted by \fBum\fP when no subcommand is specified:
19 | .TP
20 | \-v, \(emversion
21 | Display the version number, or \[u2018]unknown\[u2019] if the version is not known\.
22 | .TP
23 | \-h, \(emhelp
24 | Display help information, including a list of subcommands\.
25 | .TP
26 | \-t, \(emtopic
27 | Set the topic for this invocation of \fBum\fP\&\. \fBum\fP will then assume that \fIpagename\fP refers to a page under this topic\.
28 | .SH "FILES"
29 | \fBum\fP can be configured using a configuration file stored at \fB~/\.um/umconfig\fP\&\. See README\.md for more information about how to set configuration options\. See um\-config(1) for information about how to view the current configuration\.
30 | .P
31 | \fBum\fP can also make use of template files stored under \fB~/\.um\fP\&\. See README\.md for more information about template files\.
32 | .SH "ENVIRONMENT"
33 | See um\-config(1) for more information about the environment variables accessed by \fBum\fP\&\.
34 | .SH "SEE ALSO"
35 | um\-config(1), um\-help(1), um\-list(1), um\-edit(1), um\-read(1), um\-topic(1), um\-topics(1), um\-rm(1)
36 | .SH "AUTHORS"
37 | Sinclair Target \fB\fP\&\.
38 |
--------------------------------------------------------------------------------
/doc/um-config.md:
--------------------------------------------------------------------------------
1 | # um-config(1) -- View the current um configuration
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um config** [-h \| --help] [*option_key*]
6 |
7 | ## DESCRIPTION
8 | When run without arguments, this subcommand prints the entire configuration
9 | environment.
10 |
11 | You can supply the name of a particular option to get only the value set for
12 | that option.
13 |
14 | See README.md for more information about the configuration options that can be
15 | set and how to set them.
16 |
17 | ## OPTIONS
18 | -h, --help
19 | : Display help information for this subcommand.
20 |
21 | ## ENVIRONMENT
22 | When loading the configuration environment, **um** will check the PAGER and
23 | EDITOR environment variables before falling back to defaults for the **pager**
24 | and **editor** configuration options.
25 |
26 | If the **pager** and **editor** configuration options are set explicitly in the
27 | umconfig file, then the PAGER and EDITOR environment variables are ignored.
28 |
29 | **um** uses the **man** program to view markdown files that have been converted
30 | to the **troff** man format. In this case the **pager** configuration option is
31 | not consulted. See man(1) for information about how **man** chooses a pager.
32 |
33 | ## SEE ALSO
34 | um(1), um-help(1), um-list(1), um-edit(1), um-read(1), um-topic(1),
35 | um-topics(1), um-rm(1)
36 |
37 | ## AUTHORS
38 | Sinclair Target ``.
39 |
--------------------------------------------------------------------------------
/doc/um-edit.md:
--------------------------------------------------------------------------------
1 | # um-edit(1) -- Edit an um page
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um edit** [-t *topic* | --topic *topic*] *pagename* \\
6 | **um edit** [-h \| --help]
7 |
8 | ## DESCRIPTION
9 | This subcommand searches for an um page matching *pagename* and opens it in the
10 | configured editor. See um-config(1) for more information about configuring the
11 | editor.
12 |
13 | If no page already exists matching *pagename*, then this command creates a new
14 | file based on the template for the currently configured **pages_ext**. See
15 | um-config(1) for more information about this configuration option. See
16 | README.md for more information on page templating.
17 |
18 | ## OPTIONS
19 | -h, --help
20 | : Displays help information for this subcommand.
21 |
22 | -t, --topic
23 | : Override the current topic for this invocation of **um**. **um** will then
24 | assume that *pagename* refers to a page under this topic.
25 |
26 | ## SEE ALSO
27 | um(1), um-config(1), um-help(1), um-list(1), um-read(1), um-topic(1),
28 | um-topics(1), um-rm(1)
29 |
30 | ## AUTHORS
31 | Sinclair Target ``.
32 |
--------------------------------------------------------------------------------
/doc/um-help.md:
--------------------------------------------------------------------------------
1 | # um-help(1) -- Get help about um subcommands
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um help** [-h \| --help] [*subcommand*]
6 |
7 | ## DESCRIPTION
8 | When run without providing a subcommand, **um help** is equivalent to **um
9 | --help**.
10 |
11 | Otherwise, **um help** *subcommand* is equivalent to running **um**
12 | *subcommand* **--help**.
13 |
14 | ## OPTIONS
15 | -h, --help
16 | : Display help about the help command. (This one is for bad days.)
17 |
18 | ## SEE ALSO
19 | um(1), um-config(1), um-list(1), um-edit(1), um-read(1), um-topic(1),
20 | um-topics(1), um-rm(1)
21 |
22 | ## AUTHORS
23 | Sinclair Target ``.
24 |
--------------------------------------------------------------------------------
/doc/um-list.md:
--------------------------------------------------------------------------------
1 | # um-list(1) -- List all um pages under the current topic
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um list** [-h | --help] [-t *topic* | --topic *topic*]
6 |
7 | ## DESCRIPTION
8 | This subcommand lists all the available um pages under the current topic.
9 |
10 | ## OPTIONS
11 | -h, --help
12 | : Displays help information for this subcommand.
13 |
14 | -t, --topic
15 | : Override the current topic for this invocation of **um**. **um list** will
16 | then list the um pages under *topic*.
17 |
18 | ## SEE ALSO
19 | um(1), um-config(1), um-help(1), um-read(1), um-edit(1), um-topic(1),
20 | um-topics(1), um-rm(1)
21 |
22 | ## AUTHORS
23 | Sinclair Target ``.
24 |
--------------------------------------------------------------------------------
/doc/um-read.md:
--------------------------------------------------------------------------------
1 | # um-read(1) -- Read an um page
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um read** [-t *topic* | --topic *topic*] *pagename* \\
6 | **um read** [-h \| --help]
7 |
8 | ## DESCRIPTION
9 | This subcommand searches for an um page matching *pagename* and displays it.
10 |
11 | If the um page is a markdown file with the **.md** extension, then **um** will
12 | first convert the markdown file to **troff** man format before displaying the
13 | page using the **man** command.
14 |
15 | Otherwise **um** will simply display the file using the configured pager. See
16 | um-config(1).
17 |
18 | ## OPTIONS
19 | -h, --help
20 | : Displays help information for this subcommand.
21 |
22 | -t, --topic
23 | : Override the current topic for this invocation of **um**. **um** will then
24 | assume that *pagename* refers to a page under this topic.
25 |
26 | ## SEE ALSO
27 | um(1), um-config(1), um-help(1), um-list(1), um-edit(1), um-topic(1),
28 | um-topics(1), um-rm(1)
29 |
30 | ## AUTHORS
31 | Sinclair Target ``.
32 |
--------------------------------------------------------------------------------
/doc/um-rm.md:
--------------------------------------------------------------------------------
1 | # um-rm(1) -- Remove um pages
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um rm** [-t *topic* | --topic *topic*] *pagename* \\
6 | **um rm** [-h \| --help]
7 |
8 | ## DESCRIPTION
9 | This subcommand deletes um pages. It will ask you for confirmation before
10 | actually deleting any files.
11 |
12 | ## OPTIONS
13 | -h, --help
14 | : Displays help information for this subcommand.
15 |
16 | -t, --topic
17 | : Override the current topic for this invocation of **um**. **um** will then
18 | assume that *pagename* refers to a page under this topic.
19 |
20 | ## SEE ALSO
21 | um(1), um-config(1), um-help(1), um-list(1), um-read(1), um-edit(1),
22 | um-topic(1), um-topics(1)
23 |
24 | ## AUTHORS
25 | Sinclair Target ``.
26 |
--------------------------------------------------------------------------------
/doc/um-topic.md:
--------------------------------------------------------------------------------
1 | # um-topic(1) -- Get and set the current um topic
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um topic** [-h \| --help] [-d \| --default] [*topic*]
6 |
7 | ## DESCRIPTION
8 | The current topic determines which set of um pages are in use.
9 |
10 | When run without any options or arguments, **um topic** prints the current um
11 | topic.
12 |
13 | When a topic is provided as an argument, the current topic is changed to the
14 | provided topic.
15 |
16 | ## OPTIONS
17 | -h, --help
18 | : Displays help information for this subcommand.
19 |
20 | -d, --default
21 | : Sets the topic to the default topic ("shell").
22 |
23 | ## SEE ALSO
24 | um(1), um-config(1), um-help(1), um-list(1), um-edit(1), um-read(1),
25 | um-topics(1), um-rm(1)
26 |
27 | ## AUTHORS
28 | Sinclair Target ``.
29 |
--------------------------------------------------------------------------------
/doc/um-topics.md:
--------------------------------------------------------------------------------
1 | # um-topics(1) -- List all um topics
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um topics** [-h \| --help]
6 |
7 | ## DESCRIPTION
8 | This subcommand lists all the topics that um knows about.
9 |
10 | You can create a new topic by setting the current topic to something new. See
11 | um-topic(1).
12 |
13 | ## OPTIONS
14 | -h, --help
15 | : Displays help information for this subcommand.
16 |
17 | ## SEE ALSO
18 | um(1), um-config(1), um-help(1), um-list(1), um-edit(1), um-read(1),
19 | um-topic(1), um-rm(1)
20 |
21 | ## AUTHORS
22 | Sinclair Target ``.
23 |
--------------------------------------------------------------------------------
/doc/um.md:
--------------------------------------------------------------------------------
1 | # um(1) -- Create your own man pages
2 | {:data-date="September 26, 2017"}
3 |
4 | ## SYNOPSIS
5 | **um** [-t *topic* | --topic *topic*] *pagename* \\
6 | **um** [-h \| --help] [-v \| --version] \\
7 | **um** *subcommand* [OPTIONS ...]
8 |
9 | ## DESCRIPTION
10 | **um** helps you create and manage your own personal collection of man pages.
11 | These custom man pages are called um pages, though by default they look exactly
12 | like man pages and are indeed viewed using the **man** utility. You can record
13 | what you know about a command in an um page so that you can refer back to it
14 | later without wading through all the information in the offical man page for
15 | that command.
16 |
17 | **um** can be invoked on its own or in conjunction with a subcommand. Invoking
18 | **um** on its own is equivalent to invoking **um read**. In this case, you must
19 | provide a *pagename* argument that tells **um** which page you want to display.
20 |
21 | For more information about the available subcommands, refer to the SEE ALSO
22 | section below.
23 |
24 | ## OPTIONS
25 | These options are accepted by **um** when no subcommand is specified:
26 |
27 | -v, --version
28 | : Display the version number, or 'unknown' if the version is not known.
29 |
30 | -h, --help
31 | : Display help information, including a list of subcommands.
32 |
33 | -t, --topic
34 | : Set the topic for this invocation of **um**. **um** will then assume that
35 | *pagename* refers to a page under this topic.
36 |
37 | ## FILES
38 | **um** can be configured using a configuration file stored at
39 | **~/.um/umconfig**. See README.md for more information about how to set
40 | configuration options. See um-config(1) for information about how to view the
41 | current configuration.
42 |
43 | **um** can also make use of template files stored under **~/.um**. See
44 | README.md for more information about template files.
45 |
46 | ## ENVIRONMENT
47 | See um-config(1) for more information about the environment variables accessed
48 | by **um**.
49 |
50 | ## SEE ALSO
51 | um-config(1), um-help(1), um-list(1), um-edit(1), um-read(1), um-topic(1),
52 | um-topics(1), um-rm(1)
53 |
54 | ## AUTHORS
55 | Sinclair Target ``.
56 |
--------------------------------------------------------------------------------
/lib/um.rb:
--------------------------------------------------------------------------------
1 | require_relative 'um/umconfig.rb'
2 | require_relative 'um/topic.rb'
3 | require_relative 'um/preprocessor.rb'
4 | require_relative 'um/commands.rb'
5 | require_relative 'um/options.rb'
6 |
--------------------------------------------------------------------------------
/lib/um/commands.rb:
--------------------------------------------------------------------------------
1 | module Commands
2 | LIBEXEC_FILENAME_FORMAT = 'um-%s.rb'.freeze
3 |
4 | ALIASES = {
5 | 'l' => 'list',
6 | 'r' => 'read',
7 | 'e' => 'edit',
8 | 's' => 'edit', # legacy support
9 | 'set' => 'edit', # legacy support
10 | 't' => 'topic',
11 | 'c' => 'config',
12 | 'h' => 'help'
13 | }.freeze
14 |
15 | # Executes the right Ruby file for the given command.
16 | def self.libexec(libexec_dir, cmd)
17 | file_path = file_path_for_command(libexec_dir, cmd)
18 |
19 | if file_path
20 | ARGV.shift
21 | run file_path
22 | else
23 | run file_path_for_command(libexec_dir, 'read')
24 | end
25 | end
26 |
27 | def self.file_path_for_command(libexec_dir, cmd)
28 | cmd = resolve_alias(cmd) || cmd
29 | filename = LIBEXEC_FILENAME_FORMAT % [cmd]
30 | path = "#{libexec_dir}/#{filename}"
31 |
32 | if File.exist?(path)
33 | path
34 | else
35 | nil
36 | end
37 | end
38 |
39 | class << self
40 | private
41 |
42 | def resolve_alias(cmd)
43 | ALIASES[cmd]
44 | end
45 |
46 | def run(file_path)
47 | exec(%{ruby "#{file_path}" #{ARGV.join(" ")}})
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/um/options.rb:
--------------------------------------------------------------------------------
1 | require 'optparse'
2 |
3 | # Thin wrapper for optparse
4 | class Options
5 | def initialize(parser, set_options)
6 | @parser = parser
7 | @set_options = set_options
8 | end
9 |
10 | def available
11 | @parser
12 | end
13 |
14 | def [](key)
15 | @set_options[key]
16 | end
17 |
18 | def self.parse!
19 | set_options = {}
20 |
21 | opts_parser = OptionParser.new do |opts|
22 | yield opts, set_options
23 | end
24 |
25 | begin
26 | opts_parser.parse! ARGV
27 | rescue OptionParser::InvalidOption => e
28 | $stderr.puts e
29 | $stderr.puts opts_parser
30 | exit 1
31 | end
32 |
33 | Options.new opts_parser, set_options
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/um/preprocessor.rb:
--------------------------------------------------------------------------------
1 | require 'time'
2 | require 'date'
3 |
4 | # template preprocessor
5 | module Preprocessor
6 | def self.preprocess(template, page_name, topic)
7 | template.gsub(/\$\w+/) do |match|
8 | case match
9 | when '$name'
10 | page_name
11 | when '$NAME'
12 | page_name.upcase
13 | when '$topic'
14 | topic
15 | when '$time'
16 | Time.now.rfc2822
17 | when '$date'
18 | Date.today.strftime("%B %d, %Y")
19 | else
20 | match
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/um/topic.rb:
--------------------------------------------------------------------------------
1 | require 'etc'
2 | require 'fileutils'
3 |
4 | module Topic
5 | def self.current(config)
6 | file_path = topic_file_path
7 | if File.exist? file_path
8 | read_topic(file_path)
9 | else
10 | default = config[:default_topic]
11 | write_topic(topic_file_path, default)
12 | default
13 | end
14 | end
15 |
16 | def self.set(topic)
17 | write_topic(topic_file_path, topic) unless topic.empty?
18 | end
19 |
20 | def self.clear
21 | file_path = topic_file_path
22 | File.delete file_path if File.exist? file_path
23 | end
24 |
25 | class << self
26 | private
27 |
28 | def topic_file_path
29 | tmp_dir_path = '/var/tmp/um/' + Etc.getlogin
30 | FileUtils.mkdir_p tmp_dir_path
31 |
32 | tmp_dir_path + '/current.topic'
33 | end
34 |
35 | def read_topic(path)
36 | line = File.readlines(path).first
37 | if line
38 | line.chomp
39 | else
40 | raise RuntimeError, 'Empty or corrupt .topic file.'
41 | end
42 | end
43 |
44 | def write_topic(path, topic)
45 | File.write(path, topic)
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/um/umconfig.rb:
--------------------------------------------------------------------------------
1 | require 'etc'
2 | require 'fileutils'
3 | require 'forwardable'
4 |
5 | class UmConfig
6 | extend Forwardable
7 |
8 | DEFAULT_CONFIG_DIR_REL_PATH = '~/.um'.freeze
9 | DEFAULT_TEMPLATE_REL_PATH = '../../templates/template'.freeze
10 |
11 | UM_MARKDOWN_EXT = '.md'.freeze
12 |
13 | DEFAULT_CONFIG = {
14 | editor: ENV['EDITOR'] || 'vi',
15 | pager: ENV['PAGER'] || 'less',
16 | pages_directory: File.expand_path('~/.um/pages'),
17 | default_topic: 'shell',
18 | pages_ext: UM_MARKDOWN_EXT
19 | }.freeze
20 |
21 | def_delegators :@config, :each, :[], :has_key?
22 |
23 | attr_reader :file_path
24 |
25 | def initialize(config_path)
26 | @file_path = config_path
27 | @parsed_config = {}
28 |
29 | if File.exist? config_path
30 | @parsed_config = parse_config(config_path)
31 | end
32 |
33 | @config = DEFAULT_CONFIG.merge(@parsed_config)
34 | end
35 |
36 | def overridden?(key)
37 | @parsed_config.has_key?(key)
38 | end
39 |
40 | def pages_directory
41 | File.expand_path(@config[:pages_directory])
42 | end
43 |
44 | # Returns the path that should be used for a new um page.
45 | #
46 | # This method respects the `:pages_ext` config option.
47 | def new_page_path(page_name, topic)
48 | "#{topic_directory(topic)}/#{page_name}#{@config[:pages_ext]}"
49 | end
50 |
51 | # Returns the path to an existing page, or nil if no page exists.
52 | #
53 | # This method returns the first existing page file regardless of extension.
54 | def existing_page_path(page_name, topic)
55 | Dir["#{topic_directory(topic)}/#{page_name}.*"].first
56 | end
57 |
58 | def topic_directory(topic)
59 | "#{pages_directory}/#{topic}"
60 | end
61 |
62 | def template_path
63 | File.expand_path(self.class.config_dir, File.dirname(__FILE__)) +
64 | "/template#{@config[:pages_ext]}"
65 | end
66 |
67 | def default_template_path
68 | File.expand_path(DEFAULT_TEMPLATE_REL_PATH, File.dirname(__FILE__)) +
69 | @config[:pages_ext]
70 | end
71 |
72 | # Sources the config file, returning the config environment as an UmConfig
73 | # object.
74 | def self.source
75 | config_path = File.expand_path(self.config_dir + '/umconfig')
76 | config = UmConfig.new config_path
77 |
78 | write_pages_directory(config.pages_directory)
79 |
80 | config
81 | end
82 |
83 | def self.config_dir
84 | ENV['UMCONFIG_HOME'] || DEFAULT_CONFIG_DIR_REL_PATH
85 | end
86 |
87 | private
88 |
89 | def parse_config(path)
90 | config = {}
91 |
92 | parse_error_occurred = false
93 | File.foreach(path) do |line|
94 | if line[/(\w+) = ([\w \/\(\)\.~-]+)/]
95 | config[$1.downcase.to_sym] = $2
96 | elsif line.chomp.length > 0
97 | $stderr.puts "Unable to parse configuration file line #{$.}: " +
98 | "'#{line.chomp}', skipping"
99 | parse_error_occurred = true
100 | end
101 | end
102 |
103 | $stderr.puts "Your configuration file is #{path}" if parse_error_occurred
104 | config
105 | end
106 |
107 | class << self
108 | private
109 |
110 | # Cache the current pages directory in a file. This is used by the bash
111 | # completion script to avoid spinning up Ruby.
112 | def write_pages_directory(pages_directory_path)
113 | tmp_dir_path = '/var/tmp/um/' + Etc.getlogin
114 | FileUtils.mkdir_p tmp_dir_path
115 |
116 | tmp_file_path = tmp_dir_path + '/current.pagedir'
117 | unless File.exist?(tmp_file_path)
118 | File.write(tmp_file_path, pages_directory_path)
119 | end
120 | end
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/libexec/um-config.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/um.rb'
2 |
3 | Options.parse! do |available_opts|
4 | available_opts.banner = 'usage: um config [config key]'
5 |
6 | available_opts.on('-h', '--help', 'Print this help message.') do
7 | puts available_opts
8 | exit 0
9 | end
10 | end
11 |
12 | config = UmConfig.source
13 |
14 | # If a valid key has been specified, print the value
15 | config_key = ARGV.first
16 | if config_key and config.has_key?(config_key.to_sym)
17 | puts config[config_key.to_sym]
18 | exit 0
19 | end
20 |
21 | # Otherwise print all key/value pairs in the configuration
22 | puts "Options prefixed by '*' are set in #{config.file_path}."
23 |
24 | config.each do |key, value|
25 | option = "#{key} = #{value}"
26 |
27 | if config.overridden?(key)
28 | puts '* ' + option
29 | else
30 | puts ' ' + option
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/libexec/um-edit.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'tempfile'
3 | require_relative '../lib/um.rb'
4 |
5 | options = Options.parse! do |available_opts, set_opts|
6 | available_opts.banner = 'usage: um edit [OPTIONS...] '
7 |
8 | available_opts.on(
9 | '-t', '--topic TOPIC', 'Set topic for a single invocation.'
10 | ) do |topic|
11 | set_opts[:topic] = topic
12 | end
13 |
14 | available_opts.on('-h', '--help', 'Print this help message.') do
15 | puts available_opts
16 | exit 0
17 | end
18 | end
19 |
20 | page_name = ARGV.first
21 | if page_name.to_s.empty?
22 | $stderr.puts options.available
23 | exit 1
24 | end
25 |
26 | config = UmConfig.source
27 | topic = options[:topic] || Topic.current(config)
28 | page_path = config.existing_page_path(page_name, topic)
29 |
30 | temp_file = nil
31 | unless page_path
32 | page_path = config.new_page_path(page_name, topic)
33 |
34 | # set up template
35 | default_template_path = config.default_template_path
36 | template_path = config.template_path
37 |
38 | FileUtils.mkdir_p(File.dirname(page_path))
39 |
40 | if File.exist? template_path
41 | FileUtils.cp template_path, page_path
42 | elsif File.exist? default_template_path
43 | FileUtils.cp default_template_path, page_path
44 | else
45 | FileUtils.touch page_path
46 | end
47 |
48 | template = File.read page_path
49 | processed_template = Preprocessor.preprocess template, page_name, topic
50 | File.write page_path, processed_template
51 |
52 | # create temp copy of preprocessed file so we can diff later
53 | temp_file = Tempfile.new('um')
54 | temp_file.write processed_template
55 | temp_file.flush
56 | end
57 |
58 | begin
59 | editor = config[:editor]
60 | system(%{#{editor} "#{page_path}"})
61 |
62 | if temp_file
63 | `diff "#{page_path}" "#{temp_file.path}"`
64 |
65 | if $?.success? # files are the same
66 | `rm -f "#{page_path}"`
67 | end
68 | end
69 | ensure
70 | if temp_file
71 | temp_file.unlink
72 | temp_file.close
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/libexec/um-help.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/um.rb'
2 |
3 | def run_help_only(file_name)
4 | exec(%{ruby "#{file_name}" --help})
5 | end
6 |
7 | options = Options.parse! do |opts|
8 | opts.banner = 'usage: um help '
9 |
10 | opts.on('-h', '--help', 'Print this help message.') do
11 | puts opts
12 | exit 0
13 | end
14 | end
15 |
16 | sub_command = ARGV.first
17 | if sub_command.to_s.empty?
18 | $stderr.puts options.available
19 | exit 1
20 | end
21 |
22 | libexec_dir = File.expand_path('..', __FILE__)
23 | file_name = Commands.file_path_for_command(libexec_dir, sub_command)
24 | if file_name
25 | run_help_only file_name
26 | else
27 | $stderr.puts 'No sub-command with that name.'
28 | end
29 |
--------------------------------------------------------------------------------
/libexec/um-list.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/um.rb'
2 |
3 | options = Options.parse! do |available_opts, set_opts|
4 | available_opts.banner = 'usage: um list [OPTIONS...]'
5 |
6 | available_opts.on(
7 | '-t', '--topic TOPIC', 'Set topic for a single invocation.'
8 | ) do |topic|
9 | set_opts[:topic] = topic
10 | end
11 |
12 | available_opts.on('-h', '--help', 'Print this help message.') do
13 | puts available_opts
14 | exit 0
15 | end
16 | end
17 |
18 | config = UmConfig.source
19 | topic = options[:topic] || Topic.current(config)
20 |
21 | topic_directory = config.topic_directory(topic)
22 | unless Dir.exist? topic_directory
23 | $stderr.puts %{No pages found for topic "#{topic}."}
24 | exit 2
25 | end
26 |
27 | if $stdout.isatty
28 | exec(%{ls "#{topic_directory}" | sed 's/\.[[:alnum:]]*$//' | column})
29 | else
30 | exec(%{ls "#{topic_directory}" | sed 's/\.[[:alnum:]]*$//' })
31 | end
32 |
--------------------------------------------------------------------------------
/libexec/um-read.rb:
--------------------------------------------------------------------------------
1 | require 'tempfile'
2 | require 'kramdown'
3 | require_relative '../lib/um.rb'
4 |
5 | options = Options.parse! do |available_opts, set_opts|
6 | available_opts.banner = 'usage: um read [OPTIONS...] '
7 |
8 | available_opts.on(
9 | '-t', '--topic TOPIC', 'Set topic for a single invocation.'
10 | ) do |topic|
11 | set_opts[:topic] = topic
12 | end
13 |
14 | available_opts.on('-h', '--help', 'Print this help message.') do
15 | puts available_opts
16 | exit 0
17 | end
18 | end
19 |
20 | page_name = ARGV.first
21 | if page_name.to_s.empty?
22 | $stderr.puts options.available
23 | exit 1
24 | end
25 |
26 | config = UmConfig.source
27 | topic = options[:topic] || Topic.current(config)
28 | page_path = config.existing_page_path(page_name, topic)
29 |
30 | unless page_path
31 | msg = %{No um page found for "#{page_name}" under topic "#{topic}."}
32 | $stderr.puts msg
33 | exit 2
34 | end
35 |
36 | if File.extname(page_path) == UmConfig::UM_MARKDOWN_EXT
37 | begin
38 | temp_file = Tempfile.new('um')
39 | doc = Kramdown::Document.new(File.read(page_path))
40 | File.write(temp_file.path, doc.to_man)
41 |
42 | system(%{man "#{temp_file.path}"})
43 | ensure
44 | temp_file.unlink
45 | end
46 | else
47 | pager = config[:pager]
48 | exec(%{#{pager} "#{page_path}"})
49 | end
50 |
--------------------------------------------------------------------------------
/libexec/um-rm.rb:
--------------------------------------------------------------------------------
1 | require 'shellwords'
2 | require_relative '../lib/um.rb'
3 |
4 | options = Options.parse! do |available_opts, set_opts|
5 | available_opts.banner = 'usage: um rm [OPTIONS...] '
6 |
7 | available_opts.on(
8 | '-t', '--topic TOPIC', 'Set topic for a single invocation.'
9 | ) do |topic|
10 | set_opts[:topic] = topic
11 | end
12 |
13 | available_opts.on('-h', '--help', 'Print this help message.') do
14 | puts available_opts
15 | exit 0
16 | end
17 | end
18 |
19 | page_name = ARGV.first
20 | if page_name.to_s.empty?
21 | $stderr.puts options.available
22 | exit 1
23 | end
24 |
25 | config = UmConfig.source
26 | topic = options[:topic] || Topic.current(config)
27 | page_path = config.existing_page_path(page_name, topic)
28 |
29 | unless page_path
30 | msg = %{No um page found for "#{page_name}" under topic "#{topic}."}
31 | $stderr.puts msg
32 | exit 2
33 | end
34 |
35 | puts "Are you sure you want to remove the page for '#{page_name}'? (y/n):"
36 | input = $stdin.gets.chomp
37 |
38 | if input == 'y'
39 | exec(%{rm "#{page_path}"})
40 | end
41 |
--------------------------------------------------------------------------------
/libexec/um-topic.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/um.rb'
2 |
3 | Options.parse! do |available_opts|
4 | available_opts.banner = 'usage: um topic [OPTIONS...] [topic]'
5 |
6 | available_opts.on('-d', '--default', 'Resets the topic to the default.') do
7 | Topic.clear
8 | exit 0
9 | end
10 |
11 | available_opts.on('-h', '--help', 'Print this help message.') do
12 | puts available_opts
13 | exit 0
14 | end
15 | end
16 |
17 | config = UmConfig.source
18 | topic = ARGV.first
19 |
20 | if topic.to_s.empty?
21 | puts Topic.current(config)
22 | else
23 | Topic.set topic
24 | end
25 |
--------------------------------------------------------------------------------
/libexec/um-topics.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/um.rb'
2 |
3 | Options.parse! do |available_opts|
4 | available_opts.banner = 'usage: um topics [OPTIONS...]'
5 |
6 | available_opts.on('-h', '--help', 'Print this help message.') do
7 | puts available_opts
8 | exit 0
9 | end
10 | end
11 |
12 |
13 | config = UmConfig.source
14 | files = Dir["#{config.pages_directory}/*"].map { |file| File.basename(file) }
15 |
16 | output = files.join("\n")
17 |
18 | if $stdout.isatty
19 | exec(%{echo "#{output}" | column})
20 | else
21 | puts output
22 | end
23 |
--------------------------------------------------------------------------------
/templates/template.md:
--------------------------------------------------------------------------------
1 | # $name --
2 | {:data-section="$topic"}
3 | {:data-date="$date"}
4 | {:data-extra="Um Pages"}
5 |
6 | ## SYNOPSIS
7 |
8 |
9 | ## DESCRIPTION
10 |
11 |
12 | ## OPTIONS
13 |
14 |
--------------------------------------------------------------------------------
/templates/template.txt:
--------------------------------------------------------------------------------
1 | $name -
2 |
--------------------------------------------------------------------------------
/um-completion.sh:
--------------------------------------------------------------------------------
1 | # Defines a shell function used to provide tab completion for um
2 | _um()
3 | {
4 | local current previous subs user topic_path topic pages_directory
5 |
6 | COMPREPLY=()
7 |
8 | current="${COMP_WORDS[COMP_CWORD]}"
9 | previous="${COMP_WORDS[COMP_CWORD-1]}"
10 | subs="list read edit rm topic topics config help"
11 |
12 | user=$(whoami)
13 |
14 | # retrieve topic from tmp file, or fall back to invoking program
15 | topic_path="/var/tmp/um/${user}/current.topic"
16 | if [[ -e $topic_path ]]; then
17 | topic=$(cat $topic_path)
18 | else
19 | topic=$(um topic)
20 | fi
21 |
22 | # retrieve pages directory from tmp file, or fall back to invoking program
23 | pagedir_path="/var/tmp/um/${user}/current.pagedir"
24 | if [[ -e $pagedir_path ]]; then
25 | pages_directory=$(cat $pagedir_path)
26 | else
27 | pages_directory=$(um config pages_directory)
28 | fi
29 |
30 | case "${previous}" in
31 | list|topics|config) # Unsupported / Nonsensical
32 | return 0
33 | ;;
34 | read|edit|rm)
35 | local pages=$(ls "${pages_directory}/${topic}" | sed 's/\..*$//')
36 | COMPREPLY=($(compgen -W "${pages}" -- ${current}))
37 | return 0
38 | ;;
39 | topic)
40 | COMPREPLY=($(cd "${pages_directory}" && compgen -d ${current}))
41 | return 0
42 | ;;
43 | help)
44 | COMPREPLY=($(compgen -W "${subs}" -- ${current}))
45 | return 0
46 | ;;
47 | *)
48 | ;;
49 | esac
50 |
51 | local pages=$(ls "${pages_directory}/${topic}" | sed 's/\..*$//')
52 | COMPREPLY=($(compgen -W "${subs} ${pages}" -- ${current}))
53 | return 0
54 | }
55 |
56 | complete -F _um um
57 |
--------------------------------------------------------------------------------
/um-completion.zsh:
--------------------------------------------------------------------------------
1 | #compdef _um um
2 |
3 | _um() {
4 | local state line
5 | typeset -A opt_args
6 |
7 | user=$(whoami)
8 |
9 | # retrieve topic from tmp file, or fall back to invoking program
10 | topic_path="/var/tmp/um/${user}/current.topic"
11 | if [[ -e $topic_path ]]; then
12 | topic=$(cat $topic_path)
13 | else
14 | topic=$(um topic)
15 | fi
16 |
17 | # retrieve pages directory from tmp file, or fall back to invoking program
18 | pagedir_path="/var/tmp/um/${user}/current.pagedir"
19 | if [[ -e $pagedir_path ]]; then
20 | pages_directory=$(cat $pagedir_path)
21 | else
22 | pages_directory=$(um config pages_directory)
23 | fi
24 |
25 | local -a pages=(${(f)"$(ls "${pages_directory}/${topic}" | \
26 | sed 's/\.[^\.]*$//')"})
27 |
28 | # First argument to the script is a subcommand the second is always
29 | # another argument
30 | _arguments \
31 | '1: :->subcommand' \
32 | '2: :->argument'
33 |
34 | # Complete differently depending on whether we are at position 1 or 2
35 | case $state in
36 | subcommand)
37 | local -a subcommands
38 | subcommands=(
39 | 'config:Display configuration environment' \
40 | 'edit:Create or edit the given page under the current topic' \
41 | 'help:Display a help message, or the help message for a sub-command' \
42 | 'list:List the available pages for the current topic' \
43 | 'read:Read the given page under the current topic' \
44 | 'rm:Remove the given page' \
45 | 'topic:Get or set the current topic' \
46 | 'topics:List all topics'
47 | )
48 | # Complete all subcommands (only the long versions)
49 | _describe 'Subcommands' subcommands
50 | # Additionally complete all pages, e.g. 'um grep'
51 | compadd $pages
52 | ;;
53 | argument)
54 | # Now we are at position 2 and need to complete differently
55 | # depending on the chosen subcommand
56 | case $line[1] in
57 | rm|edit|read|r|e)
58 | compadd $pages
59 | ;;
60 | topic|t)
61 | local -a topics=(${(f)"$(ls "${pages_directory}")"})
62 | compadd $topics
63 | ;;
64 | help|h)
65 | compadd config edit help list read rm topic topics
66 | ;;
67 | esac
68 | ;;
69 | esac
70 | }
71 | # vim: expandtab sw=4 ts=4:
72 |
--------------------------------------------------------------------------------
/um.gemspec:
--------------------------------------------------------------------------------
1 | require 'rake'
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = 'um'
5 | spec.version = File.read('version.txt').chomp
6 | spec.summary = 'Um man tool'
7 | spec.description = 'Command-line utility for creating and maintaining personal man pages'
8 | spec.authors = ['Sinclair Target']
9 | spec.email = 'sinclairtarget@gmail.com'
10 | spec.homepage = 'https://github.com/sinclairtarget/um'
11 | spec.licenses = ['MIT']
12 | spec.executables = ['um']
13 |
14 | spec.files = FileList[
15 | 'lib/**/*.rb',
16 | 'libexec/**/*.rb',
17 | 'bin/um',
18 | 'version.txt',
19 | 'templates/**/*'
20 | ]
21 |
22 | spec.add_runtime_dependency 'kramdown', ['~> 1.17']
23 | end
24 |
--------------------------------------------------------------------------------
/version.txt:
--------------------------------------------------------------------------------
1 | 4.2.0
2 |
--------------------------------------------------------------------------------