(feature) add support for expanding paths on Windows + added more usage examples to cli

This commit is contained in:
Kim Biesbjerg 2020-03-06 13:53:46 +01:00
parent 7b94c9b9a5
commit b813ec0063
6 changed files with 81 additions and 36 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ npm-debug.log*
# Compiled files # Compiled files
dist dist
src/**/*.js
tests/**/*.js
# Extracted strings # Extracted strings
strings.json strings.json

6
package-lock.json generated
View File

@ -107,6 +107,12 @@
"any-observable": "^0.3.0" "any-observable": "^0.3.0"
} }
}, },
"@types/braces": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz",
"integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==",
"dev": true
},
"@types/chai": { "@types/chai": {
"version": "4.2.10", "version": "4.2.10",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.10.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.10.tgz",

View File

@ -61,6 +61,7 @@
}, },
"config": {}, "config": {},
"devDependencies": { "devDependencies": {
"@types/braces": "^3.0.0",
"@types/chai": "^4.2.10", "@types/chai": "^4.2.10",
"@types/flat": "^5.0.0", "@types/flat": "^5.0.0",
"@types/glob": "^7.1.1", "@types/glob": "^7.1.1",
@ -68,6 +69,7 @@
"@types/mocha": "^7.0.2", "@types/mocha": "^7.0.2",
"@types/node": "^12.12.29", "@types/node": "^12.12.29",
"@types/yargs": "^15.0.4", "@types/yargs": "^15.0.4",
"braces": "^3.0.2",
"chai": "^4.2.0", "chai": "^4.2.0",
"husky": "^4.2.3", "husky": "^4.2.3",
"lint-staged": "^10.0.8", "lint-staged": "^10.0.8",

View File

@ -1,4 +1,3 @@
import * as fs from 'fs';
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import { ExtractTask } from './tasks/extract.task'; import { ExtractTask } from './tasks/extract.task';
@ -14,9 +13,22 @@ import { NullAsDefaultValuePostProcessor } from '../post-processors/null-as-defa
import { PurgeObsoleteKeysPostProcessor } from '../post-processors/purge-obsolete-keys.post-processor'; import { PurgeObsoleteKeysPostProcessor } from '../post-processors/purge-obsolete-keys.post-processor';
import { CompilerInterface } from '../compilers/compiler.interface'; import { CompilerInterface } from '../compilers/compiler.interface';
import { CompilerFactory } from '../compilers/compiler.factory'; import { CompilerFactory } from '../compilers/compiler.factory';
import { normalizePaths } from '../utils/fs-helpers';
import { donateMessage } from '../utils/donate'; import { donateMessage } from '../utils/donate';
export const cli = yargs // First parsing pass to be able to access pattern argument for use input/output arguments
const y = yargs
.option('patterns', {
alias: 'p',
describe: 'Default patterns',
type: 'array',
default: ['/**/*.html', '/**/*.ts'],
hidden: true
});
const parsed = y.parse();
export const cli = y
.usage('Extract strings from files for translation.\nUsage: $0 [options]') .usage('Extract strings from files for translation.\nUsage: $0 [options]')
.version(require(__dirname + '/../../package.json').version) .version(require(__dirname + '/../../package.json').version)
.alias('version', 'v') .alias('version', 'v')
@ -30,19 +42,9 @@ export const cli = yargs
normalize: true, normalize: true,
required: true required: true
}) })
.check(options => { .coerce('input', (input: string[]) => {
options.input.forEach((dir: string) => { const paths = normalizePaths(input, parsed.patterns);
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) { return paths;
throw new Error(`The path you supplied was not found: '${dir}'`);
}
});
return true;
})
.option('patterns', {
alias: 'p',
describe: 'Extract strings from the following file patterns',
type: 'array',
default: ['/**/*.html', '/**/*.ts']
}) })
.option('output', { .option('output', {
alias: 'o', alias: 'o',
@ -51,16 +53,20 @@ export const cli = yargs
normalize: true, normalize: true,
required: true required: true
}) })
.coerce('output', (output: string[]) => {
const paths = normalizePaths(output, parsed.patterns);
return paths;
})
.option('format', { .option('format', {
alias: 'f', alias: 'f',
describe: 'Output format', describe: 'Format',
default: 'json', default: 'json',
type: 'string', type: 'string',
choices: ['json', 'namespaced-json', 'pot'] choices: ['json', 'namespaced-json', 'pot']
}) })
.option('format-indentation', { .option('format-indentation', {
alias: 'fi', alias: 'fi',
describe: 'Output format indentation', describe: 'Format indentation (JSON/Namedspaced JSON)',
default: '\t', default: '\t',
type: 'string' type: 'string'
}) })
@ -71,31 +77,39 @@ export const cli = yargs
}) })
.option('sort', { .option('sort', {
alias: 's', alias: 's',
describe: 'Sort strings in alphabetical order when saving', describe: 'Sort strings in alphabetical order',
type: 'boolean' type: 'boolean'
}) })
.option('clean', { .option('clean', {
alias: 'c', alias: 'c',
describe: 'Remove obsolete strings when merging', describe: 'Remove obsolete strings after merge',
type: 'boolean' type: 'boolean'
}) })
.option('key-as-default-value', { .option('key-as-default-value', {
alias: 'k', alias: 'k',
describe: 'Use key as default value for translations', describe: 'Use key as default value',
type: 'boolean' type: 'boolean'
}) })
.option('null-as-default-value', { .option('null-as-default-value', {
alias: 'n', alias: 'n',
describe: 'Use null as default value for translations', describe: 'Use null as default value',
type: 'boolean' type: 'boolean'
}) })
.group(['format', 'format-indentation', 'sort', 'clean'], 'Output')
.group(['key-as-default-value', 'null-as-default-value'], 'Default value (defaults to empty string)')
.conflicts('key-as-default-value', 'null-as-default-value') .conflicts('key-as-default-value', 'null-as-default-value')
.example(`$0 -i ./src-a/ -i ./src-b/ -o strings.json`, 'Extract (ts, html) from multiple paths')
.example(`$0 -i './{src-a,src-b}/' -o strings.json`, 'Extract (ts, html) from multiple paths using brace expansion')
.example(`$0 -i ./src/ -o ./i18n/da.json -o ./i18n/en.json`, 'Extract (ts, html) and save to da.json+en.json')
.example(`$0 -i ./src/ -o './i18n/{en,da}.json'`, 'Extract (ts, html) and save to da.json+en.json using brace expansion')
.example(`$0 -i './src/**/*.{ts,tsx,html}' -o strings.json`, 'Extract from ts, tsx and html')
.example(`$0 -i './src/**/!(*.spec).{ts,html}' -o strings.json`, 'Extract from ts, html, excluding files with ".spec" in filename')
.wrap(110)
.exitProcess(true) .exitProcess(true)
.parse(process.argv); .parse(process.argv);
const extractTask = new ExtractTask(cli.input, cli.output, { const extractTask = new ExtractTask(cli.input, cli.output, {
replace: cli.replace, replace: cli.replace
patterns: cli.patterns
}); });
// Parsers // Parsers

