pt 0.1

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.
@@ -0,0 +1,3 @@
1
+ # pt changelog
2
+
3
+ ## v0.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Raul Murciano raul@murciano.net
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # pt
2
+
3
+ Minimal client to use Pivotal Tracker from the console.
4
+
5
+ ## Setup
6
+
7
+ gem install pt
8
+
9
+ The first time you run it, `pt` will ask you some data about your Pivotal Tracker account and your current project.
10
+
11
+ ## Usage
12
+
13
+ Run `pt` from the root folder of your project.
14
+
15
+ pt # shows your "My work" tasks list
16
+
17
+ Run `pt create` to create a new bug, chore or feature.
18
+
19
+ The rest of the commands will open you a list of your tasks and let you interact with it:
20
+
21
+ pt open # open a task in the browser
22
+
23
+ pt assign # assign owner
24
+
25
+ pt comment # add a comment
26
+
27
+ pt estimate # estimate a task in points scale
28
+
29
+ pt start # mark a task as started
30
+
31
+ pt finish # indicate you've finished a task
32
+
33
+ pt deliver # indicate the task is delivered
34
+
35
+ pt accept # mark a task as accepted
36
+
37
+ pt reject # mark a task as rejected, explaining why
38
+
39
+ ## Thanks to...
40
+
41
+ - the [Pivotal Tracker](https://www.pivotaltracker.com) guys for making a planning tool that doesn't suck and has an API
42
+ - [Justin Smestad](https://github.com/jsmestad) for his nice `pivotal-tracker` gem
43
+ - [Bryan Liles](http://smartic.us/) for letting me take over the gem name
44
+
45
+ ## License
46
+ See the LICENSE file included in the distribution.
47
+
48
+ ## Copyright
49
+ Copyright (C) 2011 Raul Murciano <raul@murciano.net>.
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+
6
+ desc 'Run tests'
7
+ Rake::TestTask.new('test') do |t|
8
+ t.pattern = 'test/*_test.rb'
9
+ t.verbose = true
10
+ t.warning = true
11
+ end
data/bin/pt ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require File.dirname(__FILE__) + '/../lib/pt'
7
+
8
+ PT::UI.new ARGV
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+
3
+ module PT
4
+ class InputError < StandardError; end
5
+ VERSION = '0.1'
6
+ end
7
+
8
+ require 'pt/client'
9
+ require 'pt/data_row'
10
+ require 'pt/data_table'
11
+ require 'pt/ui'
@@ -0,0 +1,96 @@
1
+ require 'pivotal-tracker'
2
+
3
+ class PT::Client
4
+
5
+ def self.get_api_token(email, password)
6
+ PivotalTracker::Client.token(email, password)
7
+ rescue RestClient::Unauthorized
8
+ raise PT::InputError.new("Bad email/password combination.")
9
+ end
10
+
11
+ def initialize(api_number)
12
+ PivotalTracker::Client.token = api_number
13
+ end
14
+
15
+ def get_projects
16
+ PivotalTracker::Project.all
17
+ end
18
+
19
+ def get_project(project_id)
20
+ PivotalTracker::Project.find(project_id)
21
+ end
22
+
23
+ def get_membership(project, email)
24
+ PivotalTracker::Membership.all(project).select{ |m| m.email == email }.first
25
+ end
26
+
27
+ def get_current_iteration(project)
28
+ PivotalTracker::Iteration.current(project)
29
+ end
30
+
31
+ def get_my_work(project, user_name)
32
+ project.stories.all :mywork => user_name
33
+ end
34
+
35
+ def get_my_open_tasks(project, user_name)
36
+ project.stories.all :owner => user_name
37
+ end
38
+
39
+ def get_my_tasks_to_estimate(project, user_name)
40
+ project.stories.all(:owner => user_name, :story_type => 'feature').select{ |t| t.estimate == -1 }
41
+ end
42
+
43
+ def get_my_tasks_to_start(project, user_name)
44
+ tasks = project.stories.all(:owner => user_name, :current_state => 'unscheduled,rejected')
45
+ tasks.reject{ |t| (t.story_type == 'feature') && (t.estimate == -1) }
46
+ end
47
+
48
+ def get_my_tasks_to_finish(project, user_name)
49
+ project.stories.all(:owner => user_name, :current_state => 'started')
50
+ end
51
+
52
+ def get_my_tasks_to_deliver(project, user_name)
53
+ project.stories.all(:owner => user_name, :current_state => 'finished')
54
+ end
55
+
56
+ def get_my_tasks_to_accept(project, user_name)
57
+ project.stories.all(:owner => user_name, :current_state => 'delivered')
58
+ end
59
+
60
+ def get_my_tasks_to_reject(project, user_name)
61
+ project.stories.all(:owner => user_name, :current_state => 'delivered')
62
+ end
63
+
64
+ def get_tasks_to_assign(project, user_name)
65
+ project.stories.all.select{ |t| t.owned_by == nil }
66
+ end
67
+
68
+ def get_members(project)
69
+ project.memberships.all
70
+ end
71
+
72
+ def mark_task_as(project, task, state)
73
+ task = PivotalTracker::Story.find(task.id, project.id)
74
+ task.update(:current_state => state)
75
+ end
76
+
77
+ def estimate_task(project, task, points)
78
+ task = PivotalTracker::Story.find(task.id, project.id)
79
+ task.update(:estimate => points)
80
+ end
81
+
82
+ def assign_task(project, task, owner)
83
+ task = PivotalTracker::Story.find(task.id, project.id)
84
+ task.update(:owned_by => owner)
85
+ end
86
+
87
+ def comment_task(project, task, comment)
88
+ task = PivotalTracker::Story.find(task.id, project.id)
89
+ task.notes.create(:text => comment)
90
+ end
91
+
92
+ def create_task(project, name, owner, requester, task_type)
93
+ project.stories.create(:name => name, :owned_by => owner, :requested_by => requester, :story_type => task_type)
94
+ end
95
+
96
+ end
@@ -0,0 +1,22 @@
1
+ class PT::DataRow
2
+
3
+ attr_accessor :num, :record
4
+
5
+ def initialize(orig, dataset)
6
+ @record = orig
7
+ @num = dataset.index(orig) + 1
8
+ end
9
+
10
+ def method_missing(method)
11
+ @record.send method
12
+ end
13
+
14
+ def to_s
15
+ @record.send(self.to_s_attribute)
16
+ end
17
+
18
+ def to_s_attribute
19
+ @n.to_s
20
+ end
21
+
22
+ end
@@ -0,0 +1,62 @@
1
+ require 'hirb'
2
+
3
+ module PT
4
+
5
+ class DataTable
6
+
7
+ extend ::Hirb::Console
8
+
9
+ def initialize(dataset)
10
+ @rows = dataset.map{ |row| DataRow.new(row, dataset) }
11
+ end
12
+
13
+ def print
14
+ if @rows.empty?
15
+ puts "\n -- empty list -- \n"
16
+ else
17
+ self.class.table @rows, :fields => [:num] + self.class.fields, :unicode => true, :description => false
18
+ end
19
+ end
20
+
21
+ def [](pos)
22
+ pos = pos.to_i
23
+ (pos < 1 || pos > @rows.length) ? nil : @rows[pos-1].record
24
+ end
25
+
26
+ def length
27
+ @rows.length
28
+ end
29
+
30
+ def self.fields
31
+ []
32
+ end
33
+
34
+ end
35
+
36
+
37
+ class ProjectTable < DataTable
38
+
39
+ def self.fields
40
+ [:name]
41
+ end
42
+
43
+ end
44
+
45
+
46
+ class TasksTable < DataTable
47
+
48
+ def self.fields
49
+ [:name, :current_state]
50
+ end
51
+
52
+ end
53
+
54
+ class MembersTable < DataTable
55
+
56
+ def self.fields
57
+ [:name]
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,292 @@
1
+ require 'yaml'
2
+ require 'colored'
3
+ require 'highline'
4
+
5
+ class PT::UI
6
+
7
+ GLOBAL_CONFIG_PATH = ENV['HOME'] + "/.pt"
8
+ LOCAL_CONFIG_PATH = Dir.pwd + '/.pt'
9
+
10
+ def initialize(args)
11
+ @io = HighLine.new
12
+ @global_config = load_global_config
13
+ @client = PT::Client.new(@global_config[:api_number])
14
+ @local_config = load_local_config
15
+ @project = @client.get_project(@local_config[:project_id])
16
+ command = args[0].to_sym rescue :my_work
17
+ params = args[1..-1]
18
+ commands.include?(command.to_sym) ? send(command.to_sym) : help(command)
19
+ end
20
+
21
+ def my_work
22
+ title("My Work for #{user_s} in #{project_to_s}")
23
+ stories = @client.get_my_work(@project, @local_config[:user_name])
24
+ PT::TasksTable.new(stories).print
25
+ end
26
+
27
+ def create
28
+ title("Let's create a new task:")
29
+ name = ask("Name for the new task:")
30
+ if ask('Do you want to assign it now? (y/n)').downcase == 'y'
31
+ members = @client.get_members(@project)
32
+ table = PT::MembersTable.new(members)
33
+ owner = select("Please select a member to assign him the task", table).name
34
+ else
35
+ owner = nil
36
+ end
37
+ requester = @local_config[:user_name]
38
+ task_type = ask('Type? (c)hore, (b)ug, anything else for feature)')
39
+ task_type = case task_type
40
+ when 'c', 'chore'
41
+ 'chore'
42
+ when 'b', 'bug'
43
+ 'bug'
44
+ else
45
+ 'feature'
46
+ end
47
+ result = @client.create_task(@project, name, owner, requester, task_type)
48
+ if result.errors.any?
49
+ error(result.errors.errors)
50
+ else
51
+ congrats("Task created, yay!")
52
+ end
53
+ end
54
+
55
+ def open
56
+ title("Tasks for #{user_s} in #{project_to_s}")
57
+ tasks = @client.get_my_open_tasks(@project, @local_config[:user_name])
58
+ table = PT::TasksTable.new(tasks)
59
+ task = select("Please select a story to open it in the browser", table)
60
+ `open #{task.url}`
61
+ end
62
+
63
+ def comment
64
+ title("Tasks for #{user_s} in #{project_to_s}")
65
+ tasks = @client.get_my_work(@project, @local_config[:user_name])
66
+ table = PT::TasksTable.new(tasks)
67
+ task = select("Please select a story to comment it", table)
68
+ comment = ask("Write your comment")
69
+ if @client.comment_task(@project, task, comment)
70
+ congrats("Comment sent, thanks!")
71
+ else
72
+ error("Ummm, something went wrong.")
73
+ end
74
+ end
75
+
76
+ def assign
77
+ title("Tasks for #{user_s} in #{project_to_s}")
78
+ tasks = @client.get_tasks_to_assign(@project, @local_config[:user_name])
79
+ table = PT::TasksTable.new(tasks)
80
+ task = select("Please select a task to assign it an owner", table)
81
+ members = @client.get_members(@project)
82
+ table = PT::MembersTable.new(members)
83
+ owner = select("Please select a member to assign him the task", table).name
84
+ result = @client.assign_task(@project, task, owner)
85
+ if result.errors.any?
86
+ error(result.errors.errors)
87
+ else
88
+ congrats("Task assigned, thanks!")
89
+ end
90
+
91
+ end
92
+
93
+ def estimate
94
+ title("Tasks for #{user_s} in #{project_to_s}")
95
+ tasks = @client.get_my_tasks_to_estimate(@project, @local_config[:user_name])
96
+ table = PT::TasksTable.new(tasks)
97
+ task = select("Please select a story to estimate it", table)
98
+ estimation = ask("How many points you estimate for it? (#{@project.point_scale})")
99
+ result = @client.estimate_task(@project, task, estimation)
100
+ if result.errors.any?
101
+ error(result.errors.errors)
102
+ else
103
+ congrats("Task estimated, thanks!")
104
+ end
105
+ end
106
+
107
+ def start
108
+ title("Tasks for #{user_s} in #{project_to_s}")
109
+ tasks = @client.get_my_tasks_to_start(@project, @local_config[:user_name])
110
+ table = PT::TasksTable.new(tasks)
111
+ task = select("Please select a story to mark it as started", table)
112
+ result = @client.mark_task_as(@project, task, 'started')
113
+ if result.errors.any?
114
+ error(result.errors.errors)
115
+ else
116
+ congrats("Task started, go for it!")
117
+ end
118
+ end
119
+
120
+ def finish
121
+ title("Tasks for #{user_s} in #{project_to_s}")
122
+ tasks = @client.get_my_tasks_to_finish(@project, @local_config[:user_name])
123
+ table = PT::TasksTable.new(tasks)
124
+ task = select("Please select a story to mark it as finished", table)
125
+ if task.story_type == 'chore'
126
+ result = @client.mark_task_as(@project, task, 'accepted')
127
+ else
128
+ result = @client.mark_task_as(@project, task, 'finished')
129
+ end
130
+ if result.errors.any?
131
+ error(result.errors.errors)
132
+ else
133
+ congrats("Another task bites the dust, yeah!")
134
+ end
135
+ end
136
+
137
+ def deliver
138
+ title("Tasks for #{user_s} in #{project_to_s}")
139
+ tasks = @client.get_my_tasks_to_deliver(@project, @local_config[:user_name])
140
+ table = PT::TasksTable.new(tasks)
141
+ task = select("Please select a story to mark it as delivered", table)
142
+ result = @client.mark_task_as(@project, task, 'delivered')
143
+ error(result.errors.errors) if result.errors.any?
144
+ if result.errors.any?
145
+ error(result.errors.errors)
146
+ else
147
+ congrats("Task delivered, congrats!")
148
+ end
149
+ end
150
+
151
+ def accept
152
+ title("Tasks for #{user_s} in #{project_to_s}")
153
+ tasks = @client.get_my_tasks_to_accept(@project, @local_config[:user_name])
154
+ table = PT::TasksTable.new(tasks)
155
+ task = select("Please select a story to mark it as accepted", table)
156
+ result = @client.mark_task_as(@project, task, 'accepted')
157
+ if result.errors.any?
158
+ error(result.errors.errors)
159
+ else
160
+ congrats("Task accepted, hooray!")
161
+ end
162
+ end
163
+
164
+ def reject
165
+ title("Tasks for #{user_s} in #{project_to_s}")
166
+ tasks = @client.get_my_tasks_to_reject(@project, @local_config[:user_name])
167
+ table = PT::TasksTable.new(tasks)
168
+ task = select("Please select a story to mark it as rejected", table)
169
+ comment = ask("Please explain why are you rejecting the task")
170
+ if @client.comment_task(@project, task, comment)
171
+ result = @client.mark_task_as(@project, task, 'rejected')
172
+ congrats("Task rejected, thanks!")
173
+ else
174
+ error("Ummm, something went wrong.")
175
+ end
176
+ end
177
+
178
+ protected
179
+
180
+ def commands
181
+ (public_methods - Object.public_methods).sort
182
+ end
183
+
184
+ # Config
185
+
186
+ def load_global_config
187
+ config = YAML.load(File.read(GLOBAL_CONFIG_PATH)) rescue {}
188
+ if config.empty?
189
+ message "I can't find info about your Pivotal Tracker account in #{GLOBAL_CONFIG_PATH}"
190
+ while !config[:api_number] do
191
+ config[:email] = ask "What is your email?"
192
+ password = ask_secret "And your password? (won't be displayed on screen)"
193
+ begin
194
+ config[:api_number] = PT::Client.get_api_token(config[:email], password)
195
+ rescue PT::InputError => e
196
+ error e.message + " Please try again."
197
+ end
198
+ end
199
+ congrats "Thanks!",
200
+ "Your API id is " + config[:api_number],
201
+ "I'm saving it in #{GLOBAL_CONFIG_PATH} to don't ask you again."
202
+ save_config(config, GLOBAL_CONFIG_PATH)
203
+ end
204
+ config
205
+ end
206
+
207
+ def load_local_config
208
+ config = YAML.load(File.read(LOCAL_CONFIG_PATH)) rescue {}
209
+ if config.empty?
210
+ message "I can't find info about this project in #{LOCAL_CONFIG_PATH}"
211
+ projects = PT::ProjectTable.new(@client.get_projects)
212
+ project = select("Please select the project for the current directory", projects)
213
+ config[:project_id], config[:project_name] = project.id, project.name
214
+ membership = @client.get_membership(project, @global_config[:email])
215
+ config[:user_name], config[:user_id], config[:user_initials] = membership.name, membership.id, membership.initials
216
+ congrats "Thanks! I'm saving this project's info",
217
+ "in #{LOCAL_CONFIG_PATH}: remember to .gitignore it!"
218
+ save_config(config, LOCAL_CONFIG_PATH)
219
+ end
220
+ config
221
+ end
222
+
223
+ def save_config(config, path)
224
+ File.new(path, 'w') unless File.exists?(path)
225
+ File.open(path, 'w') {|f| f.write(config.to_yaml) }
226
+ end
227
+
228
+ # I/O
229
+
230
+ def split_lines(text)
231
+ text.respond_to?(:join) ? text.join("\n") : text
232
+ end
233
+
234
+ def title(*msg)
235
+ puts "\n#{split_lines(msg)}".bold
236
+ end
237
+
238
+ def congrats(*msg)
239
+ puts "\n#{split_lines(msg).green.bold}"
240
+ end
241
+
242
+ def message(*msg)
243
+ puts "\n#{split_lines(msg)}"
244
+ end
245
+
246
+ def error(*msg)
247
+ puts "\n#{split_lines(msg).red.bold}"
248
+ end
249
+
250
+ def select(msg, table)
251
+ if table.length > 0
252
+ begin
253
+ table.print
254
+ row = ask "#{msg} (1-#{table.length}, 'q' to exit)"
255
+ quit if row == 'q'
256
+ selected = table[row]
257
+ error "Invalid selection, try again:" unless selected
258
+ end until selected
259
+ selected
260
+ else
261
+ table.print
262
+ message "Sorry, there are no options to select."
263
+ quit
264
+ end
265
+ end
266
+
267
+ def quit
268
+ message "bye!"
269
+ exit
270
+ end
271
+
272
+ def ask(msg)
273
+ @io.ask("#{msg.bold}")
274
+ end
275
+
276
+ def ask_secret(msg)
277
+ @io.ask("#{msg.bold}"){ |q| q.echo = '*' }
278
+ end
279
+
280
+ def help(command)
281
+ error "Command <#{command}> unknown.", "Available commands:" + commands.map{ |c| "\n- #{c}" }.join
282
+ end
283
+
284
+ def user_s
285
+ "#{@local_config[:user_name]} (#{@local_config[:user_initials]})"
286
+ end
287
+
288
+ def project_to_s
289
+ "Project #{@local_config[:project_name].upcase}"
290
+ end
291
+
292
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pt
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Raul Murciano
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2011-06-30 00:00:00 -07:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: pivotal-tracker
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: hirb
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: colored
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ type: :runtime
57
+ version_requirements: *id003
58
+ - !ruby/object:Gem::Dependency
59
+ name: highline
60
+ prerelease: false
61
+ requirement: &id004 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ type: :runtime
70
+ version_requirements: *id004
71
+ description: Minimalist, opinionated client to manage your Pivotal Tracker tasks from the command line.
72
+ email:
73
+ - raul@murciano.net
74
+ executables:
75
+ - pt
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - lib/pt/client.rb
82
+ - lib/pt/data_row.rb
83
+ - lib/pt/data_table.rb
84
+ - lib/pt/ui.rb
85
+ - lib/pt.rb
86
+ - Changelog.md
87
+ - Gemfile
88
+ - LICENSE
89
+ - RakeFile
90
+ - README.md
91
+ - bin/pt
92
+ has_rdoc: true
93
+ homepage: http://www.github.com/raul/pt
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options: []
98
+
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project: pt
120
+ rubygems_version: 1.3.7
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Client to use Pivotal Tracker from the console.
124
+ test_files: []
125
+