#!/usr/bin/env ruby # / Usage: ronn ... # / ronn -m|--man # / ronn -S|--server ... # / ronn --pipe [...] # / Convert ronn source s to roff or HTML manpage. In the first synopsis form, # / build HTML and roff output files based on the input file names. # / # / Mode options alter the default behavior of generating files: # / --pipe write to standard output instead of generating files # / -m, --man show manual like with man(1) # / -S, --server serve s at http://localhost:1207/ # / --port run server at specified port instead of 1207 # / -o, --output-dir write generated files to specified directory # / # / Format options control which files / formats are generated: # / -r, --roff generate roff output # / -5, --html generate entire HTML page with layout # / -f, --fragment generate HTML fragment # / --markdown generate post-processed markdown output # / # / Document attributes: # / --date= published date in YYYY-MM-DD format (bottom-center) # / --manual= name of the manual (top-center) # / --name= title of the manual page (top-left, top-right, bottom-right) # / --organization= publishing group or individual (bottom-left) # / --section= section of the manual page (with name) # / # / Misc options: # / -w, --warnings show troff warnings on stderr # / -W disable previously enabled troff warnings # / --version show ronn version and exit # / --help show this help message # / -E specify the encoding files are in (default is UTF-8) # / Note: this only affects files. Data passed in to ronn's STDIN # / must be in UTF-8 regardless of the -E setting. # / # / A named example.1.ronn generates example.1.html (HTML manpage) # / and example.1 (roff manpage) by default. require 'date' require 'optparse' # Ronn wants its inputs to be in UTF-8, regardless of the user's # locale settings or Ruby version. Encoding.default_external = Encoding::UTF_8 def usage puts File.readlines(__FILE__) .grep(/^# \/.*/) .map { |line| line.chomp[4..-1] } .join("\n") end ## # Libraries and LOAD_PATH shenanigans begin require 'kramdown' require 'nokogiri' require 'ronn' rescue LoadError => e if e.to_s =~ /ronn/ libdir = File.expand_path('../lib', __dir__).sub(%r{^#{Dir.pwd}/}, './') if File.directory?(libdir) && !$LOAD_PATH.include?(libdir) # warn "warn: #{boom}. adding #{libdir} to RUBYLIB ..." $LOAD_PATH.unshift libdir retry end elsif !defined?(Gem) warn "warn: #{e}. loading rubygems ..." require 'rubygems' retry end abort e.to_s end ## # Argument defaults build = true view = false server = false port_arg = nil formats = nil encoding = 'UTF-8' options = {} write_index = false styles = %w[man] groff = 'groff -Wall -mtty-char -mandoc -Tascii -t' pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more -s' output_dir = nil ## # Environment variables %w[manual organization date].each do |attribute| value = ENV["RONN_#{attribute.upcase}"] next if value.nil? || value.empty? options[attribute] = value end ## # Argument parsing ARGV.options do |argv| # modes argv.on('--pipe') { build = server = false } argv.on('-b', '--build') { build = true; server = false } argv.on('-m', '--man') { build = server = false; view = true } argv.on('-S', '--server') { build = view = false; server = true } argv.on('-i', '--index') { write_index = true } argv.on('-o', '--output-dir=V') { |val| output_dir = val } argv.on('--port=V') { |val| port_arg = val } # format options argv.on('-r', '--roff') { (formats ||= []) << 'roff' } argv.on('-5', '--html') { (formats ||= []) << 'html' } argv.on('-f', '--fragment') { (formats ||= []) << 'html_fragment' } argv.on('--markdown') { (formats ||= []) << 'markdown' } argv.on('--yaml') { (formats ||= []) << 'yaml' } argv.on('-E', '--encoding=V') { |val| encoding = val } # html output options argv.on('-s', '--style=V') { |val| styles += val.split(/[, \n]+/) } # manual attribute options %w[name section manual organization date].each do |attribute| argv.on("--#{attribute}=VALUE") { |val| options[attribute] = val } end # misc argv.on('-w', '--warnings') { groff += ' -ww' } argv.on('-W') { groff += ' -Ww' } argv.on('-v', '--version') do require 'ronn' if Ronn.release? printf "nRonn v%s\n", Ronn::VERSION else printf "nRonn v%s (%s)\n", Ronn::VERSION, Ronn::REV end printf "https://github.com/n-ronn/nronn/tree/%s\n", Ronn.revision exit 0 end argv.on_tail('--help') { usage; exit 0 } argv.parse! end ## # Modes, Formats, Options if ARGV.empty? && $stdin.tty? usage exit 2 elsif ARGV.empty? && !server ARGV.push '-' build = false formats ||= %w[roff] elsif view formats ||= %w[roff] elsif build formats ||= %w[roff html] end formats ||= [] formats.delete('html') if formats.include?('html_fragment') options['date'] &&= Date.strptime(options['date'], '%Y-%m-%d') options['styles'] = styles options['outdir'] = output_dir options['encoding'] = encoding unless port_arg.nil? begin options['port'] = Integer(port_arg) rescue ArgumentError warn "Error: invalid port number: '#{port_arg}'" exit 1 end end ## # Server if server require 'ronn/server' Ronn::Server.run(ARGV, options) exit 0 end ## # Build Pipeline pid = nil wr = $stdout documents = ARGV.map { |file| Ronn::Document.new(file, options) } documents.each do |doc| # setup the man pipeline if the --man option was specified if view && !build rd, wr = IO.pipe pid = fork if pid rd.close else wr.close $stdin.reopen rd exec "#{groff} | #{pager}" end end # write output for each format formats.each do |fmt| if build path = doc.path_for(fmt) case fmt when 'html' warn format('%9s: %-43s%15s', fmt, path, '+' + doc.styles.join(',')) when 'roff', 'html_fragment', 'markdown' warn format('%9s: %-43s', fmt, path) end output = doc.convert(fmt) File.open(path, 'wb') { |f| f.puts(output) } if fmt == 'roff' if view system "man #{path}" else system "#{groff} <#{path} >/dev/null" end end else output = doc.convert(fmt) wr.puts(output) end end # wait for children to exit if pid wr.close Process.wait end end # Write index.txt files if write_index indexes = documents.map(&:index).uniq indexes.each do |index| File.open(index.path, 'wb') do |fd| fd.puts(index.to_text) end end end