@lionweb/utilities 0.7.0-beta.18 → 0.7.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,7 @@
5
5
  * Move `dependenciesThroughDirectInheritanceOf` and `verboseDependencies` functions from `class-core-generator` to `utilities` package, and expose it.
6
6
  * Rename `tsTypesForLanguage` → `tsTypeDefsForLanguage`.
7
7
  (This is a breaking change, but that function's hardly used anyway, and it's trivial to detect and fix.)
8
+ * Package `src/` again (— i.e., don't ignore for NPM packaging.)
8
9
 
9
10
 
10
11
  ## 0.6.12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionweb/utilities",
3
- "version": "0.7.0-beta.18",
3
+ "version": "0.7.0-beta.19",
4
4
  "description": "LionWeb utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,10 +15,10 @@
15
15
  "url": "https://github.com/LionWeb-io/lionweb-typescript/issues"
16
16
  },
17
17
  "dependencies": {
18
- "@lionweb/core": "0.7.0-beta.18",
19
- "@lionweb/json": "0.7.0-beta.18",
20
- "@lionweb/textgen-utils": "0.7.0-beta.18",
21
- "@lionweb/ts-utils": "0.7.0-beta.18",
18
+ "@lionweb/core": "0.7.0-beta.19",
19
+ "@lionweb/json": "0.7.0-beta.19",
20
+ "@lionweb/textgen-utils": "0.7.0-beta.19",
21
+ "@lionweb/ts-utils": "0.7.0-beta.19",
22
22
  "littoral-templates": "0.5.0",
23
23
  "nanoid": "5.1.5"
24
24
  },
