# The LaTeX class.
# Note that methods with a ! in them work like the * versions of the LaTeX
# functions, and methods with a ? in them do not print a newline.
# Alternatively, you can use _star and _suppress, if you prefer a more
# English-like syntax.
class LaTeX
  def initialize()
    @nesting = 0
    @indent = "  "
  end

  # Allows the user to change the default indenting (default is two spaces
  # per nesting)
  attr_accessor :indent

  # Return the string for a newline
  def newline()
    return "\\\n"
  end

  # Return the string for a newline that won't break a page
  def newline!()
    return "\\*\n"
  end

  # Return a string for the LaTeX symbol
  def latex()
    return "\\LaTeX"
  end

  # Return a string for a comment, ended with a carriage-return
  def comment(comment)
    return "% #{comment}\n"
  end

  # Let % be an alternative way to specify a comment
  alias_method :%, :comment #,

  # Return an indented string, terminated by a newline if it isn't a comment
  # that is already newline-terminated
  def indent(str)
    s = indent?(str)
    if not s =~ /^\s*%.*\n/ then
      s << "\n"
    end
    return s
  end

  # Return an indented string that is never newline-terminated
  def indent?(str)
    s = ""
    @nesting.times do
      s << @indent
    end
    s << str
    return s
  end

  # Create a new LaTeX command
  def newcommand(cmd, func)
    method_missing("newcommand", "\\#{cmd}", func)
  end

  # Print a LaTeX command using the rules specified above
  def method_missing(symbol, *args)
    symbol = symbol.to_s.gsub('!', '*')
    symbol = symbol.to_s.gsub('_star', '*')
    nl = !(symbol =~ /(\?|_suppress)\*$/)
    symbol = symbol.gsub('\?', '')
    symbol = symbol.gsub('_suppress', '')
    s = ""
    if block_given? then
      s << __begin__(symbol, *args) << __gen_newline__(nl)
      nesting_tmp = @nesting
      @nesting = @nesting.succ
      s << proc.call()
      @nesting = nesting_tmp
      s << __end__(symbol) << __gen_newline__(nl)
    else
      s << indent?("\\#{symbol}#{__arg_list__(args)}#{__gen_newline__(nl)}")
    end
    return s
  end

  alias_method :__cmd__, :method_missing
  alias_method :__env__, :method_missing

  # Return the arguments of a LaTeX function; Use an array to specify
  # optional arguments
  def __arg_list__(args)
    s = ""
    args.each do |arg|
      case arg
      when Array
        s << "["
        arg.each do |a|
          s << "#{a},"
        end
        s.chop!
        s << "]"
      else
        s << "{#{arg}}"
      end
    end
    return s
  end

  # Return a newline if nl is true, otherwise return an empty string
  def __gen_newline__(nl)
    return nl ? "\n" : ""
  end

  def __begin__(symbol, *args)
    return indent?("\\begin{#{symbol}}#{__arg_list__(args)}")
  end

  def __end__(symbol)
    return indent?("\\end{#{symbol}}")
  end
end

if __FILE__ == $0 then
  l = LaTeX.new
  s =
  l.documentclass("article") +
  l.newcommand("foo", l.latex) +
  l.newenvironment("myitemize", l.__begin__("itemize"), l.__end__("itemize")) +
  l.document {
    l.title("#{l.LARGE?} This is a test") +
    l.author("Me") +
    l.date() +
    l.maketitle() +

    l.section("Writing #{l.foo_suppress} documents in Ruby") {
      l.indent(l % "This is a comment.") +
      l.indent("I can put text here.")
    } +

    l.section("More about #{l.foo_suppress} and Ruby") {
      l.myitemize {
        l.item?(["a)"]) + "This is item a.\n" +
        l.item?(["b)"]) + "This is item b.\n"
      }
    }
  }
  puts s
end
