diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json
index ea344c7..7a9692b 100644
--- a/.obsidian/community-plugins.json
+++ b/.obsidian/community-plugins.json
@@ -1,4 +1,5 @@
[
"obsidian-kanban",
- "obsidian-git"
+ "obsidian-git",
+ "automatic-table-of-contents"
]
\ No newline at end of file
diff --git a/.obsidian/plugins/automatic-table-of-contents/main.js b/.obsidian/plugins/automatic-table-of-contents/main.js
new file mode 100644
index 0000000..bbc2b0f
--- /dev/null
+++ b/.obsidian/plugins/automatic-table-of-contents/main.js
@@ -0,0 +1,291 @@
+let Plugin = class {}
+let MarkdownRenderer = {}
+let MarkdownRenderChild = class {}
+let htmlToMarkdown = (html) => html
+
+if (isObsidian()) {
+ const obsidian = require('obsidian')
+ Plugin = obsidian.Plugin
+ MarkdownRenderer = obsidian.MarkdownRenderer
+ MarkdownRenderChild = obsidian.MarkdownRenderChild
+ htmlToMarkdown = obsidian.htmlToMarkdown
+}
+
+const codeblockId = 'table-of-contents'
+const codeblockIdShort = 'toc'
+const availableOptions = {
+ title: {
+ type: 'string',
+ default: '',
+ comment: '',
+ },
+ style: {
+ type: 'value',
+ default: 'nestedList',
+ values: ['nestedList', 'nestedOrderedList', 'inlineFirstLevel'],
+ comment: 'TOC style (nestedList|nestedOrderedList|inlineFirstLevel)',
+ },
+ minLevel: {
+ type: 'number',
+ default: 0,
+ comment: 'Include headings from the specified level',
+ },
+ maxLevel: {
+ type: 'number',
+ default: 0,
+ comment: 'Include headings up to the specified level',
+ },
+ includeLinks: {
+ type: 'boolean',
+ default: true,
+ comment: 'Make headings clickable',
+ },
+ debugInConsole: {
+ type: 'boolean',
+ default: false,
+ comment: 'Print debug info in Obsidian console',
+ },
+}
+
+class ObsidianAutomaticTableOfContents extends Plugin {
+ async onload() {
+ const handler = (sourceText, element, context) => {
+ context.addChild(new Renderer(this.app, element, context.sourcePath, sourceText))
+ }
+ this.registerMarkdownCodeBlockProcessor(codeblockId, handler)
+ this.registerMarkdownCodeBlockProcessor(codeblockIdShort, handler)
+ this.addCommand({
+ id: 'insert-automatic-table-of-contents',
+ name: 'Insert table of contents',
+ editorCallback: onInsertToc,
+ })
+ this.addCommand({
+ id: 'insert-automatic-table-of-contents-docs',
+ name: 'Insert table of contents (documented)',
+ editorCallback: onInsertTocWithDocs,
+ })
+ }
+}
+
+function onInsertToc(editor) {
+ const markdown = '```' + codeblockId + '\n```'
+ editor.replaceRange(markdown, editor.getCursor())
+}
+
+function onInsertTocWithDocs(editor) {
+ let markdown = ['```' + codeblockId]
+ Object.keys(availableOptions).forEach((optionName) => {
+ const option = availableOptions[optionName]
+ const comment = option.comment.length > 0 ? ` # ${option.comment}` : ''
+ markdown.push(`${optionName}: ${option.default}${comment}`)
+ })
+ markdown.push('```')
+ editor.replaceRange(markdown.join('\n'), editor.getCursor())
+}
+
+class Renderer extends MarkdownRenderChild {
+ constructor(app, element, sourcePath, sourceText) {
+ super(element)
+ this.app = app
+ this.element = element
+ this.sourcePath = sourcePath
+ this.sourceText = sourceText
+ }
+
+ // Render on load
+ onload() {
+ this.render()
+ this.registerEvent(this.app.metadataCache.on('changed', this.onMetadataChange.bind(this)))
+ }
+
+ // Render on file change
+ onMetadataChange() {
+ this.render()
+ }
+
+ render() {
+ try {
+ const options = parseOptionsFromSourceText(this.sourceText)
+ if (options.debugInConsole) debug('Options', options)
+
+ const metadata = this.app.metadataCache.getCache(this.sourcePath)
+ const headings = metadata && metadata.headings ? metadata.headings : []
+ if (options.debugInConsole) debug('Headings', headings)
+
+ const markdown = getMarkdownFromHeadings(headings, options)
+ if (options.debugInConsole) debug('Markdown', markdown)
+
+ this.element.empty()
+ MarkdownRenderer.renderMarkdown(markdown, this.element, this.sourcePath, this)
+ } catch(error) {
+ const readableError = `_💥 Could not render table of contents (${error.message})_`
+ MarkdownRenderer.renderMarkdown(readableError, this.element, this.sourcePath, this)
+ }
+ }
+}
+
+function getMarkdownFromHeadings(headings, options) {
+ const markdownHandlersByStyle = {
+ nestedList: getMarkdownNestedListFromHeadings,
+ nestedOrderedList: getMarkdownNestedOrderedListFromHeadings,
+ inlineFirstLevel: getMarkdownInlineFirstLevelFromHeadings,
+ }
+ let markdown = ''
+ if (options.title && options.title.length > 0) {
+ markdown += options.title + '\n'
+ }
+ const noHeadingMessage = '_Table of contents: no headings found_'
+ markdown += markdownHandlersByStyle[options.style](headings, options) || noHeadingMessage
+ return markdown
+}
+
+function getMarkdownNestedListFromHeadings(headings, options) {
+ return getMarkdownListFromHeadings(headings, false, options)
+}
+
+function getMarkdownNestedOrderedListFromHeadings(headings, options) {
+ return getMarkdownListFromHeadings(headings, true, options)
+}
+
+function getMarkdownListFromHeadings(headings, isOrdered, options) {
+ const prefix = isOrdered ? '1.' : '-'
+ const lines = []
+ const minLevel = options.minLevel > 0
+ ? options.minLevel
+ : Math.min(...headings.map((heading) => heading.level))
+ headings.forEach((heading) => {
+ if (heading.level < minLevel) return
+ if (options.maxLevel > 0 && heading.level > options.maxLevel) return
+ lines.push(`${'\t'.repeat(heading.level - minLevel)}${prefix} ${getMarkdownHeading(heading, options)}`)
+ })
+ return lines.length > 0 ? lines.join('\n') : null
+}
+
+function getMarkdownInlineFirstLevelFromHeadings(headings, options) {
+ const minLevel = options.minLevel > 0
+ ? options.minLevel
+ : Math.min(...headings.map((heading) => heading.level))
+ const items = headings
+ .filter((heading) => heading.level === minLevel)
+ .map((heading) => {
+ return getMarkdownHeading(heading, options)
+ })
+ return items.length > 0 ? items.join(' | ') : null
+}
+
+function getMarkdownHeading(heading, options) {
+ const stripMarkdown = (text) => {
+ text = text.replaceAll('*', '').replaceAll('_', '').replaceAll('`', '')
+ text = text.replaceAll('==', '').replaceAll('~~', '')
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Strip markdown links
+ return text
+ }
+ const stripHtml = (text) => stripMarkdown(htmlToMarkdown(text))
+ const stripWikilinks = (text, isForLink) => {
+ // Strip [[link|text]] format
+ // For the text part of the final link we only keep "text"
+ // For the link part we need the text + link
+ // Example: "# Some [[file.md|heading]]" must be translated to "[[#Some file.md heading|Some heading]]"
+ text = text.replace(/\[\[([^\]]+)\|([^\]]+)\]\]/g, isForLink ? '$1 $2' : '$2')
+ text = text.replace(/\[\[([^\]]+)\]\]/g, '$1') // Strip [[link]] format
+ // Replace malformed links & reserved wikilinks chars
+ text = text.replaceAll('[[', '').replaceAll('| ', isForLink ? '' : '- ').replaceAll('|', isForLink ? ' ' : '-')
+ return text
+ }
+ const stripTags = (text) => text.replaceAll('#', '')
+ if (options.includeLinks) {
+ // Remove markdown, HTML & wikilinks from text for readability, as they are not rendered in a wikilink
+ let text = heading.heading
+ text = stripMarkdown(text)
+ text = stripHtml(text)
+ text = stripWikilinks(text, false)
+ // Remove wikilinks & tags from link or it won't be clickable (on the other hand HTML & markdown must stay)
+ let link = heading.heading
+ link = stripWikilinks(link, true)
+ link = stripTags(link)
+
+ // Return wiklink style link
+ return `[[#${link}|${text}]]`
+ // Why not markdown links? Because even if it looks like the text part would have a better compatibility
+ // with complex headings (as it would support HTML, markdown, etc) the link part is messy,
+ // because it requires some encoding that looks buggy and undocumented; official docs state the link must be URL encoded
+ // (https://help.obsidian.md/Linking+notes+and+files/Internal+links#Supported+formats+for+internal+links)
+ // but it doesn't work properly, example: "## Some heading with simple HTML" must be encoded as:
+ // [Some heading with simple HTML](#Some%20heading%20with%20simpler%20HTML)
+ // and not
+ // [Some heading with simple HTML](#Some%20%3Cem%3Eheading%3C%2Fem%3E%20with%20simpler%20HTML)
+ // Also it won't be clickable at all if the heading contains #tags or more complex HTML
+ // (example: ## Some heading #with-a-tag)
+ // (unless there is a way to encode these use cases that I didn't find)
+ }
+ return heading.heading
+}
+
+function parseOptionsFromSourceText(sourceText = '') {
+ const options = {}
+ Object.keys(availableOptions).forEach((option) => {
+ options[option] = availableOptions[option].default
+ })
+ sourceText.split('\n').forEach((line) => {
+ const option = parseOptionFromSourceLine(line)
+ if (option !== null) {
+ options[option.name] = option.value
+ }
+ })
+ return options
+}
+
+function parseOptionFromSourceLine(line) {
+ const matches = line.match(/([a-zA-Z0-9._ ]+):(.*)/)
+ if (line.startsWith('#') || !matches) return null
+ const possibleName = matches[1].trim()
+ const optionParams = availableOptions[possibleName]
+ let possibleValue = matches[2].trim()
+ if (!optionParams || optionParams.type !== 'string') {
+ // Strip comments from values except for strings (as a string may contain markdown)
+ possibleValue = possibleValue.replace(/#[^#]*$/, '').trim()
+ }
+ const valueError = new Error(`Invalid value for \`${possibleName}\``)
+ if (optionParams && optionParams.type === 'number') {
+ const value = parseInt(possibleValue)
+ if (value < 0) throw valueError
+ return { name: possibleName, value }
+ }
+ if (optionParams && optionParams.type === 'boolean') {
+ if (!['true', 'false'].includes(possibleValue)) throw valueError
+ return { name: possibleName, value: possibleValue === 'true' }
+ }
+ if (optionParams && optionParams.type === 'value') {
+ if (!optionParams.values.includes(possibleValue)) throw valueError
+ return { name: possibleName, value: possibleValue }
+ }
+ if (optionParams && optionParams.type === 'string') {
+ return { name: possibleName, value: possibleValue }
+ }
+ return null
+}
+
+function debug(type, data) {
+ console.log(...[
+ `%cAutomatic Table Of Contents %c${type}:\n`,
+ 'color: orange; font-weight: bold',
+ 'font-weight: bold',
+ data,
+ ])
+}
+
+function isObsidian() {
+ if (typeof process !== 'object') {
+ return true // Obsidian mobile doesn't have a global process object
+ }
+ return !process.env || !process.env.JEST_WORKER_ID // Jest runtime is not Obsidian
+}
+
+if (isObsidian()) {
+ module.exports = ObsidianAutomaticTableOfContents
+} else {
+ module.exports = {
+ parseOptionsFromSourceText,
+ getMarkdownFromHeadings,
+ }
+}
diff --git a/.obsidian/plugins/automatic-table-of-contents/manifest.json b/.obsidian/plugins/automatic-table-of-contents/manifest.json
new file mode 100644
index 0000000..535186f
--- /dev/null
+++ b/.obsidian/plugins/automatic-table-of-contents/manifest.json
@@ -0,0 +1,10 @@
+{
+ "id": "automatic-table-of-contents",
+ "name": "Automatic Table Of Contents",
+ "version": "1.4.0",
+ "minAppVersion": "1.3.0",
+ "description": "Create a table of contents in a note, that updates itself when the note changes",
+ "author": "Johan Satgé",
+ "authorUrl": "https://github.com/johansatge",
+ "isDesktopOnly": false
+}
\ No newline at end of file
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index 710224d..d95b74e 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,19 +13,7 @@
"state": {
"type": "markdown",
"state": {
- "file": "Meetings/DevOps/2024-06-01.md",
- "mode": "source",
- "source": false
- }
- }
- },
- {
- "id": "baccd079c408fe2f",
- "type": "leaf",
- "state": {
- "type": "markdown",
- "state": {
- "file": "Kanban Pages/HelyxVerify.md",
+ "file": "Kanban Pages/V&V.md",
"mode": "source",
"source": false
}
@@ -97,7 +85,7 @@
"state": {
"type": "backlink",
"state": {
- "file": "Meetings/DevOps/2024-06-01.md",
+ "file": "Kanban Pages/V&V.md",
"collapseAll": true,
"extraContext": false,
"sortOrder": "alphabetical",
@@ -114,7 +102,7 @@
"state": {
"type": "outgoing-link",
"state": {
- "file": "Meetings/DevOps/2024-06-01.md",
+ "file": "Kanban Pages/V&V.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
@@ -137,7 +125,7 @@
"state": {
"type": "outline",
"state": {
- "file": "Meetings/DevOps/2024-06-01.md"
+ "file": "Kanban Pages/V&V.md"
}
}
}
@@ -158,16 +146,16 @@
"obsidian-kanban:Create new board": false
}
},
- "active": "2f56f8d2c998700c",
+ "active": "676a3e53a7c36034",
"lastOpenFiles": [
- "Meetings/QA/2024-07-01.md",
+ "Kanban Pages/Work Kanban.md",
+ "Kanban Pages/V&V.md",
+ "Kanban Pages/HelyxVerify.md",
"Meetings/DevOps/2024-06-01.md",
+ "Meetings/QA/2024-07-01.md",
"Meetings/DevOps",
- "Kanban Pages/Work Kanban.md",
"Meetings/QA",
"Meetings",
- "Kanban Pages/HelyxVerify.md",
- "Kanban Pages/V&V.md",
"Kanban Pages/QA.md",
"Windows Compilation.md",
"Kanban Pages/Windows Native Compilation.md",
diff --git a/Kanban Pages/V&V.md b/Kanban Pages/V&V.md
index dfe9327..50911ec 100644
--- a/Kanban Pages/V&V.md
+++ b/Kanban Pages/V&V.md
@@ -1,3 +1,11 @@
+```table-of-contents
+title:
+style: nestedList # TOC style (nestedList|nestedOrderedList|inlineFirstLevel)
+minLevel: 0 # Include headings from the specified level
+maxLevel: 0 # Include headings up to the specified level
+includeLinks: true # Make headings clickable
+debugInConsole: false # Print debug info in Obsidian console
+```
# Docker Container
`docker build . -t vv:latest`
@@ -7,12 +15,13 @@
- Core: `/core`
`vv init --collection_path=/collection/`
-
-## Error 1
+# Pipeline
+Looks like AndrewC is also working on something like this.
+# Error 1
`CommandSequence.__init__() got an unexpected keyword argument 'mode'`
Found out that CommandSequence doesn't have a "mode" parameter, but all the configurations in vv-collection have it in their commands.
Fix was to add the property, although it's clear things are now broken
-## Error 2
+# Error 2
```
Traceback (most recent call last):
File "/usr/local/bin/vv", line 8, in
@@ -43,7 +52,7 @@ Traceback (most recent call last):
AttributeError: 'dict' object has no attribute 'parameters'
```
-## Ruan
+# Ruan
[2:24 PM] Ruan Engelbrecht
Ah yes so at the bottom there's the list of cases specified under the run_cases key
@@ -54,4 +63,26 @@ Uncomment the last two lines of my file, and source the mid-sized-ci collection,
[2:26 PM] Ruan Engelbrecht
-And source your core
\ No newline at end of file
+And source your core
+# MPI in docker
+mpirun has detected an attempt to run as root.
+
+Running as root is *strongly* discouraged as any mistake (e.g., in
+defining TMPDIR) or bug can result in catastrophic damage to the OS
+file system, leaving your system in an unusable state.
+
+We strongly suggest that you run mpirun as a non-root user.
+
+You can override this protection by adding the --allow-run-as-root option
+to the cmd line or by setting two environment variables in the following way:
+the variable OMPI_ALLOW_RUN_AS_ROOT=1 to indicate the desire to override this
+protection, and OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 to confirm the choice and
+add one more layer of certainty that you want to do so.
+We reiterate our advice against doing so - please proceed at your own risk.
+# HPC03 machine
+$ cat /etc/centos-release
+CentOS Linux release 7.9.2009 (Core)
+# Important collections
+- customers_report
+- engys/automotive
+- mid-sized-ci
\ No newline at end of file
diff --git a/Kanban Pages/Work Kanban.md b/Kanban Pages/Work Kanban.md
index a6e4aa6..80e44c4 100644
--- a/Kanban Pages/Work Kanban.md
+++ b/Kanban Pages/Work Kanban.md
@@ -31,13 +31,13 @@ kanban-plugin: board
## Working
+- [ ] Create V&V pipeline
+ [[V&V]]
+ #jenkins#vv
- [ ] Allow helyxVerify cases not to be reduced
https://engys.atlassian.net/browse/DO-1198
[[HelyxVerify#Not reducing]]
#helyxVerify
-- [ ] Create V&V pipeline
- [[V&V]]
- #jenkins
- [ ] verificationDict
[[HelyxVerify#system/validationDict]]
#helyxVerify
@@ -53,6 +53,16 @@ kanban-plugin: board
[[Windows Native Compilation]]
+## DevOps Dec24 - A7
+
+- [ ] Run all cases to completion, instead of stopping in the first failure.
+ https://engys.atlassian.net/browse/DO-1211
+ #qa
+- [ ] On nightly, change the version to have the GUI commit
+ https://engys.atlassian.net/browse/DO-1210
+ #packager
+
+
## DevOps Dec24 - A6
- [ ] Python Testing Pipeline
@@ -92,6 +102,6 @@ kanban-plugin: board
%% kanban:settings
```
-{"kanban-plugin":"board","list-collapse":[false,false,false,false]}
+{"kanban-plugin":"board","list-collapse":[false,false,false,false,true]}
```
%%
\ No newline at end of file