archsight 0.3.3 → 0.4.1
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.
- checksums.yaml +4 -4
- data/README.md +7 -1
- data/docs/archimate.md +21 -1
- data/docs/configuration.md +1 -0
- data/docs/diagram.md +15 -3
- data/docs/docker.md +4 -1
- data/docs/icons.md +8 -2
- data/docs/import.md +178 -55
- data/docs/index.md.erb +24 -18
- data/docs/modeling.md +645 -34
- data/docs/pages.md +46 -15
- data/docs/search.md +21 -1
- data/docs/togaf.md +2 -1
- data/lib/archsight/analysis/result.rb +8 -2
- data/lib/archsight/analysis/sandbox.rb +8 -0
- data/lib/archsight/analysis/typst_renderer.rb +245 -0
- data/lib/archsight/analysis.rb +1 -0
- data/lib/archsight/annotations/annotation.rb +30 -3
- data/lib/archsight/annotations/architecture_annotations.rb +47 -0
- data/lib/archsight/annotations/link_annotations.rb +15 -0
- data/lib/archsight/annotations/resource_reference.rb +12 -0
- data/lib/archsight/cli.rb +124 -23
- data/lib/archsight/database.rb +40 -0
- data/lib/archsight/diagram/cli.rb +50 -2
- data/lib/archsight/diagram/formatter.rb +112 -0
- data/lib/archsight/diagram/routing/edge_router/orthogonal_path.rb +38 -10
- data/lib/archsight/diagram/routing/edge_router.rb +29 -4
- data/lib/archsight/diagram/routing/edge_routing.rb +61 -5
- data/lib/archsight/diagram/style/theme.rb +7 -2
- data/lib/archsight/documentation.rb +4 -4
- data/lib/archsight/editor/page_source.rb +13 -1
- data/lib/archsight/export/confluence/analysis_output.rb +100 -0
- data/lib/archsight/export/confluence/client.rb +13 -0
- data/lib/archsight/export/confluence/diagram_links.rb +1 -1
- data/lib/archsight/export/confluence/exporter.rb +43 -14
- data/lib/archsight/export/confluence/labels.rb +25 -0
- data/lib/archsight/export/confluence/page_properties.rb +6 -9
- data/lib/archsight/export/confluence/storage.rb +63 -14
- data/lib/archsight/export/confluence/tables.rb +2 -3
- data/lib/archsight/git_source.rb +81 -0
- data/lib/archsight/helpers/asset_images.rb +3 -4
- data/lib/archsight/helpers/embeds.rb +18 -7
- data/lib/archsight/helpers/resource_resolver.rb +25 -0
- data/lib/archsight/import/executor.rb +140 -16
- data/lib/archsight/import/handler.rb +55 -6
- data/lib/archsight/import/handler_loader.rb +64 -0
- data/lib/archsight/import/handlers/cache_pruner.rb +30 -7
- data/lib/archsight/import/handlers/command.rb +120 -0
- data/lib/archsight/import/handlers/github.rb +4 -1
- data/lib/archsight/import/handlers/gitlab.rb +4 -1
- data/lib/archsight/import/handlers/go_dep_resolver.rb +10 -2
- data/lib/archsight/import/handlers/go_grapher.rb +102 -5
- data/lib/archsight/import/handlers/go_module_parser.rb +88 -0
- data/lib/archsight/import/handlers/grapher.rb +40 -1
- data/lib/archsight/import/handlers/repository.rb +3 -0
- data/lib/archsight/import/handlers/rest_api_index.rb +3 -0
- data/lib/archsight/import/registry.rb +22 -1
- data/lib/archsight/import.rb +89 -1
- data/lib/archsight/linter/structure.rb +239 -0
- data/lib/archsight/linter.rb +123 -16
- data/lib/archsight/markdown.rb +48 -0
- data/lib/archsight/openapi.rb +169 -0
- data/lib/archsight/page_loader.rb +12 -1
- data/lib/archsight/page_tree.rb +2 -3
- data/lib/archsight/query/evaluator.rb +13 -6
- data/lib/archsight/query/parser.rb +73 -6
- data/lib/archsight/query.rb +1 -1
- data/lib/archsight/references.rb +3 -3
- data/lib/archsight/renderer.rb +80 -23
- data/lib/archsight/resources/analysis.rb +3 -2
- data/lib/archsight/resources/application_component.rb +75 -7
- data/lib/archsight/resources/application_interface.rb +12 -0
- data/lib/archsight/resources/application_service.rb +12 -1
- data/lib/archsight/resources/base.rb +58 -5
- data/lib/archsight/resources/business_actor.rb +5 -0
- data/lib/archsight/resources/business_contract.rb +72 -0
- data/lib/archsight/resources/business_product.rb +12 -0
- data/lib/archsight/resources/business_role.rb +1 -0
- data/lib/archsight/resources/implementation_deliverable.rb +1 -0
- data/lib/archsight/resources/implementation_plateau.rb +8 -2
- data/lib/archsight/resources/implementation_work_package.rb +2 -1
- data/lib/archsight/resources/import.rb +37 -11
- data/lib/archsight/resources/location.rb +56 -0
- data/lib/archsight/resources/motivation_requirement.rb +6 -0
- data/lib/archsight/resources/motivation_stakeholder.rb +14 -0
- data/lib/archsight/resources/page.rb +4 -7
- data/lib/archsight/resources/strategy_capability.rb +9 -0
- data/lib/archsight/resources/technology_artifact.rb +11 -4
- data/lib/archsight/resources/technology_communication_network.rb +91 -0
- data/lib/archsight/resources/technology_device.rb +88 -0
- data/lib/archsight/resources/technology_equipment.rb +75 -0
- data/lib/archsight/resources/technology_facility.rb +70 -0
- data/lib/archsight/resources/technology_node.rb +50 -0
- data/lib/archsight/resources/technology_service.rb +33 -0
- data/lib/archsight/resources/technology_system_software.rb +28 -0
- data/lib/archsight/resources.rb +12 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/analysis_helpers.rb +55 -0
- data/lib/archsight/web/api/docs.rb +2 -1
- data/lib/archsight/web/api/json_helpers.rb +3 -37
- data/lib/archsight/web/api/openapi/spec.yaml +146 -35
- data/lib/archsight/web/api/page_helpers.rb +1 -8
- data/lib/archsight/web/api/routes.rb +9 -4
- data/lib/archsight/web/application.rb +40 -4
- data/lib/archsight/web/editor/routes.rb +2 -1
- data/lib/archsight/web/public/vendor/redoc/LICENSE +22 -0
- data/lib/archsight/web/public/vendor/redoc/VERSION +1 -0
- data/lib/archsight/web/public/vendor/redoc/api.html +27 -0
- data/lib/archsight/web/public/vendor/redoc/api.js +19 -0
- data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js +1851 -0
- data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js.LICENSE.txt +102 -0
- data/lib/archsight/web/public/vue/DocPage-C2ubTXH7.js +1 -0
- data/lib/archsight/web/public/vue/EditorPage-Dso6PLMn.js +35 -0
- data/lib/archsight/web/public/vue/ErrorPage-DnZ6YJLi.js +2 -0
- data/lib/archsight/web/public/vue/GraphView-CTl03Yre.js +1 -0
- data/lib/archsight/web/public/vue/HomePage-C339nihQ.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-DFW7tOrD.js +1 -0
- data/lib/archsight/web/public/vue/InstanceRouter-ZaCcjlIX.css +1 -0
- data/lib/archsight/web/public/vue/KindList-C0oaM3CF.js +1 -0
- data/lib/archsight/web/public/vue/KindList-yYcRT2BV.css +1 -0
- data/lib/archsight/web/public/vue/PageView-B93woeN3.css +1 -0
- data/lib/archsight/web/public/vue/PageView-OZB6G9Wu.js +1 -0
- data/lib/archsight/web/public/vue/QueryError-C2xLkCEm.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-D75O87zG.css +1 -0
- data/lib/archsight/web/public/vue/ResourceList-DooWFwQP.js +2 -0
- data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
- data/lib/archsight/web/public/vue/{SearchResults-BewvsfOc.css → SearchResults-BV6UHriJ.css} +1 -1
- data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -0
- data/lib/archsight/web/public/vue/WikiPage-TpmM1VAF.css +1 -0
- data/lib/archsight/web/public/vue/architecture-WOLXFQ4H-BGN-TV5r.js +1 -0
- data/lib/archsight/web/public/vue/cynefin-EF2NZ3EQ-CZd6g_tq.js +1 -0
- data/lib/archsight/web/public/vue/eventmodeling-K75KTNOO-CYnbEx8O.js +1 -0
- data/lib/archsight/web/public/vue/gitGraph-VSP46ZUC-DialfQSk.js +1 -0
- data/lib/archsight/web/public/vue/graphviz-1lWBhpEX.js +3 -0
- data/lib/archsight/web/public/vue/index-BLSE5DSv.css +1 -0
- data/lib/archsight/web/public/vue/index-CLuLTE5I.js +3 -0
- data/lib/archsight/web/public/vue/info-OHQRW6UA-BuhOW7ss.js +1 -0
- data/lib/archsight/web/public/vue/katex-B_JcIwqj.js +257 -0
- data/lib/archsight/web/public/vue/mermaid-C3bOamfn.js +3999 -0
- data/lib/archsight/web/public/vue/packet-JDAUHWVQ-CUeRz7w4.js +1 -0
- data/lib/archsight/web/public/vue/pie-XZMESJXO-DnuewJI6.js +1 -0
- data/lib/archsight/web/public/vue/radar-ABXABTNO-Bi9l8bcv.js +1 -0
- data/lib/archsight/web/public/vue/railroad-I3PHUGI6-DSlxFbxk.js +1 -0
- data/lib/archsight/web/public/vue/railroad-abnf-LNEFI6M7-D6Kqu8wu.js +1 -0
- data/lib/archsight/web/public/vue/railroad-ebnf-DWYD2UWJ-MR7uCYT0.js +1 -0
- data/lib/archsight/web/public/vue/railroad-peg-JR7BVNR7-BL7aKIc-.js +1 -0
- data/lib/archsight/web/public/vue/rolldown-runtime-COiEfhsA.js +1 -0
- data/lib/archsight/web/public/vue/treeView-D4JQ5SDB-CamtB04D.js +1 -0
- data/lib/archsight/web/public/vue/treemap-SAJKECNS-DeQxu_HM.js +1 -0
- data/lib/archsight/web/public/vue/{useGraphviz-DlSnFeCL.js → useGraphviz-Dnm2pkon.js} +2 -2
- data/lib/archsight/web/public/vue/useHighlight-BH32C7yt.js +10 -0
- data/lib/archsight/web/public/vue/usePanZoom-HxgEsqq2.js +12 -0
- data/lib/archsight/web/public/vue/wardley-7MLQ67FV-BvKIB6Tv.js +1 -0
- data/lib/archsight/web/public/vue.html +5 -5
- metadata +68 -41
- data/lib/archsight/web/public/vue/ApiDocsPage-BgqnQgwa.js +0 -1
- data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
- data/lib/archsight/web/public/vue/DocPage-CNO71nBH.js +0 -1
- data/lib/archsight/web/public/vue/EditorPage-DR0FCNTz.js +0 -35
- data/lib/archsight/web/public/vue/ErrorPage-BfYArv6s.js +0 -2
- data/lib/archsight/web/public/vue/GraphView-BVTW30Ak.js +0 -1
- data/lib/archsight/web/public/vue/HomePage-Wk9P4Pma.js +0 -2
- data/lib/archsight/web/public/vue/InstanceRouter-BV8ycydz.js +0 -1
- data/lib/archsight/web/public/vue/InstanceRouter-jeavM6PW.css +0 -1
- data/lib/archsight/web/public/vue/KindList-C8yMsN5J.js +0 -1
- data/lib/archsight/web/public/vue/PageView-BaN6TyJB.js +0 -1
- data/lib/archsight/web/public/vue/QueryError-D790wH-a.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +0 -1
- data/lib/archsight/web/public/vue/ResourceList-D-66nas2.js +0 -2
- data/lib/archsight/web/public/vue/SearchResults-CHyqIerQ.js +0 -1
- data/lib/archsight/web/public/vue/WikiPage-D2svpk6B.js +0 -13
- data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +0 -1
- data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +0 -1
- data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +0 -1
- data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +0 -1
- data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +0 -1
- data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
- data/lib/archsight/web/public/vue/index-BXXkUK1V.js +0 -3
- data/lib/archsight/web/public/vue/index-Cov1SnzY.css +0 -1
- data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +0 -1
- data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
- data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +0 -3334
- data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +0 -1
- data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +0 -1
- data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +0 -1
- data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +0 -1
- data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +0 -1
- data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +0 -1
- data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +0 -1
- data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +0 -1
- data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +0 -1
- data/lib/archsight/web/public/vue/useHighlight-D5Gf8DLl.js +0 -10
- data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +0 -1
data/docs/pages.md
CHANGED
|
@@ -18,7 +18,9 @@ properties:
|
|
|
18
18
|
Git Repository: https://git.example.com/org/language-strategy
|
|
19
19
|
Ticket: "{jira:ARCH-42}"
|
|
20
20
|
toc: yes
|
|
21
|
-
|
|
21
|
+
links:
|
|
22
|
+
confluence: https://confluence.example.com/spaces/ARCH/pages/12345/Language+Strategy
|
|
23
|
+
jira: https://jira.example.com/browse/ARCH-12
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
# Language Strategy
|
|
@@ -34,7 +36,7 @@ confluence: https://confluence.example.com/spaces/ARCH/pages/12345/Language+Stra
|
|
|
34
36
|
| `properties` | Everything else you want to record, as a mapping of `key: value` pairs (kept in order and shown in the page header). Values are inline markdown: links, emphasis, `{macros}` such as `{jira:KEY}` and `[[wiki links]]` work, bare URLs become links. In the API and in queries it is `page/properties`, one `Key: value` per line |
|
|
35
37
|
| `status` | Free text, e.g. `rfc`, `wip`, `approved` |
|
|
36
38
|
| `toc` | `yes` shows a table of contents |
|
|
37
|
-
| `
|
|
39
|
+
| `links` | A mapping of names to URLs: where the page lives in other systems (`confluence`, `jira`, `github`, ...), shown as links with the icon of the system. Each entry is the annotation `link/<name>` (`link/confluence`), the same links every other resource can have; a link has no direction, it is where the page comes from or where it is published to. The old `confluence: <url>` key still works, is reported as deprecated by `archsight lint` and is moved into `links` when the page is saved in the editor |
|
|
38
40
|
| `name` | Optional unique name; defaults to the file name without `.md`. Files with the same name in different folders need an explicit `name` |
|
|
39
41
|
|
|
40
42
|
Markdown files without a frontmatter block are ignored.
|
|
@@ -128,21 +130,27 @@ is served:
|
|
|
128
130
|
such requests, so an image embedded in a diagram from an `https://` URL is not shown. Formulas are not typeset.
|
|
129
131
|
- **Kubernetes**: binary files do not fit a ConfigMap (1 MB limit), provide the resources with git-sync or a volume.
|
|
130
132
|
|
|
131
|
-
## Embedding views and
|
|
133
|
+
## Embedding views, analyses and descriptions
|
|
132
134
|
|
|
133
135
|
`![[View/Name]]` and `![[Analysis/Name]]` show the live content of a view (its result list) or an analysis (its
|
|
134
|
-
result) inside a page, or in the description of any resource
|
|
136
|
+
result) inside a page, or in the description of any resource. Any other resource, written `![[Kind/Name]]`, shows its
|
|
137
|
+
description (`architecture/description`), for example the goal a concept is written for:
|
|
135
138
|
|
|
136
139
|
```markdown
|
|
137
140
|
![[View/View:ServiceDependencies]]
|
|
138
141
|
![[Analysis/Analysis:Service:Count]]
|
|
142
|
+
![[MotivationGoal/Goal:SimplifyArchitecture]]
|
|
139
143
|
```
|
|
140
144
|
|
|
145
|
+
The **Embed** button on the page of a resource, view or analysis copies its `![[Kind/Name]]`. An embedded description
|
|
146
|
+
is set off like a quote and links to its resource; `![[...]]` embeds inside it are shown as links (two descriptions
|
|
147
|
+
cannot embed each other without end). Pages cannot be embedded.
|
|
148
|
+
|
|
141
149
|
The page is shown immediately. Each embed loads on its own, with a spinner, and an analysis runs when the page opens
|
|
142
|
-
(
|
|
150
|
+
(reload the page to run it again), so a slow query or script never delays the text around it. The embed
|
|
143
151
|
links to the view or analysis page. The server only writes a placeholder: rendering a page (`GET /api/v1/pages/{name}`)
|
|
144
152
|
never runs a query or a script, and API or MCP consumers get `<div class="kind-embed" data-kind data-name>` with a plain
|
|
145
|
-
link to the resource.
|
|
153
|
+
link to the resource. An unknown name, a page or an embed written inline in
|
|
146
154
|
a sentence (instead of on its own line) is shown as a marker or a link, and `archsight lint` reports embeds that do not
|
|
147
155
|
resolve. Code blocks and inline code are left alone.
|
|
148
156
|
|
|
@@ -269,16 +277,26 @@ The form has the frontmatter keys as fields and the body as a markdown field (ri
|
|
|
269
277
|
conflict if the file changed since the form was opened, and a file with broken frontmatter, without
|
|
270
278
|
frontmatter or with a changed name is rejected before it is written.
|
|
271
279
|
|
|
272
|
-
The saved frontmatter is rewritten in the order `title, tags, author, owner, status, toc,
|
|
273
|
-
`name` and keys the form does not know are kept; YAML comments inside the frontmatter are not.
|
|
280
|
+
The saved frontmatter is rewritten in the order `title, tags, author, owner, status, toc, created, updated, properties`.
|
|
281
|
+
`name`, `links` and keys the form does not know are kept (an old `confluence:` key becomes `links: { confluence: ... }`); YAML comments inside the frontmatter are not.
|
|
274
282
|
|
|
275
283
|
Inside the rich editor, an ` ```asd ` block shows a live [diagram preview](/doc/diagram#preview-in-the-editor).
|
|
276
284
|
|
|
285
|
+
### Migrating `confluence:` to `links`
|
|
286
|
+
|
|
287
|
+
`archsight lint` lists every page that still uses `confluence:` (or `page/confluence`) as a deprecation without failing.
|
|
288
|
+
Pages written as markdown can be moved with a find and replace over the resources directory; a page that has `links`
|
|
289
|
+
already keeps that value:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
find resources -name '*.md' -exec perl -0pi -e 's/^confluence:\s*(\S+)\s*$/links:\n confluence: $1/m' {} +
|
|
293
|
+
```
|
|
294
|
+
|
|
277
295
|
## Exporting to Confluence
|
|
278
296
|
|
|
279
|
-
`archsight export --to confluence [PAGE...]` publishes pages to the Confluence page named in their `confluence
|
|
297
|
+
`archsight export --to confluence [PAGE...]` publishes pages to the Confluence page named in their `links.confluence` frontmatter
|
|
280
298
|
(Confluence Data Center; a page URL such as `https://host/spaces/KEY/pages/12345/Title`, `.../pages/viewpage.action?pageId=12345`
|
|
281
|
-
or `.../display/KEY/Title`). Without `PAGE` every page that has a `confluence
|
|
299
|
+
or `.../display/KEY/Title`). Without `PAGE` every page that has a `confluence` link is exported.
|
|
282
300
|
|
|
283
301
|
```bash
|
|
284
302
|
archsight export --to confluence -r resources # all linked pages
|
|
@@ -290,8 +308,13 @@ archsight export --to confluence handbook-home --force # overwrite change
|
|
|
290
308
|
**Page properties**: the frontmatter is exported as a Page Properties table (the `details` macro) above the body, so Confluence
|
|
291
309
|
keeps its property reports working: Document status (a status lozenge; `wip`/`draft` yellow, `rfc`/`review` blue,
|
|
292
310
|
`approved`/`done`/`ga` green, `deprecated`/`rejected` red, anything else grey), Document owner, Document author, Teams (the
|
|
293
|
-
`Team:<name>` tags)
|
|
294
|
-
exported. The Confluence import (not part of Archsight) does the reverse, which is why `Team:` tags and `properties` exist.
|
|
311
|
+
`Team:<name>` tags) and every entry of `properties`. People are written as their name. `created` and `updated` are not
|
|
312
|
+
exported, and neither are the other tags: they are the page's labels (below). The Confluence import (not part of Archsight) does the reverse, which is why `Team:` tags and `properties` exist.
|
|
313
|
+
|
|
314
|
+
**Labels**: the tags of a page become its Confluence labels, so label macros and searches find the page. A label is
|
|
315
|
+
lowercase without spaces or `!#&()*,.:;<>?@[]^`, those become a dash (`Team:BSS` is the label `team-bss`). A tag that is
|
|
316
|
+
removed removes the label again, but only labels the export added itself (recorded in the marker): labels somebody set
|
|
317
|
+
in Confluence stay. Labels are not versioned, so a page whose labels alone change gets no new version.
|
|
295
318
|
|
|
296
319
|
**Credentials** are a personal access token, set as `confluence.token` in the [configuration file](/doc/configuration)
|
|
297
320
|
(`~/.config/archsight/archsight.yaml`, `--config PATH` for another file) or in `ARCHSIGHT_CONFLUENCE_TOKEN`. The host comes from
|
|
@@ -316,9 +339,9 @@ confluence:
|
|
|
316
339
|
With `drawio` off nothing draw.io-specific is written, so any Confluence shows the diagrams. Rendering a `.drawio` needs the
|
|
317
340
|
draw.io desktop CLI (`drawio`, or `ARCHSIGHT_DRAWIO_CLI`) on the machine that exports; the preview of an SVG needs
|
|
318
341
|
`rsvg-convert` or that CLI. A diagram that cannot be rendered fails the page instead of leaving a blank diagram. Attachments an
|
|
319
|
-
earlier export added and the page no longer uses are removed; attachments added by others are left alone. `[[links]]` and markdown links to pages (`[text](/pages/name)`) that have a Confluence link
|
|
342
|
+
earlier export added and the page no longer uses are removed (a draw.io diagram is attached under its name and a hash of its content, so a changed diagram is a new attachment, because the draw.io macro would keep showing the first version of an attachment it names); attachments added by others are left alone. `[[links]]` and markdown links to pages (`[text](/pages/name)`) that have a Confluence link
|
|
320
343
|
become links to them, other links to Archsight (pages without a Confluence link, resources, searches) are plain text. A page with a broken image, a diagram that does not render or an invalid
|
|
321
|
-
```` ```view ````/```` ```requirements ```` block is not exported and reported as failed. The Confluence title is kept. The exported page uses the Confluence layout: the page properties top left, the note that the page is generated by Archsight top right, and the content below in a section of its own.
|
|
344
|
+
```` ```view ````/```` ```requirements ```` block is not exported and reported as failed. The Confluence title is kept. The exported page uses the Confluence layout: the page properties top left, the note that the page is generated by Archsight top right, and the content below in a section of its own. When the resources are a git checkout with an `origin` remote, the note names the repository and links the source file to its page on the git host (GitHub `/blob/`, GitLab `/-/blob/`, on the checked out branch, or the commit when the HEAD is detached; other hosts get only the repository link, and so does a file git does not track). User names and tokens in the remote URL never appear in the link.
|
|
322
345
|
|
|
323
346
|
**Views and requirements** are live in Archsight, so Confluence gets a regular table with the data of the moment of the export (every
|
|
324
347
|
export writes the current data; a page whose tables did not change is `unchanged`): `![[View/Name]]` on a line of its own, a
|
|
@@ -326,7 +349,15 @@ export writes the current data; a page whose tables did not change is `unchanged
|
|
|
326
349
|
the view (Name, Kind unless `list:name`, the `view/fields`, sorted by `view/sort`; times are the stored values, not "3 days ago") or
|
|
327
350
|
the requirements table (status and priority as status lozenges: implemented green, partial yellow, planned blue; `must` red,
|
|
328
351
|
`should` yellow, `may` grey), and names of pages that have a Confluence link as links. At most 200 rows are exported, a note says how many
|
|
329
|
-
were left out. A `![[View/..]]` that is not alone on its line or names no view stays a note
|
|
352
|
+
were left out. A `![[View/..]]` that is not alone on its line or names no view stays a note.
|
|
353
|
+
|
|
354
|
+
**Analyses** (`![[Analysis/Name]]` on a line of its own) are run at the export and their result is written into the page, the data
|
|
355
|
+
of the moment like a view: headings, text, tables and lists as Confluence content (`[[links]]` as for the page, `<br>` and newlines
|
|
356
|
+
in a cell break the line), `info`/`warning`/`error` messages as info, note and warning panels, code as a code macro. The result
|
|
357
|
+
holds no time or run time, so a page whose analysis gives the same result stays `unchanged`. A failing analysis is a note in the page,
|
|
358
|
+
an analysis that is not alone on its line or names no analysis stays a note.
|
|
359
|
+
An embedded description (`![[MotivationGoal/Name]]` on a line of its own) is text, not live content: it is written into the page
|
|
360
|
+
as it is in Archsight; embeds inside it stay notes.
|
|
330
361
|
|
|
331
362
|
**Links in diagrams**: a node with `resource "Some Page"` links to the Confluence page of that wiki page (the page's own
|
|
332
363
|
`confluence:` link) instead of its Archsight address, which means nothing in Confluence. A reference to a page without a
|
data/docs/search.md
CHANGED
|
@@ -24,9 +24,17 @@ Type any word to search by name (case-insensitive regex match):
|
|
|
24
24
|
name != "OldService" # not equal
|
|
25
25
|
name in ("a", "b") # name is one of several options
|
|
26
26
|
|
|
27
|
+
Names often contain colons (`Archsight:CLI`) and so do [aliases](/doc/modeling#identity-and-links) (`ITGS:A001`). Typed
|
|
28
|
+
without spaces they are one word, found by name or alias like any other bare word:
|
|
29
|
+
|
|
30
|
+
Archsight:CLI # the resources named Archsight:CLI
|
|
31
|
+
ITGS:A001 # the resource with the alias ITGS:A001
|
|
32
|
+
ApplicationService:ITGS:A001 # the same, in one kind
|
|
33
|
+
|
|
27
34
|
## Kind Filter
|
|
28
35
|
|
|
29
|
-
Prefix your query with `Kind:` to filter by resource type
|
|
36
|
+
Prefix your query with `Kind:` to filter by resource type (a capitalised word that is a kind, followed by a space or
|
|
37
|
+
nothing; `Kind:Name` without a space is the kind and a name):
|
|
30
38
|
|
|
31
39
|
TechnologyArtifact: # all of this kind
|
|
32
40
|
TechnologyArtifact: activity/status == "active" # filtered
|
|
@@ -246,6 +254,18 @@ Active containerized services:
|
|
|
246
254
|
|
|
247
255
|
TechnologyArtifact: activity/status == "active" & repository/artifacts == "container"
|
|
248
256
|
|
|
257
|
+
Everything that is on its way out:
|
|
258
|
+
|
|
259
|
+
lifecycle/status in ("deprecated", "sunset", "end-of-life")
|
|
260
|
+
|
|
261
|
+
Resources in use (a resource without a status is active, the annotation's default):
|
|
262
|
+
|
|
263
|
+
lifecycle/status == "active"
|
|
264
|
+
|
|
265
|
+
What is planned, not yet in use:
|
|
266
|
+
|
|
267
|
+
lifecycle/status == "planned"
|
|
268
|
+
|
|
249
269
|
Services exposing APIs:
|
|
250
270
|
|
|
251
271
|
ApplicationComponent: -> ApplicationInterface
|
data/docs/togaf.md
CHANGED
|
@@ -123,13 +123,14 @@ The metamodel defines architectural entities and their relationships across all
|
|
|
123
123
|
**Cross-Cutting:**
|
|
124
124
|
|
|
125
125
|
- **Principle**, **Constraint**, **Requirement**, **Location**
|
|
126
|
+
- **Stakeholder**: `MotivationStakeholder`, with its concerns (`hasConcern`); the organisation unit or actor behind it is linked with `representedBy` (`BusinessActor`, `BusinessRole`). The stakeholder that has the concern is the source of a requirement, as recorded in the requirements repository
|
|
126
127
|
- **Gap**, **Work Package**, **Deliverable**, **Plateau**, **Implementation Event**: the Implementation & Migration layer (`ImplementationGap`, `ImplementationWorkPackage`, `ImplementationDeliverable`, `ImplementationPlateau`, `ImplementationEvent`)
|
|
127
128
|
|
|
128
129
|
**Governance:**
|
|
129
130
|
|
|
130
131
|
- **Control**: a decision-making step with accountability and authority, applied to a process or function. ArchiMate has no element for it; Archsight models it as `BusinessControl`, which a `BusinessProcess` is `guidedBy`
|
|
131
132
|
|
|
132
|
-
**Risk and security:** the Open Group overlay (*Modeling Enterprise Risk Management and Security with the ArchiMate Language*) maps threats to **Driver** (`MotivationDriver`), threat and loss events to **Business/Application/Technology Event**, risks and vulnerabilities to **Assessment** (`MotivationAssessment`), control objectives to **Goal**, control measures to **Requirement** and policies to **Principle** (`MotivationPrinciple`), each with a type annotation. See [Modeling Guide](modeling.md#security-and-risk). How relations are directed (top-down, DAG) is described under [Direction of Relations](modeling.md#direction-of-relations).
|
|
133
|
+
**Risk and security:** the Open Group overlay (*Modeling Enterprise Risk Management and Security with the ArchiMate Language*) maps threats to **Driver** (`MotivationDriver`), threat and loss events to **Business/Application/Technology Event**, risks and vulnerabilities to **Assessment** (`MotivationAssessment`), control objectives to **Goal**, control measures to **Requirement** and policies to **Principle** (`MotivationPrinciple`), each with a type annotation. See [Modeling Guide](modeling.md#security-and-risk). How relations are directed (top-down, DAG) is described under [Direction of Relations](modeling.md#direction-of-relations). Which of capability, product, service and component an element is follows the questions in [Choosing the Kind](modeling.md#choosing-the-kind).
|
|
133
134
|
|
|
134
135
|
### Critical Relationships
|
|
135
136
|
|
|
@@ -36,7 +36,8 @@ module Archsight
|
|
|
36
36
|
@analysis.name
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# @return [Boolean] true if
|
|
39
|
+
# @return [Boolean] true if the script produced any output (tables, lists, text, ...; not counting messages).
|
|
40
|
+
# Output is not a problem (many analyses are reports); see #warnings?
|
|
40
41
|
def has_findings?
|
|
41
42
|
@sections.any? { |s| %i[table list text heading code].include?(s[:type]) }
|
|
42
43
|
end
|
|
@@ -51,6 +52,11 @@ module Archsight
|
|
|
51
52
|
@sections.count { |s| s[:type] == :message && s[:level] == :warning }
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# @return [Boolean] true if the script reported an error or a warning itself (`error(...)`, `warning(...)`)
|
|
56
|
+
def warnings?
|
|
57
|
+
(error_count + warning_count).positive?
|
|
58
|
+
end
|
|
59
|
+
|
|
54
60
|
# Convert result to markdown (only script-generated content)
|
|
55
61
|
# @param verbose [Boolean] Include detailed output
|
|
56
62
|
# @return [String] Markdown formatted output
|
|
@@ -82,7 +88,7 @@ module Archsight
|
|
|
82
88
|
def status_emoji
|
|
83
89
|
return "❌" unless success?
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
warnings? ? "⚠️" : "✅"
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
# Formatted duration string
|
|
@@ -221,6 +221,14 @@ module Archsight
|
|
|
221
221
|
@sections << { type: :code, content: content, lang: lang }
|
|
222
222
|
end
|
|
223
223
|
|
|
224
|
+
# Attach structured data for document templates (archsight analyze --format typst --template).
|
|
225
|
+
# Not shown in the web UI or the terminal; a template renders it instead of the other sections.
|
|
226
|
+
# @param key [String, Symbol] Name the template looks the data up by
|
|
227
|
+
# @param value [Hash, Array, String, Numeric, Boolean, nil] JSON-like value
|
|
228
|
+
def data(key, value)
|
|
229
|
+
@sections << { type: :data, key: key.to_s, value: value }
|
|
230
|
+
end
|
|
231
|
+
|
|
224
232
|
# Legacy output methods - still supported for backward compatibility
|
|
225
233
|
|
|
226
234
|
# Report findings (legacy method - creates appropriate section based on data type)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../markdown"
|
|
4
|
+
require_relative "../diagram"
|
|
5
|
+
|
|
6
|
+
module Archsight
|
|
7
|
+
module Analysis
|
|
8
|
+
# Renders analysis results as one Typst document (https://typst.app).
|
|
9
|
+
#
|
|
10
|
+
# All text is emitted as Typst string literals (`#"..."`) and structure as
|
|
11
|
+
# function calls (`#strong[...]`), so no content can be mistaken for markup.
|
|
12
|
+
class TypstRenderer
|
|
13
|
+
ROW_LIMIT = 10
|
|
14
|
+
HEADING_OFFSET = 2 # section level 0 becomes a level-2 heading, the analysis name is level 1
|
|
15
|
+
LANGUAGE = /\A[A-Za-z0-9+#-]+\z/
|
|
16
|
+
MESSAGE_FILL = { error: "rgb(\"#fde8e8\")", warning: "rgb(\"#fff3cc\")", info: "rgb(\"#ddeeff\")" }.freeze
|
|
17
|
+
SYMBOLS = {
|
|
18
|
+
ndash: "–", mdash: "—", hellip: "…", laquo: "«", raquo: "»", laquo_space: "« ", raquo_space: " »",
|
|
19
|
+
lsquo: "‘", rsquo: "’", ldquo: "“", rdquo: "”"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
PREAMBLE = <<~TYPST
|
|
23
|
+
#set document(title: "Archsight analysis")
|
|
24
|
+
#set page(paper: "a4", margin: 2cm, numbering: "1")
|
|
25
|
+
#set text(size: 10pt)
|
|
26
|
+
#show raw.where(block: true): it => block(fill: luma(245), inset: 8pt, radius: 3pt, width: 100%, it)
|
|
27
|
+
TYPST
|
|
28
|
+
|
|
29
|
+
# @param results [Array<Archsight::Analysis::Result>]
|
|
30
|
+
# @param verbose [Boolean] show every table row and list item and the error backtrace
|
|
31
|
+
# @param template [String, nil] Typst file to import instead of the built-in layout. It must define
|
|
32
|
+
# `archsight-report` (a show rule for the document) and `analysis-result` (one call per analysis,
|
|
33
|
+
# with the data sections as a dictionary and the other sections as body).
|
|
34
|
+
def initialize(results, verbose: false, template: nil)
|
|
35
|
+
@results = results
|
|
36
|
+
@verbose = verbose
|
|
37
|
+
@template = template
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [String] Typst source
|
|
41
|
+
def render
|
|
42
|
+
return [template_preamble, *@results.map { |result| template_result(result) }].join("\n") if @template
|
|
43
|
+
|
|
44
|
+
[PREAMBLE, title, summary, *@results.map { |result| render_result(result) }].join("\n")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def title
|
|
50
|
+
"#align(center)[#text(size: 20pt, weight: \"bold\")[#{str("Archsight analysis")}]]\n"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def summary
|
|
54
|
+
completed = @results.count(&:success?)
|
|
55
|
+
failed = @results.count(&:failed?)
|
|
56
|
+
with_warnings = @results.count { |result| result.success? && result.warnings? }
|
|
57
|
+
items = ["#{completed} completed"]
|
|
58
|
+
items << "#{failed} failed" if failed.positive?
|
|
59
|
+
items << "#{with_warnings} with warnings" if with_warnings.positive?
|
|
60
|
+
"#list(#{items.map { |item| "[#{str(item)}]" }.join(", ")})\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def template_preamble
|
|
64
|
+
counts = { count: @results.size, passed: @results.count(&:success?), failed: @results.count(&:failed?),
|
|
65
|
+
findings: @results.count(&:has_findings?) }
|
|
66
|
+
"#import #{lit(@template)}: *\n#show: archsight-report.with(#{named_args(counts)})\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# One `analysis-result` call: metadata and data as named arguments, the rendered sections as body
|
|
70
|
+
def template_result(result)
|
|
71
|
+
data = result.sections.select { |s| s[:type] == :data }.to_h { |s| [s[:key], s[:value]] }
|
|
72
|
+
body = []
|
|
73
|
+
body << error_block(result) if result.failed?
|
|
74
|
+
body.concat(result.sections.filter_map { |section| render_section(section) })
|
|
75
|
+
args = { name: result.name, description: description(result), state: state(result),
|
|
76
|
+
duration: result.duration_str, data: data }
|
|
77
|
+
"#analysis-result(#{named_args(args)})[\n#{body.join("\n\n")}\n]\n"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def description(result)
|
|
81
|
+
analysis = result.analysis
|
|
82
|
+
analysis.respond_to?(:annotations) ? analysis.annotations["analysis/description"] : nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def state(result)
|
|
86
|
+
return "failed" if result.failed?
|
|
87
|
+
|
|
88
|
+
result.has_findings? ? "findings" : "passed"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def named_args(hash)
|
|
92
|
+
hash.map { |key, value| "#{key}: #{typst_value(value)}" }.join(", ")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A Ruby value as a Typst literal: Hash -> dictionary (string keys), Array -> array, nil -> none
|
|
96
|
+
def typst_value(value)
|
|
97
|
+
case value
|
|
98
|
+
when Hash then value.empty? ? "(:)" : "(#{value.map { |k, v| "#{lit(k.to_s)}: #{typst_value(v)}" }.join(", ")})"
|
|
99
|
+
when Array then "(#{value.map { |v| typst_value(v) }.join(", ")}#{"," if value.size == 1})"
|
|
100
|
+
when Integer, true, false then value.to_s
|
|
101
|
+
when Float then value.finite? ? value.to_s : "none"
|
|
102
|
+
when nil then "none"
|
|
103
|
+
else lit(value.to_s)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def render_result(result)
|
|
108
|
+
parts = ["#pagebreak(weak: true)", "#heading(level: 1)[#{str(result.name)}]"]
|
|
109
|
+
status = result.failed? ? "failed" : "completed"
|
|
110
|
+
status += ", #{result.duration_str}" unless result.duration_str.empty?
|
|
111
|
+
parts << "#text(fill: gray)[#{str(status)}]"
|
|
112
|
+
parts << error_block(result) if result.failed?
|
|
113
|
+
parts.concat(result.sections.filter_map { |section| render_section(section) })
|
|
114
|
+
"#{parts.join("\n\n")}\n"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def error_block(result)
|
|
118
|
+
lines = [message_block(:error, "**Error:** #{result.error}")]
|
|
119
|
+
lines << "#raw(#{lit(result.error_backtrace.join("\n"))}, block: true)" if @verbose && result.error_backtrace&.any?
|
|
120
|
+
lines.join("\n\n")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def render_section(section)
|
|
124
|
+
case section[:type]
|
|
125
|
+
when :heading then "#heading(level: #{[section[:level] + HEADING_OFFSET, 6].min})[#{str(section[:text])}]"
|
|
126
|
+
when :text then markdown(section[:content])
|
|
127
|
+
when :table then table(section[:headers], section[:rows])
|
|
128
|
+
when :list then list(section[:items])
|
|
129
|
+
when :code then code(section)
|
|
130
|
+
when :message then message_block(section[:level], section[:message])
|
|
131
|
+
when :data then nil # only for templates, passed to analysis-result
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def message_block(level, text)
|
|
136
|
+
fill = MESSAGE_FILL.fetch(level, MESSAGE_FILL[:info])
|
|
137
|
+
"#block(fill: #{fill}, inset: 8pt, radius: 3pt, width: 100%)[#{markdown(text, inline: true)}]"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# A template produces a document to hand out, so it gets every row
|
|
141
|
+
def full?
|
|
142
|
+
@verbose || !@template.nil?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def table(headers, rows)
|
|
146
|
+
shown = full? ? rows : rows.first(ROW_LIMIT)
|
|
147
|
+
head = headers.map { |header| "[#strong[#{str(header)}]]" }
|
|
148
|
+
cells = shown.flat_map { |row| headers.each_index.map { |i| "[#{str(row[i])}]" } }
|
|
149
|
+
out = "#table(columns: #{headers.size}, inset: 5pt, stroke: 0.5pt + luma(200), " \
|
|
150
|
+
"table.header(#{head.join(", ")}), #{cells.join(", ")})"
|
|
151
|
+
out += "\n\n#emph[#{str("...and #{rows.size - ROW_LIMIT} more rows")}]" if shown.size < rows.size
|
|
152
|
+
out
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def list(items)
|
|
156
|
+
shown = full? ? items : items.first(ROW_LIMIT)
|
|
157
|
+
out = "#list(#{shown.map { |item| "[#{markdown(item, inline: true)}]" }.join(", ")})"
|
|
158
|
+
out += "\n\n#emph[#{str("...and #{items.size - ROW_LIMIT} more items")}]" if shown.size < items.size
|
|
159
|
+
out
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def code(section)
|
|
163
|
+
return diagram(section[:content]) if section[:lang].to_s == "asd"
|
|
164
|
+
|
|
165
|
+
raw(section[:content], section[:lang])
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def diagram(source)
|
|
169
|
+
svg = Archsight::Diagram.render(source)
|
|
170
|
+
"#image(bytes(#{lit(svg)}), format: \"svg\", width: 100%)"
|
|
171
|
+
rescue Archsight::Diagram::Error => e
|
|
172
|
+
[message_block(:error, "Diagram could not be rendered: #{e.message}"), raw(source, "asd")].join("\n\n")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def raw(source, lang)
|
|
176
|
+
lang_arg = lang.to_s.match?(LANGUAGE) ? ", lang: #{lit(lang)}" : ""
|
|
177
|
+
"#raw(#{lit(source.to_s.chomp)}, block: true#{lang_arg})"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# A Typst string literal, for use as a function argument
|
|
181
|
+
def lit(text)
|
|
182
|
+
escaped = text.to_s.delete("\r").gsub(/[\\"]/) { "\\#{Regexp.last_match(0)}" }.gsub("\n", "\\n")
|
|
183
|
+
"\"#{escaped}\""
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# A Typst string literal as markup, for use inside content
|
|
187
|
+
def str(text)
|
|
188
|
+
"##{lit(text)}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# ---- markdown -> Typst
|
|
192
|
+
|
|
193
|
+
def markdown(source, inline: false)
|
|
194
|
+
root = Archsight::Markdown.document(source).root
|
|
195
|
+
out = blocks(root.children)
|
|
196
|
+
inline ? out.gsub(/\n\n+/, " #linebreak() ") : out
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def blocks(elements)
|
|
200
|
+
elements.filter_map { |node| block(node) }.join("\n\n")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def block(node)
|
|
204
|
+
case node.type
|
|
205
|
+
when :p then inlines(node.children)
|
|
206
|
+
when :header then "#heading(level: #{[node.options[:level] + 1, 6].min})[#{inlines(node.children)}]"
|
|
207
|
+
when :codeblock then raw(node.value, node.options[:lang])
|
|
208
|
+
when :blockquote then "#quote(block: true)[#{blocks(node.children)}]"
|
|
209
|
+
when :ul then "#list(#{node.children.map { |li| "[#{blocks(li.children)}]" }.join(", ")})"
|
|
210
|
+
when :ol then "#enum(#{node.children.map { |li| "[#{blocks(li.children)}]" }.join(", ")})"
|
|
211
|
+
when :hr then "#line(length: 100%)"
|
|
212
|
+
when :table then markdown_table(node)
|
|
213
|
+
when :blank, :html_element, :xml_comment then nil
|
|
214
|
+
else inlines(node.children) unless node.children.empty?
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def markdown_table(node)
|
|
219
|
+
rows = node.children.flat_map(&:children)
|
|
220
|
+
cells = rows.map { |tr| tr.children.map { |td| "[#{inlines(td.children)}]" } }
|
|
221
|
+
width = cells.map(&:size).max || 1
|
|
222
|
+
"#table(columns: #{width}, inset: 5pt, stroke: 0.5pt + luma(200), #{cells.flatten.join(", ")})"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def inlines(elements)
|
|
226
|
+
elements.map { |node| inline(node) }.join
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def inline(node)
|
|
230
|
+
case node.type
|
|
231
|
+
when :text then str(node.value)
|
|
232
|
+
when :em then "#emph[#{inlines(node.children)}]"
|
|
233
|
+
when :strong then "#strong[#{inlines(node.children)}]"
|
|
234
|
+
when :codespan then "#raw(#{lit(node.value)})"
|
|
235
|
+
when :a then "#link(#{lit(node.attr["href"])})[#{inlines(node.children)}]"
|
|
236
|
+
when :br then "#linebreak()"
|
|
237
|
+
when :entity then str(node.value.char)
|
|
238
|
+
when :smart_quote, :typographic_sym then str(SYMBOLS.fetch(node.value, node.value.to_s))
|
|
239
|
+
when :html_element then inlines(node.children)
|
|
240
|
+
else inlines(node.children)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
data/lib/archsight/analysis.rb
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "email_recipient"
|
|
4
4
|
require_relative "person"
|
|
5
|
+
require_relative "resource_reference"
|
|
5
6
|
|
|
6
7
|
# Annotation represents a single annotation definition with its schema and behavior
|
|
7
8
|
class Archsight::Annotations::Annotation
|
|
8
|
-
attr_reader :key, :description, :filter, :format, :enum, :sidebar, :type, :summary, :editor
|
|
9
|
+
attr_reader :key, :description, :filter, :format, :enum, :sidebar, :type, :summary, :editor, :deprecated, :default
|
|
9
10
|
|
|
10
11
|
def initialize(key, options = {})
|
|
11
12
|
@key = key
|
|
@@ -18,6 +19,9 @@ class Archsight::Annotations::Annotation
|
|
|
18
19
|
@summary = options.fetch(:summary, false)
|
|
19
20
|
@editor = options.fetch(:editor, true)
|
|
20
21
|
@type = options[:type]
|
|
22
|
+
@unique = options[:unique] == true
|
|
23
|
+
@deprecated = options[:deprecated]
|
|
24
|
+
@default = options[:default]
|
|
21
25
|
|
|
22
26
|
# Auto-add filter if enum present
|
|
23
27
|
@filter ||= :word if @enum
|
|
@@ -51,6 +55,22 @@ class Archsight::Annotations::Annotation
|
|
|
51
55
|
@filter == :list
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
# A unique annotation holds identifiers: no two resources of a kind share a value (checked by `archsight lint`)
|
|
59
|
+
def unique?
|
|
60
|
+
@unique
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A deprecated annotation still loads and works, but `archsight lint` reports every use with what to use instead
|
|
64
|
+
# (the `deprecated:` text)
|
|
65
|
+
def deprecated?
|
|
66
|
+
!@deprecated.nil?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] true if the value names another resource (`Name` or `Kind/Name`)
|
|
70
|
+
def resource?
|
|
71
|
+
@format == :resource
|
|
72
|
+
end
|
|
73
|
+
|
|
54
74
|
# Summary annotations are returned with every search hit (at most MAX_SUMMARY per kind)
|
|
55
75
|
def summary?
|
|
56
76
|
@summary == true
|
|
@@ -122,9 +142,11 @@ class Archsight::Annotations::Annotation
|
|
|
122
142
|
@format if code? || diagram?
|
|
123
143
|
end
|
|
124
144
|
|
|
125
|
-
# Example value for templates
|
|
145
|
+
# Example value for templates: the default, else the first value of the enum
|
|
126
146
|
def example_value
|
|
127
|
-
if @
|
|
147
|
+
if @default
|
|
148
|
+
@default
|
|
149
|
+
elsif @enum
|
|
128
150
|
@enum.first || "TODO"
|
|
129
151
|
elsif @type == Float
|
|
130
152
|
0.0
|
|
@@ -144,6 +166,7 @@ class Archsight::Annotations::Annotation
|
|
|
144
166
|
when "Float" then "Expected a float value"
|
|
145
167
|
when "Archsight::Annotations::EmailRecipient" then 'Expected email format: "Name <email@domain.com>" or "email@domain.com"'
|
|
146
168
|
when "Archsight::Annotations::Person" then 'Expected "Name", "Name <email@domain.com>" or "email@domain.com"'
|
|
169
|
+
when "Archsight::Annotations::ResourceReference" then "Expected a resource name of the same kind (Archsight:Web) or Kind/Name (ApplicationService/Archsight:Web)"
|
|
147
170
|
else "Invalid value for type #{@type}"
|
|
148
171
|
end
|
|
149
172
|
end
|
|
@@ -184,6 +207,7 @@ class Archsight::Annotations::Annotation
|
|
|
184
207
|
when "URI" then valid_uri?(string_value)
|
|
185
208
|
when "Archsight::Annotations::EmailRecipient" then Archsight::Annotations::EmailRecipient.valid?(string_value)
|
|
186
209
|
when "Archsight::Annotations::Person" then Archsight::Annotations::Person.valid?(string_value)
|
|
210
|
+
when "Archsight::Annotations::ResourceReference" then Archsight::Annotations::ResourceReference.valid?(string_value)
|
|
187
211
|
else true
|
|
188
212
|
end
|
|
189
213
|
end
|
|
@@ -203,6 +227,9 @@ class Archsight::Annotations::Annotation
|
|
|
203
227
|
end
|
|
204
228
|
|
|
205
229
|
def derive_format
|
|
230
|
+
return :identifier if unique?
|
|
231
|
+
return :resource if @type == Archsight::Annotations::ResourceReference
|
|
232
|
+
|
|
206
233
|
case @filter
|
|
207
234
|
when :word then :tag_word
|
|
208
235
|
when :list then :tag_list
|
|
@@ -4,7 +4,11 @@ require "uri"
|
|
|
4
4
|
|
|
5
5
|
# Architecture module adds common architecture annotations to resource classes
|
|
6
6
|
module Archsight::Annotations::Architecture
|
|
7
|
+
# in the order of a resource's life; a resource that sets none is `active` (the annotation's default)
|
|
8
|
+
LIFECYCLE_STATUSES = %w[planned active deprecated sunset end-of-life].freeze
|
|
9
|
+
|
|
7
10
|
def self.included(base)
|
|
11
|
+
base.include Archsight::Annotations::Links
|
|
8
12
|
base.class_eval do
|
|
9
13
|
annotation "architecture/abbr",
|
|
10
14
|
description: "Abbreviation or short name",
|
|
@@ -21,10 +25,53 @@ module Archsight::Annotations::Architecture
|
|
|
21
25
|
description: "Documentation URL or reference",
|
|
22
26
|
title: "Documentation",
|
|
23
27
|
type: URI
|
|
28
|
+
annotation "architecture/aliases",
|
|
29
|
+
description: "Other identifiers of the resource (comma-separated), e.g. ITGS:A001; " \
|
|
30
|
+
"unique among the resources of a kind",
|
|
31
|
+
title: "Aliases",
|
|
32
|
+
filter: :list,
|
|
33
|
+
sidebar: false,
|
|
34
|
+
unique: true
|
|
24
35
|
annotation "architecture/tags",
|
|
25
36
|
description: "Comma-separated tags",
|
|
26
37
|
filter: :list,
|
|
27
38
|
title: "Tags"
|
|
28
39
|
end
|
|
40
|
+
define_lifecycle(base)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.define_lifecycle(base)
|
|
44
|
+
base.class_eval do
|
|
45
|
+
annotation "lifecycle/status",
|
|
46
|
+
description: "Lifecycle state: planned (not yet in use), active (the default), deprecated (do not " \
|
|
47
|
+
"start new uses), sunset (being phased out) or end-of-life (no longer operated or supported)",
|
|
48
|
+
enum: LIFECYCLE_STATUSES,
|
|
49
|
+
default: "active",
|
|
50
|
+
title: "Lifecycle"
|
|
51
|
+
annotation "lifecycle/activeAt",
|
|
52
|
+
description: "When the resource goes (or went) into use (ISO 8601 date or time)",
|
|
53
|
+
title: "Active at",
|
|
54
|
+
validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
|
|
55
|
+
annotation "lifecycle/deprecatedAt",
|
|
56
|
+
description: "When the resource was deprecated (ISO 8601 date or time)",
|
|
57
|
+
title: "Deprecated at",
|
|
58
|
+
validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
|
|
59
|
+
annotation "lifecycle/sunsetAt",
|
|
60
|
+
description: "When the resource goes (or went) into sunset (ISO 8601 date or time)",
|
|
61
|
+
title: "Sunset at",
|
|
62
|
+
validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
|
|
63
|
+
annotation "lifecycle/endOfLifeAt",
|
|
64
|
+
description: "When the resource reaches (or reached) end-of-life (ISO 8601 date or time)",
|
|
65
|
+
title: "End of life at",
|
|
66
|
+
validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
|
|
67
|
+
annotation "lifecycle/replacedBy",
|
|
68
|
+
description: "The resource that replaces this one: a name for the same kind, Kind/Name for another (a component can be replaced by an ApplicationService/...)",
|
|
69
|
+
title: "Replaced by",
|
|
70
|
+
type: Archsight::Annotations::ResourceReference
|
|
71
|
+
annotation "lifecycle/reason",
|
|
72
|
+
description: "Why the resource is deprecated",
|
|
73
|
+
title: "Reason",
|
|
74
|
+
format: :markdown
|
|
75
|
+
end
|
|
29
76
|
end
|
|
30
77
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Links module adds the links of a resource to other systems: `link/<name>` holds a URL, the name says which
|
|
4
|
+
# (confluence, jira, github, ...). A link has no direction; it can be where the resource comes from or where it
|
|
5
|
+
# was published to.
|
|
6
|
+
module Archsight::Annotations::Links
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.class_eval do
|
|
9
|
+
annotation "link/*",
|
|
10
|
+
description: "Links to the resource in other systems and to related documents (name and URL)",
|
|
11
|
+
sidebar: false,
|
|
12
|
+
validator: ->(value) { "'#{value}' is not an http(s) URL" unless value.match?(%r{\Ahttps?://\S+\z}) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|