skops
skops copied to clipboard
Programmatically add sections to model card
Currently add only supports injecting values to already existing sections in a template, meanwhile add_plot can append new sections at the end of the model card and write plots. add should also support adding sections and subsections to the model card, without appending if possible. e.g. for default template, if user wants to add a subsection to training procedure (which is in the middle of the card) they should be able to do that without modifying the template themselves.
We could also document how users can do this by adding markdown markers. As in, if the user adds this as the content of a section:
this is the main content.
## Subsection
this is the subsection content.
it will be rendered as a subsection.
Can anyone assign this to me
@rushic24 Before working on this, let's first discuss how to actually implement this feature, as it's not trivial. If you already have an idea, please describe it here.
Is it neccesary for add to take multiple sections at a time?
If not, we could refactor the method to take in a single section at a time, and add an optional "parent" parameter, that would allow a workflow like:
- If parent, add under parent
- If section in template, add as normal
- Otherwise, add at end
This would need a bit of a refactor for the _generate_card method and the way self._template_sections stores sections, but should be intuitive for the user
Is it neccesary for
addto take multiple sections at a time?
I wouldn't change it, honestly, as I believe it's quite useful as is.
Perhaps there is a way, however, to keep it and still add the feature. OTOH:
card = Card(...)
card.add({
"Model description/Training Procedure/New section": "some text",
})
where "Model description" and "Training Procedure" are existing sections and "New section" is added as a subsection. This still leaves a few open questions, but it's maybe worth exploring.
Going from API to implementation, this is the bigger concern for me though. I don't think the current way we handle model cards lends itself well for this type of dynamic additions. Trying to insert sections with regex looks too brittle to me. I couldn't yet come up with a good solution that works with the current model card implementation.
Instead of string we can enable markdown rendering for card values like
card = Card(...)
card.add({
"Model description/Training Procedure/New section": """
# Test
sdjksdksdjsjks
## Subsection
""",
})
Perhaps there is a way, however, to keep it and still add the feature. OTOH: ...
I think an API like that could be powerful, but it would need some thought on handling edge cases and how to keep it intuitive for the users.
I don't think the current way we handle model cards lends itself well for this type of dynamic additions. Trying to insert sections with regex looks too brittle to me.
I definitely agree, I don't think regex would be the right approach.
I think to have robust, programatic dynamic additions, it might need a bit of an overhaul, perhaps using a markdown util library to parse the template and keep a running .md structure in a python object we can work with?
I think to have robust, programatic dynamic additions, it might need a bit of an overhaul, perhaps using a markdown util library to parse the template and keep a running .md structure in a python object we can work with?
Yes, that's something that I have also thought about. Having some container type in Python to hold the content of the model card instead of a template would give us a couple of advantages. It would be much easier to add, delete, or otherwise modify the model card.
Parsing the template didn't look trivial to me, maybe I just missed something but it looked like it would require quite some effort. Not using a template at all would be a solution, instead having aforementioned container be pre-filled by default with the same sections as the template, but the template gives users a nice thing to look at for guidance, so I'm not sure.
Instead of string we can enable markdown rendering for card values
Maybe I don't quite understand your suggestion, but your example should already work right now.
Parsing the template didn't look trivial to me, maybe I just missed something but it looked like it would require quite some effort. Not using a template at all would be a solution, instead having aforementioned container be pre-filled by default with the same sections as the template, but the template gives users a nice thing to look at for guidance, so I'm not sure.
It wouldn't be trivial, but there are some nice libraries that parse .md into a Python Object, which you can extend and work with. Python-Markdown comes to mind, and python markdown comes with utilities to edit and add to the element tree. Unsure if adding a dependency to get this functionality is the right call, but I think writing a robust .md parser from scratch that converts to an element tree we can work with and then recompiles to md would be overkill.
I could play with this and try to get a proof of concept up and running if it's a direction that you think might be worth exploring @BenjaminBossan
I could play with this and try to get a proof of concept up and running if it's a direction that you think might be worth exploring
Personally, I think having this feature could be useful, so having a POC would be nice, but before you spend too much time on it, I would like to hear if the other @skops-dev/maintainers agree.
Instead of string we can enable markdown rendering for card values like
card = Card(...) card.add({ "Model description/Training Procedure/New section": """ # Test sdjksdksdjsjks ## Subsection """, })
Sooo, this won't fix it ?
So I've played with it a bit more, and I think there are two main ways forward if we want the functionality to add sections to specific areas of existing Markdown:
-
Use a md parser that makes use of an etree under the hood, like Python-Markdown, then after adding to it recompile back to .md from HTML/XML with something like markdownify, which feels a little heavy handed but would work.
-
Write our own methods to parse the .md template into an object we can work with and then recompile in a similar way as is being done now.
I think either method would be fine, but looking at the implementation of how Python-Markdown converts .md to an etree, writing a well specced parser from scratch would likely be a big task. However, option 1 would need a lot of testing make sure that everything works after the the multiple conversion steps.
There is still the option of just inserting into the markdown string with some regex logic, which having seen what the alternatives would look like, doesn't sound as bad to me
Sooo, this won't fix it ?
Hmm, I think you need to elaborate further what the result of this call would be, at least to me it's not clear.
So I've played with it a bit more, and I think there are two main ways forward if we want the functionality to add sections to specific areas of existing Markdown:
- Use a md parser that makes use of an etree under the hood, like Python-Markdown, then after adding to it recompile back to .md from HTML/XML with something like markdownify, which feels a little heavy handed but would work.
Yes, I was afraid that it would be quite heavy handed in the end, I'm really not sure if it's worth the cost.
- Write our own methods to parse the .md template into an object we can work with and then recompile in a similar way as is being done now.
There is still the option of just inserting into the markdown string with some regex logic
Both of these will probably be quite easy for 95% of cases but getting all the edge cases right will most likely require a lot of work. As with the first option, I feel it's too costly to add this.
Another method that I considered would be:
- Drop the markdown parsing part completely. Instead, have some Python data structure that contains the content (dicts/lists) and then render it to markdown, which would have most of the benefits but circumvent the difficulties being discussed.
If we do this, we need to think about the advantages we would lose from having a md template. The template provide some defaults, but we can add those to the solution 3 as well. The template can also be inspected easily just in a browser or text editor, which this solution cannot provide (or maybe...).
I think a major downside to dropping .md templating is that is the way it seems most other HF libs handle making cards right now ( e.g here is how repo and model cards in HF_hub are handled ).
In my opinion, it would be really unintuitive if skops used a completely different way to make and template cards than other HF repos
I think a major downside to dropping .md templating is that is the way it seems most other HF libs handle making cards right now
I can see that argument, although I believe most users don't care too much about how the model card is generated under the hood, they probably care more about features and the final markdown file. However, it is true that if we diverge too far, it might become more difficult to keep parity with the "normal" way model cards are handled on HF.
There's also an argument for having model cards in scikit-learn itself. So I'd say we shouldn't worry too much about how things are done in other HF libs.
Hey! Sorry for being slow to reply, I've been working on a few other bits but I'm getting close to finishing with those, and I'd be happy to start getting a POC up for this.
Currently thinking of trying out a JSON/XML type template system, I think that would be much easier to parse into Python and work with programatically before rendering to .md
hmm, that sounds like a HUGE undertaking though 🤔
WDYT @BenjaminBossan
I agree, it really sounds like the cost would exceed the benefit in this case.
This is really a tough question. I have thought about it and here are some suggestions I came up with:
- Extremely simple parser
Write an extremely simple parser without any external dependency. It doesn't care about edge cases and works with the default we provide but not much more.
- Add second card class
It would be easier to say we add another method to create cards that is independent of the template (but still can have the same default sections + contents). This new class would use some Python data structures that would make working with it much easier and enable dynamically adding sections.
The big disadvantage here would be that now we have two classes that do very similar but not identical things (some method supported on one but not the other). This is highly undesirable.
Honestly, if we could start, I would probably advocate for the second variant and not use templates at all.
- Leave the current state
For just the feature of adding sections dynamically, the effort might not be worth it. However, I can imagine that our current, inflexible implementation will cause other headaches in the future.
Good points.
It seems we're adding functionalities which are supposed to be added by changing the template. Like, with the current design, if users want to add sections, they should actually write a new template, but that's too much work and I don't think we want users to do that.
Therefore I'm with @BenjaminBossan here that maybe using templates is not the best idea here afterall?
I agree with you both honestly.
It does feel like a pretty major rework would be needed to allow programatic additions to a template, and if we're at the point of thinking of second card class, or adding in multiple new dependencies and a lot of extra logic, I feel like it's a good sign the current implementation with templating is just too inflexible for what we want to do.
Patching what is here right now would just add extra tech debt imo, I think it would be a good idea to consider moving away from having .md templates all together.
It seems we're adding functionalities which are supposed to be added by changing the template. Like, with the current design, if users want to add sections, they should actually write a new template, but that's too much work and I don't think we want users to do that.
Yes, it just sounds too cumbersome to do this, I'd imagine many users would just dump everything to the end of the document instead. However, once a template has been modified, it can be shared easily, it would be a pity to lose that ability.
Patching what is here right now would just add extra tech debt imo, I think it would be a good idea to consider moving away from having .md templates all together.
There is probably no better time than now to still make such a change :)
The benefit of templates is that it nudges people to include certain information in the model card. But we can achieve the same by making sure those bits which are not filled are programmatically generated as empty and need work at the end of the generated file.
Maybe we could have:
- a
ModeCardclass which takes the same information as provided by the jinja templates in terms of what sections are required/suggested, but in an easier format, with sane defaults so that most users won't have to provide that - generate the whole thing programmatically, and if something's not provided, add them to the end of the card as missing
WDYT?
Maybe we could have:
a
ModeCardclass which takes the same information as provided by the jinja templates in terms of what sections are required/suggested, but in an easier format, with sane defaults so that most users won't have to provide thatgenerate the whole thing programmatically, and if something's not provided, add them to the end of the card as missing
WDYT?
Sounds good to me, at least we could give it a try and see if we feel better about it than the current approach.
Regarding the question of parsing a markdown file to a Python data structure, I wonder if we can make use of pandoc scripting to achieve this. Here is their doc on it: https://pandoc.org/scripting-1.12.html. The advantage of using pandoc is that it's widely used, so probably very bug free, and they already do the difficult parsing step. On top, pandoc could be used to read non-markdown as well.
Pandoc is haskell based, but they provide a Python package that can interpret the representation created by pandoc called pandocfilters. I haven't explored that option in depth, but it looks like it should be possible to use pandoc > pandocfilter > model card Python data structure.
Maybe we can experiment with this and see if it works. If it does, we could perhaps move that functionality to a separate package in the skops-dev space, so that users who don't need it don't suddenly have a pandoc dependency.
To give you an idea how a parsed document looks like, I ran pandoc on one of the model cards used in our unit tests. It looks a bit verbose, not sure if the output can be simplified, but regardless, it could be workable.
Output of pandoc -t json -s README.md | json_pp
{
"blocks" : [
{
"c" : [
1,
[
"model-description",
[],
[]
],
[
{
"c" : "Model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "description",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "[More",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Needed]",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
2,
[
"intended-uses-limitations",
[],
[]
],
[
{
"c" : "Intended",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "uses",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "&",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "limitations",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "[More",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Needed]",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
2,
[
"training-procedure",
[],
[]
],
[
{
"c" : "Training",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Procedure",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
3,
[
"hyperparameters",
[],
[]
],
[
{
"c" : "Hyperparameters",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "The",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "is",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "trained",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "with",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "below",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "hyperparameters.",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
"html",
""
],
"t" : "RawBlock"
},
{
"c" : [
{
"c" : [
"html",
""
],
"t" : "RawInline"
},
{
"t" : "Space"
},
{
"c" : "Click",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "to",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "expand",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : [
"html",
"
"
],
"t" : "RawInline"
}
],
"t" : "Para"
},
{
"c" : [
[],
[
{
"t" : "AlignDefault"
},
{
"t" : "AlignDefault"
}
],
[
0,
0
],
[
[
{
"c" : [
{
"c" : "Hyperparameter",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "Value",
"t" : "Str"
}
],
"t" : "Plain"
}
]
],
[
[
[
{
"c" : [
{
"c" : "copy_X",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "True",
"t" : "Str"
}
],
"t" : "Plain"
}
]
],
[
[
{
"c" : [
{
"c" : "fit_intercept",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "True",
"t" : "Str"
}
],
"t" : "Plain"
}
]
],
[
[
{
"c" : [
{
"c" : "n_jobs",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[]
],
[
[
{
"c" : [
{
"c" : "normalize",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "deprecated",
"t" : "Str"
}
],
"t" : "Plain"
}
]
],
[
[
{
"c" : [
{
"c" : "positive",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "False",
"t" : "Str"
}
],
"t" : "Plain"
}
]
]
]
],
"t" : "Table"
},
{
"c" : [
"html",
""
],
"t" : "RawBlock"
},
{
"c" : [
3,
[
"model-plot",
[],
[]
],
[
{
"c" : "Model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Plot",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "The",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "plot",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "is",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "below.",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
"html",
""
],
"t" : "RawBlock"
},
{
"c" : [
[
"sk-container-id-1",
[
"sk-top-container"
],
[
[
"style",
"overflow: auto;"
]
]
],
[
{
"c" : [
[
"",
[
"sk-text-repr-fallback"
],
[]
],
[
{
"c" : [
"html",
"LinearRegression()
"
],
"t" : "RawBlock"
},
{
"c" : [
{
"c" : [
"html",
""
],
"t" : "RawInline"
},
{
"c" : "In",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "a",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Jupyter",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "environment,",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "please",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "rerun",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "this",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "cell",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "to",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "show",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "HTML",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "representation",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "or",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "trust",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "notebook.",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : [
"html",
"
"
],
"t" : "RawInline"
},
{
"c" : "On",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "GitHub,",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "HTML",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "representation",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "is",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "unable",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "to",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "render,",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "please",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "try",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "loading",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "this",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "page",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "with",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "nbviewer.org.",
"t" : "Str"
},
{
"c" : [
"html",
""
],
"t" : "RawInline"
}
],
"t" : "Plain"
}
]
],
"t" : "Div"
},
{
"c" : [
[
"",
[
"sk-container"
],
[
[
"hidden",
""
]
]
],
[
{
"c" : [
[
"",
[
"sk-item"
],
[]
],
[
{
"c" : [
[
"",
[
"sk-estimator",
"sk-toggleable"
],
[]
],
[
{
"c" : [
{
"c" : [
"html",
""
],
"t" : "RawInline"
},
{
"c" : [
"html",
""
],
"t" : "RawInline"
}
],
"t" : "Plain"
},
{
"c" : [
[
"",
[
"sk-toggleable__content"
],
[]
],
[
{
"c" : [
"html",
"LinearRegression()
"
],
"t" : "RawBlock"
}
]
],
"t" : "Div"
}
]
],
"t" : "Div"
}
]
],
"t" : "Div"
}
]
],
"t" : "Div"
}
]
],
"t" : "Div"
},
{
"c" : [
2,
[
"evaluation-results",
[],
[]
],
[
{
"c" : "Evaluation",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Results",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "You",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "can",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "find",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "details",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "about",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "evaluation",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "process",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "and",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "evaluation",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "results.",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
[],
[
{
"t" : "AlignDefault"
},
{
"t" : "AlignDefault"
}
],
[
0,
0
],
[
[
{
"c" : [
{
"c" : "Metric",
"t" : "Str"
}
],
"t" : "Plain"
}
],
[
{
"c" : [
{
"c" : "Value",
"t" : "Str"
}
],
"t" : "Plain"
}
]
],
[]
],
"t" : "Table"
},
{
"c" : [
1,
[
"how-to-get-started-with-the-model",
[],
[]
],
[
{
"c" : "How",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "to",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Get",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Started",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "with",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Model",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "[More",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Needed]",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
1,
[
"model-card-authors",
[],
[]
],
[
{
"c" : "Model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Card",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Authors",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "This",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "card",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "is",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "written",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "by",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "following",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "authors:",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
{
"c" : "[More",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Needed]",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
1,
[
"model-card-contact",
[],
[]
],
[
{
"c" : "Model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Card",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Contact",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "You",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "can",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "contact",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "the",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "model",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "card",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "authors",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "through",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "following",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "channels:",
"t" : "Str"
},
{
"t" : "SoftBreak"
},
{
"c" : "[More",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "Needed]",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
1,
[
"citation",
[],
[]
],
[
{
"c" : "Citation",
"t" : "Str"
}
]
],
"t" : "Header"
},
{
"c" : [
{
"c" : "Below",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "you",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "can",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "find",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "information",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "related",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "to",
"t" : "Str"
},
{
"t" : "Space"
},
{
"c" : "citation.",
"t" : "Str"
}
],
"t" : "Para"
},
{
"c" : [
{
"c" : [
{
"c" : "BibTeX:",
"t" : "Str"
}
],
"t" : "Strong"
}
],
"t" : "Para"
},
{
"c" : [
[
"",
[],
[]
],
"[More Information Needed]"
],
"t" : "CodeBlock"
}
],
"meta" : {},
"pandoc-api-version" : [
1,
17,
5,
4
]
}
That sounds good to me!
@BenjaminBossan TIL of pandoc 🤩 seems very neat!
I think we can consider this being solved by #203. The parsing of model cards is yet to come, but that was not part of the initial issue.