Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 83fcbb2

Browse files
munozemiliochristopheranderson
authored andcommitted
Luis command handle empty lu file (#252)
* Fix for Empty lu file creation #230 * Adding variable declaration back * Fixes: #241, #247, #239 * Fixes #247, #246, #239 * Fixing PR comments * Fixing PR comments
1 parent e9b646f commit 83fcbb2

File tree

18 files changed

+295
-215
lines changed

18 files changed

+295
-215
lines changed

packages/chatdown/src/commands/chatdown.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default class Chatdown extends Command {
2121
out: flags.string({char: 'o', description: 'Path to the directory where the output of the multiple chat file processing (-o) will be placed.'}),
2222
static: flags.boolean({char: 's', description: 'Use static timestamps when generating timestamps on activities.'}),
2323
prefix: flags.boolean({char: 'p', description: 'Prefix stdout with package name.'}),
24+
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
2425
help: flags.help({char: 'h', description: 'Chatdown command help'})
2526
}
2627

@@ -41,7 +42,7 @@ export default class Chatdown extends Command {
4142

4243
if (inputIsDirectory) {
4344
let inputDir = flags.in ? flags.in.trim() : ''
44-
const len = await this.processFiles(inputDir, outputDir)
45+
const len = await this.processFiles(inputDir, outputDir, flags.force)
4546
if (len === 0) {
4647
throw new CLIError('No chat files found at: ' + flags.in)
4748
}
@@ -52,7 +53,7 @@ export default class Chatdown extends Command {
5253
const fileName = flags.in ? this.getFileName(flags.in) : ''
5354
if (fileContents) {
5455
const activities = await chatdown(fileContents, flags)
55-
const writeConfirmation = await this.writeOut(activities, fileName, outputDir)
56+
const writeConfirmation = await this.writeOut(activities, fileName, outputDir, flags.force)
5657
/* tslint:disable:strict-type-predicates */
5758
if (typeof writeConfirmation === 'string') {
5859
process.stdout.write(`${chalk.green('Successfully wrote file:')} ${writeConfirmation}\n`)
@@ -110,7 +111,7 @@ export default class Chatdown extends Command {
110111
return fileName
111112
}
112113

113-
private async processFiles(inputDir: any, outputDir: any) {
114+
private async processFiles(inputDir: any, outputDir: any, force: boolean) {
114115
return new Promise(async (resolve, reject) => {
115116
let files: any = []
116117
if (inputDir.indexOf('*') > -1) {
@@ -127,7 +128,7 @@ export default class Chatdown extends Command {
127128
try {
128129
const fileName = this.getFileName(files[i])
129130
let activities = await chatdown(await utils.readTextFile(files[i]))
130-
await this.writeOut(activities, fileName, outputDir)
131+
await this.writeOut(activities, fileName, outputDir, force)
131132
} catch (e) {
132133
if (e.message.match(/no such file or directory/)) {
133134
reject(new CLIError(e.message))
@@ -141,11 +142,12 @@ export default class Chatdown extends Command {
141142
})
142143
}
143144

144-
private async writeOut(activities: any, fileName: string, outputDir: any) {
145+
private async writeOut(activities: any, fileName: string, outputDir: any, force: boolean) {
145146
if (fileName && outputDir) {
146147
let writeFile = path.join(outputDir, `${fileName}.transcript`)
147148
await fs.ensureFile(writeFile)
148-
await fs.writeJson(writeFile, activities, {spaces: 2})
149+
let validatedPath = utils.validatePath(writeFile, '', force)
150+
await fs.writeJson(validatedPath, activities, {spaces: 2})
149151
return writeFile
150152
}
151153
const output = JSON.stringify(activities, null, 2)

packages/cli/test/fixtures/lastversioncheck/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414
"init": "../../../src/hooks/init/inithook"
1515
}
1616
},
17-
"lastversioncheck": "2019-09-27T23:36:07.377Z"
17+
"lastversioncheck": "2019-10-21T22:09:52.140Z"
1818
}

packages/command/src/utils.ts

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,90 @@
11
const fs = require('fs-extra')
2-
import {CLIError} from '@oclif/errors'
3-
4-
const utils = {
5-
readTextFile: async (file: any) => {
6-
return new Promise(async (resolve, reject) => {
7-
try {
8-
if (!fs.existsSync(file)) {
9-
return reject('ENOENT: no such file or directory, ' + file)
10-
}
11-
let fileBuffer = await fs.readFile(file)
12-
if (fileBuffer) {
13-
// If the data starts with BOM, we know it is UTF
14-
if (fileBuffer[0] === 0xEF && fileBuffer[1] === 0xBB && fileBuffer[2] === 0xBF) {
15-
// EF BB BF UTF-8 with BOM
16-
fileBuffer = fileBuffer.slice(3)
17-
} else if (fileBuffer[0] === 0xFF && fileBuffer[1] === 0xFE && fileBuffer[2] === 0x00 && fileBuffer[3] === 0x00) {
18-
// FF FE 00 00 UTF-32, little-endian BOM
19-
fileBuffer = fileBuffer.slice(4)
20-
} else if (fileBuffer[0] === 0x00 && fileBuffer[1] === 0x00 && fileBuffer[2] === 0xFE && fileBuffer[3] === 0xFF) {
21-
// 00 00 FE FF UTF-32, big-endian BOM
22-
fileBuffer = fileBuffer.slice(4)
23-
} else if (fileBuffer[0] === 0xFE && fileBuffer[1] === 0xFF && fileBuffer[2] === 0x00 && fileBuffer[3] === 0x00) {
24-
// FE FF 00 00 UCS-4, unusual octet order BOM (3412)
25-
fileBuffer = fileBuffer.slice(4)
26-
} else if (fileBuffer[0] === 0x00 && fileBuffer[1] === 0x00 && fileBuffer[2] === 0xFF && fileBuffer[3] === 0xFE) {
27-
// 00 00 FF FE UCS-4, unusual octet order BOM (2143)
28-
fileBuffer = fileBuffer.slice(4)
29-
} else if (fileBuffer[0] === 0xFF && fileBuffer[1] === 0xFE) {
30-
// FF FE UTF-16, little endian BOM
31-
fileBuffer = fileBuffer.slice(2)
32-
} else if (fileBuffer[0] === 0xFE && fileBuffer[1] === 0xFF) {
33-
// FE FF UTF-16, big endian BOM
34-
fileBuffer = fileBuffer.slice(2)
35-
}
36-
}
37-
return resolve(fileBuffer.toString('utf8').replace(/\0/g, ''))
38-
} catch (err) {
39-
if (err.message.match(/ENOENT: no such file or directory/)) {
40-
return reject(new CLIError(err.message))
41-
}
2+
const path = require('path')
3+
import {CLIError} from './clierror'
424

43-
return reject(`Invalid Input. Sorry, unable to parse file: ${err}`)
5+
async function readTextFile(file: any): Promise<string> {
6+
return new Promise(async (resolve, reject) => {
7+
try {
8+
if (!fs.existsSync(file)) {
9+
return reject('ENOENT: no such file or directory, ' + file)
10+
}
11+
let fileBuffer = await fs.readFile(file)
12+
if (fileBuffer) {
13+
// If the data starts with BOM, we know it is UTF
14+
if (fileBuffer[0] === 0xEF && fileBuffer[1] === 0xBB && fileBuffer[2] === 0xBF) {
15+
// EF BB BF UTF-8 with BOM
16+
fileBuffer = fileBuffer.slice(3)
17+
} else if (fileBuffer[0] === 0xFF && fileBuffer[1] === 0xFE && fileBuffer[2] === 0x00 && fileBuffer[3] === 0x00) {
18+
// FF FE 00 00 UTF-32, little-endian BOM
19+
fileBuffer = fileBuffer.slice(4)
20+
} else if (fileBuffer[0] === 0x00 && fileBuffer[1] === 0x00 && fileBuffer[2] === 0xFE && fileBuffer[3] === 0xFF) {
21+
// 00 00 FE FF UTF-32, big-endian BOM
22+
fileBuffer = fileBuffer.slice(4)
23+
} else if (fileBuffer[0] === 0xFE && fileBuffer[1] === 0xFF && fileBuffer[2] === 0x00 && fileBuffer[3] === 0x00) {
24+
// FE FF 00 00 UCS-4, unusual octet order BOM (3412)
25+
fileBuffer = fileBuffer.slice(4)
26+
} else if (fileBuffer[0] === 0x00 && fileBuffer[1] === 0x00 && fileBuffer[2] === 0xFF && fileBuffer[3] === 0xFE) {
27+
// 00 00 FF FE UCS-4, unusual octet order BOM (2143)
28+
fileBuffer = fileBuffer.slice(4)
29+
} else if (fileBuffer[0] === 0xFF && fileBuffer[1] === 0xFE) {
30+
// FF FE UTF-16, little endian BOM
31+
fileBuffer = fileBuffer.slice(2)
32+
} else if (fileBuffer[0] === 0xFE && fileBuffer[1] === 0xFF) {
33+
// FE FF UTF-16, big endian BOM
34+
fileBuffer = fileBuffer.slice(2)
35+
}
4436
}
45-
})
37+
return resolve(fileBuffer.toString('utf8').replace(/\0/g, ''))
38+
} catch (err) {
39+
if (err.message.match(/ENOENT: no such file or directory/)) {
40+
return reject(new CLIError(err.message))
41+
}
42+
43+
return reject(`Invalid Input. Sorry, unable to parse file: ${err}`)
44+
}
45+
})
46+
}
47+
48+
function validatePath(outputPath: string, defaultFileName: string, forceWrite = false): string {
49+
let completePath = path.resolve(outputPath)
50+
const containingDir = path.dirname(completePath)
51+
52+
// If the cointaining folder doesnt exist
53+
if (!fs.existsSync(containingDir)) throw new CLIError(`Containing directory path doesn't exist: ${containingDir}`)
54+
55+
const baseElement = path.basename(completePath)
56+
const pathAlreadyExist = fs.existsSync(completePath)
57+
58+
// If the last element in the path is a file
59+
if (baseElement.includes('.')) {
60+
return pathAlreadyExist && !forceWrite ? enumerateFileName(completePath) : completePath
4661
}
62+
63+
// If the last element in the path is a folder
64+
if (!pathAlreadyExist) throw new CLIError(`Target directory path doesn't exist: ${completePath}`)
65+
completePath = path.join(completePath, defaultFileName)
66+
return fs.existsSync(completePath) && !forceWrite ? enumerateFileName(completePath) : completePath
67+
}
68+
69+
function enumerateFileName(filePath: string): string {
70+
const fileName = path.basename(filePath)
71+
const containingDir = path.dirname(filePath)
72+
73+
if (!fs.existsSync(containingDir)) throw new CLIError(`Containing directory path doesn't exist: ${containingDir}`)
74+
75+
const extension = path.extname(fileName)
76+
const baseName = path.basename(fileName, extension)
77+
let nextNumber = 0
78+
let newPath = ''
79+
80+
do {
81+
newPath = path.join(containingDir, baseName + `(${++nextNumber})` + extension)
82+
} while (fs.existsSync(newPath))
83+
84+
return newPath
4785
}
4886

49-
export default utils
87+
export default {
88+
readTextFile,
89+
validatePath
90+
}

packages/config/src/commands/index.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/lu/src/commands/luis/convert.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {CLIError, Command, flags} from '@microsoft/bf-cli-command'
1+
import {CLIError, Command, flags, utils} from '@microsoft/bf-cli-command'
22
const exception = require('./../../parser/lufile/classes/exception')
33
const fs = require('fs-extra')
44
const file = require('./../../utils/filehelper')
@@ -10,16 +10,18 @@ export default class LuisConvert extends Command {
1010
static description = 'Convert .lu file(s) to a LUIS application JSON model or vice versa'
1111

1212
static flags: flags.Input<any> = {
13-
in: flags.string({description: 'Source .lu file(s) or LUIS application JSON model'}),
14-
recurse: flags.boolean({description: 'Indicates if sub-folders need to be considered to file .lu file(s)', default: false}),
13+
in: flags.string({char: 'i', description: 'Source .lu file(s) or LUIS application JSON model'}),
14+
recurse: flags.boolean({char: 'r', description: 'Indicates if sub-folders need to be considered to file .lu file(s)', default: false}),
1515
log: flags.boolean({description: 'Enables log messages', default: false}),
1616
sort: flags.boolean({description: 'When set, intent, utterances, entities are alphabetically sorted in .lu files', default: false}),
17-
out: flags.string({description: 'Output file or folder name. If not specified stdout will be used as output'}),
17+
out: flags.string({char: 'o', description: 'Output file or folder name. If not specified stdout will be used as output'}),
1818
name: flags.string({description: 'Name of the LUIS application'}),
1919
description: flags.string({description: 'Text describing the LUIS applicaion'}),
2020
culture: flags.string({description: 'Lang code for the LUIS application'}),
2121
versionid: flags.string({description: 'Version ID of the LUIS application'}),
2222
schemaversion: flags.string({description: 'Schema version of the LUIS application'}),
23+
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
24+
help: flags.help({char: 'h', description: 'luis:convert help'})
2325
}
2426

2527
async run() {
@@ -76,7 +78,8 @@ export default class LuisConvert extends Command {
7678
let filePath = await file.generateNewFilePath(flags.out, flags.in, isLu)
7779
// write out the final file
7880
try {
79-
await fs.writeFile(filePath, convertedObject, 'utf-8')
81+
const validatedPath = utils.validatePath(filePath, '', flags.force)
82+
await fs.writeFile(validatedPath, convertedObject, 'utf-8')
8083
} catch (err) {
8184
throw new CLIError('Unable to write file - ' + filePath + ' Error: ' + err.message)
8285
}

packages/lu/src/commands/luis/generate/cs.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ export default class LuisGenerateCs extends Command {
1010
static description = 'Generate:cs generates a strongly typed C# source code from an exported (json) LUIS model.'
1111

1212
static flags: flags.Input<any> = {
13-
in: flags.string({description: 'Path to the file containing the LUIS application JSON model'}),
14-
out: flags.string({description: 'Output file or folder name. If not specified stdout will be used as output', default: ''}),
13+
in: flags.string({char: 'i', description: 'Path to the file containing the LUIS application JSON model'}),
14+
out: flags.string({char: 'o', description: 'Output file or folder name. If not specified stdout will be used as output', default: ''}),
1515
className: flags.string({description: 'Name of the autogenerated class (can include namespace)'}),
16-
force: flags.boolean({description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
16+
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
17+
help: flags.help({char: 'h', description: 'luis:generate:cs help'})
18+
1719
}
1820

1921
reorderEntities(app: any, name: string): void {

packages/lu/src/commands/luis/generate/ts.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ export default class LuisGenerateTs extends Command {
1010
static description = 'Generate:ts generates a strongly typed typescript source code from an exported (json) LUIS model.'
1111

1212
static flags: flags.Input<any> = {
13-
in: flags.string({description: 'Path to the file containing the LUIS application JSON model'}),
14-
out: flags.string({description: 'Output file or folder name. If not specified stdout will be used as output', default: ''}),
13+
in: flags.string({char: 'i', description: 'Path to the file containing the LUIS application JSON model'}),
14+
out: flags.string({char: 'o', description: 'Output file or folder name. If not specified stdout will be used as output', default: ''}),
1515
className: flags.string({description: 'Name of the autogenerated class'}),
16-
force: flags.boolean({description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
16+
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
17+
help: flags.help({char: 'h', description: 'luis:generate:ts help'})
18+
1719
}
1820

1921
reorderEntities(app: any, name: string): void {

packages/lu/src/commands/luis/translate.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {CLIError, Command, flags} from '@microsoft/bf-cli-command'
1+
import {CLIError, Command, flags, utils} from '@microsoft/bf-cli-command'
22
const fs = require('fs-extra')
33
const path = require('path')
44
const fileHelper = require('./../../utils/filehelper')
@@ -12,14 +12,16 @@ export default class LuisTranslate extends Command {
1212
static description = ' Translate given LUIS application JSON model or lu file(s)'
1313

1414
static flags: flags.Input<any> = {
15-
in: flags.string({description: 'Source .lu file(s) or LUIS application JSON model'}),
16-
recurse: flags.boolean({description: 'Indicates if sub-folders need to be considered to file .lu file(s)'}),
17-
out: flags.string({description: 'Output folder name. If not specified stdout will be used as output'}),
15+
in: flags.string({char: 'i', description: 'Source .lu file(s) or LUIS application JSON model'}),
16+
recurse: flags.boolean({char: 'r', description: 'Indicates if sub-folders need to be considered to file .lu file(s)'}),
17+
out: flags.string({char: 'o', description: 'Output folder name. If not specified stdout will be used as output'}),
1818
srclang: flags.string({description: 'Source lang code. Auto detect if missing.'}),
1919
tgtlang: flags.string({description: 'Comma separated list of target languages.', required: true}),
2020
translatekey: flags.string({description: 'Machine translation endpoint key.', required: true}),
2121
translate_comments: flags.boolean({description: 'When set, machine translate comments found in .lu file'}),
2222
translate_link_text: flags.boolean({description: 'When set, machine translate link description in .lu file'}),
23+
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
24+
help: flags.help({char: 'h', description: 'luis:translate help'})
2325
}
2426

2527
/* tslint:disable:forin no-for-in*/
@@ -49,7 +51,7 @@ export default class LuisTranslate extends Command {
4951
}
5052

5153
if (flags.out) {
52-
await this.writeOutput(result, flags.out, isLu)
54+
await this.writeOutput(result, flags.out, isLu, flags.force)
5355
} else {
5456
if (isLu) {
5557
this.log(result)
@@ -66,14 +68,15 @@ export default class LuisTranslate extends Command {
6668
}
6769
}
6870

69-
private async writeOutput(translatedObject: any, out: string, isLu: boolean) {
71+
private async writeOutput(translatedObject: any, out: string, isLu: boolean, force: boolean) {
7072
let filePath = ''
7173
try {
7274
for (let file in translatedObject) {
7375
for (let lng in translatedObject[file]) {
7476
filePath = await fileHelper.generateNewTranslatedFilePath(file, lng, out)
7577
let content = isLu ? translatedObject[file][lng] : JSON.stringify(translatedObject[file][lng], null, 2)
76-
await fs.writeFile(filePath, content, 'utf-8')
78+
const validatedPath = utils.validatePath(filePath, '', force)
79+
await fs.writeFile(validatedPath, content, 'utf-8')
7780
}
7881
}
7982
} catch (err) {

0 commit comments

Comments
 (0)