rpw 1.1.0 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 317f37137765f78e5a587b36e3ecb4898d18e4b011cc3ef67f60d5d0e710c6d8
4
- data.tar.gz: 4c9f5e3edf56c6dad2d887e4653d358850255dbf9682e0a6fcc8261e0e3a543f
3
+ metadata.gz: c4c79a03ffb13bfdd6fd37581cdca30aa3a69712608add26105f51b130b15fcb
4
+ data.tar.gz: '05079633b4d192a7654dea1f82972ecb735ea634de59c5e83bde843eec73ef42'
5
5
  SHA512:
6
- metadata.gz: 7526987cc970e87b387154632256c89190b8cb7ec9fb9cfe9f1fe2364e73a46cabe81ecbe6a6707e8d5c7ca2c3a5f1591f2bf27d8b63a4e801ff69fcfefa1f11
7
- data.tar.gz: eeb1628660e72e1d655e428c0640b6255ba43794db4704b42293eee6975a7f1a157c345ba6f2e87e8952af4198750d7aa271f7c925c4977d8f50e0f3e8fdb837
6
+ metadata.gz: f81567fe95bda4102cc1109e8f643043ee9dcb58d0fc81c1c9faee34ff91fcc3ba3d7265f718c473a19eafbdcf852801c6bea803ddf49c7ad74280f8a85cd3dc
7
+ data.tar.gz: 66fb292f109656d91d97166733ba21f095d0c7f1cfd06a82e580ab87c8b4bb2d28b03eb95dcb02c32ca5eb4a7e2d379a04afa5b9ae419511c927ea9755c1f877
@@ -20,7 +20,7 @@ jobs:
20
20
  fail-fast: true
21
21
  matrix:
22
22
  os: [ ubuntu-20.04, macos-10.15, windows-2019 ]
23
- ruby: [ 2.3, 2.6, 2.7, head ]
23
+ ruby: [ 2.6, 2.7, head ]
24
24
 
25
25
  steps:
26
26
  - name: repo checkout
@@ -33,7 +33,7 @@ jobs:
33
33
 
34
34
  - name: bundle install
35
35
  run: |
36
- bundle install --jobs 4 --retry 3
36
+ gem install bundler && bundle install --jobs 4 --retry 3
37
37
 
38
38
  - name: standardrb
39
39
  run: bundle exec standardrb
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rpw (1.1.0)
4
+ rpw (1.2.0)
5
5
  cli-ui
6
6
  excon
7
7
  thor
data/HISTORY.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 1.2.0
2
+
3
+ * Actual release version!
4
+ * Greatly streamlined commands
5
+
1
6
  ## 1.1.0
2
7
 
3
8
  * sick colors
@@ -1,6 +1,6 @@
1
1
  ## Installation Requirements
2
2
 
3
- This client assumes you're using Ruby 2.3 or later.
3
+ This client assumes you're using Ruby 2.6 or later.
4
4
 
5
5
  This client assumes you have `tar` installed and available on your PATH.
6
6
 
