dev_flow 0.0.4
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 +200 -0
- data/ROADMAP +15 -0
- data/Rakefile +3 -0
- data/bin/dw +82 -0
- data/dev_flow.gemspec +18 -0
- data/examples/ROADMAP +75 -0
- data/examples/ROADMAP_SPEC +75 -0
- data/examples/members.yml +4 -0
- data/lib/dev_flow/app.rb +252 -0
- data/lib/dev_flow/commands/cleanup.rb +20 -0
- data/lib/dev_flow/commands/close.rb +69 -0
- data/lib/dev_flow/commands/complete.rb +42 -0
- data/lib/dev_flow/commands/info.rb +103 -0
- data/lib/dev_flow/commands/init.rb +87 -0
- data/lib/dev_flow/commands/progress.rb +35 -0
- data/lib/dev_flow/commands/ur.rb +17 -0
- data/lib/dev_flow/girc.rb +161 -0
- data/lib/dev_flow/member.rb +10 -0
- data/lib/dev_flow/roadmap.rb +132 -0
- data/lib/dev_flow/task.rb +174 -0
- data/lib/dev_flow/task_console.rb +46 -0
- data/lib/dev_flow/version.rb +3 -0
- data/lib/dev_flow.rb +26 -0
- data/spec/app_spec.rb +16 -0
- data/spec/roadmap_spec.rb +28 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/task_spec.rb +182 -0
- data/spec/version_spec.rb +50 -0
- data/tags +105 -0
- metadata +119 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
module DevFlow
|
2
|
+
class Girc
|
3
|
+
attr_accessor :git
|
4
|
+
|
5
|
+
def initialize cmd = 'git', v = true
|
6
|
+
@git = cmd
|
7
|
+
@v = v
|
8
|
+
end
|
9
|
+
|
10
|
+
def info msg
|
11
|
+
return unless @v
|
12
|
+
puts "[GITC] #{msg}" if msg.size > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# general informations
|
16
|
+
# -----------------------
|
17
|
+
# return modified files (without additions/deletions)
|
18
|
+
def modified_files
|
19
|
+
files = Array.new
|
20
|
+
`#{@git} status`.split("\n").each do |line|
|
21
|
+
if /modified\:\s+(?<file_>.+)/ =~ line
|
22
|
+
files << File.expand_path(file_)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
files
|
26
|
+
end
|
27
|
+
|
28
|
+
# return config list as a hash
|
29
|
+
def config
|
30
|
+
h = Hash.new
|
31
|
+
`#{@git} config --list`.split("\n").each do |line|
|
32
|
+
key, value = line.split("=")
|
33
|
+
h[key] = value
|
34
|
+
end
|
35
|
+
h
|
36
|
+
end
|
37
|
+
|
38
|
+
# return the value of user.name in configuration
|
39
|
+
def me
|
40
|
+
config["user.name"] || "?"
|
41
|
+
end
|
42
|
+
|
43
|
+
# all branches include remote branches
|
44
|
+
def branches
|
45
|
+
branch_list = Array.new
|
46
|
+
`#{@git} branch -a`.split("\n").each do |line|
|
47
|
+
line.gsub!('* ', '')
|
48
|
+
line.gsub!(/\s/, '')
|
49
|
+
branch_list << line unless branch_list.include? line
|
50
|
+
end
|
51
|
+
branch_list
|
52
|
+
end
|
53
|
+
|
54
|
+
# the branch currently working on
|
55
|
+
def current_branch
|
56
|
+
`#{@git} branch`.split("\n").each do |line|
|
57
|
+
if /\*/.match line
|
58
|
+
return line.gsub('* ', '')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def remote_list
|
65
|
+
lst = Array.new
|
66
|
+
`#{@git} remote -v`.split("\n").each do |line|
|
67
|
+
rn = line.split(/\s+/)[0]
|
68
|
+
lst << rn unless lst.include? rn
|
69
|
+
end
|
70
|
+
lst
|
71
|
+
end
|
72
|
+
|
73
|
+
# is the working directory has modified file
|
74
|
+
def wd_clean?
|
75
|
+
clean = true
|
76
|
+
`#{@git} status`.split("\n").each do |line|
|
77
|
+
clean = false if /Changes/.match line
|
78
|
+
end
|
79
|
+
clean
|
80
|
+
end
|
81
|
+
|
82
|
+
# whether the current directory is a git working directory
|
83
|
+
def in_git_dir?
|
84
|
+
`#{@git} status` =~ /fatal/ ? false : true
|
85
|
+
end
|
86
|
+
|
87
|
+
# modifications
|
88
|
+
# --------------------
|
89
|
+
|
90
|
+
# pull from the remote use fetch/merge
|
91
|
+
def pull! remote = 'origin'
|
92
|
+
cb = self.current_branch
|
93
|
+
info "Fetch from #{remote}"
|
94
|
+
rslt = `#{@git} fetch #{remote}`
|
95
|
+
raise "fetch failed with message: #{rslt}" unless $?.success?
|
96
|
+
info rslt
|
97
|
+
info `#{@git} merge #{remote}/#{cb}`
|
98
|
+
end
|
99
|
+
|
100
|
+
# create a new branch, if remote set, push it to remote too
|
101
|
+
# then switch to that branch
|
102
|
+
def new_branch! branch, remote=nil
|
103
|
+
raise "You need clean up you working directory" unless wd_clean?
|
104
|
+
raise "Branch #{branch} already exists" if self.branches.include? branch
|
105
|
+
`#{@git} checkout -b #{branch}`
|
106
|
+
`#{@git} push #{remote} #{branch}` if remote
|
107
|
+
end
|
108
|
+
|
109
|
+
# delete a branch
|
110
|
+
def del_branch! branch, remote=nil
|
111
|
+
rslt = `#{@git} branch -d #{branch}`
|
112
|
+
raise "Cat not delete branch #{branch}: #{rslt}" unless $?.success?
|
113
|
+
`#{@git} push #{remote} :#{branch}` if remote
|
114
|
+
end
|
115
|
+
|
116
|
+
def stash!
|
117
|
+
unless wd_clean?
|
118
|
+
info "Save you change to stash"
|
119
|
+
`#{@git} add .`
|
120
|
+
`#{@git} stash`
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def stash_pop!
|
125
|
+
raise "You may clean up you work directroy first before pop out from the stash" unless wd_clean?
|
126
|
+
info "Pop out from you last stash"
|
127
|
+
`#{@git} stash pop`
|
128
|
+
end
|
129
|
+
|
130
|
+
# remote from a specified remote ref
|
131
|
+
def rebase! remote = 'origin', branch = 'develop'
|
132
|
+
cb = self.current_branch
|
133
|
+
stashed = false
|
134
|
+
unless self.wd_clean?
|
135
|
+
self.stash!
|
136
|
+
stashed = true
|
137
|
+
end
|
138
|
+
|
139
|
+
if branch == self.current_branch
|
140
|
+
info "Rebase pull from remote"
|
141
|
+
`#{@git} pull --rebase #{remote} #{branch}`
|
142
|
+
else
|
143
|
+
info "Switch to branch #{branch}"
|
144
|
+
`#{@git} fetch #{remote}`
|
145
|
+
rslt = `#{@git} checkout #{branch}`
|
146
|
+
raise "Checkout failed: #{rslt}" unless $?.success?
|
147
|
+
info "Update (rabase) branch"
|
148
|
+
rslt = `#{@git} pull --rebase #{remote} #{branch}`
|
149
|
+
raise "Rebase pull for #{branch} failed: #{rslt}" unless $?.success?
|
150
|
+
info "Switch back to branch #{cb}"
|
151
|
+
`#{@git} checkout #{cb}`
|
152
|
+
info "Rebase from #{branch}"
|
153
|
+
rslt = `#{@git} rebase #{branch}`
|
154
|
+
raise "Rebase with #{branch} failed: #{rslt}" unless $?.success?
|
155
|
+
end
|
156
|
+
|
157
|
+
self.stash_pop! if stashed
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module DevFlow
|
2
|
+
## a road map represents a list of tasks
|
3
|
+
class RoadMap
|
4
|
+
attr_accessor :file, :config, :tasks,
|
5
|
+
:branch_tasks, # branch name to task hash
|
6
|
+
:ln_tasks, # line number to task hash, used for rewrite
|
7
|
+
:top_tasks # level 1 task list (used for id calculation)
|
8
|
+
|
9
|
+
def initialize file, config
|
10
|
+
@file, @config = file, config
|
11
|
+
@tasks = Array.new
|
12
|
+
@branch_tasks = Hash.new
|
13
|
+
@ln_tasks = Hash.new
|
14
|
+
@top_tasks = Array.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def last_task
|
18
|
+
@tasks.last
|
19
|
+
end
|
20
|
+
|
21
|
+
def title
|
22
|
+
@config[:title]
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse file = nil
|
26
|
+
self.file = file if file
|
27
|
+
fh = File.open(self.file, "r:utf-8")
|
28
|
+
head_part = ""
|
29
|
+
in_header = 0
|
30
|
+
fh.each do |line|
|
31
|
+
if /^\%\s*\-\-\-+/ =~ line
|
32
|
+
in_header += 1
|
33
|
+
next
|
34
|
+
end
|
35
|
+
|
36
|
+
# before any task defined, parse line begin with % as head field:
|
37
|
+
if in_header == 1 and @tasks.size == 0
|
38
|
+
head_part += line
|
39
|
+
end
|
40
|
+
|
41
|
+
if /^\s*\[(?<plus_>[\+\s]+)\]\s(?<contents_>.+)/ =~ line
|
42
|
+
if @tasks.size == 0 and head_part.size > 0
|
43
|
+
hhash = YAML.load(head_part)
|
44
|
+
members = @config["members"] || {}
|
45
|
+
members.merge!(hhash["members"]) if hhash["members"]
|
46
|
+
@config = @config.merge hhash
|
47
|
+
@config["members"] = members
|
48
|
+
end
|
49
|
+
line.chomp!
|
50
|
+
task = Task.new(plus_.to_s.count("+"), self.file, $.).parse(contents_, @config)
|
51
|
+
task.validate! # raise for format errors
|
52
|
+
raise "branch name #{task.branch_name} already used on #{self.file}:#{self.branch_tasks[task.branch_name].ln}" if self.branch_tasks[task.branch_name]
|
53
|
+
if task.is_a?(Task)
|
54
|
+
# find perant for the task:
|
55
|
+
parent = self.find_parent task.level
|
56
|
+
if parent
|
57
|
+
parent.children << task
|
58
|
+
task.parent = parent
|
59
|
+
# task.id = (sprintf "%d%d", parent.id, parent.child_number).to_i
|
60
|
+
end
|
61
|
+
@tasks << task
|
62
|
+
@branch_tasks[task.branch_name] = task
|
63
|
+
@ln_tasks[task.ln] = task
|
64
|
+
@top_tasks << task if task.level == 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
fh.close
|
69
|
+
|
70
|
+
# check and set dependencies
|
71
|
+
self.tasks.each do |task|
|
72
|
+
task.dependencie_names.each do |branch|
|
73
|
+
d_task = @branch_tasks[branch]
|
74
|
+
raise "task #{task.branch_name} (#{task.file}:#{task.ln}) has dependency #{branch} not found on the file" unless d_task
|
75
|
+
task.dependencies << d_task
|
76
|
+
end
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
## the last task in row that less than the given level is parent task
|
82
|
+
def find_parent level
|
83
|
+
self.tasks.reverse.each { |t| return t if t.level < level }
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def rewrite! task_hash
|
88
|
+
# task_hash: {task_line_as_integer => progress_as_integer}
|
89
|
+
task_hash.each do |ln, progress|
|
90
|
+
raise "invalid line number #{ln}" unless ln.to_s =~ /^\d+$/ and ln > 0
|
91
|
+
raise "invalid progress #{progress}" unless progress.to_s =~ /^\d+$/ and progress > 0 and progress <= 100
|
92
|
+
end
|
93
|
+
|
94
|
+
file = self.file
|
95
|
+
tmp_file = self.file + ".tmp"
|
96
|
+
|
97
|
+
# backup the file to tmp_file
|
98
|
+
FileUtils.mv file, tmp_file
|
99
|
+
tfh = File.open(tmp_file, "r:utf-8")
|
100
|
+
wfh = File.open(file, "w:utf-8")
|
101
|
+
|
102
|
+
tfh.each do |line|
|
103
|
+
if task_hash[$.]
|
104
|
+
progress = task_hash[$.]
|
105
|
+
task = @ln_tasks[$.]
|
106
|
+
|
107
|
+
if progress == 100
|
108
|
+
com_date = DateTime.now.strftime("%Y/%m/%d")
|
109
|
+
com_date = DateTime.now.strftime("%m/%d") if DateTime.now.year == @config["year"]
|
110
|
+
progress = com_date
|
111
|
+
end
|
112
|
+
|
113
|
+
new_line = line
|
114
|
+
if /(?<resource_>\@[a-z\@\;]+)(\:[PD\d\/]+)?/ =~ line
|
115
|
+
new_line.gsub!(/(?<resource_>\@[a-z\@\;]+)(\:[PD\d\/]+)?/, resource_ + ":" + progress.to_s)
|
116
|
+
elsif /(?<dep_>\-\>.+)$/ =~ line
|
117
|
+
new_line.gsub!(/\s*\-\>.+$/, '@' + self.headers["leader"] + ":" + progress.to_s + " " + dep_)
|
118
|
+
else
|
119
|
+
new_line += '@' + self.headers["leader"] + ":" + progress.to_s
|
120
|
+
end
|
121
|
+
wfh.puts new_line
|
122
|
+
else
|
123
|
+
wfh.puts line
|
124
|
+
end
|
125
|
+
end
|
126
|
+
tfh.close
|
127
|
+
wfh.close
|
128
|
+
|
129
|
+
FileUtils.rm tmp_file
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# representations for a task in roadmap and in Gantt chart.
|
2
|
+
# ==========================================================
|
3
|
+
#
|
4
|
+
# @author: Huang Wei <huangw@pe-po.com>
|
5
|
+
# version 1.0a
|
6
|
+
|
7
|
+
module DevFlow
|
8
|
+
## Task object represent a single line on the Gantt chart
|
9
|
+
class Task
|
10
|
+
attr_accessor :file, :ln, # which line of file defined the task
|
11
|
+
:level, :branch_name, :display_name, :resources, :resource_names,
|
12
|
+
:progress, :completed_at, # complete date time
|
13
|
+
:dependencies, # depends on those tasks (list of branch_names)
|
14
|
+
:dependencie_names, # denpendencies in branch names (for display)
|
15
|
+
:start_date, :end_date,
|
16
|
+
:parent, :children, # all in branch_names first
|
17
|
+
:is_pending, :is_deleted
|
18
|
+
|
19
|
+
# initialize with level, file and line number
|
20
|
+
def initialize level,file="-",ln=0
|
21
|
+
@level = level.to_i
|
22
|
+
raise "invalid level #{level}" unless level.to_i > 0
|
23
|
+
@file, @ln = file, ln
|
24
|
+
@children = Array.new
|
25
|
+
@dependencies = Array.new
|
26
|
+
@dependencie_names = Array.new
|
27
|
+
@progress = 0
|
28
|
+
@resources = Array.new
|
29
|
+
@resource_names = Array.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# filter methods
|
33
|
+
def is_milestone?
|
34
|
+
self.branch_name =~ /^(milestone|release)\_/ ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_release?
|
38
|
+
self.branch_name =~ /^release\_/ ? true : false
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_completed?
|
42
|
+
self.progress == 100
|
43
|
+
end
|
44
|
+
|
45
|
+
def is_pending?
|
46
|
+
self.is_pending ? true :false
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_deleted?
|
50
|
+
self.is_deleted ? true : false
|
51
|
+
end
|
52
|
+
|
53
|
+
def is_parent?
|
54
|
+
self.children.size > 0 ? true : false
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_urgent? # usually orange
|
58
|
+
today = DateTime.now.strftime("%Y%m%d").to_i
|
59
|
+
start_day = self.start_date.strftime("%Y%m%d").to_i
|
60
|
+
end_day = self.end_date.strftime("%Y%m%d").to_i
|
61
|
+
|
62
|
+
return true if start_day == today and progress == 0
|
63
|
+
return true if end_day == today and self.progress < 100
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def is_delayed? # usually red
|
68
|
+
today = DateTime.now.strftime("%Y%m%d").to_i
|
69
|
+
start_day = self.start_date.strftime("%Y%m%d").to_i
|
70
|
+
end_day = self.end_date.strftime("%Y%m%d").to_i
|
71
|
+
return true if self.progress < 100 and today > end_day
|
72
|
+
return true if self.progress == 0 and today > start_day
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
## check whether the task is well defined, raise error otherwise.
|
77
|
+
def validate!
|
78
|
+
if self.is_milestone?
|
79
|
+
unless self.level == 1
|
80
|
+
raise "you can only tag a top level task as a release, on #{self.file}:#{self.ln}" if self.branch_name =~ /^release\_/
|
81
|
+
end
|
82
|
+
end
|
83
|
+
raise "resource not found on #{self.file}:#{self.ln}" unless self.resources.size > 0
|
84
|
+
raise "display_name not found on #{self.file}:#{self.ln}" unless self.display_name
|
85
|
+
raise "valid start_date not found on #{self.file}:#{self.ln}" unless self.start_date
|
86
|
+
raise "wrong date on #{self.file}:#{self.ln}" unless self.start_date and self.end_date and self.start_date <= self.end_date
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
## parse the line from file:ln (line number), initialize the
|
92
|
+
# task object
|
93
|
+
def parse line, headers = {}
|
94
|
+
line = line.strip # delete head/trailing spaces
|
95
|
+
|
96
|
+
/^((?<branch_name_>[a-zA-Z0-9_\-\#\.\/]+):)?\s*(?<display_name_>.+)\s+(?<start_date_>(\d\d\d\d\/)?\d\d?\/\d\d?)(-(?<end_date_>(\d\d\d\d\/)?\d\d?\/\d\d?))?(\s+\@(?<resource_>[a-z\@\;]+))?(\:(?<status_>(P|D))?(?<progress_>[\d\/]+)?)?(\s+\-\>\s*(?<dependencies_>.+))?$/ =~ line
|
97
|
+
|
98
|
+
# assign branch name and display name
|
99
|
+
self.branch_name = branch_name_
|
100
|
+
|
101
|
+
self.display_name = display_name_
|
102
|
+
|
103
|
+
# assign start and end date
|
104
|
+
end_date_ = start_date_ unless end_date_
|
105
|
+
raise "no valid start date found on #{self.file}:#{self.ln}" unless start_date_ and start_date_.size > 0
|
106
|
+
if headers["year"]
|
107
|
+
start_date_ = headers["year"].to_s + "/" + start_date_ unless start_date_ =~ /^\d\d\d\d/
|
108
|
+
end_date_ = headers["year"].to_s + "/" + end_date_ unless end_date_ =~ /^\d\d\d\d/
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
self.start_date = DateTime.parse(start_date_)
|
113
|
+
self.end_date = DateTime.parse(end_date_)
|
114
|
+
rescue Exception => e
|
115
|
+
raise "invalid date on #{self.file}:#{self.ln}"
|
116
|
+
end
|
117
|
+
|
118
|
+
# assign for the resources (user name)
|
119
|
+
unless resource_
|
120
|
+
if headers["leader"]
|
121
|
+
resource_ = headers["leader"]
|
122
|
+
else
|
123
|
+
raise "no resource defined on #{self.file}:#{self.ln}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
resource_.gsub!("\@", "") # @ for other user is optional
|
128
|
+
resource_.split(";").each do |r|
|
129
|
+
self.resources << r
|
130
|
+
# if the user is listed on known members
|
131
|
+
rname = r
|
132
|
+
if headers["members"] and headers["members"][r] and headers["members"][r][0]
|
133
|
+
rname = headers["members"][r][0]
|
134
|
+
end
|
135
|
+
self.resource_names << rname
|
136
|
+
end
|
137
|
+
|
138
|
+
if dependencies_
|
139
|
+
dependencies_.strip!
|
140
|
+
dependencies_.gsub!(/;$/, "")
|
141
|
+
self.dependencie_names = dependencies_.split(/;/)
|
142
|
+
end
|
143
|
+
|
144
|
+
# pending or deleted status
|
145
|
+
if status_
|
146
|
+
if status_ == "P"
|
147
|
+
self.is_pending = true
|
148
|
+
elsif status_ == "D"
|
149
|
+
self.is_deleted = true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# progress
|
154
|
+
if progress_
|
155
|
+
if progress_ =~ /^\d\d?$/ and progress_.to_i > 0 and progress_.to_i < 100
|
156
|
+
self.progress = progress_.to_i
|
157
|
+
elsif progress_ =~ /^\d\d\d\d\/\d\d?\/\d\d?$/
|
158
|
+
self.progress = 100
|
159
|
+
self.completed_at = DateTime.parse(progress_)
|
160
|
+
elsif progress_ =~ /^\d\d?\/\d\d?$/ and headers["year"]
|
161
|
+
self.progress = 100
|
162
|
+
self.completed_at = DateTime.parse(headers["year"].to_s + "/" + progress_.to_s)
|
163
|
+
else
|
164
|
+
msg = "worng format around progress parameter '#{progress_}', on #{self.file}:#{self.ln}"
|
165
|
+
msg += " (HINT: use date to complete a task, not 100)" if progress_.to_i == 100
|
166
|
+
raise msg
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
end # class Task
|
174
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module DevFlow
|
2
|
+
class Task
|
3
|
+
|
4
|
+
def as_title header = ' '
|
5
|
+
name = self.display_name
|
6
|
+
name = self.display_name.bold if self.is_workable?
|
7
|
+
name = self.display_name.blue if self.progress > 0
|
8
|
+
name = self.display_name.green if self.is_completed?
|
9
|
+
name = self.display_name.magenta if self.is_urgent?
|
10
|
+
name = self.display_name.red if self.is_delayed?
|
11
|
+
|
12
|
+
if self.progress > 0 and self.progress < 100
|
13
|
+
on_branch = sprintf "(=> %s, %02d%%)", self.branch_name.bold, self.progress
|
14
|
+
end
|
15
|
+
|
16
|
+
title = sprintf("%s[%s]%s%s", ' '*(self.level-1), header, name, on_branch)
|
17
|
+
end
|
18
|
+
|
19
|
+
## a task is completable if all children complated
|
20
|
+
def is_completable?
|
21
|
+
return false if self.is_completed?
|
22
|
+
self.children.each do |child|
|
23
|
+
return false unless child.is_completed?
|
24
|
+
end
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
## a task is workable (can be started) if all children
|
29
|
+
# task and dependent task are completed
|
30
|
+
def is_workable?
|
31
|
+
# trivial: if already completed, do not start again
|
32
|
+
return false if self.is_completed?
|
33
|
+
return false if self.is_pending or self.is_deleted
|
34
|
+
|
35
|
+
self.dependencies.each do |t|
|
36
|
+
return false unless t.is_completed?
|
37
|
+
end
|
38
|
+
|
39
|
+
self.children.each do |t|
|
40
|
+
return false unless t.is_completed?
|
41
|
+
end
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/dev_flow.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
# model layer
|
6
|
+
require 'dev_flow/task'
|
7
|
+
require 'dev_flow/roadmap'
|
8
|
+
require 'dev_flow/member'
|
9
|
+
|
10
|
+
# presenter
|
11
|
+
require 'dev_flow/task_console'
|
12
|
+
|
13
|
+
# application and commands
|
14
|
+
require 'dev_flow/app'
|
15
|
+
require 'dev_flow/version'
|
16
|
+
|
17
|
+
# other helper and libraries
|
18
|
+
require 'dev_flow/girc'
|
19
|
+
|
20
|
+
module DevFlow
|
21
|
+
def self.invoke! config, command
|
22
|
+
require "dev_flow/commands/#{command}"
|
23
|
+
klass = command.to_s.capitalize
|
24
|
+
eval(klass).new(config, command).process!
|
25
|
+
end
|
26
|
+
end
|
data/spec/app_spec.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe DevFlow::App do
|
4
|
+
describe "#all_member_names" do
|
5
|
+
subject (:app) do
|
6
|
+
DevFlow::App.new({members_file:"examples/members.yml", roadmap:"examples/ROADMAP"}, "info")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should read 7 members" do
|
10
|
+
app.all_member_names.size.should eq(7)
|
11
|
+
app.all_member_names.include?("huangw").should be_true
|
12
|
+
app.all_member_names.include?("wangqh").should be_true
|
13
|
+
app.all_member_names.include?("sunyr").should be_true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DevFlow::RoadMap do
|
4
|
+
describe "#parse" do
|
5
|
+
context "valid file" do
|
6
|
+
subject (:roadmap) do
|
7
|
+
DevFlow::RoadMap.new('examples/ROADMAP', {}).parse
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be a Roadmap object" do
|
11
|
+
roadmap.is_a?(DevFlow::RoadMap).should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should assign parents well" do
|
15
|
+
roadmap.ln_tasks[28].parent.is_a?(DevFlow::Task)
|
16
|
+
roadmap.ln_tasks[28].level.should eq(2)
|
17
|
+
roadmap.ln_tasks[28].parent.branch_name.should eq("scope")
|
18
|
+
roadmap.ln_tasks[28].parent.level.should eq(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should assign dependencies well" do
|
22
|
+
roadmap.ln_tasks[70].dependencies.size.should eq(2)
|
23
|
+
roadmap.ln_tasks[70].dependencies[0].branch_name.should eq("release_api_design_0.1")
|
24
|
+
roadmap.ln_tasks[70].dependencies[1].branch_name.should eq("model_spec")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|