5

Is there some way in ruby to edit the YAML Frontmatter at the top of a markdown file, like those used in Jekyll and Middleman?

Something like:

def update_yaml
  #magic that changes A: 1 to A: 2 in Frontmatter block
end

Then my markdown file which would change from

---
A: 1
---
# Title
Words. More words. This is the words part of the file.

to

---
A: 2
---
# Title
Words. More words. This is the words part of the file.

It seems like the only option is parsing the entire file, then rewriting the entire file with only the desired part changed but I'm hoping there is something better.

lyonsinbeta
  • 919
  • 6
  • 25

4 Answers4

4

Recently I faced the same problem, as an alternative, you can use python-frontmatter. It is easy to use. Here is the code to change the value of a yaml variable:

    import frontmatter
    import io
    with io.open('File.md', 'r') as f:
        post = frontmatter.load(f)
        post['A'] = 2

        # Save the file.
        newfile = io.open(fname, 'w', encoding='utf8')
        frontmatter.dump(post, newfile)
        newfile.close()

For more examples you can visit this page

Alejandro Alcalde
  • 5,990
  • 6
  • 39
  • 79
2

I don't know of anything better, but it's pretty simple to implement:

require "yaml"

YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m

def update_data(data)
  data.merge("A" => 2)
end

if $stdin.read =~ YAML_FRONT_MATTER_REGEXP
  data, content = YAML.load($1), Regexp.last_match.post_match
  data = update_data(data)
  YAML.dump(data, $stdout)
  $stdout.puts("---", content)
end

The above reads from $stdin and writes to $stdout (see it in action on Ideone), but in practice you'd probably want to read from a file and write to a Tempfile, and upon success replace the original file with the Tempfile (using, say, FileUtils).

If you're wondering, I stole YAML_FRONT_MATTER_REGEXP straight from Jekyll and adapted its frontmatter handling code.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
2

Yes you can do better you only have to read the YAML document from the source and stop reading at its end (---) then you process your YAML, write it out to a new file (the actually length of the data might change, so it is unlikely that you can rewrite that in place), then read the rest of input file, and write it out.

Biggest problem I see is that your ruby YAML parser will drop all comments, tag names, and possible other things that make YAML human readable, in the round-trip process.

Anthon
  • 69,918
  • 32
  • 186
  • 246
0

One of the devs from Middleman actually reached out on Twitter and provided a Middleman specific, but still really generous and helpful response. It's similar in practice to the other answers (as of time of writing) but it uses some Middleman functionality. Their response (edited to make sense in this context) is below.


If you make a script or extension you can require middleman-core/util/data which provides ::Middleman::Util::Data.parse

This will take a filename and a Middleman "source file" and a list of delimiters (the --- used in front matter) and return 2 values: a data object of the front matter and the string content of the rest of the file.

You can then modify this ruby object and write the file.

So, reading would look like:

require "middleman-core/util/data”

resource = app.sitemap.resources.find_resource_by_destination_path(“whatever.html”)

frontmatter, content = ::Middleman::Util::Data.parse(resource.file_descriptor, app.config[:frontmatter_delims])

And writing:

# change frontmatter

::File.write(resource.source_file, %Q{
---
#{frontmatter.to_yaml}
---

#{content}
})

Sorry, the data parsing stuff is a little weird (requiring a special file descriptor and config value), this stuff isn’t usually used outside of the core.

lyonsinbeta
  • 919
  • 6
  • 25