@@ -11,6 +11,11 @@ The way that the client opens files (using `open` or `xdg-open` depending on pla
11
11
 
12
12
  ## Slack Invite
13
13
 
14
+ The Slack channel is your best resource for questions about Rails Performance
15
+ or other material in the workshop. Nate is almost always monitoring that channel.
16
+
17
+ If you encounter a **bug or other software problem**, please email support@speedshop.co.
18
+
14
19
  If you purchased the Workshop yourself, you will receive a Slack channel invitation
15
20
  shortly. If you are attending the Workshop as part of a group and your license key
16
21
  was provided to you, you need to register your key to get an invite:
@@ -21,33 +26,30 @@ $ rpw key register [YOUR_EMAIL_ADDRESS]
21
26
 
22
27
  Please note you can only register your key once.
23
28
 
24
- The Slack channel is your best resource for questions about Rails Performance
25
- or other material in the workshop. Nate is almost always monitoring that channel.
26
-
27
- If you encounter a **bug or other software problem**, please email support@speedshop.co.
28
-
29
29
  ## Important Commands
30
30
 
31
31
  Here are some important commands for you to know:
32
32
 
33
33
  ```
34
- $ rpw lesson next | Proceed to the next part of the workshop.
35
- $ rpw lesson complete | Mark current lesson as complete.
36
- $ rpw lesson list | List all workshop lessons. Note each lesson is preceded with an ID.
37
- $ rpw lesson download | Download all lessons. Useful for offline access.
38
- $ rpw lesson show | Show any particular workshop lesson.
39
- $ rpw progress | Show where you're currently at in the workshop.
34
+ $ rpw next | Proceed to the next part of the workshop.
35
+ $ rpw complete | Mark current lesson as complete.
36
+ $ rpw list | List all workshop lessons. Shows progress.
37
+ $ rpw download | Download all lessons. Useful for offline access.
38
+ $ rpw show | Show any particular workshop lesson.
39
+ $ rpw current | Opens the current lesson.
40
40
  ```
41
41
 
42
- Generally, you'll just be doing a lot of `$ rpw lesson next`!
42
+ Generally, you'll just be doing a lot of `$ rpw next`! It's basically the same thing as `$ rpw complete && rpw show`.
43
+
44
+ #### --no-open
43
45
 
44
- By default, `$ rpw lesson next` will try to open the content it downloads. If you
45
- either don't like this, or for some reason it doesn't work, use `$ rpw lesson next --no-open`.
46
+ By default, `$ rpw next` (and `$ rpw show` and `$ rpw current`) will try to open the content it downloads. If you
47
+ either don't like this, or for some reason it doesn't work, use `$ rpw next --no-open`.
46
48
 
47
49
  ## Working Offline
48
50
 
49
51
  By default, the course will download each piece of content as you progress through
50
- the course. However, you can use `rpw lesson download all` to download all content
52
+ the course. However, you can use `rpw download` to download all content
51
53
  at once, and complete the workshop entirely offline.
52
54
 
53
55
  Videos in this workshop are generally about 100MB each, which means the entire
@@ -4,8 +4,6 @@ require "rpw"
4
4
  require "rpw/cli/bannerlord"
5
5
  require "rpw/cli/sub_command_base"
6
6
  require "rpw/cli/key"
7
- require "rpw/cli/lesson"
8
- require "rpw/cli/progress"
9
7
  require "cli/ui"
10
8
 
11
9
  CLI::UI::StdoutRouter.enable
@@ -16,10 +14,6 @@ module RPW
16
14
 
17
15
  desc "key register [EMAIL_ADDRESS]", "Change email registered w/Speedshop"
18
16
  subcommand "key", Key
19
- desc "lesson [SUBCOMMAND]", "View and download lessons"
20
- subcommand "lesson", Lesson
21
- desc "progress [SUBCOMMAND]", "View and set progress"
22
- subcommand "progress", Progress
23
17
 
24
18
  def self.exit_on_failure?
25
19
  true
@@ -60,13 +54,137 @@ module RPW
60
54
  exit(0)
61
55
  end
62
56
 
63
- puts ""
57
+ say ""
64
58
  say "Successfully authenticated with the RPW server and saved your key."
65
- puts ""
59
+ say ""
66
60
  say "Setup complete!"
67
- puts ""
68
- say "To learn how to use this command-line client, consult ./README.md, which we just created."
69
- say "Once you've read that and you're ready to get going: $ rpw lesson next"
61
+ say ""
62
+ say "To learn how to use this command-line client, consult ./README.md,"
63
+ say "which we just created."
64
+ say ""
65
+ say "Once you've read that and you're ready to get going: $ rpw next"
66
+ end
67
+
68
+ desc "next", "Proceed to the next lesson of the workshop"
69
+ option :"no-open", type: :boolean
70
+ def next
71
+ exit_with_no_key
72
+ content = client.next
73
+
74
+ if content.nil?
75
+ RPW::CLI.new.print_banner
76
+ say "Congratulations!"
77
+ say "You have completed the Rails Performance Workshop."
78
+ exit(0)
79
+ end
80
+
81
+ say "Proceeding to next lesson: #{content["title"]}"
82
+ client.download_and_extract(content)
83
+ client.complete(content["position"])
84
+ display_content(content, !options[:"no-open"])
85
+ end
86
+
87
+ desc "current", "Open the current lesson"
88
+ option :"no-open", type: :boolean
89
+ def current
90
+ exit_with_no_key
91
+ content = client.current
92
+ say "Opening: #{content["title"]}"
93
+ client.download_and_extract(content)
94
+ display_content(content, !options[:"no-open"])
95
+ end
96
+
97
+ desc "complete", "Mark the current lesson as complete"
98
+ def complete
99
+ say "Marked current lesson as complete"
100
+ client.complete(nil)
101
+ end
102
+
103
+ desc "list", "Show all available workshop lessons"
104
+ def list
105
+ ::CLI::UI::Frame.open("{{*}} {{bold:All Lessons}}", color: :green)
106
+
107
+ frame_open = false
108
+ client.list.each do |lesson|
109
+ if lesson["title"].start_with?("Section")
110
+ ::CLI::UI::Frame.close(nil) if frame_open
111
+ ::CLI::UI::Frame.open(lesson["title"])
112
+ frame_open = true
113
+ next
114
+ end
115
+
116
+ no_data = client.send(:client_data)["completed"].nil?
117
+ completed = client.send(:client_data)["completed"]&.include?(lesson["position"])
118
+
119
+ str = if no_data
120
+ ""
121
+ elsif completed
122
+ "\u{2705} "
123
+ else
124
+ "\u{274C} "
125
+ end
126
+
127
+ case lesson["style"]
128
+ when "video"
129
+ puts str + ::CLI::UI.fmt("{{red:#{lesson["title"]}}}")
130
+ when "quiz"
131
+ # puts ::CLI::UI.fmt "{{green:#{" " + lesson["title"]}}}"
132
+ when "lab"
133
+ puts str + ::CLI::UI.fmt("{{yellow:#{" " + lesson["title"]}}}")
134
+ when "text"
135
+ puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
136
+ else
137
+ puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
138
+ end
139
+ end
140
+
141
+ ::CLI::UI::Frame.close(nil)
142
+ ::CLI::UI::Frame.close(nil, color: :green)
143
+ end
144
+
145
+ desc "download", "Download all workshop contents"
146
+ def download
147
+ exit_with_no_key
148
+ total = client.list.size
149
+ client.list.each do |content|
150
+ current = client.list.index(content) + 1
151
+ puts "Downloading #{content["title"]} (#{current}/#{total})"
152
+ client.download_and_extract(content)
153
+ end
154
+ end
155
+
156
+ desc "show", "Show any individal workshop lesson"
157
+ option :"no-open", type: :boolean
158
+ def show
159
+ exit_with_no_key
160
+ title = ::CLI::UI::Prompt.ask(
161
+ "Which lesson would you like to view?",
162
+ options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
163
+ )
164
+ title.strip!
165
+ content_order = client.list.find { |l| l["title"] == title }["position"]
166
+ content = client.show(content_order)
167
+ client.download_and_extract(content)
168
+ display_content(content, !options[:"no-open"])
169
+ end
170
+
171
+ desc "set_progress", "Set current lesson to a particular lesson"
172
+ def set_progress
173
+ title = ::CLI::UI::Prompt.ask(
174
+ "Which lesson would you like to set your progress to? All prior lessons will be marked complete",
175
+ options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
176
+ )
177
+ title.strip!
178
+ content_order = client.list.find { |l| l["title"] == title }["position"]
179
+ content = client.set_progress(content_order, all_prior: true)
180
+ say "Setting current progress to #{content.last["title"]}"
181
+ end
182
+
183
+ desc "reset", "Erase all progress and start over"
184
+ def reset
185
+ return unless ::CLI::UI.confirm("Are you sure you want to erase all of your progress?", default: false)
186
+ say "Resetting progress."
187
+ client.set_progress(nil)
70
188
  end
71
189
 
72
190
  no_commands do
@@ -77,10 +195,63 @@ module RPW
77
195
 
78
196
  private
79
197
 
198
+ def exit_with_no_key
199
+ unless client.setup?
200
+ say "You have not yet set up the client. Run $ rpw start"
201
+ exit(1)
202
+ end
203
+ unless client.directories_ready?
204
+ say "You are not in your workshop scratch directory, or you have not yet"
205
+ say "set up the client. Change directory or run $ rpw start"
206
+ exit(1)
207
+ end
208
+ end
209
+
80
210
  def client
81
211
  @client ||= RPW::Client.new
82
212
  end
83
213
 
214
+ def display_content(content, open_after)
215
+ openable = false
216
+ case content["style"]
217
+ when "video"
218
+ location = "video/#{content["s3_key"]}"
219
+ openable = true
220
+ when "quiz"
221
+ Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
222
+ when "lab"
223
+ location = "lab/#{content["s3_key"][0..-8]}"
224
+ openable = true
225
+ when "text"
226
+ location = "text/#{content["s3_key"]}"
227
+ openable = true
228
+ when "cgrp"
229
+ say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
230
+ say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
231
+ say "You can check it out now, or to continue: $ rpw next "
232
+ end
233
+ if location
234
+ if openable && !open_after
235
+ say "Download complete. Open with: $ #{open_command} #{location}"
236
+ elsif open_after && openable
237
+ exec "#{open_command} #{location}"
238
+ end
239
+ end
240
+ end
241
+
242
+ require "rbconfig"
243
+ def open_command
244
+ host_os = RbConfig::CONFIG["host_os"]
245
+ case host_os
246
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
247
+ "start"
248
+ when /darwin|mac os/
249
+ "open"
250
+ else
251
+ "xdg-open"
252
+ end
253
+ end
254
+
84
255
  def warn_if_already_started
85
256
  return unless client.setup?
86
257
  exit(0) unless ::CLI::UI.confirm "You have already started the workshop. Continuing "\
@@ -1,9 +1,11 @@
1
1
  module RPW
2
2
  class Key < SubCommandBase
3
- class_before :exit_with_no_key
4
-
5
3
  desc "register [EMAIL_ADDRESS]", "Change email registered with Speedshop. One-time only."
6
4
  def register(email)
5
+ unless client.setup?
6
+ say "You have not yet set up the client. Run $ rpw start"
7
+ exit(1)
8
+ end
7
9
  if client.register_email(email)
8
10
  say "Key registered with #{email}. You should receive a Slack invite soon."
9
11
  else
@@ -13,18 +13,6 @@ module RPW
13
13
  def client
14
14
  @client ||= RPW::Client.new
15
15
  end
16
-
17
- def exit_with_no_key
18
- unless client.setup?
19
- say "You have not yet set up the client. Run $ rpw start"
20
- exit(1)
21
- end
22
- unless client.directories_ready?
23
- say "You are not in your workshop scratch directory, or you have not yet"
24
- say "set up the client. Change directory or run $ rpw start"
25
- exit(1)
26
- end
27
- end
28
16
  end
29
17
  end
30
18
  end
@@ -25,6 +25,11 @@ module RPW
25
25
  list.sort_by { |c| c["position"] }.find { |c| c["position"] > current_position }
26
26
  end
27
27
 
28
+ def current
29
+ return list.first unless client_data["completed"]
30
+ list.sort_by { |c| c["position"] }.find { |c| c["position"] == current_position }
31
+ end
32
+
28
33
  def list
29
34
  @list ||= begin
30
35
  if client_data["content_cache_generated"] &&
@@ -75,22 +80,17 @@ module RPW
75
80
  end
76
81
  end
77
82
 
78
- def progress
79
- completed_lessons = client_data["completed"] || []
80
- {
81
- completed: completed_lessons.size,
82
- total: list.size,
83
- current_lesson: list.find { |c| c["position"] == current_position },
84
- sections: chart_section_progress(list, completed_lessons)
85
- }
86
- end
87
-
88
- def set_progress(pos)
83
+ def set_progress(pos, all_prior: false)
89
84
  client_data["completed"] = [] && return if pos.nil?
90
- lesson = list.find { |l| l["position"] == pos }
91
- raise Error.new("No such lesson - use the IDs in $ rpw lesson list") unless lesson
92
- client_data["completed"] += [pos]
93
- lesson
85
+ if all_prior
86
+ lessons = list.select { |l| l["position"] <= pos }
87
+ client_data["completed"] = lessons.map { |l| l["position"] }
88
+ lessons
89
+ else
90
+ lesson = list.find { |l| l["position"] == pos }
91
+ client_data["completed"] += [pos]
92
+ lesson
93
+ end
94
94
  end
95
95
 
96
96
  def latest_version?
@@ -146,25 +146,6 @@ module RPW
146
146
  @current_position ||= client_data["completed"]&.last || 0
147
147
  end
148
148
 
149
- def chart_section_progress(contents, completed)
150
- contents.group_by { |c| c["position"] / 100 }
151
- .each_with_object([]) do |(_, c), memo|
152
- completed_str = c.map { |l|
153
- if l["position"] == current_position
154
- "O"
155
- elsif completed.include?(l["position"])
156
- "X"
157
- else
158
- "."
159
- end
160
- }.join
161
- memo << {
162
- title: c[0]["title"],
163
- progress: completed_str
164
- }
165
- end
166
- end
167
-
168
149
  def client_data
169
150
  @client_data ||= ClientData.new
170
151
  end
@@ -1,3 +1,3 @@
1
1
  module RPW
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpw
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Berkopec
@@ -90,8 +90,6 @@ files:
90
90
  - lib/rpw/cli.rb
91
91
  - lib/rpw/cli/bannerlord.rb
92
92
  - lib/rpw/cli/key.rb
93
- - lib/rpw/cli/lesson.rb
94
- - lib/rpw/cli/progress.rb
95
93
  - lib/rpw/cli/quiz.rb
96
94
  - lib/rpw/cli/sub_command_base.rb
97
95
  - lib/rpw/client.rb
@@ -1,129 +0,0 @@
1
- module RPW
2
- class Lesson < SubCommandBase
3
- desc "next", "Proceed to the next lesson of the workshop"
4
- option :"no-open"
5
- def next
6
- exit_with_no_key
7
- say "Proceeding to next lesson..."
8
- content = client.next
9
-
10
- if content.nil?
11
- RPW::CLI.new.print_banner
12
- say "Congratulations!"
13
- say "You have completed the Rails Performance Workshop."
14
- exit(0)
15
- end
16
-
17
- client.download_and_extract(content)
18
- client.complete(content["position"])
19
- display_content(content, !options[:"no-open"])
20
- end
21
-
22
- desc "complete", "Mark the current lesson as complete"
23
- def complete
24
- say "Marked current lesson as complete"
25
- client.complete(nil)
26
- end
27
-
28
- desc "list", "Show all available workshop lessons"
29
- def list
30
- ::CLI::UI::Frame.open("{{*}} {{bold:All Lessons}}", color: :green)
31
-
32
- frame_open = false
33
- client.list.each do |lesson|
34
- if lesson["title"].start_with?("Section")
35
- ::CLI::UI::Frame.close(nil) if frame_open
36
- ::CLI::UI::Frame.open(lesson["title"])
37
- frame_open = true
38
- next
39
- end
40
-
41
- case lesson["style"]
42
- when "video"
43
- puts ::CLI::UI.fmt "{{red:#{lesson["title"]}}}"
44
- when "quiz"
45
- # puts ::CLI::UI.fmt "{{green:#{" " + lesson["title"]}}}"
46
- when "lab"
47
- puts ::CLI::UI.fmt "{{yellow:#{" " + lesson["title"]}}}"
48
- when "text"
49
- puts ::CLI::UI.fmt "{{magenta:#{" " + lesson["title"]}}}"
50
- else
51
- puts ::CLI::UI.fmt "{{magenta:#{" " + lesson["title"]}}}"
52
- end
53
- end
54
-
55
- ::CLI::UI::Frame.close(nil)
56
- ::CLI::UI::Frame.close(nil, color: :green)
57
- end
58
-
59
- desc "download", "Download all workshop contents"
60
- def download
61
- exit_with_no_key
62
- total = client.list.size
63
- client.list.each do |content|
64
- current = client.list.index(content)
65
- puts "Downloading #{content["title"]} (#{current}/#{total})"
66
- client.download_and_extract(content)
67
- end
68
- end
69
-
70
- desc "show", "Show any individal workshop lesson"
71
- option :"no-open"
72
- def show
73
- exit_with_no_key
74
- title = ::CLI::UI::Prompt.ask(
75
- "Which lesson would you like to view?",
76
- options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
77
- )
78
- title.strip!
79
- content_order = client.list.find { |l| l["title"] == title }["position"]
80
- content = client.show(content_order)
81
- client.download_and_extract(content)
82
- display_content(content, !options[:"no-open"])
83
- end
84
-
85
- private
86
-
87
- def display_content(content, open_after)
88
- say "Current Lesson: #{content["title"]}"
89
- openable = false
90
- case content["style"]
91
- when "video"
92
- location = "video/#{content["s3_key"]}"
93
- openable = true
94
- when "quiz"
95
- Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
96
- when "lab"
97
- location = "lab/#{content["s3_key"][0..-8]}"
98
- openable = true
99
- when "text"
100
- location = "text/#{content["s3_key"]}"
101
- openable = true
102
- when "cgrp"
103
- say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
104
- say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
105
- say "You can check it out now, or to continue: $ rpw lesson next "
106
- end
107
- if location
108
- if openable && !open_after
109
- say "Download complete. Open with: $ #{open_command} #{location}"
110
- elsif open_after && openable
111
- exec "#{open_command} #{location}"
112
- end
113
- end
114
- end
115
-
116
- require "rbconfig"
117
- def open_command
118
- host_os = RbConfig::CONFIG["host_os"]
119
- case host_os
120
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
121
- "start"
122
- when /darwin|mac os/
123
- "open"
124
- else
125
- "xdg-open"
126
- end
127
- end
128
- end
129
- end
@@ -1,38 +0,0 @@
1
- module RPW
2
- class Progress < SubCommandBase
3
- desc "set [LESSON]", "Set current lesson to a particular lesson"
4
- def set(pos)
5
- lesson = client.set_progress(pos.to_i)
6
- say "Set current progress to #{lesson["title"]}"
7
- end
8
-
9
- desc "reset", "Erase all progress and start over"
10
- def reset
11
- return unless ::CLI::UI.confirm("Are you sure you want to erase all of your progress?", default: false)
12
- say "Resetting progress."
13
- client.set_progress(nil)
14
- end
15
-
16
- desc "show", "Show current workshop progress"
17
- def show
18
- data = client.progress
19
- ::CLI::UI::Frame.open("The Rails Performance Workshop", timing: false, color: :red) do
20
- say "You have completed #{data[:completed]} out of #{data[:total]} total sections."
21
- say ""
22
- say "Current lesson: #{data[:current_lesson]["title"]}" if data[:current_lesson]
23
- say ""
24
- ::CLI::UI::Frame.open("Progress", timing: false, color: :red) do
25
- puts ::CLI::UI.fmt "{{i}} (X == completed, O == current)"
26
- say ""
27
- data[:sections].each do |section|
28
- say "#{section[:title]}: #{section[:progress]}"
29
- end
30
- end
31
- end
32
- end
33
-
34
- private
35
-
36
- default_task :show
37
- end
38
- end