Initial commit.

This commit is contained in:
Dylan R. E. Moonfire 2016-10-31 19:25:37 -05:00
commit 9e42f79fc2
16 changed files with 602 additions and 0 deletions

23
.editorconfig Normal file
View file

@ -0,0 +1,23 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
indent_brace_style = K&R
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 80
tab_width = 4
trim_trailing_whitespace = true
curly_bracket_next_line = true
[*.{js,ts}]
quote_type = double
[*.json]
indent_size = 2
tab_width = 2

25
.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
*~
.tscache
/.ntvs_analysis.dat
/.ntvs_analysis.dat.tmp
.baseDir.ts
npm-debug.log
*.tgz
src/*.js
src/*.js.map
src/*.d.ts
lib/
es6/
amd/
umd/
dist/
commonjs/
spec/*.js
spec/*.js.map
spec/*.d.ts
node_modules/
typings/

11
.npmignore Normal file
View file

@ -0,0 +1,11 @@
*~
*.tgz
TODO.markdown
.editorconfig
.jsbeautifyrc
src/
lib/spec/
typings/
tsd.json

11
LICENSE.md Normal file
View file

@ -0,0 +1,11 @@
The MIT License (MIT)
=====================
Copyright (c) 2016 Moonfire Games
---------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

84
README.md Normal file
View file

@ -0,0 +1,84 @@
markdowny
===========================
> A set of command-line utilities for working with Markdown + YAML files, specifically for novels and short stories.
## Setup and usage
Install `markdowny` using `npm` as a utility:
```sh
npm install --global markdowny
```
Basic usage is:
```sh
markdown verb *.markdown
```
## Verbs
### version
> Retrieves the version of the command-line utility and prints it out to the console.
```sh
markdowny version
```
### count
> Counts the number of words excluding the YAML header and displays it.
```sh
markdowny count *.markdown [options]
```
Options:
* `-t`, `--total`: Add a total line to the bottom that combines.
* `-s`, `--separator`: Comma-separates the numbers in the total.
### sections
> Displays each file as a heading 1 section with the title and contents provided.
Assuming that a file has a `title` and `summary` (which can be multiple lines) inside it, display a section for each file with the title as heading 1 and the contents of the `summary` value as the text for that section.
```sh
markdowny sections *.markdown -t title -f summary
```
Options:
* `-t`, `--title`: The name of the property to put in for the section. May be nested such as `author.name.first`. Defaults to `title`.
* `-f`, `--field`: The name of the property used for the contents of the section. Defaults to `summary`.
### table
> Displays a table of one or more fields inside the summary.
To list every file, it's title, the number of words inside it, and the list of secondary characters inside a nested value. The `:rs` makes the "Words" column right-aligned (`r`) and comma-separated (`s`).
```sh
markdowny table *.markdown -f _basename title _words characters.secondary -t File Title Words:rs "Secondary Characters"
```
Options:
* `-f`, `--fields`: A parameter-separated list of YAML fields to list in the table, in the order they should be shown. Defaults to `_basename title`. These may also have a format specifier, starting with `:` (see below). This may be a nested value, such as `characters.secondary`.
* `-t`, `--titles`: A parameter-separated list of titles for the columns. If there are more fields than titles, the field names will be used for the headers. These may have format specifiers.
* `--table-start`: The characters to insert at the beginning of the row. Defaults to `| `.
* `--table-end`: The characters to insert at the end of each row. Defaults to ` |`.
* `--table-delimiter`: The characters to insert between each field. Defaults to ` | `.
* `--no-table-rule`: Do not put a dashed line between the header and the various files.
* `--no-table-header`: Do not display the header titles. The format of the headers is still parsed and used.
* `--list-delimiter`: The characters to put between list items when inserted into a field. Defaults to `, `.
* `--prefix`: The filename prefix that is trimmed from the `_filename` propery.
There are a number of special properties that are included in all files.
* `_filename`: The full path of the filename, with the `--prefix` removed.
* `_basename`: The name of the file without a directory.
* `_words`: The number of words in the file, excluding the YAML header.

4
bin/markdowny Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
rdlkf() { [ -L "$1" ] && (local lk="$(readlink "$1")"; local d="$(dirname "$1")"; cd "$d"; local l="$(rdlkf "$lk")"; ([[ "$l" = /* ]] && echo "$l" || echo "$d/$l")) || echo "$1"; }
DIR="$(dirname "$(rdlkf "$0")")"
exec /usr/bin/env node --harmony "$DIR/../lib/cli.js" "$@"

4
bin/markdowny-wc Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
rdlkf() { [ -L "$1" ] && (local lk="$(readlink "$1")"; local d="$(dirname "$1")"; cd "$d"; local l="$(rdlkf "$lk")"; ([[ "$l" = /* ]] && echo "$l" || echo "$d/$l")) || echo "$1"; }
DIR="$(dirname "$(rdlkf "$0")")"
exec /usr/bin/env node --harmony "$DIR/../lib/cli.js" count "$@"

0
gulpfile.js Normal file
View file

42
package.json Normal file
View file

@ -0,0 +1,42 @@
{
"name": "markdowny",
"version": "0.0.0",
"description": "A set of command-line tools for working with Markdown files with YAML headers.",
"repository": {
"type": "git",
"url": "git+https://git.mfgames.com/author-intrusion/markdowny.git"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"markdown"
],
"author": {
"email": "d.moonfire@mfgames.com",
"name": "Dylan R. E. Moonfire",
"url": "https://mfgames.com/"
},
"license": "MIT",
"bugs": {
"url": "https://git.mfgames.com/author-intrusion/markdowny/issues"
},
"homepage": "https://git.mfgames.com/author-intrusion/markdowny#README",
"bin": {
"markdowny": "./bin/markdowny",
"markdowny-wc": "./bin/markdowny-wc"
},
"dependencies": {
"@types/node": "^6.0.46",
"@types/yargs": "0.0.34",
"add-commas": "0.0.4",
"dotted": "^0.1.1",
"gulp": "^3.9.1",
"lodash": "^4.16.5",
"markdown-table": "^1.0.0",
"typescript": "^2.0.6",
"yaml-front-matter": "^3.4.0",
"yargs": "^6.3.0"
}
}

37
src/cli.ts Normal file
View file

@ -0,0 +1,37 @@
import * as yargs from "yargs";
import * as count from "./count";
import * as sections from "./sections";
import * as table from "./table";
import * as version from "./version";
// Combine everything together to create a composite arguments which is used
// to parse the input and create the usage if required.
var argv = yargs
.usage("mfgames-writing-format command")
.help("help")
.showHelpOnFail(true, "Specify --help for available options")
.demand(1)
.command("count", count.help, count.args)
.command("sections", sections.help, sections.args)
.command("table", table.help, table.args)
.command("version", version.help, version.args)
.argv;
// Use the first command to determine what we are going to do.
switch (argv._[0]) {
case "count":
count.run(argv);
break;
case "sections":
sections.run(argv);
break;
case "table":
table.run(argv);
break;
case "version":
version.run(argv);
break;
default:
console.error(`Unknown command: ${argv._[0]}`);
break;
}

48
src/count.ts Normal file
View file

@ -0,0 +1,48 @@
import * as yargs from "yargs";
import * as table from "./table";
import * as scanner from "./scanner";
export var help = "Counts the number of works in the given input";
export function args(argv) {
return argv
.help("help")
.default('fields', ['_basename', '_words:r'])
.default('table-start', '')
.default('table-end', '')
.default('table-delimiter', ': ')
.boolean('table-rule')
.default('table-rule', false)
.boolean('table-header')
.default('table-header', false)
.alias('t', 'total')
.boolean('total')
.alias('s', 'separator')
.boolean('separator')
.alias('o', 'output')
.default('output', '-')
.demand(1)
.argv;
}
export function run(argv) {
var files = argv._.splice(1);
var data = scanner.scanFiles(argv, files);
if (argv.total)
{
argv.fields[1] += "t";
}
if (argv.separator)
{
argv.fields[1] += "s";
}
table.render(argv, data);
}

32
src/scanner.ts Normal file
View file

@ -0,0 +1,32 @@
import * as fs from "fs";
import * as path from "path";
import * as yamlFrontMatter from "yaml-front-matter";
/**
* Parses the input files and returns a list of YAML metadata with special
* columns for calculated values.
*/
export function scanFiles(argv, files: string[])
{
var list = [];
for (var file of files)
{
// Load the metadata from the given file.
var contents = fs.readFileSync(file, 'utf8')
var metadata = yamlFrontMatter.loadFront(contents);
// Add in the standard fields as "_" entries.
metadata._filename = file.replace(argv.prefix, "");
metadata._basename = path.basename(file);
metadata._words = metadata.__content
.replace("'", "")
.split(/\s+/g)
.length;
// Add the metadata to the list.
list.push(metadata);
}
return list;
}

