diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 67f21e1c4d..37d8955f62 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -192,7 +192,8 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, result.cache = cache result.outDir = conf.outDir.string initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex), - conf.configVars, filename.string, {roSupportRawDirective, roSupportMarkdown}, + conf.configVars, filename.string, + {roSupportRawDirective, roSupportMarkdown, roNimFile}, docgenFindFile, compilerMsgHandler) if conf.configVars.hasKey("doc.googleAnalytics"): diff --git a/doc/contributing.rst b/doc/contributing.rst index 279a4ee941..7ee3aa4442 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -295,6 +295,44 @@ example below) from `Nim Index`_ can be used in doc comment this way: .. _`Nim Index`: https://nim-lang.org/docs/theindex.html +Inline monospaced text can be input using \`single backticks\` or +\`\`double backticks\`\`. The former are syntactically highlighted, +the latter are not. +To avoid accidental highlighting follow this rule in `*.nim` files: + +* use single backticks for fragments of code in Nim and other + programming languages, including identifiers, in `*.nim` files. + + For languages other than Nim add a role after final backtick, + e.g. for C++ inline highlighting:: + + `#include `:cpp: + + For a currently unsupported language add the `:code:` role, + like for SQL in this example:: + + `SELECT * FROM ;`:code: + +* prefer double backticks otherwise: + + * for file names: \`\`os.nim\`\` + * for fragments of strings **not** enclosed by `"` and `"` and not + related to code, e.g. text of compiler messages + * for command line options: \`\`--docInternal\`\` + * also when code ends with a standalone ``\`` (otherwise a combination of + ``\`` and a final \` would get escaped) + +.. Note:: `*.rst` files have `:literal:` as their default role. + So for them the rule above is only applicable if the `:nim:` role + is set up manually as the default:: + + .. role:: nim(code) + :language: nim + .. default-role:: nim + + The first 2 lines are for other RST implementations, + including Github one. + Best practices ============== diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index d331c2c12d..66efa3a3c5 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -78,6 +78,13 @@ ## ## * directives: ``code-block`` [cmp:Sphinx]_, ``title``, ## ``index`` [cmp:Sphinx]_ +## * predefined roles ``:nim:`` (default), ``:c:`` (C programming language), +## ``:python:``, ``:yaml:``, ``:java:``, ``:cpp:`` (C++), ``:csharp`` (C#). +## That is every language that `highlite `_ supports. +## They turn on appropriate syntax highlighting in inline code. +## +## .. Note:: default role for Nim files is ``:nim:``, +## for ``*.rst`` it's currently ``:literal:``. ## ## * ***triple emphasis*** (bold and italic) using \*\*\* ## * ``:idx:`` role for \`interpreted text\` to include the link to this @@ -161,7 +168,9 @@ type roSupportSmilies, ## make the RST parser support smilies like ``:)`` roSupportRawDirective, ## support the ``raw`` directive (don't support ## it for sandboxing) - roSupportMarkdown ## support additional features of Markdown + roSupportMarkdown, ## support additional features of Markdown + roNimFile ## set for Nim files where default interpreted + ## text role should be :nim: RstParseOptions* = set[RstParseOption] @@ -454,6 +463,8 @@ type hTitleCnt: int # =0 if no title, =1 if only main title, # =2 if both title and subtitle are present hCurLevel: int # current section level + currRole: string # current interpreted text role + currRoleKind: RstNodeKind # ... and its node kind subs: seq[Substitution] # substitutions refs: seq[Substitution] # references anchors: seq[AnchorSubst] # internal target substitutions @@ -514,10 +525,36 @@ proc defaultFindFile*(filename: string): string = if fileExists(filename): result = filename else: result = "" +proc defaultRole(options: RstParseOptions): string = + if roNimFile in options: "nim" else: "literal" + +# mirror highlite.nim sourceLanguageToStr with substitutions c++ cpp, c# csharp +const supportedLanguages = ["nim", "yaml", "python", "java", "c", + "cpp", "csharp"] + +proc whichRoleAux(sym: string): RstNodeKind = + let r = sym.toLowerAscii + case r + of "idx": result = rnIdx + of "literal": result = rnInlineLiteral + of "strong": result = rnStrongEmphasis + of "emphasis": result = rnEmphasis + of "sub", "subscript": result = rnSub + of "sup", "superscript": result = rnSup + # literal and code are the same in our implementation + of "code": result = rnInlineLiteral + # c++ currently can be spelled only as cpp, c# only as csharp + elif r in supportedLanguages: + result = rnInlineCode + else: # unknown role + result = rnUnknownRole + proc newSharedState(options: RstParseOptions, findFile: FindFileHandler, msgHandler: MsgHandler): PSharedState = new(result) + result.currRole = defaultRole(options) + result.currRoleKind = whichRoleAux(result.currRole) result.subs = @[] result.refs = @[] result.options = options @@ -1018,15 +1055,28 @@ proc fixupEmbeddedRef(n, a, b: PRstNode) = for i in countup(0, sep - incr): a.add(n.sons[i]) for i in countup(sep + 1, n.len - 2): b.add(n.sons[i]) -proc whichRole(sym: string): RstNodeKind = - case sym - of "idx": result = rnIdx - of "literal": result = rnInlineLiteral - of "strong": result = rnStrongEmphasis - of "emphasis": result = rnEmphasis - of "sub", "subscript": result = rnSub - of "sup", "superscript": result = rnSup - else: result = rnGeneralRole +proc whichRole(p: RstParser, sym: string): RstNodeKind = + result = whichRoleAux(sym) + if result == rnUnknownRole: + rstMessage(p, mwUnsupportedLanguage, p.s.currRole) + +proc toInlineCode(n: PRstNode, language: string): PRstNode = + ## Creates rnInlineCode and attaches `n` contents as code (in 3rd son). + result = newRstNode(rnInlineCode) + let args = newRstNode(rnDirArg) + var lang = language + if language == "cpp": lang = "c++" + elif language == "csharp": lang = "c#" + args.add newLeaf(lang) + result.add args + result.add PRstNode(nil) + var lb = newRstNode(rnLiteralBlock) + var s: string + for i in n.sons: + assert i.kind == rnLeaf + s.add i.text + lb.add newLeaf(s) + result.add lb proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode = var newKind = n.kind @@ -1052,14 +1102,23 @@ proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode = result = newRstNode(newKind, newSons) elif match(p, p.idx, ":w:"): # a role: - newKind = whichRole(nextTok(p).symbol) - if newKind == rnGeneralRole: + let roleName = nextTok(p).symbol + newKind = whichRole(p, roleName) + if newKind == rnUnknownRole: let newN = newRstNode(rnInner, n.sons) - newSons = @[newN, newLeaf(nextTok(p).symbol)] + newSons = @[newN, newLeaf(roleName)] + result = newRstNode(newKind, newSons) + elif newKind == rnInlineCode: + result = n.toInlineCode(language=roleName) + else: + result = newRstNode(newKind, newSons) inc p.idx, 3 - result = newRstNode(newKind, newSons) - else: # no change - result = n + else: + if p.s.currRoleKind == rnInlineCode: + result = n.toInlineCode(language=p.s.currRole) + else: + newKind = p.s.currRoleKind + result = newRstNode(newKind, newSons) proc matchVerbatim(p: RstParser, start: int, expr: string): int = result = start @@ -1315,9 +1374,12 @@ proc parseInline(p: var RstParser, father: PRstNode) = parseUntil(p, n, "``", false) father.add(n) elif match(p, p.idx, ":w:") and p.tok[p.idx+3].symbol == "`": - let k = whichRole(nextTok(p).symbol) - let n = newRstNode(k) + let roleName = nextTok(p).symbol + let k = whichRole(p, roleName) + var n = newRstNode(k) inc p.idx, 3 + if k == rnInlineCode: + n = n.toInlineCode(language=roleName) parseUntil(p, n, "`", false) # bug #17260 father.add(n) elif isInlineMarkupStart(p, "`"): @@ -2421,6 +2483,18 @@ proc dirAdmonition(p: var RstParser, d: string): PRstNode = proc dirDefaultRole(p: var RstParser): PRstNode = result = parseDirective(p, rnDefaultRole, {hasArg}, nil) + if result.sons[0].len == 0: p.s.currRole = defaultRole(p.s.options) + else: + assert result.sons[0].sons[0].kind == rnLeaf + p.s.currRole = result.sons[0].sons[0].text + p.s.currRoleKind = whichRole(p, p.s.currRole) + +proc dirRole(p: var RstParser): PRstNode = + result = parseDirective(p, rnDirective, {hasArg, hasOptions}, nil) + # just check that language is supported, TODO: real role association + let lang = getFieldValue(result, "language").strip + if lang != "" and lang notin supportedLanguages: + rstMessage(p, mwUnsupportedLanguage, lang) proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind, contentParser: SectionParser) = @@ -2465,7 +2539,9 @@ proc selectDir(p: var RstParser, d: string): PRstNode = of "code-block": result = dirCodeBlock(p, nimExtension = true) of "container": result = dirContainer(p) of "contents": result = dirContents(p) - of "danger", "error": result = dirAdmonition(p, d) + of "danger": result = dirAdmonition(p, d) + of "default-role": result = dirDefaultRole(p) + of "error": result = dirAdmonition(p, d) of "figure": result = dirFigure(p) of "hint": result = dirAdmonition(p, d) of "image": result = dirImage(p) @@ -2478,10 +2554,10 @@ proc selectDir(p: var RstParser, d: string): PRstNode = result = dirRaw(p) else: rstMessage(p, meInvalidDirective, d) + of "role": result = dirRole(p) of "tip": result = dirAdmonition(p, d) of "title": result = dirTitle(p) of "warning": result = dirAdmonition(p, d) - of "default-role": result = dirDefaultRole(p) else: let tok = p.tok[p.idx-2] # report on directive in ".. directive::" rstMessage(p, meInvalidDirective, d, tok.line, tok.col) diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index ff34da2d19..dd456b5779 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -55,12 +55,15 @@ type # * `file#id `_ # * `file#id '_ rnSubstitutionDef, # a definition of a substitution - rnGeneralRole, # Inline markup: + # Inline markup: + rnInlineCode, + rnUnknownRole, # interpreted text with an unknown role rnSub, rnSup, rnIdx, rnEmphasis, # "*" rnStrongEmphasis, # "**" rnTripleEmphasis, # "***" - rnInterpretedText, # "`" + rnInterpretedText, # "`" an auxiliary role for parsing that will + # be converted into other kinds like rnInlineCode rnInlineLiteral, # "``" rnInlineTarget, # "_`target`" rnSubstitutionReferences, # "|" @@ -252,7 +255,7 @@ proc renderRstToRst(d: var RenderContext, n: PRstNode, result: var string) = result.add(" <") renderRstToRst(d, n.sons[1], result) result.add(">`_") - of rnGeneralRole: + of rnUnknownRole: result.add('`') renderRstToRst(d, n.sons[0],result) result.add("`:") diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 30c7f3080f..1a16f590e1 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -942,7 +942,7 @@ proc parseCodeBlockParams(d: PDoc, n: PRstNode): CodeBlockParams = result.init if n.isNil: return - assert n.kind == rnCodeBlock + assert n.kind in {rnCodeBlock, rnInlineCode} assert(not n.sons[2].isNil) # Parse the field list for rendering parameters if there are any. @@ -987,8 +987,8 @@ proc buildLinesHtmlTable(d: PDoc; params: CodeBlockParams, code: string, "" & ( d.config.getOrDefault"doc.listing_button" % id) -proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = - ## Renders a code block, appending it to `result`. +proc renderCode(d: PDoc, n: PRstNode, result: var string) = + ## Renders a code (code block or inline code), appending it to `result`. ## ## If the code block uses the ``number-lines`` option, a table will be ## generated with two columns, the first being a list of numbers and the @@ -997,7 +997,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = ## may also come from the parser through the internal ``default-language`` ## option to differentiate between a plain code block and Nim's code block ## extension. - assert n.kind == rnCodeBlock + assert n.kind in {rnCodeBlock, rnInlineCode} if n.sons[2] == nil: return var params = d.parseCodeBlockParams(n) var m = n.sons[2].sons[0] @@ -1006,10 +1006,23 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = if params.testCmd.len > 0 and d.onTestSnippet != nil: d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text) - let (blockStart, blockEnd) = buildLinesHtmlTable(d, params, m.text, + var blockStart, blockEnd: string + case d.target + of outHtml: + if n.kind == rnCodeBlock: + (blockStart, blockEnd) = buildLinesHtmlTable(d, params, m.text, n.anchor.idS) - dispA(d.target, result, blockStart, - "\\begin{rstpre}\n" & n.anchor.idS & "\n", []) + else: # rnInlineCode + blockStart = "" + blockEnd = "" + of outLatex: + if n.kind == rnCodeBlock: + blockStart = "\n\n\\begin{rstpre}" & n.anchor.idS & "\n" + blockEnd = "\n\\end{rstpre}\n" + else: # rnInlineCode + blockStart = "\\texttt{" + blockEnd = "}" + dispA(d.target, result, blockStart, blockStart, []) if params.lang == langNone: if len(params.langStr) > 0: d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr) @@ -1028,7 +1041,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = esc(d.target, substr(m.text, g.start, g.length+g.start-1)), tokenClassToStr[g.kind]]) deinitGeneralTokenizer(g) - dispA(d.target, result, blockEnd, "\n\\end{rstpre}\n") + dispA(d.target, result, blockEnd, blockEnd) proc renderContainer(d: PDoc, n: PRstNode, result: var string) = var tmp = "" @@ -1294,13 +1307,13 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) = result.add addNodes(lastSon(n)) of rnImage, rnFigure: renderImage(d, n, result) - of rnCodeBlock: renderCodeBlock(d, n, result) + of rnCodeBlock, rnInlineCode: renderCode(d, n, result) of rnContainer: renderContainer(d, n, result) of rnSubstitutionReferences, rnSubstitutionDef: renderAux(d, n, "|$1|", "|$1|", result) of rnDirective: renderAux(d, n, "", "", result) - of rnGeneralRole: + of rnUnknownRole: var tmp0 = "" var tmp1 = "" renderRstToOut(d, n.sons[0], tmp0) diff --git a/nimdoc/rst2html/expected/rst_examples.html b/nimdoc/rst2html/expected/rst_examples.html index 8253289a44..32abc0f80d 100644 --- a/nimdoc/rst2html/expected/rst_examples.html +++ b/nimdoc/rst2html/expected/rst_examples.html @@ -213,7 +213,7 @@ stmt = IND{>} stmt ^+ IND{=} DED # list of statements

