flux 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ Flux
2
+ ====
3
+
4
+ Flux is a command-line tool that tries to eliminate the drudgery from your
5
+ workflow.
6
+
7
+ It provides hooks for interacting with the following:
8
+
9
+ * Tracker (currently only Pivotal Tracker is supported)
10
+ * RCS (currently, git only)
11
+ * Anything else you can think of, as long as someone implements it.
12
+
13
+ Some examples
14
+ -------------
15
+
16
+ Let's list all stories that have been scheduled (i.e., moved out of the icebox
17
+ and into some iteration):
18
+
19
+ $ flux stories
20
+ ID STATE ASSIGNEE STORY
21
+ 12345678 unstarted A developer can grab a story
22
+ 12345679 finished David Leal A developer can link a branch to a story
23
+ 12345680 finished David Leal A developer can mark a story as started
24
+ ...
25
+
26
+ [TODO Complete workflow. For now try `flux` to see a list of implemented tasks.]
27
+
28
+ Configuration
29
+ -------------
30
+
31
+ A project is configured by placing a file named `.flux` in the project root.
32
+ Each top level key represents a functional domain.
33
+
34
+ This project's `.flux` file is as follows:
35
+
36
+ trackers:
37
+ adapter: pivotal_tracker
38
+ project_id: 128808
39
+ rcs:
40
+ adapter: git
41
+ workflows:
42
+ adapter: mojotech
43
+
44
+ ### Local configuration
45
+
46
+ The `.flux` file should be used for project-wide configuration and kept under
47
+ version control. Obviously you aren't going to store your account credentials
48
+ there, so you need a different place where you can store them. That place is
49
+ `.flux.local`. If you're using Git, this file should be added to the project's
50
+ `.gitignore`.
51
+
52
+ Here's an example of a `.flux.local` file.
53
+
54
+ trackers:
55
+ token: abcdef1234567890
56
+ email: david@mojotech.com
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/flux.gemspec ADDED
@@ -0,0 +1,87 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{flux}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["David Leal"]
12
+ s.date = %q{2011-09-07}
13
+ s.default_executable = %q{flux}
14
+ s.email = %q{david@mojotech.com}
15
+ s.executables = ["flux"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".flux",
23
+ ".flux.local.sample",
24
+ ".rspec",
25
+ "Gemfile",
26
+ "LICENSE.txt",
27
+ "README.md",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "bin/flux",
31
+ "flux.gemspec",
32
+ "lib/flux.rb",
33
+ "lib/flux/ext/pivotal-tracker.rb",
34
+ "lib/flux/rcs/git.rb",
35
+ "lib/flux/trackers/pivotal_tracker.rb",
36
+ "lib/flux/util.rb",
37
+ "lib/flux/util/output.rb",
38
+ "lib/flux/util/table.rb",
39
+ "lib/flux/workflows/mojotech.rb",
40
+ "spec/flux/rcs/git_spec.rb",
41
+ "spec/flux/trackers/pivotal_tracker_spec.rb",
42
+ "spec/flux/util/table_spec.rb",
43
+ "spec/flux_spec.rb",
44
+ "spec/spec_helper.rb",
45
+ "spec/support/matchers/print_table.rb",
46
+ "spec/support/rr.rb"
47
+ ]
48
+ s.homepage = %q{http://github.com/mojotech/flux}
49
+ s.licenses = ["MIT"]
50
+ s.require_paths = ["lib"]
51
+ s.rubygems_version = %q{1.6.2}
52
+ s.summary = %q{Command line workflow manager.}
53
+
54
+ if s.respond_to? :specification_version then
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<thor>, ["~> 0.14.0"])
59
+ s.add_runtime_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
60
+ s.add_development_dependency(%q<grit>, ["~> 2.4.0"])
61
+ s.add_development_dependency(%q<rspec>, ["~> 2.0"])
62
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
63
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
64
+ s.add_development_dependency(%q<rcov>, [">= 0"])
65
+ s.add_development_dependency(%q<rr>, ["~> 1.0.0"])
66
+ else
67
+ s.add_dependency(%q<thor>, ["~> 0.14.0"])
68
+ s.add_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
69
+ s.add_dependency(%q<grit>, ["~> 2.4.0"])
70
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
73
+ s.add_dependency(%q<rcov>, [">= 0"])
74
+ s.add_dependency(%q<rr>, ["~> 1.0.0"])
75
+ end
76
+ else
77
+ s.add_dependency(%q<thor>, ["~> 0.14.0"])
78
+ s.add_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
79
+ s.add_dependency(%q<grit>, ["~> 2.4.0"])
80
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
81
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
82
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
83
+ s.add_dependency(%q<rcov>, [">= 0"])
84
+ s.add_dependency(%q<rr>, ["~> 1.0.0"])
85
+ end
86
+ end
87
+
data/lib/flux/rcs/git.rb CHANGED
@@ -10,16 +10,35 @@ module Flux
10
10
  repo.head.name.tap { |h| $stdout.puts h }
