├── LICENSE ├── README.md └── panrun /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mauro Bieg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Panrun 2 | 3 | Minimal script that runs [pandoc](http://pandoc.org/) with the options it finds in the YAML metadata of the input markdown file. For example: 4 | 5 | panrun input.md 6 | 7 | with the following `input.md`: 8 | 9 | --- 10 | title: my document 11 | output: 12 | html: 13 | standalone: true 14 | output: test.html 15 | include-in-header: 16 | - foo.css 17 | - bar.js 18 | latex: 19 | toc: true 20 | toc-depth: 3 21 | output: test.pdf 22 | template: letter.tex 23 | metadata: 24 | fontsize: 12pt 25 | --- 26 | 27 | # my content 28 | 29 | Will execute: 30 | 31 | pandoc test.md --standalone --output test.html --include-in-header foo.css --include-in-header bar.js 32 | 33 | Note how panrun defaults to using the first key in the YAML, in this case `html`. 34 | 35 | 36 | ## Usage 37 | 38 | panrun input-file [pandoc-options] 39 | 40 | You can also supply more options, but only _after_ the input file. They will be forwarded to pandoc. Panrun also looks at the `-o` (`--output`) and `-t` (`--to`) options to determine the output format. For example: 41 | 42 | panrun input.md -t latex -o test.pdf 43 | 44 | Panrun will only look at the YAML in the first input-file, but more are passed along to pandoc: 45 | 46 | panrun 01.md 02.md 03.md -o output.pdf 47 | 48 | Thus `panrun *.md` will work, as long as the YAML is found in the alphabetically first file. 49 | 50 | The input-file doesn't even have to be a markdown file. As long as it starts with a YAML block, it should work. 51 | 52 | 53 | ### Defaults and document types 54 | 55 | If you put some YAML in `~/.panrun/default.yaml` (see `panrun -h` for the Windows location), panrun will merge this with the YAML in your input file and add the `--metadata-file` option when calling pandoc. The YAML should be in the same format as always, for example: 56 | 57 | --- 58 | author: Always Me 59 | output: 60 | html: 61 | standalone: true 62 | --- 63 | 64 | Finally, you can e.g. put `type: letter` in the YAML of your input document. In that case, panrun will look for `~/.panrun/letter.yaml` instead of `default.yaml`. 65 | 66 | 67 | ## Design 68 | 69 | - Panrun should run with no dependencies except pandoc and `ruby >= 2.3.3`, which is the builtin in macOS 10.13. 70 | - Fortunately, Ruby comes with a YAML parser, which is the same one Jekyll uses. 71 | - Panrun doesn't hardcode or assume anything about the options. It simply asks your installed pandoc which options it supports (through `pandoc --bash-completion`) and ignores the unknown options in your YAML. 72 | - The idea is to be somewhat compatible with [rmarkdown's document format](https://bookdown.org/yihui/rmarkdown/output-formats.html). Therefore you can use, for example, either the `html` or `html_document` key (or even `pdf_document` or `slidy_presentation`), or either `toc-depth` or `toc_depth`, and the value of `pandoc_args` is also passed on. (However, as opposed to rmarkdown, panrun doesn't do anything more than passing on the options it finds.) Question: is this useful to anyone, or does this introduce more confusion, since a lot of rmarkdown-options will be silently ignored? 73 | - If you're looking for more than a simple wrapper script, have a look at [panzer](https://github.com/msprev/panzer) or [pandocomatic](https://github.com/htdebeer/pandocomatic). 74 | - If you're wondering whether this functionality will soon be part of pandoc itself, the answer is [probably not](https://github.com/jgm/pandoc/issues/4627#issuecomment-422108494). 75 | - Look at the source, it's really quite minimal! (In the end, I couldn't resist adding another ~40 lines of code for the defaults functionality...) 76 | - Possible TODOs: 77 | - [ ] Expand usage to `panrun [options] input.md [pandoc-options]`, so we could pass the target format to panrun without worrying about it having the same name as a pandoc format. For example, `panrun -t html_pdf input.md` could look for the `html_pdf` key in the `output` field in the YAML. 78 | - [ ] Tests 79 | - [ ] Look for non-format specific options directly in the `output` mapping? 80 | 81 | 82 | ## Installation 83 | 84 | 1. [Download panrun](https://raw.githubusercontent.com/mb21/panrun/master/panrun) 85 | 2. Place the file somewhere on your `PATH` (e.g. in `/usr/local/bin/`) 86 | 3. Make sure the file has no extension and make it executable. On macOS/Linux (for Windows [read this](https://stackoverflow.com/questions/1422380/)): 87 | 88 | chmod +x ./panrun 89 | -------------------------------------------------------------------------------- /panrun: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'yaml' 3 | 4 | # guess output_format from ARGV 5 | def get_output_format() 6 | output_file = nil 7 | ARGV.each_with_index do |a, i| 8 | if a == "-o" || a == "--output" 9 | output_file = ARGV[i+1] 10 | elsif a.start_with? "--output=" 11 | output_file = a[9..-1] 12 | end 13 | end 14 | output_format = nil 15 | ARGV.each_with_index do |a, i| 16 | if a == "-t" || a == "--to" 17 | output_format = ARGV[i+1] 18 | elsif a.start_with? "--to=" 19 | output_format = a[5..-1] 20 | end 21 | end 22 | if output_format.nil? 23 | if !output_file.nil? && output_file.include?(".") 24 | output_format = output_file.split(".").last 25 | if ["pdf", "tex"].include? output_format 26 | output_format = "latex" 27 | end 28 | end 29 | end 30 | return output_format 31 | end 32 | 33 | def get_pandoc_opts() 34 | opts = %x(pandoc --bash-completion).scan(/opts\=\"([^"]*)/).first.first 35 | 36 | # strip the two preceding dashes and ignore one-letter variants 37 | opts.split(' ').map do |o| 38 | if o[0..1] == "--" 39 | o[2..-1] 40 | end 41 | end 42 | end 43 | 44 | # convert a meta-hash to an arguments-array 45 | def get_args(meta) 46 | pandoc_opts = get_pandoc_opts() 47 | args = [] 48 | meta.each do |key, val| 49 | # check whether `key` is an option that can be 50 | # used with the installed pandoc version 51 | if pandoc_opts.include? key 52 | opt = key 53 | else 54 | # since RMarkdown YAML uses e.g. `toc_depth` instead of `toc-depth` 55 | # try that as well: 56 | key = key.gsub('_', '-') 57 | if pandoc_opts.include? key 58 | opt = key 59 | end 60 | end 61 | 62 | if opt && val != false 63 | if val.is_a? Hash 64 | val.each do |k, v| 65 | args.push "--" ++ opt 66 | args.push k ++ "=" ++ v 67 | end 68 | elsif val.is_a? Array 69 | val.each do |v| 70 | args.push "--" ++ opt 71 | args.push v 72 | end 73 | else 74 | args.push "--" ++ opt 75 | if not (val.nil? || val.is_a?(TrueClass)) 76 | # try to only include a value for an option that takes one 77 | args.push val.to_s 78 | end 79 | end 80 | end 81 | end 82 | 83 | if more_args = meta['pandoc_args'] 84 | args.concat more_args 85 | end 86 | return args 87 | end 88 | 89 | def output_key_name() 90 | "output" 91 | end 92 | 93 | def data_dir_name() 94 | if ENV['APPDATA'] 95 | # Windows 96 | File.join ENV['APPDATA'], "panrun" 97 | else 98 | # POSIX 99 | File.join Dir.home, ".panrun" 100 | end 101 | end 102 | 103 | def load_yaml(file_name) 104 | begin 105 | # try ruby v3 arguments 106 | YAML.load_file(file_name, permitted_classes:["Date"]) 107 | rescue ArgumentError 108 | # fallback to ruby v2 arguments 109 | YAML.load_file(file_name) 110 | end 111 | end 112 | 113 | # try to load default YAML from other files and merge it with local YAML 114 | def get_meta_from_other_file(meta, type=nil) 115 | if not type.is_a?(String) 116 | type = "default" 117 | end 118 | data_dir = data_dir_name() 119 | file_name = if [".", "..", "/", "\\"].include? type[0] 120 | if File.file? type 121 | type 122 | else 123 | abort "Could not find file #{type}" 124 | end 125 | else 126 | # look in ~/.panrun/ 127 | name = File.join(data_dir, type + ".yaml") 128 | if File.file? name 129 | name 130 | else 131 | nil 132 | end 133 | end 134 | file_meta, args = if file_name && m = load_yaml(file_name) 135 | [ m[output_key_name] || {}, ["--metadata-file", file_name] ] 136 | else 137 | [{}, []] 138 | end 139 | file_meta.each do |format, _| 140 | meta[format] = file_meta[format].merge( meta[format] || {} ) 141 | end 142 | return [meta, args] 143 | end 144 | 145 | 146 | # determine input file 147 | input_file = ARGV[0] 148 | if input_file.nil? || input_file[0] == "-" 149 | abort "Usage: panrun input.md [pandoc-options]\n\n"\ 150 | "Looking for default.yaml etc. in #{data_dir_name}\n"\ 151 | "For more info, see https://github.com/mb21/panrun" 152 | end 153 | 154 | # load and merge various metadata 155 | yaml = load_yaml(input_file) || {} 156 | doc_meta = yaml[output_key_name] || {} 157 | meta, file_arg = get_meta_from_other_file doc_meta, yaml["type"] 158 | 159 | # determine output format 160 | output_format = get_output_format() 161 | if output_format.nil? 162 | if meta.is_a?(Hash) && meta.first 163 | # fallback to the first output key (hashes are ordered in ruby) 164 | output_format = meta.first[0] 165 | meta_val = meta.first[1] 166 | if not meta_val["to"] || meta_val["output"] 167 | STDERR.puts "panrun: [WARNING] defaulting to the YAML for output format '#{output_format}',\n"\ 168 | " but pandoc may not default to the same format.\n"\ 169 | " It is recommended to add a `to:` or `output:` field to your YAML." 170 | end 171 | else 172 | abort "Could not find any output format in YAML." 173 | end 174 | end 175 | 176 | # lookup format in meta, else try various rmarkdown formats 177 | meta_out = if meta[output_format] 178 | meta[output_format] 179 | elsif meta[output_format + "_document"] 180 | meta[output_format + "_document"] 181 | elsif output_format == "latex" && meta["pdf_document"] 182 | meta["pdf_document"] 183 | elsif meta[output_format + "_presentation"] 184 | meta[output_format + "_presentation"] 185 | else 186 | abort "Could not find YAML key for detected output format '#{output_format}'." 187 | end 188 | 189 | args = get_args(meta_out) 190 | 191 | args = ["pandoc", input_file] + args + file_arg + ARGV[1..-1] 192 | 193 | STDERR.puts "panrun calling: " + args.join(' ') 194 | 195 | exec *args 196 | --------------------------------------------------------------------------------