ruby_drills 0.1.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/CREDITS.txt +0 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +202 -0
- data/NOTICE.txt +11 -0
- data/README.md +118 -0
- data/Rakefile +6 -0
- data/bin/ruby_drills +13 -0
- data/lib/ruby_drills/array/all_drill.rb +28 -0
- data/lib/ruby_drills/array/all_names_drill.rb +30 -0
- data/lib/ruby_drills/array/array_drills.rb +18 -0
- data/lib/ruby_drills/array/chunk_drill.rb +34 -0
- data/lib/ruby_drills/array/drop_while_drill.rb +27 -0
- data/lib/ruby_drills/array/partition_drill.rb +27 -0
- data/lib/ruby_drills/array/reduce_drill.rb +24 -0
- data/lib/ruby_drills/array/take_while_drill.rb +26 -0
- data/lib/ruby_drills/array/zip_drill.rb +28 -0
- data/lib/ruby_drills/chomper.rb +23 -0
- data/lib/ruby_drills/cli.rb +76 -0
- data/lib/ruby_drills/commands.rb +106 -0
- data/lib/ruby_drills/config.rb +19 -0
- data/lib/ruby_drills/data/gambler.ascii +65 -0
- data/lib/ruby_drills/drill.rb +66 -0
- data/lib/ruby_drills/drills.rb +52 -0
- data/lib/ruby_drills/hash/hash_drills.rb +17 -0
- data/lib/ruby_drills/sessions/collector_client.rb +39 -0
- data/lib/ruby_drills/sessions/local.rb +45 -0
- data/lib/ruby_drills/sessions/timestamp.rb +9 -0
- data/lib/ruby_drills/string/string_drills.rb +16 -0
- data/lib/ruby_drills/version.rb +3 -0
- data/lib/ruby_drills/welcome/welcome_drills.rb +13 -0
- data/lib/starter.rb +68 -0
- data/ruby_drills.gemspec +29 -0
- data/spec/commands_spec.rb +24 -0
- data/spec/ruby_drills_spec.rb +7 -0
- data/spec/sessions/timestamp_spec.rb +10 -0
- data/spec/spec_helper.rb +2 -0
- metadata +194 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module RubyDrills
|
2
|
+
|
3
|
+
# Manage the processing of command line options
|
4
|
+
class CLI
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# @return [Proc] The Proc defining the valid command line options.
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
# @return [Array] The Procs that process the parsed options.
|
12
|
+
attr_accessor :option_processors
|
13
|
+
|
14
|
+
# @return [Array<String>] The input array of strings to process
|
15
|
+
# as CLI options.
|
16
|
+
attr_accessor :input_args
|
17
|
+
|
18
|
+
# Add another set of CLI options (a Slop block)
|
19
|
+
def add_options(&block)
|
20
|
+
if options
|
21
|
+
old_options = options
|
22
|
+
self.options = proc do
|
23
|
+
instance_exec(&old_options)
|
24
|
+
instance_exec(&block)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
self.options = block
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add a block responsible for processing parsed options.
|
33
|
+
def process_options(&block)
|
34
|
+
self.option_processors ||= []
|
35
|
+
option_processors << block
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Clear `options` and `option_processors`
|
40
|
+
def reset
|
41
|
+
self.options = nil
|
42
|
+
self.option_processors = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_options(args=ARGV.dup)
|
46
|
+
unless options
|
47
|
+
raise NoOptionsError, "No command line options defined! Use RubyDrills::CLI.add_options to add command line options."
|
48
|
+
end
|
49
|
+
|
50
|
+
self.input_args = args
|
51
|
+
|
52
|
+
opts = Slop.parse!(args, :help => true, :multiple_switches => false, &options)
|
53
|
+
|
54
|
+
# Option processors are optional.
|
55
|
+
if option_processors
|
56
|
+
option_processors.each { |processor| processor.call(opts) }
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
reset
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
RubyDrills::CLI.add_options do
|
69
|
+
on :v, :version, "Display the version" do
|
70
|
+
puts "RubyDrills version #{RubyDrills::VERSION} on Ruby #{RUBY_VERSION}"
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
end.process_options do |opts|
|
74
|
+
exit if opts.help?
|
75
|
+
Starter.new
|
76
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Commands
|
2
|
+
GAMBLER = File.read(File.join(File.dirname(__FILE__), 'data/gambler.ascii'))
|
3
|
+
|
4
|
+
# Each command returns a response indicating whether or not the drill is complete.
|
5
|
+
def continue
|
6
|
+
puts "\nPress any key to continue:"
|
7
|
+
Chomper.get_char
|
8
|
+
system('clear');
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def fail(input, message=nil)
|
13
|
+
puts "\n\tnot yet...".yellow
|
14
|
+
RubyDrills::Config::SESSIONS.attempt(self.class.name, input, reference, 'fail')
|
15
|
+
puts message.yellow unless message.nil?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def win(input)
|
20
|
+
puts "\n\t!!! WIN !!!\n".green
|
21
|
+
RubyDrills::Config::SESSIONS.attempt(self.class.name, input, reference, 'pass')
|
22
|
+
if (reference != input.strip)
|
23
|
+
puts "How does your answer compare to the reference solution?"
|
24
|
+
puts reference
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip
|
30
|
+
puts "\n\tskipping...for now...".yellow
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def review
|
35
|
+
puts "\n\t#{reference}".green
|
36
|
+
puts "\n\ttry this one again soon...".yellow
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def fold
|
41
|
+
puts GAMBLER
|
42
|
+
puts "\nYou got to know when to hold 'em, know when to fold 'em...\n".yellow
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def hint
|
47
|
+
puts hints.rotate!.last
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def back
|
52
|
+
puts "\tYou're already at the first drill".yellow if !previous
|
53
|
+
previous
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear
|
57
|
+
system('clear')
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def help
|
62
|
+
puts %{
|
63
|
+
For each drill, type in some Ruby.
|
64
|
+
Answer correctly and you'll get a 'WIN' on the board.
|
65
|
+
Otherwise, you'll see 'not yet'.
|
66
|
+
|
67
|
+
These commands are also available to you:
|
68
|
+
|
69
|
+
help:\tthis screen
|
70
|
+
show:\tshow the problem description
|
71
|
+
hint:\tget unstuck
|
72
|
+
back:\tback to the previous drill
|
73
|
+
skip:\ton to the next drill
|
74
|
+
review:\tsee the answer
|
75
|
+
clear:\tclear the screen
|
76
|
+
menu:\tback to the main menu
|
77
|
+
exit:\tend your session}
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def welcome
|
82
|
+
puts %{
|
83
|
+
Welcome to Ruby Drills!
|
84
|
+
|
85
|
+
Drills are a way to engage in deliberate practice to master a language.
|
86
|
+
Challenges in Ruby Drills focus on a specific method.
|
87
|
+
Answers typically consist of a single line.
|
88
|
+
Your objective is to complete the drill with ease and joy,
|
89
|
+
without consulting any external documentation.
|
90
|
+
|
91
|
+
-----------------------------------------------------------------------
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def quit
|
96
|
+
system('clear')
|
97
|
+
puts %{
|
98
|
+
Mastery...the mysterious process during which what is at first difficult
|
99
|
+
becomes progressively easier and more pleasurable through practice.
|
100
|
+
|
101
|
+
--- George Leonard}
|
102
|
+
puts
|
103
|
+
exit
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RubyDrills
|
2
|
+
class Config
|
3
|
+
|
4
|
+
SESSIONS = Sessions::CollectorClient.new
|
5
|
+
|
6
|
+
def self.pry_config
|
7
|
+
Pry.config.pager = false
|
8
|
+
|
9
|
+
Pry.config.prompt = [
|
10
|
+
proc { |target_self, nest_level, pry|
|
11
|
+
"#{":#{nest_level}" unless nest_level.zero?}>> "
|
12
|
+
},
|
13
|
+
proc { |target_self, nest_level, pry|
|
14
|
+
"#{":#{nest_level}" unless nest_level.zero?}* "
|
15
|
+
}
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
7I,.:=,===~~,~~:~~:,~=~~:,:~~~,~::.....,..,,,,,~77:?+777I=I777?:+I?????????+,,~~
|
2
|
+
III++?,~~:~~:~=::~~.~~~~~.:~~~:,............,?7?:,:,,777I=I77I?+~,..,~::~~~~~,~~
|
3
|
+
III=?I::~~:~::~::~~.:~~~~.:~~~................,,.,,,,,,=?+?I7I???+I,~~~:~~~~:,~~
|
4
|
+
I??..:~~~~:~~,~::~:,,~~:~.:~~..................,......,.?+?77??+++?,:~~:~~~~:,~~
|
5
|
+
I??II,+=:~::~,:~:::,.~~:~.:~....................,,..,,...++I7I?++??,::~,~~::,,~~
|
6
|
+
I???=.++,,~:~:,:,~,:.~~:~.,:...................,,,,,:::,.==II?+=?I+.::~:~~~~,,~~
|
7
|
+
????~,.,~.~::~::~~::.,~::.:.....................,~=,~+,.,,~II++=??+.::::~~::.,::
|
8
|
+
??????~++:,~~:~:~~::,,:::......................,.,~,,::,,.=II===?=?,::::~:::.,:~
|
9
|
+
??????..:,~,~,::::::,.~~~,................,.....,,,,,,:,.,=II===?+=,::::::::.:~~
|
10
|
+
???I???+,?:.~::::~~:,.:~:.......................,,..,...,.=II++=?+~,::~::~::.:~~
|
11
|
+
???????+:?:,,:::::~::.,::......................,..,,..,:..+?I+=~=+=,::~~:~:,.::~
|
12
|
+
??I????....?.:~,::::,..~.................,,..,:.,......,..?=I=~~:+:,::~::::,.:~~
|
13
|
+
????I.....,:,,:::::::,.:...,....,...,,,,,.,......,..,.....?=I==~:+,,::~::::..:~~
|
14
|
+
++I..............,:~::..,,..........,,,:,,:,,:,.,..,,,,...?~+~~~,+,,::~~~::..:::
|
15
|
+
??....................:=:..,,,....,...,,::,~::,,,,.,,,,,:.?~+~~~,~.,:,:~:::.,::~
|
16
|
+
??:........................,==,.......,:,,,:::::,,,,,:,,,,I,=::~,~.,:::~::,.,:::
|
17
|
+
???............................,==:...,,,,,,,,.,,:,,,::,,,I,=::,,:.,::~~::,.,::~
|
18
|
+
+?+?................................:+==,,..........,::,:~I.~,,,.:.:::~:::,.,:::
|
19
|
+
+++++,.....................................~++=,:,...,~:,??.:,,,.,.::~~~::,.::~:
|
20
|
+
+++?++?....,.....................................:==+=~~~??.,,,,.:.::~:~::,,::~,
|
21
|
+
+?????+?,..,,,,:~~+++::.,:,,,,,...................................,=I?=::,..::~.
|
22
|
+
???????=...,,,.::~~:,:::~~,:~~==~...~?+:................................~?..::~.
|
23
|
+
?+???+=,.....,,:.,..~:::==+===,,,=?:~++??????:.............................=:::,
|
24
|
+
++???+,...........,~:::~=+++++++??+=+=+???==~,,..............................:,,
|
25
|
+
++??+~.......,,...:.~~,~=+?+?+??????==+??+++=,.+?+=~,~.......................:,:
|
26
|
+
+?++=:.......=,..::I=:,~=++???????+=+=+++=+++??~~.=:~=:.~:::................::,:
|
27
|
+
++++~........,..:=+++::~=++??++++++====++++?+I?+??+++~~:~:~~,..............~::,:
|
28
|
+
???+,...........:++II,:~=++++?????==~~=++????+???????+~=+~~+,+.......,~::.,~:,,:
|
29
|
+
+++~............~??I=,:~==+++?????===+?I????????????++~+:.~=+:.....::~~:,.:~:,::
|
30
|
+
++:.........:..,~+I??::~~==+?????=~:~+++++??I??????+++=+.:..~~..:::,:~~:,,~~:::,
|
31
|
+
+~..............,??I?::~~==+?????+=,,.:,,~=??I????+++=I?,...,:..::=~,:~:,~~::::~
|
32
|
+
=,..............~+???::~==+??=::,,::=:~~:,+++??+?+++II+?:,=:,..~~~~~:.....,:::~~
|
33
|
+
...............,,????::~=+++,~.:,::::,::~::+?+?+++++??++:~=+.:++=~=~::,..,,..,~~
|
34
|
+
.................+?I?~:~=++,=,::,~::==:==~::+?+++++???I=:==:....,+~,,::~~~~:,:~:
|
35
|
+
................:=+?+~:~=+===~++=+I?.,,==~=:+++++++?II?::=......~,,..:::~:=~~=~:
|
36
|
+
................,????+?==+,==+=+++=+==~~,,~+?+?+++I???~~~?.....==,,=,:,,,::::~~=
|
37
|
+
..........,.....,~==+?:,,::==+===~=~=+++++:~+++++?I?II,,.......,,,,~:,.=~,:,:::~
|
38
|
+
......,.....,....,~:::=~,:,=+=++++=++++=++=~++++??????..........,,,,:.,+~:,,=::~
|
39
|
+
...,...............:~,,~~:+=,?:==++++=+?+++:+++??+I?I+......,......,:.:,::,.,=::
|
40
|
+
....,.......,....?,::.~===,~=~~=??==?I?+~+?=,..~??+?~..................:,:.:,,::
|
41
|
+
............,..,.II,.~,~::==?~~+?+I++?I?+~+??==~+??+.......................:,.::
|
42
|
+
...........,..,,.III...=,,,,=????+?=I7?IIII+?==?+=~...........................:~
|
43
|
+
...,.,.....,.,.,=I7II...,,:,+:?=++?I+=+???+I????II,...........................,:
|
44
|
+
.,,........,,...IIIII7....,~:,+=?I?.,+~+?===,~IIII.............................:
|
45
|
+
...,.,..........I=~.II=,,,,,::~::?:+=,=,=~+=IIIIII,............................,
|
46
|
+
,,..,.,...,:.....7I..II::,,,,:,,,,,,:~~:++=?II?III=............................,
|
47
|
+
,....,...,.,,,..,,,77777I====~:~~~~~==+++=+7III7II7.............................
|
48
|
+
..,:,,,...:,,.,,,:I77777777I7==7==++++++=~+7I7I?77:,............................
|
49
|
+
,,..,...::..?77777777777777777777?+=++=~~+II7I7,,.,.............................
|
50
|
+
..,~..,,.77II77777777II77777777777777~~=:IIII,.,............................,...
|
51
|
+
,,.,:,,,:I77777777III77777777777777777?,I7I+.,..............................,...
|
52
|
+
,.,.,,....777II777I777777777I77777777I7III?=...................................,
|
53
|
+
:,........77I7I77I7777777777777777777777II?I,...................................
|
54
|
+
,,,,,.....+777I777777777777777777777777777??..............................,.....
|
55
|
+
,:,,,......77IIII77I77777777I77777777777777I..................,.................
|
56
|
+
..,,.,.....?777777II77I7I7777777777777777I7I,...................................
|
57
|
+
,,,.,.......77I++???????????I777777777777777,,,...........................,,.,.,
|
58
|
+
,,..::..:+???I???I??IIIIIII7II777777777777777.,..........,...............,..,,.,
|
59
|
+
.,:,,=?I??IIIIII?I??I????????7777777777777777I.............................,.,,.
|
60
|
+
.,,,=IIIIIIII???=~,,77I777777I7777777I7III7777,...........................,...,,
|
61
|
+
:...+IIIIII?????I???IIIIIIII??III777II77I77I,,,........................,.......,
|
62
|
+
...:??IIIIIIIIIII?IIIIIIIIIIIIII7II7I7I777II..............................,..,,,
|
63
|
+
...:?IIIIIIIIIIII?+I??????+????I77I77777?III.,......................,.......,,,,
|
64
|
+
,.,:?IIIIIIII?I?+++?IIIII??IIIIII7I77IIIII??...,.......................,..,,,,:,
|
65
|
+
..:~?IIIIIIIIII7IIIIIIIIIIIIIII7777IIIIII+++.,,,,,....................,,,,,,,,,,
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Drill
|
2
|
+
include Commands
|
3
|
+
attr_accessor :next, :previous, :hints
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
setup
|
7
|
+
@context = Pry.binding_for(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def drills
|
11
|
+
# Composite pattern: Allow for drill.start to work in the REPL or in tests.
|
12
|
+
[self]
|
13
|
+
end
|
14
|
+
|
15
|
+
def expected
|
16
|
+
eval(reference)
|
17
|
+
end
|
18
|
+
|
19
|
+
def done?(input)
|
20
|
+
case input.to_sym
|
21
|
+
when :menu
|
22
|
+
RubyDrills::Config::SESSIONS.command(self.class.name, input)
|
23
|
+
clear
|
24
|
+
true
|
25
|
+
when :exit
|
26
|
+
RubyDrills::Config::SESSIONS.command(self.class.name, input)
|
27
|
+
quit
|
28
|
+
when :show
|
29
|
+
RubyDrills::Config::SESSIONS.command(self.class.name, input)
|
30
|
+
show
|
31
|
+
when lambda {|s| Commands.instance_methods.include?(s) }
|
32
|
+
RubyDrills::Config::SESSIONS.command(self.class.name, input)
|
33
|
+
self.send(input)
|
34
|
+
else
|
35
|
+
check_answer(input)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def check_answer(input)
|
42
|
+
begin
|
43
|
+
Pry.run_command input, :context => @context, :output => answer = StringIO.new
|
44
|
+
Pry.run_command reference, :context => @context, :output => exp = StringIO.new
|
45
|
+
|
46
|
+
puts answer.string
|
47
|
+
|
48
|
+
return fail(input) if answer.string != exp.string
|
49
|
+
return false if !comparable_answer?(input)
|
50
|
+
win(input)
|
51
|
+
rescue SyntaxError => ex
|
52
|
+
puts "SyntaxError"
|
53
|
+
fail(input)
|
54
|
+
rescue StandardError => ex
|
55
|
+
puts "#{ex.inspect}"
|
56
|
+
fail(input)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def comparable_answer?(input)
|
61
|
+
valid?(input).tap do |pass|
|
62
|
+
fail(input, "\tyou have the right answer, but try a different method.") if !pass
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Drills
|
2
|
+
include Commands
|
3
|
+
|
4
|
+
def start
|
5
|
+
drill = linked_drills[0]
|
6
|
+
while drill do
|
7
|
+
drill.show
|
8
|
+
|
9
|
+
begin
|
10
|
+
input = Readline.readline("\n>> ", true)
|
11
|
+
end while (!drill.done?(input))
|
12
|
+
|
13
|
+
case input
|
14
|
+
when 'back'
|
15
|
+
drill = drill.previous
|
16
|
+
clear
|
17
|
+
when 'menu'
|
18
|
+
break
|
19
|
+
else
|
20
|
+
drill = drill.next
|
21
|
+
continue
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a linked list from the array of drills
|
28
|
+
def linked_drills
|
29
|
+
ordered_drills.tap do |linked|
|
30
|
+
for i in 0..linked.size-1
|
31
|
+
linked[i].previous = linked[i-1] unless (i == 0)
|
32
|
+
linked[i].next = linked[i+1] unless (i == linked.size-1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# deliver drills in random order
|
38
|
+
def ordered_drills
|
39
|
+
drills.shuffle
|
40
|
+
end
|
41
|
+
|
42
|
+
# Determines the list of drills in this directory
|
43
|
+
def drills
|
44
|
+
mod = self.class.name.gsub(/Drills/, '').downcase
|
45
|
+
Dir["#{File.dirname(__FILE__)}/#{mod}/*drill.rb"].map do |f|
|
46
|
+
require f
|
47
|
+
name = File.basename(f, '.rb')
|
48
|
+
clazz = name.split('_').map(&:capitalize).join
|
49
|
+
Module.const_get(clazz).new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class HashDrills < Drills
|
2
|
+
|
3
|
+
def banner
|
4
|
+
%{
|
5
|
+
Ruby Drills: Hash
|
6
|
+
|
7
|
+
A Hash is a dictionary-like collection of unique keys and their values.
|
8
|
+
Also called associative arrays, they are similar to Arrays, but where an
|
9
|
+
Array uses integers as its index, a Hash allows you to use any object type.
|
10
|
+
|
11
|
+
COMING SOON
|
12
|
+
|
13
|
+
------------------------------------------------------------------
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
module Sessions
|
5
|
+
class CollectorClient
|
6
|
+
include HTTParty
|
7
|
+
base_uri 'https://drill-collector.herokuapp.com'
|
8
|
+
# base_uri 'http://localhost:9091'
|
9
|
+
# debug_output $stderr
|
10
|
+
|
11
|
+
attr_reader :session_id
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@session_id = SecureRandom.urlsafe_base64
|
15
|
+
end
|
16
|
+
|
17
|
+
def command(name, input)
|
18
|
+
store({context: name, input: input, type: 'command'})
|
19
|
+
end
|
20
|
+
|
21
|
+
def attempt(name, input, reference, result)
|
22
|
+
store({context: name, input: input, reference: reference, result: result, type: 'attempt'})
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def store(entry)
|
28
|
+
Thread.new do
|
29
|
+
begin
|
30
|
+
data = entry.merge(timestamp: Sessions::Timestamp.collector, session_id: @session_id)
|
31
|
+
self.class.post('/record', {:body => data})
|
32
|
+
rescue => e
|
33
|
+
# silent scream for now...
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pstore'
|
3
|
+
|
4
|
+
module Sessions
|
5
|
+
class Local
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
DIR = '/usr/local/var/ruby_drills'
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
ensure_directory_exists
|
12
|
+
@db = PStore.new(File.join(DIR, 'progress.pstore'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def command(name, input)
|
16
|
+
store({context: name, input: input, type: 'command'})
|
17
|
+
end
|
18
|
+
|
19
|
+
def attempt(name, input, reference, result)
|
20
|
+
store({context: name, input: input, reference: reference, result: result, type: 'attempt'})
|
21
|
+
end
|
22
|
+
|
23
|
+
def stats
|
24
|
+
@db.transaction(true) do
|
25
|
+
@db.roots.each do |key|
|
26
|
+
puts @db[key].inspect
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ensure_directory_exists
|
34
|
+
FileUtils.mkdir_p(DIR) if !File.exists?(DIR)
|
35
|
+
end
|
36
|
+
|
37
|
+
def store(entry)
|
38
|
+
@db.transaction do
|
39
|
+
t = Sessions::Timestamp.collector
|
40
|
+
@db[t] = entry.merge({ time: t })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class StringDrills < Drills
|
2
|
+
|
3
|
+
def banner
|
4
|
+
%{
|
5
|
+
Ruby Drills: String
|
6
|
+
|
7
|
+
A String object holds and manipulates an arbitrary sequence of bytes,
|
8
|
+
typically representing characters.
|
9
|
+
|
10
|
+
COMING SOON
|
11
|
+
|
12
|
+
------------------------------------------------------------------
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/starter.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'slop'
|
2
|
+
require 'pry'
|
3
|
+
require 'colorize'
|
4
|
+
require 'readline'
|
5
|
+
|
6
|
+
require 'ruby_drills/version'
|
7
|
+
require 'ruby_drills/cli'
|
8
|
+
require 'ruby_drills/commands'
|
9
|
+
require 'ruby_drills/drill'
|
10
|
+
require 'ruby_drills/drills'
|
11
|
+
require 'ruby_drills/chomper'
|
12
|
+
require 'ruby_drills/sessions/collector_client'
|
13
|
+
require 'ruby_drills/sessions/timestamp'
|
14
|
+
require 'ruby_drills/config'
|
15
|
+
|
16
|
+
class Starter
|
17
|
+
include Commands
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
RubyDrills::Config.pry_config
|
21
|
+
clear
|
22
|
+
welcome
|
23
|
+
menu(drills)
|
24
|
+
clear
|
25
|
+
quit
|
26
|
+
end
|
27
|
+
|
28
|
+
def menu(options)
|
29
|
+
choice = nil
|
30
|
+
valid_choices = valid_number_to_drill_associations(options)
|
31
|
+
|
32
|
+
while (choice != 'q')
|
33
|
+
if valid_choices.include?(choice)
|
34
|
+
clear
|
35
|
+
run_drill(options[choice.to_i])
|
36
|
+
elsif (choice == 's')
|
37
|
+
RubyDrills::Config::SESSIONS.stats
|
38
|
+
end
|
39
|
+
|
40
|
+
puts "\nWhat would you like to learn next?\n\n"
|
41
|
+
options.each_with_index {|opt, i| puts "\t#{i}: #{opt.capitalize}"}
|
42
|
+
puts "\n\ts: see your stats"
|
43
|
+
puts "\tq: quit"
|
44
|
+
|
45
|
+
choice = Readline.readline("\n>> ", true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def drills
|
50
|
+
%w[welcome array string hash]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def valid_number_to_drill_associations(options)
|
56
|
+
(0...options.size).to_a.map {|x| x.to_s}
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_drill(d)
|
60
|
+
require "ruby_drills/#{d}/#{d}_drills"
|
61
|
+
clazz = Module.const_get("#{d.capitalize}Drills")
|
62
|
+
drill = clazz.new
|
63
|
+
puts drill.banner
|
64
|
+
continue
|
65
|
+
drill.start
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/ruby_drills.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruby_drills/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "ruby_drills"
|
8
|
+
gem.version = RubyDrills::VERSION
|
9
|
+
|
10
|
+
gem.authors = ["Bobby Norton"]
|
11
|
+
gem.email = ["bobby@testedminds.com"]
|
12
|
+
gem.description = "A deliberate practice tool for the core Ruby API's."
|
13
|
+
gem.summary = gem.description
|
14
|
+
gem.homepage = "http://rubydrills.com"
|
15
|
+
gem.license = "Apache 2.0"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.add_dependency("pry", "0.9.12.1")
|
23
|
+
gem.add_dependency("colorize", "0.5.8")
|
24
|
+
gem.add_dependency("httparty", "0.11.0")
|
25
|
+
|
26
|
+
gem.add_development_dependency("rake", "10.0.4")
|
27
|
+
gem.add_development_dependency("rspec", "~> 2.14.0.rc1")
|
28
|
+
gem.add_development_dependency("wrong", "0.7.0")
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands do
|
4
|
+
describe "#hint" do
|
5
|
+
it "returns hints in order" do
|
6
|
+
wrapper = Class.new do
|
7
|
+
include Commands
|
8
|
+
attr_accessor :hints
|
9
|
+
def setup
|
10
|
+
@hints = %w[first second third]
|
11
|
+
end
|
12
|
+
end.new
|
13
|
+
wrapper.setup
|
14
|
+
expect(wrapper).to receive(:puts).with("first").ordered
|
15
|
+
expect(wrapper).to receive(:puts).with("second").ordered
|
16
|
+
expect(wrapper).to receive(:puts).with("third").ordered
|
17
|
+
expect(wrapper).to receive(:puts).with("first").ordered
|
18
|
+
wrapper.hint
|
19
|
+
wrapper.hint
|
20
|
+
wrapper.hint
|
21
|
+
wrapper.hint
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED