share_learning 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/Share_learning.svg)](https://badge.fury.io/rb/Share_learning)
|
4
|
+
[![Build Status](https://travis-ci.org/BlueStarAshes/Share_learning.svg?branch=master)](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
|