Skip to content
Ryan S. Northrup edited this page Jul 27, 2015 · 3 revisions
#!/usr/bin/env ruby

# First, we define a namespace for our app.  You *could*
# hypothetically skip this part and just define everything on the main
# namespace, but this makes things more clear.
module MyApp

  # Next, we define an 'Application' class that inherits from
  # Bales::Application
  class Application < Bales::Application

    # Now, we're in the heart of our bales app.  Here, inside this
    # class, we have full access to a DSL for creating our app.  Let's
    # start with defining our "root" command, which is what is called
    # by default.

    # First, we should define a version.
    version "1.2.3"

    # A summary is a short description of a command (or, in this case,
    # our app as a whole).  This is used in things like help text
    # output.
    summary "Ruby on Bales demo"

    # A description is longer than a summary.  Like summaries,
    # descriptions are also used in helptext.
    description %q( This program is a demonstration of Ruby on Bales, a
command-line application framework that combines simplicity, ease-of-use,
functionality, ergonomics, and all those other buzzwords that Ruby hackers
like to use when describing their own things. )

    # Now we'll define our action.  Not that this is a very exciting
    # action, but it's good enough for now.
    action do
      puts "Hello, world!"
    end

    # "But Ryan!" I already hear you asking.  "What if I want
    # subcommands, like in Git's or Rails' command-line tools?"  Well
    # it's a good thing I already thought of that, isn't it?
    command "foo" do
      summary "A subcommand"
      option :bar,
             type: TrueClass,
             description: "Whether or not I should bar",
             short_form: '-b'
      
      action do |bar:false|
        if bar
          puts "bar"
        else
          puts "foo"
        end
      end
    end
    # ...and with that, you now have a "foo" subcommand.

    # Lastly, we wrap it all together with a call to `parse_and_run`,
    # which is what actually causes our app to run
    parse_and_run
  end
end

# Meanwhile, let's step outside the above for a moment.  You're
# probably wondering "well, how does this all work?  Is it magic?
# Sufficiently-advanced technology?  Are you a wizard?"
#
# Well, the answer to the latter-three questions is *obviously* yes,
# but I might as well give you a taste of the sorcery involved by
# showing you the end result.  Here, let's create another subcommand,
# but this time *outside* our application's class.
class MyApp::Command::Bar < Bales::Command
  # As you might be able to guess, that 'command "foo" do ... end' you
  # saw earlier translates to a class called MyApp::Command::Foo,
  # which inherits from Bales::Command.  Meanwhile, the block is
  # important, since it places you *inside the class definition of
  # your command*.  Don't believe me?  Well, let's try some of those
  # DSL methods we were using.
  summary "foo bar baz"
  description "a b c d e f g h i j k elemeno p q r s t u v w x y z"

  option :baz,
         type: String,
         description: "the thing we should baz"

  action do |foo, **opts|
    if opts[:baz].nil?
      puts "#{foo}"
    else
      puts "#{foo} #{opts[:baz]}"
    end
  end

  # See?  Seeing *is* believing, after all.
end

# But there's another level of magic that still hasn't really been
# uncovered yet.  Let's define one last subcommand - again, using a
# direct class definition - but this time without using so many helper
# methods.
class MyApp::Command::Baz < Bales::Command
  SUMMARY="baz"
  DESCRIPTION="entirely-manually-defined subcommand"

  OPTIONS={ foo: {
              type: TrueClass,
              description: "To foo or not to foo.  That is the question."
            }
          }

  def run(**opts)
    if opts[:foo]
      puts "foo"
    else
      puts "baz"
    end
  end
end

# There's still a bit of magic involved here (in terms of how
# MyApp::Application invokes the command, parses options, etc.).  More
# details on that can be found in the RDoc-generated documentation.
#
# You can mix and match these styles to your heart's content.  Have fun!

Clone this wiki locally