16

When calling thor commands on the command line, the methods are namespaced by their module/class structure, e.g.

class App < Thor
  desc 'hello', 'prints hello'
  def hello
    puts 'hello'
  end
end

would be run with the command

thor app:hello

However, if you make that self executable by putting

App.start

at the bottom you can run the command like:

app hello

Is there any way to namespace those commands? So that you could call, for example

app say:hello
app say:goodbye
philnash
  • 70,667
  • 10
  • 60
  • 88

2 Answers2

24

Another way of doing this is to use register:

class CLI < Thor
  register(SubTask, 'sub', 'sub <command>', 'Description.')
end

class SubTask < Thor
  desc "bar", "..."
  def bar()
    # ...
  end
end

CLI.start

Now - assuming your executable is called foo - you can call:

$ foo sub bar

In the current thor version (0.15.0.rc2) there is a bug though, which causes the help texts to skip the namespace of sub commands:

$ foo sub
Tasks:
   foo help [COMMAND]  # Describe subcommands or one specific subcommand
   foo bar             #

You can fix that by overriding self.banner and explicitly setting the namespace.

class SubTask < Thor
  namespace :sub

  def bar ...

  def self.banner(task, namespace = true, subcommand = false)
    "#{basename} #{task.formatted_usage(self, true, subcommand)}"
  end
end

The second parameter of formatted_usage is the only difference to the original implemtation of banner. You can also do this once and have other sub command thor classes inherit from SubTask. Now you get:

$ foo sub
Tasks:
   foo sub help [COMMAND]  # Describe subcommands or one specific subcommand
   foo sub bar             #

Hope that helps.

tfischbach
  • 3,003
  • 3
  • 22
  • 16
  • 1
    Holy crap! Where were you 8 hours of my life ago? Thanks for the elegant solution. – elmt Dec 23 '11 at 08:16
  • That didn't work for me (thor 0.18.1) but a similar work around described at https://github.com/wycats/thor/issues/261#issuecomment-16880836 did work – Robert J Berger May 09 '13 at 18:44
5

This is one way with App as the default namespace (quite hacky though):

#!/usr/bin/env ruby
require "rubygems"
require "thor"

class Say < Thor
  # ./app say:hello
  desc 'hello', 'prints hello'
  def hello
    puts 'hello'
  end
end

class App < Thor
  # ./app nothing
  desc 'nothing', 'does nothing'
  def nothing
    puts 'doing nothing'
  end
end

begin
  parts = ARGV[0].split(':')
  namespace = Kernel.const_get(parts[0].capitalize)
  parts.shift
  ARGV[0] = parts.join
  namespace.start
rescue
  App.start
end

Or, also not ideal:

define_method 'say:hello'
Ian Fleeton
  • 1,157
  • 8
  • 14
  • so there is no clean way to do this? it's just not supported? – rubiii May 11 '11 at 10:10
  • I can't say for sure. I tried to find a clean way and failed. I think the namespace method only works when using the thor command rather than standalone executable. – Ian Fleeton May 11 '11 at 14:08