maestro 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'maestro'
4
+ Maestro::CLI.run
@@ -0,0 +1,75 @@
1
+ require 'dispatcher'
2
+ require 'commands/command'
3
+
4
+ module Maestro
5
+ class CLI
6
+ class << self
7
+ def run
8
+ parse_options
9
+ read_config
10
+
11
+ ActiveResource::Base.logger = Logger.new($stdout) if Config[:verbose]
12
+ abort("API Key is required") unless Config[:api_key]
13
+
14
+ [ Story, Project ].each do |resource|
15
+ resource.headers['X-APIKey'] = Config[:api_key]
16
+ end
17
+
18
+ puts Maestro::Dispatcher.invoke(ARGV.shift, ARGV)
19
+ rescue Maestro::Command::RuntimeError => e
20
+ puts e.message
21
+ rescue Maestro::Command::ArgumentError => e
22
+ puts e.message
23
+ end
24
+
25
+ private
26
+
27
+ def read_config
28
+ maestro = nil
29
+ if File.exist?('.maestro.yml')
30
+ verbose "Reading options from .maestro.yml"
31
+ maestro = YAML::load(File.read('.maestro.yml'))
32
+
33
+ Config[:project_id] = maestro['project_id']
34
+ else
35
+ verbose "No options file .maestro.yml"
36
+ end
37
+ verbose "Using project ID #{Config[:project_id]}"
38
+ end
39
+
40
+ def parse_options
41
+ option_parser = OptionParser.new do |opts|
42
+ opts.banner = "Usage: maestro [options] <command> [arguments]"
43
+ opts.on("-a", "--api-key=abc123", "Specify an API key to use for access (default: ENV['MAESTRO_API_KEY'])") { |o| Config[:api_key] = o }
44
+ opts.on("-p", "--project=13", "Specify project ID (default: read from ./.maestro.yml)") { |o| Config[:project_id] = o }
45
+ opts.on("-v", "--[no-]verbose", "Verbose output") { |o| Config[:verbose] = o }
46
+
47
+ opts.on_tail("--version", "Show version") do
48
+ puts 'Maestro CLI: 0.0.1'
49
+ exit
50
+ end
51
+
52
+ opts.on_tail("-h", "--help", "Show this message") do
53
+ puts opts
54
+ puts ""
55
+ Dispatcher.commands.each do |name, command|
56
+ puts command.describe
57
+ puts ""
58
+ end
59
+ exit
60
+ end
61
+ end
62
+
63
+ begin
64
+ option_parser.parse!
65
+ rescue OptionParser::ParseError
66
+ abort("Error: #{$!}\noption_parser")
67
+ end
68
+ end
69
+
70
+ def verbose(message)
71
+ puts messages if Config[:verbose]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ module Maestro
2
+ class Command
3
+ # to differentiate between where the error originated, we only want to
4
+ # rescue from ones that are raised from a Maestro::Command
5
+ class ArgumentError < ::ArgumentError;end;
6
+ class RuntimeError < ::RuntimeError;end;
7
+
8
+ class << self
9
+ attr_reader :name
10
+ attr_reader :description
11
+ attr_reader :arguments
12
+
13
+ def command(description=nil, arguments=nil)
14
+ @name = self.to_s.split("::").last.downcase.to_sym
15
+ @description = description || ''
16
+ @arguments = arguments || {}
17
+
18
+ Maestro::Dispatcher.register self
19
+ end
20
+
21
+ def describe
22
+ "#{name} #{arguments.collect{ |a, d| "<#{a}>"}}\n\t#{description}\n\t#{arguments.collect{ |a, desc| "<#{a}> - #{desc}" }.join(", ")}"
23
+ end
24
+ end
25
+
26
+ def initialize(*args)
27
+ end
28
+
29
+ def invoke
30
+ raise RuntimeError.new("#{self.class} has no invocation!")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ class Maestro::Command::Info < Maestro::Command
2
+ command "Show info about the project"
3
+
4
+ def invoke
5
+ project = Maestro::Project.find(Maestro::Config[:project_id], :params => { :context => 'user' })
6
+ [ "#{project.name}", "#{project.email}" ].join("\n")
7
+ end
8
+ end
9
+
10
+ class Maestro::Command::Init < Maestro::Command
11
+ command "Initialize the given project in the current directory", { "project" => "Name of project to initialize" }
12
+
13
+ def initialize(project)
14
+ @project = project
15
+ end
16
+
17
+ def invoke
18
+ project = Maestro::Project.find(@project, :params => { :context => 'user' })
19
+ File.open(".maestro.yml", "w") do |io|
20
+ io.puts "project_id: #{project.id}"
21
+ end
22
+ Dir.pwd + "/.maestro.yml written successfully"
23
+ rescue ActiveResource::ResourceNotFound
24
+ raise RuntimeError.new("Could not find project \"#{@project}\". Please double-check the name on Maestro.")
25
+ end
26
+ end
@@ -0,0 +1,97 @@
1
+ class Maestro::Command::List < Maestro::Command
2
+ command "List project stories"
3
+
4
+ def invoke
5
+ Maestro::Story.find(:all, :params => { :context => 'user', :project_id => Maestro::Config[:project_id] }).collect do |story|
6
+ story.to_s
7
+ end.join("\n")
8
+ end
9
+ end
10
+
11
+ class Maestro::Command::Find < Maestro::Command
12
+ command "Find stories matching a regular expression", { 'query' => "Regular expression to use" }
13
+
14
+ def initialize(query)
15
+ @query = query
16
+ end
17
+
18
+ def invoke
19
+ stories = Maestro::Story.find(:all, :params => { :context => 'user', :project_id => Maestro::Config[:project_id] })
20
+ stories.select{ |s| s.title =~ Regexp.new(@query) }.collect do |story|
21
+ story.to_s
22
+ end.join("\n")
23
+ end
24
+ end
25
+
26
+ class Maestro::Command::StoryUpdater < Maestro::Command
27
+ private
28
+
29
+ def update(attribute, value)
30
+ story = Maestro::Story.find(@id, :params => { :context => 'user', :project_id => Maestro::Config[:project_id] })
31
+ story.update_attributes(attribute => value)
32
+ story
33
+ end
34
+ end
35
+
36
+ class Maestro::Command::Time < Maestro::Command::StoryUpdater
37
+ command "Update the actual time on the story", { 'time' => "Time spent working on story (HH:MM)" }
38
+
39
+ def initialize(id, time)
40
+ @id = id
41
+ @time = time
42
+ end
43
+
44
+ def invoke
45
+ hours, minutes = @time.split(":")
46
+ story = update :time, (hours.to_i * 60 + (minutes.to_i || 0))
47
+ story.to_s + " time: #{(story.time / 60).to_i}:#{(story.time % 60).to_i}"
48
+ end
49
+ end
50
+
51
+ class Maestro::Command::StateUpdater < Maestro::Command::StoryUpdater
52
+ class << self;attr_reader :state;end;
53
+
54
+ def initialize(id)
55
+ @id = id
56
+ end
57
+
58
+ def invoke
59
+ update_state
60
+ end
61
+
62
+ private
63
+
64
+ def update_state
65
+ update :state, self.class.state
66
+ end
67
+ end
68
+
69
+ class Maestro::Command::Start < Maestro::Command::StateUpdater
70
+ command "Start the story", { 'id' => "ID of story to start" }
71
+ @state = 'started'
72
+ end
73
+
74
+ class Maestro::Command::Finish < Maestro::Command::StateUpdater
75
+ command "Finish the story", { 'id' => "ID of story to finish" }
76
+ @state = 'finished'
77
+ end
78
+
79
+ class Maestro::Command::Deliver < Maestro::Command::StateUpdater
80
+ command "Deliver the story", { 'id' => "ID of story to deliver" }
81
+ @state = 'delivered'
82
+ end
83
+
84
+ class Maestro::Command::Accept < Maestro::Command::StateUpdater
85
+ command "Accept the story", { 'id' => "ID of story to accept" }
86
+ @state = 'accepted'
87
+ end
88
+
89
+ class Maestro::Command::Reject < Maestro::Command::StateUpdater
90
+ command "Reject the story", { 'id' => "ID of story to reject" }
91
+ @state = 'rejected'
92
+ end
93
+
94
+ class Maestro::Command::Cancel < Maestro::Command::StateUpdater
95
+ command "Cancel the story", { 'id' => "ID of story to cancel" }
96
+ @state = 'cancelled'
97
+ end
@@ -0,0 +1,24 @@
1
+ module Maestro
2
+ class Dispatcher
3
+ cattr_reader :commands
4
+ @@commands = {}
5
+
6
+ class << self
7
+ def register(command)
8
+ commands[command.name] = command
9
+ end
10
+
11
+ def invoke(name, args)
12
+ return false unless commands.has_key?(name.to_sym)
13
+ commands[name.to_sym].new(*args).invoke
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Dir.glob(File.dirname(__FILE__) + '/commands/*.rb').each do |requirement|
20
+ require requirement
21
+ end
22
+ Dir.glob(File.dirname(__FILE__) + '/resources/*.rb').each do |requirement|
23
+ require requirement
24
+ end
@@ -0,0 +1,18 @@
1
+ require 'optparse'
2
+ require 'rubygems'
3
+ require 'logger'
4
+ require 'activeresource'
5
+ require 'yaml'
6
+
7
+ $: << File.dirname(__FILE__)
8
+
9
+ module Maestro
10
+ Config = {
11
+ :api_key => ENV['MAESTRO_API_KEY'],
12
+ :project_id => nil,
13
+ :verbose => false,
14
+ :host => 'http://maestro.citrusbyte.com'
15
+ }
16
+ end
17
+
18
+ require 'cli'
@@ -0,0 +1,5 @@
1
+ module Maestro
2
+ class Project < ActiveResource::Base
3
+ self.site = Maestro::Config[:host] + "/:context"
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ module Maestro
2
+ class Story < ActiveResource::Base
3
+ self.site = Maestro::Config[:host] + "/:context/projects/:project_id"
4
+
5
+ def update_attributes(attributes={})
6
+ prefix_options[:context] = 'writer'
7
+ attributes.each do |attribute, value|
8
+ send "#{attribute}=", value
9
+ end
10
+ save
11
+ end
12
+
13
+ def to_s
14
+ "#{id}\t#{title} (#{state})" + (owner_id ? " (#{self.owner_id})" : "")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "maestro"
3
+ s.version = "0.0.1"
4
+ s.summary = "A command line interface for Maestro project management."
5
+ s.description = "A command line interface for Maestro project management."
6
+ s.authors = ["Ben Alavi"]
7
+ s.email = ["ben.alavi@citrusbyte.com"]
8
+ s.homepage = "http://maestro.citrusbyte.com/"
9
+
10
+ s.rubyforge_project = "maestro"
11
+
12
+ s.executables << "maestro"
13
+
14
+ s.add_dependency("activeresource")
15
+
16
+ s.files = ["bin/maestro", "lib/cli.rb", "lib/commands/command.rb", "lib/commands/project_commands.rb", "lib/commands/story_commands.rb", "lib/dispatcher.rb", "lib/maestro.rb", "lib/resources/project.rb", "lib/resources/story.rb", "maestro.gemspec"]
17
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maestro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Alavi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-01 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activeresource
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: A command line interface for Maestro project management.
26
+ email:
27
+ - ben.alavi@citrusbyte.com
28
+ executables:
29
+ - maestro
30
+ extensions: []
31
+
32
+ extra_rdoc_files: []
33
+
34
+ files:
35
+ - bin/maestro
36
+ - lib/cli.rb
37
+ - lib/commands/command.rb
38
+ - lib/commands/project_commands.rb
39
+ - lib/commands/story_commands.rb
40
+ - lib/dispatcher.rb
41
+ - lib/maestro.rb
42
+ - lib/resources/project.rb
43
+ - lib/resources/story.rb
44
+ - maestro.gemspec
45
+ has_rdoc: true
46
+ homepage: http://maestro.citrusbyte.com/
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project: maestro
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A command line interface for Maestro project management.
73
+ test_files: []
74
+