share_learning 0.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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/LICENSE +24 -0
- data/README.md +146 -0
- data/Rakefile +48 -0
- data/bin/coursera +55 -0
- data/bin/udacity +83 -0
- data/bin/youtube +21 -0
- data/lib/share_learning/coursera_api.rb +110 -0
- data/lib/share_learning/coursera_courses.rb +59 -0
- data/lib/share_learning/share_learning_version.rb +3 -0
- data/lib/share_learning/udacity_api.rb +29 -0
- data/lib/share_learning/udacity_course.rb +101 -0
- data/lib/share_learning/youtube_api.rb +26 -0
- data/lib/share_learning/youtube_playlist.rb +33 -0
- data/lib/share_learning.rb +4 -0
- data/share_learning.gemspec +33 -0
- data/spec/coursera_api_spec.rb +48 -0
- data/spec/coursera_api_spec_helper.rb +13 -0
- data/spec/data/course_test_data.json +17 -0
- data/spec/udacity_api_spec.rb +64 -0
- data/spec/udacity_api_spec_helper.rb +18 -0
- data/spec/udacity_exe_spec.rb +51 -0
- data/spec/udacity_exe_spec_helper.rb +16 -0
- data/spec/youtube_api_spec.rb +23 -0
- data/spec/youtube_api_spec_helper.rb +21 -0
- metadata +226 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 51881b5c17d1723c09e004f02a36627054aef086
|
4
|
+
data.tar.gz: ff070d4da0cb833834c221eecdc5ef8f25e5bbd2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62e1d9b8dbabc97ec0b5b664611c50fd895ba76fca95298c66c19a65897cfe503a43506f2ac3da3cef1245abfe2b33d69151cce57329b777a7e748b555751d39
|
7
|
+
data.tar.gz: 472b4fcb6e1f682e8eaa8f7176f0f3ca2a6f3492ab0b4203f3834881889fe1dacc4acd6cf8681a6f2ee7b75c5c38f9e9ec3775e41dcd5dee279e5db2790d7fa9
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c)
|
4
|
+
ashleycheng <ashley830204@gmail.com>
|
5
|
+
blureze <blureze@gmail.com>
|
6
|
+
meegoStar <andy19933@gmail.com>
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
16
|
+
all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
|
2
|
+
# share_learning
|
3
|
+
[](https://badge.fury.io/rb/Share_learning)
|
4
|
+
[](https://travis-ci.org/BlueStarAshes/Share_learning)
|
5
|
+
|
6
|
+
|
7
|
+
## Introduction
|
8
|
+
`share_learning` is aimed to be a crowd sourcing open learning platform.
|
9
|
+
|
10
|
+
It collects courses links from MOOCS like Coursera, Udacity, Udemy, etc. and relating videos links on Youtube, and lets users easily find learning resources matching their familiarity to a want-to-learn subject / topic on just one website. Users can leave suggestions, tags, and ratings to each learning resource. They can also add relating learning resource links to each subject / topic.
|
11
|
+
|
12
|
+
But now it is still under construction. :sweat_smile: :beer:
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
If you are working on a project, add this to your Gemfile: `gem 'share_learning'`
|
16
|
+
|
17
|
+
For ad hoc installation from command line:
|
18
|
+
|
19
|
+
`$ gem install share_learning`
|
20
|
+
|
21
|
+
|
22
|
+
## Setup Youtube Credentials
|
23
|
+
Please setup your YouTube credentials by create a project in the Google Developers Console: https://console.developers.google.com and obtain authorization credentials so your application can submit API requests. You should get a YouTube API key.
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Coursera
|
27
|
+
#### Command line
|
28
|
+
To use `coursera` in command line, please type as the folling format:
|
29
|
+
`coursera [command] [keyword]`
|
30
|
+
|
31
|
+
`[command]` now includes `[title]`, and `[description]`.
|
32
|
+
|
33
|
+
* `[title]`
|
34
|
+
helps you search courses on Coursea with titles containing the `[keyword]` you give.
|
35
|
+
|
36
|
+
* `[description]`
|
37
|
+
helps you search courses on Coursea with descriptions containing the `[keyword]` you give.
|
38
|
+
|
39
|
+
|
40
|
+
#### In your project
|
41
|
+
To use `coursera` in your project, `require 'share_learning'` in your code.
|
42
|
+
|
43
|
+
See the following code for more detail of the usage.
|
44
|
+
```ruby
|
45
|
+
# Access the courses on Coursera
|
46
|
+
courses = Coursera::CourseraCourses.find.courses
|
47
|
+
|
48
|
+
# Check how many courses there are on Coursera
|
49
|
+
total = Coursera::CourseraCourses.find.total_course_num
|
50
|
+
|
51
|
+
# Access each course's title, type, ID, Slug, link, description, and photo URL
|
52
|
+
courses.size.times do |i|
|
53
|
+
course = courses[i]
|
54
|
+
puts "Course #{sequence_number}:\n"\
|
55
|
+
"\tTitle: #{course[:course_name]}\n"\
|
56
|
+
"\tType: #{course[:course_type]}\n"\
|
57
|
+
"\tID: #{course[:course_id]}\n"\
|
58
|
+
"\tSlug: #{course[:course_slug]}\n"\
|
59
|
+
"\tLink: #{course[:link]}\n"\
|
60
|
+
"\tDescription: #{course[:description][0..100]}...\n"\
|
61
|
+
"\tPhoto URL: #{course[:photo_url]}\n"\
|
62
|
+
"\n"
|
63
|
+
|
64
|
+
# Search courses with titles or descriptions containing a given keyword
|
65
|
+
# results is an array of hash where each hash represents a course
|
66
|
+
keyword = 'machine learning'
|
67
|
+
results = Coursera::CourseraCourses.find.search_courses(:all, keyword)
|
68
|
+
|
69
|
+
# Search courses with titles containing a given keyword
|
70
|
+
results = Coursera::CourseraCourses.find.search_courses(:course_name, keyword)
|
71
|
+
|
72
|
+
# Search courses with descriptions containing a given keyword
|
73
|
+
results = Coursera::CourseraCourses.find.search_courses(:description, keyword)
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Udacity
|
78
|
+
Udacity application allows you to get the information includes title, introduction, link to the homepage and the image of the course. There are two ways to use the Udacity application:
|
79
|
+
#### Command line
|
80
|
+
|
81
|
+
BASIC USAGE: `udacity [command][feature]`
|
82
|
+
|
83
|
+
`[command]` now includes `[help]`, `[all]`, `[id]`, `[title]` and `[search]`
|
84
|
+
* `[help]`
|
85
|
+
Give the introduction of how to use it.
|
86
|
+
* `[all]`
|
87
|
+
List all the courses on Udacity.
|
88
|
+
* `[id]`
|
89
|
+
Search a particular course on Udacity with the specific course id.
|
90
|
+
* `[title]`
|
91
|
+
Search a particular course on Udacity with the specific course title.
|
92
|
+
* `[search]`
|
93
|
+
Search courses on Udacity with the keyword.
|
94
|
+
|
95
|
+
`[feature]` is only needed when using the command`[id]`,`[title]` and `[search]`
|
96
|
+
* If you're using the command `[id]`, then `[feature]` is the course id
|
97
|
+
* If you're using the command `[title]`, then `[feature]` is the course title
|
98
|
+
* If you're using the command `[search]`, then `[feature]` is the keyword you want to search
|
99
|
+
* Example: `udacity id 'cs101'` or `udacity title 'Introduction to Virtual Reality'` or `udacity search 'java'`
|
100
|
+
|
101
|
+
#### In your project
|
102
|
+
|
103
|
+
First, `require 'Share_learning'` in your code.
|
104
|
+
See the following example code for more usage details:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
# Access courses data
|
108
|
+
courses = Udacity::UdacityCourse.find()
|
109
|
+
|
110
|
+
# Acquire all courses information
|
111
|
+
all_courses = courses.acquire_all_courses
|
112
|
+
|
113
|
+
# Acquire course information with a given course id
|
114
|
+
# example: courses.acquire_course_by_id('cs101')
|
115
|
+
get_course_by_id = courses.acquire_course_by_id(course_id)
|
116
|
+
|
117
|
+
|
118
|
+
# Acquire course information with a given course title
|
119
|
+
# # example: courses.acquire_course_by_title('Introduction to Virtual Reality')
|
120
|
+
get_course_by_id = courses.acquire_course_by_title(course_title)
|
121
|
+
|
122
|
+
```
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
### YouTube
|
127
|
+
#### Command line
|
128
|
+
* Setup environment variables: `ENV[YOUTUBE_API_KEY]`
|
129
|
+
* USAGE: `youtube [keyword]`
|
130
|
+
* `[keyword]` - helps you search and get information of playlists on YouTube.
|
131
|
+
|
132
|
+
#### In your project
|
133
|
+
* `require 'Share_learning'`
|
134
|
+
See the following example code for more usage details:
|
135
|
+
```ruby
|
136
|
+
# Access playlists data
|
137
|
+
playlist_data = YouTube::YouTubePlaylist.find(keyword: 'keyword')
|
138
|
+
playlist_data.results.each.with_index do |playlist, index|
|
139
|
+
print "#{index + 1}. "
|
140
|
+
puts "Playlist on YouTube: #{playlist['title']}"
|
141
|
+
puts "Description: #{playlist['description']}"
|
142
|
+
puts "Image: #{playlist['image']}"
|
143
|
+
puts "URL: #{playlist['url']}"
|
144
|
+
puts
|
145
|
+
end
|
146
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
namespace :tests do
|
4
|
+
desc 'run all tests'
|
5
|
+
task all: [:spec_udacity, :spec_coursera, :spec_youtube]
|
6
|
+
|
7
|
+
task :spec_udacity do
|
8
|
+
sh 'ruby spec/udacity_api_spec.rb'
|
9
|
+
puts "\n\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
task :spec_coursera do
|
13
|
+
sh 'ruby spec/coursera_api_spec.rb'
|
14
|
+
puts "\n\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
task :spec_youtube do
|
18
|
+
sh 'ruby spec/youtube_api_spec.rb'
|
19
|
+
puts "\n\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'delete cassette fixtures'
|
24
|
+
task :wipe do
|
25
|
+
sh 'rm spec/fixtures/cassettes/*.yml' do |ok, _|
|
26
|
+
puts(ok ? 'Cassettes deleted' : 'No casseettes found')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :quality do
|
31
|
+
desc 'run all quality checks'
|
32
|
+
task all: [:flog, :flay, :rubocop]
|
33
|
+
|
34
|
+
task :rubocop do
|
35
|
+
sh 'rubocop lib/share_learning/udacity_course.rb lib/share_learning/coursera_* lib/share_learning/youtube_playlist.rb'
|
36
|
+
puts "\n\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
task :flog do
|
40
|
+
sh 'flog lib/share_learning/udacity_course.rb lib/share_learning/coursera_* lib/share_learning/youtube_playlist.rb'
|
41
|
+
puts "\n\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
task :flay do
|
45
|
+
sh 'flay lib/share_learning/udacity_course.rb lib/share_learning/coursera_* lib/share_learning/youtube_playlist.rb'
|
46
|
+
puts "\n\n"
|
47
|
+
end
|
48
|
+
end
|
data/bin/coursera
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
|
3
|
+
require 'share_learning'
|
4
|
+
|
5
|
+
def command_valid?(query)
|
6
|
+
%w(title description).include?(query)
|
7
|
+
end
|
8
|
+
|
9
|
+
def help
|
10
|
+
puts "USAGE: coursera [command] [keyword]\n\n"\
|
11
|
+
"[command] now includes [title], [description].\n"\
|
12
|
+
" - \n"\
|
13
|
+
" - [title]\t\t"\
|
14
|
+
'helps you search courses on Coursea with titles'\
|
15
|
+
" containing the [keyword] you give.\n"\
|
16
|
+
" - \n"\
|
17
|
+
" - [description]\t"\
|
18
|
+
'helps you search courses on Coursea with descriptions '\
|
19
|
+
"containing the [keyword] you give.\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
def reject
|
23
|
+
help
|
24
|
+
exit(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def show_course(course, sequence_number)
|
28
|
+
puts "Course #{sequence_number}:\n"\
|
29
|
+
"\tTitle: #{course[:course_name]}\n"\
|
30
|
+
"\tType: #{course[:course_type]}\n"\
|
31
|
+
"\tSlug: #{course[:course_slug]}\n"\
|
32
|
+
"\tLink: #{course[:link]}\n"\
|
33
|
+
"\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute_command(command, keyword)
|
37
|
+
case command
|
38
|
+
when 'title'
|
39
|
+
method = :course_name
|
40
|
+
when 'description'
|
41
|
+
method = :description
|
42
|
+
end
|
43
|
+
reject unless keyword
|
44
|
+
# search_courses(search_method, keyword)
|
45
|
+
results = Coursera::CourseraCourses.find.search_courses(method, keyword)
|
46
|
+
results.size.times { |i| show_course(results[i], i + 1) }
|
47
|
+
end
|
48
|
+
|
49
|
+
command = ARGV[0]
|
50
|
+
keyword = ARGV[1]
|
51
|
+
keyword = keyword.downcase if keyword
|
52
|
+
|
53
|
+
reject unless command_valid?(command)
|
54
|
+
|
55
|
+
execute_command(command, keyword)
|
data/bin/udacity
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
|
4
|
+
require 'share_learning'
|
5
|
+
|
6
|
+
def show(course)
|
7
|
+
puts 'title: ' + course[:title]
|
8
|
+
puts 'introduction: ' + course[:intro]
|
9
|
+
puts 'link: ' + course[:link]
|
10
|
+
end
|
11
|
+
|
12
|
+
def help
|
13
|
+
puts 'USAGE: udacity [command]'
|
14
|
+
puts 'command: '
|
15
|
+
puts ' - all: show all the courses information in Udacity'
|
16
|
+
puts ' - id: search for a course by specific course id'
|
17
|
+
puts ' - title: search for a course by specific course title'
|
18
|
+
puts ' - search: search for courses related to the keyword'
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_command_available(command)
|
22
|
+
case command
|
23
|
+
when 'help', 'all', 'id', 'title', 'search'
|
24
|
+
else
|
25
|
+
puts 'USAGE: udacity [help] [all] [id course_id] [title course_title] [search keyowrd]'
|
26
|
+
puts "Use 'udacity help' to see more information."
|
27
|
+
exit(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_feature_available(command, feature)
|
32
|
+
case command
|
33
|
+
when 'id'
|
34
|
+
unless feature
|
35
|
+
puts 'USAGE: udacity id [course_id]'
|
36
|
+
exit(1)
|
37
|
+
end
|
38
|
+
when 'title'
|
39
|
+
unless feature
|
40
|
+
puts 'USAGE: udacity title [course_title]'
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
when 'search'
|
44
|
+
unless feature
|
45
|
+
puts 'USAGE: udacity search [keyword]'
|
46
|
+
exit(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def action(command, feature=nil)
|
52
|
+
case command
|
53
|
+
when 'help'
|
54
|
+
help
|
55
|
+
when 'all'
|
56
|
+
course = Udacity::UdacityCourse.find().acquire_all_courses
|
57
|
+
show(course)
|
58
|
+
when 'id'
|
59
|
+
check_feature_available('id', feature)
|
60
|
+
course = Udacity::UdacityCourse.find().acquire_course_by_id(feature)
|
61
|
+
show(course)
|
62
|
+
when 'title'
|
63
|
+
check_feature_available('title', feature)
|
64
|
+
course = Udacity::UdacityCourse.find().acquire_course_by_title(feature)
|
65
|
+
show(course)
|
66
|
+
when 'search'
|
67
|
+
check_feature_available('search', feature)
|
68
|
+
course = Udacity::UdacityCourse.find().acquire_courses_by_keywords(feature)
|
69
|
+
if course.class == Array
|
70
|
+
course.each do |item|
|
71
|
+
show(item)
|
72
|
+
puts "\n" + "------"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
puts 'no courses found'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
command = ARGV[0]
|
81
|
+
feature = ARGV[1]
|
82
|
+
check_command_available(command)
|
83
|
+
action(command, feature)
|
data/bin/youtube
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
|
4
|
+
require 'share_learning'
|
5
|
+
|
6
|
+
keyword = ARGV[0] || ENV['KEYWORD']
|
7
|
+
unless keyword
|
8
|
+
puts 'USAGE: share_learning [keyword]'
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
playlist_data = YouTube::YouTubePlaylist.find(keyword: keyword)
|
13
|
+
|
14
|
+
playlist_data.results.each.with_index do |playlist, index|
|
15
|
+
print "#{index + 1}. "
|
16
|
+
puts "Playlist on YouTube: #{playlist['title']}"
|
17
|
+
puts "Description: #{playlist['description']}"
|
18
|
+
puts "Image: #{playlist['image']}"
|
19
|
+
puts "URL: #{playlist['url']}"
|
20
|
+
puts
|
21
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Coursera
|
5
|
+
# This class is in charge of the work involving using Coursera API
|
6
|
+
class CourseraApi
|
7
|
+
COURSERA_CATOLOG_API_URL = 'https://api.coursera.org/api/courses.v1'.freeze
|
8
|
+
COURSERA_COURSE_LINK_BASE = 'https://www.coursera.org/learn/'.freeze
|
9
|
+
BATCH_SIZE = 100
|
10
|
+
|
11
|
+
def self.total_course_num
|
12
|
+
@total_course_num = retrieve_total_course_num
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.courses
|
16
|
+
return @courses if @courses
|
17
|
+
@courses = retrieve_all_courses
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.retrieve_total_course_num
|
21
|
+
return @total_course_num if @total_course_num
|
22
|
+
# Retrieve the total number of courses on the catlog
|
23
|
+
course_start_num = 0
|
24
|
+
course_num_limit = 0
|
25
|
+
query_response =
|
26
|
+
HTTP.get(COURSERA_CATOLOG_API_URL,
|
27
|
+
params: { start: course_start_num,
|
28
|
+
limit: course_num_limit })
|
29
|
+
JSON.parse(query_response.body.to_s)['paging']['total']
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.retrieve_all_courses
|
33
|
+
# Create an dump for retrieved courses
|
34
|
+
retrieved_courses = {}
|
35
|
+
# Set the relating numbers
|
36
|
+
batch_start = 0
|
37
|
+
# Retrieve courses batch by batch
|
38
|
+
number_of_batches.times do
|
39
|
+
# Jump out the loop if we know there is not any courses left
|
40
|
+
break if batch_start >= @total_course_num
|
41
|
+
|
42
|
+
retrieved_courses = new_batch_merged(retrieved_courses, batch_start)
|
43
|
+
# Increase the course number to start with next batch
|
44
|
+
batch_start += BATCH_SIZE
|
45
|
+
end
|
46
|
+
retrieved_courses
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.number_of_batches
|
50
|
+
result = retrieve_total_course_num / BATCH_SIZE
|
51
|
+
result += 1 unless (@total_course_num % BATCH_SIZE).zero?
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.new_batch_merged(retrieved_courses, course_start)
|
56
|
+
# Use the API to get courses response on Coursera
|
57
|
+
retrieved_batch =
|
58
|
+
query_a_batch_of_courses(course_start)
|
59
|
+
# Parse the response to get the desired courses data
|
60
|
+
parsed_batch = parse_batch_result(course_start, retrieved_batch)
|
61
|
+
# Merge the parsed batch of courses
|
62
|
+
retrieved_courses.merge(parsed_batch)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.query_a_batch_of_courses(course_start)
|
66
|
+
# Create an API response dump
|
67
|
+
retrieved_batch = {}
|
68
|
+
query_response =
|
69
|
+
HTTP.get(COURSERA_CATOLOG_API_URL,
|
70
|
+
params: { start: course_start,
|
71
|
+
limit: BATCH_SIZE,
|
72
|
+
fields: 'description,photoUrl' })
|
73
|
+
body_str = query_response.body.to_s
|
74
|
+
retrieved_batch[:courses] = JSON.parse(body_str)['elements']
|
75
|
+
retrieved_batch
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.parse_batch_result(course_start, retrieved_batch)
|
79
|
+
# Create an parsed dump
|
80
|
+
parsed_courses = {}
|
81
|
+
count = course_start
|
82
|
+
retrieved_batch[:courses].each do |course|
|
83
|
+
parsed_courses[count] = parse_course(course)
|
84
|
+
count += 1
|
85
|
+
end
|
86
|
+
parsed_courses
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.parse_course(course)
|
90
|
+
parsed_course = {}
|
91
|
+
parsed_course[:course_type] = course['courseType']
|
92
|
+
parsed_course[:course_id] = course['id']
|
93
|
+
parsed_course[:course_slug] = course['slug']
|
94
|
+
parsed_course[:course_name] = course['name']
|
95
|
+
parsed_course[:link] =
|
96
|
+
COURSERA_COURSE_LINK_BASE + course['slug']
|
97
|
+
parsed_course[:description] = course['description']
|
98
|
+
parsed_course[:photo_url] = course['photoUrl']
|
99
|
+
parsed_course
|
100
|
+
end
|
101
|
+
|
102
|
+
private_class_method :retrieve_total_course_num
|
103
|
+
private_class_method :retrieve_all_courses
|
104
|
+
private_class_method :number_of_batches
|
105
|
+
private_class_method :new_batch_merged
|
106
|
+
private_class_method :query_a_batch_of_courses
|
107
|
+
private_class_method :parse_batch_result
|
108
|
+
private_class_method :parse_course
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'coursera_api'
|
2
|
+
|
3
|
+
module Coursera
|
4
|
+
# This class is for Coursera courses
|
5
|
+
class CourseraCourses
|
6
|
+
attr_reader :total_course_num, :courses
|
7
|
+
|
8
|
+
def initialize(total_course_num, courses)
|
9
|
+
@total_course_num = total_course_num
|
10
|
+
@courses = courses
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find
|
14
|
+
total_course_num = CourseraApi.total_course_num
|
15
|
+
courses = CourseraApi.courses
|
16
|
+
new(total_course_num, courses)
|
17
|
+
end
|
18
|
+
|
19
|
+
def course_matched?(course, method, keyword)
|
20
|
+
available_methods = [:course_name, :description]
|
21
|
+
result = false
|
22
|
+
available_methods.each do |m|
|
23
|
+
next unless method == :all || m == method
|
24
|
+
result = true if course[m].downcase.include?(keyword)
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def search_courses(method, keyword)
|
30
|
+
matched_courses = []
|
31
|
+
@courses.size.times do |i|
|
32
|
+
course = courses[i]
|
33
|
+
matched_courses.push(course) if course_matched?(course, method, keyword)
|
34
|
+
end
|
35
|
+
matched_courses
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_all_courses
|
39
|
+
puts "#{@total_course_num} courses:"
|
40
|
+
@courses.size.times do |i|
|
41
|
+
print_course(@courses[i], i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_course(course, sequence_number)
|
46
|
+
puts "Course #{sequence_number}:\n"\
|
47
|
+
"\tTitle: #{course[:course_name]}\n"\
|
48
|
+
"\tType: #{course[:course_type]}\n"\
|
49
|
+
"\tID: #{course[:course_id]}\n"\
|
50
|
+
"\tSlug: #{course[:course_slug]}\n"\
|
51
|
+
"\tLink: #{course[:link]}\n"\
|
52
|
+
"\tDescription: #{course[:description][0..100]}...\n"\
|
53
|
+
"\tPhoto URL: #{course[:photo_url]}\n"\
|
54
|
+
"\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
private :print_course
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Udacity
|
5
|
+
# Service for all Udacity API calls
|
6
|
+
class UdacityAPI
|
7
|
+
UDACITY_URL = 'https://www.udacity.com/public-api/v0/courses'.freeze
|
8
|
+
|
9
|
+
# get all courses info in json format through RESTful API
|
10
|
+
def self.acquire_json_response
|
11
|
+
response = HTTP.get(URI.parse(UDACITY_URL))
|
12
|
+
JSON.parse(response)
|
13
|
+
end
|
14
|
+
|
15
|
+
# get total courses number
|
16
|
+
def self.total_course_num
|
17
|
+
@total_course_num = retrieve_total_course_num
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.retrieve_total_course_num
|
21
|
+
return @total_course_num if @total_course_num
|
22
|
+
|
23
|
+
# Retrieve the total number of courses on the catlog
|
24
|
+
json_resp = acquire_json_response
|
25
|
+
puts json_resp.class
|
26
|
+
@total_course_num = json_resp['courses'].size
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'json'
|
3
|
+
require_relative 'udacity_api'
|
4
|
+
|
5
|
+
module Udacity
|
6
|
+
# Service for all Udacity API calls
|
7
|
+
class UdacityCourse
|
8
|
+
attr_reader :json_response, :total_course_num
|
9
|
+
|
10
|
+
def initialize(udacity_api, data, total_course_num)
|
11
|
+
@udacity_api = udacity_api
|
12
|
+
@json_response = data
|
13
|
+
@total_course_num = total_course_num
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_hash(title, intro, link, image)
|
17
|
+
{ title: title, intro: intro, link: link, image: image }
|
18
|
+
end
|
19
|
+
|
20
|
+
# get all courses information
|
21
|
+
def acquire_all_courses
|
22
|
+
course_array = []
|
23
|
+
@json_response['courses'].each do |course|
|
24
|
+
h = create_hash(course['title'], course['summary'], \
|
25
|
+
course['homepage'], course['image'])
|
26
|
+
course_array.push(h)
|
27
|
+
end
|
28
|
+
course_array
|
29
|
+
end
|
30
|
+
|
31
|
+
# get course information by course id
|
32
|
+
def acquire_course_by_id(id)
|
33
|
+
acquire_course_info('key', id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# get course information by course title
|
37
|
+
def acquire_course_by_title(title)
|
38
|
+
acquire_course_info('title', title)
|
39
|
+
end
|
40
|
+
|
41
|
+
# get course information
|
42
|
+
def acquire_course_info(key, value)
|
43
|
+
@json_response['courses'].each do |course|
|
44
|
+
next unless course[key] == value
|
45
|
+
h = create_hash(course['title'], course['summary'], \
|
46
|
+
course['homepage'], course['image'])
|
47
|
+
return h
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def substring?(title, summary, keyword)
|
52
|
+
((title.include? keyword) || (summary.include? keyword))
|
53
|
+
end
|
54
|
+
|
55
|
+
def append_course(course_array, course)
|
56
|
+
h = create_hash(course['title'], course['summary'], \
|
57
|
+
course['homepage'], course['image'])
|
58
|
+
course_array.push(h)
|
59
|
+
course_array
|
60
|
+
end
|
61
|
+
|
62
|
+
def acquire_courses_by_keywords(keyword)
|
63
|
+
course_array = []
|
64
|
+
@json_response['courses'].each do |course|
|
65
|
+
next unless substring?(course['title'].downcase, \
|
66
|
+
course['summary'].downcase, keyword.downcase)
|
67
|
+
|
68
|
+
course_array = append_course(course_array, course)
|
69
|
+
end
|
70
|
+
|
71
|
+
return 'no courses found' if course_array.empty?
|
72
|
+
course_array # return course_array if it is not empty
|
73
|
+
end
|
74
|
+
|
75
|
+
# # get courses by skill levels ('', 'beginner', 'intermediate', 'advanced')
|
76
|
+
# def acquire_courses_by_level(level)
|
77
|
+
# course_array = []
|
78
|
+
# @json_response['courses'].each do |course|
|
79
|
+
# next unless course['level'] == level
|
80
|
+
# h = create_hash(course['title'], course['summary'], \
|
81
|
+
# course['homepage'], course['image'])
|
82
|
+
# course_array.push(h)
|
83
|
+
# end
|
84
|
+
# course_array
|
85
|
+
# end
|
86
|
+
|
87
|
+
# # get courses by tracks (return a list of course id)
|
88
|
+
# def acquire_courses_by_tracks(track_name)
|
89
|
+
# @json_response['tracks'].each do |track|
|
90
|
+
# next unless track['name'] == track_name
|
91
|
+
# return track['courses'].inspect
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
|
95
|
+
def self.find
|
96
|
+
course_data = UdacityAPI.acquire_json_response
|
97
|
+
total_course_num = UdacityAPI.total_course_num
|
98
|
+
new(UdacityAPI, course_data, total_course_num)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module YouTube
|
5
|
+
# using YouTube API to get playlists
|
6
|
+
class YouTubeAPI
|
7
|
+
max_results = 8 # number of videos that should be returned
|
8
|
+
YouTube_URL = 'https://www.googleapis.com/youtube/v3/search?part=snippet&type=playlist&order=relevance&maxResults=' + max_results.to_s
|
9
|
+
|
10
|
+
def self.config=(credentials)
|
11
|
+
@config ? @config.update(credentials) : @config = credentials
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
return @config if @config
|
16
|
+
@config = { api_key: ENV['YOUTUBE_API_KEY'] } # export YOUTUBE_API_KEY=....
|
17
|
+
end
|
18
|
+
|
19
|
+
# Retrieve the search results
|
20
|
+
def self.get_playlist(keyword)
|
21
|
+
search_response =
|
22
|
+
HTTP.get(YouTube_URL + '&q=' + keyword.split().join('+') + '&key=' + config[:api_key])
|
23
|
+
JSON.parse(search_response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'youtube_api'
|
2
|
+
|
3
|
+
module YouTube
|
4
|
+
# Playlist on Youtube
|
5
|
+
class YouTubePlaylist
|
6
|
+
attr_reader :results
|
7
|
+
|
8
|
+
def initialize(data: nil)
|
9
|
+
@results = load_data(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find(keyword:)
|
13
|
+
playlists = YouTube::YouTubeAPI.get_playlist(keyword)
|
14
|
+
new(data: playlists)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Get information of playlists
|
20
|
+
def load_data(playlists)
|
21
|
+
results = []
|
22
|
+
playlists['items'].each do |playlist|
|
23
|
+
title = playlist['snippet']['title']
|
24
|
+
des = playlist['snippet']['description']
|
25
|
+
image = playlist['snippet']['thumbnails']['high']['url']
|
26
|
+
playlistId = playlist['id']['playlistId']
|
27
|
+
url = 'https://www.youtube.com/channel/' + playlistId
|
28
|
+
results.push({'title' => title, 'description' => des, 'image' => image, 'url' => url})
|
29
|
+
end
|
30
|
+
results
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'share_learning/share_learning_version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'share_learning'
|
7
|
+
s.version = ShareLearning::VERSION
|
8
|
+
|
9
|
+
s.summary = 'Gets learning resource from Coursera, Udacity, and Youtube.'
|
10
|
+
s.description = 'Extracts course\'s titles, descriptions, images and links '\
|
11
|
+
'from Coursera and Udacity. '\
|
12
|
+
'And searches relating resource on Youtube.'
|
13
|
+
s.authors = ['ashleycheng, blureze, meegoStar']
|
14
|
+
s.email = ['ashley830204@gmail.com', 'blureze@gmail.com', 'andy19933@gmail.com']
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
18
|
+
s.executables << 'udacity' << 'coursera' << 'youtube'
|
19
|
+
|
20
|
+
s.add_runtime_dependency 'http', '~> 2.0'
|
21
|
+
|
22
|
+
s.add_development_dependency 'minitest', '~> 5.9'
|
23
|
+
s.add_development_dependency 'minitest-rg', '~> 5.2'
|
24
|
+
s.add_development_dependency 'rake', '~> 11.2'
|
25
|
+
s.add_development_dependency 'vcr', '~> 3.0'
|
26
|
+
s.add_development_dependency 'webmock', '~> 2.1'
|
27
|
+
s.add_development_dependency 'simplecov', '~> 0.12'
|
28
|
+
s.add_development_dependency 'flog', '~> 4.4'
|
29
|
+
s.add_development_dependency 'flay', '~> 2.8'
|
30
|
+
s.add_development_dependency 'rubocop', '~> 0.44'
|
31
|
+
s.homepage = 'https://github.com/BlueStarAshes/share_learning'
|
32
|
+
s.license = 'MIT'
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'coursera_api_spec_helper'
|
2
|
+
|
3
|
+
describe 'Coursera API specifications' do
|
4
|
+
VCR.configure do |c|
|
5
|
+
c.cassette_library_dir = CASSETTES_FOLDER
|
6
|
+
c.hook_into :webmock
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
VCR.eject_cassette
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should be able to retrieve how many the courses are on the
|
18
|
+
Coursera catlog' do
|
19
|
+
coursera_courses = Coursera::CourseraCourses.find
|
20
|
+
coursera_courses.total_course_num.must_be :>=, 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should be able to retrieve correct number of all courses' do
|
24
|
+
coursera_courses = Coursera::CourseraCourses.find
|
25
|
+
coursera_courses.courses.size.must_equal coursera_courses.total_course_num
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be able to search related courses with the given keyword' do
|
29
|
+
keyword = 'machine learning'
|
30
|
+
|
31
|
+
matched_courses =
|
32
|
+
Coursera::CourseraCourses.find.search_courses(:all, keyword)
|
33
|
+
matched_courses.count.must_be :>, 0
|
34
|
+
|
35
|
+
matched_courses_by_title =
|
36
|
+
Coursera::CourseraCourses.find.search_courses(:course_name, keyword)
|
37
|
+
matched_courses_by_title.count.must_be :>, 0
|
38
|
+
|
39
|
+
matched_courses_by_description =
|
40
|
+
Coursera::CourseraCourses.find.search_courses(:description, keyword)
|
41
|
+
matched_courses_by_description.count.must_be :>, 0
|
42
|
+
|
43
|
+
matched_courses.count.must_be :>=, matched_courses_by_title.count
|
44
|
+
matched_courses.count.must_be :>=, matched_courses_by_description.count
|
45
|
+
sum = matched_courses_by_title.count + matched_courses_by_description.count
|
46
|
+
matched_courses.count.must_be :<=, sum
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/rg'
|
6
|
+
require 'vcr'
|
7
|
+
require 'webmock'
|
8
|
+
|
9
|
+
require_relative '../lib/share_learning/coursera_courses.rb'
|
10
|
+
|
11
|
+
FIXTURE_FOLDER = 'spec/fixtures'.freeze
|
12
|
+
CASSETTES_FOLDER = "#{FIXTURE_FOLDER}/cassettes".freeze
|
13
|
+
CASSETTE_FILE = 'coursera_api'.freeze
|
@@ -0,0 +1,17 @@
|
|
1
|
+
{
|
2
|
+
"get_course_by_id":
|
3
|
+
{
|
4
|
+
"title": "Intro to Computer Science",
|
5
|
+
"intro": "In this introduction to computer programming course, you’ll learn and practice key computer science concepts by building your own versions of popular web applications. You’ll learn Python, a powerful, easy-to-learn, and widely used programming language, and you’ll explore computer science basics, as you build your own search engine and social network.",
|
6
|
+
"link": "https://www.udacity.com/course/intro-to-computer-science--cs101?utm_medium=referral&utm_campaign=api",
|
7
|
+
"image": "https://lh5.ggpht.com/ITepKi-2pz4Q6lrLfv6QDNViEGIfxyupzgQwx1YgS4L8m3MFITBKWDpaZb_VoAP-zV3bEEoIbFY7mauj8HM=s0#w=1724&h=1060"
|
8
|
+
}
|
9
|
+
,
|
10
|
+
"get_course_by_title":
|
11
|
+
{
|
12
|
+
"title": "Intro to Java Programming",
|
13
|
+
"intro": "In this introductory course, you'll learn and practice essential computer science concepts using the Java programming language. You'll learn about Object Oriented Programming, a technique that allows you to use code written by other programmers in your own programs. You'll put your new Java programming skills to the test by solving real-world problems faced by software engineers.",
|
14
|
+
"link": "https://www.udacity.com/course/intro-to-java-programming--cs046?utm_medium=referral&utm_campaign=api",
|
15
|
+
"image": "https://lh4.ggpht.com/9ytiUdz0QYHwuMJFTXcNXZn4FctGW6Zszm7Aj5s7mXHHXsapIKYPL08vPWeghAjF2QmuhPiYCU2Q3kNeW7w=s0#w=1725&h=1060"
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'udacity_api_spec_helper.rb'
|
2
|
+
|
3
|
+
describe 'Udacity course' do
|
4
|
+
VCR.configure do |c|
|
5
|
+
c.cassette_library_dir = CASSETTES_FOLDER
|
6
|
+
c.hook_into :webmock
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
|
11
|
+
|
12
|
+
@udacity_api = Udacity::UdacityAPI
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
VCR.eject_cassette
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should get json response successfully' do
|
20
|
+
udacity = Udacity::UdacityAPI.acquire_json_response
|
21
|
+
udacity.nil?.must_equal false
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should get all courses successfully' do
|
25
|
+
udacity = Udacity::UdacityCourse.find()
|
26
|
+
all_courses = udacity.acquire_all_courses
|
27
|
+
all_courses.nil?.must_equal false
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should get course by id successfully' do
|
31
|
+
udacity = Udacity::UdacityCourse.find()
|
32
|
+
course = udacity.acquire_course_by_id('cs101')
|
33
|
+
course.must_equal (UDACITY_RESULT[:get_course_by_id])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should get course by title successfully' do
|
37
|
+
udacity = Udacity::UdacityCourse.find()
|
38
|
+
course = udacity.acquire_course_by_title('Intro to Java Programming')
|
39
|
+
course.must_equal (UDACITY_RESULT[:get_course_by_title])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should get course by keywords successfully' do
|
43
|
+
udacity = Udacity::UdacityCourse.find()
|
44
|
+
course = udacity.acquire_courses_by_keywords('Java Programming')
|
45
|
+
course.nil?.must_equal false
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should get the number of course successfully' do
|
49
|
+
udacity = Udacity::UdacityCourse.find()
|
50
|
+
udacity.total_course_num.must_be :>=, 0
|
51
|
+
end
|
52
|
+
|
53
|
+
# it 'should get courses by level successfully' do
|
54
|
+
# udacity = Udacity::UdacityCourse.find()
|
55
|
+
# course = udacity.acquire_courses_by_level('beginner')
|
56
|
+
# course.nil?.must_equal false
|
57
|
+
# end
|
58
|
+
|
59
|
+
# it 'should get courses by tracks successfully' do
|
60
|
+
# udacity = Udacity::UdacityCourse.find()
|
61
|
+
# course = udacity.acquire_courses_by_tracks('Data Science')
|
62
|
+
# course.nil?.must_equal false
|
63
|
+
# end
|
64
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/rg'
|
6
|
+
require 'json'
|
7
|
+
require 'yaml'
|
8
|
+
require 'vcr'
|
9
|
+
require 'webmock'
|
10
|
+
|
11
|
+
require_relative '../lib/share_learning/udacity_api.rb'
|
12
|
+
require_relative '../lib/share_learning/udacity_course.rb'
|
13
|
+
|
14
|
+
FIXTURES_FOLDER = 'spec/fixtures'
|
15
|
+
CASSETTES_FOLDER = "#{FIXTURES_FOLDER}/cassettes"
|
16
|
+
CASSETTE_FILE = 'udacity_api'
|
17
|
+
RESULT_FILE = "#{FIXTURES_FOLDER}/udacity_api_results.json"
|
18
|
+
UDACITY_RESULT = JSON.parse(File.read(RESULT_FILE), :symbolize_names => true)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative 'udacity_exe_spec_helper.rb'
|
2
|
+
|
3
|
+
describe 'Udacity executable files' do
|
4
|
+
VCR.configure do |c|
|
5
|
+
c.cassette_library_dir = CASSETTES_FOLDER
|
6
|
+
c.hook_into :webmock
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
VCR.eject_cassette
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should support help instruction' do
|
18
|
+
result = help
|
19
|
+
result.must_equal (UDACITY_RESULT['help'])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should get all courses successfully' do
|
23
|
+
result = action('all')
|
24
|
+
result.nil?.must_equal false
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should get course by id successfully' do
|
28
|
+
result = action('id', 'cs101')
|
29
|
+
result.must_equal (UDACITY_RESULT['get_course_by_id'])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should get course by title successfully' do
|
33
|
+
result = action('title', 'Intro to Java Programming')
|
34
|
+
result.must_equal (UDACITY_RESULT['get_course_by_title'])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should throw error message when given wrong command' do
|
38
|
+
result = check_command_available('ttt')
|
39
|
+
result.must_equal (UDACITY_RESULT['error_command'])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should throw error message when given wrong feature for id' do
|
43
|
+
result = check_feature_available('ttt')
|
44
|
+
result.must_equal (UDACITY_RESULT['error_feature']['id'])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should throw error message when given wrong feature for title' do
|
48
|
+
result = check_feature_available('ttt')
|
49
|
+
result.must_equal (UDACITY_RESULT['error_feature']['title'])
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/rg'
|
6
|
+
require 'yaml'
|
7
|
+
require 'vcr'
|
8
|
+
require 'webmock'
|
9
|
+
|
10
|
+
require 'udacity'
|
11
|
+
|
12
|
+
FIXTURES_FOLDER = 'spec/fixtures'.freeze
|
13
|
+
CASSETTES_FOLDER = "#{FIXTURES_FOLDER}/cassettes".freeze
|
14
|
+
CASSETTE_FILE = 'udacity_exe'.freeze
|
15
|
+
RESULT_FILE = "#{FIXTURES_FOLDER}/udacity_exe_results.yml".freeze
|
16
|
+
UDACITY_RESULT = YAML.load(File.read(RESULT_FILE))
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'youtube_api_spec_helper.rb'
|
2
|
+
|
3
|
+
describe 'YouTube API' do
|
4
|
+
VCR.configure do |c|
|
5
|
+
c.cassette_library_dir = CASSETTES_FOLDER
|
6
|
+
c.hook_into :webmock
|
7
|
+
|
8
|
+
c.filter_sensitive_data('<API_KEY>') { ENV['YOUTUBE_API_KEY'] }
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
VCR.eject_cassette
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should get playlist by keyword' do
|
20
|
+
courses = YouTube::YouTubePlaylist.find(keyword: 'machine learning')
|
21
|
+
courses.results.count.must_be :>=, 0
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/rg'
|
6
|
+
require 'yaml'
|
7
|
+
require 'json'
|
8
|
+
require 'vcr'
|
9
|
+
require 'webmock'
|
10
|
+
|
11
|
+
require_relative '../lib/share_learning'
|
12
|
+
|
13
|
+
FIXTURES_FOLDER = 'fixtures'.freeze
|
14
|
+
CASSETTES_FOLDER = "#{FIXTURES_FOLDER}/cassettes".freeze
|
15
|
+
CASSETTE_FILE = 'youtube_api'.freeze
|
16
|
+
|
17
|
+
# read credentials from a Yaml file into environment variables
|
18
|
+
if File.file?('../config/credentials.yml')
|
19
|
+
credentials = YAML.load(File.read('../config/credentials.yml'))
|
20
|
+
ENV['YOUTUBE_API_KEY'] = credentials[:api_key]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: share_learning
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ashleycheng, blureze, meegoStar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-rg
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '11.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '11.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: vcr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.12'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.12'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: flog
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.4'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.4'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: flay
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2.8'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2.8'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.44'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.44'
|
153
|
+
description: Extracts course's titles, descriptions, images and links from Coursera
|
154
|
+
and Udacity. And searches relating resource on Youtube.
|
155
|
+
email:
|
156
|
+
- ashley830204@gmail.com
|
157
|
+
- blureze@gmail.com
|
158
|
+
- andy19933@gmail.com
|
159
|
+
executables:
|
160
|
+
- udacity
|
161
|
+
- coursera
|
162
|
+
- youtube
|
163
|
+
extensions: []
|
164
|
+
extra_rdoc_files: []
|
165
|
+
files:
|
166
|
+
- ".gitignore"
|
167
|
+
- ".travis.yml"
|
168
|
+
- Gemfile
|
169
|
+
- LICENSE
|
170
|
+
- README.md
|
171
|
+
- Rakefile
|
172
|
+
- bin/coursera
|
173
|
+
- bin/udacity
|
174
|
+
- bin/youtube
|
175
|
+
- lib/share_learning.rb
|
176
|
+
- lib/share_learning/coursera_api.rb
|
177
|
+
- lib/share_learning/coursera_courses.rb
|
178
|
+
- lib/share_learning/share_learning_version.rb
|
179
|
+
- lib/share_learning/udacity_api.rb
|
180
|
+
- lib/share_learning/udacity_course.rb
|
181
|
+
- lib/share_learning/youtube_api.rb
|
182
|
+
- lib/share_learning/youtube_playlist.rb
|
183
|
+
- share_learning.gemspec
|
184
|
+
- spec/coursera_api_spec.rb
|
185
|
+
- spec/coursera_api_spec_helper.rb
|
186
|
+
- spec/data/course_test_data.json
|
187
|
+
- spec/udacity_api_spec.rb
|
188
|
+
- spec/udacity_api_spec_helper.rb
|
189
|
+
- spec/udacity_exe_spec.rb
|
190
|
+
- spec/udacity_exe_spec_helper.rb
|
191
|
+
- spec/youtube_api_spec.rb
|
192
|
+
- spec/youtube_api_spec_helper.rb
|
193
|
+
homepage: https://github.com/BlueStarAshes/share_learning
|
194
|
+
licenses:
|
195
|
+
- MIT
|
196
|
+
metadata: {}
|
197
|
+
post_install_message:
|
198
|
+
rdoc_options: []
|
199
|
+
require_paths:
|
200
|
+
- lib
|
201
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
202
|
+
requirements:
|
203
|
+
- - ">="
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
207
|
+
requirements:
|
208
|
+
- - ">="
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: '0'
|
211
|
+
requirements: []
|
212
|
+
rubyforge_project:
|
213
|
+
rubygems_version: 2.6.8
|
214
|
+
signing_key:
|
215
|
+
specification_version: 4
|
216
|
+
summary: Gets learning resource from Coursera, Udacity, and Youtube.
|
217
|
+
test_files:
|
218
|
+
- spec/coursera_api_spec.rb
|
219
|
+
- spec/coursera_api_spec_helper.rb
|
220
|
+
- spec/data/course_test_data.json
|
221
|
+
- spec/udacity_api_spec.rb
|
222
|
+
- spec/udacity_api_spec_helper.rb
|
223
|
+
- spec/udacity_exe_spec.rb
|
224
|
+
- spec/udacity_exe_spec_helper.rb
|
225
|
+
- spec/youtube_api_spec.rb
|
226
|
+
- spec/youtube_api_spec_helper.rb
|