share_learning 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ config/
2
+ .DS_Store
3
+ spec/.DS_Store
4
+ lib/.DS_Store
5
+ spec/data/.DS_Store
6
+ coverage/
7
+ spec/fixtures/
8
+ Gemfile.lock
9
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ - 2.2
5
+ - 2.1
6
+ branches:
7
+ only:
8
+ - master
9
+ script: bundle exec rake spec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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,3 @@
1
+ module ShareLearning
2
+ VERSION = '0.2.0'.freeze
3
+ 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ files = Dir.glob(File.join(File.dirname(__FILE__), 'share_learning/*.rb'))
4
+ files.each { |lib| require_relative lib }
@@ -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