11
11
  end
12
12
 
13
+ desc "checkout BRANCH_ID", "checkout a branch"
14
+ def checkout(branch_id)
15
+ git.checkout({}, branch_id)
16
+ end
17
+
18
+ desc "create BRANCH_ID", "create a branch"
19
+ method_option :public, :type => :boolean, :aliases => '-p'
20
+ def create(branch_id)
21
+ git.branch({}, branch_id)
22
+
23
+ if options[:public]
24
+ git.push({}, 'origin', "+#{branch_id}:#{branch_id}")
25
+ git.branch({:set_upstream => true}, branch_id, "origin/#{branch_id}")
26
+ end
27
+ end
28
+
13
29
  private
14
30
 
31
+ def git
32
+ @git ||= Grit::Git.new(repo_path)
33
+ end
34
+
15
35
  def repo
16
- @repo ||=
17
- begin
18
- repo = Flux.find_upwards('.git', Dir.pwd) or
19
- raise RCSError, "Couldn't find git repo starting at #{Dir.pwd}."
36
+ @repo ||= Grit::Repo.new(repo_path)
37
+ end
20
38
 
21
- Grit::Repo.new(repo)
22
- end
39
+ def repo_path
40
+ Flux.find_upwards('.git', Dir.pwd) or
41
+ raise RCSError, "Couldn't find git repo starting at #{Dir.pwd}."
23
42
  end
24
43
  end
25
44
  end
@@ -23,8 +23,12 @@ module Flux
23
23
  end
24
24
 
25
25
  desc "grab STORY_ID", "assign yourself the given story"
26
+ method_option :estimate, :type => :numeric, :aliases => '-e'
26
27
  def grab(story_id)
27
- invoke :update, [story_id], :attributes => {:owner => me.name}
28
+ attrs = {:owner => me.name}
29
+ attrs[:estimate] = options[:estimate] if options[:estimate]
30
+
31
+ update_attributes story_id, attrs
28
32
  end
29
33
 
30
34
  desc "start STORY_ID", "start the given story"
@@ -43,34 +47,7 @@ module Flux
43
47
  desc "update STORY_ID", "update a story's attributes"
44
48
  method_option :attributes, :type => :hash, :required => true
45
49
  def update(story_id)
46
- story = story!(story_id)
47
- native, custom =
48
- options[:attributes].
49
- map { |k, v| [MAPPINGS[k.to_s], v] }.
50
- partition { |(k, v)| story.respond_to?("#{k}=") }
51
-
52
- native_h = Hash[*native.flatten]
53
-
54
- if native_h['current_state']
55
- unless STATES.include?(native_h['current_state'])
56
- raise TrackerError, "Invalid state: #{native_h['current_state']}"
57
- end
58
-
59
- if STATES_W_ESTIMATE.include?(native_h['current_state']) &&
60
- ! story.estimate &&
61
- ! native_h['estimate']
62
- raise TrackerError,
63
- "Need an estimate for state `#{native_h['current_state']}'."
64
- end
65
- end
66
-
67
- story.update(native_h) unless native.empty?
68
-
69
- unless custom.empty?
70
- str = YAML.dump(Hash[*custom.flatten])
71
-
72
- pt::Note.new(:owner => story, :text => str).create
73
- end
50
+ update_attributes story_id, options[:attributes]
74
51
  end
75
52
 
76
53
  private
@@ -88,11 +65,15 @@ module Flux
88
65
  :project_id => config['project_id'])
89
66
  end
90
67
 