40
src/sections.ts Normal file
View file

@ -0,0 +1,40 @@
import * as comma from "add-commas";
import * as dotted from "dotted";
import * as markdownTable from "markdown-table";
import * as scanner from "./scanner";
export var help = "Create a summary table of requested fields";
export function args(argv) {
return argv
.help("help")
.alias('f', 'field')
.default('field', 'summary')
.alias('t', 'title')
.default('title', 'title')
.alias('o', 'output')
.default('output', '-')
.demand(1)
.argv;
}
export function run(argv) {
var files = argv._.splice(1);
var data = scanner.scanFiles(argv, files);
render(argv, data);
}
export function render(argv, data) {
for (var item of data) {
var title = dotted.getNested(item, argv.title);
var value = dotted.getNested(item, argv.field);
console.log(`# ${title}`);
console.log();
console.log(value.replace("\n", "\n\n");
}
}

188
src/table.ts Normal file
View file

@ -0,0 +1,188 @@
import * as comma from "add-commas";
import * as dotted from "dotted";
import * as markdownTable from "markdown-table";
import * as scanner from "./scanner";
export var help = "Create a summary table of requested fields";
export function args(argv) {
return argv
.help("help")
.alias('f', 'fields')
.array('fields')
.default('fields', ['_basename', 'title'])
.alias('t', 'titles')
.array('titles')
.default('table-start', '| ')
.default('table-end', ' |')
.default('table-delimiter', ' | ')
.boolean('table-rule')
.default('table-rule', true)
.boolean('table-header')
.default('table-header', true)
.default('list-delimiter', ', ')
.alias('o', 'output')
.default('output', '-')
.default('prefix', '')
.demand(1)
.argv;
}
export function run(argv) {
var files = argv._.splice(1);
var data = scanner.scanFiles(argv, files);
render(argv, data);
}
export function render(argv, data) {
// Parse out the options and fields from the sources.
var columns = parse(argv, argv.titles, argv.fields);
// Create the header row.
var header = [];
for (var column of columns) {
header.push(column.header);
}
// Create the initial table with the header.
var table = [header];
// Loop through the results and get the fields we need to display.
var totals = ["Totals"];
for (var metadata of data) {
// Add the row to the table.
var row = [];
table.push(row);
// Loop through our fields and retrieve each one.
for (var index = 0; index < columns.length; index++) {
// Grab the value, even if nested.
var column = columns[index];
var value = dotted.getNested(metadata, column.ref);
// If we have a list, format it with spaces.
if (Array.isArray(value)) {
value = value.join(argv.listDelimiter);
}
// If we have totals, then add them.
if (column.total) {
totals[index] = totals[index] ? parseInt(totals[index]) : 0;
totals[index] += parseInt(value);
}
// If we have commas requested, then add those.
if (column.comma) {
value = comma(value);
}
// Add the final row to the table.
row.push(value);
}
}
// If we have totals, then add it at the bottom.
if (totals.length > 1) {
for (var index = 0; index < columns.length && index < totals.length; index++) {
var column = columns[index];
if (column.comma) {
totals[index] = comma(totals[index]);
}
}
table.push(totals);
}
// Format the results in a table.
var formattedTable = markdownTable(table, {
align: columns.map(c => c.align),
delimiter: argv.tableDelimiter,
start: argv.tableStart,
end: argv.tableEnd,
rule: argv.tableRule
});
// If we don't want the header row, strip off the first line.
if (!argv.tableHeader) {
formattedTable = formattedTable.substring(formattedTable.indexOf("\n") + 1);
}
console.log(formattedTable);
}
class Column {
ref: string;
header: string;
align: string = "l";
comma: boolean = false;
total: boolean = false;
public set(field, header) {
this.ref = this.parseSpecifier(field);
this.header = this.parseSpecifier(header ? header : field);
}
private parseSpecifier(spec): string {
// See if we have options.
var m = spec.match(/^(.*?):([lcr\.st]+)?$/);
if (m) {
// We have a match, so put the first part as the specifier.
spec = m[1];
for (var s of m[2]) {
switch (s) {
case 'c':
case 'l':
case 'r':
case '.':
this.align = s;
break;
case 's':
this.comma = true;
break;
case 't':
this.total = true;
break;
}
}
}
// Return the resulting specifier.
return spec;
}
}
function parse(argv, titles, fields): Column[] {
var columns: Column[] = [];
if (!titles) titles =[];
for (var index = 0; index < fields.length; index++) {
var column = new Column();
column.set(
fields[index],
titles.length >= index ? titles[index] : undefined);
columns.push(column);
}
return columns;
}
/* Notes
Comma-separated list of lists. I like spaces.
*/

37
src/tsconfig.json Normal file
View file

@ -0,0 +1,37 @@
{
"compilerOptions": {
"target": "es5",
"module": "umd",
"moduleResolution": "node",
"isolatedModules": false,
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true,
"inlineSourceMap": true,
"outDir": "../lib"
},
"filesGlob": [
"**/*.ts",
"**/*.tsx",
"!node_modules/**"
],
"compileOnSave": true,
"buildOnSave": false,
"files": [
"cli.ts",
"count.ts",
"scanner.ts",
"sections.ts",
"table.ts",
"version.ts"
],
"atom": {
"rewriteTsconfig": true
}
}

16
src/version.ts Normal file
View file

@ -0,0 +1,16 @@
import * as yargs from "yargs";
export var help = "Displays version information about the program";
export function args(argv) {
return argv
.help("help")
.demand(0)
.argv;
}
export function run(argv) {
let json = require("../package.json");
console.log(`${json.name} v${json.version}`);
}