archsight 0.4.0 → 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/docs/configuration.md +1 -1
- data/docs/modeling.md +8 -0
- data/docs/pages.md +2 -2
- data/lib/archsight/analysis/typst_renderer.rb +2 -3
- data/lib/archsight/export/confluence/analysis_output.rb +3 -4
- data/lib/archsight/export/confluence/exporter.rb +3 -1
- data/lib/archsight/export/confluence/page_properties.rb +2 -3
- data/lib/archsight/export/confluence/storage.rb +25 -12
- 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/import/executor.rb +1 -0
- data/lib/archsight/import.rb +27 -2
- data/lib/archsight/linter.rb +2 -2
- data/lib/archsight/markdown.rb +48 -0
- data/lib/archsight/openapi.rb +169 -0
- data/lib/archsight/page_tree.rb +2 -3
- data/lib/archsight/resources/application_interface.rb +12 -0
- data/lib/archsight/resources/technology_communication_network.rb +2 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +2 -1
- data/lib/archsight/web/api/openapi/spec.yaml +136 -27
- data/lib/archsight/web/api/routes.rb +2 -2
- data/lib/archsight/web/application.rb +2 -2
- 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-D52OwaKc.js → DocPage-C2ubTXH7.js} +1 -1
- 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/KindList-C0oaM3CF.js +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-DooWFwQP.js +2 -0
- data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
- data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -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-eUHmJ7K2.css → index-BLSE5DSv.css} +1 -1
- 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-4evusK9i.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 +4 -4
- metadata +46 -39
- data/lib/archsight/web/public/vue/ApiDocsPage-COp4onwn.js +0 -1
- data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
- data/lib/archsight/web/public/vue/EditorPage-3aqL7yVf.js +0 -35
- data/lib/archsight/web/public/vue/ErrorPage--b9XeSYa.js +0 -2
- data/lib/archsight/web/public/vue/GraphView-qh_EtRrQ.js +0 -1
- data/lib/archsight/web/public/vue/HomePage-C2TNh2U-.js +0 -2
- data/lib/archsight/web/public/vue/InstanceRouter-uzb_OMG0.js +0 -1
- data/lib/archsight/web/public/vue/KindList-CyJuP_uq.js +0 -1
- data/lib/archsight/web/public/vue/PageView-FyygrS-b.js +0 -1
- data/lib/archsight/web/public/vue/QueryError-B4Ex_csZ.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-BY6Z1Kcb.js +0 -2
- data/lib/archsight/web/public/vue/SearchResults-CkFpHF8m.js +0 -1
- data/lib/archsight/web/public/vue/WikiPage-BoHNewCX.js +0 -3
- data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BvzKZE4e.js +0 -1
- data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-D-xs_Hxh.js +0 -1
- data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-nbjxG01S.js +0 -1
- data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-CW6mw2ab.js +0 -1
- data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
- data/lib/archsight/web/public/vue/index-BGlYCbi7.js +0 -3
- data/lib/archsight/web/public/vue/info-A6RAGUB7-C12sjlHe.js +0 -1
- data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
- data/lib/archsight/web/public/vue/mermaid-DjsXQ_XN.js +0 -3334
- data/lib/archsight/web/public/vue/packet-AYTQ26CC-wMwKwHmA.js +0 -1
- data/lib/archsight/web/public/vue/pie-WAS4IAKB-w1-sAU_K.js +0 -1
- data/lib/archsight/web/public/vue/radar-RG4KPBEZ-DpwqIFoy.js +0 -1
- data/lib/archsight/web/public/vue/railroad-74A4TZTK-Ce5XC24d.js +0 -1
- data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-BSQO2pds.js +0 -1
- data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-Dq1c2YtD.js +0 -1
- data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DKj6dli0.js +0 -1
- data/lib/archsight/web/public/vue/rolldown-runtime-hePW80VL.js +0 -1
- data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-BmHNTWxU.js +0 -1
- data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DsEI7sSD.js +0 -1
- data/lib/archsight/web/public/vue/useHighlight-DPguaxfm.js +0 -10
- data/lib/archsight/web/public/vue/usePanZoom-seQE1TPk.js +0 -12
- data/lib/archsight/web/public/vue/wardley-WFR3VGLG-CSCW83-M.js +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0312d7e0c0790b8b2a117ad6277e49bfb18c61545134b27510fa3209e951697d
|
|
4
|
+
data.tar.gz: 94915564bbbab8c59e139422c1a99317ec7fd5a87370e9905b16ed330cd56951
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b15fc5fb4130fe0783efe8e20cf7aa2ce3962e6566d54ae5c1c85834692e5363cc3a9667eb2f7cfbf17832badc0461cbf684c1d5a5c04711e5e529d1fb11bb2
|
|
7
|
+
data.tar.gz: 36e154fb3fefd3dd0de59ced6d6f404e4238242ec182d78f5ced051fec01002a3af8dbe11c8eced3ff16fc978791301627fe3f06641e0ea859e9a529326ea814
|
data/docs/configuration.md
CHANGED
|
@@ -37,7 +37,7 @@ that is set but empty counts as not set.
|
|
|
37
37
|
| `jira.server` | `ARCHSIGHT_JIRA_SERVER` | Name of the Jira server as Confluence knows it (the `server` parameter of its Jira macro). Needed by the Confluence export when Confluence has more than one Jira server: without it the exported `{jira:KEY}` macros have no server and Confluence shows an error |
|
|
38
38
|
| `jira.server_id` | `ARCHSIGHT_JIRA_SERVER_ID` | Id of that server (the `serverId` parameter). Copy both from the storage format of a Jira macro that already works in Confluence (page "Edit in storage format") |
|
|
39
39
|
| `jira.token` | `ARCHSIGHT_JIRA_TOKEN` | Reserved, nothing uses it yet |
|
|
40
|
-
| `import.cache_root` | `ARCHSIGHT_IMPORT_CACHE_ROOT` | Where the GitHub, GitLab and repository imports clone repositories, and what importers of the resources repository get as `cache_root` / `ARCHSIGHT_CACHE_DIR`. Default `~/.cache/archsight/git`; an import's own `import/config/cacheRoot` wins, see [Importers of the Resources Repository](/doc/import#importers-of-the-resources-repository) |
|
|
40
|
+
| `import.cache_root` | `ARCHSIGHT_IMPORT_CACHE_ROOT` | Where the GitHub, GitLab and repository imports clone repositories, and what importers of the resources repository get as `cache_root` / `ARCHSIGHT_CACHE_DIR`. Default `~/.cache/archsight/git`; an import's own `import/config/cacheRoot` wins, see [Importers of the Resources Repository](/doc/import#importers-of-the-resources-repository). Every import makes sure the clones are not indexed by Spotlight (macOS): an empty `.metadata_never_index` in `~/.cache/archsight`, or in the cache root when it is elsewhere |
|
|
41
41
|
|
|
42
42
|
## Command line
|
|
43
43
|
|
data/docs/modeling.md
CHANGED
|
@@ -190,6 +190,9 @@ TechnologySystemSoftware, TechnologyService, TechnologyArtifact). The same princ
|
|
|
190
190
|
with `composedOf` (metro network → site networks, campus → halls, backbone → links).
|
|
191
191
|
7. A network that supports a metro region is `locatedAt` the Location and `composedOf` the networks of its sites.
|
|
192
192
|
8. Nodes attach to networks with `servedBy` and stand in facilities with `locatedAt`.
|
|
193
|
+
An `ApplicationInterface` is `servedBy` the networks on which it can be reached. Write them only when they are
|
|
194
|
+
known: an interface without a network makes no claim about its reachability, and an analysis cannot tell "not
|
|
195
|
+
reachable" from "not recorded".
|
|
193
196
|
9. An overlay that software builds (an SDN agent wiring tenant networks on each host, an operator creating VXLAN
|
|
194
197
|
segments, a load-balancer controller announcing public addresses) is `realizedBy` that `ApplicationComponent` or
|
|
195
198
|
`ApplicationService` (ArchiMate cross-layer realization). This holds even when the software runs on the nodes the
|
|
@@ -546,6 +549,7 @@ Which kind to pick is in [Choosing the Kind: Infrastructure](#choosing-the-kind-
|
|
|
546
549
|
| `TechnologyCommunicationNetwork` | `realizedBy` | `TechnologyDevice` (the switches and routers that build it) |
|
|
547
550
|
| `TechnologyCommunicationNetwork` | `connects` | `TechnologyDevice` (the devices attached to it) |
|
|
548
551
|
| `TechnologyNode` | `servedBy` | `TechnologyCommunicationNetwork` |
|
|
552
|
+
| `ApplicationInterface` | `servedBy` | `TechnologyCommunicationNetwork` (the networks on which it can be reached) |
|
|
549
553
|
|
|
550
554
|
- **Underlay and overlay:** the underlay *serves* the overlay, so the overlay is `servedBy` the underlay. A BGP-EVPN
|
|
551
555
|
network that carries customer workloads is one `overlay` network served by the underlay and realized by the routing
|
|
@@ -560,6 +564,10 @@ Which kind to pick is in [Choosing the Kind: Infrastructure](#choosing-the-kind-
|
|
|
560
564
|
- **Attachment:** a node is `servedBy` the networks it is attached to; the network page lists them. A physical
|
|
561
565
|
device is attached the other way round: the network `connects` it, so the network can also be `realizedBy` the
|
|
562
566
|
same switch without a cycle.
|
|
567
|
+
- **Reachability of an interface:** an `ApplicationInterface` is `servedBy` the networks on which it can be reached,
|
|
568
|
+
next to the `TechnologyInterface` that carries it. With `api/authenticationMethod` this is the exposure of the
|
|
569
|
+
interface (see the example analysis `Analysis:Interface:ExposedWithoutAuthentication`). Leave it out when the
|
|
570
|
+
networks are not known.
|
|
563
571
|
|
|
564
572
|
```
|
|
565
573
|
# all overlay networks
|
data/docs/pages.md
CHANGED
|
@@ -339,9 +339,9 @@ confluence:
|
|
|
339
339
|
With `drawio` off nothing draw.io-specific is written, so any Confluence shows the diagrams. Rendering a `.drawio` needs the
|
|
340
340
|
draw.io desktop CLI (`drawio`, or `ARCHSIGHT_DRAWIO_CLI`) on the machine that exports; the preview of an SVG needs
|
|
341
341
|
`rsvg-convert` or that CLI. A diagram that cannot be rendered fails the page instead of leaving a blank diagram. Attachments an
|
|
342
|
-
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
|
|
343
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
|
|
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.
|
|
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.
|
|
345
345
|
|
|
346
346
|
**Views and requirements** are live in Archsight, so Confluence gets a regular table with the data of the moment of the export (every
|
|
347
347
|
export writes the current data; a page whose tables did not change is `unchanged`): `![[View/Name]]` on a line of its own, a
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "kramdown-parser-gfm"
|
|
3
|
+
require_relative "../markdown"
|
|
5
4
|
require_relative "../diagram"
|
|
6
5
|
|
|
7
6
|
module Archsight
|
|
@@ -192,7 +191,7 @@ module Archsight
|
|
|
192
191
|
# ---- markdown -> Typst
|
|
193
192
|
|
|
194
193
|
def markdown(source, inline: false)
|
|
195
|
-
root =
|
|
194
|
+
root = Archsight::Markdown.document(source).root
|
|
196
195
|
out = blocks(root.children)
|
|
197
196
|
inline ? out.gsub(/\n\n+/, " #linebreak() ") : out
|
|
198
197
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
-
|
|
5
|
-
require "kramdown-parser-gfm"
|
|
4
|
+
require_relative "../../markdown"
|
|
6
5
|
|
|
7
6
|
module Archsight
|
|
8
7
|
module Export
|
|
@@ -90,8 +89,8 @@ module Archsight
|
|
|
90
89
|
return "" if text.strip.empty?
|
|
91
90
|
|
|
92
91
|
safe = text.gsub(%r{<(?!(?:https?://|mailto:))}, "<")
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
Archsight::Markdown.unwrapped(safe, entity_output: :as_char, auto_ids: false).to_html.strip
|
|
93
|
+
.gsub(%r{<(br|hr|img)([^>]*?)\s*/?>}, '<\1\2 />')
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
def h(text) = ERB::Util.html_escape(text)
|
|
@@ -7,6 +7,7 @@ require "time"
|
|
|
7
7
|
require_relative "../../version"
|
|
8
8
|
require_relative "../../assets"
|
|
9
9
|
require_relative "../../helpers"
|
|
10
|
+
require_relative "../../git_source"
|
|
10
11
|
require_relative "client"
|
|
11
12
|
require_relative "credentials"
|
|
12
13
|
require_relative "page_url"
|
|
@@ -117,7 +118,8 @@ module Archsight
|
|
|
117
118
|
storage = Storage.new(page_name: page.name, source: relative_source(page),
|
|
118
119
|
base_dir: Assets.base_dir_for(page, resources_dir: @resources_dir).to_s,
|
|
119
120
|
resources_dir: @resources_dir, wiki: Helpers::WikiLinks.new(@database), page_id: id, base: url.base,
|
|
120
|
-
drawio: drawio?, diagram_links: DiagramLinks.new(@database), database: @database
|
|
121
|
+
drawio: drawio?, diagram_links: DiagramLinks.new(@database), database: @database,
|
|
122
|
+
git: GitSource.locate(page.path_ref.path.to_s))
|
|
121
123
|
storage.convert(body_of(page), toc: page.annotations["page/toc"] == "yes", header: PageProperties.xml(page))
|
|
122
124
|
end
|
|
123
125
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
-
|
|
5
|
-
require "kramdown-parser-gfm"
|
|
4
|
+
require_relative "../../markdown"
|
|
6
5
|
require_relative "../../helpers/macros"
|
|
7
6
|
|
|
8
7
|
module Archsight
|
|
@@ -69,7 +68,7 @@ module Archsight
|
|
|
69
68
|
kept << live(match)
|
|
70
69
|
"ARCHSIGHTKEPT#{kept.length - 1}X"
|
|
71
70
|
end.gsub("<", "<") # raw HTML is not allowed, it could break the storage XML
|
|
72
|
-
html =
|
|
71
|
+
html = Archsight::Markdown.unwrapped(marked, auto_ids: false, entity_output: :as_char, smart_quotes: %w[apos apos quot quot]).to_html.strip
|
|
73
72
|
html.sub(%r{\A<p>(.*)</p>\z}m, '\1').gsub(/ARCHSIGHTKEPT(\d+)X/) { kept.fetch(Regexp.last_match(1).to_i) }
|
|
74
73
|
end
|
|
75
74
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require "uri"
|
|
5
5
|
require "digest"
|
|
6
|
-
|
|
7
|
-
require "kramdown-parser-gfm"
|
|
6
|
+
require_relative "../../markdown"
|
|
8
7
|
require_relative "../../assets"
|
|
9
8
|
require_relative "../../diagram"
|
|
10
9
|
require_relative "../../helpers/macros"
|
|
@@ -80,8 +79,9 @@ module Archsight
|
|
|
80
79
|
|
|
81
80
|
MXFILE = "application/vnd.jgraph.mxfile"
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
HOMEPAGE = "https://github.com/ionos-cloud/archsight"
|
|
83
|
+
BANNER = "This page is generated by <a href=\"#{HOMEPAGE}\">Archsight</a> from %<source>s. " \
|
|
84
|
+
"Changes made here are overwritten by the next export: edit the source instead.".freeze
|
|
85
85
|
|
|
86
86
|
LANGUAGES = {
|
|
87
87
|
"sh" => "bash", "shell" => "bash", "zsh" => "bash", "yml" => "yaml", "json" => "javascript", "js" => "javascript",
|
|
@@ -95,6 +95,8 @@ module Archsight
|
|
|
95
95
|
|
|
96
96
|
# @param page_name [String] used to name generated attachments
|
|
97
97
|
# @param source [String] the markdown file, shown in the banner
|
|
98
|
+
# @param git [Archsight::GitSource::Location, nil] where the file is in its git repository: the banner then
|
|
99
|
+
# links it to the file in the web UI of the git host and names the repository
|
|
98
100
|
# @param base_dir [String] directory of the markdown file relative to the resources directory
|
|
99
101
|
# @param resources_dir [String]
|
|
100
102
|
# @param wiki [Archsight::Helpers::WikiLinks] resolves [[Targets]]
|
|
@@ -103,8 +105,10 @@ module Archsight
|
|
|
103
105
|
# @param database [Archsight::Database, nil] lets macros that name pages (pagetree) find them
|
|
104
106
|
# @param diagram_links [#call, nil] resolves a diagram's `resource` references (DiagramLinks); none leaves them inert
|
|
105
107
|
# @param drawio [Boolean] the Confluence has the draw.io app: diagrams go in as draw.io macros, else as images
|
|
106
|
-
def initialize(page_name:, source:, base_dir:, resources_dir:, wiki:, page_id: nil, base: nil, drawio: false, diagram_links: nil, database: nil
|
|
108
|
+
def initialize(page_name:, source:, base_dir:, resources_dir:, wiki:, page_id: nil, base: nil, drawio: false, diagram_links: nil, database: nil,
|
|
109
|
+
git: nil)
|
|
107
110
|
@database = database
|
|
111
|
+
@git = git
|
|
108
112
|
@page_name = page_name
|
|
109
113
|
@source = source
|
|
110
114
|
@base_dir = base_dir
|
|
@@ -125,7 +129,7 @@ module Archsight
|
|
|
125
129
|
# @param header [String] storage format of the page properties, shown top left next to the banner
|
|
126
130
|
# @return [Converted]
|
|
127
131
|
def convert(markdown, toc: false, header: "")
|
|
128
|
-
document =
|
|
132
|
+
document = Archsight::Markdown.unwrapped(protect(markdown), auto_ids: false, entity_output: :as_char, smart_quotes: %w[apos apos quot quot])
|
|
129
133
|
html, = Converter.convert(document.root, document.options.merge(storage: self))
|
|
130
134
|
Converted.new(body: layout(header, banner, (toc ? %(<ac:structured-macro ac:name="toc" />\n) : "") + html), attachments: @attachments, problems: @problems)
|
|
131
135
|
end
|
|
@@ -226,7 +230,17 @@ module Archsight
|
|
|
226
230
|
end
|
|
227
231
|
|
|
228
232
|
def banner
|
|
229
|
-
%(<ac:structured-macro ac:name="info"><ac:rich-text-body><p>#{format(BANNER, source:
|
|
233
|
+
%(<ac:structured-macro ac:name="info"><ac:rich-text-body><p>#{format(BANNER, source: banner_source)}</p></ac:rich-text-body></ac:structured-macro>\n)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# `<code>path</code>`, in a git repository the path from its root, linked to the file on the git host, and
|
|
237
|
+
# the repository
|
|
238
|
+
def banner_source
|
|
239
|
+
return "<code>#{h(@source)}</code>" unless @git
|
|
240
|
+
|
|
241
|
+
file = "<code>#{h(@git.path)}</code>"
|
|
242
|
+
file = %(<a href="#{h(@git.url)}">#{file}</a>) if @git.url
|
|
243
|
+
%(#{file} in the repository <a href="#{h(@git.repository_url)}">#{h(@git.repository)}</a>)
|
|
230
244
|
end
|
|
231
245
|
|
|
232
246
|
# {macros}, [[Target|label]] and ![[View/Name]] become placeholders before kramdown sees them (it would split the
|
|
@@ -394,13 +408,12 @@ module Archsight
|
|
|
394
408
|
%(<p><ac:image><ri:attachment ri:filename="#{h(filename)}" /></ac:image></p>\n)
|
|
395
409
|
end
|
|
396
410
|
|
|
397
|
-
# The diagram file of a draw.io macro and its `.png` preview, which must share the name
|
|
411
|
+
# The diagram file of a draw.io macro and its `.png` preview, which must share the name. The name carries a hash
|
|
412
|
+
# of the content: the macro shows revision 1 of the attachment it names, so a changed diagram uploaded under the
|
|
413
|
+
# old name (as a new version of it) would not be shown. A new name is a new attachment, the old one is removed.
|
|
398
414
|
# @return [String] the name the macro refers to
|
|
399
415
|
def attach_diagram(stem, xml, png)
|
|
400
|
-
|
|
401
|
-
name = stem
|
|
402
|
-
taken = ->(file, data) { @attachments.key?(file) && @attachments[file].data != data }
|
|
403
|
-
name = "#{stem}-#{sha[0, 8]}" if taken.call(stem, xml) || taken.call("#{stem}.png", png)
|
|
416
|
+
name = "#{stem}-#{Digest::SHA256.hexdigest(xml)[0, 8]}"
|
|
404
417
|
@attachments[name] = Attachment.new(filename: name, data: xml, content_type: MXFILE)
|
|
405
418
|
@attachments["#{name}.png"] = Attachment.new(filename: "#{name}.png", data: png, content_type: "image/png")
|
|
406
419
|
name
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
-
|
|
5
|
-
require "kramdown-parser-gfm"
|
|
4
|
+
require_relative "../../markdown"
|
|
6
5
|
require_relative "../../view_table"
|
|
7
6
|
require_relative "../../helpers/macros"
|
|
8
7
|
require_relative "diagram_links"
|
|
@@ -67,7 +66,7 @@ module Archsight
|
|
|
67
66
|
return "" if text.to_s.strip.empty?
|
|
68
67
|
|
|
69
68
|
safe = text.gsub(%r{<(?!(?:https?://|mailto:))}, "<")
|
|
70
|
-
html =
|
|
69
|
+
html = Archsight::Markdown.unwrapped(safe, entity_output: :as_char, auto_ids: false).to_html.strip
|
|
71
70
|
html.match(%r{\A<p>((?:(?!</?p>).)*)</p>\z}m) ? Regexp.last_match(1) : html
|
|
72
71
|
end
|
|
73
72
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
module Archsight
|
|
9
|
+
# Where a file lives in its git repository and its page in the web UI of the git host, for links to the source of
|
|
10
|
+
# generated content (the banner of an exported Confluence page). Only local git is asked (the repository of the
|
|
11
|
+
# file, its `origin` remote, the checked out branch), nothing goes over the network.
|
|
12
|
+
#
|
|
13
|
+
# The web URL is `https://<host>/<owner>/<repo>` of the remote, without the user, password or token a remote URL
|
|
14
|
+
# may carry. GitHub files are `/blob/<ref>/<path>`, GitLab files `/-/blob/<ref>/<path>`; on other hosts, and for a
|
|
15
|
+
# file git does not track, only the repository is linked. The ref is the checked out branch, the commit when the
|
|
16
|
+
# HEAD is detached (CI).
|
|
17
|
+
module GitSource
|
|
18
|
+
# repository: "owner/repo"; repository_url: its web page; path: the file relative to the repository root;
|
|
19
|
+
# url: the web page of the file, nil if the host is not known
|
|
20
|
+
Location = Struct.new(:repository, :repository_url, :path, :url, keyword_init: true)
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# @param file [String] path of a file
|
|
25
|
+
# @return [Location, nil] nil if the file is not in a git repository with an `origin` remote
|
|
26
|
+
def locate(file)
|
|
27
|
+
dir = File.dirname(File.expand_path(file))
|
|
28
|
+
root = git(dir, "rev-parse", "--show-toplevel") or return nil
|
|
29
|
+
remote = git(dir, "remote", "get-url", "origin") or return nil
|
|
30
|
+
host, repository = host_and_repository(remote)
|
|
31
|
+
return nil unless host && repository
|
|
32
|
+
|
|
33
|
+
path = Pathname.new(File.realpath(file)).relative_path_from(Pathname.new(File.realpath(root))).to_s
|
|
34
|
+
ref = git(dir, "symbolic-ref", "--short", "-q", "HEAD") || git(dir, "rev-parse", "HEAD")
|
|
35
|
+
repository_url = "https://#{host}/#{repository}"
|
|
36
|
+
url = tracked?(root, path) ? file_url(host, repository_url, ref, path) : nil
|
|
37
|
+
Location.new(repository: repository, repository_url: repository_url, path: path, url: url)
|
|
38
|
+
rescue SystemCallError, ArgumentError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# `git@host:owner/repo.git`, `ssh://git@host:22/owner/repo.git`, `https://user:token@host/owner/repo` ->
|
|
43
|
+
# ["host", "owner/repo"]; the port of an ssh remote is not the port of the web UI and is left out
|
|
44
|
+
def host_and_repository(remote)
|
|
45
|
+
if (scp = remote.match(%r{\A(?:[^@/\s]+@)?([^:/\s]+):(?!//)(.+)\z}))
|
|
46
|
+
host, path = scp.captures
|
|
47
|
+
else
|
|
48
|
+
uri = URI.parse(remote)
|
|
49
|
+
return [nil, nil] unless uri.host && %w[http https ssh git].include?(uri.scheme)
|
|
50
|
+
|
|
51
|
+
host = uri.scheme.start_with?("http") && uri.port != uri.default_port ? "#{uri.host}:#{uri.port}" : uri.host
|
|
52
|
+
path = uri.path
|
|
53
|
+
end
|
|
54
|
+
repository = path.delete_prefix("/").delete_suffix("/").delete_suffix(".git")
|
|
55
|
+
repository.empty? ? [nil, nil] : [host, repository]
|
|
56
|
+
rescue URI::InvalidURIError
|
|
57
|
+
[nil, nil]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def file_url(host, repository_url, ref, path)
|
|
61
|
+
blob = if host.match?(/(\A|\.)github\./) then "blob"
|
|
62
|
+
elsif host.match?(/(\A|\.)gitlab\./) then "-/blob"
|
|
63
|
+
end
|
|
64
|
+
return nil unless blob && ref
|
|
65
|
+
|
|
66
|
+
"#{repository_url}/#{blob}/#{escape(ref)}/#{escape(path)}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# An untracked or ignored file is not on the git host: no link that leads nowhere
|
|
70
|
+
def tracked?(root, path) = !git(root, "ls-files", "--", path).nil?
|
|
71
|
+
|
|
72
|
+
# each segment escaped, the slashes kept
|
|
73
|
+
def escape(path) = path.split("/").map { |segment| ERB::Util.url_encode(segment) }.join("/")
|
|
74
|
+
|
|
75
|
+
def git(dir, *)
|
|
76
|
+
out, _err, status = Open3.capture3("git", "-C", dir, *)
|
|
77
|
+
value = out.strip
|
|
78
|
+
status.success? && !value.empty? ? value : nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "cgi"
|
|
4
4
|
require "erb"
|
|
5
|
-
|
|
6
|
-
require "kramdown-parser-gfm"
|
|
5
|
+
require_relative "../markdown"
|
|
7
6
|
require_relative "../assets"
|
|
8
7
|
|
|
9
8
|
module Archsight
|
|
@@ -52,7 +51,7 @@ module Archsight
|
|
|
52
51
|
# Problems with the image references of a markdown text.
|
|
53
52
|
# @return [Array<Hash>] `{ reference:, status: :outside|:type|:missing, path: }`
|
|
54
53
|
def audit(markdown, base_dir:, resources_dir:)
|
|
55
|
-
html =
|
|
54
|
+
html = Archsight::Markdown.document(markdown).to_html
|
|
56
55
|
html.scan(IMG).filter_map do |tag|
|
|
57
56
|
reference = attribute(tag, SRC)
|
|
58
57
|
next if reference.nil? || Assets.external?(reference)
|
|
@@ -65,7 +64,7 @@ module Archsight
|
|
|
65
64
|
# The existing .asd files a markdown text embeds (for the linter)
|
|
66
65
|
# @return [Array<Array(String, String)>] normalized asset path and real file
|
|
67
66
|
def asd_files(markdown, base_dir:, resources_dir:)
|
|
68
|
-
html =
|
|
67
|
+
html = Archsight::Markdown.document(markdown).to_html
|
|
69
68
|
html.scan(IMG).filter_map do |tag|
|
|
70
69
|
reference = attribute(tag, SRC)
|
|
71
70
|
next if reference.nil? || Assets.external?(reference)
|
|
@@ -442,6 +442,7 @@ class Archsight::Import::Executor
|
|
|
442
442
|
progress: import_progress,
|
|
443
443
|
shared_writer: @shared_writer
|
|
444
444
|
)
|
|
445
|
+
Archsight::Import.never_index(handler.cache_root)
|
|
445
446
|
handler.execute
|
|
446
447
|
remove_stale_outputs(import, handler.owned_outputs)
|
|
447
448
|
@mutex.synchronize { @ran_this_run.add(import.name) }
|
data/lib/archsight/import.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
3
5
|
# Define import module namespaces
|
|
4
6
|
module Archsight
|
|
5
7
|
module Import
|
|
@@ -13,8 +15,31 @@ module Archsight
|
|
|
13
15
|
# expand it before use)
|
|
14
16
|
def self.cache_root(override = nil)
|
|
15
17
|
require "archsight/user_config"
|
|
16
|
-
override || Archsight::UserConfig.setting("import", "cache_root")&.to_s ||
|
|
17
|
-
|
|
18
|
+
override || Archsight::UserConfig.setting("import", "cache_root")&.to_s || File.join(cache_dir, "git")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The cache directory of Archsight, ~/.cache/archsight
|
|
22
|
+
def self.cache_dir = File.join(Dir.home, ".cache", "archsight")
|
|
23
|
+
|
|
24
|
+
# Tells Spotlight (macOS) not to index this
|
|
25
|
+
NEVER_INDEX = ".metadata_never_index"
|
|
26
|
+
|
|
27
|
+
# Makes sure the clones are not indexed by Spotlight: the marker file in ~/.cache/archsight, or in the cache root
|
|
28
|
+
# itself when it is somewhere else. Run before every import; a directory that cannot be written is left alone,
|
|
29
|
+
# the import does not depend on it.
|
|
30
|
+
# @param root [String] the cache root (Handler#cache_root)
|
|
31
|
+
# @return [String, nil] the marker file, nil if it could not be written
|
|
32
|
+
def self.never_index(root)
|
|
33
|
+
dir = File.expand_path(root)
|
|
34
|
+
dir = cache_dir if dir.start_with?("#{cache_dir}/")
|
|
35
|
+
marker = File.join(dir, NEVER_INDEX)
|
|
36
|
+
return marker if File.exist?(marker)
|
|
37
|
+
|
|
38
|
+
FileUtils.mkdir_p(dir)
|
|
39
|
+
FileUtils.touch(marker)
|
|
40
|
+
marker
|
|
41
|
+
rescue SystemCallError
|
|
42
|
+
nil
|
|
18
43
|
end
|
|
19
44
|
|
|
20
45
|
# The entries of `import/dependsOn`: import names and glob patterns
|
data/lib/archsight/linter.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "markdown"
|
|
4
4
|
require_relative "helpers"
|
|
5
5
|
require_relative "linter/structure"
|
|
6
6
|
|
|
@@ -319,7 +319,7 @@ module Archsight
|
|
|
319
319
|
return if instance.annotations.key?("generated/script")
|
|
320
320
|
|
|
321
321
|
begin
|
|
322
|
-
|
|
322
|
+
Archsight::Markdown.document(value)
|
|
323
323
|
rescue StandardError => e
|
|
324
324
|
@errors << "#{instance.path_ref}: Markdown syntax error in annotation '#{key}': #{e.message}"
|
|
325
325
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kramdown"
|
|
4
|
+
require "kramdown-parser-gfm"
|
|
5
|
+
|
|
6
|
+
module Archsight
|
|
7
|
+
# The one way markdown is parsed, for the web, the export, the linter and the analysis output alike. GFM, but a
|
|
8
|
+
# single newline inside a paragraph is a space, as in CommonMark and GitHub's rendering of `.md` files (the GFM
|
|
9
|
+
# parser of kramdown would make it a `<br />`, as GitHub does in comments). A line break is two trailing spaces,
|
|
10
|
+
# a trailing backslash or `<br>`.
|
|
11
|
+
module Markdown
|
|
12
|
+
OPTIONS = { input: "GFM", hard_wrap: false }.freeze
|
|
13
|
+
|
|
14
|
+
# Raw HTML whose text is kept as it is
|
|
15
|
+
VERBATIM = %w[pre code script style textarea].freeze
|
|
16
|
+
|
|
17
|
+
def self.document(text, **)
|
|
18
|
+
Kramdown::Document.new(text.to_s, **OPTIONS, **)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The document without the newlines of the source in its text, for a target that keeps a newline instead of
|
|
22
|
+
# reading it as a space (Confluence): a soft line break becomes a space, the newline after a line break goes.
|
|
23
|
+
# Inline code over two lines is one line (as in CommonMark). Paragraphs, breaks and code blocks are unchanged.
|
|
24
|
+
def self.unwrapped(text, **)
|
|
25
|
+
document(text, **).tap { |doc| unwrap(doc.root) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.unwrap(element)
|
|
29
|
+
element.children.each_with_index do |child, index|
|
|
30
|
+
if child.type == :text
|
|
31
|
+
previous = index.positive? ? element.children[index - 1] : nil
|
|
32
|
+
joined = child.value.gsub(/[ \t]*\r?\n[ \t]*/, " ")
|
|
33
|
+
child.value = line_break?(previous) ? joined.lstrip : joined
|
|
34
|
+
elsif child.type == :codespan
|
|
35
|
+
child.value = child.value.gsub(/\r?\n/, " ")
|
|
36
|
+
elsif !(child.type == :html_element && VERBATIM.include?(child.value))
|
|
37
|
+
unwrap(child)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
private_class_method :unwrap
|
|
42
|
+
|
|
43
|
+
def self.line_break?(element)
|
|
44
|
+
element && (element.type == :br || (element.type == :html_element && element.value == "br"))
|
|
45
|
+
end
|
|
46
|
+
private_class_method :line_break?
|
|
47
|
+
end
|
|
48
|
+
end
|