Skip to content

[strawman]: declarative "template tools" #4153

Description

@jonasfj

There is some debate about making first-class dartdoc directives for tools like snippets, and I was thinking why don't we have "template tools", or rather tools that don't involve running dart code, but maybe just evaluating a template.

This is an extremely ugly attempt at defining what that could look like, without us inventing a new language and still giving template authors some power.

Warning

Please don't think of this as a serious proposal, it's more of an exploratory idea 🤣

dartdoc:
  # What if we allowed "template tools"?
  template_tools:
    # We could almost define something like snippets that way.
    snippets:
      # Snippets don't have flags or options, so this is just illustrative
      flags:
        - warning     # ArgParser.addFlag('warning')      , allowing --[no]-warning (defaulting to false)
      options:
        - language    # ArgParser.addOption('language')   , allowing --language=foo

      # We have a context dictionary as follows:
      #   source.line
      #   source.column
      #   source.path
      #   package.name
      #   element.name
      #   invocationIndex
      #   input                         # text between {@tool snippets} and {@end-tool}
      #   arguments.raw                 # space separated arguments given in {@tool} invocation
      #   arguments.warning             # true, if --warning was given in {@tool} invocation
      #   arguments.language            # value, if --language=value was given in {@tool} invocation
      #
      # Using this context we can do some limited steps like:
      #  - extract variables from context using regular expressions and save them to context again.
      #  - assert values from context and throw an error instead of rendering a faulty template!
      steps:
      # Create a new "description" field in the context dictionary, running pattern as regex against text
      - extract: description
        text:           '{{input}}'
        pattern:        '^(?<content>.*)^```'
        multiLine:      true
        caseSensitive:  true
        dotAll:         true
      - extract: code
        text:           '{{input}}'
        pattern:        '^```(?<language>.*)\n(?<source>.*)^```'
        multiLine:      true
        caseSensitive:  true
        dotAll:         true
      # Evaluate assertion with package:boolean_selector (already used in package:test)
      # Giving template tool authors an option to sanity check data before the template is rendered.
      - assert: 'code.hasMatch && description.hasMatch'
        message: 'Failed to extract code and language from snippet'

      # After running "steps" we've expanded context with:
      #   description.hasMatch  # True, if regex was matched
      #   description.content   # capture group content
      #   code.hasMatch         # True, if regex was matched!
      #   code.language         # capture group language
      #   code.source           # capture group source
      #
      # We can access context in the template using mustache as illustrated below:
      template: |
        {@inject-html}
        <a name="{{element.name}}.{{invocationIndex}}"></a>
        <div class="snippet snippet-container anchor-container">
          {{description}}
          <a class="anchor-button-overlay anchor-button" title="Copy link to clipboard"
            onmouseenter="fixHref(this, '{{element.name}}.{{invocationIndex}}');" onclick="fixHref(this, '{{element.name}}.{{invocationIndex}}'); copyStringToClipboard(this.href);"
            href="#">
            <i class="material-icons anchor-image">link</i>
          </a>
          <div class="copyable-container">
            <button class="copy-button-overlay copy-button" title="Copy to clipboard"
              onclick="copyTextToClipboard(findSiblingWithId(this, 'sample-code'));">
              <i class="material-icons copy-image">content_copy</i>
            </button>
            <pre class="language-{{code.language}}" id="sample-code"><code class="language-{{code.language}}">{{code.source}}</code></pre>
          </div>
        </div>
        {@end-inject-html}

Note that context can probably just be Map<String, String>, we dont' have to treat foo.bar as sub-property access :D

Note:

  • You can still do some attacks by using degenerate regular expressions
    • We could probably accept that since it'll just loop indefinitely (or be slow), or,
    • We could evaluate the regular expressions in an isolate (which is cheap).
  • Regexes gives template tool authors a lot of power to do at-least something.
  • The steps of repeated regex evaluation is a powerful, but also horrific programming environment 🤣

I'm not sure this is the way, we could also implement a small expression language that can be safely evaluated in templates. Options range form jinja-like things, to implementing a safe subset Dart in a small interpreter. To porting json-e to Dart and feeding the output into mustache.

But we could probably get far with regexes, boolean_selector for assertions and mustache for templating.


All this being explored: We should perhaps ask the question if we want people inventing their own HTML section types. Or whether it'd be better to argue that:

  • (A) users should be using existing markdown constructs (there aren't much missing).
  • (B) We should introduce dartdoc directives when good markdown constructs are missing.

In the case of snippets, it could be argued that:

  • Using a ## Section title in markdown is better than a snippet macro.
  • All sections in dartdoc markdown should have a perma-link.
  • All code blocks should have a "copy to clipboard" button.

Though, we'd have to give up the special background color for snippet section blocks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions