flux 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.flux.local.rb.sample +3 -0
- data/.flux.rb.sample +4 -0
- data/Gemfile +2 -0
- data/README.md +52 -2
- data/VERSION +1 -1
- data/flux.gemspec +18 -11
- data/lib/flux.rb +27 -31
- data/lib/flux/cli.rb +3 -0
- data/lib/flux/cli/feature.rb +39 -0
- data/lib/flux/cli/pivotal_tracker.rb +72 -0
- data/lib/flux/cli/review.rb +190 -0
- data/lib/flux/git.rb +81 -0
- data/lib/flux/pivotal_tracker.rb +81 -0
- data/lib/flux/rcs.rb +1 -0
- data/lib/flux/rcs/git.rb +132 -15
- data/lib/flux/util.rb +0 -1
- data/lib/flux/workflows/mojotech.rb +23 -17
- data/spec/flux_spec.rb +17 -28
- metadata +51 -28
- data/.flux +0 -9
- data/.flux.local.sample +0 -3
- data/lib/flux/trackers/pivotal_tracker.rb +0 -146
- data/lib/flux/util/table.rb +0 -99
- data/spec/flux/rcs/git_spec.rb +0 -47
- data/spec/flux/trackers/pivotal_tracker_spec.rb +0 -164
- data/spec/flux/util/table_spec.rb +0 -47
- data/spec/support/matchers/print_table.rb +0 -46
data/.flux.rb.sample
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -46,8 +46,8 @@ This project's `.flux` file is as follows:
|
|
46
46
|
The `.flux` file should be used for project-wide configuration and kept under
|
47
47
|
version control. Obviously you aren't going to store your account credentials
|
48
48
|
there, so you need a different place where you can store them. That place is
|
49
|
-
`.flux.local`. If you're using Git
|
50
|
-
`.gitignore`.
|
49
|
+
`.flux.local`. If you're using Git (and really, why wouldn't you?), this file
|
50
|
+
should be added to the project's `.gitignore`.
|
51
51
|
|
52
52
|
Here's an example of a `.flux.local` file.
|
53
53
|
|
@@ -56,3 +56,53 @@ Here's an example of a `.flux.local` file.
|
|
56
56
|
email: david@mojotech.com
|
57
57
|
|
58
58
|
Note for Pivotal Tracker: Your API token is at https://www.pivotaltracker.com/profile
|
59
|
+
|
60
|
+
|
61
|
+
Code Reviews Workflow
|
62
|
+
---------------------
|
63
|
+
|
64
|
+
# start your feature on a branch
|
65
|
+
$ git checkout feature-branch
|
66
|
+
|
67
|
+
# code your feature
|
68
|
+
$ git commit # rinse, repeat
|
69
|
+
|
70
|
+
# make sure your code to review is available
|
71
|
+
$ git push
|
72
|
+
|
73
|
+
# request a review
|
74
|
+
$ flux branches:review feature-branch [ --parent master ]
|
75
|
+
# Note: currently, you must explicitly list your feature branch name
|
76
|
+
# This feature branch name will be used to keep track of multiple rounds
|
77
|
+
# of the review.
|
78
|
+
|
79
|
+
# flux will generate a pull request and print its URL
|
80
|
+
|
81
|
+
# Github takes sends out notices.
|
82
|
+
# other devs review and comment
|
83
|
+
|
84
|
+
# You fix up your commits.
|
85
|
+
$ git rebase -i origin/master
|
86
|
+
# I'm in your branch... rewriting your history.
|
87
|
+
|
88
|
+
# Time to resubmit for review
|
89
|
+
$ git push
|
90
|
+
$ flux request-review feature-branch [ --parent master ] [ --close ]
|
91
|
+
|
92
|
+
# flux closes old review request for feature-branch (optionally)
|
93
|
+
# flux opens a new review request for feature-branch
|
94
|
+
# The process repeats.
|
95
|
+
|
96
|
+
# Now the reviewers want to see if you addressed their concerns.
|
97
|
+
# They run:
|
98
|
+
$ flux branches:all_reviews
|
99
|
+
# (TODO: should be able to limit this report to one feature branch)
|
100
|
+
|
101
|
+
# They see: the most recent review request for each branch, but also links
|
102
|
+
# to the diffs between each previous review request for that branch.
|
103
|
+
|
104
|
+
# So, instead of repeating the entire review, they can look at just the "inter-diff"
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.5
|
data/flux.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "flux"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["David Leal"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2012-02-17"
|
13
13
|
s.email = "david@mojotech.com"
|
14
14
|
s.executables = ["flux"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
-
".flux",
|
22
|
-
".flux.
|
21
|
+
".flux.local.rb.sample",
|
22
|
+
".flux.rb.sample",
|
23
23
|
".rspec",
|
24
24
|
"Gemfile",
|
25
25
|
"LICENSE.txt",
|
@@ -29,25 +29,26 @@ Gem::Specification.new do |s|
|
|
29
29
|
"bin/flux",
|
30
30
|
"flux.gemspec",
|
31
31
|
"lib/flux.rb",
|
32
|
+
"lib/flux/cli.rb",
|
33
|
+
"lib/flux/cli/feature.rb",
|
34
|
+
"lib/flux/cli/pivotal_tracker.rb",
|
35
|
+
"lib/flux/cli/review.rb",
|
32
36
|
"lib/flux/ext/pivotal-tracker.rb",
|
37
|
+
"lib/flux/git.rb",
|
38
|
+
"lib/flux/pivotal_tracker.rb",
|
39
|
+
"lib/flux/rcs.rb",
|
33
40
|
"lib/flux/rcs/git.rb",
|
34
|
-
"lib/flux/trackers/pivotal_tracker.rb",
|
35
41
|
"lib/flux/util.rb",
|
36
42
|
"lib/flux/util/output.rb",
|
37
|
-
"lib/flux/util/table.rb",
|
38
43
|
"lib/flux/workflows/mojotech.rb",
|
39
|
-
"spec/flux/rcs/git_spec.rb",
|
40
|
-
"spec/flux/trackers/pivotal_tracker_spec.rb",
|
41
|
-
"spec/flux/util/table_spec.rb",
|
42
44
|
"spec/flux_spec.rb",
|
43
45
|
"spec/spec_helper.rb",
|
44
|
-
"spec/support/matchers/print_table.rb",
|
45
46
|
"spec/support/rr.rb"
|
46
47
|
]
|
47
48
|
s.homepage = "http://github.com/mojotech/flux"
|
48
49
|
s.licenses = ["MIT"]
|
49
50
|
s.require_paths = ["lib"]
|
50
|
-
s.rubygems_version = "1.8.
|
51
|
+
s.rubygems_version = "1.8.11"
|
51
52
|
s.summary = "Command line workflow manager."
|
52
53
|
|
53
54
|
if s.respond_to? :specification_version then
|
@@ -57,6 +58,8 @@ Gem::Specification.new do |s|
|
|
57
58
|
s.add_runtime_dependency(%q<thor>, ["~> 0.14.0"])
|
58
59
|
s.add_runtime_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
|
59
60
|
s.add_runtime_dependency(%q<grit>, ["~> 2.4.0"])
|
61
|
+
s.add_runtime_dependency(%q<hirb>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<github_api>, [">= 0"])
|
60
63
|
s.add_development_dependency(%q<rspec>, ["~> 2.0"])
|
61
64
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
65
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
@@ -66,6 +69,8 @@ Gem::Specification.new do |s|
|
|
66
69
|
s.add_dependency(%q<thor>, ["~> 0.14.0"])
|
67
70
|
s.add_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
|
68
71
|
s.add_dependency(%q<grit>, ["~> 2.4.0"])
|
72
|
+
s.add_dependency(%q<hirb>, [">= 0"])
|
73
|
+
s.add_dependency(%q<github_api>, [">= 0"])
|
69
74
|
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
70
75
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
71
76
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
@@ -76,6 +81,8 @@ Gem::Specification.new do |s|
|
|
76
81
|
s.add_dependency(%q<thor>, ["~> 0.14.0"])
|
77
82
|
s.add_dependency(%q<pivotal-tracker>, ["~> 0.4.0"])
|
78
83
|
s.add_dependency(%q<grit>, ["~> 2.4.0"])
|
84
|
+
s.add_dependency(%q<hirb>, [">= 0"])
|
85
|
+
s.add_dependency(%q<github_api>, [">= 0"])
|
79
86
|
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
80
87
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
81
88
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
data/lib/flux.rb
CHANGED
@@ -6,21 +6,34 @@ require 'yaml'
|
|
6
6
|
require 'flux/util'
|
7
7
|
|
8
8
|
module Flux
|
9
|
-
|
10
|
-
|
9
|
+
NAME = '.flux'
|
10
|
+
RC = "#{NAME}.rb"
|
11
|
+
RC_LOCAL = "#{NAME}.local.rb"
|
11
12
|
|
12
13
|
class FluxError < StandardError; end
|
13
14
|
class TrackerError < FluxError; end
|
14
15
|
|
15
16
|
class << self
|
16
|
-
|
17
|
+
def environment
|
18
|
+
@project ||= {}
|
19
|
+
end
|
17
20
|
|
18
21
|
def setup
|
19
|
-
|
22
|
+
load rc
|
23
|
+
load rc_local if File.exist?(rc_local)
|
24
|
+
|
25
|
+
require 'flux/rcs'
|
26
|
+
require 'flux/cli'
|
27
|
+
end
|
28
|
+
|
29
|
+
def rc
|
30
|
+
find_upwards(RC, Dir.pwd) or
|
31
|
+
raise FluxError, "Could not find a '#{RC}' " <<
|
32
|
+
"file in the current filesystem hierarchy."
|
33
|
+
end
|
20
34
|
|
21
|
-
|
22
|
-
|
23
|
-
}
|
35
|
+
def rc_local
|
36
|
+
find_upwards(RC_LOCAL, Dir.pwd)
|
24
37
|
end
|
25
38
|
|
26
39
|
def find_upwards(object, start_dir)
|
@@ -32,34 +45,17 @@ module Flux
|
|
32
45
|
elsif p == p.parent
|
33
46
|
nil
|
34
47
|
else
|
35
|
-
find_upwards(p.parent)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def load_adapter(kind, adapter)
|
42
|
-
adapter_path = "flux/#{kind}/#{adapter}"
|
43
|
-
|
44
|
-
begin
|
45
|
-
require adapter_path
|
46
|
-
rescue LoadError
|
47
|
-
raise FluxError, "Could not load `#{adapter_path}'."
|
48
|
+
find_upwards(object, p.parent)
|
48
49
|
end
|
49
50
|
end
|
51
|
+
end
|
52
|
+
end
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
rc_l = File.join(File.dirname(rc), RC_LOCAL)
|
56
|
-
|
57
|
-
env = YAML.load_file(rc)
|
58
|
-
env_l = File.exist?(rc_l) ? YAML.load_file(rc_l) : {}
|
54
|
+
module Kernel
|
55
|
+
def use(mod, options = {})
|
56
|
+
(Flux.environment[mod.to_s] ||= {}).
|
57
|
+
merge! Hash[*options.map { |k, v| [k.to_s, v] }.flatten]
|
59
58
|
|
60
|
-
env_l.each { |k, v| env[k].merge!(v) if env[k].respond_to?(:merge) }
|
61
59
|
|
62
|
-
env
|
63
|
-
end
|
64
60
|
end
|
65
61
|
end
|
data/lib/flux/cli.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'flux/git'
|
2
|
+
|
3
|
+
module Flux
|
4
|
+
module CLI
|
5
|
+
class Feature < Thor
|
6
|
+
namespace :feature
|
7
|
+
|
8
|
+
desc "start STORY_ID BRANCH_ID", "start working on a story"
|
9
|
+
method_option :estimate, :type => :numeric
|
10
|
+
method_option :parent_branch, :type => :string,
|
11
|
+
:default => 'master',
|
12
|
+
:aliases => '-b'
|
13
|
+
def start(story_id, branch_id)
|
14
|
+
invoke 'pt:grab', [story_id], :estimate => options[:estimate]
|
15
|
+
invoke 'pt:start', [story_id]
|
16
|
+
|
17
|
+
create_branch branch_id, story_id, options
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_branch(branch_id, story_id, options = {})
|
23
|
+
up = Git::Branch.remote('origin', branch_id)
|
24
|
+
parent = Git::Branch.local(options[:parent_branch])
|
25
|
+
branch = Git::Branch.local(branch_id).
|
26
|
+
create(parent).
|
27
|
+
publish(up).
|
28
|
+
track(up).
|
29
|
+
checkout
|
30
|
+
|
31
|
+
link_branch_to_story branch_id, story_id
|
32
|
+
end
|
33
|
+
|
34
|
+
def link_branch_to_story(branch_id, story_id)
|
35
|
+
Git::Branch.local(branch_id).config('story_id', story_id)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'pivotal-tracker'
|
2
|
+
require 'hirb'
|
3
|
+
require 'flux/pivotal_tracker'
|
4
|
+
require 'flux/ext/pivotal-tracker'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
module Flux
|
8
|
+
module CLI
|
9
|
+
class PT < Thor
|
10
|
+
extend Forwardable
|
11
|
+
include Flux::Util
|
12
|
+
|
13
|
+
namespace :pt
|
14
|
+
|
15
|
+
default_task :list
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def config
|
19
|
+
Flux.environment['pivotal_tracker']
|
20
|
+
end
|
21
|
+
|
22
|
+
def login
|
23
|
+
::PivotalTracker::Client.token = config['token']
|
24
|
+
end
|
25
|
+
|
26
|
+
def story_update(name, attrs = nil, &get_attrs)
|
27
|
+
desc "#{name} STORY_ID", "#{name} a story"
|
28
|
+
method_option :estimate, :type => :numeric, :aliases => '-e'
|
29
|
+
define_method name do |story_id|
|
30
|
+
a = attrs || instance_eval(&get_attrs)
|
31
|
+
|
32
|
+
a[:estimate] = options[:estimate] if options[:estimate]
|
33
|
+
|
34
|
+
story(story_id).update a
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
no_tasks {
|
40
|
+
def_delegator self, :config
|
41
|
+
}
|
42
|
+
|
43
|
+
login if config
|
44
|
+
|
45
|
+
desc "list", "list stories, excluding icebox by default"
|
46
|
+
def list
|
47
|
+
puts Hirb::Helpers::ObjectTable.render(
|
48
|
+
current_project.stories.scheduled,
|
49
|
+
:fields => [:id, :state, :owned_by, :est, :name]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
story_update :finish, :current_state => 'finished'
|
54
|
+
story_update(:grab) {{:owned_by => me.name}}
|
55
|
+
story_update :start, :current_state => 'started'
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def current_project
|
60
|
+
Flux::PT::Project.new(config['project_id'])
|
61
|
+
end
|
62
|
+
|
63
|
+
def me
|
64
|
+
current_project.members.find { |m| m.email == config['email'] }
|
65
|
+
end
|
66
|
+
|
67
|
+
def story(id)
|
68
|
+
current_project.stories.find(id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'flux/git'
|
3
|
+
require 'github_api'
|
4
|
+
|
5
|
+
module Flux
|
6
|
+
module CLI
|
7
|
+
class Review < Thor
|
8
|
+
namespace 'review'
|
9
|
+
|
10
|
+
desc "diff", "show interdiff between last 2 reviews"
|
11
|
+
method_option :from, :type => :numeric, :aliases => '-f'
|
12
|
+
method_option :to, :type => :numeric, :aliases => '-t'
|
13
|
+
method_option :color, :type => :boolean, :aliases => '-c'
|
14
|
+
def diff(branch_name = Git::Branch.current.name)
|
15
|
+
reqs = pull_requests(:state => 'all', :branch_name => branch_name)
|
16
|
+
|
17
|
+
if options[:from] && options[:to]
|
18
|
+
to = reqs.find { |r| r[:number].to_i == options[:to].to_i }
|
19
|
+
from = reqs.find { |r| r[:number].to_i == options[:from].to_i }
|
20
|
+
else
|
21
|
+
to, from = reqs.first(2)
|
22
|
+
end
|
23
|
+
|
24
|
+
if from
|
25
|
+
auth = [gh_config['username'], gh_config['password']]
|
26
|
+
|
27
|
+
to_diff = Tempfile.new("to_diff")
|
28
|
+
to_diff.write(open(to[:diff_url],
|
29
|
+
:http_basic_authentication => auth).read)
|
30
|
+
|
31
|
+
from_diff = Tempfile.new("from_diff")
|
32
|
+
from_diff.write(open(from[:diff_url],
|
33
|
+
:http_basic_authentication => auth).read)
|
34
|
+
|
35
|
+
cmd = "interdiff #{from_diff.path} #{to_diff.path}"
|
36
|
+
cmd << "| colordiff"
|
37
|
+
|
38
|
+
system cmd
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "request", "request a review for the current feature"
|
43
|
+
def request
|
44
|
+
branch = Git::Branch.current
|
45
|
+
story = current_project.stories.find(branch.config('story_id'))
|
46
|
+
body = {:branch_name => branch.name,
|
47
|
+
:story_url => story.url,
|
48
|
+
:iterations => iterations(branch.name)}
|
49
|
+
data = {:base => 'master',
|
50
|
+
:head => branch.commit.sha,
|
51
|
+
:title => story.name,
|
52
|
+
:body => PullRequestBody.to_markdown(body)}
|
53
|
+
|
54
|
+
gh.pull_requests.create_request gh_config['repo_user'],
|
55
|
+
gh_config['repo_name'],
|
56
|
+
data
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "list", "list pull requests for the current project"
|
60
|
+
def list
|
61
|
+
list_pull_requests pull_requests
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "history BRANCH_ID", "show review history for branch"
|
65
|
+
def history(branch_id = Git::Branch.current.name)
|
66
|
+
reqs = pull_requests(:branch_name => branch_id, :state => 'all')
|
67
|
+
|
68
|
+
list_pull_requests reqs
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def config
|
74
|
+
Flux.environment
|
75
|
+
end
|
76
|
+
|
77
|
+
def current_project
|
78
|
+
Flux::PT::Project.new(pt_config['project_id'])
|
79
|
+
end
|
80
|
+
|
81
|
+
def gh
|
82
|
+
Github.new :login => gh_config['username'],
|
83
|
+
:password => gh_config['password']
|
84
|
+
end
|
85
|
+
|
86
|
+
def gh_config
|
87
|
+
config['github']
|
88
|
+
end
|
89
|
+
|
90
|
+
def pt_config
|
91
|
+
config['pivotal_tracker']
|
92
|
+
end
|
93
|
+
|
94
|
+
def iterations(branch_name)
|
95
|
+
last = pull_requests(:branch_name => branch_name).first
|
96
|
+
|
97
|
+
if last
|
98
|
+
[last[:url]].concat(last[:body][:iterations])
|
99
|
+
else
|
100
|
+
[]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def pull_requests(opts = {})
|
105
|
+
if opts[:state] == 'all'
|
106
|
+
return pull_requests(opts.merge(:state => 'open')) +
|
107
|
+
pull_requests(opts.merge(:state => 'closed'))
|
108
|
+
end
|
109
|
+
|
110
|
+
branch_name = opts.delete(:branch_name)
|
111
|
+
|
112
|
+
reqs = gh.pull_requests.pull_requests(gh_config['repo_user'],
|
113
|
+
gh_config['repo_name'],
|
114
|
+
opts).each { |r|
|
115
|
+
r[:author] = r[:user][:login]
|
116
|
+
r[:body] = PullRequestBody.from_markdown(r[:body])
|
117
|
+
r[:story_id] = Flux::PT::Story.id_from_url(r[:body][:story_url])
|
118
|
+
}
|
119
|
+
|
120
|
+
if branch_name
|
121
|
+
reqs.select { |r| r[:body][:branch_name] == branch_name }
|
122
|
+
else
|
123
|
+
reqs
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def list_pull_requests(pull_requests)
|
128
|
+
puts Hirb::Helpers::Table.render(
|
129
|
+
pull_requests,
|
130
|
+
:fields => [:number, :title, :story_id, :author, :created_at]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
module PullRequestBody
|
135
|
+
def self.from_markdown(md)
|
136
|
+
if (md || '').include?(signoff)
|
137
|
+
_, branch_name, story_url, iter = md.
|
138
|
+
gsub(signoff, '').
|
139
|
+
split(/### [^\n]+/).map(&:strip)
|
140
|
+
|
141
|
+
if iter
|
142
|
+
_, iterations = iter.split('*').map(&:strip)
|
143
|
+
|
144
|
+
iterations = Array(iterations)
|
145
|
+
else
|
146
|
+
iterations = []
|
147
|
+
end
|
148
|
+
else
|
149
|
+
branch_name = nil
|
150
|
+
story_url = "http://mojotech.com"
|
151
|
+
iterations = []
|
152
|
+
end
|
153
|
+
|
154
|
+
{:branch_name => branch_name,
|
155
|
+
:story_url => story_url,
|
156
|
+
:iterations => iterations}
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.to_markdown(data)
|
160
|
+
tmpl = <<MARKDOWN
|
161
|
+
### Branch
|
162
|
+
|
163
|
+
#{data[:branch_name]}
|
164
|
+
|
165
|
+
### Story
|
166
|
+
|
167
|
+
#{data[:story_url]}
|
168
|
+
|
169
|
+
MARKDOWN
|
170
|
+
|
171
|
+
if data[:iterations].empty?
|
172
|
+
tmpl << signoff << "\n"
|
173
|
+
else
|
174
|
+
tmpl << <<ITERATIONS
|
175
|
+
### Prior Versions
|
176
|
+
|
177
|
+
#{"* " + data[:iterations].join("\n* ") + "\n"}
|
178
|
+
|
179
|
+
#{signoff}
|
180
|
+
ITERATIONS
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.signoff
|
185
|
+
"*Your friendly neighborhood Flux*"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|