kevin940726 / remark-code-import

📝 Populate code blocks from files
https://npm.im/remark-code-import
MIT License
62 stars 11 forks source link

Add support for comment markers #5

Closed jknoxville closed 3 years ago

jknoxville commented 3 years ago

Feature

Here's what I am envisioning:

Mardown usage:

```js file=./say-hi.js start=start_example end=end_example
along with file contents (say-hi.js):
```js
// some stuff
...

// start_example
function hi() {
  return "hi";
}
// end_example

...
// some other stuff

gets tranformed into:

```js file=./say-hi.js start=start_example end=end_example
function hi() {
  return "hi";
}


The use case is for documenting code by pointing to real life code snippets. You don't have to worry about the rest of the source file being modified - the imported snippet will always be whats between the markers. There's another GitHub issue here about using line numbers, that may be ok in some situations but if you're linking to source code, line numbers will become out of date so I think comment markers are a better solution.

Full language parsing support would probably be ideal, where you could say include function "hi" based on symbols, but that's a much bigger undertaking so what I have in mind is much simpler.

I think we could consider various different common comment syntax, mainly `//`, `#`. It probably doesn't even need to be language aware, if a line with contents: `// start_example` or `# start_example` is found then that would be sufficient.

---------

Is this a feature you'd be open to including? This is something I'd like to build, so it would be great to be able to merge it here rather than creating a fork.

--------

## Other considerations

Ideally, there'll be a config option to fail if the markers aren't found, or are ambiguous. This would prevent someone from accidentally breaking documentation by removing code.
kevin940726 commented 3 years ago

Thank you for opening this!

This feature does sound interesting, but I don't think that I would like this to be implemented in this plugin. The main consideration is that this plugin is meant to be simple. I want this plugin to be simple to use, with minimal magic and features, and as little configuration as possible. I would imagine a feature like this to be implemented as a different remark plugin. We should be able leverage the plugin system of remark and use them together. Use remark-code-import to copy the content of the file to the code block, and use a different plugin (maybe like remark-trim-code-block?) to trim the content of the code block. Not only does this make this plugin simple but also make it possible for the users to combine different plugins to achieve more complex situations. Does it make sense :)?

The reason why I think #3 can be included in this plugin is that it is indeed a simple enough use case, and I also don't want any more features beyond that. To be honest, I'm one the edge of that feature either, but I'm more lean on allowing that for now.

Another consideration of your proposal is that this plugin should allow any language file to be imported to the code block. // is a common comment syntax for many languages, but might be considered syntax error in the others. Not to mention that it doesn't make much sense in plain text files either. Requiring the use of a predefined syntax in the code block means that the files to be imported is "part of" this plugin, but not just any files in your file system. (For instance, sometimes we might want to run the file we're trying to import.)

jknoxville commented 3 years ago

Gotcha, that makes sense. Thanks for the response.

nafg commented 2 years ago

This would make things a lot easier for me right now. I'm porting a site from Ornate, which supports this.

There shouldn't be different start and end markers, they should be paired in the source file. You could have an open/close marker syntax, but Ornate simply uses the same tag in pairs, like this:

  //#features-composable
  // Create a query for coffee names with a price less than 10, sorted by name
  coffees.filter(_.price < 10.0).sortBy(_.name).map(_.name)
  // The generated SQL is equivalent to:
  // select name from COFFEES where PRICE < 10.0 order by NAME
  //#features-composable

I think if it occurs multiple times, all pairs are included (that is, when you hit the first occurrence, you start including lines, when you hit the 2nd, you start omitting lines, after the 3rd you include lines again, and so on). This way you can use one 'label' to pick multiple fragments. Anyway that's how Ornate works...

Anyway besides that it would be less porting work, using named regions is hugely beneficial for the simple reason that code changes over time. With line number ranges, every time the file is changed you have to remember to update the line number references, and even if you do remember, doing so can be time consuming. Named ranges are much more maintenance-friendly.

kevin940726 commented 2 years ago

@nafg As stated above, I don't think such functionality should be implemented in this plugin. Feel free to create another plugin and chain it with remark-code-import. Something like (not tested)...

function stripContentOnMarker({ marker = '#MARKER#' } = {}) {
  return (tree) => {
    visit(tree, 'code', node => {
      const lines = node.value.split(new RegExp(`\n${marker}\n?`));

      // No markers found
      if (lines.length === 1) {
        return;
      }

      // Join lines in between markers and remove others
      node.value = lines.filter((_, i) => i % 2 === 1).join('\n');
    });
  }
}

// Use it like
remark()
  // We have to first import the code that it's in the code block to be processed
  .use(remarkCodeImport)
  // Then we can run your custom plugin to strip the content
  .use(stripContentOnMarker)
  .process(yourFile)