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 +56 -0
- data/VERSION +1 -1
- data/flux.gemspec +87 -0
- data/lib/flux/rcs/git.rb +25 -6
- data/lib/flux/trackers/pivotal_tracker.rb +49 -32
- data/lib/flux/util.rb +1 -0
- data/lib/flux/util/output.rb +25 -0
- data/lib/flux/workflows/mojotech.rb +12 -9
- data/spec/flux/rcs/git_spec.rb +47 -0
- data/spec/flux/trackers/pivotal_tracker_spec.rb +9 -0
- metadata +8 -3
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
|
+
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
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
142
|
+
update_attributes story_id, attrs
|
126
143
|
end
|
127
144
|
end
|
128
145
|
end
|
data/lib/flux/util.rb
CHANGED
@@ -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.
|
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-
|
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:
|
154
|
+
hash: 1054297281
|
150
155
|
segments:
|
151
156
|
- 0
|
152
157
|
version: "0"
|