|
4 | 4 | import { |
5 | 5 | FileSystem, |
6 | 6 | JsonFile, |
7 | | - JsonSchema |
| 7 | + JsonSchema, |
| 8 | + Terminal, |
| 9 | + ConsoleTerminalProvider |
8 | 10 | } from '@microsoft/node-core-library'; |
9 | 11 | import * as glob from 'glob'; |
10 | 12 | import * as path from 'path'; |
11 | 13 | import { EOL } from 'os'; |
12 | 14 |
|
13 | | -import { ILocJsonFile } from './interfaces'; |
| 15 | +import { ILocFile } from './interfaces'; |
| 16 | +import { ResxReader, ILogger } from './utilities/ResxReader'; |
14 | 17 |
|
15 | 18 | /** |
16 | 19 | * @public |
17 | 20 | */ |
18 | | -export interface ILocJsonPreprocessorOptions { |
| 21 | +export interface ILocFilePreprocessorOptions { |
19 | 22 | srcFolder: string; |
20 | 23 | generatedTsFolder: string; |
| 24 | + terminal: Terminal; |
21 | 25 | filesToIgnore?: string[]; |
22 | 26 | } |
23 | 27 |
|
24 | 28 | /** |
25 | | - * This is a simple tool that generates .d.ts files for .loc.json files. |
| 29 | + * This is a simple tool that generates .d.ts files for .loc.json and .resx files. |
26 | 30 | * |
27 | 31 | * @public |
28 | 32 | */ |
29 | | -export class LocJsonPreprocessor { |
30 | | - public static preprocessLocJsonFiles(options: ILocJsonPreprocessorOptions): void { |
31 | | - const { |
32 | | - srcFolder, |
33 | | - generatedTsFolder |
34 | | - }: ILocJsonPreprocessorOptions = options; |
35 | | - |
36 | | - const filesToIgnore: Set<string> = new Set<string>((options.filesToIgnore || []).map((fileToIgnore) => { |
| 33 | +export class LocFilePreprocessor { |
| 34 | + private _options: ILocFilePreprocessorOptions; |
| 35 | + private _loggingOptions: ILogger; |
| 36 | + |
| 37 | + public constructor(options: ILocFilePreprocessorOptions) { |
| 38 | + this._options = { |
| 39 | + filesToIgnore: [], |
| 40 | + ...options |
| 41 | + }; |
| 42 | + |
| 43 | + if (this._options.terminal) { |
| 44 | + this._options.terminal = new Terminal(new ConsoleTerminalProvider({ verboseEnabled: true })); |
| 45 | + } |
| 46 | + |
| 47 | + function logWithLocation( |
| 48 | + loggingFn: typeof Terminal.prototype.writeErrorLine, |
| 49 | + message: string, |
| 50 | + filePath: string, |
| 51 | + line?: number, |
| 52 | + position?: number |
| 53 | + ): void { |
| 54 | + let location: string; |
| 55 | + if (position !== undefined) { |
| 56 | + location = `${filePath}(${line},${position})`; |
| 57 | + } else if (line !== undefined) { |
| 58 | + location = `${filePath}(${line})`; |
| 59 | + } else { |
| 60 | + location = filePath; |
| 61 | + } |
| 62 | + |
| 63 | + loggingFn(`${location}: ${message}`); |
| 64 | + } |
| 65 | + |
| 66 | + this._loggingOptions = { |
| 67 | + logError: (message: string) => this._options.terminal.writeErrorLine(message), |
| 68 | + logWarning: (message: string) => this._options.terminal.writeWarningLine(message), |
| 69 | + logFileError: (message: string, filePath: string, line?: number, position?: number) => { |
| 70 | + logWithLocation( |
| 71 | + this._options.terminal.writeErrorLine.bind(this._options.terminal), |
| 72 | + message, |
| 73 | + filePath, |
| 74 | + line, |
| 75 | + position |
| 76 | + ); |
| 77 | + }, |
| 78 | + logFileWarning: (message: string, filePath: string, line?: number, position?: number) => { |
| 79 | + logWithLocation( |
| 80 | + this._options.terminal.writeWarningLine.bind(this._options.terminal), |
| 81 | + message, |
| 82 | + filePath, |
| 83 | + line, |
| 84 | + position |
| 85 | + ); |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + public generateTypings(): void { |
| 91 | + FileSystem.ensureEmptyFolder(this._options.generatedTsFolder); |
| 92 | + |
| 93 | + const filesToIgnore: Set<string> = new Set<string>((this._options.filesToIgnore!).map((fileToIgnore) => { |
37 | 94 | if (path.isAbsolute(fileToIgnore)) { |
38 | 95 | return fileToIgnore; |
39 | 96 | } else { |
40 | | - return path.resolve(srcFolder, fileToIgnore); |
| 97 | + return path.resolve(this._options.srcFolder, fileToIgnore); |
41 | 98 | } |
42 | 99 | })); |
43 | 100 |
|
44 | | - FileSystem.ensureEmptyFolder(generatedTsFolder); |
45 | 101 | const locJsonFiles: string[] = glob.sync( |
46 | 102 | path.join('**', '*.loc.json'), |
47 | 103 | { |
48 | | - root: srcFolder, |
| 104 | + root: this._options.srcFolder, |
49 | 105 | absolute: true |
50 | 106 | } |
51 | 107 | ); |
52 | 108 |
|
53 | 109 | const locJsonSchema: JsonSchema = JsonSchema.fromFile(path.resolve(__dirname, 'schemas', 'locJson.schema.json')); |
54 | | - |
55 | 110 | for (let locJsonFilePath of locJsonFiles) { |
56 | 111 | locJsonFilePath = path.resolve(locJsonFilePath); |
57 | 112 |
|
58 | 113 | if (filesToIgnore.has(locJsonFilePath)) { |
59 | 114 | continue; |
60 | 115 | } |
61 | 116 |
|
62 | | - const outputLines: string[] = [ |
63 | | - '// This file was generated by a tool. Modifying it will produce unexpected behavior', |
64 | | - '' |
65 | | - ]; |
| 117 | + const locFileData: ILocFile = JsonFile.loadAndValidate(locJsonFilePath, locJsonSchema); |
| 118 | + this._generateTypingsForLocFile(locJsonFilePath, locFileData); |
| 119 | + } |
| 120 | + |
| 121 | + const resxFiles: string[] = glob.sync( |
| 122 | + path.join('**', '*.resx'), |
| 123 | + { |
| 124 | + root: this._options.srcFolder, |
| 125 | + absolute: true |
| 126 | + } |
| 127 | + ); |
| 128 | + |
| 129 | + for (let resxFilePath of resxFiles) { |
| 130 | + resxFilePath = path.resolve(resxFilePath); |
| 131 | + |
| 132 | + if (filesToIgnore.has(resxFilePath)) { |
| 133 | + continue; |
| 134 | + } |
| 135 | + |
| 136 | + const locFileData: ILocFile = ResxReader.readResxFileAsLocFile({ ...this._loggingOptions, resxFilePath }); |
| 137 | + this._generateTypingsForLocFile(resxFilePath, locFileData); |
| 138 | + } |
| 139 | + } |
66 | 140 |
|
67 | | - const locJsonFile: ILocJsonFile = JsonFile.loadAndValidate(locJsonFilePath, locJsonSchema); |
68 | | - for (const stringName in locJsonFile) { // eslint-disable-line guard-for-in |
69 | | - const { comment } = locJsonFile[stringName]; |
| 141 | + private _generateTypingsForLocFile(locFilePath: string, locFileData: ILocFile): void { |
| 142 | + const outputLines: string[] = [ |
| 143 | + '// This file was generated by a tool. Modifying it will produce unexpected behavior', |
| 144 | + '' |
| 145 | + ]; |
70 | 146 |
|
71 | | - if (comment.trim()) { |
72 | | - outputLines.push(...[ |
73 | | - '/**', |
74 | | - ` * ${comment.replace(/\*\//g, '*\\/')}`, |
75 | | - ' */' |
76 | | - ]); |
77 | | - } |
| 147 | + for (const stringName in locFileData) { // eslint-disable-line guard-for-in |
| 148 | + const { comment } = locFileData[stringName]; |
78 | 149 |
|
| 150 | + if (comment.trim()) { |
79 | 151 | outputLines.push(...[ |
80 | | - `export declare const ${stringName}: string;`, |
81 | | - '' |
| 152 | + '/**', |
| 153 | + ` * ${comment.replace(/\*\//g, '*\\/')}`, |
| 154 | + ' */' |
82 | 155 | ]); |
83 | 156 | } |
84 | 157 |
|
85 | | - const generatedTsFilePath: string = ( |
86 | | - `${path.resolve(generatedTsFolder, path.relative(srcFolder, locJsonFilePath))}.d.ts` |
87 | | - ); |
88 | | - FileSystem.ensureFolder(path.dirname(generatedTsFilePath)); |
89 | | - FileSystem.writeFile(generatedTsFilePath, outputLines.join(EOL)); |
| 158 | + outputLines.push(...[ |
| 159 | + `export declare const ${stringName}: string;`, |
| 160 | + '' |
| 161 | + ]); |
90 | 162 | } |
| 163 | + |
| 164 | + const generatedTsFilePath: string = path.resolve( |
| 165 | + this._options.generatedTsFolder, |
| 166 | + path.relative(this._options.srcFolder, `${locFilePath}.d.ts`) |
| 167 | + ); |
| 168 | + FileSystem.writeFile(generatedTsFilePath, outputLines.join(EOL), { ensureFolderExists: true }); |
91 | 169 | } |
92 | 170 | } |
0 commit comments