Compare commits
No commits in common. "master" and "gt-unsync1" have entirely different histories.
master
...
gt-unsync1
@ -13,7 +13,8 @@ BaselineOfMiniDocs >> baseline: spec [
|
||||
"Dependencies"
|
||||
self setUpTeapot: spec.
|
||||
self setUpPetitParser: spec.
|
||||
self setUpLepiterBuildingBlocs: spec. "working in v1.0.993"
|
||||
"LepiterBuildingBlocs commented while resolving the conflict with the internal gtoolkit renaming."
|
||||
"self setUpLepiterBuildingBlocs: spec"
|
||||
spec
|
||||
baseline: 'Mustache' with: [ spec repository: 'github://noha/mustache' ];
|
||||
baseline: 'Temple' with: [ spec repository: 'github://astares/Pharo-Temple/src' ];
|
||||
@ -31,8 +32,7 @@ BaselineOfMiniDocs >> baseline: spec [
|
||||
'Mustache' 'Temple' "Templating"
|
||||
'Teapot' 'Tealight' "Web server"
|
||||
'PetitMarkdown' 'PetitParser' "Parsers"
|
||||
'DataFrame' "Tabular data"
|
||||
'LepiterBuildingBlocs' "Lepiter utilities")].
|
||||
'DataFrame' "Tabular data")].
|
||||
.
|
||||
|
||||
"Groups"
|
||||
@ -60,7 +60,10 @@ BaselineOfMiniDocs >> semanticVersion [
|
||||
BaselineOfMiniDocs >> setUpLepiterBuildingBlocs: spec [
|
||||
spec
|
||||
baseline: 'LepiterBuildingBlocs'
|
||||
with: [spec repository: 'github://botwhytho/LepiterBuildingBlocs:main/src']
|
||||
with: [spec
|
||||
repository: 'github://botwhytho/LepiterBuildingBlocs:main/src';
|
||||
loads: #('ALL')];
|
||||
import: 'LepiterBuildingBlocs'
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
|
@ -1,18 +0,0 @@
|
||||
"
|
||||
I model a possible bridge between TaskWarrior and MiniDocs. (starting DRAFT).
|
||||
"
|
||||
Class {
|
||||
#name : #AcroReport,
|
||||
#superclass : #Object,
|
||||
#category : #MiniDocs
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
AcroReport class >> project: projectName [
|
||||
| jsonReport |
|
||||
jsonReport := (GtSubprocessWithInMemoryOutput new
|
||||
shellCommand: 'task project:', projectName , ' export';
|
||||
runAndWait;
|
||||
stdout).
|
||||
^ STONJSON fromString: jsonReport
|
||||
]
|
@ -1,57 +0,0 @@
|
||||
Class {
|
||||
#name : #AlphanumCounter,
|
||||
#superclass : #Object,
|
||||
#instVars : [
|
||||
'letters',
|
||||
'digits',
|
||||
'currentLetter',
|
||||
'currentDigit'
|
||||
],
|
||||
#category : #MiniDocs
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> current [
|
||||
^ self currentLetter asString, self currentDigit asString
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> currentDigit [
|
||||
|
||||
^ currentDigit ifNil: [ currentDigit := self digits first ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> currentLetter [
|
||||
^ currentLetter ifNil: [ currentLetter := self letters first ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> currentLetterIndex [
|
||||
^ self letters detectIndex: [:n | n = self currentLetter]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> digits [
|
||||
^ digits ifNil: [ digits := 1 to: 9 ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> digits: aNumbersArray [
|
||||
digits := aNumbersArray
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> increase [
|
||||
(self currentDigit < self digits last)
|
||||
ifTrue: [ currentDigit := currentDigit + 1 ]
|
||||
ifFalse: [
|
||||
currentLetter := self letters at: (self currentLetterIndex + 1).
|
||||
currentDigit := self digits first
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
AlphanumCounter >> letters [
|
||||
^ letters ifNil: [ letters := $A to: $Z ]
|
||||
]
|
@ -26,20 +26,3 @@ Array >> bagOfWordsFor: sentenceArray [
|
||||
].
|
||||
^ bagOfWords
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
Array >> replaceWithUniqueNilsAndBooleans [
|
||||
| response |
|
||||
(self includesAny: #(true false nil))
|
||||
ifFalse: [ response := self ]
|
||||
ifTrue: [ | newItem |
|
||||
response := OrderedCollection new.
|
||||
self do: [:item |
|
||||
(item isBoolean or: [ item isNil ])
|
||||
ifTrue: [ newItem := item asString, '-', (NanoID generate copyFrom: 1 to: 3) ]
|
||||
ifFalse: [ newItem := item ].
|
||||
response add: newItem.
|
||||
].
|
||||
].
|
||||
^ response
|
||||
]
|
||||
|
@ -1,12 +0,0 @@
|
||||
Extension { #name : #ByteString }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
ByteString >> asHTMLComment [
|
||||
^ '<!-- ', self , ' -->'
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
ByteString >> email [
|
||||
"Quick fix for importing Lepiter pages that have a plain ByteString field as email."
|
||||
^ self
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
Extension { #name : #Dictionary }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
Dictionary >> treeView [
|
||||
^ self asOrderedDictionary treeView
|
||||
]
|
@ -1,53 +0,0 @@
|
||||
Extension { #name : #FileLocator }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
FileLocator class >> aliases [
|
||||
| fileAliases |
|
||||
fileAliases := self fileAliases.
|
||||
fileAliases exists
|
||||
ifFalse: [ | initialConfig |
|
||||
initialConfig := Dictionary new.
|
||||
fileAliases ensureCreateFile.
|
||||
MarkupFile exportAsFileOn: fileAliases containing: initialConfig
|
||||
].
|
||||
^ STON fromString: fileAliases contents
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
FileLocator class >> atAlias: aString put: aFolderOrFile [
|
||||
| updatedAliases |
|
||||
updatedAliases:= self aliases
|
||||
at: aString put: aFolderOrFile;
|
||||
yourself.
|
||||
MarkupFile exportAsFileOn: self fileAliases containing: updatedAliases.
|
||||
^ updatedAliases
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
FileLocator >> extractMetadata [
|
||||
"I package the functionality from [[How to extract meta information using ExifTool]],
|
||||
from the GToolkit Book.
|
||||
I depend on the external tool ExifTool."
|
||||
|
||||
| process variablesList |
|
||||
process := GtSubprocessWithInMemoryOutput new
|
||||
command: 'exiftool';
|
||||
arguments: { self fullName}.
|
||||
process errorBlock: [ :proc | ^ self error: 'Failed to run exiftool' ].
|
||||
process runAndWait.
|
||||
variablesList := process stdout lines collect: [ :currentLine |
|
||||
| separatorIndex name value |
|
||||
separatorIndex := currentLine indexOf: $:.
|
||||
name := (currentLine copyFrom: 1 to: separatorIndex - 1) trimBoth.
|
||||
value := (currentLine
|
||||
copyFrom: separatorIndex + 1
|
||||
to: currentLine size) trimBoth.
|
||||
name -> value
|
||||
].
|
||||
^ variablesList asOrderedDictionary
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
FileLocator class >> fileAliases [
|
||||
^ MiniDocs appFolder / 'fileAliases.ston'
|
||||
]
|
@ -1,51 +1,5 @@
|
||||
Extension { #name : #GtGQLSnippet }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
GtGQLSnippet >> asMarkdeep [
|
||||
| output |
|
||||
output := WriteStream on: ''.
|
||||
(self metadata)
|
||||
at: 'operation' put: self operation;
|
||||
at: 'input' put: self input;
|
||||
at: 'context' put: self context;
|
||||
yourself.
|
||||
output
|
||||
nextPutAll: self metadataDiv;
|
||||
nextPutAll: self markdeepCustomOpener;
|
||||
nextPutAll: self asMarkdownString;
|
||||
nextPut: Character lf;
|
||||
nextPutAll: self markdeepCustomCloser;
|
||||
nextPut: Character lf;
|
||||
nextPutAll: '</div>';
|
||||
nextPut: Character lf;
|
||||
nextPut: Character lf.
|
||||
^ output contents withInternetLineEndings
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
GtGQLSnippet >> markdeepCustomCloser [
|
||||
^ self markdeepCustomOpener
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
GtGQLSnippet >> markdeepCustomOpener [
|
||||
^ '* * *'
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
GtGQLSnippet >> metadataDiv [
|
||||
"PENDING: Shared among several snippets. Should be abstracted further?"
|
||||
| output |
|
||||
output := WriteStream on: ''.
|
||||
output
|
||||
nextPutAll: '<div st-class="' , self class greaseString , '"';
|
||||
nextPut: Character lf;
|
||||
nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
|
||||
nextPut: Character lf.
|
||||
^ output contents withInternetLineEndings.
|
||||
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
GtGQLSnippet >> metadataUpdate [
|
||||
| createEmailSanitized editEmailSanitized |
|
||||
@ -53,7 +7,7 @@ GtGQLSnippet >> metadataUpdate [
|
||||
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
|
||||
^ OrderedDictionary new
|
||||
at: 'id' put: self uidString;
|
||||
at: 'parent' put: self parent uid asString36;
|
||||
at: 'parent' put: self parent uuid;
|
||||
at: 'created' put: self createTime asString;
|
||||
at: 'modified' put: self latestEditTime asString;
|
||||
at: 'creator' put: createEmailSanitized;
|
||||
|
@ -29,36 +29,15 @@ HedgeDoc class >> newDefault [
|
||||
|
||||
{ #category : #accessing }
|
||||
HedgeDoc >> asLePage [
|
||||
| newPage sanitizedMarkdown |
|
||||
sanitizedMarkdown := self bodyWithoutTitleHeader promoteMarkdownHeaders.
|
||||
| newPage snippet |
|
||||
snippet := LeTextSnippet new
|
||||
string: self bodyWithoutTitleHeader promoteMarkdownHeaders.
|
||||
newPage := LePage new
|
||||
initializeTitle: self title.
|
||||
sanitizedMarkdown := sanitizedMarkdown markdownSplitted.
|
||||
sanitizedMarkdown class = OrderedCollection ifTrue: [
|
||||
sanitizedMarkdown do: [:lines | | snippet |
|
||||
snippet := LeTextSnippet new
|
||||
string: lines asStringWithCr;
|
||||
uid: LeUID new.
|
||||
newPage
|
||||
addSnippet: snippet;
|
||||
yourself
|
||||
]
|
||||
].
|
||||
sanitizedMarkdown class = ByteString ifTrue: [ | snippet |
|
||||
snippet := LeTextSnippet new
|
||||
string: sanitizedMarkdown;
|
||||
uid: LeUID new.
|
||||
newPage
|
||||
addSnippet: snippet;
|
||||
yourself
|
||||
].
|
||||
newPage
|
||||
incomingLinks;
|
||||
splitAdmonitionSnippets.
|
||||
newPage editTime: DateAndTime now.
|
||||
newPage options
|
||||
at: 'HedgeDoc' at: 'yamlFrontmatter' put: self metadata;
|
||||
at: 'HedgeDoc' at: 'url' put: self url asString asHTMLComment.
|
||||
initializeTitle: self title;
|
||||
addSnippet: snippet;
|
||||
yourself.
|
||||
newPage incomingLinks.
|
||||
newPage metadata addAll: self metadata.
|
||||
^ newPage
|
||||
]
|
||||
|
||||
@ -172,7 +151,7 @@ HedgeDoc >> server: aUrlString [
|
||||
|
||||
{ #category : #accessing }
|
||||
HedgeDoc >> url [
|
||||
url ifNotNil: [ ^ url asUrl ]
|
||||
^ url asUrl
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -183,6 +162,7 @@ HedgeDoc >> url: anObject [
|
||||
(html xpath: '//head/meta[@name="application-name"][@content = "HedgeDoc - Ideas grow better together"]') isEmpty
|
||||
ifTrue: [ self inform: 'Not a hedgedoc url'.
|
||||
url := nil ].
|
||||
self metadata at: 'title' put: tempUrl firstPathSegment.
|
||||
server := tempUrl host.
|
||||
url := anObject
|
||||
]
|
||||
|
@ -37,39 +37,40 @@ LeDatabase >> addPageCopy: aLePage [
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeDatabase >> addPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation [
|
||||
| remoteMetadata divSnippets dataSnippets page |
|
||||
| remoteMetadata divSnippets dataSnippets snippets page |
|
||||
divSnippets := (markdeepDocTree xpath: '//div[@st-class]') asOrderedCollection
|
||||
collect: [ :xmlElement | xmlElement postCopy ].
|
||||
remoteMetadata := Markdeep new metadataFromXML: markdeepDocTree.
|
||||
"Ensuring remote metadata has consistent data"
|
||||
remoteMetadata at: 'origin' put: externalDocLocation.
|
||||
remoteMetadata at: 'title' ifAbsentPut: [ markdeepDocTree detectMarkdeepTitle ].
|
||||
remoteMetadata at: 'id' ifAbsentPut: [UUID new asString36].
|
||||
remoteMetadata at: 'created' ifAbsentPut: [ DateAndTime now] .
|
||||
remoteMetadata at: 'creator' ifAbsentPut: [ 'unknown' ].
|
||||
remoteMetadata at: 'modified' ifAbsentPut: [ DateAndTime now].
|
||||
remoteMetadata at: 'modifier' ifAbsentPut: [ 'unknown' ].
|
||||
dataSnippets := self sanitizeMarkdeepSnippets: divSnippets withMetadata: remoteMetadata.
|
||||
snippets := dataSnippets collect: [ :each | each asLepiterSnippet ].
|
||||
page := LePage new.
|
||||
page fromDictionary: remoteMetadata.
|
||||
dataSnippets do: [:each | | snippet|
|
||||
snippet := each asLepiterSnippet.
|
||||
page
|
||||
title: (remoteMetadata at: 'title' ifAbsent: [ page detectMarkdeepTitleFrom: markdeepDocTree ]);
|
||||
basicUid: (UUID fromString36: (remoteMetadata at: 'id' ifAbsent: [UUID new asString36]));
|
||||
createTime: (LeTime new
|
||||
time: (remoteMetadata at: 'created' ifAbsent: [ DateAndTime now]) asDateAndTime);
|
||||
editTime: (LeTime new
|
||||
time: (remoteMetadata at: 'modified' ifAbsent: [ DateAndTime now]) asDateAndTime);
|
||||
latestEditTime: (LeTime new
|
||||
time: (remoteMetadata at: 'modified' ifAbsent: [ DateAndTime now]) asDateAndTime);
|
||||
createEmail: (remoteMetadata at: 'creator' ifAbsent: [ 'unknown' ]);
|
||||
editEmail: (remoteMetadata at: 'modifier' ifAbsent: [ 'unknown' ]).
|
||||
snippets do: [ :snippet | "| currentParent |"
|
||||
page addSnippet: snippet.
|
||||
"currentParent := page detectParentSnippetWithUid: (snippet metadata at: 'parent').
|
||||
snippet parent: currentParent."
|
||||
].
|
||||
page children
|
||||
do: [ :snippet |
|
||||
(self hasBlockUID: snippet uid)
|
||||
ifTrue: [ | existingPage |
|
||||
existingPage := self pages
|
||||
detect: [ :pageTemp | pageTemp includesSnippetUid: snippet uid ]
|
||||
ifFound: [
|
||||
self importErrorForLocal: existingPage withRemote: externalDocLocation.
|
||||
^ self
|
||||
]
|
||||
ifNone: [ snippet database: self ].
|
||||
]
|
||||
ifFalse: [ snippet database: self ]
|
||||
].
|
||||
detect: [ :pageTemp | pageTemp includesSnippetUid: snippet uid ].
|
||||
self importErrorForLocal: existingPage withRemote: externalDocLocation.
|
||||
^ self ]
|
||||
ifFalse: [ snippet database: self.
|
||||
self registerSnippet: snippet ] ].
|
||||
self addPage: page.
|
||||
^ page
|
||||
]
|
||||
|
@ -3,7 +3,7 @@ Extension { #name : #LePage }
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> asHtmlFile [
|
||||
|
||||
self asMarkdownFileWithMetadataWrappers.
|
||||
self asMarkdownFile.
|
||||
self defaultPandocTemplate exists
|
||||
ifFalse: [ MarkupFile installTemplate: 'https://mutabit.com/repos.fossil/mutabit/doc/trunk/plantillas/Pandoc/clean-menu-mod.html' into: self defaultPandocTemplate parent ].
|
||||
|
||||
@ -21,7 +21,6 @@ LePage >> asHtmlFile [
|
||||
LePage >> asMarkdeep [
|
||||
| bodyStream markdeep |
|
||||
bodyStream := '' writeStream.
|
||||
bodyStream nextPutAll: self notebookMetadataSnippet asMarkdeep.
|
||||
self preorderTraversal
|
||||
do: [ :snippet | bodyStream nextPutAll: snippet asMarkdeep ].
|
||||
markdeep := Markdeep new
|
||||
@ -48,7 +47,6 @@ LePage >> asMarkdeepFile [
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> asMarkdown [
|
||||
"PENDING: to debug the output."
|
||||
| bodyStream markdown |
|
||||
bodyStream := '' writeStream.
|
||||
bodyStream
|
||||
@ -56,13 +54,13 @@ LePage >> asMarkdown [
|
||||
self preorderTraversal
|
||||
do: [ :snippet | bodyStream nextPutAll: snippet asMarkdown ].
|
||||
markdown := Markdown new
|
||||
contents: bodyStream contents promoteMarkdownHeaders;
|
||||
contents: bodyStream contents demoteMarkdownHeaders;
|
||||
metadata: (self metadata at: 'original' ifAbsentPut: Dictionary new).
|
||||
^ markdown
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> asMarkdownFileWithMetadataWrappers [
|
||||
LePage >> asMarkdownFile [
|
||||
| folder |
|
||||
folder := self storage.
|
||||
^ MarkupFile exportAsFileOn: folder / self markdownFileName containing: self asMarkdownWithMetadataWrappers contents
|
||||
@ -72,15 +70,13 @@ LePage >> asMarkdownFileWithMetadataWrappers [
|
||||
LePage >> asMarkdownWithMetadataWrappers [
|
||||
| bodyStream markdown |
|
||||
bodyStream := '' writeStream.
|
||||
"bodyStream
|
||||
nextPut: Character lf;
|
||||
nextPutAll: '# ', self title; cr; cr."
|
||||
bodyStream
|
||||
nextPutAll: '# ', self title; cr; cr.
|
||||
self preorderTraversal
|
||||
do: [ :snippet | bodyStream nextPutAll: snippet asMarkdownWithMetadataWrappers ].
|
||||
markdown := Markdown new
|
||||
contents: bodyStream contents promoteMarkdownHeaders;
|
||||
title: self title;
|
||||
metadata: self metadata.
|
||||
contents: bodyStream contents demoteMarkdownHeaders;
|
||||
metadata: (self metadata at: 'original' ifAbsentPut: Dictionary new).
|
||||
^ markdown
|
||||
]
|
||||
|
||||
@ -99,6 +95,14 @@ LePage >> defaultPandocTemplate [
|
||||
^ FileLocator home / '.pandoc' / 'templates' / 'clean-menu-mod.html'
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> detectMarkdeepTitleFrom: xmlSubtree [
|
||||
| titleLine |
|
||||
titleLine := (xmlSubtree nodesCollect: [:node | node contentString ]) first lines
|
||||
detect: [:line | line includesSubstring: ' **'] ifNone: ['Untitled'].
|
||||
^ titleLine trimmed trimBoth: [:char | char = $* ]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> detectParentSnippetWithUid: uidString [
|
||||
uidString = self uid asString36 ifTrue: [ ^ self ].
|
||||
@ -120,33 +124,11 @@ LePage >> exportMetadataToHead: markdeep [
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> exportedFileName [
|
||||
| sanitized titleWords shortTitle |
|
||||
titleWords := self title splitOn: Character space.
|
||||
(titleWords size > 11)
|
||||
ifTrue: [
|
||||
titleWords := titleWords copyFrom: 1 to: 3.
|
||||
shortTitle := titleWords joinUsing: Character space.
|
||||
]
|
||||
ifFalse: [shortTitle := self title].
|
||||
sanitized := shortTitle asDashedLowercase romanizeAccents copyWithoutAll: #($/ $: $🢒 $,).
|
||||
| sanitized |
|
||||
sanitized := self title asDashedLowercase romanizeAccents copyWithoutAll: #($/ $: $🢒).
|
||||
^ sanitized , '--' , (self uidString copyFrom: 1 to: 5)
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> fromDictionary: aDictionary [
|
||||
self
|
||||
title: (aDictionary at: 'title');
|
||||
basicUid: (UUID fromString36: (aDictionary at: 'id'));
|
||||
createTime: (LeTime new
|
||||
time: (aDictionary at: 'created') asDateAndTime);
|
||||
editTime: (LeTime new
|
||||
time: (aDictionary at: 'modified') asDateAndTime);
|
||||
latestEditTime: (LeTime new
|
||||
time: (aDictionary at: 'modified') asDateAndTime);
|
||||
createEmail: (aDictionary at: 'creator');
|
||||
editEmail: (aDictionary at: 'modifier').
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> fromMarkdeepUrl: aString [
|
||||
| docTree pageMetadata |
|
||||
@ -180,10 +162,7 @@ LePage >> latestEditTime: aLeTime [
|
||||
LePage >> localHostAddress [
|
||||
| localUrl route |
|
||||
MiniDocsServer teapot server isRunning ifFalse: [ MiniDocsServer restart ].
|
||||
route := self storage path segments joinUsing: '/'.
|
||||
MiniDocsServer teapot
|
||||
serveStatic: ('/', route, '/', self markdeepFileName)
|
||||
from: self storage / self markdeepFileName.
|
||||
route := MiniDocsServer teapot staticRouter prefix joinUsing: '/'.
|
||||
localUrl := MiniDocsServer teapot server localUrl asString.
|
||||
^ localUrl, route, '/', self markdeepFileName
|
||||
]
|
||||
@ -227,17 +206,6 @@ LePage >> navTop [
|
||||
ifTrue: [ ^ topNavFile contents ]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> notebookMetadataSnippet [
|
||||
| response |
|
||||
response := LeTextSnippet new fromString: '<!-- See this snippet source code for this notebook''s metadata -->'.
|
||||
response parent: self.
|
||||
self optionAt: 'HedgeDoc' ifAbsent: [ ^ response ].
|
||||
(response extra)
|
||||
at: 'HedgeDoc' put: (self optionAt: 'HedgeDoc').
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> olderChild [
|
||||
"I provide the last edited child node.
|
||||
@ -297,25 +265,6 @@ LePage >> sharedVariablesBindings [
|
||||
^ shared asDictionary
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> splitAdmonitionSnippets [
|
||||
"I'm used to clean after importing from HedgeDoc to ensure that a snippet contains only admonitions and extra content is put in a new cell."
|
||||
| admonitionSnippets |
|
||||
admonitionSnippets := self children select: [:node | node string startsWithMarkdownAdmonition ].
|
||||
admonitionSnippets ifEmpty: [ ^ self ].
|
||||
admonitionSnippets do: [:node | | nodeContent |
|
||||
node ifNotNil: [
|
||||
nodeContent := node string.
|
||||
nodeContent startsWithMarkdownAdmonition
|
||||
ifTrue: [ | snippetCommand |
|
||||
snippetCommand := node splitSnippetCommandAtPosition: nodeContent admonitionEndingPosition.
|
||||
snippetCommand execute.
|
||||
node tagWith: (nodeContent lines first trimBoth withoutPrefix: ':::')
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePage >> storage [
|
||||
| current |
|
||||
|
@ -8,17 +8,6 @@ LePharoSnippet >> contentAsStringCustomized [
|
||||
^ thisObject perform: self detectMessage trimmed asSymbol.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePharoSnippet >> fromDictionary: anOrderedDictionary [
|
||||
self
|
||||
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
|
||||
parent: (anOrderedDictionary at: 'parent');
|
||||
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
|
||||
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
|
||||
editEmail: (anOrderedDictionary at: 'modifier');
|
||||
createEmail: (anOrderedDictionary at: 'creator').
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePharoSnippet >> fromMarkdeep: markdeepDiv [
|
||||
|
||||
@ -28,7 +17,7 @@ LePharoSnippet >> fromMarkdeep: markdeepDiv [
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePharoSnippet >> fromString: aString [
|
||||
|
||||
[ self coder forSource: aString ] onErrorDo: [ ]
|
||||
self code: aString
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
|
@ -41,37 +41,22 @@ LePictureSnippet >> contentFrom: markdeepDiv [
|
||||
self urlString: (markdeepDiv // 'img' @ 'src') stringValue.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePictureSnippet >> fromDictionary: anOrderedDictionary [
|
||||
| sanitizedUrl|
|
||||
sanitizedUrl := (anOrderedDictionary at: 'url').
|
||||
sanitizedUrl := sanitizedUrl copyFrom: 5 to: sanitizedUrl size - 3.
|
||||
self
|
||||
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
|
||||
parent: (anOrderedDictionary at: 'parent');
|
||||
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
|
||||
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
|
||||
editEmail: (anOrderedDictionary at: 'modifier');
|
||||
createEmail: (anOrderedDictionary at: 'creator');
|
||||
urlString: sanitizedUrl;
|
||||
caption: (anOrderedDictionary at: 'content') first
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePictureSnippet >> fromMarkdeep: markdeepDiv [
|
||||
^ markdeepDiv asSnippetDictionary asLepiterSnippet
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LePictureSnippet >> fromString: aStringArray [
|
||||
"aStringArray should contain as first element the sanitized string and
|
||||
as second the full original image Link string, which may contains links in the description."
|
||||
| args urlTemp |
|
||||
LePictureSnippet >> fromString: aString [
|
||||
"aString should be a valid Markdown/Markdeep image string"
|
||||
| args captionTemp urlTemp |
|
||||
|
||||
args := aStringArray second splitOn: ']('.
|
||||
urlTemp := args last.
|
||||
args := aString splitOn: ']('.
|
||||
captionTemp := args first.
|
||||
captionTemp := captionTemp copyFrom: 3 to: captionTemp size.
|
||||
urlTemp := args second.
|
||||
urlTemp := urlTemp copyFrom: 1 to: urlTemp size - 1.
|
||||
self caption: aStringArray first.
|
||||
self caption: captionTemp.
|
||||
self urlString: urlTemp.
|
||||
^ self
|
||||
]
|
||||
|
@ -13,18 +13,7 @@ LeSnippet class >> fromMetaMarkdeep: div [
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeSnippet >> metadata [
|
||||
| createEmailSanitized editEmailSanitized |
|
||||
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
|
||||
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
|
||||
self optionAt: 'metadata' ifAbsentPut: [ OrderedDictionary new ].
|
||||
^ (self optionAt: 'metadata')
|
||||
at: 'id' put: self uidString;
|
||||
at: 'parent' put: self parent uid asString36;
|
||||
at: 'created' put: self createTime asString;
|
||||
at: 'modified' put: self latestEditTime asString;
|
||||
at: 'creator' put: createEmailSanitized;
|
||||
at: 'modifier' put: editEmailSanitized;
|
||||
yourself
|
||||
^ self metadataUpdate
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
|
@ -1,25 +1,5 @@
|
||||
Extension { #name : #LeTextCoderSnippetElement }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextCoderSnippetElement >> asLePage [
|
||||
| currentSnippet newPage |
|
||||
currentSnippet := self snippet.
|
||||
newPage := LePage new.
|
||||
newPage
|
||||
title: (currentSnippet text asString trimLeft: [:char | char = $# ]) trim.
|
||||
self page database
|
||||
addPage: newPage.
|
||||
currentSnippet allChildrenBreadthFirstDo: [:child |
|
||||
child moveToPageTitled: newPage title.
|
||||
].
|
||||
^ newPage
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextCoderSnippetElement >> asSnippetViewModel [
|
||||
^ self snippetContent
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextCoderSnippetElement >> moveToPageTitled: pageName [
|
||||
| db origin destination |
|
||||
|
@ -18,17 +18,6 @@ LeTextSnippet >> asLePage [
|
||||
^ page.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextSnippet >> fromDictionary: anOrderedDictionary [
|
||||
self
|
||||
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
|
||||
parent: (anOrderedDictionary at: 'parent');
|
||||
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
|
||||
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
|
||||
editEmail: (anOrderedDictionary at: 'modifier');
|
||||
createEmail: (anOrderedDictionary at: 'creator')
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextSnippet >> fromMarkdeep: markdeepDiv [
|
||||
|
||||
@ -38,9 +27,7 @@ LeTextSnippet >> fromMarkdeep: markdeepDiv [
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextSnippet >> fromString: aString [
|
||||
|
||||
self
|
||||
string: aString;
|
||||
uid: LeUID new.
|
||||
self string: aString
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
@ -62,18 +49,7 @@ LeTextSnippet >> parentId [
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextSnippet >> tagWith: aString [
|
||||
self tags add: aString.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextSnippet >> withFollowingSnippets [
|
||||
"I'm the same method implemented for PharoSnippets,
|
||||
but present also here as a way to improve moving prose snippets from pages.
|
||||
"
|
||||
| snippets stop start |
|
||||
snippets := self parent children asArray.
|
||||
start := snippets indexOf: self.
|
||||
stop := snippets size.
|
||||
^ snippets copyFrom: start to: stop
|
||||
LeTextSnippet >> taggedWith: aString [
|
||||
self metadata at: 'tags' ifPresent: [ (self metadata at: 'tags') add: aString; yourself ] ifAbsentPut: [ Set new ].
|
||||
^ self metadata at: 'tags'
|
||||
]
|
||||
|
@ -2,11 +2,16 @@ Extension { #name : #LeTextualSnippet }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> asMarkdeep [
|
||||
"Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use
|
||||
'st-' properties as a way to extend divs metadata regarding its contents."
|
||||
|
||||
| output |
|
||||
output := WriteStream on: ''.
|
||||
output
|
||||
nextPutAll: self metadataDiv;
|
||||
nextPutAll: '<div st-class="' , self class greaseString , '"';
|
||||
nextPut: Character lf;
|
||||
nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
|
||||
nextPut: Character lf;
|
||||
nextPutAll: self markdeepCustomOpener;
|
||||
nextPutAll: self contentAsStringAnnotated;
|
||||
nextPut: Character lf;
|
||||
@ -34,18 +39,44 @@ LeTextualSnippet >> asMarkdownWithMetadataWrappers [
|
||||
| output |
|
||||
output := '' writeStream.
|
||||
output
|
||||
nextPutAll: self metadataDiv;
|
||||
nextPutAll: '<div st-class="', self class asString, '"'; lf;
|
||||
nextPutAll: ' st-data="', (STON toString: self metadata), '">'; lf;
|
||||
nextPutAll: self markdownCustomOpener;
|
||||
nextPutAll: self contentAsStringCustomized; lf;
|
||||
nextPutAll: self markdownCustomCloser;
|
||||
nextPutAll: '</div>'; lf; lf.
|
||||
^ output contents withInternetLineEndings
|
||||
^ output contents
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> contentAsStringAnnotated [
|
||||
self ast ifNotNil: [ ^ self processSnippetAnnotations ].
|
||||
^ self contentAsString
|
||||
| annotations substitutions exported pageConfig|
|
||||
self ast ifNil: [ ^ self contentAsString ].
|
||||
annotations := self ast parts select: [:each | each className includesSubstring: 'AnnotationNode' ].
|
||||
annotations ifEmpty: [ ^ self contentAsString ].
|
||||
substitutions := OrderedDictionary new.
|
||||
pageConfig := self page config.
|
||||
annotations do: [ :each | | key type value color |
|
||||
key := each source.
|
||||
type := (key splitOn: ':') first copyWithoutAll: '{{'.
|
||||
value := key copyFrom: type size + 4 to: key size - 2.
|
||||
pageConfig
|
||||
ifNil: [ color := 'default' ]
|
||||
ifNotNil: [ | colors |
|
||||
colors := pageConfig at: 'annotationColors' ifAbsent: [ nil ].
|
||||
colors ifNotNil: [
|
||||
color := colors at: type ifAbsent: [ colors at: 'defaultColor' ifAbsentPut: ['default'] ]
|
||||
]
|
||||
].
|
||||
substitutions
|
||||
at: key
|
||||
put: '<span st-class="',type,'" style="color:', color, '">', value,'</span>'.
|
||||
].
|
||||
exported := self contentAsString.
|
||||
substitutions keysAndValuesDo: [:k :v |
|
||||
exported := exported copyReplaceAll: k with: v.
|
||||
].
|
||||
^ exported
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
@ -55,11 +86,6 @@ LeTextualSnippet >> contentAsStringCustomized [
|
||||
ifFalse: [ ^ self contentAsString ]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> extra [
|
||||
^ self optionAt: 'extra' ifAbsentPut: [ Dictionary new ]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> markdeepCustomCloser [
|
||||
^ ''
|
||||
@ -86,21 +112,6 @@ LeTextualSnippet >> metadata [
|
||||
^ self metadataUpdate
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> metadataDiv [
|
||||
"Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use
|
||||
'st-' properties as a way to extend divs metadata regarding its contents."
|
||||
"PENDING: this is repeated in several snippets. Can be abstracted up in a common object of the class hierarchy?"
|
||||
| output |
|
||||
output := WriteStream on: ''.
|
||||
output
|
||||
nextPutAll: '<div st-class="' , self class greaseString , '"';
|
||||
nextPut: Character lf;
|
||||
nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
|
||||
nextPut: Character lf.
|
||||
^ output contents withInternetLineEndings.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> metadataUpdate [
|
||||
| createEmailSanitized editEmailSanitized |
|
||||
@ -108,46 +119,14 @@ LeTextualSnippet >> metadataUpdate [
|
||||
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
|
||||
^ OrderedDictionary new
|
||||
at: 'id' put: self uidString;
|
||||
at: 'parent' put: (self parent ifNotNil: [self parent uidString ]);
|
||||
at: 'parent' put: self parent uidString;
|
||||
at: 'created' put: self createTime asString;
|
||||
at: 'modified' put: self latestEditTime asString;
|
||||
at: 'creator' put: createEmailSanitized;
|
||||
at: 'modifier' put: editEmailSanitized;
|
||||
at: 'extra' put: self extra;
|
||||
yourself
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> processSnippetAnnotations [
|
||||
| exported substitutions annotations pageConfig |
|
||||
annotations := self ast parts
|
||||
select: [ :each | each className includesSubstring: 'AnnotationNode' ].
|
||||
annotations ifEmpty: [ ^ self contentAsString ].
|
||||
substitutions := OrderedDictionary new.
|
||||
pageConfig := self page config.
|
||||
annotations
|
||||
do: [ :each |
|
||||
| key type value color |
|
||||
key := each source.
|
||||
type := (key splitOn: ':') first copyWithoutAll: '{{'.
|
||||
value := key copyFrom: type size + 4 to: key size - 2.
|
||||
pageConfig
|
||||
ifNil: [ color := 'default' ]
|
||||
ifNotNil: [ | colors |
|
||||
colors := pageConfig at: 'annotationColors' ifAbsent: [ nil ].
|
||||
colors
|
||||
ifNotNil: [ color := colors
|
||||
at: type
|
||||
ifAbsent: [ colors at: 'defaultColor' ifAbsentPut: [ 'default' ] ] ] ].
|
||||
substitutions
|
||||
at: key
|
||||
put: '<span st-class="' , type , '" style="color:' , color , '">' , value , '</span>' ].
|
||||
exported := self contentAsString.
|
||||
substitutions
|
||||
keysAndValuesDo: [ :k :v | exported := exported copyReplaceAll: k with: v ].
|
||||
^ exported
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> sanitizeMetadata [
|
||||
self options ifNil: [^ self ].
|
||||
@ -162,5 +141,5 @@ LeTextualSnippet >> sanitizeMetadata [
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
LeTextualSnippet >> tags [
|
||||
^ self extra at: 'tags' ifAbsentPut: [ Set new ]
|
||||
^ self metadata at: 'tags' ifAbsentPut: [ Set new ]
|
||||
]
|
||||
|
@ -4,7 +4,7 @@ Class {
|
||||
#instVars : [
|
||||
'folder'
|
||||
],
|
||||
#category : #'MiniDocs-Model'
|
||||
#category : #MiniDocs
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -29,5 +29,5 @@ Logseq >> journals [
|
||||
|
||||
{ #category : #accessing }
|
||||
Logseq >> pages [
|
||||
^self folder/ 'pages'
|
||||
self folder/ 'pages'
|
||||
]
|
||||
|
@ -27,15 +27,6 @@ Markdown >> asMarkdeep [
|
||||
commentYAMLMetadata
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Markdown >> asMarkdownTiddler [
|
||||
^ Tiddler new
|
||||
title: self title;
|
||||
text: self contents;
|
||||
type: 'text/x-markdown';
|
||||
created: Tiddler nowLocal.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Markdown >> body [
|
||||
^ body
|
||||
@ -49,7 +40,7 @@ Markdown >> body: aString [
|
||||
{ #category : #operation }
|
||||
Markdown >> commentYAMLMetadata [
|
||||
| newContents |
|
||||
self contents detectYAMLMetadata ifFalse: [ ^ self ].
|
||||
self detectYAMLMetadata ifFalse: [ ^ self ].
|
||||
newContents := '' writeStream.
|
||||
newContents nextPutAll: '<!--@yaml'; lf.
|
||||
newContents nextPutAll: self yamlMetadataString.
|
||||
@ -66,15 +57,14 @@ Markdown >> containsYAMLMetadataClosing [
|
||||
|
||||
{ #category : #accessing }
|
||||
Markdown >> contents [
|
||||
| response metadataString |
|
||||
| response |
|
||||
response := WriteStream on: ''.
|
||||
metadataString := self metadataAsYAML
|
||||
ifEmpty: [ '' ]
|
||||
ifNotEmpty: [ '---', String cr, self metadataAsYAML, String cr, '---', String cr ].
|
||||
response
|
||||
nextPutAll: metadataString;
|
||||
nextPutAll: (self body ifNil: [ '' ]).
|
||||
^ response contents withInternetLineEndings
|
||||
nextPutAll: '---'; cr;
|
||||
nextPutAll: self metadataAsYAML; cr;
|
||||
nextPutAll: '---'; cr;
|
||||
nextPutAll: self body.
|
||||
^ response contents
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -142,7 +132,7 @@ Markdown >> exportMetadataAsYaml [
|
||||
|
||||
{ #category : #accessing }
|
||||
Markdown >> file [
|
||||
^ file ifNil: [ file := FileLocator temp / 'temporalMarkdeep.md' ]
|
||||
^ file ifNil: [ file := FileLocator temp / 'temporalMarkdeep.md.html' ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -159,18 +149,8 @@ Markdown >> fromFile: aFileReference [
|
||||
|
||||
{ #category : #'instance creation' }
|
||||
Markdown >> fromString: markdownString [
|
||||
| yamlMetadataRaw bodyTemp |
|
||||
yamlMetadataRaw := (YamlHeaderParser parse: markdownString).
|
||||
bodyTemp := '' writeStream.
|
||||
(yamlMetadataRaw removeKey: 'body') do: [:paragraph |
|
||||
bodyTemp nextPutAll: paragraph; cr; cr
|
||||
].
|
||||
self body: bodyTemp contents withInternetLineEndings.
|
||||
(yamlMetadataRaw sanitizeMultilineValuesWith: markdownString)
|
||||
ifNotNil: [
|
||||
self metadata
|
||||
ifEmpty: [ self metadata: yamlMetadataRaw ]
|
||||
ifNotEmpty: [ self metadata at: 'hedgeDoc' put: yamlMetadataRaw ]].
|
||||
(self metadata) at: 'original' put: markdownString yamlMetadata.
|
||||
self body: markdownString contentsWithoutYAMLMetadata
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -200,7 +180,6 @@ Markdown >> metadata [
|
||||
|
||||
^ metadata ifNil: [ metadata := Dictionary new].
|
||||
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@ -240,8 +219,3 @@ Markdown >> printOn: aStream [
|
||||
Markdown >> title [
|
||||
^ title ifNil: [ title:= self headerAsTitle ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Markdown >> title: aString [
|
||||
title := aString
|
||||
]
|
||||
|
@ -34,8 +34,11 @@ MiniDocs class >> altShiftRightCombo [
|
||||
|
||||
{ #category : #accessing }
|
||||
MiniDocs class >> appFolder [
|
||||
| tempFolder |
|
||||
tempFolder := ExoRepo userDataFolder / 'Mutabit' / 'MiniDocs'.
|
||||
| tempFolder userDataFolder |
|
||||
userDataFolder := Smalltalk os isWindows
|
||||
ifTrue: [ FileLocator home / 'AppData' / 'Local' ]
|
||||
ifFalse: [ FileLocator userData ].
|
||||
tempFolder := userDataFolder / 'Mutabit' / 'MiniDocs'.
|
||||
tempFolder exists ifFalse: [ tempFolder ensureCreateDirectory ].
|
||||
^ tempFolder
|
||||
]
|
||||
|
64
src/MiniDocs/NanoID.class.st
Normal file
64
src/MiniDocs/NanoID.class.st
Normal file
@ -0,0 +1,64 @@
|
||||
"
|
||||
I'm run an implementation of the [Nano ID](https://github.com/ai/nanoid) tiny, secure URL-friendly unique string ID generator via its [Nim implementation](https://github.com/icyphox/nanoid.nim).
|
||||
|
||||
The Nim script has hard coded:
|
||||
|
||||
* a [base 58 encoding](https://medium.com/concerning-pharo/understanding-base58-encoding-23e673e37ff6) alphabet to avoid similar looking letter and the use of non-alphanumeric characters.
|
||||
* a 12 characters length output, which gives [a pretty low probability collision](https://zelark.github.io/nano-id-cc/) for the previous alphabet:
|
||||
~616 years needed, in order to have a 1% probability of at least one collision at a speed of 1000 IDs per hour.
|
||||
This is more than enough for our unique IDs applications, mostly in the documentation context,
|
||||
which consists of hand crafted and/or programmatically produced notes ,
|
||||
for example in data narratives, book(lets) and TiddlyWiki tiddlers of tens or hundreds of notes at most,
|
||||
unevenly produced between hours, days and/or weeks..
|
||||
|
||||
The `External` tag is related on its dependency on other programming languages and frameworks,
|
||||
though the dependency should be loaded by just loading a small binary with no dependencies.
|
||||
"
|
||||
Class {
|
||||
#name : #NanoID,
|
||||
#superclass : #Object,
|
||||
#category : #'MiniDocs-External'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
NanoID class >> binaryFile [
|
||||
^ MiniDocs appFolder / self scriptSourceCode basenameWithoutExtension
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
NanoID class >> generate [
|
||||
self binaryFile exists ifFalse: [ NanoID install].
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ ^ (LibC resultOfCommand:self binaryFile fullName) copyWithoutAll: (Character lf asString) ].
|
||||
OSSUnixSubprocess new
|
||||
command: self binaryFile fullName;
|
||||
redirectStdout;
|
||||
redirectStdout;
|
||||
runAndWaitOnExitDo: [ :process :outString | ^ outString copyWithoutAll: (Character lf asString) ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
NanoID class >> install [
|
||||
"For the moment, only Gnu/Linux and Mac are supported.
|
||||
IMPORTANT: Nimble, Nim's package manager should be installed, as this process doesn't verify its proper installation."
|
||||
self binaryFile exists ifTrue: [ ^ MiniDocs appFolder ].
|
||||
Nimble install: 'nanoid'.
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ ^ LibC resultOfCommand: 'nanoid c ',self scriptSourceCode fullName ].
|
||||
OSSUnixSubprocess new
|
||||
command: 'nim';
|
||||
arguments: {'c'. self scriptSourceCode fullName};
|
||||
runAndWaitOnExitDo: [ :process :outString |
|
||||
(self scriptSourceCode parent / (self scriptSourceCode) basenameWithoutExtension) moveToPageTitled: MiniDocs appFolder asFileReference.
|
||||
^ MiniDocs appFolder ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
NanoID class >> isInstalled [
|
||||
^ self binaryFile exists
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
NanoID class >> scriptSourceCode [
|
||||
^ FileLocator image parent / 'pharo-local/iceberg/Offray/MiniDocs/src/nanoIdGen.nim'
|
||||
]
|
89
src/MiniDocs/Nimble.class.st
Normal file
89
src/MiniDocs/Nimble.class.st
Normal file
@ -0,0 +1,89 @@
|
||||
"
|
||||
I'm a helper class modelling the common uses of the Nim's [Nimble package manager](https://github.com/nim-lang/nimble).
|
||||
This was evolved in the context of the [Grafoscopio](mutabit.com/grafoscopio/en.html) community exploration and prototyping of interactive documentation.
|
||||
"
|
||||
Class {
|
||||
#name : #Nimble,
|
||||
#superclass : #Object,
|
||||
#category : #'MiniDocs-External'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
Nimble class >> detect: packageName [
|
||||
^ self installed
|
||||
detect: [ :dependency | dependency beginsWith: packageName ]
|
||||
ifFound: [ ^ true ]
|
||||
ifNone: [ ^ false ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Nimble class >> install: packageName [
|
||||
(self detect: packageName) ifTrue: [ ^ self ].
|
||||
self installPackagesList.
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ ^ LibC runCommand: 'nimble install ', packageName ].
|
||||
OSSUnixSubprocess new
|
||||
command: 'nimble';
|
||||
arguments: {'install'.
|
||||
packageName};
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString :errString |
|
||||
process isSuccess
|
||||
ifTrue: [ Transcript show: 'Command exited correctly with output: ', outString. ]
|
||||
ifFalse: [
|
||||
^ 'Command exit with error status: ', process exitStatusInterpreter printString, String cr,
|
||||
'Stderr contents: ', errString.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Nimble class >> installPackagesList [
|
||||
|
||||
(FileLocator home / '.nimble' / 'packages_official.json') exists
|
||||
ifTrue: [ ^ self ].
|
||||
(Smalltalk os isUnix or: [ Smalltalk os isMacOS ])
|
||||
ifTrue: [
|
||||
OSSUnixSubprocess new
|
||||
command: 'nimble';
|
||||
arguments: #('refresh');
|
||||
redirectStdout;
|
||||
runAndWaitOnExitDo: [ :process :outString | ^ outString ].
|
||||
].
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ ^ LibC resultOfCommand: 'nimble refresh' ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Nimble class >> installed [
|
||||
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ | process |
|
||||
process := GtExternalProcessBuilder new
|
||||
command: 'nimble.exe';
|
||||
args: #('list' '--installed');
|
||||
output.
|
||||
^ process stdout lines ].
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'nimble';
|
||||
arguments: #('list' '--installed');
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString :errString |
|
||||
process isSuccess
|
||||
ifTrue: [ ^ outString lines ];
|
||||
ifFalse: [ ^ nil ]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
Nimble class >> version [
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'nimble';
|
||||
arguments: #('--version');
|
||||
redirectStdout;
|
||||
runAndWaitOnExitDo: [ :process :outString | ^ outString ]
|
||||
]
|
@ -10,18 +10,19 @@ OrderedDictionary >> asLepiterSnippet [
|
||||
| response |
|
||||
self at: 'className' ifAbsent: [ ^ nil ].
|
||||
response := (self at: 'className') asClass new.
|
||||
[ response fromDictionary: self ] onErrorDo: [ ].
|
||||
[ response fromString: (self at: 'content') ] onErrorDo: [ ].
|
||||
response fromString: (self at: 'content').
|
||||
response
|
||||
uid: (LeUID new uidString: (self at: 'id'));
|
||||
parent: (self at: 'parent');
|
||||
createTime: (LeTime new time: ((self at: 'created')asDateAndTime));
|
||||
editTime: (LeTime new time: ((self at: 'modified') asDateAndTime));
|
||||
editEmail: (self at: 'modifier');
|
||||
createEmail: (self at: 'creator').
|
||||
self at: 'origin' ifPresent: [ response metadata at: 'origin' put: (self at: 'origin') ].
|
||||
self at: 'errata' ifPresent: [ response metadata at: 'errata' put: (self at: 'errata') ].
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> asYAML [
|
||||
^ (YQ jsonToYaml: self) accentedCharactersCorrection.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> errata [
|
||||
^ self at: 'errata' ifAbsentPut: [ OrderedCollection new]
|
||||
@ -32,69 +33,3 @@ OrderedDictionary >> redefineTimestampsBefore: dateAndTime [
|
||||
self at: 'modified' put: dateAndTime asDateAndTime.
|
||||
self at: 'created' put: dateAndTime asDateAndTime - 1 second.
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> replaceNilsWith: aCharacter [
|
||||
self associationsDo: [:each |
|
||||
each value ifNil: [self at: each key put: aCharacter].
|
||||
each value isDictionary ifTrue: [each value replaceNilsWith: aCharacter].
|
||||
each value isArray ifTrue: [ | newArray|
|
||||
newArray := (each value asDataSeries replaceNilsWith: aCharacter) asArray.
|
||||
self at: each key put: newArray
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> replaceWithUniqueNilsAndBooleansStartingAt: anInteger [
|
||||
| totalNils shortUID |
|
||||
totalNils := self flattened asDataSeries countNils.
|
||||
shortUID := [NanoID generate copyFrom: 1 to: 3].
|
||||
self associations doWithIndex: [:assoc :i | | subIndex |
|
||||
subIndex := anInteger asString, '-', i asString.
|
||||
assoc value
|
||||
ifNil: [ self at: assoc key put: 'nil-', subIndex ].
|
||||
assoc value isBoolean
|
||||
ifTrue: [ self at: assoc key put: assoc value asString, '-', subIndex ].
|
||||
assoc value isDictionary ifTrue: [assoc replaceWithUniqueNilsAndBooleansStartingAt: i].
|
||||
assoc value isArray
|
||||
ifTrue: [ self at: assoc key put: (assoc value replaceWithUniqueNilsAndBooleans)]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> sanitizeMultilineValuesWith: aString [
|
||||
| toSanitize response |
|
||||
toSanitize := OrderedCollection new.
|
||||
response := OrderedCollection new.
|
||||
self keysAndValuesDo: [:k :v |
|
||||
(v isString and: [v lines size > 1])
|
||||
ifTrue: [
|
||||
aString lines
|
||||
detect: [:line | line includesSubstring: k ]
|
||||
ifFound: [:line | | sanitized|
|
||||
sanitized := (line withoutPrefix: k, ':'), String cr,
|
||||
v indentedWithExtraSpaces: 4.
|
||||
self at: k put: sanitized ]
|
||||
]
|
||||
].
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
OrderedDictionary >> treeView [
|
||||
| view |
|
||||
view := GtMondrian new.
|
||||
view nodes
|
||||
stencil: [ :x |
|
||||
BlElement new
|
||||
border: (BlBorder paint: Color black);
|
||||
geometry: BlEllipseGeometry new;
|
||||
layout: (BlLinearLayout new alignCenter);
|
||||
addChild: (BlTextElement text: (x asRopedText fontSize: 10)) ];
|
||||
with: (self flatCollectAsSet: #yourself) , self keys.
|
||||
view edges
|
||||
stencil: [ :x | BlLineElement new border: (BlBorder paint: (Color blue alpha: 0.5) width: 4) ];
|
||||
connect: self associations from: #key toAll: #value.
|
||||
view layout tree.
|
||||
^ view
|
||||
]
|
||||
|
@ -147,11 +147,17 @@ Pandoc class >> markdownToHtmlOnUnix: inputFile [
|
||||
outputFile := inputFile parent / (inputFile basenameWithoutExtension , '.html').
|
||||
outputFile ensureDelete.
|
||||
outputFile ensureCreateFile.
|
||||
GtSubprocessWithInMemoryOutput new
|
||||
shellCommand: 'pandoc -f markdown+startnum+task_lists --standalone -t html ', inputFile fullName, ' --output ', outputFile fullName;
|
||||
runAndWait;
|
||||
stdout.
|
||||
^ outputFile.
|
||||
OSSUnixSubprocess new
|
||||
command: 'pandoc';
|
||||
arguments: {'-f'. 'markdown+startnum+task_lists'. '--standalone'. '-t'. 'html'. inputFile fullName.
|
||||
'--output'. outputFile fullName };
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString :errString |
|
||||
process isSuccess
|
||||
ifTrue: [ ^ outputFile ]
|
||||
ifFalse: [ ^ inputFile ]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : #converters }
|
||||
|
@ -11,7 +11,7 @@ Class {
|
||||
'footnoteLabel',
|
||||
'footnoteContent'
|
||||
],
|
||||
#category : #'MiniDocs-Model'
|
||||
#category : #MiniDocs
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
|
@ -4,7 +4,7 @@ Extension { #name : #String }
|
||||
String >> accentedCharactersCorrection [
|
||||
| modified corrections |
|
||||
corrections := {
|
||||
'ó' -> 'ó' . 'Ã' -> 'Ó' . 'ú' -> 'ú' . 'ñ' -> 'ñ' . 'Ã' -> 'Ñ' .
|
||||
'ó' -> 'ó' . 'ú' -> 'ú' . 'ñ' -> 'ñ' .
|
||||
'Ã' -> 'í' . 'á' -> 'á' . 'é' -> 'é' . 'â' -> $' asString} asDictionary.
|
||||
modified := self copy.
|
||||
corrections keysAndValuesDo: [ :k :v |
|
||||
@ -13,41 +13,6 @@ String >> accentedCharactersCorrection [
|
||||
^ modified
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> admonitionBorderLines [
|
||||
| response |
|
||||
response := OrderedDictionary new.
|
||||
self lines doWithIndex: [:line :index |
|
||||
(self admonitionBorders includes: line trimBoth)
|
||||
ifTrue: [ response at: index put: line trimBoth ]
|
||||
].
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> admonitionBorders [
|
||||
"For the moment I only work with the admonition starting border
|
||||
as adding the closing one would imply to redo the #markdownSplitted
|
||||
method implementing a proper parser, which, ATM is overkill."
|
||||
| response |
|
||||
response := #('info' 'success' 'warning' 'danger') collect: [ :each | ':::', each ].
|
||||
^ response "copyWith: ':::'"
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> admonitionEndingPosition [
|
||||
| response |
|
||||
response := 0.
|
||||
self startsWithMarkdownAdmonition ifFalse: [ ^ response ].
|
||||
self lines do: [:line |
|
||||
response > 0 ifTrue: [ response := response + 1 ].
|
||||
(line trimBoth = ':::')
|
||||
ifFalse: [ response := response + line size ]
|
||||
ifTrue: [ ^ response := response + line size. ]
|
||||
].
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> asDashedLowercase [
|
||||
"I convert phrases like 'This is a phrase' into 'this-is-a-phrase'."
|
||||
@ -105,16 +70,6 @@ String >> detectYAMLMetadata [
|
||||
ifFound: [ ^ true ] ifNone: [ ^ false ] ]
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> indentedWithExtraSpaces: spaceNumber [
|
||||
| response indent |
|
||||
response := '' writeStream.
|
||||
indent := String new.
|
||||
spaceNumber timesRepeat: [ indent := indent, ' ' ].
|
||||
self lines do: [:line | response nextPutAll: indent, line, String lf ].
|
||||
^ response contents
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> markdownHeaders [
|
||||
| response headers |
|
||||
@ -127,38 +82,6 @@ String >> markdownHeaders [
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> markdownSplitLines [
|
||||
"I'm useful for conversions between the HedgeDoc Markdown variant and Lepiter page snippets.
|
||||
I provide broad places to where semantic breaks should be located in a page,
|
||||
depending on headers or admonitions to create pages snippets with similar divisions.
|
||||
Further page splits should be provided manually by the document author."
|
||||
| response |
|
||||
response := OrderedDictionary new.
|
||||
response := response
|
||||
addAll: self markdownHeaders;
|
||||
addAll: self admonitionBorderLines;
|
||||
yourself.
|
||||
^ (response associations sorted: [ :x :y | x key < y key ]) asOrderedDictionary
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> markdownSplitted [
|
||||
| response lastPart |
|
||||
self markdownSplitLines ifEmpty: [ ^ self ].
|
||||
response := OrderedCollection new.
|
||||
self markdownSplitLines keys allButLast doWithIndex: [:key :index | | nextLine part |
|
||||
nextLine := (self markdownSplitLines keys at: index + 1) - 1.
|
||||
part := self lines copyFrom: key to: nextLine.
|
||||
response add: part.
|
||||
].
|
||||
lastPart := self lines
|
||||
copyFrom: self markdownSplitLines keys last
|
||||
to: self lines size.
|
||||
response add: lastPart.
|
||||
^ response
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> promoteMarkdownHeaders [
|
||||
| response |
|
||||
@ -181,12 +104,6 @@ String >> romanizeAccents [
|
||||
^ modified
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> startsWithMarkdownAdmonition [
|
||||
self lines ifEmpty: [ ^ false ].
|
||||
^ self admonitionBorders includes: self lines first trimBoth
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> startsWithYAMLMetadataDelimiter [
|
||||
self lines ifEmpty: [^false].
|
||||
@ -199,6 +116,11 @@ String >> withoutXMLTagDelimiters [
|
||||
^ self copyWithoutAll: #($< $>)
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> yamlMetadata [
|
||||
^ (YAML2JSON fromString: self yamlMetadataString)
|
||||
]
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
String >> yamlMetadataClosingLineNumber [
|
||||
"I return the line where the closing of the YAML metadata occurs or 0 if no closing is found."
|
||||
|
@ -1,9 +0,0 @@
|
||||
Extension { #name : #XMLDocument }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
XMLDocument >> detectMarkdeepTitle [
|
||||
| titleLine |
|
||||
titleLine := (self nodesCollect: [:node | node contentString ]) first lines
|
||||
detect: [:line | line includesSubstring: ' **'] ifNone: ['Untitled'].
|
||||
^ titleLine trimmed trimBoth: [:char | char = $* ]
|
||||
]
|
52
src/MiniDocs/YQ.class.st
Normal file
52
src/MiniDocs/YQ.class.st
Normal file
@ -0,0 +1,52 @@
|
||||
"
|
||||
The `External` tag is related on its dependency on other programming languages and frameworks,
|
||||
though the dependency should be loaded by just loading a small binary with no dependencies.
|
||||
"
|
||||
Class {
|
||||
#name : #YQ,
|
||||
#superclass : #Object,
|
||||
#category : #'MiniDocs-External'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
YQ class >> binaryDownloadLinkFor: operativeSystem on: processor [
|
||||
| binaryName binaryDownloadData |
|
||||
binaryName := 'yq_', operativeSystem , '_', processor.
|
||||
binaryDownloadData := ((self lastReleaseData at: 'assets')
|
||||
select: [:each | (each at: 'name') beginsWith: binaryName ]) first.
|
||||
^ binaryDownloadData at: 'browser_download_url'
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
YQ class >> binaryFile [
|
||||
"Starting with location on Arch Linux and its derivates. Multidistro and multiOS support should be added."
|
||||
^ FileLocator root / 'usr/bin/yq'
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
YQ class >> install [
|
||||
^ self lastReleaseData
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
YQ class >> jsonToYaml: aDictionary [
|
||||
| jsonFile |
|
||||
self binaryFile exists ifFalse: [ YQ install].
|
||||
jsonFile := MarkupFile exportAsFileOn: FileLocator temp / 'data.json' containing: aDictionary.
|
||||
(Smalltalk os isUnix or: [ Smalltalk os isMacOS ])
|
||||
ifTrue: [
|
||||
OSSUnixSubprocess new
|
||||
shellCommand: 'cat ', jsonFile fullName,' | yq -y';
|
||||
redirectStdout;
|
||||
runAndWaitOnExitDo: [ :command :outString |
|
||||
^ outString
|
||||
]].
|
||||
Smalltalk os isWindows
|
||||
ifTrue: [ ^ LibC resultOfCommand: 'yq -p=json ', jsonFile fullName ].
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
YQ class >> lastReleaseData [
|
||||
^ (STONJSON
|
||||
fromString: 'https://api.github.com/repos/mikefarah/yq/releases' asUrl retrieveContents) first
|
||||
]
|
@ -1,10 +0,0 @@
|
||||
Extension { #name : #ZnConstants }
|
||||
|
||||
{ #category : #'*MiniDocs' }
|
||||
ZnConstants class >> maximumLineLength [
|
||||
"Return the maximum line length to accept.
|
||||
Used by ZnLineReader and thus for reading request/status lines as well as headers.
|
||||
This helps to protect us from malicious content."
|
||||
|
||||
^ 5096 "8192"
|
||||
]
|
Loading…
Reference in New Issue
Block a user