View File

@ -12,13 +12,11 @@ import * as mkdirp from 'mkdirp';
export interface ExtractTaskOptionsInterface { export interface ExtractTaskOptionsInterface {
replace?: boolean; replace?: boolean;
patterns?: string[];
} }
export class ExtractTask implements TaskInterface { export class ExtractTask implements TaskInterface {
protected options: ExtractTaskOptionsInterface = { protected options: ExtractTaskOptionsInterface = {
replace: false, replace: false
patterns: []
}; };
protected parsers: ParserInterface[] = []; protected parsers: ParserInterface[] = [];
@ -100,8 +98,8 @@ export class ExtractTask implements TaskInterface {
*/ */
protected extract(): TranslationCollection { protected extract(): TranslationCollection {
let collection: TranslationCollection = new TranslationCollection(); let collection: TranslationCollection = new TranslationCollection();
this.inputs.forEach(dir => { this.inputs.forEach(pattern => {
this.readDir(dir, this.options.patterns).forEach(filePath => { this.getFiles(pattern).forEach(filePath => {
this.out(dim('- %s'), filePath); this.out(dim('- %s'), filePath);
const contents: string = fs.readFileSync(filePath, 'utf-8'); const contents: string = fs.readFileSync(filePath, 'utf-8');
this.parsers.forEach(parser => { this.parsers.forEach(parser => {
@ -138,15 +136,12 @@ export class ExtractTask implements TaskInterface {
} }
/** /**
* Get all files in dir matching patterns * Get all files matching pattern
*/ */
protected readDir(dir: string, patterns: string[]): string[] { protected getFiles(pattern: string): string[] {
return patterns.reduce((results, pattern) => {
return glob return glob
.sync(dir + pattern) .sync(pattern)
.filter(filePath => fs.statSync(filePath).isFile()) .filter(filePath => fs.statSync(filePath).isFile());
.concat(results);
}, []);
} }
protected out(...args: any[]): void { protected out(...args: any[]): void {

26
src/utils/fs-helpers.ts Normal file
View File

@ -0,0 +1,26 @@
import * as os from 'os';
import * as fs from 'fs';
import * as braces from 'braces';
export function normalizeHomeDir(path: string): string {
if (path.substring(0, 1) === '~') {
return `${os.homedir()}/${path.substring(1)}`;
}
return path;
}
export function expandPattern(pattern: string): string[] {
return braces(pattern, { expand: true });
}
export function normalizePaths(patterns: string[], defaultPatterns: string[] = []): string[] {
return patterns.map(pattern =>
expandPattern(pattern).map(path => {
path = normalizeHomeDir(path);
if (fs.existsSync(path) && fs.statSync(path).isDirectory()) {
return defaultPatterns.map(defaultPattern => path + defaultPattern);
}
return path;
}).flat()
).flat();
}