mosjs / mos

:pill: A pluggable module that injects content into your markdown files via hidden JavaScript snippets
108 stars 8 forks source link

Create a plugin that will put content from files into the markdown #1

Closed zkochan closed 8 years ago

rozzzly commented 8 years ago

How about a syntax like:

<!--@snippet(show: { fileName:true, lineNumber: true, commitHash: false } )-->

[code]

<!--/@-->

which would result in something like:

...
        // convert to an Immutable (Ordered)Set
        context.childNodes = OrderedSet.of(...context.childNodes);
        // compute each `TreeNode` composing the selector within the context we just created
        context.selectors.forEach(selector => {
            // an instance of the current node we just constructed a context for
            const childNode = new TreeNode(this, selector, context);
            // pass off the nodes internal compilation to le instance just created
            childNode.compile();
        });
...

Excerpt from src/StyleTree/TreeWalker.js (lines 123-131)

Maybe even add a 'template' functionality

<!--@render('./docs/mos-templates/snippetTemplate.md', snippet())-->

[code]

<!--/@-->

You don't really even need to create your own template system. Markdown is so simple, you could pick anything up off the shelf and use it with little difficulty.

And finally, for brevity's sake, if it's just a one liner... what about a self closing tag syntax akin to HTML? <!--@doThing(param)/@--> Edit: I just realized this wouldn't be possible/feasible because you need the closing tag to determine where the injected content ends upon rewrites.

Aw man this is gonna be so cool.

zkochan commented 8 years ago

I'm really happy you've got so inspired by the idea of mos!

In your first example, I don't really understand how the code snippet is fetched. Do you put it manually inside the tags and then it is searched up in the project? Hence the full version would be like this:

