diff --git a/.github/scripts/preLoading.st b/.github/scripts/preLoading.st index 519dffb6..641e54b2 100644 --- a/.github/scripts/preLoading.st +++ b/.github/scripts/preLoading.st @@ -1,11 +1,3 @@ -"IceRepository registry - detect: [ :each | #('Microdown' 'NewTools-DocumentationReader') includes: each name ] - ifFound: [ :aRepository | aRepository forget ]. -#( 'BaselineOfMicrodownDev' 'BaselineOfMicrodown' 'Microdown' 'Microdown-Tests' 'Microdown-MicrodownRichTextComposer' 'Microdown-ResolvePath' -'Microdown-ResolvePath-Tests' 'NewTools-DocumentationReader-Tests' 'BaselineOfNewToolsDocumentationReader' 'Microdown-RichTextComposer' 'Spec2-Microdown' -'NewTools-DocumentationReader' 'BaselineOfBeautifulComments' 'BeautifulComments') do: [ :each | - (RPackageOrganizer default packageNamed: each ifAbsent: [ nil ]) - ifNotNil: [ :aPackage | aPackage removeFromSystem ] ]" #( 'Microdown' 'BeautifulComments' 'DocumentBrowser' ) do: [ :name | (IceRepository repositoryNamed: name) diff --git a/doc/api.md b/doc/api.md index 8458c1a7..9db5338f 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1 +1,111 @@ -# Microdown API There is a lot of functionality in Microdown, and this document gives an overview of the Microdown architecture. - Architecture - key objects in Microdown - Microdown facade methods - Resource references and resolvers. Dealing with links, images, and included documents Several parts of Microdown allows for extending the Microdown tool suite. [Extension has is own documentation page](extension.md). The full suite of microdown tools are not loaded by default. In particular there is also support for - Translation of Microdown to Latex and Beamer - Translation of Microdown to HTML - Translation from Pillar to Microdown - Misc other tools ## Architecture The overall idea in Microdown is like this: ![](https://www.planttext.com/api/plantuml/png/VP1D2u9048Rl-oi6xq9q35c4EdHG2fJkYmwofBioEqj2zDzJAoNxcECy3xmlR-nO4Vkc5iATjMaLgGO82rQcgX6k0leZwqsvjMIGOBqIDo5c8qXrGRQq5nF0IvzWPZqL256KCMbJIGaBOMSBtw3XNia9KSe5px4RsC5pw_c39egn-uttUPeiwRDH6CevUmD7HGvf5ARle8pn6pXffzb-uOy2VuInmZiVvelHbFtcTm00) $$%comment You can edit the figure using this source on planttext.com @startuml skinparam rectangle { roundCorner 20 } rectangle "Microdown" { rectangle Source <> rectangle Document <> rectangle Text <> rectangle Latex <> rectangle HTML <> Source --> Document : Parser Document --> Text : Visitor Document --> Latex : Visitor Document --> HTML : Visitor } @enduml $$ The format of the source code is explained in the [section on syntax](syntax.md). The parser translates the source code into an abstract syntax tree which we refer to as a "document". Translation into various output formats are done using visitors. By default only the visitor for translation into pharo `Text` is in the image. Sometimes `Text` is refered to as `RichText` to emphasize its difference from plain character `String`. ## Microdown facade ### Parse and output generation The class `Microdown` has facade methods for the main actions in the architecture. - `Microdown class >> #parse:` translates a microdown source string into a document. - `Microdown class >> #asRichText:` translates a document into rich text. `asRichText:` can also accept a source string directly, cirumventing the `parse:` method. There are two facade methods for _resolution_. - `Microdown class >> #resolveDocument:withBase:` and - `Microdown class >> #resolveString:withBase:` The will be explained in the next section ## Resolution and references References to other documents or images can be either _absolute_ or _relative_. A typical absolute reference is `[Pharo](https:pharo.org)` or `![](file:/path/to/image.png)`. Absolute references cause no problems for the visitors to find the image or follow the link. However, when writing larger documents it is customary to keep images for the document in a seperate image folder, and refer to the images as `![](img/figure2.png)`. Such a reference is _relative_. It is relative to to location of the document containing the reference. **Resolution** is the process of replacing all relative references with absolute references. This process takes two inputs: - A document D containing relative references - The absolute location of document D (the base of the document). The two methods - `Microdown class >> #resolveDocument:withBase:` and - `Microdown class >> #resolveString:withBase:` are the prefered way to resolve documents. ### Resource references If we go one level below this, references are first order objects, all subclasses of `MicResourceReference`. In the standard image there are three kinds of absolute references: - http (and https) - for example: 'https://pharo.org/web/files/pharo.png' - file - for example 'file:/user/joe/docs/api.md' - the pharo image - for example 'pharo:///Object/iconNamed:/thumbsUp' (![](pharo:///Object/iconNamed:/thumbsUp)) #### Creating references The prefered way is to use the extension method `asMicResourceReference`, which is defined for - String - for example `'file:/user/joe/docs/api.md' asMicResourceReference` - ZnUrl - for example `(ZnUri fromString: 'https://pharo.org/web/files/pharo.png') asMicResourceReference` - FileReference - for example `(FileSystem memory) asMicResourceReference` #### Using references There are two main key methods on references: - `loadImage` - returns a `Form` by reading the image (read using `ImageReadWriter class >> #formFromStream:`) - `loadMicrodown` - loads _and resolves_ a document #### Background The resolution is done using `ZnUrl >> #withRelativeReference:`, which implements the full resolution standard [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). ##### Ambiguity of file uri In pharo there is an issue that uri ' file://path/to/my/document.md' is ambiguous, because it can not be determined in which file system the path is supposed to be resolved (memory or disk). This problem is dealt with, in the following manner: - `' file://path/to/my/document.md' asMicResourceReference` will always use the disk file system - `aFileReference asMicResourceReference` will create a `MicFileResourceReference` which remembers the file system in the file reference. - `aMicFileResourceReference loadDocument` will use the right filesystem for resolution. \ No newline at end of file +# Microdown API + +There is a lot of functionality in Microdown, and this document gives an overview of the Microdown architecture. + +- Architecture - key objects in Microdown. +- Microdown facade methods. +- Resource references and resolvers. Dealing with links, images, and included documents. +- Document checkers: Microdown provides checkers that validate documents. + +Several parts of Microdown allows for extending the Microdown tool suite. Chapter *@[Extension](extension.md)@* presents the mechanisms in place. + +The full suite of microdown tools are not loaded by default. In particular there is also support for: +- Translation of Microdown to Latex and Beamer, +- Translation of Microdown to HTML, +- Translation from Pillar to Microdown, and +- Document checkers. + +## Architecture +The overall idea in Microdown is like this: + +![](https://www.planttext.com/api/plantuml/png/VP1D2u9048Rl-oi6xq9q35c4EdHG2fJkYmwofBioEqj2zDzJAoNxcECy3xmlR-nO4Vkc5iATjMaLgGO82rQcgX6k0leZwqsvjMIGOBqIDo5c8qXrGRQq5nF0IvzWPZqL256KCMbJIGaBOMSBtw3XNia9KSe5px4RsC5pw_c39egn-uttUPeiwRDH6CevUmD7HGvf5ARle8pn6pXffzb-uOy2VuInmZiVvelHbFtcTm00) + +You can edit the figure using this source on planttext.com +``` +@startuml +skinparam rectangle { + roundCorner 20 +} +rectangle "Microdown" { + rectangle Source <> + rectangle Document <> + rectangle Text <> + rectangle Latex <> + rectangle HTML <> + Source --> Document : Parser + Document --> Text : Visitor + Document --> Latex : Visitor + Document --> HTML : Visitor +} +@enduml +``` + +The format of the source code is explained in the Chapter *@[Syntax](syntax.md)@*. + +The parser translates the source code into an abstract syntax tree which we refer to as a "document". Translation into various output formats are done using visitors. By default only the visitor for translation into Pharo `Text` objects is in the image. Sometimes `Text` is refered to as `RichText` to emphasize its difference from plain character `String`. + + +## Microdown facade +### Parse and output generation +The class `Microdown` has facade methods for the main actions in the architecture. + +- `Microdown class >> #parse:` translates a microdown source string into a document. +- `Microdown class >> #asRichText:` translates a document into rich text. `asRichText:` can also accept a source string directly, cirumventing the `parse:` method. + +There are two facade methods for _resolution_. +- `Microdown class >> #resolveDocument:withBase:` and +- `Microdown class >> #resolveString:withBase:` + +The will be explained in the next section. + +## Resolution and references + +References to other documents or images can be either _absolute_ or _relative_. + +#### Absolute vs. relative references. + +A typical absolute reference is `[Pharo](https:pharo.org)` or `![](file:/path/to/image.png)`. Absolute references cause no problems for the visitors to find the image or follow the link. +However, when writing larger documents it is customary to keep images for the document in a seperate image folder, and refer to the images as `![](img/figure2.png)`. Such a reference is _relative_. It is relative to to location of the document containing the reference. + +**Resolution** is the process of replacing all relative references with absolute references. This process takes two inputs: +- A document D containing relative references and +- The absolute location of document D (the base of the document). + +The two methods +- `Microdown class >> #resolveDocument:withBase:` and +- `Microdown class >> #resolveString:withBase:` +are the prefered way to resolve documents. + +### Resource references + +If we go one level below this, references are first order objects, all subclasses of `MicResourceReference`. + +In the standard image there are three kinds of absolute references: +- http (and https) - for example: 'https://pharo.org/web/files/pharo.png' +- file - for example 'file:/user/joe/docs/api.md' +- the pharo image - for example 'pharo:///Object/iconNamed:/thumbsUp' `(![](pharo:///Object/iconNamed:/thumbsUp))` +- +#### Creating references + +The prefered way is to use the extension method `asMicResourceReference`, which is defined for +- String - for example `'file:/user/joe/docs/api.md' asMicResourceReference` +- ZnUrl - for example `(ZnUri fromString: 'https://pharo.org/web/files/pharo.png') asMicResourceReference` +- FileReference - for example `(FileSystem memory) asMicResourceReference` + +#### Using references + +There are two main key methods on references: +- `loadImage` - returns a `Form` by reading the image (read using `ImageReadWriter class >> #formFromStream:`) +- `loadMicrodown` - loads _and resolves_ a document + +#### Background + +The resolution is done using `ZnUrl >> #withRelativeReference:`, which implements the full resolution standard [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). + +##### Ambiguity of file uri + +In pharo there is an issue that uri ' file://path/to/my/document.md' is ambiguous, because it can not be determined in which file system the path is supposed to be resolved (memory or disk). This problem is dealt with, in the following manner: +- `' file://path/to/my/document.md' asMicResourceReference` will always use the disk file system +- `aFileReference asMicResourceReference` will create a `MicFileResourceReference` which remembers the file system in the file reference. +- `aMicFileResourceReference loadDocument` will use the right filesystem for resolution. + diff --git a/doc/extension.md b/doc/extension.md index b3caacb7..f1f427e9 100644 --- a/doc/extension.md +++ b/doc/extension.md @@ -1 +1,84 @@ -# Extension mechanisms Microdown is inspired by github markdown. Unlike github markdown which is only intended to generate html, microdown is used also for in-image rendering, latex generation (books and slides for example), but also html as github. While the core of microdown and markdown is the same, microdown has 3 extension mechanisms for paragraph level blocks, and one inline extension mechanism. ## Inline extension The inline extension mechanism is `MicAnnotationBlock`. As its name indicates, its intended purpose is to provide meta data to the document. Its syntax is `{!annotationName|arg1=value1&arg2=value2&...!}`. ### Mechanism To make an annotation type, a number of steps must be done. Here we will go through the steps of implementing an annotation for providing giving a footnote in the form: `{!footnote|note=It is cool to be cool!}`. The class is implemented in the package `Microdown` under tag `Extensions` as `MicFootnoteBlock`. - Subclass of `MicAnnotationBlock`. For footnote to be recognised as an annotation, it must be a subclass of `MicAnnotationBlock`. - class side method `tag`. To find the class implementing 'footnote', `MicAnnotationBlock` will look through its subclasses for the class whoes tag method returns 'footnote'. - instance side method `accept:` - this is part of the visitor framework. As in this case, it introduce an extension specific visit method (`visitFootnote:`). - footnote specific functionality. The footnote can be given a 'note' argument. Its implementation of a `note` accessor is good practice. ### Connecting the appropriate visitor To actually give the extension meaning, it must be implemented in one or more visitors. One implementation of foodnotes can be seen in `MicRichTextComposer>>#visitFootnote:`. It pulls the note from the footnote block, and makes clickable {!footnote|note=Keep morphic balloons alive :-)!} # Block extensions Microdown has three paragraph extension points - MicCodeBlock `\`\`\`` which one can extend with custom **stylers**. That is, the intended usage is that the body of the code block is styled (typically coloured), and the body is not treated as microdown (is not parsed). - MicEnvironmentBlock `` is intended for making custom markdown by subclassing MicEnvironmentBlock, tagging and MicArgumentList to concert the instantiation of the extension. The body of this block is treated as microdown and is parsed. - MicMathBlock (was recently changed from `&...&` to `$$...$$` to stay compatible with github markdown). Math block has been designed to do latex math, hence its name. However, is an extension point as latex is merely the default. The body of math block is not intended to be parsed by microdown but latex. The output is not intended to be just coloured, but completely transformed (for Latex into an image of the formula). ## Mechanism The mechanism for extending an Environment or the MathBlock (to do something else than Math) is pretty much the same as for annotations. - Make a subclass of `MicEnvironmentBlock` or `MicMathBlock`. - implement the tag on the class side - implememnt necessary accessors to help implement the visitors - implement `accept:` to interface to the visitor (calling a `visitMyNewExtension:`) - implement `visitMyNewExtension:`. ### Example: support for source code extraction `MicSourceMethodBlock` allows source code to be inserted into microdown using an extension of the form: ```text ``` Yielding: The key implementation overrides a few methods. `MicSourceMethodBloc>>#closeMe` is intendended is called by the parser to provide a hook to set up instance variables and other fields after the block has been parsed. The super closeMe sets up the arguments, so it should always go first. Next we simply build the codeblock, parses it, and make the codeblock be the children of this MicSourceMethodBlock. There is actually only one child, the codeblock. The `accept:` method specify that visiting should be done by visiting the children (the code block). ### Example: support for PlantUml diagrams [PlantUmlMicrodownExtension](https://github.com/kasperosterbye/PlantUmlMicrodownExtension) is a simple example of extending MicMathBlock to render [plantuml](https://plantuml.com) diagrams inlined in Microdown. It uses the tag plantUml: ```Method tag ^#plantuml ``` and the accept: just tells to `visitPlantUML:`. In addition it defines `visitPlantUML:` as an extension method on `MicRichTextComposer` ```Method visitPlantUML: plantUMLNode | aString diagramRef anchoredImage | aString := plantUMLNode body. aString ifEmpty: [ ^self ]. diagramRef := (PlantUMLBridge pngFromSource: aString) asMicResourceReference. anchoredImage := diagramRef loadImage. canvas << ((String value: 1) asText addAttribute: (TextAnchor new anchoredMorph: anchoredImage)) ``` The `body` of an extension of `MicMathBlock` is all the text between the two `$$`, which in this case is assumed to be a plantUML script. The visit method is rather similar to the standard implementation of Math/Latex support. \ No newline at end of file +## About extension mechanisms + +Microdown is inspired by GitHub markdown. Unlike GitHub markdown which is only intended to generate html, microdown is used also for in-image rendering, latex generation (books and slides for example), but also html as GitHub. While the core of Microdown and Markdown is the same, Microdown has 3 extension mechanisms for paragraph level blocks, and one inline extension mechanism. + +### Inline extension + +The inline extension mechanism is `MicAnnotationBlock`. As its name indicates, its intended purpose is to provide new node. Its syntax is `{!annotationName|arg1=value1&arg2=value2&...!}`. + +#### Mechanism +To make an annotation type, a number of steps must be done. Here we will go through the steps of implementing an annotation for providing giving a footnote in the form: `{!footnote|note=It is cool to be cool!}`. The class is implemented in the package `Microdown` under tag `Extensions` as `MicFootnoteBlock`. + +- Subclass of `MicAnnotationBlock`. For footnote to be recognised as an annotation, it must be a subclass of `MicAnnotationBlock`. +- class side method `tag`. To find the class implementing 'footnote', `MicAnnotationBlock` will look through its subclasses for the class whoes tag method returns 'footnote'. +- class side method `key` should return the default key of the extension. This way the writer can omit it. +- instance side method `accept:` - this is part of the visitor framework. As in this case, it introduces an extension specific visit method (`visitFootnote:`). +- footnote specific functionality. The footnote can be given a 'note' argument. Its implementation of a `note` accessor is good practice. + +Note that recent versions of Microdown support optional key, therefore `{!footnote|note=This is cool!}` is equivalent to `{!footnote|This is cool!}`. + +### Connecting the appropriate visitor +To actually give the extension meaning, it must be implemented in one or more visitors. One implementation of foodnotes can be seen in `MicRichTextComposer>>#visitFootnote:`. It pulls the note from the footnote block, and makes clickable {!footnote|note=Keep morphic balloons alive :-)!} + + +### Block extensions + +Microdown has three paragraph extension points +- MicCodeBlock `\`\`\`` which one can extend with custom **stylers**. That is, the intended usage is that the body of the code block is styled (typically colored), and the body is not treated as Microdown (is not parsed). +- MicEnvironmentBlock `` is intended for making custom markdown by subclassing MicEnvironmentBlock, tagging and MicArgumentList to concert the instantiation of the extension. The body of this block is treated as Microdown and is parsed. +- MicMathBlock (was recently changed from `&...&` to `$$...$$` to stay compatible with github markdown). Math block has been designed to do latex math, hence its name. However, is an extension point as latex is merely the default. The body of math block is not intended to be parsed by microdown but latex. The output is not intended to be just coloured, but completely transformed (for Latex into an image of the formula). + +### Mechanism +The mechanism for extending an Environment or the MathBlock (to do something else than Math) is pretty much the same as for annotations. +- Make a subclass of `MicEnvironmentBlock` or `MicMathBlock`. +- Implement the tag on the class side +- Implememnt necessary accessors to help implement the visitors +- Implement `accept:` to interface to the visitor (calling a `visitMyNewExtension:`) +- Implement `visitMyNewExtension:`. + +### Example: support for source code extraction + +`MicSourceMethodBlock` allows source code to be inserted into Microdown using an extension of the form: +```text + +``` +Yielding: + + +The key implementation overrides a few methods. `MicSourceMethodBloc>>#closeMe` is called by the parser to provide a hook to set up instance variables and other fields after the block has been parsed. + + +The super `closeMe` sets up the arguments, so it should always go first. Next we simply build the codeblock, parses it, and make the codeblock be the children of this MicSourceMethodBlock. There is actually only one child, the codeblock. + +The `accept:` method specifies that visiting should be done by visiting the children (the code block). + + + +### Example: support for PlantUml diagrams + +[PlantUmlMicrodownExtension](https://github.com/kasperosterbye/PlantUmlMicrodownExtension) is a simple example of extending MicMathBlock to render [plantuml](https://plantuml.com) diagrams inlined in Microdown. + +It uses the tag plantUml: +```Method +tag + ^#plantuml +``` + +and the accept: just tells to `visitPlantUML:`. + +In addition it defines `visitPlantUML:` as an extension method on `MicRichTextComposer` + +```Method +visitPlantUML: plantUMLNode + + | aString diagramRef anchoredImage | + aString := plantUMLNode body. + aString ifEmpty: [ ^self ]. + diagramRef := (PlantUMLBridge pngFromSource: aString) asMicResourceReference. + anchoredImage := diagramRef loadImage. + canvas << ((String value: 1) asText + addAttribute: (TextAnchor new anchoredMorph: anchoredImage)) +``` + +The `body` of an extension of `MicMathBlock` is all the text between the two `$$`, which in this case is assumed to be a plantUML script. The visit method is rather similar to the standard implementation of Math/Latex support. + diff --git a/doc/readme.md b/doc/readme.md index a0e5247c..dbd0dc12 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -1,3 +1,33 @@ -# Microdown +## Microdown -Microdown is the Pharo version of markdown. In general it is very close to github markdown, with two major differences and a number of smaller ones. Principal differences: - Microdown has a number of extension points to allow new functionality to be added without introducing new markdown syntax. In paticular Microdown has support for Latex, Pharo defined color highlighting of embedded code, inclusion of one document into an other - Microdown does not allow embedded html, as microdown is not restricted to being translated into html ## Syntax [This document](syntax.md) defines the syntax of Microdown [This document](extension.md) defines the extension mechanisms of Microdown ## API An important aspect of Microdown is its API, which allow you to work with Microdown from within Pharo. [This document](api.md) explaines the overall architecture of Microdown and the key methods.thods. \ No newline at end of file +Microdown is the Pharo version of markdown. In general, it is very close to the GitHub markdown, with two major differences and a number of smaller ones. +Now pay really attention that Microdown is used by the Pillar compilation chain to produce books in PDF, HTML,..., presentations, websites, class comments in Phar. Microdown is not limited to Github rendering. +Now it tries its best to achieve a good compromise between rendering on Github, and expressiveness in the other cases. + +### Principal differences +Microdown supports metadata for figures, equations, ... It allows one to have anchors. +It supports by default cross-referencing. + +Microdown has a number of extension points to allow new functionality to be added without introducing new markdown syntax. These extensions can be the level of main block such as environments or as inline element (such as footnote or citation). + +In particular, Microdown has support for LaTeX, Pharo-defined color highlighting of embedded code, inclusion of one document into another. + +### Syntax +*@[The Syntax Chapter](syntax.md)@* defines the syntax of Microdown. +*@[This Extension Chapter](extension.md)@* defines the extension mechanisms of Microdown. + + +### Available checkers +Based on this Microdown offers document checkers as shown in Chapter *@[Validation](validation.md)@*. +By default a checker will report: +- Duplicated anchors, +- References to unknown anchors, +- Unreferenced figures, +- Figures referencing unknown files, +- References to unknown included file, +- Invalid code snippets (acting as tests for code samples), +- Copied method diverging from their source. + +## API +An important aspect of Microdown is its API, which allows you to work with Microdown from within Pharo. +*@[The Chapter API](api.md)@* explains the overall architecture of Microdown and the key methods. \ No newline at end of file diff --git a/doc/syntax.md b/doc/syntax.md index 7f9c7cdb..e6af4eec 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -1,2 +1,285 @@ -# Microdown Syntax -The syntax covers two distict categories - inline and paragraphs. - Inline commands cover things like emphasis and links. - Paragraphs are commands that span several lines of text (lists, code examples, etc). ## Inline syntax ### Text emphasis - **bold** is done by `**bold**` (You can get \** by `\\**`) - _italics_ is done by `_italics_` (should be underline, but now it stuck, there is no underline) - ~strike~ is done by `~strike~` - `inline code` is done by \`inline code\` ### Links and Figure - [Link](https://Pharo.org) is done as `[Link](https://Pharo.org)`. The produced link will open in the standard browser (Pharo `WebBrowser class >> #openOn:`) - ![alt text](https://pharo.org/web/files/pharo-logo-small.png) is done using ` ![alt text](https://pharo.org/web/files/pharo-logo-small.png) `. Often larger figures are done by placing using this syntax on a line by itself ### Microdown specials - $x^2$ - inline $\LaTeX$ is done using `$x^2$` (and `$LaTeX$`). - {{**bold**}} can be done also using raw ### Microdown inline extensions There is potentially an endless number of extensions one would like to add to Microdown. Rather than keep inventing new syntax, the generic syntax for inline syntax is: `{!extensionName|parameter1=value1¶meter2=value2¶meter3=value3!}`. What the extension does will typically depend on the vistor ($\LaTeX$ generation, Text generation, HTML generation etc) Some extensions are already defined in the Microdown library: - **inputFile|path=uri** - insert the contents of the microdown document at uri at this place. - **footnote|note=some note which goes to the foot** - adds a footnote the the generated document - **citation|ref=somewhere so others can find it** - adds a reference to the generated document ## Microdown Paragraphs ### Headers Headers are done by lines starting with `#`. One `#` is the largest header, six `######` is the smallest header. The header text is written after the `#` signs.` ##### This is a header level 5, done as `##### This is a header level 5` ### Comments It is possible to put comments in the Microdown source. Lines starting with `%` are creates a comment paragraph, but most tools ignore them. % They really are ### Quote blocks > Lines starting with `>` are supposed to be rendered in an indented manner > At present the in-image rendering leaves something to be desired > But it works for now Each source line which is part of the quote block need to start with a `>` ### Unordered lists Unordered list are made by prefixing a line with '-' or `*`. * item 1 - item 2 * It is possible to make a long item with several lines of text by indenting the following lines by 2 spaces (two!, not three or one or tab) #### Indented lists * Item 1 * by prefixing the `-` or `*` by two spaces, one indent the list by one level * Item 1.1.1 * Item 1.2 * Againg, the indentation is two spaces! * If you indent by say 4 spaces instead of two, it is just seen as a new line in the preceding bullet point ### Ordered lists Ordered lists are done by prefixing a line with a digit followed by `.` or `)`. 2. First item 4) The numbers need not be in order, the rendering will start the numbering from one and increment ### Mixed lists It is possible to mix the two types of lists 1. This is first item, made using `1. This is first item` - sub item * Indenting sublist is tricky, but the bullet (`*` or '-') must be matched up under the first letter in the line `This is the ...` In this case 3 spaces of indentation `1._` 1. Next numbered item ### Horizontal lines *** Are created by a single line starting with at least three `*` - this works: `***` as well as `*************` ************* ### Code blocks To show code one use a tripple backquote ```text ```pharo Metacello new repository: 'github://svenvc/zinc/repository'; baseline: 'ZincHTTPComponents'; load. ``` ``` produces: ```pharo Metacello new repository: 'github://svenvc/zinc/repository'; baseline: 'ZincHTTPComponents'; load. ``` #### Predefind language modifiers The `pharo` modifier used above is good for pharo expressions. Often one will show a full method, and should use `method` to get a better highlighting and coloring of the method header ```text ```method accept: aVisitor ^ aVisitor visitCode: self ``` ``` produces: ```method accept: aVisitor ^ aVisitor visitCode: self ``` Plain unformatted text us made by using the `text` modifier, which will just print the body as is. ```text ```text accept: aVisitor ^ aVisitor visitCode: self ``` ``` produces: ```text accept: aVisitor ^ aVisitor visitCode: self ``` ### Tables It is possible to write tables in markup. These tables are intended to be short and not too wide. The cells of the tables can only be markedup with inline markup. ```text | Header1 | Header2 | Header 3 | | --- | ---- | --- | | cell 1 1 | cell 1 2 | cell 1 3 | | cell 2 1 | cell 2 2 | cell 2 3 | ``` produces: | Header1 | Header2 | Header 3 | | --- | ---- | --- | | cell 1 1 | cell 1 2 | cell 1 3 | | cell 2 1 | cell 2 2 | cell 2 3 | **Notice**, unlike some other markdowns, Microdown requires lines to start with `|`. There has to be at least the `---` in the line after the header. Leaving out the second line merely produce a table without header highlighting. | Header1 | Header2 | Header 3 | | cell 1 1 | cell 1 2 | cell 1 3 | | cell 2 1 | cell 2 2 | cell 2 3 | \ No newline at end of file +# Microdown syntax +The syntax covers two distinct categories - inline and paragraphs. +- Inline commands cover things like emphasis and links. +- Paragraphs are commands that span several lines of text (lists, code examples, etc). We often named them Bloc-level elements. + + +## Basic inline syntax +The inline syntax is about elements inside paragraphs. + +### Text emphasis +- **bold** is done by `**bold**` (You can get \** by `\\**`) +- _italics_ is done by `_italics_` (should be underline, but now it stuck, there is no underline) +- ~strike~ is done by `~strike~` +- `inline code` is done by \`inline code\` + +### Links and Figure +- [Link](https://Pharo.org) is done as ` [Link](https://Pharo.org) `. The produced link will open in the standard browser (Pharo `WebBrowser class >> #openOn:`) +- ![alt text](https://pharo.org/web/files/pharo-logo-small.png) is done using ` ![alt text](https://pharo.org/web/files/pharo-logo-small.png) `. Often larger figures are done by placing using this syntax on a line by itself + + + +## Key systematic features +Microdown offers anchors and references. Most of anchors are expressed using bloc parameters. +So we present this basic feature first then show its usage. + +### Bloc parameters + +Microdown has a generic way to manage arguments of elements. The syntax is `tag|key1=value1&key2=value2`. + +### Anchors and References +Microdown supports different anchors: at the heading level, math equations, and figures. + +#### Heading anchors +`@anAnchor` starting a new line defines the anchor `anAnchor`. This type of anchor is a bloc-level element. + +#### Figures + +``` +![This is a caption. %width=50&anchor=aFigAnchor](testMicrodown/toplevel.png) +``` +![This is a caption. %width=50&anchor=aFigAnchor](testMicrodown/toplevel.png) + +Any anchor can be referred to using an anchor reference following this syntax: `*@anAnchor@*`. +For example we refer to Figure *@aFigAnchor@*. + +#### Equations + +Using the same mechanism you can define an anchor of an equation. +If the LaTeX style supports caption for equations (this is not the default in LaTeX), you can also define caption using the parameters (i.e., `&caption=A caption.`). + +``` +$$ %anchor=frac +\frac{1}{1+2} +$$ +``` + +### Microdown Math + +Microdown supports both inline math using `$` and equation `$$` + +`$x^2$` - inline LaTeX is done using `$x^2$`. + +``` +$$ %anchor=frac +\frac{1}{1+2} +$$ +``` + +produces a fraction. Notice the use of `%anchor=frac` to define metadata. + +$$ +\frac{1}{1+2} +$$ + + +### Raw + +Microdown also supports the possibility to express raw text using `{{{` and `}}}`. + +``` +{{{**bold**}}} +``` + + +## Microdown Bloc level elements a.k.a Paragraphs + +### Headers +Headers are done by lines starting with `#`. One `#` is the largest header, six `######` is the smallest header. The header text is written after the `#` signs.` + +##### This is a header level 5, done as `##### This is a header level 5` + +### Comments + +It is possible to put comments in the Microdown source. Lines starting with `%` are creates a comment paragraph, but most tools ignore them. + +``` +% They really are ignored +``` + +### File level Metadata + +Microdown uses JSON to handle file-header metadata as shown after: + +``` +{ +"author" : "Stéphane Ducasse", +"title" : "a cool documentation" +} +``` + + +### Quote blocks + +> Lines starting with `>` are supposed to be rendered in an indented manner +> At present the in-image rendering leaves something to be desired +> But it works for now +Each source line which is part of the quote block need to start with a `>` + +``` +> this is a quote block +> spawning on multiple lines +``` + + +### Annotated paragraphs + +Microdown supports annotated paragraphs using `>[! as follows: + +``` +>[! Important] +> Microdown is extensible +``` + +>[! Important] +> Microdown is extensible + + +### Raw HTML paragraphs + +With recent versions of Microdown you can embedded top level HTML blocks. + +``` +
+kjlkjljlkj +hkjhkjh +<\address> +``` + + +### Unordered lists +Unordered list are made by prefixing a line with '-' or `*`. +* item 1 +- item 2 +* It is possible to make a long item + with several lines of text by indenting the following lines + by 2 spaces (two!, not three or one or tab) + +#### Indented lists +* Item 1 + * by prefixing the `-` or `*` by two spaces, one indent the list by one level + * Item 1.1.1 + * Item 1.2 +* Againg, the indentation is two spaces! + * If you indent by say 4 spaces instead of two, it is just seen as a new line in the preceding bullet point + +### Ordered lists +Ordered lists are done by prefixing a line with a digit followed by `.` or `)`. +2. First item +4) The numbers need not be in order, the rendering will start the numbering from one and increment + +### Mixed lists +It is possible to mix the two types of lists +1. This is first item, made using `1. This is first item` + - sub item + * Indenting sublist is tricky, but the bullet (`*` or '-') must be matched up under the first letter in the line `This is the ...` + In this case 3 spaces of indentation `1._` +1. Next numbered item + +### Horizontal lines +*** +Are created by a single line starting with at least three `*` - this works: `***` as well as `*************` +************* + +### Code blocks +To show code one use a triple backquote + +```text + ```pharo + Metacello new + repository: 'github://svenvc/zinc/repository'; + baseline: 'ZincHTTPComponents'; + load. + ``` +``` + +produces: + +```pharo + Metacello new + repository: 'github://svenvc/zinc/repository'; + baseline: 'ZincHTTPComponents'; + load. +``` + +#### Predefind language modifiers + +The `pharo` modifier used above is good for pharo expressions. Often one will show a full method, and should use `method` to get a better highlighting and coloring of the method header. + +```text + ```method + accept: aVisitor + ^ aVisitor visitCode: self + ``` +``` + +produces: + +```method +accept: aVisitor + ^ aVisitor visitCode: self +``` + +Plain unformatted text is made by using the `text` modifier, which will just print the body as is. + +```text + ```text + accept: aVisitor + ^ aVisitor visitCode: self + ``` +``` +produces: + +```text +accept: aVisitor + ^ aVisitor visitCode: self +``` + +### Tables + +It is possible to write tables in markup. These tables are intended to be short and not too wide. The cells of the tables can only be marked up with inline markup. +```text +| Header1 | Header2 | Header 3 | +| --- | ---- | --- | +| cell 1 1 | cell 1 2 | cell 1 3 | +| cell 2 1 | cell 2 2 | cell 2 3 | +``` +produces: +| Header1 | Header2 | Header 3 | +| --- | ---- | --- | +| cell 1 1 | cell 1 2 | cell 1 3 | +| cell 2 1 | cell 2 2 | cell 2 3 | + +**Notice**, unlike some other markdowns, Microdown requires lines to start with `|`. +There has to be at least the `---` in the line after the header. +Leaving out the second line merely produces a table without header highlighting. + +| Header1 | Header2 | Header 3 | +| cell 1 1 | cell 1 2 | cell 1 3 | +| cell 2 1 | cell 2 2 | cell 2 3 | + +## Extensions + +### Microdown inline extensions + +There is potentially an endless number of extensions one would like to add to Microdown. Rather than keep inventing new syntax, the generic syntax for inline syntax is: +`{!extensionName|parameter1=value1¶meter2=value2¶meter3=value3!}`. What the extension does will typically depend on the visitor (LaTeX generation, Text generation, HTML generation etc) + +Some extensions are already defined in the Microdown library: +- **footnote|note=some note which goes to the foot** - adds a footnote the the generated document +- **citation|ref=somewhere so others can find it** - adds a reference to the generated document + +Note that each extension has the possibility to define a default first key. +So `{!citation|ref=Duca99a}` can be expressed as `{!citation|Duca99a}`. + +### Microdown paragraph extensions + +Microdown offers a generic way to create new bloc-level elements. These can also be nested +and are close to LaTeX environments. +There are defined using `` + +The most common use is the inputFile one: `` inserts the contents of the microdown document at uri at this place. + +## Possible changes in future versions + +We are about to revise the syntax of the extension because it conflicts with the introduction of raw paragraphs. We are also considering adding element level meta data as in Kramdown. This should let users define HTML class at the node level. diff --git a/doc/tools.md b/doc/tools.md new file mode 100644 index 00000000..ee0469f4 --- /dev/null +++ b/doc/tools.md @@ -0,0 +1,74 @@ +# Microdown tools: Checkers + +Microdown proposes several tools to help document writers. In this chapter, we present the one related to +book validation and the available checkers. + +Three kinds exist +- Reference. The checker checks for anchors and references. +- Code validation. The checker checks whether code snippets are correctly executed. +- Version validation. The checker checks whether the code snippet is the same as in Pharo. + +Note that other tools are under development such as the help to hide method body automatically so that writers can write manuals where readers should provide answers. + +### References + +Microdown via the Pillar compilation chain offers the possibility to report: + +- Duplicated anchors. +- References to unexisting anchors +- Unreferenced figures +- Missing figure files +- Missing input files + +Note that such verification takes into account section, figure, and equation anchors. + +Here is an extract of a report produced by the checker. + +``` +pillar referenceCheck index.md +``` + +``` +File index.md has reference problems. + +## Undefined input files: + + Undefined file input: booklet-PharoVirtualMachine/Part2-JIT/CallingConventions/CallingConventions.md (in file /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/index.md) refer to file that does not exist. + + +## Duplicated Anchors: + + Anchor sec:references is duplicated in file: Booklet-PharoVirtualMachine/Part0-Preamble/1-ObjectStructure/objectStructure.md + Anchor sec:references is duplicated in file: PharoBooks2/Booklet-PharoVirtualMachine/Part0-Preamble/1-ObjectStructure/objectStructure.md + + +## Undefined Anchors: + + Anchor Ephemerons is undefined in file: /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/Part3-MemoryManagement/GarbageCollector/ephemerons.md + ... + +## Unreferenced Figures + + Figure 'figures/interpreter_variables.pdf' with anchor 'interpreterVariables' (in file /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/Part1-InterpreterAndBytecode/4-Interpreter/theInterpreter.md) is not referenced. + ... +``` + +### Code snippet validation + +Microdown supports the automatic validation of code snippets. +The following snippets illustrate the points. + +Here the writer wants to make sure that the expression 3 + 4 returns the correct result 7. He tags the code and use the `>>>` on a separate line. + +``` + ```example=true 3 + 4 >>> 7 ``` +``` + +He can also declare that a given expression is expected to give a failure + +``` + ```example=true&expectedFailure=true 3 + 4 >>> 12 + ``` ``` + + + \ No newline at end of file diff --git a/doc/validation.md b/doc/validation.md new file mode 100644 index 00000000..73118f8c --- /dev/null +++ b/doc/validation.md @@ -0,0 +1,122 @@ +## Microdown tools: Checkers + +Microdown proposes several tools to help document writers. In this chapter, we present the one related to +book validation and the available checkers. + +Three kinds exist +- Reference. The checker checks for anchors and references. +- Code validation. The checker checks whether code snippets are correctly executed. +- Version validation. The checker checks whether the code snippet is the same as in Pharo. + +Note that other tools are under development such as the help to hide method body automatically so that writers can write manuals where readers should provide answers. + +### References + +Microdown via the Pillar compilation chain offers the possibility to report: + +- Duplicated anchors. +- References to unexisting anchors +- Unreferenced figures +- Missing figure files +- Missing input files + +Note that such verification takes into account section, figure, and equation anchors. + +Here is an extract of a report produced by the checker. + +``` +pillar referenceCheck index.md +``` + +``` +File index.md has reference problems. + +## Undefined input files: + + Undefined file input: booklet-PharoVirtualMachine/Part2-JIT/CallingConventions/CallingConventions.md (in file /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/index.md) refer to file that does not exist. + + +## Duplicated Anchors: + + Anchor sec:references is duplicated in file: Booklet-PharoVirtualMachine/Part0-Preamble/1-ObjectStructure/objectStructure.md + Anchor sec:references is duplicated in file: PharoBooks2/Booklet-PharoVirtualMachine/Part0-Preamble/1-ObjectStructure/objectStructure.md + + +## Undefined Anchors: + + Anchor Ephemerons is undefined in file: /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/Part3-MemoryManagement/GarbageCollector/ephemerons.md + ... + +## Unreferenced Figures + + Figure 'figures/interpreter_variables.pdf' with anchor 'interpreterVariables' (in file /Users/ducasse/Workspace/FirstCircle/MyBooks/Bk-Writing/PharoBooks2/Booklet-PharoVirtualMachine/Part1-InterpreterAndBytecode/4-Interpreter/theInterpreter.md) is not referenced. + ... +``` + +### Code snippet validation + +Microdown supports the automatic validation of code snippets. +The following snippets illustrate the points. + +Here the writer wants to make sure that the expression 3 + 4 returns the correct result 7. He tags the code and use the `>>>` on a separate line. + +``` + ```example=true + 3 + 4 + >>> 7 + ``` +``` + +He can also declare that a given expression is expected to cause a failure using the `expectedFailure` tag as follows. + + +``` + ```example=true&expectedFailure=true + 3 + 4 + >>> 12 + ``` +``` + +### Version drift detection + +As an author, you may copy and paste a method definition from a Pharo class to your book. +Then the natural question that arises is whether that copy is still valid in a more recent version of the system. + +Imagine that in class `A` you define the method `simpleCode`. + +``` +A>>simpleCode + "The comment of simpleCode" + + ^ 100 slowFactorial +``` + +Imagine that you want to have this method definition in your document and that you want the checker to report if the method changes. You can do it using the `sync` key. + +``` + ```sync=true&origin=MicMethodBodySyncTest>>#simpleCode + simpleCode + "The comment of simpleCode" + + ^ 100 slowFactorial + ``` +``` + +### An architecture to plug extra analyses. + +Code blocks are often used to define extensions. The `MicCodeBlockValidator` defines a way to modularly define new analyses. To declare a new analysis is easy: + +- You have to define a new class subclass of `MicBookTesterStrategy` +- This class should define a method `verify:` expecting a code block. +- Define a class method named `handleKey` that returns the key of the extension (`#sync` in the previous example). + + +### Conclusion + +Microdown is more than a Markdown language. In addition, to be extensible and having nice features such as references or anchors, it is a symbiotic markdown for Pharo. It gives authors of Pharo books a clear advantage with powerful extensions and tools that make them more efficient. + + + + + + \ No newline at end of file diff --git a/src/Microdown-BookTester-Tests/MicReferenceCheckerTest.class.st b/src/Microdown-BookTester-Tests/MicReferenceCheckerTest.class.st index 7bef0d62..de434de3 100644 --- a/src/Microdown-BookTester-Tests/MicReferenceCheckerTest.class.st +++ b/src/Microdown-BookTester-Tests/MicReferenceCheckerTest.class.st @@ -9,12 +9,21 @@ Class { 'defFig1AndRefToAnsC0', 'defFig1AndRefToAncUnkS0', 'refToUnkS1', - 'defCode1AndReferToCode1' + 'defCode1AndReferToCode1', + 'defFig1' ], #category : 'Microdown-BookTester-Tests', #package : 'Microdown-BookTester-Tests' } +{ #category : 'as yet unclassified' } +MicReferenceCheckerTest >> anchorNames: checker [ + + ^ checker results + select: [ :each | each isKindOf: MicAnchorResult ] + thenCollect: [ :each | each anchorLabel ] +] + { #category : 'helpers - anchors & references' } MicReferenceCheckerTest >> defAnCS0DoubleEq2DoubleEq1RefEq1 [ @@ -54,6 +63,7 @@ MicReferenceCheckerTest >> defAncS0DoubleFig1Fig2RefAncS1 [ | doubleFig1 | doubleFig1 := dir / 'defAncS0DoubleFig1Fig2RefAncS1.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. doubleFig1 writeStreamDo: [ :stream | stream nextPutAll: '# Section @ancS0 @@ -133,10 +143,25 @@ See *@Eq1@* defEq1AndReferToEq1 ensureCreateFile ] +{ #category : 'helpers - anchors & references' } +MicReferenceCheckerTest >> defFig1 [ + + defFig1 := fs / 'defFig1.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. + defFig1 ensureCreateFile. + defFig1 writeStreamDo: [ :stream | + stream nextPutAll: '# Section 1 +![alittle caption. %anchor=Fig1](figures/f.png) + + +' ] +] + { #category : 'helpers - anchors & references' } MicReferenceCheckerTest >> defFig1AndRefFig1 [ defAndRefFig1 := dir / 'defAndRefFig1.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. defAndRefFig1 writeStreamDo: [ :stream | stream nextPutAll: '# Section 1 @@ -153,10 +178,11 @@ See *@Fig1@* MicReferenceCheckerTest >> defFig1AndRefToAncS0 [ defFig1AndRefToAnsC0 := fs / 'defFig1AndRefToAncS0.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. defFig1AndRefToAnsC0 ensureCreateFile. defFig1AndRefToAnsC0 writeStreamDo: [ :stream | stream nextPutAll: '# Section 1 -![alittle caption.](figures/f.png anchor=Fig1) +![alittle caption. %anchor=Fig1](figures/f.png) See *@ancS0@* @@ -167,6 +193,7 @@ See *@ancS0@* MicReferenceCheckerTest >> defFig1AndRefToAncUnkS0 [ defFig1AndRefToAncUnkS0 := fs / 'defFig1AndRefToAncUnkS0.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. defFig1AndRefToAncUnkS0 ensureCreateFile. defFig1AndRefToAncUnkS0 writeStreamDo: [ :stream | stream nextPutAll: '# Section 1 @@ -178,11 +205,26 @@ See *@ancUnkS0@* defFig1AndRefToAncUnkS0 ensureCreateFile ] +{ #category : 'helpers - anchors & references' } +MicReferenceCheckerTest >> defFig1FilePathToUnk [ + + defAndRefFig1 := dir / 'defAndRefFig1.md'. + defAndRefFig1 writeStreamDo: [ :stream | + stream nextPutAll: '# Section 1 + +![alittle caption.](figures/f.png anchor=Fig1) + +' ]. + defAndRefFig1 ensureCreateFile. + ^ defAndRefFig1 +] + { #category : 'helpers - anchors & references' } MicReferenceCheckerTest >> duplicatedFigSecEq [ | conflictBetweenFigSecEq | conflictBetweenFigSecEq := dir / 'duplicatedFigSecEq.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. conflictBetweenFigSecEq writeStreamDo: [ :stream | stream nextPutAll: '# Section @ancS0 @@ -222,6 +264,7 @@ MicReferenceCheckerTest >> duplicatedFigSecEqPart1 [ | duplicatedFigSecEqPart1 | duplicatedFigSecEqPart1 := dir / 'duplicatedFigSecEqPart1.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. duplicatedFigSecEqPart1 writeStreamDo: [ :stream | stream nextPutAll: '# Section @ancS0 @@ -237,8 +280,7 @@ $$ # Section 3 @ancS3 -' - ]. +' ]. duplicatedFigSecEqPart1 ensureCreateFile. ^ duplicatedFigSecEqPart1 ] @@ -248,6 +290,7 @@ MicReferenceCheckerTest >> duplicatedFigSecEqPart2 [ | duplicatedFigSecEqPart2 | duplicatedFigSecEqPart2 := dir / 'duplicatedFigSecEqPart2.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. duplicatedFigSecEqPart2 writeStreamDo: [ :stream | stream nextPutAll: ' # Duplication with Figure @@ -274,6 +317,7 @@ See *@ancS1@* and *@ancS0@* MicReferenceCheckerTest >> fileDefAncS1UndAncS0 [ fileDefAncS1UndAncS0 := fs / 'fileDefAncS1UndAncS0.md'. + (dir / 'figures' / 'f.png') ensureCreateFile. fileDefAncS1UndAncS0 ensureCreateFile. fileDefAncS1UndAncS0 writeStreamDo: [ :stream | stream nextPutAll: '# Section 1 @@ -283,7 +327,7 @@ MicReferenceCheckerTest >> fileDefAncS1UndAncS0 [ See *@ancS0@* ' ]. - fileDefAncS1UndAncS0 ensureCreateFile + fileDefAncS1UndAncS0 ensureCreateFile ] { #category : 'helpers - anchors & references' } @@ -339,14 +383,15 @@ MicReferenceCheckerTest >> testDefAndReferToAMathEquationInFile [ { #category : 'tests - single file' } MicReferenceCheckerTest >> testDefFig1AndRefToAncS0UnknowAnchor [ - | visitor | + | visitor badRefs | self defFig1AndRefToAncS0. visitor := MicReferenceChecker new. visitor rootDirectory: dir. visitor checkProject: defFig1AndRefToAnsC0. self deny: visitor isOkay. - self assert: visitor results first anchorLabel equals: 'ancS0'. - self assert: visitor results first sourceFileReference fullName equals: '/defFig1AndRefToAncS0.md' + badRefs := visitor results select: [ :each | each isKindOf: MicAnchorResult ]. + self assert: badRefs first anchorLabel equals: 'ancS0'. + self assert: badRefs first sourceFileReference fullName equals: '/defFig1AndRefToAncS0.md' ] { #category : 'tests - single file ok references' } @@ -355,11 +400,14 @@ MicReferenceCheckerTest >> testDefFig1AndRefToAncUnkS0 [ | visitor | self defFig1AndRefToAncUnkS0. visitor := MicReferenceChecker new. + visitor rootDirectory: dir. visitor checkList: { defFig1AndRefToAncUnkS0 }. self deny: visitor isOkay. - self assert: visitor results size equals: 1. - self assert: visitor results first anchorLabel equals: 'ancUnkS0'. - self assert: visitor results first sourceFileReference fullName equals: '/defFig1AndRefToAncUnkS0.md' + self assert: visitor results size equals: 3. + self assert: visitor results second anchorLabel equals: 'ancUnkS0'. + self + assert: visitor results second sourceFileReference fullName + equals: '/defFig1AndRefToAncUnkS0.md' ] { #category : 'tests - single file ok references' } @@ -380,9 +428,9 @@ MicReferenceCheckerTest >> testDefFig1ReferToAFigureInFile [ | visitor | self defFig1AndRefFig1. visitor := MicReferenceChecker new. + visitor rootDirectory: dir. visitor checkList: { defAndRefFig1 }. - self assert: visitor isOkay. - + self assert: visitor isOkay ] { #category : 'tests - single file' } @@ -394,9 +442,9 @@ MicReferenceCheckerTest >> testDefS1ButRefersToS0UnknownAnchor [ visitor rootDirectory: dir. visitor checkProject: fileDefAncS1UndAncS0. self deny: visitor isOkay. - self assert: visitor results size equals: 1. - self assert: visitor results first anchorLabel equals: 'ancS0'. - self assert: visitor results first sourceFileReference fullName equals: '/fileDefAncS1UndAncS0.md' + self assert: visitor results size equals: 3. + self assert: visitor results second anchorLabel equals: 'ancS0'. + self assert: visitor results second sourceFileReference fullName equals: '/fileDefAncS1UndAncS0.md' ] @@ -434,7 +482,7 @@ Just a reference See *@ancS1@* ' ]. { #category : 'skipped for now' } MicReferenceCheckerTest >> testDuplicatedAnchorDir [ - | dir file1 file2 visitor | + | file1 file2 visitor | self skip. dir := (FileSystem workingDirectory / 'myDirectory') asFileReference. dir ensureCreateDirectory. @@ -510,8 +558,7 @@ MicReferenceCheckerTest >> testDuplicatedAnchorInDifferentFilesOfTheSameDir [ MicReferenceCheckerTest >> testDuplicatedAnchors [ | defAncS0TripleAncS1RefAncS1AncS0 checker dict dup1 | - defAncS0TripleAncS1RefAncS1AncS0 := self - defAncS0TripleAncS1RefAncS1AncS0. + defAncS0TripleAncS1RefAncS1AncS0 := self defAncS0TripleAncS1RefAncS1AncS0. checker := MicReferenceChecker new. checker checkList: { defAncS0TripleAncS1RefAncS1AncS0 }. self deny: checker isOkay. @@ -534,11 +581,13 @@ MicReferenceCheckerTest >> testDuplicatedBetweenSectionFigureEq [ | conflictBetweenFigSecEq visitor | conflictBetweenFigSecEq := self duplicatedFigSecEq. visitor := MicReferenceChecker new. + visitor rootDirectory: dir. visitor checkList: { conflictBetweenFigSecEq }. self deny: visitor isOkay. self - assert: (visitor results collect: [ :each | each anchorLabel ]) - equals: OrderedCollection <- #( 'ancS1' 'ancS1' 'ancS0' 'ancS0' 'fig2' 'fig2' ) + assert: (self anchorNames: visitor) + equals: OrderedCollection + <- #( 'ancS1' 'ancS1' 'ancS0' 'ancS0' 'fig2' 'fig2' ) ] { #category : 'tests - duplicated' } @@ -548,11 +597,15 @@ MicReferenceCheckerTest >> testDuplicatedBetweenSectionFigureEqInDifferentFile [ duplicatedFigSecEqPart1 := self duplicatedFigSecEqPart1. duplicatedFigSecEqPart2 := self duplicatedFigSecEqPart2. visitor := MicReferenceChecker new. - visitor checkList: { duplicatedFigSecEqPart1 . duplicatedFigSecEqPart2 }. + visitor rootDirectory: dir. + visitor checkList: { + duplicatedFigSecEqPart1. + duplicatedFigSecEqPart2 }. self deny: visitor isOkay. self - assert: (visitor results collect: [ :each | each anchorLabel ]) - equals: OrderedCollection <- #( 'ancS1' 'ancS1' 'ancS0' 'ancS0' 'fig2' 'fig2') + assert: (self anchorNames: visitor) + equals: OrderedCollection + <- #( 'ancS1' 'ancS1' 'ancS0' 'ancS0' 'fig2' 'fig2' ) ] { #category : 'tests - duplicated' } @@ -561,19 +614,21 @@ MicReferenceCheckerTest >> testDuplicatedFigures [ | doubleFig1 checker dict dup1 | doubleFig1 := self defAncS0DoubleFig1Fig2RefAncS1. checker := MicReferenceChecker new. + checker rootDirectory: dir. checker checkList: { doubleFig1 }. self deny: checker isOkay. self - assert: (checker results collect: [ :each | each anchorLabel ]) - equals: OrderedCollection <- #( 'fig1' 'fig1' 'fig1' 'fig1' 'ancS1' ). - + assert: (self anchorNames: checker) + equals: + OrderedCollection <- #( 'fig1' 'fig1' 'fig1' 'fig1' 'ancS1' ). + dict := checker results groupedBy: [ :each | each class ]. - + dup1 := (dict at: MicDuplicatedAnchorResult) first. - self assert: dup1 sourceFileReference fullName equals: '/myDirectory/defAncS0DoubleFig1Fig2RefAncS1.md'. - self assert: dup1 anchorLabel equals: 'fig1'. - - + self + assert: dup1 sourceFileReference fullName + equals: '/myDirectory/defAncS0DoubleFig1Fig2RefAncS1.md'. + self assert: dup1 anchorLabel equals: 'fig1' ] { #category : 'tests - duplicated' } @@ -602,6 +657,25 @@ MicReferenceCheckerTest >> testDuplicatedMaths [ self assert: dup2 anchorLabel equals: 'Eq1'. ] +{ #category : 'tests - single file' } +MicReferenceCheckerTest >> testFigureFilePathToUnkFile [ + + | visitor refToUnexistingFiles | + visitor := MicReferenceChecker new. + visitor rootDirectory: dir. + visitor checkProject: self defFig1FilePathToUnk. + self deny: visitor isOkay. + self assert: visitor results size equals: 2. + refToUnexistingFiles := (visitor results groupedBy: [ :each | each class name ]) at: #MicUndefinedFigureFileResult. + self assert: refToUnexistingFiles size equals: 1. + self assert: refToUnexistingFiles first what equals: 'figures/f.png'. + self assert: refToUnexistingFiles first figureFileString equals: 'figures/f.png'. + + self + assert: refToUnexistingFiles first sourceFileReference fullName + equals: '/myDirectory/defAndRefFig1.md' +] + { #category : 'tests - full project' } MicReferenceCheckerTest >> testFullProjectWithFowardAndBackWardRefBetweenTwoFiles [ @@ -626,7 +700,7 @@ MicReferenceCheckerTest >> testFullProjectWithReferencesToUnknowAnchor [ dict := checker results groupedBy: [ :each | each class ]. - unk := (dict at: MicUndefinedAnchorResult) first. + unk := (dict at: MicReferenceToUnexistingAnchorResult) first. self assert: unk sourceFileReference fullName equals: '/myDirectory/sections/section2.md'. self assert: unk anchorLabel equals: 'ancUnkS2'. @@ -648,7 +722,7 @@ See *@anchorSection1@* '. visitor := MicReferenceChecker new. doc accept: visitor. - visitor collectBadReferences. + visitor collectReferencesToUnexistingAnchors. self assert: visitor isOkay ] @@ -662,10 +736,12 @@ MicReferenceCheckerTest >> testLowLevelReferToAFigure [ See *@anchorSection1@* '. + doc fromFile: 'file.md' asFileReference. visitor := MicReferenceChecker new. doc accept: visitor. - visitor collectBadReferences. - self assert: visitor isOkay + visitor collectReferencesToUnexistingAnchors. + self assert: visitor results first class equals: MicUndefinedFigureFileResult + ] { #category : 'tests - internal - parse only' } @@ -682,7 +758,7 @@ See *@anchorSection1@* '. visitor := MicReferenceChecker new. doc accept: visitor. - visitor collectBadReferences. + visitor collectReferencesToUnexistingAnchors. self assert: visitor isOkay ] @@ -698,7 +774,7 @@ See *@anchorSection1@* doc fromFile: 'fakedFile'. visitor := MicReferenceChecker new. doc accept: visitor. - visitor collectBadReferences. + visitor collectReferencesToUnexistingAnchors. self deny: visitor isOkay ] @@ -773,7 +849,7 @@ MicReferenceCheckerTest >> testReportingSTONFormatUnknownAnchorDir [ { #category : 'skipped for now' } MicReferenceCheckerTest >> testReportingUnknownAnchorDir [ - | dir file1 file2 visitor | + | file1 file2 visitor | self skip. dir := (FileSystem workingDirectory / 'myDirectory') asFileReference. dir ensureCreateDirectory. @@ -830,3 +906,20 @@ MicReferenceCheckerTest >> testUndefinedInputFilesAreMentionedInResult [ ] ] + +{ #category : 'tests - single file' } +MicReferenceCheckerTest >> testUnreferencedFigure [ + + | visitor badRefs unref | + self defFig1. + visitor := MicReferenceChecker new. + visitor rootDirectory: dir. + visitor checkProject: defFig1. + self deny: visitor isOkay. + badRefs := (visitor results groupedBy: [:each | each class name]) at: #MicUnreferencedFigureResult. + unref := badRefs first. + self assert: badRefs size equals: 1. + self assert: unref figurePath equals: 'figures/f.png'. + self assert: unref anchorLabel equals: 'Fig1'. + self assert: unref sourceFileReference fullName equals: '/defFig1.md' +] diff --git a/src/Microdown-BookTester-Tests/MicResultTest.class.st b/src/Microdown-BookTester-Tests/MicResultTest.class.st index fcb21641..a7556e69 100644 --- a/src/Microdown-BookTester-Tests/MicResultTest.class.st +++ b/src/Microdown-BookTester-Tests/MicResultTest.class.st @@ -23,7 +23,7 @@ MicResultTest >> testExplanationDuplicatedAnchor [ MicResultTest >> testExplanationUndefinedAnchor [ | res | - res := MicUndefinedAnchorResult new. + res := MicReferenceToUnexistingAnchorResult new. res anchorLabel: 'ancS0'. "pay attention since in this package fromFile is pass a fullName and not a file we will have to change this later" @@ -61,13 +61,13 @@ MicResultTest >> testMicBrokenSyncDefinitionResultExplanationWorksWithEmptyInsta { #category : 'tests - basic result creation' } MicResultTest >> testResultCreation [ - { MicBookTestResult . MicBrokenSyncDefinition . MicBrokenSyncOriginDefinition . MicDesynchronizedCodeResult . MicDuplicatedAnchorResult . MicUndefinedAnchorResult . MicUndefinedFigureFileResult . MicUndefinedInputFileResult} do: [ :each | each new printString ] + { MicBookTestResult . MicBrokenSyncDefinition . MicBrokenSyncOriginDefinition . MicDesynchronizedCodeResult . MicDuplicatedAnchorResult . MicReferenceToUnexistingAnchorResult . MicUndefinedFigureFileResult . MicUndefinedInputFileResult} do: [ :each | each new printString ] ] { #category : 'tests - basic result creation' } MicResultTest >> testResultCreationSmokeTest [ - { MicBookTestResult . MicBrokenSyncDefinition . MicBrokenSyncOriginDefinition . MicDesynchronizedCodeResult . MicDuplicatedAnchorResult . MicUndefinedAnchorResult . MicUndefinedFigureFileResult . MicUndefinedInputFileResult} do: [ :each | each new printString ] + { MicBookTestResult . MicBrokenSyncDefinition . MicBrokenSyncOriginDefinition . MicDesynchronizedCodeResult . MicDuplicatedAnchorResult . MicReferenceToUnexistingAnchorResult . MicUndefinedFigureFileResult . MicUndefinedInputFileResult} do: [ :each | each new printString ] ] { #category : 'tests' } @@ -93,7 +93,7 @@ MicResultTest >> testSTONFormatExplanationDuplicatedAnchor [ MicResultTest >> testSTONFormatExplanationUndefinedAnchor [ | res association | - res := MicUndefinedAnchorResult new. + res := MicReferenceToUnexistingAnchorResult new. res anchorLabel: 'ancS0'. "pay attention since in this package fromFile is pass a fullName and not a file we will have to change this later" @@ -118,7 +118,7 @@ MicResultTest >> testUndefinedFigureFile [ self assert: res explanation - equals: 'File input myDir/foo.png (in file /myDir/chapter2.md) does not exist' + equals: 'Figure not found: myDir/foo.png (in file /myDir/chapter2.md) was not found.' ] @@ -133,6 +133,7 @@ MicResultTest >> testUndefinedInputFile [ self assert: res explanation - equals: 'File input myDir/foo.md (in file /myDir/chapter2.md) does not exist' + equals: 'Undefined file input: myDir/foo.md (in file /myDir/chapter2.md) refer to file that does not exist.' + ] diff --git a/src/Microdown-BookTester/MicAnalysisReportWriter.class.st b/src/Microdown-BookTester/MicAnalysisReportWriter.class.st index b24c4331..e8111e53 100644 --- a/src/Microdown-BookTester/MicAnalysisReportWriter.class.st +++ b/src/Microdown-BookTester/MicAnalysisReportWriter.class.st @@ -1,5 +1,5 @@ " -I'm a little util classes that encapsulates writing reports about analyses. +I'm a little util class that encapsulates writing reports about analyses. " Class { #name : 'MicAnalysisReportWriter', @@ -25,7 +25,7 @@ MicAnalysisReportWriter >> buildReportOn: str [ dict := results groupedBy: [ :each | each class ]. dict keysAndValuesDo: [ :k :v | str cr; nextPutAll: '## ' ;nextPutAll: k headerString; cr; cr. - self reportElementsOn: str. + self reportElements: v on: str. str cr; cr. ] ] @@ -68,6 +68,15 @@ MicAnalysisReportWriter >> report [ ] ] +{ #category : 'reporting' } +MicAnalysisReportWriter >> reportElements: partialResults on: aStream [ + + partialResults + do: [ :each | aStream tab; nextPutAll: each explanation ] + separatedBy: [ aStream cr ] + +] + { #category : 'reporting' } MicAnalysisReportWriter >> reportElementsOn: aStream [ diff --git a/src/Microdown-BookTester/MicAnchorResult.class.st b/src/Microdown-BookTester/MicAnchorResult.class.st index a4d6de7d..9538b30c 100644 --- a/src/Microdown-BookTester/MicAnchorResult.class.st +++ b/src/Microdown-BookTester/MicAnchorResult.class.st @@ -1,3 +1,6 @@ +" +I'm the superclass of problem related to anchors. +" Class { #name : 'MicAnchorResult', #superclass : 'MicResult', @@ -12,7 +15,7 @@ Class { { #category : 'kinds' } MicAnchorResult class >> errorType [ - ^ 'UndefinedAnchor' + ^ 'Anchor problem' ] { #category : 'accessing' } @@ -36,7 +39,7 @@ MicAnchorResult >> explanation [ { #category : 'accessing' } MicAnchorResult >> isWhat [ - ^ 'undefined' + ^ 'anchor problem' ] { #category : 'accessing' } diff --git a/src/Microdown-BookTester/MicCodeBlockValidator.class.st b/src/Microdown-BookTester/MicCodeBlockValidator.class.st index f430cfba..95d98abc 100644 --- a/src/Microdown-BookTester/MicCodeBlockValidator.class.st +++ b/src/Microdown-BookTester/MicCodeBlockValidator.class.st @@ -93,6 +93,7 @@ MicCodeBlockValidator >> validTests [ MicCodeBlockValidator >> visitCode: aCodeBlock [ "Creates an instance of PRBookTestResult with parameters depending of the type of the codeblock." + (strategies keys includes: aCodeBlock mainExtensionTag) ifFalse: [ ^ self ]. diff --git a/src/Microdown-BookTester/MicDuplicatedAnchorResult.class.st b/src/Microdown-BookTester/MicDuplicatedAnchorResult.class.st index 81f7cb33..232ca6b3 100644 --- a/src/Microdown-BookTester/MicDuplicatedAnchorResult.class.st +++ b/src/Microdown-BookTester/MicDuplicatedAnchorResult.class.st @@ -1,3 +1,6 @@ +" +I represent the fact that an achor is defined more than one. +" Class { #name : 'MicDuplicatedAnchorResult', #superclass : 'MicAnchorResult', diff --git a/src/Microdown-BookTester/MicFileResult.class.st b/src/Microdown-BookTester/MicFileResult.class.st index d5cea294..27c14b43 100644 --- a/src/Microdown-BookTester/MicFileResult.class.st +++ b/src/Microdown-BookTester/MicFileResult.class.st @@ -9,7 +9,7 @@ Class { { #category : 'accessing' } MicFileResult >> explanation [ - ^ 'File input ', self what,' (in file ', source fullName , ') does not exist' + ^ 'File input ', self what,' (in file ', source fullName , ') does not exist.' ] diff --git a/src/Microdown-BookTester/MicReferenceChecker.class.st b/src/Microdown-BookTester/MicReferenceChecker.class.st index 944ae6e3..7b88031c 100644 --- a/src/Microdown-BookTester/MicReferenceChecker.class.st +++ b/src/Microdown-BookTester/MicReferenceChecker.class.st @@ -26,28 +26,18 @@ Class { #instVars : [ 'reportWriter', 'references', - 'anchors', 'duplicatedAnchors', 'results', 'rootDirectory', - 'listOfFiles' + 'listOfFiles', + 'figures', + 'anchoringEntities' ], #category : 'Microdown-BookTester-Analysis', #package : 'Microdown-BookTester', #tag : 'Analysis' } -{ #category : 'accessing' } -MicReferenceChecker >> addBadReferenceAnchor: anAnchorReference [ - - | micResultInstance | - micResultInstance := MicUndefinedAnchorResult new. - micResultInstance - anchorLabel: anAnchorReference anchorLabel; - sourceFileReference: anAnchorReference fromFile. - results add: micResultInstance -] - { #category : 'accessing' } MicReferenceChecker >> addDuplicatedAnchor: anAnchor [ @@ -62,13 +52,47 @@ MicReferenceChecker >> addDuplicatedAnchor: anAnchor [ { #category : 'visiting' } MicReferenceChecker >> addDuplicatedFirstAnchor: anAnchor [ - anchors do: [ :each | + anchoringEntities do: [ :each | each anchorLabel = anAnchor anchorLabel ifTrue: [ (duplicatedAnchors includes: each) ifFalse: [ duplicatedAnchors add: each. self addDuplicatedAnchor: each ] ] ] ] +{ #category : 'accessing' } +MicReferenceChecker >> addReferenceToUnexistingAnchor: anAnchorReference [ + + | micResultInstance | + micResultInstance := MicReferenceToUnexistingAnchorResult new. + micResultInstance + anchorLabel: anAnchorReference anchorLabel; + sourceFileReference: anAnchorReference fromFile. + results add: micResultInstance +] + +{ #category : 'accessing' } +MicReferenceChecker >> addUnexistingFigureFile: aFigure [ + + | micResultInstance | + micResultInstance := MicUndefinedFigureFileResult new. + micResultInstance + figureFileString: aFigure reference path; + sourceFileReference: aFigure fromFile. + results add: micResultInstance +] + +{ #category : 'accessing' } +MicReferenceChecker >> addUnreferencedFigure: aFigure [ + + | micResultInstance | + micResultInstance := MicUnreferencedFigureResult new. + micResultInstance + figurePath: aFigure reference relativePath; + anchorLabel: aFigure anchorLabel; + sourceFileReference: aFigure fromFile. + results add: micResultInstance +] + { #category : 'main API' } MicReferenceChecker >> checkDirectory: aDir [ "Take the directory, parse all its children with microdown file parser and let the visitor visit each time then return visitor is ok which should be true if every thing is okay, the visitor turned out to treat the many documents that it visits as one, so if anchor is duplicated in another file it will detect that . " @@ -80,11 +104,14 @@ MicReferenceChecker >> checkDirectory: aDir [ MicReferenceChecker >> checkList: aCollection [ "Pay attention checking a file in isolation is DIFFERENT from a list, because a document can have references between them and the checker should be shared amongst the documents since it collects the references." - aCollection do: [ :each | - | document | - document := Microdown parseFile: each. - document accept: self ]. - self collectBadReferences. + aCollection do: [ :each | + | document | + document := Microdown parseFile: each. + document accept: self ]. + self + collectReferencesToUnexistingAnchors; + collectUnreferencedFigures + ] { #category : 'main API' } @@ -102,16 +129,24 @@ MicReferenceChecker >> checkProject: aFileReference [ self checkList: listOfFiles ] -{ #category : 'visiting' } -MicReferenceChecker >> collectBadReferences [ - " should be called just after all the docs are visited otherwise the result can be wrong" +{ #category : 'internal' } +MicReferenceChecker >> collectReferencesToUnexistingAnchors [ + "Should be called after all the docs are visited otherwise the result can be wrong." | badReference existingAnchorNames | - existingAnchorNames := anchors collect: [ :each | each anchorLabel ]. + existingAnchorNames := anchoringEntities collect: [ :each | each anchorLabel ]. badReference := references reject: [ :anchorReference | - existingAnchorNames includes: - anchorReference anchorLabel ]. - badReference do: [ :each | self addBadReferenceAnchor: each ] + existingAnchorNames includes: anchorReference anchorLabel ]. + badReference do: [ :each | self addReferenceToUnexistingAnchor: each ] +] + +{ #category : 'internal' } +MicReferenceChecker >> collectUnreferencedFigures [ + + | unreferencedFigures anchorReferenceNames | + anchorReferenceNames := references collect: [ :each | each anchorLabel ]. + unreferencedFigures := figures reject: [ :figure | anchorReferenceNames includes: figure anchorLabel ]. + unreferencedFigures do: [ :each | self addUnreferencedFigure: each ] ] { #category : 'internal' } @@ -134,7 +169,7 @@ MicReferenceChecker >> handleAnchorOf: anElement [ duplicatedAnchors add: anElement. self addDuplicatedAnchor: anElement. self addDuplicatedFirstAnchor: anElement. ]. - anchors add: anElement + anchoringEntities add: anElement ] { #category : 'internal' } @@ -154,7 +189,7 @@ MicReferenceChecker >> hasAlreadyDefinedAs: anAnchor [ | alreadyDefined | alreadyDefined := false. - anchors do: + anchoringEntities do: [ :each | each anchorLabel = anAnchor anchorLabel ifTrue: [ alreadyDefined := true ] ]. ^ alreadyDefined @@ -168,7 +203,8 @@ MicReferenceChecker >> initialize [ rootDirectory := FileSystem workingDirectory. results := OrderedCollection new. references := OrderedCollection new. - anchors := OrderedCollection new. + figures := OrderedCollection new. + anchoringEntities := OrderedCollection new. duplicatedAnchors := OrderedCollection new. "by default if the reporter is not set from outside then it shares the results of this current checker." @@ -238,7 +274,7 @@ MicReferenceChecker >> unknownAnchors [ unknown := OrderedCollection new. ref := references copy. ref do: [ :reference | - (anchors noneSatisfy: [ :each | + (anchoringEntities noneSatisfy: [ :each | ref anchorLabel = each anchorLabel ]) ifTrue: [ unknown add: reference ] ]. ^ unknown @@ -246,19 +282,23 @@ MicReferenceChecker >> unknownAnchors [ { #category : 'visiting' } MicReferenceChecker >> visitAnchor: anAnchor [ - + "the problem with visit anchor is that it only concerns + @anchor and not the one of figure. + So we should then keep a list of anchorNames for the figures, math equations + or reify these anchor - would be nicer" + | isAlready | isAlready := self hasAlreadyDefinedAs: anAnchor. isAlready ifTrue: [ duplicatedAnchors add: anAnchor. self addDuplicatedAnchor: anAnchor. self addDuplicatedFirstAnchor: anAnchor. ]. - anchors add: anAnchor + anchoringEntities add: anAnchor ] { #category : 'visiting' } MicReferenceChecker >> visitAnchorReference: anAnchorReference [ - + references add: anAnchorReference ] @@ -270,8 +310,12 @@ MicReferenceChecker >> visitCode: aCodeBlock [ { #category : 'visiting' } MicReferenceChecker >> visitFigure: aFigure [ - - self handleAnchorOf: aFigure + + figures add: aFigure. + self handleAnchorOf: aFigure. + "check for unexisting file." + (aFigure fromFile parent / aFigure reference path) exists + ifFalse: [ self addUnexistingFigureFile: aFigure ] ] { #category : 'visiting' } diff --git a/src/Microdown-BookTester/MicUndefinedAnchorResult.class.st b/src/Microdown-BookTester/MicReferenceToUnexistingAnchorResult.class.st similarity index 50% rename from src/Microdown-BookTester/MicUndefinedAnchorResult.class.st rename to src/Microdown-BookTester/MicReferenceToUnexistingAnchorResult.class.st index 019fbd13..f9f49310 100644 --- a/src/Microdown-BookTester/MicUndefinedAnchorResult.class.st +++ b/src/Microdown-BookTester/MicReferenceToUnexistingAnchorResult.class.st @@ -1,5 +1,8 @@ +" +I represent the fact that a reference refers to an undefined anchor. +" Class { - #name : 'MicUndefinedAnchorResult', + #name : 'MicReferenceToUnexistingAnchorResult', #superclass : 'MicAnchorResult', #category : 'Microdown-BookTester-Results', #package : 'Microdown-BookTester', @@ -7,18 +10,18 @@ Class { } { #category : 'kinds' } -MicUndefinedAnchorResult class >> errorType [ +MicReferenceToUnexistingAnchorResult class >> errorType [ ^ 'UndefinedAnchor' ] { #category : 'kinds' } -MicUndefinedAnchorResult class >> headerString [ +MicReferenceToUnexistingAnchorResult class >> headerString [ ^ 'Undefined Anchors:' ] { #category : 'accessing' } -MicUndefinedAnchorResult >> isWhat [ +MicReferenceToUnexistingAnchorResult >> isWhat [ ^ 'undefined' ] diff --git a/src/Microdown-BookTester/MicResult.class.st b/src/Microdown-BookTester/MicResult.class.st index bc65aa6e..f2c81c13 100644 --- a/src/Microdown-BookTester/MicResult.class.st +++ b/src/Microdown-BookTester/MicResult.class.st @@ -33,7 +33,7 @@ MicResult class >> errorType [ { #category : 'kinds' } MicResult class >> headerString [ - ^ self subclassResponsibility + ^ 'Unreferenced Figures' ] { #category : 'accessing' } diff --git a/src/Microdown-BookTester/MicUndefinedFigureFileResult.class.st b/src/Microdown-BookTester/MicUndefinedFigureFileResult.class.st index de360768..1b2e2e45 100644 --- a/src/Microdown-BookTester/MicUndefinedFigureFileResult.class.st +++ b/src/Microdown-BookTester/MicUndefinedFigureFileResult.class.st @@ -1,3 +1,6 @@ +" +I represent a figure referencing an image file that does not exist. +" Class { #name : 'MicUndefinedFigureFileResult', #superclass : 'MicFileResult', @@ -21,6 +24,19 @@ MicUndefinedFigureFileResult class >> headerString [ ^ 'Undefined figure files:' ] +{ #category : 'accessing' } +MicUndefinedFigureFileResult >> explanation [ + + ^ 'Figure not found: ', self what,' (in file ', source fullName , ') was not found.' + +] + +{ #category : 'accessing' } +MicUndefinedFigureFileResult >> figureFileString [ + + ^ figureFileString +] + { #category : 'accessing' } MicUndefinedFigureFileResult >> figureFileString: aString [ diff --git a/src/Microdown-BookTester/MicUndefinedInputFileResult.class.st b/src/Microdown-BookTester/MicUndefinedInputFileResult.class.st index 336c563b..70d3e5b5 100644 --- a/src/Microdown-BookTester/MicUndefinedInputFileResult.class.st +++ b/src/Microdown-BookTester/MicUndefinedInputFileResult.class.st @@ -1,3 +1,6 @@ +" +I represent an input referencing a file that does not exist. +" Class { #name : 'MicUndefinedInputFileResult', #superclass : 'MicFileResult', @@ -21,6 +24,13 @@ MicUndefinedInputFileResult class >> headerString [ ^ 'Undefined input files:' ] +{ #category : 'accessing' } +MicUndefinedInputFileResult >> explanation [ + + ^ 'Undefined file input: ', self what,' (in file ', source fullName , ') refer to file that does not exist.' + +] + { #category : 'inputFileBlock:' } MicUndefinedInputFileResult >> inputFile [ @@ -41,5 +51,10 @@ MicUndefinedInputFileResult >> inputFileBlock: anInputFileBlock [ { #category : 'accessing' } MicUndefinedInputFileResult >> what [ - ^ self inputFile + | path | + self flag: #fixItLatter. + path := self inputFileBlock path. + ^ path isString + ifTrue: [ path ] + ifFalse: [ path fullName ] ] diff --git a/src/Microdown-BookTester/MicUnreferencedFigureResult.class.st b/src/Microdown-BookTester/MicUnreferencedFigureResult.class.st new file mode 100644 index 00000000..6d092ee4 --- /dev/null +++ b/src/Microdown-BookTester/MicUnreferencedFigureResult.class.st @@ -0,0 +1,58 @@ +" +I represent a figure that refers to a file that does not exist. +" +Class { + #name : 'MicUnreferencedFigureResult', + #superclass : 'MicResult', + #instVars : [ + 'figure', + 'anchorLabel', + 'figurePath' + ], + #category : 'Microdown-BookTester-Results', + #package : 'Microdown-BookTester', + #tag : 'Results' +} + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> anchorLabel [ + + ^ anchorLabel +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> anchorLabel: aString [ + + anchorLabel := aString +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> explanation [ + + ^ 'Figure ', self what,' with anchor ', anchorLabel printString, ' (in file ', source fullName , ') is not referenced.' + +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> figure: aFigureBlock [ + + figure := aFigureBlock +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> figurePath [ + + ^ figurePath +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> figurePath: aString [ + + figurePath := aString +] + +{ #category : 'accessing' } +MicUnreferencedFigureResult >> what [ + + ^ figurePath printString +] diff --git a/src/Microdown-Evaluator/MicCompilationContext.class.st b/src/Microdown-Evaluator/MicCompilationContext.class.st index f9a45ffd..d2b757b8 100644 --- a/src/Microdown-Evaluator/MicCompilationContext.class.st +++ b/src/Microdown-Evaluator/MicCompilationContext.class.st @@ -14,11 +14,6 @@ MicCompilationContext class >> configuration: aConfiguration [ ^ self new configuration: aConfiguration ] -{ #category : 'instance creation' } -MicCompilationContext class >> isAbstract [ - ^ false -] - { #category : 'instance creation' } MicCompilationContext class >> withDocument: aDocument [ ^ self new diff --git a/src/Microdown-LaTeXExporter-Tests/MicAbstractLaTexWriterTest.class.st b/src/Microdown-LaTeXExporter-Tests/MicAbstractLaTexWriterTest.class.st index 703cd8ff..f5aafd3d 100644 --- a/src/Microdown-LaTeXExporter-Tests/MicAbstractLaTexWriterTest.class.st +++ b/src/Microdown-LaTeXExporter-Tests/MicAbstractLaTexWriterTest.class.st @@ -12,7 +12,8 @@ Class { } { #category : 'asserting' } -MicAbstractLaTexWriterTest class >> shouldInheritSelectors [ +MicAbstractLaTexWriterTest class >> isAbstract [ + ^ true ] @@ -65,9 +66,10 @@ MicAbstractLaTexWriterTest >> factory: aFactory [ { #category : 'accessing' } MicAbstractLaTexWriterTest >> newLine: aNewLine [ - (aNewLine = String cr) ifTrue:[ writer crAsNewLine ]. - (aNewLine = String lf) ifTrue:[ writer lfAsNewLine ]. - (aNewLine = String crlf) ifTrue:[ writer crlfAsNewLine ]. + + aNewLine = String cr ifTrue: [ writer crAsNewLine ]. + aNewLine = String lf ifTrue: [ writer lfAsNewLine ]. + aNewLine = String crlf ifTrue: [ writer crlfAsNewLine ]. newLine := aNewLine ] @@ -449,9 +451,9 @@ MicAbstractLaTexWriterTest >> testParagraphWithNested [ { #category : 'tests' } MicAbstractLaTexWriterTest >> testQuote [ - self parse: factory quoteSample andCheckWeGet: '\begin{verbatim}', newLine , + self parse: factory quoteSample andCheckWeGet: newLine, '\begin{quote}', newLine , 'Foo', newLine , - '\end{verbatim}', newLine + '\end{quote}', newLine ] { #category : 'tests - figure/link' } @@ -689,8 +691,3 @@ MicAbstractLaTexWriterTest >> writeForElement: aNode [ writer visit: aNode. ] - -{ #category : 'accessing' } -MicAbstractLaTexWriterTest >> writer: aWriter [ - writer := aWriter new -] diff --git a/src/Microdown-LaTeXExporter-Tests/MicLaTeXWriterTest.class.st b/src/Microdown-LaTeXExporter-Tests/MicLaTeXWriterTest.class.st index 70d90654..1a61fc8d 100644 --- a/src/Microdown-LaTeXExporter-Tests/MicLaTeXWriterTest.class.st +++ b/src/Microdown-LaTeXExporter-Tests/MicLaTeXWriterTest.class.st @@ -5,7 +5,19 @@ Class { #package : 'Microdown-LaTeXExporter-Tests' } -{ #category : 'as yet unclassified' } +{ #category : 'asserting' } +MicLaTeXWriterTest class >> isAbstract [ + + ^ false +] + +{ #category : 'asserting' } +MicLaTeXWriterTest class >> shouldInheritSelectors [ + + ^ true +] + +{ #category : 'tests' } MicLaTeXWriterTest >> testCodeblock [ self writeFor: (factory codeblockNoParamBody: 'this is a code'). @@ -14,7 +26,7 @@ MicLaTeXWriterTest >> testCodeblock [ '\end{listing}', newLine ] -{ #category : 'as yet unclassified' } +{ #category : 'tests' } MicLaTeXWriterTest >> testCodeblockWithCaption [ self writeFor: (factory codeblock: 'caption=Pharo is **cool**' body: 'this is a code'). @@ -23,7 +35,7 @@ MicLaTeXWriterTest >> testCodeblockWithCaption [ '\end{listing}', newLine ] -{ #category : 'as yet unclassified' } +{ #category : 'tests' } MicLaTeXWriterTest >> testCodeblockWithLabel [ self writeFor: (factory codeblock: 'label=Pharo' body: 'this is a code'). @@ -32,198 +44,16 @@ MicLaTeXWriterTest >> testCodeblockWithLabel [ '\end{listing}', newLine ] -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testCrAsNewLine [ - - writer crAsNewLine. - self assert: writer canvas stream configuration newLine equals: Character cr -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testCrlfAsNewLine [ - - writer crlfAsNewLine. - self assert: writer canvas stream configuration newLine equals: String crlf -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testFigure [ - - self writeFor: factory figureSample. - self assert: writer contents equals: newLine , - '\begin{figure}[htpb]', newLine , - '\begin{center}', newLine , - '\includegraphics[width=0.8\textwidth]{/anUrl}', newLine , - '\caption{Foo}\label{figureTest}', newLine , - '\end{center}', newLine , - '\end{figure}', newLine, newLine -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testFigureBold [ - self writeFor: factory figureBoldSample. - self assert: writer contents equals: newLine, - '\begin{figure}[htpb]', newLine , - '\begin{center}', newLine , - '\includegraphics[width=0.8\textwidth]{/anUrl}', newLine , - '\caption{\textbf{Foo}}\label{figureTest}', newLine , - '\end{center}', newLine , - '\end{figure}', newLine , newLine -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testFigureRealSample [ - - self writeFor: factory figureRealSample. - self assert: writer contents equals: newLine , -'\begin{figure}[htpb]', newLine , -'\begin{center}', newLine , -'\includegraphics[width=0.8\textwidth]{figures/logo.png}', newLine , -'\caption{A logo png under figures folder}\label{figureTest}', newLine , -'\end{center}', newLine , -'\end{figure}', newLine, newLine -. -self flag: #todo. -"may be we should get figures/logo.png when file://figures/logo.png" - -] - { #category : 'tests - figure/link' } MicLaTeXWriterTest >> testFigureWithEmptyCaption [ self writeFor: '![](figures/f.png)'. self - assert: writer contents + assert: writer contents equals: - newLine , '\begin{center}', newLine, '\includegraphics[width=1.0\textwidth]{figures/f.png}' - , '\end{center}', newLine, newLine -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testFootnote [ - - self parseInlined: factory footnoteSample andCheckWeGet: '\footnote{Pharo is cool}' - -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel1 [ - - self parse: factory headerLevel1Sample andCheckWeGet: newLine , '\part{Foo}' - -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel2 [ - - self parse: factory headerLevel2Sample andCheckWeGet: newLine , '\chapter{Foo}' -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel3 [ - - self parse: factory headerLevel3Sample andCheckWeGet: newLine , '\section{Foo}' -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel4 [ - - self parse: factory headerLevel4Sample andCheckWeGet: newLine , '\subsection{Foo}' -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel5 [ - - self parse: factory headerLevel5Sample andCheckWeGet: newLine , '\subsubsection{Foo}' -] - -{ #category : 'tests - header' } -MicLaTeXWriterTest >> testHeaderLevel6 [ - - self parse: factory headerLevel6Sample andCheckWeGet: newLine , '\paragraph{Foo}' -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testHorizontalLine [ - - self parseInlined: factory horizontalLineSample andCheckWeGet: '\rule{\textwidth}{0.1pt}' -] - -{ #category : 'tests - formats' } -MicLaTeXWriterTest >> testItalic [ - - self parseInlined: factory italicFormatSample andCheckWeGet: '\textit{Foo}' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testLfAsNewLine [ - - writer lfAsNewLine. - self assert: writer canvas stream configuration newLine equals: Character lf -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testLink [ - self writeFor: factory linkSample. - self assert: writer contents equals: newLine, -'\href{/anUrl}{Foo}', newLine -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testLinkBold [ - - self writeFor: factory linkBoldSample. - self assert: writer contents equals: newLine , - '\href{/anUrl}{\textbf{Foo}}', newLine -] - -{ #category : 'tests - math' } -MicLaTeXWriterTest >> testMathBloc [ - - self - parseInlined:'$$ -f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz -$$' - - andCheckWeGet: - -'\begin{equation}', newLine, -'f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz', newLine, -'\end{equation}' -] - -{ #category : 'tests - math' } -MicLaTeXWriterTest >> testMathBlocWithLabel [ - - self - parseInlined:'$$ % anchor=eq1 -f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz -$$' - - andCheckWeGet: - -'\begin{equation}', newLine, -'\label{eq1}', newLine, -'f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz', newLine, -'\end{equation}' -] - -{ #category : 'tests - math' } -MicLaTeXWriterTest >> testMathInLine [ - - self - parseInlined: 'Similarly, let $f_{w,C}$ be the number of documents in corpus $C$ that contain word $w$:' - - - andCheckWeGet: - -'Similarly, let $f_{w,C}$ be the number of documents in corpus $C$ that contain word $w$:' -] - -{ #category : 'tests - formats' } -MicLaTeXWriterTest >> testMonospace [ - - self parseInlined: factory monospaceFormatSample andCheckWeGet: '\code{Foo}' + newLine, '\begin{figure}[htpb]', newLine , '\begin{center}', newLine, '\includegraphics[width=1.0\textwidth]{figures/f.png}', newLine, '\caption{}', newLine, + '\end{center}', newLine, '\end{figure}', newLine, newLine + ] { #category : 'tests' } @@ -233,365 +63,8 @@ MicLaTeXWriterTest >> testNote [ ] -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testOrderedList [ - - self writeFor: factory orderedListWithTwoItemsSample. - self assert: writer contents equals: newLine, - '\begin{enumerate}', newLine , - ' \item first', newLine , - ' \item second', newLine , - '\end{enumerate}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testOrderedListBold [ - - self writeFor: factory orderedListWithTwoItemsBoldSample. - self assert: writer contents equals: newLine , - '\begin{enumerate}', newLine , - ' \item \textbf{first}', newLine , - ' \item second', newLine , - '\end{enumerate}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testOrderedListNested [ - - self writeFor: factory orderedListWithTwoItemsBoldFirstSample. - self assert: writer contents equals: newLine, - '\begin{enumerate}', newLine , - ' \item \textbf{\textit{first}}', newLine , - ' \item second', newLine , - '\end{enumerate}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testOrderedNestedListNested [ - - self writeFor: factory nestedOrderedListSample. - self assert: writer contents equals: newLine , - '\begin{itemize}', newLine , - ' \item Foo', newLine , - ' \item Bar', newLine , - ' \begin{enumerate}', newLine , - ' \item B', newLine , - ' \item a', newLine , - ' \item r', newLine , - ' \end{enumerate}', newLine , - '\end{itemize}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testOrderedNestedListNested2 [ - - self writeFor: factory nestedOrderedList2Sample. - self assert: writer contents equals: newLine , - '\begin{itemize}', newLine , - ' \item Foo', newLine , - ' \item Bar', newLine , - ' \begin{enumerate}', newLine , - ' \item B', newLine , - ' \item a', newLine , - ' \item r', newLine , - ' \begin{itemize}', newLine , - ' \item 3rdlvel', newLine , - ' \item 3rdlevl2', newLine , - ' \end{itemize}', newLine , - ' \end{enumerate}', newLine , - ' \item Zork', newLine , - '\end{itemize}', newLine -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraph [ - - self parse: factory paragraphSample andCheckWeGet: newLine, - 'Foo',newLine - -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraphLongWithAccents [ - - self parse: factory paragraphOnMultipleLinesSample andCheckWeGet: newLine, -'Je ne connais pas la peur, car la peur tue l''esprit. La peur est la petite mort qui conduit \`{a} l''oblit\''{e}ration totale. J''affonterai ma peur. Je lui permettrais de passer sur moi, au travers de moi. Et lorsqu''elle sera pass\''{e}e, je tournerai mon oeil interieur sur son chemin. Et l\`{a} o\`{u} elle sera pass\''{e}e, il n''y aura plus rien, rien que moi.', newLine -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraphWithBold [ - - self parse: factory paragraphBoldSample andCheckWeGet: newLine , - 'this is a \textbf{paragraph}', newLine -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraphWithItalic [ - - self parse: factory paragraphItalicSample andCheckWeGet: newLine , - 'this is a \textit{paragraph}', newLine -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraphWithMonospace [ - - self parse: factory paragraphMonospaceSample andCheckWeGet: newLine , -'this is a \code{paragraph}', newLine - -] - -{ #category : 'tests - paragraph' } -MicLaTeXWriterTest >> testParagraphWithNested [ - - self parse: factory paragraphNestedSample andCheckWeGet: newLine , -'this is a \textbf{\textit{paragraph}}', newLine -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testQuote [ - - self parse: factory quoteSample andCheckWeGet: '\begin{verbatim}', newLine , - 'Foo', newLine , - '\end{verbatim}', newLine -] - -{ #category : 'tests - figure/link' } -MicLaTeXWriterTest >> testRealLinkSample [ - self writeFor: factory realLinkSample. - self assert: writer contents equals: newLine , -'\href{http://www.pharo.org}{The Pharo Project}', newLine -] - -{ #category : 'tests - math' } -MicLaTeXWriterTest >> testSimpleMathBloc [ - - | root math | - root := parser parse: ' -$$ -f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz -$$'. - math := root children first. - self - parseInlined:'$$ -f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz -$$' - - andCheckWeGet: - -'\begin{equation}', newLine, -'f(a) = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z - a} dz', newLine, -'\end{equation}' -] - -{ #category : 'tests - formats' } -MicLaTeXWriterTest >> testStrike [ - - self parseInlined: factory strikethroughFormatSample andCheckWeGet: '\sout{Foo}' - -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testTable [ - self writeFor: factory tableSample. - self assert: writer contents equals: newLine, - '\begin{tabular}{ll}', newLine , - '\toprule', newLine , - '\textbf{aaab} & \textbf{jkhjh} \\', newLine , - '\midrule', newLine , - 'bar & rab \\', newLine , - '\bottomrule', newLine , - '\end{tabular}', newLine -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testTableWhithoutHeader [ - - self writeFor: (factory simpleTableWithoutHeaderTable). - self assert: writer contents equals: newLine, - '\begin{tabular}{ll}', newLine , - '\toprule', newLine , - 'aaab & jkhjh \\', newLine , - 'bar & rab \\', newLine , - '\bottomrule', newLine , - '\end{tabular}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testUnorderedList [ - - self writeFor: factory unorderedListWithTwoItemsSample. - self assert: writer contents equals: newLine, - '\begin{itemize}', newLine , - ' \item Foo', newLine , - ' \item Bar', newLine , - '\end{itemize}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testUnorderedListBold [ - - self writeFor: factory unorderedListWithTwoItemsBoldSample. - self assert: writer contents equals: newLine , - '\begin{itemize}', newLine , - ' \item \textbf{Foo}', newLine , - ' \item Bar', newLine , - '\end{itemize}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testUnorderedListNested [ - - self writeFor: factory unorderedListWithTwoItemsNestedSample. - self assert: writer contents equals: newLine , - '\begin{itemize}', newLine , - ' \item \sout{\textit{Foo}}', newLine , - ' \item Bar', newLine , - '\end{itemize}', newLine -] - -{ #category : 'tests - lists' } -MicLaTeXWriterTest >> testUnorderedNestedListNested [ - - self writeFor: factory nestedUnorderedListSample. - self assert: writer contents equals: newLine , - '\begin{itemize}', newLine , - ' \item Foo', newLine , - ' \item Bar', newLine , - ' \begin{itemize}', newLine , - ' \item B', newLine , - ' \item a', newLine , - ' \item r', newLine , - ' \end{itemize}', newLine , - '\end{itemize}', newLine - -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> testUrlsAreProtected [ - - self assert: (writer protectUrl: '#') equals: '\#'. - self assert: (writer protectUrl: '~') equals: '~'. - self assert: (writer protectUrl: '&') equals: '&'. - self assert: (writer protectUrl: '%') equals: '\%'. - self assert: (writer protectUrl: '\') equals: '\\'. - self assert: (writer protectUrl: '\~#%') equals: '\\~\#\%'. -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCr [ - writer crAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self assert: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String cr , -'cela ne fonctionnerais pas', String cr, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCrButNewLineIsCrlf [ - writer crlfAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String cr , -'cela ne fonctionnerais pas', String cr, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCrButNewLineIsLf [ - writer lfAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String cr , -'cela ne fonctionnerais pas', String cr, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCrlf [ - writer crlfAsNewLine . - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self assert: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String crlf , -'cela ne fonctionnerais pas', String crlf, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCrlfButNewLineIsCr [ - writer crAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String crlf , -'cela ne fonctionnerais pas', String crlf, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithCrlfButNewLineIsLf [ - writer lfAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String crlf , -'cela ne fonctionnerais pas', String crlf, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithLf [ - writer lfAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self assert: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String lf , -'cela ne fonctionnerais pas', String lf, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithLfButNewLineIsCr [ - writer crAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String lf , -'cela ne fonctionnerais pas', String lf, -'car il a etait fait pour cela' -] - -{ #category : 'tests - breaklining' } -MicLaTeXWriterTest >> testWriteAParagraphWithLfButNewLineIsCrlf [ - writer crlfAsNewLine. - writer canvas nextPutAllLines: 'Je ne vois pas pourquoi -cela ne fonctionnerais pas -car il a etait fait pour cela'. - self deny: writer canvas stream contents equals: 'Je ne vois pas pourquoi', String lf , -'cela ne fonctionnerais pas', String lf, -'car il a etait fait pour cela' -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> writeFor: aString [ - | mic | - mic := parser parse: aString. - writer visit: mic. - -] - -{ #category : 'tests' } -MicLaTeXWriterTest >> writeForElement: aNode [ - - writer visit: aNode. - -] - { #category : 'accessing' } -MicLaTeXWriterTest >> writer: aWriter [ - writer := aWriter new +MicLaTeXWriterTest >> writer: aWriterClass [ + + writer := aWriterClass new. ] diff --git a/src/Microdown-LaTeXExporter/MicLaTeXWriter.class.st b/src/Microdown-LaTeXExporter/MicLaTeXWriter.class.st index f04c8699..a124a611 100644 --- a/src/Microdown-LaTeXExporter/MicLaTeXWriter.class.st +++ b/src/Microdown-LaTeXExporter/MicLaTeXWriter.class.st @@ -330,11 +330,20 @@ MicLaTeXWriter >> visitParagraph: aParagraph [ { #category : 'blocks' } MicLaTeXWriter >> visitQuote: aQuote [ - canvas environment + "canvas environment name: 'verbatim'; with: [ aQuote children do: [ :child | self visit: child. - canvas newLine ] ] + canvas newLine ] ]" + + + canvas newLine. + canvas environment + name: 'quote'; + with: [ + aQuote children do: [ :each | each accept: self ]. + canvas newLine.]. + ] { #category : 'blocks - inline' } diff --git a/src/Microdown-LaTeXExporter/MicLatexPathUpdateVisitor.class.st b/src/Microdown-LaTeXExporter/MicLatexPathUpdateVisitor.class.st index 1b52e3e8..f85b5c08 100644 --- a/src/Microdown-LaTeXExporter/MicLatexPathUpdateVisitor.class.st +++ b/src/Microdown-LaTeXExporter/MicLatexPathUpdateVisitor.class.st @@ -16,11 +16,6 @@ Class { #package : 'Microdown-LaTeXExporter' } -{ #category : 'visiting' } -MicLatexPathUpdateVisitor class >> isAbstract [ - ^ false -] - { #category : 'visiting' } MicLatexPathUpdateVisitor class >> key [ diff --git a/src/Microdown-RichTextComposer-Tests/MicRichTextCodeBlockStylerTest.class.st b/src/Microdown-RichTextComposer-Tests/MicRichTextCodeBlockStylerTest.class.st index 0b140fb3..b4337082 100644 --- a/src/Microdown-RichTextComposer-Tests/MicRichTextCodeBlockStylerTest.class.st +++ b/src/Microdown-RichTextComposer-Tests/MicRichTextCodeBlockStylerTest.class.st @@ -40,7 +40,7 @@ styledTextFor: aString asFest'. "Check that the unknown variable 'blue' is not colored red" blueLocation := richText asString findString: 'blue'. textColor := (richText attributesAt: blueLocation) detect: [ :attr | attr class = TextColor ]. - self assert: textColor color red equals: 0. + self assert: textColor color red equals: 1.0. ] { #category : 'tests' } @@ -59,7 +59,7 @@ green isColored: yellow "Check that the unknown variable 'blue' is not colored red" blueLocation := richText asString findString: 'blue'. textColor := (richText attributesAt: blueLocation) detect: [ :attr | attr class = TextColor ]. - self assert: textColor color red equals: 0. + self assert: textColor color red equals: 1.0. ] { #category : 'tests' } diff --git a/src/Microdown-RichTextComposer-Tests/MicRichTextComposerTest.class.st b/src/Microdown-RichTextComposer-Tests/MicRichTextComposerTest.class.st index 9a55c647..b62b0d1f 100644 --- a/src/Microdown-RichTextComposer-Tests/MicRichTextComposerTest.class.st +++ b/src/Microdown-RichTextComposer-Tests/MicRichTextComposerTest.class.st @@ -677,7 +677,7 @@ styledTextFor: aString asFest'. "Check that the unknown variable 'blue' is not colored red" blueLocation := richText asString findString: 'blue'. textColor := (richText attributesAt: blueLocation) detect: [ :attr | attr class = TextColor ]. - self assert: textColor color red equals: 0. + self assert: textColor color red equals: 1.0. ] { #category : 'tests - list' } diff --git a/src/Microdown-Tests/MicCodeBlockTest.class.st b/src/Microdown-Tests/MicCodeBlockTest.class.st index f29751b9..d873915a 100644 --- a/src/Microdown-Tests/MicCodeBlockTest.class.st +++ b/src/Microdown-Tests/MicCodeBlockTest.class.st @@ -72,11 +72,17 @@ MicCodeBlockTest >> testCodeBlockWithNestedCodeBlock [ "This test shows that we can write a code block inside a code block but that the later is not interpreter." | source root code textBody | - textBody := ' ``` - my fancy code - is really cool - ```'. - source := CodeblockMarkup, String cr, textBody, String cr, CodeblockMarkup. + source := '``` +This is a first level code + ``` + this is one with tabs + ``` + + ``` + this is one with space + ``` +``` '. + root := parser parse: source. self @@ -84,9 +90,7 @@ MicCodeBlockTest >> testCodeBlockWithNestedCodeBlock [ equals: 1. code := root children first. self assert: code children isEmpty. - self - assert: code code - equals: textBody. + self assert: code class equals: MicCodeBlock ] @@ -355,6 +359,30 @@ Color >> asMorph self assert: code mainExtensionTag equals: #caption ] +{ #category : 'tests' } +MicCodeBlockTest >> testMainExtensionTag2 [ + " + ```example=true + 3 + 7 + >> 10 + ``` + " + + | source root code textBody argument | + textBody := '3 + 7 +>>> 10'. + argument := 'example=true'. + source := CodeblockMarkup , argument , String cr , textBody + , String cr , CodeblockMarkup. + root := self parser parse: source. + self assert: root children size equals: 1. + code := root children first. + self assert: code code equals: textBody. + self assert: (code arguments at: #language) equals: 'Pharo'. + self assert: code mainExtensionTag equals: #example. + +] + { #category : 'tests - arguments' } MicCodeBlockTest >> testMainExtensionTagForSync [ @@ -436,6 +464,22 @@ Color >> asMorph self assert: code mainExtensionTag equals: #caption ] +{ #category : 'tests - code' } +MicCodeBlockTest >> testNestedCodeBlockOnlyProducesOneCodeBlock [ + + | doc | + doc := Microdown parse: '``` + ```example=true&expectedFailure=true + 3 + 4 + >>> 12 + ``` +```'. + + self assert: doc children size equals: 1. + self assert: doc children first class equals: MicCodeBlock. + +] + { #category : 'tests' } MicCodeBlockTest >> testOpenCanConsumeLine [ | source root textBody argument line code | diff --git a/src/Microdown/MicAbstractBlock.class.st b/src/Microdown/MicAbstractBlock.class.st index 8305b038..6a16f865 100644 --- a/src/Microdown/MicAbstractBlock.class.st +++ b/src/Microdown/MicAbstractBlock.class.st @@ -117,6 +117,13 @@ MicAbstractBlock >> listItemBlockClass [ ^ MicListItemBlock ] +{ #category : 'hooks' } +MicAbstractBlock >> massageLine: aLine [ + "In some cases an element may want to massage the line such as removing leading tab. By default we delegate to the parent because environment may contain nodes such as code block and this is this nesting that will determine what should be done with the tab. In such a case the environment should remov the tabs." + + ^ self parent massageLine: aLine +] + { #category : 'public' } MicAbstractBlock >> nestedLevel [ "Return the nesting level of main blocks. Basically only list increases this." diff --git a/src/Microdown/MicAnnotatedParagraphBlock.class.st b/src/Microdown/MicAnnotatedParagraphBlock.class.st index 49f4522d..ca64cea1 100644 --- a/src/Microdown/MicAnnotatedParagraphBlock.class.st +++ b/src/Microdown/MicAnnotatedParagraphBlock.class.st @@ -42,7 +42,7 @@ MicAnnotatedParagraphBlock >> closeMe [ body := self inlineParse: body ] -{ #category : 'testing' } +{ #category : 'accessing' } MicAnnotatedParagraphBlock >> defaultLabel [ ^ #note @@ -55,7 +55,7 @@ MicAnnotatedParagraphBlock >> initialize [ validLabels := { #note. #important. #todo } ] -{ #category : 'testing' } +{ #category : 'accessing' } MicAnnotatedParagraphBlock >> lineMarkup [ ^ AnnotatedParagraphOpeningMarkup diff --git a/src/Microdown/MicEnvironmentBlock.class.st b/src/Microdown/MicEnvironmentBlock.class.st index e30cf696..c0af9ac9 100644 --- a/src/Microdown/MicEnvironmentBlock.class.st +++ b/src/Microdown/MicEnvironmentBlock.class.st @@ -100,10 +100,13 @@ MicEnvironmentBlock >> addLineAndReturnNextNode: line [ "add line to this node. Notice, the action is allowed to create new nodes in the block tree. Returns the node to handle next line - typically self." + + | withoutPreTabs | isClosed ifTrue: [ ^ self ]. withoutPreTabs := line withoutPreTabs. + "Here the withoutPreTabs is probably not necessary because handlingPrefixTabOfLine: should do its job." (self doesLineStartWithStopMarkup: withoutPreTabs) ifTrue: [ ^ self ]. firstLine @@ -119,19 +122,19 @@ MicEnvironmentBlock >> arguments [ ^ arguments ] -{ #category : 'parse support' } +{ #category : 'accessing' } MicEnvironmentBlock >> body [ ^ String streamContents: [:s | self bodyElements do: [ :each | s nextPutAll: each substring ] ] ] -{ #category : 'parse support' } +{ #category : 'accessing' } MicEnvironmentBlock >> bodyElements [ ^ children ] -{ #category : 'parse support' } +{ #category : 'accessing' } MicEnvironmentBlock >> bodyFromLine: line [ | newBlock | @@ -195,3 +198,9 @@ MicEnvironmentBlock >> lineStopMarkup [ ^ EnvironmentClosingBlockMarkup ] + +{ #category : 'handle' } +MicEnvironmentBlock >> massageLine: aLine [ + + ^ aLine withoutPreTabs +] diff --git a/src/Microdown/MicFigureBlock.class.st b/src/Microdown/MicFigureBlock.class.st index 92b7ea4b..4a48d8c3 100644 --- a/src/Microdown/MicFigureBlock.class.st +++ b/src/Microdown/MicFigureBlock.class.st @@ -47,6 +47,12 @@ MicFigureBlock >> closeMe [ self children do: [ :each | each basicParent: self ]. ] +{ #category : 'testing' } +MicFigureBlock >> hasCaption [ + + ^ children isNotNil +] + { #category : 'printing' } MicFigureBlock >> plainText [ ^ '![', ((self children collect: #plainText) joinUsing: ' '), '](', url ,')' diff --git a/src/Microdown/MicRootBlock.class.st b/src/Microdown/MicRootBlock.class.st index 829299d4..146a8167 100644 --- a/src/Microdown/MicRootBlock.class.st +++ b/src/Microdown/MicRootBlock.class.st @@ -67,6 +67,13 @@ MicRootBlock >> indent [ ^0 ] +{ #category : 'hooks' } +MicRootBlock >> massageLine: aLine [ + "In some cases an element may want to massage the line such as removing leading tab. By default we delegate to the parent because environments may contain nodes such as code block and this is this nesting that will determine what should be done with the tab. In such a case the environment should remov the tabs. Here on the root we do nothing special." + + ^ aLine +] + { #category : 'testing' } MicRootBlock >> metaDataElement [ "if self hasMetaDataElement then metaDataElement returns it. diff --git a/src/Microdown/MicStartStopMarkupBlock.class.st b/src/Microdown/MicStartStopMarkupBlock.class.st index c1ccefb4..d6dcdc6d 100644 --- a/src/Microdown/MicStartStopMarkupBlock.class.st +++ b/src/Microdown/MicStartStopMarkupBlock.class.st @@ -75,7 +75,7 @@ MicStartStopMarkupBlock >> canConsumeLine: line [ { #category : 'testing' } MicStartStopMarkupBlock >> doesLineStartWithStopMarkup: line [ - + "return if the line starts with a stop markup" ^ line beginsWith: self lineStopMarkup ] diff --git a/src/Microdown/MicrodownParser.class.st b/src/Microdown/MicrodownParser.class.st index 0d19f4fa..7c90d25a 100644 --- a/src/Microdown/MicrodownParser.class.st +++ b/src/Microdown/MicrodownParser.class.st @@ -259,7 +259,7 @@ MicrodownParser >> handleLine: line [ -if the current block can consume the line, it manages it and this potentially creates a new block that becomes the current one. When the line is not consumed, the current block is closed and its parent becomes the current one and the process is called back to treat the line." - (current canConsumeLine: (line withoutPreTabs)) + (current canConsumeLine: (current massageLine: line)) ifTrue: [ current := current addLineAndReturnNextNode: line. ^ current ]