91
- STORY_LIST_HEADERS = %w(ID STATE ASSIGNEE STORY)
68
+ STORY_LIST_HEADERS = %w(ID STATE ASSIGNEE >EST. STORY)
92
69
 
93
70
  def list_stories(stories)
94
71
  puts_table [STORY_LIST_HEADERS] + stories.map { |s|
95
- [s.id, s.current_state, s.owned_by, s.name]
72
+ [s.id,
73
+ s.current_state,
74
+ s.owned_by,
75
+ normalize_estimate(s.estimate).to_s,
76
+ s.name.strip]
96
77
  }
97
78
  end
98
79
 
@@ -106,6 +87,10 @@ module Flux
106
87
  ms.find { |m| m.email == config['email'] }
107
88
  end
108
89
 
90
+ def normalize_estimate(estimate)
91
+ (estimate && estimate < 0) ? nil : estimate
92
+ end
93
+
109
94
  def pt
110
95
  login and return ::PivotalTracker
111
96
  end
@@ -118,11 +103,43 @@ module Flux
118
103
  story(id) or raise TrackerError, "Couldn't find story #{id}."
119
104
  end
120
105
 
106
+ def update_attributes(story_id, attrs)
107
+ story = story!(story_id)
108
+ native, custom =
109
+ attrs.
110
+ map { |k, v| [MAPPINGS[k.to_s], v] }.
111
+ partition { |(k, v)| story.respond_to?("#{k}=") }
112
+
113
+ native_h = Hash[*native.flatten]
114
+
115
+ if native_h['current_state']
116
+ unless STATES.include?(native_h['current_state'])
117
+ raise TrackerError, "Invalid state: #{native_h['current_state']}"
118
+ end
119
+
120
+ if STATES_W_ESTIMATE.include?(native_h['current_state']) &&
121
+ ! normalize_estimate(story.estimate) &&
122
+ ! native_h['estimate']
123
+ raise TrackerError,
124
+ "Need an estimate for state `#{native_h['current_state']}'."
125
+ end
126
+ end
127
+
128
+ story.update(native_h) unless native.empty?
129
+
130
+ unless custom.empty?
131
+ str = YAML.dump(Hash[*custom.flatten])
132
+
133
+ pt::Note.new(:owner => story, :text => str).create
134
+ end
135
+
136
+ end
137
+
121
138
  def update_state(story_id, state)
122
139
  attrs = {:state => state}
123
140
  attrs[:estimate] = options[:estimate] if options[:estimate]
124
141
 
125
- invoke :update, [story_id], :attributes => attrs
142
+ update_attributes story_id, attrs
126
143
  end
127
144
  end
128
145
  end
data/lib/flux/util.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  require 'flux/util/table'
2
+ require 'flux/util/output'
2
3
 
@@ -0,0 +1,25 @@
1
+ module Flux
2
+ module Util
3
+ module Output
4
+ # Capture standard IO channel.
5
+ #
6
+ # @yield the block that encloses the channel capture
7
+ #
8
+ # @return everything that was written to the given channel
9
+ def capture(channel = :stdout)
10
+ io = StringIO.new
11
+ old_io = eval("$#{channel}")
12
+
13
+ eval("$#{channel} = io")
14
+
15
+ yield
16
+
17
+ io.string
18
+ ensure
19
+ eval("$#{channel} = old_io")
20
+ end
21
+
22
+ alias_method :silence, :capture
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,8 @@ module Flux
2
2
  module Workflows
3
3
  module MojoTech
4
4
  class Developer < Thor
5
+ include Util::Output
6
+
5
7
  namespace :dev
6
8
 
7
9
  desc "link STORY_ID", "link a story to a branch"
@@ -11,20 +13,21 @@ module Flux
11
13
  :attributes => {'branch' => current_branch_id}
12
14
  end
13
15
 
16
+ desc "start STORY_ID BRANCH_ID", "start working on a story"
17
+ method_option :estimate, :type => :numeric
18
+ def start(story_id, branch_id)
19
+ invoke 'stories:grab', [story_id], :estimate => options[:estimate]
20
+ invoke 'stories:start', [story_id]
21
+ invoke 'branches:create', [branch_id]
22
+ invoke 'branches:checkout', [branch_id]
23
+ invoke :link, [story_id]
24
+ end
25
+
14
26
  private
15
27
 
16
28
  def current_branch_id