Let T's be p's return type. NRVO applies for T if sizeof(T) >= N (where N is implementation dependent), in other words, it applies for "big" structures.

Apart from built-in operations like array indexing, memory allocation, etc. the raise statement is the only way to raise an exception.

-

typedesc used as a parameter type also introduces an implicit generic. typedesc has its own set of rules:

+

typedesc used as a parameter type also introduces an implicit generic. typedesc has its own set of rules:

The !=, >, >=, in, notin, isnot operators are in fact templates:

a > b is transformed into b < a.
a in b is transformed into contains(b, a).
notin and isnot have the obvious meanings.

A template where every parameter is untyped is called an immediate template. For historical reasons templates can be explicitly annotated with an immediate pragma and then these templates do not take part in overloading resolution and the parameters' types are ignored by the compiler. Explicit immediate templates are now deprecated.

@@ -297,10 +297,10 @@ stmt = IND{>} stmt ^+ IND{=} DED # list of statements

Introduction

"Der Mensch ist doch ein Augentier -- schöne Dinge wünsch ich mir."

This document is a tutorial for the programming language Nim. This tutorial assumes that you are familiar with basic programming concepts like variables, types, or statements but is kept very basic. The manual contains many more examples of the advanced language features. All code examples in this tutorial, as well as the ones found in the rest of Nim's documentation, follow the Nim style guide.

