#!/usr/bin/env ruby
# / Usage: ronn <options> <file>...
# /        ronn -m|--man <file>
# /        ronn -S|--server <file> ...
# /        ronn --pipe [<file>...]
# / Convert ronn source <file>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 <file>s at http://localhost:1207/
# /       --port <port>         run server at specified port instead of 1207
# /   -o, --output-dir <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=<date>          published date in YYYY-MM-DD format (bottom-center)
# /       --manual=<name>        name of the manual (top-center)
# /       --name=<name>          title of the manual page (top-left, top-right, bottom-right)
# /       --organization=<name>  publishing group or individual (bottom-left)
# /       --section=<sec>        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 <encoding>             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 <file> 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..] }
           .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.fetch("RONN_#{attribute.upcase}", nil)
  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 "ronn v%s (Ronn-NG)\n", Ronn::VERSION
    else
      printf "ronn v%s (rev %s) (Ronn-NG)\n", Ronn::VERSION, Ronn::REV
    end
    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
