flux 0.0.1 → 0.0.2

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.
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"