UNPKG

11.6 kBJavaScriptView Raw
1#!/usr/bin/env node
2"use strict";
3var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12const fs = require("fs");
13const os = require("os");
14const path = require("path");
15const guess_terminal_1 = require("guess-terminal");
16const macosAppConfig = require("macos-app-config");
17const meow = require("meow");
18const parsers = require("term-schemes");
19const term_schemes_1 = require("term-schemes");
20const plist = require('plist');
21const fetch = require('node-fetch');
22const getStdin = require('get-stdin');
23const { render } = require('svg-term');
24const sander = require('@marionebl/sander');
25withCli(main, `
26 Usage
27 $ svg-term [options]
28
29 Options
30 --at timestamp of frame to render in ms [number]
31 --cast asciinema cast id to download [string], required if no stdin provided
32 --cursor display cursor, defaults to true [boolean]
33 --frame wether to frame the result with an application window [boolean]
34 --from lower range of timeline to render in ms [number]
35 --height height in lines [number]
36 --help print this help [boolean]
37 --out output file, emits to stdout if omitted
38 --padding distance between text and image bounds
39 --padding-x distance between text and image bounds on x axis
40 --padding-y distance between text and image bounds on y axis
41 --profile terminal profile file to use [file], requires --term
42 --term terminal profile format, requires [iterm2, xrdb, xresources, terminator, konsole, terminal, remmina, termite, tilda, xcfe] --profile
43 --to upper range of timeline to render in ms [number]
44 --width width in columns [number]
45
46 Examples
47 $ echo rec.json | svg-term
48 $ svg-term --cast 113643
49 $ svg-term --cast 113643 --out examples/parrot.svg
50`);
51function main(cli) {
52 return __awaiter(this, void 0, void 0, function* () {
53 const input = yield getInput(cli);
54 const error = cliError(cli);
55 if (!input) {
56 throw error(`svg-term: either stdin or --cast are required`);
57 }
58 const malformed = ensure(['height', 'width'], cli.flags, (name, val) => {
59 if (!(name in cli.flags)) {
60 return;
61 }
62 const candidate = parseInt(val, 10);
63 if (isNaN(candidate)) {
64 return new TypeError(`${name} expected to be number, received "${val}"`);
65 }
66 });
67 if (malformed.length > 0) {
68 throw error(`svg-term: ${malformed.map(m => m.message).join('\n')}`);
69 }
70 const missingValue = ensure(['cast', 'out', 'profile'], cli.flags, (name, val) => {
71 if (!(name in cli.flags)) {
72 return;
73 }
74 if (name === 'cast' && typeof val === 'number') {
75 return;
76 }
77 if (typeof val !== 'string') {
78 return new TypeError(`${name} expected to be string, received "${val}"`);
79 }
80 });
81 if (missingValue.length > 0) {
82 throw error(`svg-term: ${missingValue.map(m => m.message).join('\n')}`);
83 }
84 const shadowed = ensure(['at', 'from', 'to'], cli.flags, (name, val) => {
85 if (!(name in cli.flags)) {
86 return;
87 }
88 if (typeof val !== 'number' || isNaN(val)) {
89 return new TypeError(`${name} expected to be number, received "${val}"`);
90 }
91 if (name !== 'at' && typeof cli.flags.at === 'number') {
92 return new TypeError(`--at flag disallows --${name}`);
93 }
94 });
95 if (shadowed.length > 0) {
96 throw error(`svg-term: ${shadowed.map(m => m.message).join('\n')}`);
97 }
98 const term = guess_terminal_1.guessTerminal() || cli.flags.term;
99 const profile = term ? guessProfile(term) || cli.flags.profile : cli.flags.profile;
100 const guess = {
101 term,
102 profile
103 };
104 if (('term' in cli.flags) || ('profile' in cli.flags)) {
105 const unsatisfied = ['term', 'profile'].filter(n => !Boolean(guess[n]));
106 if (unsatisfied.length > 0) {
107 throw error(`svg-term: --term and --profile must be used together, ${unsatisfied.join(', ')} missing`);
108 }
109 }
110 const unknown = ensure(['term'], cli.flags, (name, val) => {
111 if (!(name in cli.flags)) {
112 return;
113 }
114 if (!(cli.flags.term in term_schemes_1.TermSchemes)) {
115 return new TypeError(`${name} expected to be one of ${Object.keys(term_schemes_1.TermSchemes).join(', ')}, received "${val}"`);
116 }
117 });
118 if (unknown.length > 0) {
119 throw error(`svg-term: ${unknown.map(m => m.message).join('\n')}`);
120 }
121 const p = guess.profile || '';
122 const isFileProfile = ['~', '/', '.'].indexOf(p.charAt(0)) > -1;
123 if (isFileProfile && 'profile' in cli.flags) {
124 const missing = !fs.existsSync(path.join(process.cwd(), cli.flags.profile));
125 if (missing) {
126 throw error(`svg-term: ${cli.flags.profile} must be readable file but was not found`);
127 }
128 }
129 const theme = getTheme(guess);
130 const svg = render(input, {
131 at: toNumber(cli.flags.at),
132 cursor: toBoolean(cli.flags.cursor, true),
133 from: toNumber(cli.flags.from),
134 paddingX: toNumber(cli.flags.paddingX || cli.flags.padding),
135 paddingY: toNumber(cli.flags.paddingY || cli.flags.padding),
136 to: toNumber(cli.flags.to),
137 height: toNumber(cli.flags.height),
138 theme,
139 width: toNumber(cli.flags.width),
140 window: toBoolean(cli.flags.frame, false)
141 });
142 if (typeof cli.flags.out === 'string') {
143 sander.writeFile(cli.flags.out, Buffer.from(svg));
144 }
145 else {
146 process.stdout.write(svg);
147 }
148 });
149}
150function ensure(names, flags, predicate) {
151 return names.map(name => predicate(name, flags[name])).filter(e => e instanceof Error);
152}
153function cliError(cli) {
154 return (message) => {
155 const err = new Error(message);
156 err.help = () => cli.help;
157 return err;
158 };
159}
160function getConfig(term) {
161 switch (term) {
162 case guess_terminal_1.GuessedTerminal.terminal: {
163 return macosAppConfig.sync(term)[0];
164 }
165 case guess_terminal_1.GuessedTerminal.iterm2: {
166 return macosAppConfig.sync(term)[0];
167 }
168 default:
169 return null;
170 }
171}
172function getPresets(term) {
173 const config = getConfig(term);
174 switch (term) {
175 case guess_terminal_1.GuessedTerminal.terminal: {
176 return config['Window Settings'];
177 }
178 case guess_terminal_1.GuessedTerminal.iterm2: {
179 return config['Custom Color Presets'];
180 }
181 default:
182 return null;
183 }
184}
185function guessProfile(term) {
186 if (os.platform() !== 'darwin') {
187 return null;
188 }
189 const config = getConfig(term);
190 if (!config) {
191 return null;
192 }
193 switch (term) {
194 case guess_terminal_1.GuessedTerminal.terminal: {
195 return config['Default Window Settings'];
196 }
197 case guess_terminal_1.GuessedTerminal.iterm2: {
198 const presets = config['Custom Color Presets'];
199 }
200 default:
201 return null;
202 }
203}
204function getInput(cli) {
205 return __awaiter(this, void 0, void 0, function* () {
206 if (cli.flags.cast) {
207 const response = yield fetch(`https://asciinema.org/a/${cli.flags.cast}.cast?dl=true`);
208 return response.text();
209 }
210 return yield getStdin();
211 });
212}
213function getParser(term) {
214 switch (term) {
215 case term_schemes_1.TermSchemes.iterm2:
216 return parsers.iterm2;
217 case term_schemes_1.TermSchemes.konsole:
218 return parsers.konsole;
219 case term_schemes_1.TermSchemes.remmina:
220 return parsers.remmina;
221 case term_schemes_1.TermSchemes.terminal:
222 return parsers.terminal;
223 case term_schemes_1.TermSchemes.terminator:
224 return parsers.terminator;
225 case term_schemes_1.TermSchemes.termite:
226 return parsers.termite;
227 case term_schemes_1.TermSchemes.tilda:
228 return parsers.tilda;
229 case term_schemes_1.TermSchemes.xcfe:
230 return parsers.xfce;
231 case term_schemes_1.TermSchemes.xresources:
232 return parsers.xresources;
233 case term_schemes_1.TermSchemes.xterm:
234 return parsers.xterm;
235 default:
236 throw new Error(`unknown term parser: ${term}`);
237 }
238}
239function getTheme(guess) {
240 if (guess.term === null || guess.profile === null) {
241 return null;
242 }
243 const p = guess.profile || '';
244 const isFileProfile = ['~', '/', '.'].indexOf(p.charAt(0)) > -1;
245 return isFileProfile
246 ? parseTheme(guess.term, guess.profile)
247 : extractTheme(guess.term, guess.profile);
248}
249function parseTheme(term, input) {
250 const parser = getParser(term);
251 return parser(String(fs.readFileSync(input)));
252}
253function extractTheme(term, name) {
254 if (!(term in guess_terminal_1.GuessedTerminal)) {
255 return null;
256 }
257 if (os.platform() !== 'darwin') {
258 return null;
259 }
260 if (term === guess_terminal_1.GuessedTerminal.hyper) {
261 const filename = path.resolve(os.homedir(), '.hyper.js');
262 const theme = parsers.hyper(String(fs.readFileSync(filename)), { filename });
263 return theme;
264 }
265 const presets = getPresets(term);
266 if (!presets) {
267 return null;
268 }
269 const theme = presets[name];
270 const parser = getParser(term);
271 if (!theme) {
272 return null;
273 }
274 switch (term) {
275 case guess_terminal_1.GuessedTerminal.iterm2: {
276 return parser(plist.build(theme));
277 }
278 case guess_terminal_1.GuessedTerminal.terminal:
279 return parser(plist.build(theme));
280 default:
281 return null;
282 }
283}
284function toNumber(input) {
285 if (!input) {
286 return null;
287 }
288 const candidate = parseInt(input, 10);
289 if (isNaN(candidate)) {
290 return null;
291 }
292 return candidate;
293}
294function toBoolean(input, fb) {
295 if (typeof input === 'undefined') {
296 return fb;
297 }
298 if (input === 'false') {
299 return false;
300 }
301 if (input === 'true') {
302 return true;
303 }
304 return input === true;
305}
306function withCli(fn, help = '') {
307 return main(meow(help))
308 .catch(err => {
309 setTimeout(() => {
310 if (typeof err.help === 'function') {
311 console.log(err.help());
312 console.log('\n', err.message);
313 process.exit(1);
314 }
315 throw err;
316 }, 0);
317 });
318}