(feature) add support for expanding paths on Windows + added more usage examples to cli
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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(pattern) | ||||||
| 				.sync(dir + 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
									
								
							
							
						
						
									
										26
									
								
								src/utils/fs-helpers.ts
									
									
									
									
									
										Normal 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(); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user