package/src/hashing.ts ADDED
@@ -0,0 +1,148 @@
1
+ import { LionWebId } from "@lionweb/json"
2
+ import { createHash } from "crypto"
3
+ import { nanoid } from "nanoid"
4
+
5
+
6
+ /**
7
+ * Type def. for a hashing function string → string.
8
+ */
9
+ export type StringHasher = (str: string) => string
10
+
11
+
12
+ /**
13
+ * Hasher based on the {@link https://zelark.github.io/nano-id-cc/ `nanoid` NPM package}.
14
+ */
15
+ export const nanoIdGen = (): StringHasher =>
16
+ (_) => nanoid()
17
+
18
+
19
+ const defaultHashAlgorithm = "SHA256"
20
+
21
+ /**
22
+ * Type definition for objects that configure a
23
+ * {@link StringHasher hasher}.
24
+ */
25
+ export type StringHasherConfig = {
26
+ algorithm?: typeof defaultHashAlgorithm | string
27
+ salt?: string
28
+ encoding?: "base64url" | "base64"
29
+ }
30
+
31
+ /**
32
+ * Creates a {@link StringHasher hasher},
33
+ * optionally using a {@link StringHasherConfig configuration object}.
34
+ * The default is:
35
+ * - uses the *SHA256* hashing algorithm
36
+ * - without salt prefix string
37
+ */
38
+ export const hasher = (config?: StringHasherConfig): StringHasher => {
39
+ const algorithm = config?.algorithm ?? defaultHashAlgorithm
40
+ const salt = config?.salt ?? ""
41
+ const encoding = config?.encoding ?? "base64url"
42
+
43
+ return (data) => {
44
+ if (data === undefined) {
45
+ throw new Error(`expected data for hashing`)
46
+ }
47
+ return createHash(algorithm)
48
+ .update(salt + data)
49
+ .digest(encoding)
50
+ .toString()
51
+ }
52
+ }
53
+
54
+
55
+ /**
56
+ * Augments the given {@link StringHasher hasher} by checking
57
+ * whether it's been given unique strings,
58
+ * throwing an error when not.
59
+ */
60
+ export const checkUniqueData = (hasher: StringHasher): StringHasher => {
61
+ const datas: string[] = []
62
+ return (data) => {
63
+ if (datas.indexOf(data) > -1) {
64
+ throw new Error(`duplicate data encountered: "${data}"`)
65
+ }
66
+ datas.push(data)
67
+ return hasher(data)
68
+ }
69
+ }
70
+
71
+
72
+ /**
73
+ * Augments the given {@link StringHasher hasher} by checking
74
+ * whether it's been given defined ({@code !== undefined}) data,
75
+ * throwing an error when not.
76
+ */
77
+ export const checkDefinedData = (hasher: StringHasher): StringHasher =>
78
+ (data) => {
79
+ if (data === undefined) {
80
+ throw new Error(`expected data`)
81
+ }
82
+ return hasher(data)
83
+ }
84
+
85
+
86
+ /**
87
+ * Augments the given {@link StringHasher hasher} by checking
88
+ * whether that returns unique IDs, throwing an error when not.
89
+ */
90
+ export const checkUniqueId = (hasher: StringHasher): StringHasher => {
91
+ const ids: LionWebId[] = []
92
+
93
+ return (data) => {
94
+ const id = hasher(data)
95
+ if (ids.indexOf(id) > -1) {
96
+ throw new Error(`duplicate ID generated: "${id}"`)
97
+ }
98
+ ids.push(id)
99
+ return id
100
+ }
101
+ }
102
+
103
+
104
+ /**
105
+ * Augments the given {@link StringHasher hasher} by checking
106
+ * whether it returns valid IDs, meaning [Base64URL](https://www.base64url.com/).
107
+ * (See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Variants_summary_table).)
108
+ * If a generated ID is not valid, an error is thrown.
109
+ */
110
+ export const checkValidId = (hasher: StringHasher): StringHasher =>
111
+ (data) => {
112
+ const id = hasher(data)
113
+ if (!id.match(/^[A-Za-z0-9_-]+$/)) {
114
+ throw new Error(`generated ID is not valid: ${id}`)
115
+ }
116
+ return id
117
+ }
118
+
119
+
120
+ /**
121
+ * Type definition for transformers of {@link StringHasher hashers}.
122
+ */
123
+ export type StringHasherTransformer = (hasher: StringHasher) => StringHasher
124
+
125
+ /**
126
+ * Wraps the given ("initial") {@link StringHasher hasher}
127
+ * with the given {@link HasherTransformer hasher transfomers}.
128
+ * In other words:
129
+ *
130
+ * chain(hasher, trafo1, trafo2, ..., trafoN) === trafoN(...trafo2(trafo1(hasher)))
131
+ */
132
+ export const chain = (hasher: StringHasher, ...hasherTransformers: StringHasherTransformer[]): StringHasher =>
133
+ hasherTransformers.reduce((acc, current) => current(acc), hasher)
134
+
135
+
136
+ /**
137
+ * Wraps the given ("initial") {@link StringHasher hasher} with all
138
+ * {@link HasherTransformer hasher transfomers} defined above.
139
+ */
140
+ export const checkAll = (hasher: StringHasher): StringHasher =>
141
+ chain(
142
+ hasher,
143
+ checkDefinedData,
144
+ checkUniqueData,
145
+ checkValidId,
146
+ checkUniqueId
147
+ )
148
+
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./hashing.js"
2
+ export * from "./m1/reference-utils.js"
3
+ export * from "./m3/index.js"
4
+ export * from "./serialization/index.js"
5
+ export * from "./utils/json.js"
@@ -0,0 +1,2 @@
1
+ export {referenceValues, incomingReferences, referencesToOutOfScopeNodes} from "@lionweb/core"
2
+ export type {ReferenceValue} from "@lionweb/core"
@@ -0,0 +1,62 @@
1
+ // Copyright 2025 TRUMPF Laser SE and other contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License")
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
+ // SPDX-License-Identifier: Apache-2.0
17
+
18
+ import { Classifier, inheritsDirectlyFrom, Language, nameOf, nameSorted } from "@lionweb/core"
19
+ import { indent } from "@lionweb/textgen-utils"
20
+ import { cycleWith, uniquesAmong } from "@lionweb/ts-utils"
21
+ import { asString, when } from "littoral-templates"
22
+
23
+
24
+ /**
25
+ * @return all languages that any {@link Classifier classifier} of the given {@link Language language} depends on through direct inheritance.
26
+ */
27
+ export const dependenciesThroughDirectInheritanceOf = (language: Language) =>
28
+ uniquesAmong(
29
+ language.entities
30
+ .filter((entity) => entity instanceof Classifier)
31
+ .flatMap((entity) => inheritsDirectlyFrom(entity as Classifier))
32
+ .map((classifier) => classifier.language)
33
+ .filter((depLanguage) => depLanguage !== language)
34
+ )
35
+
36
+
37
+ /**
38
+ * @return the – currently only those arising from direct inheritance on classifier-level – dependencies of the given {@link Language languages},
39
+ * as a human-readable text
40
+ */
41
+ export const verboseDependencies = (languages: Language[]): string => {
42
+ const nameSortedLanguages = nameSorted(languages)
43
+ return asString([
44
+ nameSortedLanguages.map((language) => {
45
+ const deps = dependenciesThroughDirectInheritanceOf(language)
46
+ const what = `direct type-wise dependencies (through inheritance)`
47
+ const cycle = cycleWith(language, dependenciesThroughDirectInheritanceOf)
48
+ return deps.length === 0
49
+ ? `language ${language.name} has no ${what}`
50
+ : [
51
+ `language ${language.name} has the following ${what}:`,
52
+ indent([
53
+ nameSorted(dependenciesThroughDirectInheritanceOf(language)).map(nameOf),
54
+ when(cycle.length > 0)(
55
+ `⚠ language "${language.name}" is part of the following cycle through type-wise (through direct inheritance): ${cycle.map(nameOf).join(" -> ")}`
56
+ )
57
+ ])
58
+ ]
59
+ })
60
+ ])
61
+ }
62
+
@@ -0,0 +1,129 @@
1
+ import {
2
+ Annotation,
3
+ Concept,
4
+ Containment,
5
+ Enumeration,
6
+ Feature,
7
+ Interface,
8
+ isBuiltinNodeConcept,
9
+ isRef,
10
+ Language,
11
+ LanguageEntity,
12
+ Link,
13
+ nameSorted,
14
+ nonRelationalFeatures,
15
+ PrimitiveType,
16
+ relationsOf,
17
+ type,
18
+ unresolved
19
+ } from "@lionweb/core"
20
+ import { asString, indentWith, Template } from "littoral-templates"
21
+
22
+ // define some layouting basics/building algebra:
23
+
24
+ const indented = indentWith(` `)(1)
25
+
26
+ const block = (header: Template, elements: Template): Template =>
27
+ elements.length === 0 ? header : [`${header} {`, indented(elements), `}`]
28
+
29
+ const withNewLine = (content: Template): Template => [content, ``]
30
+
31
+ /**
32
+ * Generates a string with a Mermaid class diagram
33
+ * representing the given {@link Language LionCore instance}.
34
+ */
35
+ export const generateMermaidForLanguage = ({ entities }: Language) =>
36
+ asString([
37
+ "```mermaid",
38
+ `classDiagram
39
+ `,
40
+ indented(nameSorted(entities).map(generateForEntity)),
41
+ ``,
42
+ indented(nameSorted(entities).map(generateForRelationsOf)),
43
+ ``,
44
+ "```"
45
+ ])
46
+
47
+ const generateForEnumeration = ({ name, literals }: Enumeration) =>
48
+ withNewLine(block(`class ${name}`, [`<<enumeration>>`, literals.map(({name}) => name)]))
49
+
50
+ const generateForAnnotation = ({ name, features, extends: extends_, implements: implements_, annotates }: Annotation) => [
51
+ block(
52
+ [`class ${name}`, `<<Annotation>> ${name}`, isRef(annotates) ? `${name} ..> ${annotates.name}` : []],
53
+ nonRelationalFeatures(features).map(generateForNonRelationalFeature)
54
+ ),
55
+ isRef(extends_) && !isBuiltinNodeConcept(extends_) ? `${extends_.name} <|-- ${name}` : [],
56
+ implements_.filter(isRef).map(interface_ => `${interface_.name} <|.. ${name}`),
57
+ ``
58
+ ]
59
+
60
+ const generateForConcept = ({
61
+ name,
62
+ features,
63
+ abstract: abstract_,
64
+ extends: extends_ /*, implements: implements_*/,
65
+ partition
66
+ }: Concept) => [
67
+ block(`class ${partition ? `<<partition>> ` : ``}${name}`, nonRelationalFeatures(features).map(generateForNonRelationalFeature)),
68
+ abstract_ ? `<<Abstract>> ${name}` : [],
69
+ isRef(extends_) && !isBuiltinNodeConcept(extends_) ? `${extends_.name} <|-- ${name}` : [],
70
+ ``
71
+ ]
72
+
73
+ const generateForInterface = ({ name, features, extends: extends_ }: Interface) => [
74
+ block(`class ${name}`, nonRelationalFeatures(features).map(generateForNonRelationalFeature)),
75
+ `<<Interface>> ${name}`,
76
+ extends_.map(({ name: extendsName }) => `${extendsName} <|-- ${name}`),
77
+ ``
78
+ ]
79
+
80
+ const generateForNonRelationalFeature = (feature: Feature) => {
81
+ const { name, optional } = feature
82
+ const multiple = feature instanceof Link && feature.multiple
83
+ const type_ = type(feature)
84
+ const typeText = `${multiple ? `List~` : ``}${type_ === unresolved ? `???` : type_.name}${multiple ? `~` : ``}${optional ? `?` : ``}`
85
+ return `+${typeText} ${name}`
86
+ }
87
+
88
+ const generateForPrimitiveType = ({ name }: PrimitiveType) =>
89
+ `class ${name}
90
+ <<PrimitiveType>> ${name}
91
+ `
92
+
93
+ const generateForEntity = (entity: LanguageEntity) => {
94
+ if (entity instanceof Annotation) {
95
+ return generateForAnnotation(entity)
96
+ }
97
+ if (entity instanceof Concept) {
98
+ return generateForConcept(entity)
99
+ }
100
+ if (entity instanceof Enumeration) {
101
+ return generateForEnumeration(entity)
102
+ }
103
+ if (entity instanceof Interface) {
104
+ return generateForInterface(entity)
105
+ }
106
+ if (entity instanceof PrimitiveType) {
107
+ return generateForPrimitiveType(entity)
108
+ }
109
+ return `// unhandled language entity: <${entity.constructor.name}>${entity.name}`
110
+ }
111
+
112
+ const generateForRelationsOf = (entity: LanguageEntity) => {
113
+ const relations = relationsOf(entity)
114
+ return relations.length === 0 ? `` : relations.map(relation => generateForRelation(entity, relation))
115
+ }
116
+
117
+ const generateForRelation = ({ name: leftName }: LanguageEntity, relation: Link) => {
118
+ const { name: relationName, optional, multiple, type } = relation
119
+ const rightName = isRef(type) ? type.name : type === unresolved ? `<unresolved>` : `<null>`
120
+ const isContainment = relation instanceof Containment
121
+ const leftMultiplicity = isContainment ? `1` : `*`
122
+ const rightMultiplicity = (() => {
123
+ if (multiple) {
124
+ return "*"
125
+ }
126
+ return optional ? "0..1" : "1"
127
+ })()
128
+ return `${leftName} "${leftMultiplicity}" ${isContainment ? `o` : ``}--> "${rightMultiplicity}" ${rightName}: ${relationName}`
129
+ }
@@ -0,0 +1,147 @@
1
+ import {
2
+ Annotation,
3
+ Concept,
4
+ Containment,
5
+ Enumeration,
6
+ Feature,
7
+ Interface,
8
+ isBuiltinNodeConcept,
9
+ isRef,
10
+ Language,
11
+ LanguageEntity,
12
+ Link,
13
+ nameOf,
14
+ nameSorted,
15
+ nonRelationalFeatures,
16
+ PrimitiveType,
17
+ relationsOf,
18
+ type,
19
+ unresolved
20
+ } from "@lionweb/core"
21
+ import { asString, indentWith } from "littoral-templates"
22
+
23
+ const indented = indentWith(` `)(1)
24
+
25
+ /**
26
+ * Generates a string with a PlantUML class diagram
27
+ * representing the given {@link Language LionCore instance}.
28
+ */
29
+ export const generatePlantUmlForLanguage = ({ name, entities }: Language) =>
30
+ asString([
31
+ `@startuml
32
+ hide empty members
33
+
34
+ ' qualified name: "${name}"
35
+
36
+ `,
37
+ nameSorted(entities).map(generateForEntity),
38
+ `
39
+
40
+ ' relations:
41
+ `,
42
+ nameSorted(entities).map(generateForRelationsOf),
43
+ `
44
+ @enduml`
45
+ ])
46
+
47
+ const generateForEnumeration = ({ name, literals }: Enumeration) => [
48
+ `enum ${name} {`,
49
+ indented(literals.map(({name}) => name)),
50
+ `}
51
+ `
52
+ ]
53
+
54
+ const generateForAnnotation = ({ name, features, extends: extends_, implements: implements_, annotates }: Annotation) => {
55
+ const fragments: string[] = []
56
+ fragments.push(`annotation`, name)
57
+ if (isRef(extends_) && !isBuiltinNodeConcept(extends_)) {
58
+ fragments.push(`extends`, extends_.name)
59
+ }
60
+ if (implements_.length > 0) {
61
+ fragments.push(`implements`, implements_.map(nameOf).sort().join(", "))
62
+ }
63
+ const nonRelationalFeatures_ = nonRelationalFeatures(features)
64
+ return nonRelationalFeatures_.length === 0
65
+ ? [`${fragments.join(" ")}`, isRef(annotates) ? `${name} ..> ${annotates.name}` : [], ``]
66
+ : [`${fragments.join(" ")} {`, indented(nonRelationalFeatures_.map(generateForNonRelationalFeature)), `}`, ``]
67
+ }
68
+
69
+ const generateForConcept = ({ name, features, abstract: abstract_, extends: extends_, implements: implements_, partition }: Concept) => {
70
+ const fragments: string[] = []
71
+ if (abstract_) {
72
+ fragments.push(`abstract`)
73
+ }
74
+ fragments.push(`class`, name)
75
+ if (partition) {
76
+ fragments.push(`<<partition>>`)
77
+ }
78
+ if (isRef(extends_) && !isBuiltinNodeConcept(extends_)) {
79
+ fragments.push(`extends`, extends_.name)
80
+ }
81
+ if (implements_.length > 0) {
82
+ fragments.push(`implements`, implements_.map(nameOf).sort().join(", "))
83
+ }
84
+ const nonRelationalFeatures_ = nonRelationalFeatures(features)
85
+ return nonRelationalFeatures_.length === 0
86
+ ? [`${fragments.join(" ")}`, ``]
87
+ : [`${fragments.join(" ")} {`, indented(nonRelationalFeatures_.map(generateForNonRelationalFeature)), `}`, ``]
88
+ }
89
+
90
+ const generateForInterface = ({ name, extends: extends_, features }: Interface) => {
91
+ const fragments: string[] = [`interface`, name]
92
+ if (extends_.length > 0) {
93
+ fragments.push(`extends`, extends_.map(superInterface => superInterface.name).join(", "))
94
+ }
95
+ const nonRelationalFeatures_ = nonRelationalFeatures(features)
96
+ return nonRelationalFeatures_.length === 0
97
+ ? `${fragments.join(" ")}`
98
+ : [`${fragments.join(" ")} {`, indented(nonRelationalFeatures_.map(generateForNonRelationalFeature)), `}`, ``]
99
+ }
100
+
101
+ const generateForNonRelationalFeature = (feature: Feature) => {
102
+ const { name, optional } = feature
103
+ const multiple = feature instanceof Link && feature.multiple
104
+ const type_ = type(feature)
105
+ return `${name}: ${multiple ? `List<` : ``}${type_ === unresolved ? `???` : type_.name}${optional && !multiple ? `?` : ``}${multiple ? `>` : ``}`
106
+ }
107
+
108
+ const generateForPrimitiveType = ({ name }: PrimitiveType) => `class "${name}" <<primitive type>>`
109
+
110
+ const generateForEntity = (entity: LanguageEntity) => {
111
+ if (entity instanceof Annotation) {
112
+ return generateForAnnotation(entity)
113
+ }
114
+ if (entity instanceof Enumeration) {
115
+ return generateForEnumeration(entity)
116
+ }
117
+ if (entity instanceof Concept) {
118
+ return generateForConcept(entity)
119
+ }
120
+ if (entity instanceof Interface) {
121
+ return generateForInterface(entity)
122
+ }
123
+ if (entity instanceof PrimitiveType) {
124
+ return generateForPrimitiveType(entity)
125
+ }
126
+ return `' unhandled language entity: <${entity.constructor.name}>${entity.name}
127
+ `
128
+ }
129
+
130
+ const generateForRelationsOf = (entity: LanguageEntity) => {
131
+ const relations = relationsOf(entity)
132
+ return relations.length === 0 ? `` : relations.map(relation => generateForRelation(entity, relation))
133
+ }
134
+
135
+ const generateForRelation = ({ name: leftName }: LanguageEntity, relation: Link) => {
136
+ const { name: relationName, type, optional, multiple } = relation
137
+ const rightName = isRef(type) ? type.name : type === unresolved ? `<unresolved>` : `<null>`
138
+ const isContainment = relation instanceof Containment
139
+ const leftMultiplicity = isContainment ? `1` : `*`
140
+ const rightMultiplicity = multiple ? "*" : optional ? "0..1" : "1"
141
+ return `${leftName} "${leftMultiplicity}" ${isContainment ? `o` : ``}--> "${rightMultiplicity}" ${rightName}: ${relationName}`
142
+ }
143
+
144
+ /*
145
+ Notes:
146
+ 1. No construct for PrimitiveType in PlantUML.
147
+ */
@@ -0,0 +1,6 @@
1
+ export * from "./diagrams/Mermaid-generator.js"
2
+ export * from "./diagrams/PlantUML-generator.js"
3
+ export * from "./dependencies.js"
4
+ export * from "./infer-languages.js"
5
+ export * from "./textualizer.js"
6
+ export * from "./ts-generation/ts-types-generator.js"
@@ -0,0 +1,147 @@
1
+ import {
2
+ builtinPrimitives,
3
+ Concept,
4
+ Containment,
5
+ Language,
6
+ Link,
7
+ Property,
8
+ Reference
9
+ } from "@lionweb/core"
10
+ import { LionWebId, LionWebJsonChunk, LionWebKey } from "@lionweb/json"
11
+ import { asArray, chain, concatenator, lastOf } from "@lionweb/ts-utils"
12
+ import { hasher } from "../hashing.js"
13
+
14
+ const possibleKeySeparators = ["-", "_"]
15
+
16
+ const id = chain(concatenator("-"), hasher())
17
+ const key = lastOf
18
+
19
+ const { stringDataType, booleanDataType, integerDataType } = builtinPrimitives
20
+
21
+ export const inferLanguagesFromSerializationChunk = (chunk: LionWebJsonChunk): Language[] => {
22
+ const languages = new Map<string, Language>()
23
+ const concepts = new Map<string, Concept>()
24
+ const links = new Array<{ link: Link; conceptId: LionWebId }>()
25
+
26
+ for (const chunkLanguage of chunk.languages) {
27
+ const languageName = chunkLanguage.key
28
+ const language = new Language(languageName, chunkLanguage.version, id(languageName), key(languageName))
29
+ languages.set(languageName, language)
30
+ }
31
+
32
+ for (const node of chunk.nodes) {
33
+ const languageName = node.classifier.language
34
+ const entityName = node.classifier.key
35
+
36
+ const language = findLanguage(languages, languageName)
37
+ if (language.entities.filter(entity => entity.key === entityName).length) {
38
+ continue
39
+ }
40
+
41
+ const concept = new Concept(language, entityName, key(language.name, entityName), id(language.name, entityName), false)
42
+ language.havingEntities(concept)
43
+ concepts.set(node.id, concept)
44
+
45
+ for (const property of node.properties) {
46
+ const propertyName = deriveLikelyPropertyName(property.property.key)
47
+ if (concept.features.filter(feature => feature.key === propertyName).length) {
48
+ continue
49
+ }
50
+
51
+ const feature = new Property(
52
+ concept,
53
+ propertyName,
54
+ key(languageName, concept.name, propertyName),
55
+ id(languageName, concept.name, propertyName)
56
+ ).havingKey(property.property.key)
57
+
58
+ if (property.value === null) {
59
+ feature.isOptional()
60
+ } else {
61
+ if (isBoolean(property.value)) {
62
+ feature.ofType(booleanDataType)
63
+ } else if (isNumeric(property.value)) {
64
+ feature.ofType(integerDataType)
65
+ } else {
66
+ feature.ofType(stringDataType)
67
+ }
68
+ }
69
+
70
+ concept.havingFeatures(feature)
71
+ }
72
+
73
+ for (const containment of node.containments) {
74
+ const containmentName = containment.containment.key
75
+
76
+ if (concept.features.filter(feature => feature.key === containmentName).length) {
77
+ continue
78
+ }
79
+
80
+ const children = asArray(containment.children)
81
+ const feature = new Containment(
82
+ concept,
83
+ containmentName,
84
+ key(languageName, concept.name, containmentName),
85
+ id(languageName, concept.name, containmentName)
86
+ )
87
+ if (children.length) {
88
+ feature.isMultiple()
89
+ }
90
+ concept.havingFeatures(feature)
91
+
92
+ links.push({ link: feature, conceptId: children[0] })
93
+ }
94
+
95
+ for (const reference of node.references) {
96
+ const referenceName = reference.reference.key
97
+ if (concept.features.filter(feature => feature.key === referenceName).length) {
98
+ continue
99
+ }
100
+
101
+ const feature = new Reference(
102
+ concept,
103
+ referenceName,
104
+ key(languageName, concept.name, referenceName),
105
+ id(languageName, concept.name, referenceName)
106
+ )
107
+ concept.havingFeatures(feature)
108
+
109
+ const value = reference.targets[0].reference
110
+ links.push({ link: feature, conceptId: value })
111
+ }
112
+ }
113
+
114
+ for (const link of links) {
115
+ const linkedConcept = concepts.get(link.conceptId)
116
+ if (linkedConcept) {
117
+ link.link.ofType(linkedConcept)
118
+ } else {
119
+ // Containment of primitive types??
120
+ }
121
+ }
122
+
123
+ return Array.from(languages.values())
124
+ }
125
+
126
+ const findLanguage = (languages: Map<string, Language>, languageName: string) => {
127
+ const language = languages.get(languageName)
128
+ if (language === undefined) {
129
+ throw new Error(`Language '${languageName} does not exist in the languages section`)
130
+ }
131
+ return language
132
+ }
133
+
134
+ export const deriveLikelyPropertyName = (key: LionWebKey) => {
135
+ for (const separator of possibleKeySeparators) {
136
+ const name = key.split(separator)[2]
137
+ if (name) {
138
+ return name
139
+ }
140
+ }
141
+
142
+ return key
143
+ }
144
+
145
+ const isBoolean = (value: string) => value === "true" || value === "false"
146
+
147
+ const isNumeric = (value: string) => !isNaN(parseFloat(value))