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

Commit 3786d10

Browse files
author
Chris McConnell
authored
Handle locale in .uischema. (#909)
* Handle locale in .uischema. Add check for missing $schema. * Ignore missing .nuget packages to handle target frameworks. * Top-level $schema in .uischema Support broader -o extensions Allow missing .nuspec
1 parent 52d4917 commit 3786d10

19 files changed

+190
-73
lines changed

packages/dialog/README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ This package is intended for Microsoft use only and should be consumed through @
1313

1414
# Commands
1515
<!-- commands -->
16-
- [@microsoft/bf-dialog](#microsoftbf-dialog)
17-
- [Relevant docs](#relevant-docs)
18-
- [Commands](#commands)
19-
- [`bf dialog`](#bf-dialog)
20-
- [`bf dialog:merge PATTERNS`](#bf-dialogmerge-patterns)
21-
- [`bf dialog:verify PATTERNS`](#bf-dialogverify-patterns)
16+
* [`bf dialog`](#bf-dialog)
17+
* [`bf dialog:merge PATTERNS`](#bf-dialogmerge-patterns)
18+
* [`bf dialog:verify PATTERNS`](#bf-dialogverify-patterns)
2219

2320
## `bf dialog`
2421

@@ -48,6 +45,7 @@ ARGUMENTS
4845
OPTIONS
4946
-h, --help show CLI help
5047
-o, --output=output Output path and filename for merged .schema and .uischema. Defaults to first project name.
48+
-s, --schema=schema Path to merged .schema file to use if merging .uischema only.
5149
-v, --verbose Show verbose logging of files as they are processed.
5250
--extension=extension [default: .dialog,.lg,.lu,.schema,.qna,.uischema] Extension to include as a resource for C#.
5351

packages/dialog/docs/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ For examples look at these files:
2222
- [Recognizer.schema](../test/schemas/Recognizer.schema) includes `$role:"implements(IRecognizer)"` which extends the `IRecognizer` definition when merged.
2323
- [root.schema](../test/schemas/root.schema) is a schema file that includes `$kind:"IRecognizer"` in order to make use of the `IRecognizer` place holder. The `$role: []` ensures that this is available as a top-level object.
2424
- [app.schema](../test/schemas/app.schema) was created by this tool shows how all of these definitions are merged together. In particular if you look at `IRecognizer` you will see the definition that includes a string, or the complete definition of `Recognizer`.
25-
- [nuget3.en-us.uischema](../test/commands/dialog/projects/project3/nuget3.en-us.uischema) shows an example component .uischema file.
25+
- [nuget3.component1.en-us.uischema](../test/commands/dialog/projects/project3/nuget3.component1.en-us.uischema) shows an example component .uischema file.
2626
- [project3.en-us.uischema](../test/commands/dialog/oracles/project3.en-us.uischema) shows a merged .uischema file.
2727

2828
[root.dialog](../test/examples/root.dialog) Shows how you could use the resulting schema to enter in JSON schema and get intellisense.

packages/dialog/src/commands/dialog/merge.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default class DialogMerge extends Command {
2121
help: flags.help({char: 'h'}),
2222
nugetRoot: flags.string({description: 'Nuget root directory for debugging.', hidden: true}),
2323
output: flags.string({char: 'o', description: 'Output path and filename for merged .schema and .uischema. Defaults to first project name.', required: false}),
24+
schema: flags.string({char: 's', description: 'Path to merged .schema file to use if merging .uischema only.', required: false}),
2425
verbose: flags.boolean({char: 'v', description: 'Show verbose logging of files as they are processed.', default: false}),
2526
}
2627

@@ -31,7 +32,7 @@ export default class DialogMerge extends Command {
3132

3233
async run() {
3334
const {argv, flags} = this.parse(DialogMerge)
34-
let merger = new SchemaMerger(argv, flags.output, flags.verbose, this.log, this.warn, this.error, flags.extension, flags.debug, flags.nugetRoot)
35+
let merger = new SchemaMerger(argv, flags.output, flags.verbose, this.log, this.warn, this.error, flags.extension, flags.schema, flags.debug, flags.nugetRoot)
3536
await merger.merge()
3637
}
3738
}

packages/dialog/src/library/schemaMerger.ts

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,6 @@ function mergeObjects(obj1: any, obj2: any): any {
8080
return finalTarget
8181
}
8282

83-
function fileBase(filename: string): string {
84-
return filename.substring(0, filename.indexOf('.'))
85-
}
86-
87-
function fileLocale(filename: string): string {
88-
let extPos = filename.indexOf('.')
89-
let localePos = filename.indexOf('.', extPos + 1)
90-
return localePos > 0 ? filename.substring(extPos + 1, localePos) : ''
91-
}
92-
9383
// Build a tree of component (project or package) references in order to compute a topological sort.
9484
class Component {
9585
// Name of component
@@ -216,14 +206,13 @@ export default class SchemaMerger {
216206
private readonly warn: any
217207
private readonly error: any
218208
private readonly extensions: string[]
209+
private readonly schemaPath: string | undefined
219210
private readonly debug: boolean | undefined
211+
private nugetRoot = '' // Root where nuget packages are found
220212

221213
// Track packages that have been processed
222214
private readonly packages = new Set()
223215

224-
// Root where nuget packages are found
225-
private nugetRoot = ''
226-
227216
// Component tree
228217
private readonly root = new Component()
229218
private readonly parents: Component[] = []
@@ -273,17 +262,19 @@ export default class SchemaMerger {
273262
* @param warn Logger for warning messages.
274263
* @param error Logger for error messages.
275264
* @param extensions Extensions to analyze for loader.
265+
* @param schema Path to merged .schema is doin
276266
* @param debug Generate debug output.
277267
* @param nugetRoot Root directory for nuget packages. (Useful for testing.)
278268
*/
279-
public constructor(patterns: string[], output: string, verbose: boolean, log: any, warn: any, error: any, extensions?: string[], debug?: boolean, nugetRoot?: string) {
269+
public constructor(patterns: string[], output: string, verbose: boolean, log: any, warn: any, error: any, extensions?: string[], schema?: string, debug?: boolean, nugetRoot?: string) {
280270
this.patterns = patterns
281-
this.output = output ? ppath.join(ppath.dirname(output), ppath.basename(output, '.schema')) : ''
271+
this.output = output ? ppath.join(ppath.dirname(output), ppath.basename(output, ppath.extname(output))) : ''
282272
this.verbose = verbose
283273
this.log = log
284274
this.warn = warn
285275
this.error = error
286276
this.extensions = extensions || ['.schema', '.lu', '.lg', '.qna', '.dialog', '.uischema']
277+
this.schemaPath = schema
287278
this.debug = debug
288279
this.nugetRoot = nugetRoot || ''
289280
}
@@ -343,16 +334,33 @@ export default class SchemaMerger {
343334
private async mergeSchemas(): Promise<any> {
344335
let fullSchema: any
345336

337+
if (this.schemaPath) {
338+
// Passed in merged schema
339+
this.currentFile = this.schemaPath
340+
let schema = await fs.readJSON(this.schemaPath)
341+
this.currentFile = schema.$schema
342+
this.metaSchema = await getJSON(schema.$schema)
343+
this.validator.addSchema(this.metaSchema, 'componentSchema')
344+
this.currentFile = this.schemaPath
345+
this.validateSchema(schema)
346+
return parser.dereference(schema)
347+
}
348+
346349
// Delete existing output
350+
let outputPath = ppath.resolve(this.output + '.schema')
347351
await fs.remove(this.output + '.schema')
348352
await fs.remove(this.output + '.schema.final')
349353
await fs.remove(this.output + '.schema.expanded')
350354

351355
let componentPaths: PathComponent[] = []
352356
let schemas = this.files.get('.schema')
353357
if (schemas) {
354-
for (let path of schemas.values()) {
355-
componentPaths.push(path[0])
358+
for (let pathComponents of schemas.values()) {
359+
// Just take first definition if multiple ones
360+
let pathComponent = pathComponents[0]
361+
if (pathComponent.path !== outputPath) {
362+
componentPaths.push(pathComponent)
363+
}
356364
}
357365
}
358366

@@ -377,8 +385,10 @@ export default class SchemaMerger {
377385
delete component.$ref
378386
}
379387

380-
// Pick up meta-schema from first .dialog file
381-
if (!this.metaSchema) {
388+
if (!component.$schema) {
389+
this.missingSchemaError()
390+
} else if (!this.metaSchema) {
391+
// Pick up meta-schema from first .dialog file
382392
this.metaSchemaId = component.$schema
383393
this.currentFile = this.metaSchemaId
384394
this.metaSchema = await getJSON(component.$schema)
@@ -485,13 +495,20 @@ export default class SchemaMerger {
485495
let uiSchemas = this.files.get('.uischema')
486496
let result = {}
487497
if (uiSchemas) {
498+
if (!schema || this.failed) {
499+
this.error('Error must have a merged .schema to merge .uischema files')
500+
return
501+
}
502+
488503
this.log('Merging component .uischema files')
504+
if (this.schemaPath) {
505+
this.log(`Using merged schema ${this.schemaPath}`)
506+
}
489507
let outputName = ppath.basename(this.output)
490508
for (let [fileName, componentPaths] of uiSchemas.entries()) {
491509
// Skip files that match output .uischema
492510
if (!fileName.startsWith(outputName + '.')) {
493-
let kindName = fileBase(fileName)
494-
let localeName = fileLocale(fileName)
511+
let [kindName, localeName] = this.kindAndLocale(fileName, schema)
495512
let locale = result[localeName]
496513
if (!locale) {
497514
locale = result[localeName] = {}
@@ -511,9 +528,10 @@ export default class SchemaMerger {
511528
this.vlog(`Parsing ${this.currentFile}`)
512529
}
513530
let component = await fs.readJSON(path)
514-
515-
// Pick up meta-schema from first .uischema file
516-
if (!this.metaUISchema) {
531+
if (!component.$schema) {
532+
this.missingSchemaError()
533+
} else if (!this.metaUISchema) {
534+
// Pick up meta-schema from first .uischema file
517535
this.metaUISchemaId = component.$schema
518536
this.currentFile = this.metaUISchemaId
519537
this.metaUISchema = await getJSON(component.$schema)
@@ -526,6 +544,7 @@ export default class SchemaMerger {
526544
} else {
527545
this.validateUISchema(component)
528546
}
547+
delete component.$schema
529548
locale[kindName] = mergeObjects(locale[kindName], component)
530549
} catch (e) {
531550
this.parsingError(e)
@@ -542,18 +561,32 @@ export default class SchemaMerger {
542561
}
543562
}
544563
if (!this.failed) {
545-
for (let locale in result) {
564+
for (let locale of Object.keys(result)) {
565+
let uischema = {$schema: this.metaUISchemaId, ...result[locale]}
546566
this.currentFile = ppath.join(ppath.dirname(this.output), outputName + (locale ? '.' + locale : '') + '.uischema')
547567
this.log(`Writing ${this.currentFile}`)
548-
await fs.writeJSON(this.currentFile, result[locale], this.jsonOptions)
568+
await fs.writeJSON(this.currentFile, uischema, this.jsonOptions)
549569
}
550570
}
551571
}
552572
}
553573

574+
private kindAndLocale(filename: string, schema: any): [string, string] {
575+
let kindName = ppath.basename(filename, '.uischema')
576+
let locale = ''
577+
if (!schema.definitions[kindName]) {
578+
let split = kindName.lastIndexOf('.')
579+
if (split >= 0) {
580+
locale = kindName.substring(split + 1)
581+
kindName = kindName.substring(0, split)
582+
}
583+
}
584+
return [kindName, locale]
585+
}
586+
554587
// For C# copy all assets into generated/<package>/
555588
private async copyAssets(): Promise<void> {
556-
if (!this.failed) {
589+
if (!this.failed && !this.schemaPath) {
557590
let isCS = false
558591
for (let component of this.components) {
559592
if (component.path.endsWith('.csproj') || component.path.endsWith('.nuspec')) {
@@ -569,7 +602,8 @@ export default class SchemaMerger {
569602
for (let componentPath of componentPaths) {
570603
let component = componentPath.component
571604
let path = componentPath.path
572-
if (!component.isCSProject()) {
605+
// Don't copy .schema/.uischema so that we don't pick-up in project
606+
if (!component.isCSProject() && !path.endsWith('.schema') && !path.endsWith('.uischema')) {
573607
// Copy package files to output
574608
let relativePath = ppath.relative(ppath.dirname(component.path), path)
575609
let outputPath = ppath.join(generatedPath, componentPath.component.name, relativePath)
@@ -728,7 +762,6 @@ export default class SchemaMerger {
728762
for (let dependency of group.dependency) {
729763
dependencies.push(dependency.$)
730764
}
731-
break
732765
}
733766
}
734767
}
@@ -743,8 +776,8 @@ export default class SchemaMerger {
743776
} finally {
744777
this.popParent()
745778
}
746-
} else {
747-
this.parsingError(' Could not find nuspec')
779+
} else if (this.debug) {
780+
this.parsingWarning(' Could not find nuspec')
748781
}
749782
}
750783
}
@@ -768,8 +801,12 @@ export default class SchemaMerger {
768801
pkgPath = ppath.join(pkgPath, version || '')
769802
let nuspecPath = ppath.join(pkgPath, `${packageName}.nuspec`)
770803
await this.expandNuspec(nuspecPath)
771-
} else {
772-
this.parsingError(' Nuget package does not exist')
804+
} else if (this.debug) {
805+
// Ignore any missing dependencies assuming they are from a target framework like this:
806+
// <group targetFramework=".NETFramework4.0">
807+
// <dependency id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version = "1.1.28" />
808+
// </group>
809+
this.parsingWarning('Missing package')
773810
}
774811
} catch (e) {
775812
this.parsingWarning(e.message)
@@ -1449,6 +1486,12 @@ export default class SchemaMerger {
14491486
}
14501487
}
14511488

1489+
// Missing $schema
1490+
private missingSchemaError() {
1491+
this.error(`${this.currentFile}: Error missing $schema`)
1492+
this.failed = true
1493+
}
1494+
14521495
// Error in schema validity
14531496
private schemaError(err: Validator.ErrorObject): void {
14541497
this.error(`${this.currentFile}: ${err.dataPath} error: ${err.message}`)

0 commit comments

Comments
 (0)