<!--@snippet(show: { fileName:true, lineNumber: true, commitHash: false } )-->
```js
        // convert to an Immutable (Ordered)Set
        context.childNodes = OrderedSet.of(...context.childNodes);
        // compute each `TreeNode` composing the selector within the context we just created
        context.selectors.forEach(selector => {
            // an instance of the current node we just constructed a context for
            const childNode = new TreeNode(this, selector, context);
            // pass off the nodes internal compilation to le instance just created
            childNode.compile();
        });
``
<!--/@-->

Right now it works a little bit differently. The content of the comment-tags is just the output of the code in the first tag. E.g.

<!‐-@'# ' + package.name-‐>

my-awesome-module

!&dash;-/@-&dash;

IMHO, it would be better to not copy-paste code. Maybe the solution would be to write something like

<!--@snippet('./src/StyleTree/TreeWalker.js:123-131', {show: { fileName:true, lineNumber: true, commitHash: false }} )-->
<!--/@-->

Now you might think, what if the file was changed and the snippet is not the same at those lines? That's not a problem, because mos test will not pass and tell you that one of your templates needs an update. Mos test regenerates your templates and checks whether the new version matches the current one. If the code snippet has changed, mos test will fail and you'll have to either regenerate the markdown or update the snippet source/lines

Regarding the template idea. Could you please create a new issue and elaborate on it? Might be a good idea but it'll be hard to concentrate on 2 things in one thread :smile:

rozzzly commented 8 years ago

Thx for the quick responce. I was thinking that the opening tag would be in-lined in the source containing the code for the snippet. The opening tag begins capture, the closing tag ends it. Anything in between two tags the README.md/whatever (ie: the old content of the snippet) gets thrown away. New content gets injected. Your example which specifies the line numbers, I think, kinda defeats the purpose. Personally if I had to manually maintain line numbers, I wouldn't use this feature/package. But I think my idea still works, my example just has some flaws:

  1. assuming I'm using js, <!-- --> is not a valid escape sequence for comments
    • Solution let allow users define their own regex for escape sequences, or even just accept your syntax, even if it follows/is within in a comment literal (as seen below)
  2. If the tags inlined in the source, how does it know to render it in a .md? Moreover, where within the .md?
    • Solution have the user specify some id which is unique to the snippet they're referring to in the source file, then use the same uid in their README.md. mos scans project for tags which define a snippet matching that id, if it cant find one, then it errors. The uids would probably look something like README::BasicExample, QuickStart.InstallingReact, or chaper4/creating-your-first-repository... anything you wanted. It would be rather unobtrusive when reading the source, allow enough uniqueness to prevent collision, and semantic enough to be able to tell what refers to what.

So the example would really look like this:

README.md

bla bla this is my project
checkout this code
<!--@renderSnippet('xxxx-unique-id-xxx', { show: { fileName:true, lineNumber: true, commitHash: false } })-->
{code that's been escaped/formatted with md, optionally with a caption, gets injected here}
<!--/@-->

sourcecode

     // if it's not a function, scan the node
        if (!_.isFunction(node)) {
            // iterate over keys, split them up into their seperate types
            _.forEach(node, (child, key) => {
                if (key === 'sculpt' && _.isFunction(child)) { // custom sculpt function, can hijack the current builder
                    context.builder = child;
                } else if (_.isFunction(child)) { // child node has a function for a builder
                    context.childNodes.push([key, child]);
                } else if (_.isPlainObject(child)) { // child node has an object as a builder
                    context.childNodes.push([key, child]);
                } else if (_.isString(child)) { // child node is a string --> it must be a valid {ccsRule: cssValue}
                    context.styles.push([key, child]);
                }
            });
        } else { // its a function. lets use it to build instead
            context.builder = node;
        }
        //<!--@snippet('xxxx-unique-id-xxx')-->
        // convert to an Immutable (Ordered)Set
        context.childNodes = OrderedSet.of(...context.childNodes);
        // compute each `TreeNode` composing the selector within the context we just created
        context.selectors.forEach(selector => {
            // an instance of the current node we just constructed a context for
            const childNode = new TreeNode(this, selector, context);
            // pass off the nodes internal compilation to le instance just created
            childNode.compile();
        });
        //<!--/@-->
    }
}
export default TreeWalker;

And then the output would be the same as the example output from my previous post.


And I suppose if you wanted it to scan sourcefiles for the tags, you'd need a glob/regex to let them specify which ones.

mos.json or .mosrc

{
  "scanFiles": "**/*.+{js|md}",
  "escapeSeq": "/(<!--(?:(?!-->).)*-->)/"
}

That regex sequence is for html comments (according to SO) It also lists ones for single line comments, and multi line comments. It shouldn't be too hard to mix them together/google for more to find regexs to support all different kinds of escape sequences ---> that way this remains language agnostic so everybody wins! except for the person who gets to write the regex

zkochan commented 8 years ago

Do you really think other file types need this templating? IMHO, for this use case it can work like this

README.md

bla bla this is my project
checkout this code
<!--@snippet('./src/StyleTree/TreeWalker.js#xxxx-unique-id-xxx', { show: { fileName:true, lineNumber: true, commitHash: false } })-->
{code that's been escaped/formatted with md, optionally with a caption, gets injected here}
<!--/@-->

sourcecode

     // if it's not a function, scan the node
        if (!_.isFunction(node)) {
            // iterate over keys, split them up into their seperate types
            _.forEach(node, (child, key) => {
                if (key === 'sculpt' && _.isFunction(child)) { // custom sculpt function, can hijack the current builder
                    context.builder = child;
                } else if (_.isFunction(child)) { // child node has a function for a builder
                    context.childNodes.push([key, child]);
                } else if (_.isPlainObject(child)) { // child node has an object as a builder
                    context.childNodes.push([key, child]);
                } else if (_.isString(child)) { // child node is a string --> it must be a valid {ccsRule: cssValue}
                    context.styles.push([key, child]);
                }
            });
        } else { // its a function. lets use it to build instead
            context.builder = node;
        }
        // #xxxx-unique-id-xxx
        // convert to an Immutable (Ordered)Set
        context.childNodes = OrderedSet.of(...context.childNodes);
        // compute each `TreeNode` composing the selector within the context we just created
        context.selectors.forEach(selector => {
            // an instance of the current node we just constructed a context for
            const childNode = new TreeNode(this, selector, context);
            // pass off the nodes internal compilation to le instance just created
            childNode.compile();
        });
        // end snippet
    }
}
export default TreeWalker;

The snippet function in the readme can contain the custom logic of selecting the snippet named xxxx-unique-id-xxx from the ./src/StyleTree/TreeWalker.js file.

zkochan commented 8 years ago

Your concept might be better... But it is a pretty huge change.

How do we specify the order in which the files should be analyzed? Seems like the js file should be analyzed before the README, in order to have the snippet in some dictionary by the time the renderSnippet will be executed

Maybe we implement a simpler solution first and then think if the more robust one is needed?