17
29
  silence { @current_branch_id ||= invoke('branches:current', []) }
18
30
  end
19
-
20
- def silence
21
- old_stdout = $stdout
22
- $stdout = StringIO.new
23
-
24
- yield
25
- ensure
26
- $stdout = old_stdout
27
- end
28
31
  end
29
32
  end
30
33
  end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'flux/rcs/git'
4
+
5
+ describe Flux::RCS::Branches do
6
+ include Flux::Util::Output
7
+
8
+ it "displays the current branch" do
9
+ mock(mock_root(Grit::Repo)).head.mock!.name { 'zee_current_branch' }
10
+
11
+ retval = nil
12
+ capture { retval = subject.invoke('branches:current', []) }.
13
+ should == "zee_current_branch\n"
14
+ retval.should == 'zee_current_branch'
15
+ end
16
+
17
+ it "creates a branch" do
18
+ mock(mock_root(Grit::Git)).branch({}, 'new_branch')
19
+
20
+ subject.invoke 'branches:create', %w(new_branch)
21
+ end
22
+
23
+ it "creates a public branch" do
24
+ git = mock_root(Grit::Git)
25
+
26
+ mock(git).branch({}, 'new_branch')
27
+ mock(git).push({}, 'origin', '+new_branch:new_branch')
28
+ mock(git).branch({:set_upstream => true}, 'new_branch', 'origin/new_branch')
29
+
30
+ subject.invoke 'branches:create', %w(new_branch), :public => true
31
+ end
32
+
33
+ it "checks out a branch" do
34
+ mock(mock_root(Grit::Git)).checkout({}, 'new_branch')
35
+
36
+ subject.invoke 'branches:checkout', %w(new_branch)
37
+ end
38
+
39
+ def mock_root(klass)
40
+ repo_path = File.expand_path(File.dirname(__FILE__) + '/../../../.git')
41
+ root = Object.new
42
+
43
+ mock(klass).new(repo_path) { root }
44
+
45
+ root
46
+ end
47
+ end
@@ -40,6 +40,15 @@ describe Flux::Trackers::PivotalTracker do
40
40
  subject.invoke :grab, [123]
41
41
  end
42
42
 
43
+ it "grabs a story and estimates it" do
44
+ prepare_myself
45
+
46
+ mock_update(123, 'owned_by' => 'David Leal',
47
+ 'estimate' => 2)
48
+
49
+ subject.invoke :grab, [123], :estimate => 2
50
+ end
51
+
43
52
  [%w(starts started start),
44
53
  %w(finishes finished finish)].each do |(action, state, task)|
45
54
  it "#{action} a story that was already estimated" do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: flux
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.1
5
+ version: 0.0.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - David Leal
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-09-05 00:00:00 +01:00
13
+ date: 2011-09-07 00:00:00 +01:00
14
14
  default_executable: flux
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -109,6 +109,7 @@ extensions: []
109
109
 
110
110
  extra_rdoc_files:
111
111
  - LICENSE.txt
112
+ - README.md
112
113
  files:
113
114
  - .document
114
115
  - .flux
@@ -116,16 +117,20 @@ files:
116
117
  - .rspec
117
118
  - Gemfile
118
119
  - LICENSE.txt
120
+ - README.md
119
121
  - Rakefile
120
122
  - VERSION
121
123
  - bin/flux
124
+ - flux.gemspec
122
125
  - lib/flux.rb
123
126
  - lib/flux/ext/pivotal-tracker.rb
124
127
  - lib/flux/rcs/git.rb
125
128
  - lib/flux/trackers/pivotal_tracker.rb
126
129
  - lib/flux/util.rb
130
+ - lib/flux/util/output.rb
127
131
  - lib/flux/util/table.rb
128
132
  - lib/flux/workflows/mojotech.rb
133
+ - spec/flux/rcs/git_spec.rb
129
134
  - spec/flux/trackers/pivotal_tracker_spec.rb
130
135
  - spec/flux/util/table_spec.rb
131
136
  - spec/flux_spec.rb
@@ -146,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
151
  requirements:
147
152
  - - ">="
148
153
  - !ruby/object:Gem::Version
149
- hash: 692717009
154
+ hash: 1054297281
150
155
  segments:
151
156
  - 0
152
157
  version: "0"