Skip to content

Commit

Permalink
Implement working groff(1) driver
Browse files Browse the repository at this point in the history
  • Loading branch information
Alhadis committed Jan 28, 2019
1 parent 92623bc commit 1695391
Show file tree
Hide file tree
Showing 15 changed files with 676 additions and 214 deletions.
106 changes: 106 additions & 0 deletions lib/adapters/adapter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {which} from "./utils.mjs";


/**
* Abstract base class for an external program driver.
* @abstract
* @class
*/
export default class Adapter {

/**
* Handle basic initialisation common to all subclasses.
* @param {String} path
* @constructor
*/
constructor(path){
if(!path && !this.constructor.allowMissing){
const reason = "Cannot initialise without executable path";
throw new TypeError(`[${this.constructor.name}::constructor] ${reason}`);
}

// Initialise properties
this.path = String(path || "");
this.error = null;
this.ready = false;
}


/**
* Handle any tasks which need to run before the instance can be used.
* @param {Function} [handler=null]
* @return {Adapter} Resolves with a reference to the calling instance.
* @internal
*/
async resolve(handler = null){
if(this.resolvePromise)
return this.resolvePromise;
return this.resolvePromise = Promise.resolve().then(async () => {
handler && await handler.call(this);
this.ready = true;
return this;
}).catch(e => this.error = e);
}


/**
* Name of the physical executable. Deduced from the subclass name by default.
* @property {String} programName
* @readonly
*/
static get programName(){
return this.name.replace(/Adapter$/i, "").toLowerCase();
}


/**
* Whether an instance may be created without an executable path.
*
* By default, paths are required and a {@link TypeError} will be thrown when
* initialising an adapter without one. Override this if the subclass is able
* to function without the binary being available.
*
* @property {Boolean} [allowMissing=false]
* @readonly
*/
static get allowMissing(){
return false;
}


/**
* Locate the first executable in the system's PATH which matches the
* adapter subject's name, then initialise an instance with it.
*
* NOTE: Results are cached; to force a new lookup, unset the class's
* `resolvePromise` property before calling the method again. This is
* generally necessary if the contents of the PATH variable have been
* modified, or if executables have been added/removed since the last
* method call.
*
* @public
* @example <caption>Locate the host system's "foo" program</caption>
* class FooAdapter extends Adapter {}
* const foo = await FooAdapter.resolve();
* foo.path == "/usr/bin/foo";
* (foo === await FooAdapter.resolve()) == true;
*
* @return {Adapter|null}
* Resolves with an instance of the calling class if the program
* was found in the user's PATH; otherwise, resolves with `null`.
*/
static async resolve(){
if(this.resolvePromise)
return this.resolvePromise;

// Bail if the subclass's target program is unknown or unclear
const {programName} = this;
if(!programName){
const reason = "Invalid or undefined `programName` property";
throw new TypeError(`[Adapter.resolve] ${reason}`);
}

return this.resolvePromise = which(programName).then(path =>
path ? new this(path) : null);
}
}
24 changes: 10 additions & 14 deletions lib/adapters/man.mjs → lib/adapters/man/man.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import {resolveManRef, exec, which} from "./utils.mjs";
import {readFile} from "../postproc/node-shims.mjs";
import Adapter from "../adapter.mjs";
import {resolveManRef} from "../utils.mjs";
import {readFile} from "../../postproc/node-shims.mjs";
import {unzip} from "./unzip.mjs";


export default class ManAdapter {
export default class ManAdapter extends Adapter {

static get allowMissing(){
return true;
}

constructor(path, attr = {}){
super(path);
this.cache = new Map();
this.path = path || "";
this.optAll = attr.optAll || "-a";
this.optWhich = attr.optWhich || "-w";
}
Expand All @@ -23,7 +28,7 @@ export default class ManAdapter {
return results;

const args = [this.optAll, this.optWhich, section, name].filter(Boolean);
const {stdout} = await exec(this.path, args);
const {stdout} = await this.exec(args);
results = stdout.trim().split(/\n+/);
this.cache.set(cacheKey, results);
return results;
Expand All @@ -35,13 +40,4 @@ export default class ManAdapter {
data = await unzip(data);
return String(data);
}


static async resolveDefault(){
let path = "";

if(path = await which("man")){
return new ManAdapter(path);
}
}
}
2 changes: 1 addition & 1 deletion lib/adapters/unzip.mjs → lib/adapters/man/unzip.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* possible, entrusting users to have the necessary decompressor installed on
* their system.
*/
import {exec} from "./utils.mjs";
import {exec} from "../utils.mjs";
let zlib = null;


Expand Down
184 changes: 0 additions & 184 deletions lib/adapters/troff.mjs

This file was deleted.

Loading

0 comments on commit 1695391

Please sign in to comment.