Refactor code. Add DirectiveParser (Thanks ocombe\!)
This commit is contained in:
		| @@ -1,17 +1,17 @@ | |||||||
| import { Extractor } from './extractor'; | import { Extractor } from './extractor'; | ||||||
| import { JsonSerializer } from './serializers/json.serializer'; | import { JsonSerializer } from './serializers/json.serializer'; | ||||||
|  |  | ||||||
| const dir = '/path/to/extract/strings/from'; |  | ||||||
| const dest = '/path/to/save/template/to/template.pot'; |  | ||||||
|  |  | ||||||
| const serializer = new JsonSerializer(); | const serializer = new JsonSerializer(); | ||||||
| // Or const serializer = new PotSerializer(); | // Or const serializer = new PotSerializer(); | ||||||
| const extractor = new Extractor(serializer); | const extractor = new Extractor(serializer); | ||||||
|  |  | ||||||
|  | const src = '/your/project'; | ||||||
|  | const dest = '/your/project/template.json'; | ||||||
|  |  | ||||||
| try { | try { | ||||||
| 	const messages: string[] = extractor.extract(dir); | 	const messages: string[] = extractor.extract(src); | ||||||
| 	const output: string = extractor.save(dest); | 	const output: string = extractor.save(dest); | ||||||
| 	console.log('Done!'); | 	console.log({ messages, output }); | ||||||
| } catch (e) { | } catch (e) { | ||||||
| 	console.log(`Something went wrong: ${e.toString()}`); | 	console.log(`Something went wrong: ${e.toString()}`); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,13 +12,13 @@ export class Extractor { | |||||||
|  |  | ||||||
| 	public parsers: ParserInterface[] = [ | 	public parsers: ParserInterface[] = [ | ||||||
| 		new PipeParser(), | 		new PipeParser(), | ||||||
| 		new ServiceParser(), | 		new DirectiveParser(), | ||||||
| 		new DirectiveParser() | 		new ServiceParser() | ||||||
| 	]; | 	]; | ||||||
|  |  | ||||||
| 	public globPatterns: string[] = [ | 	public globPatterns: string[] = [ | ||||||
| 		'/**/*.ts', | 		'/**/*.html', | ||||||
| 		'/**/*.html' | 		'/**/*.ts' | ||||||
| 	]; | 	]; | ||||||
|  |  | ||||||
| 	public messages: string[] = []; | 	public messages: string[] = []; | ||||||
| @@ -30,15 +30,11 @@ export class Extractor { | |||||||
| 	 */ | 	 */ | ||||||
| 	public extract(dir: string): string[] { | 	public extract(dir: string): string[] { | ||||||
| 		let messages = []; | 		let messages = []; | ||||||
| 		this.globPatterns.forEach(globPattern => { |  | ||||||
| 			const filePaths = glob.sync(dir + globPattern); | 		this._getFiles(dir).forEach(filePath => { | ||||||
| 			filePaths |  | ||||||
| 				.filter(filePath => fs.statSync(filePath).isFile()) |  | ||||||
| 				.forEach(filePath => { |  | ||||||
| 			const result = this._extractMessages(filePath); | 			const result = this._extractMessages(filePath); | ||||||
| 			messages = [...messages, ...result]; | 			messages = [...messages, ...result]; | ||||||
| 		}); | 		}); | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		return this.messages = lodash.uniq(messages); | 		return this.messages = lodash.uniq(messages); | ||||||
| 	} | 	} | ||||||
| @@ -56,19 +52,35 @@ export class Extractor { | |||||||
| 	public save(destination: string): string { | 	public save(destination: string): string { | ||||||
| 		const data = this.serialize(); | 		const data = this.serialize(); | ||||||
| 		fs.writeFileSync(destination, data); | 		fs.writeFileSync(destination, data); | ||||||
|  |  | ||||||
| 		return data; | 		return data; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Extract messages from file using specialized parser | 	 * Get all files in dir that matches glob patterns | ||||||
|  | 	 */ | ||||||
|  | 	protected _getFiles(dir: string): string[] { | ||||||
|  | 		let results: string[] = []; | ||||||
|  |  | ||||||
|  | 		this.globPatterns.forEach(globPattern => { | ||||||
|  | 			const files = glob | ||||||
|  | 				.sync(dir + globPattern) | ||||||
|  | 				.filter(filePath => fs.statSync(filePath).isFile()); | ||||||
|  |  | ||||||
|  | 			results = [...results, ...files]; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return results; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extract messages from file using parser | ||||||
| 	 */ | 	 */ | ||||||
| 	protected _extractMessages(filePath: string): string[] { | 	protected _extractMessages(filePath: string): string[] { | ||||||
| 		let results = []; | 		let results: string[] = []; | ||||||
|  |  | ||||||
| 		const contents: string = fs.readFileSync(filePath, 'utf-8'); | 		const contents: string = fs.readFileSync(filePath, 'utf-8'); | ||||||
| 		this.parsers.forEach((parser: ParserInterface) => { | 		this.parsers.forEach((parser: ParserInterface) => { | ||||||
| 			results = results.concat(parser.process(contents)); | 			results = [...results, ...parser.process(filePath, contents)]; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/parsers/abstract-template.parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/parsers/abstract-template.parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | export abstract class AbstractTemplateParser { | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Checks if file is of type javascript or typescript and | ||||||
|  | 	 * makes the assumption that it is an Angular Component | ||||||
|  | 	 */ | ||||||
|  | 	protected _isAngularComponent(filePath: string): boolean { | ||||||
|  | 		return new RegExp('\.(ts|js)$', 'i').test(filePath); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extracts inline template from components | ||||||
|  | 	 */ | ||||||
|  | 	protected _extractInlineTemplate(contents: string): string { | ||||||
|  | 		const match = new RegExp('template\\s?:\\s?(("|\'|`)(.|[\\r\\n])+?[^\\\\]\\2)').exec(contents); | ||||||
|  | 		if (match !== null) { | ||||||
|  | 			return match[1]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return ''; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,66 +1,45 @@ | |||||||
| import { ParserInterface } from './parser.interface'; | import { ParserInterface } from './parser.interface'; | ||||||
|  | import { AbstractTemplateParser } from './abstract-template.parser'; | ||||||
| import * as $ from 'cheerio'; | import * as $ from 'cheerio'; | ||||||
|  |  | ||||||
| export class DirectiveParser implements ParserInterface { | export class DirectiveParser extends AbstractTemplateParser implements ParserInterface { | ||||||
|  |  | ||||||
|     public patterns = { | 	public process(filePath: string, contents: string): string[] { | ||||||
|         template: `template:\\s?(("|'|\`)(.|[\\r\\n])+?[^\\\\]\\2)` | 		if (this._isAngularComponent(filePath)) { | ||||||
|     }; | 			contents = this._extractInlineTemplate(contents); | ||||||
|  |  | ||||||
|     protected _parseTemplate(content) { |  | ||||||
|         let results: string[] = [], |  | ||||||
|             template = content.trim() |  | ||||||
|             // hack for cheerio that doesn't support wrapped attributes |  | ||||||
|                 .replace('[translate]=', '__translate__='); |  | ||||||
|  |  | ||||||
|         $(template).find('[translate],[__translate__]').contents().filter(function() { |  | ||||||
|             return this.nodeType === 3; // node type 3 = text node |  | ||||||
|         }).each(function() { |  | ||||||
|             let key, |  | ||||||
|                 $this = $(this), |  | ||||||
|                 element = $(this).parent(), |  | ||||||
|                 wrappedAttr = element.attr('__translate__'), // previously [translate]= |  | ||||||
|                 attr = element.attr('translate'); // translate= |  | ||||||
|  |  | ||||||
|             // only support string values for now |  | ||||||
|             if(wrappedAttr && wrappedAttr.match(/^['"].*['"]$/)) { |  | ||||||
|                 key = wrappedAttr.substr(1, wrappedAttr.length - 2); |  | ||||||
|             } else if(attr) { |  | ||||||
|                 key = attr; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|             if(!key) { | 		return this._parseTemplate(contents); | ||||||
|                 key = $this.text().replace(/\\n/gi, '').trim(); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|             if(key) { | 	protected _parseTemplate(template: string): string[] { | ||||||
|                 results.push(key); | 		let results: string[] = []; | ||||||
|  |  | ||||||
|  | 		template = this._normalizeTemplateAttributes(template); | ||||||
|  |  | ||||||
|  | 		$(template).find('[translate]') | ||||||
|  | 			.each((i: number, element: CheerioElement) => { | ||||||
|  | 				const $element = $(element); | ||||||
|  | 				const attr = $element.attr('translate'); | ||||||
|  | 				const text = $element.text(); | ||||||
|  |  | ||||||
|  | 				if (attr) { | ||||||
|  | 					results.push(attr); | ||||||
|  | 				} else if (text) { | ||||||
|  | 					results.push(text); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     public process(contents: string): string[] { | 	/** | ||||||
|         const regExp = new RegExp(this.patterns.template, 'gi'); | 	 * Angular's `[attr]="'val'"` syntax is not valid HTML, | ||||||
|  | 	 * so Cheerio is not able to parse it. | ||||||
|         let results: string[] = [], | 	 * This method replaces `[attr]="'val'""` with `attr="val"` | ||||||
|             hasTemplate = false, | 	 */ | ||||||
|             matches; | 	protected _normalizeTemplateAttributes(template: string): string { | ||||||
|  | 		return template.replace(/\[([^\]]+)\]="'([^\"]*)'"/g, '$1="$2"'); | ||||||
|         while(matches = regExp.exec(contents)) { |  | ||||||
|             let content = matches[1] |  | ||||||
|                 .substr(1, matches[1].length - 2); |  | ||||||
|  |  | ||||||
|             hasTemplate = true; |  | ||||||
|             results = results.concat(this._parseTemplate(content)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!hasTemplate) { |  | ||||||
|             this._parseTemplate(contents); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return results; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| export interface ParserInterface { | export interface ParserInterface { | ||||||
|  |  | ||||||
| 	process(contents: string): string[]; | 	process(filePath: string, contents: string): string[]; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +1,25 @@ | |||||||
| import { ParserInterface } from './parser.interface'; | import { ParserInterface } from './parser.interface'; | ||||||
|  | import { AbstractTemplateParser } from './abstract-template.parser'; | ||||||
|  |  | ||||||
| export class PipeParser implements ParserInterface { | export class PipeParser extends AbstractTemplateParser implements ParserInterface { | ||||||
|  |  | ||||||
| 	public patterns = { | 	public process(filePath: string, contents: string): string[] { | ||||||
| 		pipe: `(['"\`])([^\\1\\r\\n]*)\\1\\s+\\|\\s*translate(:.*?)?` | 		if (this._isAngularComponent(filePath)) { | ||||||
| 	}; | 			contents = this._extractInlineTemplate(contents); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	public process(contents: string): string[] { | 		return this._parseTemplate(contents); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _parseTemplate(template: string): string[] { | ||||||
| 		let results: string[] = []; | 		let results: string[] = []; | ||||||
|  |  | ||||||
| 		for (let patternName in this.patterns) { | 		const regExp = new RegExp('([\'"`])([^\\1\\r\\n]*)\\1\\s+\\|\\s*translate(:.*?)?', 'g'); | ||||||
| 			const regExp = new RegExp(this.patterns[patternName], 'g'); |  | ||||||
|  |  | ||||||
| 		let matches; | 		let matches; | ||||||
| 			while (matches = regExp.exec(contents)) { | 		while (matches = regExp.exec(template)) { | ||||||
| 			results.push(matches[2]); | 			results.push(matches[2]); | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -2,48 +2,25 @@ import { ParserInterface } from './parser.interface'; | |||||||
|  |  | ||||||
| export class ServiceParser implements ParserInterface { | export class ServiceParser implements ParserInterface { | ||||||
|  |  | ||||||
| 	public patterns = { | 	public process(filePath: string, contents: string): string[] { | ||||||
| 		translateServiceMethods: `{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*(['"\`])([^\\1\\r\\n]+)\\1`, |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	public process(contents: string): string[] { |  | ||||||
| 		let results: string[] = []; | 		let results: string[] = []; | ||||||
|  |  | ||||||
| 		const translateServiceVar = this._extractTranslateServiceVar(contents); | 		const translateServiceVar = this._extractTranslateServiceVar(contents); | ||||||
| 		if (!translateServiceVar) { | 		if (!translateServiceVar) { | ||||||
| 			return []; | 			return results; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for (let patternName in this.patterns) { | 		const methodPattern: string = '(?:get|instant)\\s*\\\(\\s*([\'"`])([^\\1\\r\\n]+)\\1'; | ||||||
| 			const regExp = this._createRegExp(patternName, { | 		const regExp: RegExp = new RegExp(`${translateServiceVar}\.${methodPattern}`, 'g'); | ||||||
| 				'TRANSLATE_SERVICE': translateServiceVar |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 		let matches; | 		let matches; | ||||||
| 		while (matches = regExp.exec(contents)) { | 		while (matches = regExp.exec(contents)) { | ||||||
| 			results.push(matches[2]); | 			results.push(matches[2]); | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Create regular expression, replacing placeholders with real values |  | ||||||
| 	 */ |  | ||||||
| 	protected _createRegExp(patternName: string, replaceVars: {} = {}): RegExp { |  | ||||||
| 		if (!this.patterns.hasOwnProperty(patternName)) { |  | ||||||
| 			throw new Error('Invalid pattern name'); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		let pattern = this.patterns[patternName]; |  | ||||||
| 		Object.keys(replaceVars).forEach(key => { |  | ||||||
| 			pattern = pattern.replace('{{' + key + '}}', replaceVars[key]); |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		return new RegExp(pattern, 'g'); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Extract name of TranslateService variable for use in patterns | 	 * Extract name of TranslateService variable for use in patterns | ||||||
| 	 */ | 	 */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user