523 lines
14 KiB
Smalltalk
523 lines
14 KiB
Smalltalk
"
|
|
An UbakyeNode is and administrator of all node operations in a tree.
|
|
|
|
Instance Variables
|
|
node: <Object>
|
|
|
|
node
|
|
- xxxxx
|
|
|
|
"
|
|
Class {
|
|
#name : #GrafoscopioNode,
|
|
#superclass : #Object,
|
|
#instVars : [
|
|
'header',
|
|
'headers',
|
|
'key',
|
|
'icon',
|
|
'body',
|
|
'tags',
|
|
'children',
|
|
'parent',
|
|
'node',
|
|
'level',
|
|
'nodesInPreorder',
|
|
'cacheNode'
|
|
],
|
|
#category : #'Grafoscopio-Model'
|
|
}
|
|
|
|
{ #category : #'instance creation' }
|
|
GrafoscopioNode class >> header: aHeader body: aText [
|
|
"Create a new instance with given header and body"
|
|
|
|
^(self new)
|
|
header: aHeader;
|
|
body: aText;
|
|
yourself.
|
|
]
|
|
|
|
{ #category : #'instance creation' }
|
|
GrafoscopioNode class >> header: aHeader icon: anIcon body: aText [
|
|
"Create a new instances with given header, icon and body"
|
|
|
|
^(self new)
|
|
header: aHeader;
|
|
icon: anIcon;
|
|
body: aText;
|
|
yourself.
|
|
]
|
|
|
|
{ #category : #'instance creation' }
|
|
GrafoscopioNode class >> named: aString [
|
|
"Create a new instance with a given header and empty body"
|
|
|
|
^(self new)
|
|
header: aString;
|
|
yourself
|
|
]
|
|
|
|
{ #category : #'add/remove nodes' }
|
|
GrafoscopioNode >> addNode: aNode [
|
|
"Adds the given node to the receivers collection of children, and sets this object as the parent
|
|
of the node"
|
|
|
|
self children add: aNode.
|
|
aNode level: (self level) + 1.
|
|
aNode parent: self.
|
|
^aNode
|
|
|
|
]
|
|
|
|
{ #category : #'add/remove nodes' }
|
|
GrafoscopioNode >> addNodeAfter [
|
|
"Adds a generic node after the given node so they become slibings of the same parent"
|
|
| genericNode |
|
|
genericNode := GrafoscopioNode
|
|
header: 'nuevoNodo' body: ''.
|
|
self parent children add: genericNode after: self.
|
|
genericNode parent: self parent.
|
|
genericNode level: self level.
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> ancestors [
|
|
"Returns a collection of all the nodes wich are ancestors of the receiver node"
|
|
| currentNode ancestors |
|
|
|
|
currentNode := self.
|
|
ancestors := OrderedCollection new.
|
|
[currentNode level > 0]
|
|
whileTrue: [
|
|
ancestors add: currentNode parent.
|
|
currentNode := currentNode parent.].
|
|
ancestors := ancestors reversed.
|
|
^ ancestors
|
|
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> ancestorsHeaders [
|
|
"Returns the headers of all the ancestors of the node. Maybe this and 'headers' should be integrated, so both act on a collection of
|
|
children instead of having two separate methods"
|
|
| currentNode ancestors |
|
|
|
|
currentNode := self.
|
|
ancestors := OrderedCollection new.
|
|
(self level - 1)
|
|
timesRepeat: [
|
|
ancestors add: currentNode parent.
|
|
currentNode := currentNode parent.].
|
|
ancestors := ancestors reversed.
|
|
^ ancestors collect: [:ancestor | ancestor header ]
|
|
|
|
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> asMarkdown [
|
|
"Exports children of the current node as pandoc markdown, using special nodes accoding to tags"
|
|
| markdownOutput |
|
|
|
|
markdownOutput := '' writeStream.
|
|
(self preorderTraversal) do: [ :eachNode |
|
|
(eachNode level > 0)
|
|
ifTrue: [(eachNode hasAncestorTaggedAs: 'invisible') | (eachNode tags = 'invisible')
|
|
ifFalse: [ markdownOutput nextPutAll: (eachNode markdownContent) ]]].
|
|
|
|
^markdownOutput contents
|
|
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> asSton [
|
|
"Exports current tree as STON format"
|
|
| stonOutput |
|
|
|
|
stonOutput := '' writeStream.
|
|
stonOutput nextPutAll: (STON toStringPretty: self children).
|
|
^stonOutput contents
|
|
|
|
]
|
|
|
|
{ #category : #initialization }
|
|
GrafoscopioNode >> becomeDefaultTree [
|
|
| node1 |
|
|
self level: 0.
|
|
self header: 'Arbol principal'.
|
|
node1 := GrafoscopioNode
|
|
header: 'Nodo 1'
|
|
body: 'Texto 1'.
|
|
|
|
self
|
|
addNode: node1.
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> body [
|
|
"Returns the receivers body"
|
|
|
|
^ body
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> body: anObject [
|
|
"Sets the receivers body to the given object"
|
|
|
|
body := anObject
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> children [
|
|
"Returns the receivers list of children"
|
|
|
|
^ children ifNil: [children := OrderedCollection new]
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> children: aCollection [
|
|
"Sets the receivers children"
|
|
|
|
aCollection do: [:currentNode | currentNode parent: self ].
|
|
children := aCollection.
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> content [
|
|
"Returns the receivers body"
|
|
|
|
^ body
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> content: anObject [
|
|
"Sets the receivers body to the given object"
|
|
|
|
body := anObject
|
|
]
|
|
|
|
{ #category : #movement }
|
|
GrafoscopioNode >> demote [
|
|
"Moves the current node down in the hierachy, making a children of its current previous slibing"
|
|
|
|
| collection index predecessor |
|
|
collection := self parent children.
|
|
index := collection indexOf: self.
|
|
(index between: 2 and: collection size)
|
|
ifTrue: [ predecessor := collection before: self.
|
|
collection remove: self.
|
|
predecessor addNode: self]
|
|
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> hasAncestorHeaderWith: aSpecialWord [
|
|
"Looks if the receptor node has an ancestor with a header with 'aSpecialWord' as the only or the first word"
|
|
|
|
^ (self ancestorsHeaders includes: aSpecialWord) | ((self ancestorsHeaders collect: [:eachHeader | (eachHeader findTokens: $ ) at: 1 ]) includes: aSpecialWord)
|
|
|
|
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> hasAncestorTaggedAs: aSpecialWord [
|
|
"Looks if the receptor node has an ancestor with a header with 'aSpecialWord' in its tags"
|
|
|
|
self ancestors detect: [:eachAncestor | eachAncestor tags = aSpecialWord ] ifFound: [^true ] ifNone: [^false ].
|
|
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> header [
|
|
"Returns the receiver header"
|
|
|
|
^ header
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> header: anObject [
|
|
"Sets the receivers header"
|
|
|
|
header := anObject
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> headers [
|
|
"Returns the headers of the receiver children"
|
|
|
|
^ headers := self children collect: [:currentNode | currentNode header ]
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> icon [
|
|
"Returns the receivers icon"
|
|
|
|
^icon
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> icon: aSymbol [
|
|
"Sets the receivers icon"
|
|
|
|
icon := aSymbol
|
|
]
|
|
|
|
{ #category : #initialization }
|
|
GrafoscopioNode >> initialize [
|
|
"Creates a empty new node"
|
|
|
|
super initialize.
|
|
self header: 'newHeader'.
|
|
self body: ''.
|
|
self key: ''
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> key [
|
|
"Returns a unique key identifying the receiver in the help system"
|
|
|
|
^key
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> key: aUniqueKey [
|
|
"Sets a unique key identifying the receiver in the help system"
|
|
|
|
key := aUniqueKey
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> level [
|
|
"Returns the level of the node. See the setter message for details"
|
|
|
|
^level
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> level: anInteger [
|
|
"Sets the node level in a hierarchy. The only node with level 0 is the root node and from there levels increase
|
|
in 1 for its direct children, 2 for its grand children and so on. Silibings nodes has the same level"
|
|
|
|
level := anInteger
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> markdownContent [
|
|
"Extracts the markdown of a node using body as content, header as title and level as hierarchical level of the title.
|
|
If special nodes types are present, converts them into proper markup to be embedded inside markdown"
|
|
| markdown configDict specialWords embedNodes temporalBody invisibleChildren |
|
|
markdown := '' writeStream.
|
|
specialWords := #('%config' '%abstract' '%invisible' '%idea' '%footnote' 'nuevoNodo' '%embed').
|
|
(specialWords includes: self header) not & (specialWords includes: ((self header findTokens: $ ) at: 1)) not & (self tags = 'código') not
|
|
ifTrue: [
|
|
self level timesRepeat: [ markdown nextPutAll: '#' ].
|
|
markdown nextPutAll: ' '.
|
|
markdown nextPutAll: (self header copyReplaceTokens: #cr with: #lf); crlf; crlf.
|
|
embedNodes := self children select: [:each | ((each header findTokens: $ ) at: 1) = '%embed'].
|
|
temporalBody := self body.
|
|
embedNodes ifNotNil: [
|
|
(temporalBody includesSubstring: '%embed-all')
|
|
ifFalse: [embedNodes do: [ :each | temporalBody := temporalBody copyReplaceAll: (each header) with: each body]]
|
|
ifTrue: [
|
|
embedNodes do:
|
|
[ :each | temporalBody := temporalBody copyReplaceAll: '%embed-all' with: (each body,
|
|
(String with: Character cr),
|
|
'%embed-all')].
|
|
temporalBody := temporalBody copyReplaceAll: '%embed-all' with: ''
|
|
]
|
|
].
|
|
markdown nextPutAll: (temporalBody contents withInternetLineEndings ); crlf; crlf].
|
|
|
|
(self header = '%config')
|
|
ifTrue: [
|
|
configDict := STON fromString: (self body).
|
|
markdown nextPutAll: '---'; lf.
|
|
markdown nextPutAll: 'title: ', (configDict at: 'title'); lf.
|
|
markdown nextPutAll: 'author: ', ((configDict at: 'author') at: 'given'), ' ', ((configDict at: 'author') at: 'family'); lf.
|
|
markdown nextPutAll: 'bibliography: ', (configDict at: 'bibliography'); lf.
|
|
markdown nextPutAll: 'abstract: ', '|'; lf; nextPutAll: (configDict at: 'abstract'); lf.
|
|
markdown nextPutAll: '---'; lf. ].
|
|
((self header findString: '%idea') = 1)
|
|
ifTrue: [
|
|
embedNodes := self children select: [:each | ((each header findTokens: $ ) at: 1) = '%embed'].
|
|
temporalBody := self body.
|
|
embedNodes ifNotNil: [ embedNodes do: [ :each | temporalBody := temporalBody copyReplaceAll: (each header) with: each body]].
|
|
markdown nextPutAll: (temporalBody contents withUnixLineEndings); lf; lf.
|
|
].
|
|
((self header findString: '%footnote') = 1)
|
|
ifTrue: [
|
|
markdown nextPutAll: ('[^',(self header copyReplaceAll: '%footnote ' with: ''),']: ' ); lf.
|
|
markdown nextPutAll: (self body contents withInternetLineEndings); lf; lf. ].
|
|
((self header findString: '%embed') = 1)
|
|
ifTrue: [ ].
|
|
(self tags = 'invisible')
|
|
ifTrue: [
|
|
invisibleChildren := self children.
|
|
invisibleChildren ifNotNil: [ ] ].
|
|
^markdown contents
|
|
]
|
|
|
|
{ #category : #movement }
|
|
GrafoscopioNode >> moveAfter [
|
|
"Moves the current node a place before in the children collection where is located"
|
|
| collection index successor |
|
|
collection := self parent children.
|
|
index := collection indexOf: self.
|
|
(index between: 1 and: collection size - 1)
|
|
ifTrue: [
|
|
successor := collection after: self.
|
|
collection at: index + 1 put: self.
|
|
collection at: index put: successor]
|
|
]
|
|
|
|
{ #category : #movement }
|
|
GrafoscopioNode >> moveBefore [
|
|
"Moves the current node a place before in the children collection where is located"
|
|
| collection index predecessor |
|
|
collection := self parent children.
|
|
index := collection indexOf: self.
|
|
(index between: 2 and: collection size)
|
|
ifTrue: [
|
|
predecessor := collection before: self.
|
|
collection at: index -1 put: self.
|
|
collection at: index put: predecessor]
|
|
]
|
|
|
|
{ #category : #'instance creation' }
|
|
GrafoscopioNode >> newNode [
|
|
node := Dictionary newFrom: {
|
|
#header -> 'newHeadline'.
|
|
#body -> ''.
|
|
#children -> #()}.
|
|
^ node.
|
|
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> parent [
|
|
"Returns the parent of the current node"
|
|
^ parent
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> parent: aNode [
|
|
"A parent is a node that has the current node in its children"
|
|
parent := aNode
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> preorderTraversal [
|
|
nodesInPreorder := OrderedCollection new.
|
|
self visitedGoTo: nodesInPreorder.
|
|
^ nodesInPreorder.
|
|
]
|
|
|
|
{ #category : #movement }
|
|
GrafoscopioNode >> promote [
|
|
"Moves the current node up in the hierachy, making it a slibing of its current parent"
|
|
| collection grandparent |
|
|
collection := self parent children.
|
|
grandparent := self parent parent.
|
|
collection isNotNil & grandparent isNotNil
|
|
ifTrue: [
|
|
(grandparent children) add: self after: (self parent).
|
|
self level: (self parent) level.
|
|
self parent: grandparent.
|
|
collection remove: self.]
|
|
|
|
|
|
]
|
|
|
|
{ #category : #'add/remove nodes' }
|
|
GrafoscopioNode >> removeLastNode [
|
|
"Adds the given node to the receivers collection of children, and sets this object as the parent
|
|
of the node"
|
|
|
|
self children removeLast.
|
|
|
|
]
|
|
|
|
{ #category : #'add/remove nodes' }
|
|
GrafoscopioNode >> removeNode: aNode [
|
|
"Adds the given node to the receivers collection of children, and sets this object as the parent
|
|
of the node"
|
|
|
|
self children remove: aNode.
|
|
|
|
]
|
|
|
|
{ #category : #exporting }
|
|
GrafoscopioNode >> returnConfig [
|
|
"Detects a children node containing the configuration for the creation of the output files. If nothing is detected,
|
|
creates defaults for that. Pending:
|
|
- Verifying that file data in config node is accurate, and if not create the proper locations.
|
|
- Maybe there is a need to consider if it should run on all the tree, instead of a particular node."
|
|
|
|
| configString configNode configDict |
|
|
|
|
configNode := (self children) detect: [:nodeContent | nodeContent header = 'Config' ] ifNone: [ nil ].
|
|
"This part always enter for the nil option! MUST BE CORRECTED"
|
|
configNode isNil
|
|
ifTrue: [
|
|
configString := '{
|
|
"title": "Boostrapping para el objeto de investigación",
|
|
"author": {
|
|
"given": "Offray Vladimir",
|
|
"family": "Luna Cárdenas"
|
|
},
|
|
"file": {
|
|
"relative-path" : "U/Libertadores/Grafoscopio/",
|
|
"name": "bootstrapping-objeto-investigacion",
|
|
"formats": [
|
|
#ston,
|
|
#markdown
|
|
]
|
|
}
|
|
}'
|
|
]
|
|
ifFalse: [ configString := configNode body ].
|
|
configDict := STON fromString: configString.
|
|
^configDict
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> tagAs: aTag [
|
|
"Tags the recipient node with aTag. For the moment we will have only one tag. In the future we will have several and there will be rules to
|
|
know if a tag excludes others from the same node"
|
|
|
|
tags := aTag.
|
|
aTag = 'código'
|
|
ifTrue: [self body: (GTPlayPage new content: self body) content]
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> tags [
|
|
"Returns the receiver tags. For the moment is just one... yes silly, but will be extenden properly"
|
|
|
|
^ tags
|
|
]
|
|
|
|
{ #category : #accessing }
|
|
GrafoscopioNode >> title [
|
|
"Returns the receiver header"
|
|
|
|
^ header
|
|
]
|
|
|
|
{ #category : #'add/remove nodes' }
|
|
GrafoscopioNode >> visitedGoTo: aCollection [
|
|
"Stores the current node in a collection and recursively stores its children"
|
|
|
|
aCollection add: self.
|
|
(self children isNotEmpty) & ((self header findString: '#invisible')=1) not
|
|
ifTrue: [ (self children) do: [ :eachNode | eachNode visitedGoTo: aCollection]].
|
|
|
|
|
|
]
|