-

However, this does not work. The problem is that the procedure should not only return, but return and continue after an iteration has finished. This return and continue is called a yield statement. Now the only thing left to do is to replace the proc keyword by iterator and here it is - our first iterator:

+

However, this does not work. The problem is that the procedure should not only return, but return and continue after an iteration has finished. This return and continue is called a yield statement. Now the only thing left to do is to replace the proc keyword by iterator and here it is - our first iterator:

- +
A1 headerA2 | not fooled
C1C2 bold
D1 code \|D2
D1 code \|D2
E1 | text
F2 without pipe

not in table

diff --git a/nimdoc/rst2html/source/rst_examples.rst b/nimdoc/rst2html/source/rst_examples.rst index 54f0124c8c..7fa20de6c5 100644 --- a/nimdoc/rst2html/source/rst_examples.rst +++ b/nimdoc/rst2html/source/rst_examples.rst @@ -5,6 +5,10 @@ Not a Nim Manual :Authors: Andreas Rumpf, Zahary Karadjov :Version: |nimversion| +.. role:: nim(code) + :language: nim +.. default-role:: nim + .. contents:: diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 8525048eac..ba1791d816 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -565,14 +565,14 @@ This is deprecated with a message.
func someFunc() {....raises: [], tags: [].}
-My someFunc. Stuff in quotes here. Some link +My someFunc. Stuff in quotes here. Some link
proc fromUtils3() {....raises: [], tags: [].}
-came form utils but should be shown where fromUtilsGen is called +came form utils but should be shown where fromUtilsGen is called

