ruby_drills 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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