rubedility 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/bin/rubedility +1 -2
- data/lib/rubedility/difficulty.rb +47 -0
- data/lib/rubedility/lesson.rb +103 -2
- data/lib/rubedility/scraper.rb +76 -0
- data/lib/rubedility/task.rb +64 -2
- data/lib/rubedility.rb +62 -10
- metadata +33 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e24c19eb9bf3ce8b7308fa7a0e1ca658cc1600a
|
|
4
|
+
data.tar.gz: 30d4aa32e0d4bd4205e4115d7bbe438cd15ff3f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 20d46cf961d88b5958b163d519124ff0fd5a3024dc2ebe35a10f73aef9eee091b387df1f198bc954adf1e152117995a61dcde3ed42223e8b6b9dfc07e0ba306f
|
|
7
|
+
data.tar.gz: 372d73efbf9659961cd90005028624e8ef844e0a20cf69dbdc41cd8be7699d78c71665bd07ed3e11a14f2d659c316ee3585668a0f6d7f7eec667dcdcf7c1a652
|
data/bin/rubedility
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Difficulty
|
|
2
|
+
attr_accessor :level
|
|
3
|
+
def initialize(difficulty)
|
|
4
|
+
@level = difficulty
|
|
5
|
+
@@all.push(self)
|
|
6
|
+
@all_tasks = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@@all = []
|
|
10
|
+
@all_tasks = []
|
|
11
|
+
|
|
12
|
+
def self.all
|
|
13
|
+
@@all
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def all
|
|
17
|
+
@all_tasks
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def display_tasks
|
|
21
|
+
self.all.each do |task|
|
|
22
|
+
puts task.name
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.display_all
|
|
27
|
+
self.all.each do |diff|
|
|
28
|
+
puts "\n=====#{diff.level}====="
|
|
29
|
+
diff.display_tasks
|
|
30
|
+
puts "=======================\n"
|
|
31
|
+
end
|
|
32
|
+
puts "\n"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.find_or_create(task_difficulty)
|
|
36
|
+
self.all.each do |d|
|
|
37
|
+
if d.level == task_difficulty
|
|
38
|
+
return d
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
return self.new(task_difficulty)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_task(task)
|
|
45
|
+
self.all.push(task)
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/rubedility/lesson.rb
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
class Rubedility::Lesson
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
attr_accessor :name, :number, :lesson_url, :reading_url, :tests_started, :tests_solved
|
|
3
|
+
|
|
4
|
+
@tasks = []
|
|
5
|
+
|
|
6
|
+
def initialize(lesson_hash)
|
|
7
|
+
add_lesson_attributes(lesson_hash)
|
|
8
|
+
@tasks = []
|
|
9
|
+
@@all.push(self)
|
|
4
10
|
end
|
|
11
|
+
|
|
12
|
+
@@all = []
|
|
13
|
+
|
|
14
|
+
def self.populate_from_scraping(lessons_array)
|
|
15
|
+
lessons_array.each do |lesson|
|
|
16
|
+
self.new(lesson)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.all
|
|
21
|
+
@@all
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.user_display_one
|
|
25
|
+
self.display_all
|
|
26
|
+
print "Select Lesson Number:"
|
|
27
|
+
input = gets.strip.to_i
|
|
28
|
+
self.all.each do |lesson|
|
|
29
|
+
if lesson.number==input
|
|
30
|
+
lesson.display_lesson
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
puts "Try selecting a correct number next time."
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def display_lesson
|
|
38
|
+
self.tasks.each do |task|
|
|
39
|
+
task.display_row
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.display_all
|
|
44
|
+
puts "\nAvailable Lessons: \n"
|
|
45
|
+
self.all.each do |les|
|
|
46
|
+
puts "#{les.number}. #{les.name}"
|
|
47
|
+
end
|
|
48
|
+
puts "\n"
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.user_display_stats
|
|
53
|
+
self.display_all
|
|
54
|
+
print "Select Lesson Number for stats:"
|
|
55
|
+
input = gets.strip.to_i
|
|
56
|
+
self.all.each do |lesson|
|
|
57
|
+
if lesson.number==input
|
|
58
|
+
lesson.display_stats
|
|
59
|
+
return nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
return "Try selecting a correct number next time."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def display_stats
|
|
66
|
+
puts "\n#{self.tests_started} tests have been started from this lesson."
|
|
67
|
+
puts "#{self.tests_solved} tests have been solved from this lesson."
|
|
68
|
+
puts "#{self.tasks.length} task(s) gives an average success rate of #{(self.tests_solved.to_f/self.tests_started.to_f).round(3)*100}%"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.user_open_reading
|
|
72
|
+
self.display_all
|
|
73
|
+
print "Select Lesson Number for reading:"
|
|
74
|
+
input = gets.strip.to_i
|
|
75
|
+
self.all.each do |lesson|
|
|
76
|
+
if lesson.number==input
|
|
77
|
+
#lesson.open_reading
|
|
78
|
+
return lesson.reading_url
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
return "Try selecting a correct number next time."
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def open_reading
|
|
85
|
+
puts "launchy: #{@reading_url}"
|
|
86
|
+
launchy @reading_url
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_tasks(task_array)
|
|
90
|
+
task_array.each do |task_row|
|
|
91
|
+
self.tasks.push(Rubedility::Task.new(task_row))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def tasks
|
|
96
|
+
@tasks
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def add_lesson_attributes(attributes_hash)
|
|
100
|
+
if attributes_hash == nil
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
attributes_hash.each{|key, val| self.send(("#{key}="), val)}
|
|
104
|
+
end
|
|
105
|
+
|
|
5
106
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'open-uri'
|
|
2
|
+
require 'nokogiri'
|
|
3
|
+
require 'pry'
|
|
4
|
+
|
|
5
|
+
class Scraper
|
|
6
|
+
|
|
7
|
+
def self.scrape_index_page(index_url)
|
|
8
|
+
index = Nokogiri::HTML(open(index_url))
|
|
9
|
+
|
|
10
|
+
lessons = []
|
|
11
|
+
index.css("div.lessons_list a").each do |lesson|
|
|
12
|
+
print "."
|
|
13
|
+
name = lesson.css("div.title").text
|
|
14
|
+
number = lesson.css("div.num").text.delete("Lesson").to_i
|
|
15
|
+
lesson_url = "".concat(index_url).concat(lesson.attr("href").split("/").last)
|
|
16
|
+
lessons.push({:name=>name, :number=>number, :lesson_url=>lesson_url})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
return lessons
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.scrape_lesson_page(lesson_url)
|
|
23
|
+
begin
|
|
24
|
+
print "."
|
|
25
|
+
lesson = Nokogiri::HTML(open(lesson_url))
|
|
26
|
+
if lesson.css("a#readings").length > 0
|
|
27
|
+
reading_url = lesson.css("a#readings").attr("href").value
|
|
28
|
+
end
|
|
29
|
+
tests_started = lesson.css("span.started span.num").text.to_i
|
|
30
|
+
tests_solved = lesson.css("span.finished span.num").text.to_i
|
|
31
|
+
task_hashes_array = []
|
|
32
|
+
lesson.css("div.task-box").each do |task_row|
|
|
33
|
+
name = task_row.css("h4.title").text.strip
|
|
34
|
+
#url is just the last 'piece' of the task URL
|
|
35
|
+
url = task_row.css("a").attr("href").text
|
|
36
|
+
#have to add that to the end of the 'real' URL, but take off part of it
|
|
37
|
+
task_url = lesson_url.split("/")[0..2].join("/").concat(url)
|
|
38
|
+
difficulty = task_row.css("div.difficulty").text.strip
|
|
39
|
+
tagline = task_row.css("div.synopsis").text.strip
|
|
40
|
+
task_hashes_array.push({:name=>name, :task_url=>task_url, :difficulty=>difficulty, :tagline=>tagline, :task_reading_url=>reading_url})
|
|
41
|
+
end
|
|
42
|
+
lesson_details = {:reading_url=>reading_url, :tests_started=>tests_started, :tests_solved=>tests_solved}
|
|
43
|
+
#return [hash-of-lesson-details, array-of-task-detail-hashes]
|
|
44
|
+
return [lesson_details, task_hashes_array]
|
|
45
|
+
rescue OpenURI::HTTPError => er
|
|
46
|
+
puts "404, Lesson not found"
|
|
47
|
+
puts lesson_url
|
|
48
|
+
puts er
|
|
49
|
+
return nil
|
|
50
|
+
else
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.scrape_task_page(task_url)
|
|
55
|
+
print "."
|
|
56
|
+
begin
|
|
57
|
+
task = Nokogiri::HTML(open(task_url))
|
|
58
|
+
content = task.css("div.desc-rb-en div").first.text
|
|
59
|
+
#the way they have the content is not best for command line display.
|
|
60
|
+
#some '\n' and some '\n\n', command line looks better with '\n\n'
|
|
61
|
+
#substitube singles for doubles
|
|
62
|
+
content.gsub!(/[\n]+/,"\n")
|
|
63
|
+
#substitute doubles for singles
|
|
64
|
+
content.gsub!(/[\n]/,"\n\n")
|
|
65
|
+
return {:content=>content}
|
|
66
|
+
rescue OpenURI::HTTPError => er
|
|
67
|
+
puts "404'd!"
|
|
68
|
+
puts task_url
|
|
69
|
+
puts er
|
|
70
|
+
return nil
|
|
71
|
+
else
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
data/lib/rubedility/task.rb
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
class Rubedility::Task
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
attr_accessor :name, :difficulty, :content, :task_url, :task_reading_url, :tagline
|
|
3
|
+
|
|
4
|
+
def initialize(task_row)
|
|
5
|
+
add_task_attributes(task_row)
|
|
6
|
+
@@all.push(self)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@@all = []
|
|
10
|
+
|
|
11
|
+
def self.create_from_collection(task_hash)
|
|
12
|
+
task_hash.each do |task|
|
|
13
|
+
Task.new(task)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_task_attributes(attributes_hash)
|
|
18
|
+
if attributes_hash == nil
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
attributes_hash.each do |key, val|
|
|
22
|
+
self.send(("#{key}="), val)
|
|
23
|
+
if key.to_s == "difficulty"
|
|
24
|
+
d = Difficulty.find_or_create(val)
|
|
25
|
+
d.add_task(self)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.all
|
|
31
|
+
@@all
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.user_display_one
|
|
35
|
+
print "Enter Task Name:"
|
|
36
|
+
input = gets.strip.downcase
|
|
37
|
+
self.all.each do |task|
|
|
38
|
+
if task.name.downcase==input
|
|
39
|
+
task.display_content
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
self.display_all
|
|
44
|
+
puts "Try better next time."
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def display_row
|
|
48
|
+
puts "=#{self.name}= (#{self.difficulty})"
|
|
49
|
+
puts "#{self.tagline}\n\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def display_content
|
|
53
|
+
puts "========================================="
|
|
54
|
+
puts "==========#{self.name}=========="
|
|
55
|
+
puts "========================================="
|
|
56
|
+
puts "Difficulty: #{self.difficulty}"
|
|
57
|
+
puts @content
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.display_all
|
|
61
|
+
puts "\nAvailable Tasks: \n"
|
|
62
|
+
self.all.each do |task|
|
|
63
|
+
task.display_row
|
|
64
|
+
end
|
|
65
|
+
return nil
|
|
4
66
|
end
|
|
5
67
|
end
|
data/lib/rubedility.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
require 'launchy'
|
|
2
|
+
|
|
1
3
|
class Rubedility
|
|
2
4
|
|
|
3
5
|
def initialize
|
|
6
|
+
scrape_index
|
|
4
7
|
scrape_lessons
|
|
5
8
|
scrape_tasks
|
|
6
9
|
display_greeting
|
|
7
|
-
display_menu
|
|
10
|
+
#display_menu (menu will display from the 'while' loop below)
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
def self.list
|
|
@@ -12,27 +15,76 @@ class Rubedility
|
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def run(commands=nil)
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
input = ""
|
|
19
|
+
until input=="exit" || input=="quit" || input=="close"
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
case input
|
|
22
|
+
when "list lessons"
|
|
23
|
+
Lesson.display_all
|
|
24
|
+
|
|
25
|
+
when "list tasks"
|
|
26
|
+
Task.display_all
|
|
27
|
+
|
|
28
|
+
when "list stats"
|
|
29
|
+
Lesson.user_display_stats
|
|
30
|
+
|
|
31
|
+
when "open lesson"
|
|
32
|
+
Lesson.user_display_one
|
|
33
|
+
|
|
34
|
+
when "open task"
|
|
35
|
+
Task.user_display_one
|
|
36
|
+
|
|
37
|
+
when "list by difficulty"
|
|
38
|
+
Difficulty.display_all
|
|
39
|
+
|
|
40
|
+
when "open reading"
|
|
41
|
+
Launchy.open(Lesson.user_open_reading)
|
|
42
|
+
|
|
43
|
+
else
|
|
44
|
+
display_menu
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
input = gets.strip
|
|
48
|
+
end
|
|
21
49
|
end
|
|
22
50
|
|
|
23
|
-
def
|
|
24
|
-
|
|
51
|
+
def scrape_index
|
|
52
|
+
print "fetching index ..."
|
|
53
|
+
Lesson.populate_from_scraping(Scraper.scrape_index_page('https://codility.com/programmers/lessons/'))
|
|
54
|
+
puts "\n"
|
|
25
55
|
end
|
|
26
56
|
|
|
27
57
|
def scrape_lessons
|
|
28
|
-
|
|
58
|
+
#this would be an ideal time for some Asynchronous magical powers of awesome
|
|
59
|
+
print "fetching lessons..."
|
|
60
|
+
Lesson.all.each do |lesson|
|
|
61
|
+
attributes_hash, task_array = Scraper.scrape_lesson_page(lesson.lesson_url)
|
|
62
|
+
lesson.add_lesson_attributes(attributes_hash)
|
|
63
|
+
lesson.add_tasks(task_array)
|
|
64
|
+
end
|
|
65
|
+
puts "\n"
|
|
29
66
|
end
|
|
30
67
|
|
|
31
68
|
def scrape_tasks
|
|
32
|
-
|
|
69
|
+
print "fetching tasks ..."
|
|
70
|
+
Task.all.each do |task|
|
|
71
|
+
task.add_task_attributes(Scraper.scrape_task_page(task.task_url))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def display_greeting
|
|
76
|
+
puts "\nWelcome to Rubedility!"
|
|
77
|
+
puts "Your command line access point for Ruby-flavored Codility lessons and tasks.\n"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def display_menu
|
|
81
|
+
puts "Commands: '<list/open> <lesson(s)/task(s)/stats/reading>', 'list by difficulty', 'exit'"
|
|
82
|
+
#puts "Commands: 'list lessons', 'open lesson', 'list tasks', 'open task', 'list stats', 'open reading', 'exit'"
|
|
33
83
|
end
|
|
34
84
|
|
|
35
85
|
end
|
|
36
86
|
|
|
37
87
|
require 'rubedility/lesson'
|
|
38
88
|
require 'rubedility/task'
|
|
89
|
+
require 'rubedility/scraper'
|
|
90
|
+
require 'rubedility/difficulty'
|
metadata
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubedility
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Holland
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2016-03-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: launchy
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: nokogiri
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
13
41
|
description: Command line access to Codility.com programming lessons and tests
|
|
14
42
|
email: beehollander@gmail.com
|
|
15
43
|
executables:
|
|
@@ -19,7 +47,9 @@ extra_rdoc_files: []
|
|
|
19
47
|
files:
|
|
20
48
|
- bin/rubedility
|
|
21
49
|
- lib/rubedility.rb
|
|
50
|
+
- lib/rubedility/difficulty.rb
|
|
22
51
|
- lib/rubedility/lesson.rb
|
|
52
|
+
- lib/rubedility/scraper.rb
|
|
23
53
|
- lib/rubedility/task.rb
|
|
24
54
|
homepage: http://rubygems.org/gems/rubedility
|
|
25
55
|
licenses:
|