Example:

discard 1
@@ -765,7 +765,7 @@ the c printf. etc.
proc low[T: Ordinal | enum | range](x: T): T {.magic: "Low", noSideEffect.}
-

Returns the lowest possible value of an ordinal value x. As a special semantic rule, x may also be a type identifier.

+

Returns the lowest possible value of an ordinal value x. As a special semantic rule, x may also be a type identifier.

See also:

@@ -776,7 +776,7 @@ the c printf. etc.
proc low2[T: Ordinal | enum | range](x: T): T {.magic: "Low", noSideEffect.}
-

Returns the lowest possible value of an ordinal value x. As a special semantic rule, x may also be a type identifier.

+

Returns the lowest possible value of an ordinal value x. As a special semantic rule, x may also be a type identifier.

See also:

diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim index da01c30d21..ad0c27f059 100644 --- a/tests/stdlib/trstgen.nim +++ b/tests/stdlib/trstgen.nim @@ -10,7 +10,7 @@ import unittest, strutils, strtabs import std/private/miscdollars proc toHtml(input: string, - rstOptions: RstParseOptions = {roSupportMarkdown}, + rstOptions: RstParseOptions = {roSupportMarkdown, roNimFile}, error: ref string = nil, warnings: ref seq[string] = nil): string = ## If `error` is nil then no errors should be generated. @@ -36,6 +36,11 @@ proc toHtml(input: string, except EParseError: discard +# inline code tags (for parsing originated from highlite.nim) +proc id(str: string): string = """""" & str & "" +proc op(str: string): string = """""" & str & "" +proc pu(str: string): string = """""" & str & "" + suite "YAML syntax highlighting": test "Basics": let input = """.. code-block:: yaml @@ -201,14 +206,14 @@ not in table""" `|` outside a table cell should render as `\|` consistently with markdown, see https://stackoverflow.com/a/66557930/1426932 ]# - doAssert output1 == """ + check(output1 == """ - +
A1 headerA2 | not fooled
C1C2 bold
D1 code \|D2
D1 """ & id"code" & " " & op"\|" & """D2
E1 | text
F2 without pipe

not in table

-""" +""") let input2 = """ | A1 header | A2 | | --- | --- |""" @@ -556,19 +561,66 @@ let x = 1 doAssert "foo.bar""" - check """`foo\`\`bar`""".toHtml == """foo``bar""" - check """`foo\`bar`""".toHtml == """foo`bar""" - check """`\`bar`""".toHtml == """`bar""" - check """`a\b\x\\ar`""".toHtml == """a\b\x\\ar""" + check("""`foo.bar`""".toHtml == + """""" & + id"foo" & op"." & id"bar" & "") + check("""`foo\`\`bar`""".toHtml == + """""" & + id"foo" & pu"`" & pu"`" & id"bar" & "") + check("""`foo\`bar`""".toHtml == + """""" & + id"foo" & pu"`" & id"bar" & "") + check("""`\`bar`""".toHtml == + """""" & + pu"`" & id"bar" & "") + check("""`a\b\x\\ar`""".toHtml == + """""" & + id"a" & op"""\""" & id"b" & op"""\""" & id"x" & op"""\\""" & id"ar" & + "") test "inline literal": check """``foo.bar``""".toHtml == """foo.bar""" check """``foo\bar``""".toHtml == """foo\bar""" check """``f\`o\\o\b`ar``""".toHtml == """f\`o\\o\b`ar""" + test "default-role": + # nim(default) -> literal -> nim -> code(=literal) + let input = dedent""" + Par1 `value1`. + + .. default-role:: literal + + Par2 `value2`. + + .. default-role:: nim + + Par3 `value3`. + + .. default-role:: code + + Par4 `value4`.""" + let p1 = """Par1 """ & id"value1" & "." + let p2 = """

Par2 value2.

""" + let p3 = """

Par3 """ & id"value3" & ".

" + let p4 = """

Par4 value4.

""" + let expected = p1 & p2 & "\n" & p3 & "\n" & p4 & "\n" + check(input.toHtml == expected) + + test "role directive": + let input = dedent""" + .. role:: y(code) + :language: yaml + + .. role:: brainhelp(code) + :language: brainhelp + """ + var warnings = new seq[string] + let output = input.toHtml(warnings=warnings) + check(warnings[].len == 1 and "language 'brainhelp' not supported" in warnings[0]) + test "RST comments": let input1 = """ + Check that comment disappears: .. @@ -1341,7 +1393,8 @@ Test1 test "(not) Roles: check escaping 1": let expected = """See :subscript:""" & - """some text.""" + """""" & id"some" & " " & id"text" & + "." check """See \:subscript:`some text`.""".toHtml == expected check """See :subscript\:`some text`.""".toHtml == expected