ruby-tapas-downloader 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5cca06220dad1b22ec9ac237334ff1f860674e68
4
+ data.tar.gz: a9392d4d857a3fcd85257c9cdcb5bae7532e9f0a
5
+ SHA512:
6
+ metadata.gz: 00ebe03a4463feb0b9168118b0cbdc2ef66cafb3ccc14f6316a3fe17463fa40368b9d585019644e4aaf0fa1d5409149e722e61920315babff495164d2772caf8
7
+ data.tar.gz: 5d10b7bcb522279593ccf512b002b614d3aea17eb78a64d62cd2d6fe49c6f22da9ecbbac3e763baa4fcf5369edfd266d84378e0819564f7daf7c0809286ae28e
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2013 Leandro Facchinetti <leafac@gmail.com>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
@@ -0,0 +1,46 @@
1
+ Ruby Tapas Downloader
2
+ =====================
3
+
4
+ [Ruby Tapas][ruby-tapas] is a great series of screencasts by
5
+ [Avdi Grimm][avdi-grimm]. You should totally check it out if you don't already
6
+ know it!
7
+
8
+ There's only one problem with Ruby Tapas, in my opinion: there's no way to
9
+ watch it via straming. One can only download episodes, which soon becomes
10
+ tedious.
11
+
12
+ Enters Ruby Tapas Downloader! It downloads all episodes and attachments,
13
+ organizes them for later use and keeps an easy to use index of episodes.
14
+
15
+ Installation
16
+ ------------
17
+
18
+ ```bash
19
+ $ gem install ruby-tapas-downloader
20
+ ```
21
+
22
+ Usage
23
+ -----
24
+
25
+ ```bash
26
+ $ ruby-tapas-downloader <email> <password> <download-path>
27
+ ```
28
+
29
+ Warning
30
+ -------
31
+
32
+ Except for a few episodes, Ruby Tapas is a paid screencast. Therefore, assert
33
+ that you don't redistribute the downloaded material. Ruby Tapas Downloader is
34
+ only an utility tool and doesn't substitute the subscription.
35
+
36
+ You should do no evil!
37
+
38
+ Thanks
39
+ ------
40
+
41
+ Thanks Avdi Grimm for putting all this great material out the door!
42
+
43
+ I learn a lot from you.
44
+
45
+ [ruby-tapas]: http://www.rubytapas.com/
46
+ [avdi-grimm]: http://devblog.avdi.org/
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ARGV.count != 3
4
+ abort <<-USAGE
5
+ Usage:
6
+ $ ruby-tapas-downloader <email> <password> <download-path>
7
+ USAGE
8
+ end
9
+
10
+ require_relative '../lib/ruby_tapas_downloader/cli'
11
+
12
+ RubyTapasDownloader::CLI.new(*ARGV).download
@@ -0,0 +1,3 @@
1
+ ---
2
+ :login: https://rubytapas.dpdcart.com/subscriber/content
3
+ :feed: https://rubytapas.dpdcart.com/feed
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'rss'
4
+ require 'rexml/document'
5
+ require 'set'
6
+ require 'cgi'
7
+ require 'logger'
8
+
9
+ module RubyTapasDownloader
10
+ class << self
11
+ # The Logger for RubyTapasDownloader.
12
+ attr_writer :logger
13
+
14
+ # @return [Logger] the Logger for RubyTapasDownloader
15
+ def logger
16
+ @logger ||= Logger.new STDOUT
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'bundler/setup'
22
+ require 'mechanize'
23
+
24
+ require_relative 'ruby_tapas_downloader/downloadables'
25
+
26
+ require_relative 'ruby_tapas_downloader/extractors'
27
+
28
+ require_relative 'ruby_tapas_downloader/config'
29
+ require_relative 'ruby_tapas_downloader/login'
30
+ require_relative 'ruby_tapas_downloader/feed_fetcher'
@@ -0,0 +1,45 @@
1
+ require_relative '../ruby-tapas-downloader'
2
+
3
+ # The Command Line Interface for Ruby Tapas Downloader.
4
+ class RubyTapasDownloader::CLI
5
+ # @param email [String] the e-mail for the user.
6
+ # @param password [String] the password for the user.
7
+ # @param download_path [String] the path in which the download is performed.
8
+ def initialize email, password, download_path
9
+ @email = email
10
+ @password = password
11
+ @download_path = download_path
12
+ end
13
+
14
+ # Perform complete download procedure.
15
+ def download
16
+ create_agent
17
+ login
18
+ fetch_feed
19
+ create_catalog
20
+ download_catalog
21
+ end
22
+
23
+ private
24
+
25
+ def create_agent
26
+ @agent = Mechanize.new
27
+ end
28
+
29
+ def login
30
+ RubyTapasDownloader::Login.new(@agent, @email, @password).login
31
+ end
32
+
33
+ def fetch_feed
34
+ @feed = RubyTapasDownloader::FeedFetcher.new(@agent, @email, @password)
35
+ .fetch
36
+ end
37
+
38
+ def create_catalog
39
+ @catalog = RubyTapasDownloader::Extractors::Catalog.new.extract @feed
40
+ end
41
+
42
+ def download_catalog
43
+ @catalog.download @download_path, @agent
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ # Retrieve configurations.
2
+ class RubyTapasDownloader::Config
3
+ # Retrieve urls stored in `urls.yml`.
4
+ # @return [Hash] the urls stored in `urls.yml`.
5
+ def self.urls
6
+ @urls ||= YAML.load File.read 'config/urls.yml'
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # The contract for Downloadables.
2
+ class RubyTapasDownloader::Downloadable
3
+ # Should be implemented by children.
4
+ def download
5
+ fail NotImplementedError
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # Namespace module for Downloadable assets.
2
+ module RubyTapasDownloader::Downloadables
3
+ end
4
+
5
+ require_relative 'downloadable'
6
+
7
+ require_relative 'downloadables/file'
8
+ require_relative 'downloadables/episode'
9
+ require_relative 'downloadables/catalog'
@@ -0,0 +1,34 @@
1
+ # Catalog is the set of all Ruby Tapas Episodes.
2
+ class RubyTapasDownloader::Downloadables::Catalog <
3
+ RubyTapasDownloader::Downloadable
4
+
5
+ # @return [Set<RubyTapasDownloader::Downloadables::Episode>] the Episodes.
6
+ attr_reader :episodes
7
+
8
+ def initialize episodes
9
+ @episodes = episodes
10
+ end
11
+
12
+ # Download the Catalog.
13
+ #
14
+ # @param basepath [String] the path to place download.
15
+ # @param agent [Mechanize] the Mechanize agent.
16
+ def download basepath, agent
17
+ RubyTapasDownloader.logger.info 'Starting download of catalog in ' \
18
+ "`#{ basepath }'..."
19
+ FileUtils.mkdir_p basepath
20
+ episodes.each { |episode| episode.download basepath, agent }
21
+ end
22
+
23
+ def == other
24
+ episodes == other.episodes
25
+ end
26
+
27
+ def eql? other
28
+ episodes.eql? other.episodes
29
+ end
30
+
31
+ def hash
32
+ episodes.hash
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ # An Ruby Tapas Episode.
2
+ class RubyTapasDownloader::Downloadables::Episode <
3
+ RubyTapasDownloader::Downloadable
4
+
5
+ # @return [String] the title of the Episode.
6
+ attr_reader :title
7
+
8
+ # @return [String] the link to the Episode.
9
+ attr_reader :link
10
+
11
+ # @return [Set<RubyTapasDownloader::Downloadables::File>] the Set of Files
12
+ # for that episode.
13
+ attr_reader :files
14
+
15
+ def initialize title, link, files
16
+ @title = title
17
+ @link = link
18
+ @files = files
19
+ end
20
+
21
+ # Clean title to be used in path names.
22
+ #
23
+ # @return [String] the sanitized title.
24
+ def sanitized_title
25
+ @sanitized_title ||= title.downcase.gsub(/[^\w<>#?!$]+/, '-')
26
+ end
27
+
28
+
29
+ # Download the Episode.
30
+ #
31
+ # @param (see: RubyTapasDownloader::Downloadables::Catalog#download)
32
+ def download basepath, agent
33
+ episode_path = File.join basepath, sanitized_title
34
+ RubyTapasDownloader.logger.info 'Starting download of episode ' \
35
+ "`#{ title }' in `#{ episode_path }'..."
36
+ FileUtils.mkdir_p episode_path
37
+ files.each { |file| file.download episode_path, agent }
38
+ end
39
+
40
+ def == other
41
+ title == other.title && link == other.link && files == other.files
42
+ end
43
+
44
+ def eql? other
45
+ title.eql?(other.title) && link.eql?(other.link) && files.eql?(other.files)
46
+ end
47
+
48
+ def hash
49
+ title.hash + link.hash + files.hash
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ # The File resource of an Episode.
2
+ class RubyTapasDownloader::Downloadables::File <
3
+ RubyTapasDownloader::Downloadable
4
+
5
+ # @return [String] the name of the File.
6
+ attr_reader :name
7
+
8
+ # @return [String] the link to download the File.
9
+ attr_reader :link
10
+
11
+ def initialize name, link
12
+ @name = name
13
+ @link = link
14
+ end
15
+
16
+ # Download the File.
17
+ #
18
+ # @param (see: RubyTapasDownloader::Downloadables::Catalog#download)
19
+ def download basepath, agent
20
+ FileUtils.mkdir_p basepath
21
+
22
+ file_path = File.join(basepath, name)
23
+ RubyTapasDownloader.logger.info "Starting download of file `#{ name }' " \
24
+ "in `#{ file_path }'..."
25
+ agent.download link, file_path unless File.exists? file_path
26
+ end
27
+
28
+ def == other
29
+ name == other.name && link == other.link
30
+ end
31
+
32
+ def eql? other
33
+ name.eql?(other.name) && link.eql?(other.link)
34
+ end
35
+
36
+ def hash
37
+ name.hash + link.hash
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ # The contract for Extractors.
2
+ class RubyTapasDownloader::Extractor
3
+ # Should be implemented by children.
4
+ def extract
5
+ fail NotImplementedError
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # Namespace module for Extractors.
2
+ module RubyTapasDownloader::Extractors
3
+ end
4
+
5
+ require_relative 'extractor'
6
+
7
+ require_relative 'extractors/files'
8
+ require_relative 'extractors/episode'
9
+ require_relative 'extractors/catalog'
@@ -0,0 +1,22 @@
1
+ # Extract an Catalog from an Feed.
2
+ class RubyTapasDownloader::Extractors::Catalog < RubyTapasDownloader::Extractor
3
+ # @param episode_extractor [RubyTapasDownloader::Extractors::Episode] the
4
+ # Episode Extractor.
5
+ def initialize episode_extractor =
6
+ RubyTapasDownloader::Extractors::Episode.new
7
+ @episode_extractor = episode_extractor
8
+ end
9
+
10
+ # @param feed [RSS::Rss] the feed extracted with `RSS::Parser.parse`.
11
+ # @return [RubyTapasDownloader::Downloadables::Catalog] the Catalog extracted
12
+ # from feed.
13
+ def extract feed
14
+ episodes = Set.new
15
+
16
+ feed.items.each { |item|
17
+ episodes << @episode_extractor.extract(item)
18
+ }
19
+
20
+ RubyTapasDownloader::Downloadables::Catalog.new episodes
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # Extract an Episode from an Feed Item.
2
+ class RubyTapasDownloader::Extractors::Episode < RubyTapasDownloader::Extractor
3
+ # @param files_extractor [RubyTapasDownloader::Extractors::Files] the
4
+ # Files Extractor.
5
+ def initialize files_extractor = RubyTapasDownloader::Extractors::Files.new
6
+ @files_extractor = files_extractor
7
+ end
8
+
9
+ # @param item [RSS::Rss::Channel::Item] the feed item extracted with
10
+ # `feed.items[i]`.
11
+ # @return [RubyTapasDownloader::Downloadables::Episode] the Episode extracted
12
+ # from feed item.
13
+ def extract item
14
+ title = CGI.unescapeHTML item.title
15
+ link = item.link
16
+ files = @files_extractor.extract item.description
17
+
18
+ RubyTapasDownloader::Downloadables::Episode.new title, link, files
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # Extract a Set of Files from an Feed Item Description.
2
+ class RubyTapasDownloader::Extractors::Files < RubyTapasDownloader::Extractor
3
+ # @param item_description [String] the feed item description extracted with
4
+ # `feed.items[i].description`.
5
+ # @return [Set<RubyTapasDownloader::Downloadables::File>] the Set of Files
6
+ # extracted from feed item description.
7
+ def extract item_description
8
+ files = Set.new
9
+ document = REXML::Document.new item_description
10
+ document.elements.each("/div[@class='blog-entry']/ul/li/a") { |element|
11
+ name = element.text
12
+ link = element.attribute('href').to_s
13
+ files << RubyTapasDownloader::Downloadables::File.new(name, link)
14
+ }
15
+ files
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # Fetches feed from Ruby Tapas.
2
+ class RubyTapasDownloader::FeedFetcher
3
+ # @return [Mechanize] the Mechanize agent.
4
+ attr_reader :agent
5
+
6
+ # @return [String] the e-mail for the user.
7
+ attr_reader :email
8
+
9
+ # @return [String] the password for the user.
10
+ attr_reader :password
11
+
12
+ def initialize agent, email, password
13
+ @agent = agent
14
+ @email = email
15
+ @password = password
16
+ end
17
+
18
+ # Fetch feed from Ruby Tapas.
19
+ #
20
+ # @return [RSS::Rss] the feed for Ruby Tapas.
21
+ def fetch
22
+ RubyTapasDownloader.logger.info 'Fetching episodes feed...'
23
+
24
+ feed_url = RubyTapasDownloader::Config.urls[:feed]
25
+
26
+ agent.add_auth feed_url, email, password
27
+ RSS::Parser.parse agent.get(feed_url).body
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ # Perform Login in Ruby Tapas.
2
+ #
3
+ # Login must be performed before any attempt to download files.
4
+ class RubyTapasDownloader::Login
5
+ # @return [Mechanize] the Mechanize agent.
6
+ attr_reader :agent
7
+
8
+ # @return [String] the e-mail for the user.
9
+ attr_reader :email
10
+
11
+ # @return [String] the password for the user.
12
+ attr_reader :password
13
+
14
+ def initialize agent, email, password
15
+ @agent = agent
16
+ @email = email
17
+ @password = password
18
+ end
19
+
20
+ # Perform login.
21
+ def login
22
+ RubyTapasDownloader.logger.info 'Logging in...'
23
+ request_login_page
24
+ fill_login_form
25
+ submit_login_form
26
+ end
27
+
28
+ private
29
+
30
+ def request_login_page
31
+ @page = agent.get RubyTapasDownloader::Config.urls.fetch(:login)
32
+ end
33
+
34
+ def fill_login_form
35
+ login_form.username = email
36
+ login_form.password = password
37
+ end
38
+
39
+ def submit_login_form
40
+ login_form.submit
41
+ end
42
+
43
+ def login_form
44
+ @page.forms.first
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module RubyTapasDownloader
2
+ VERSION = '1.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-tapas-downloader
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Facchinetti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-debugger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ description: It downloads all episodes and attachments, organizes them for later use
56
+ and keeps an easy to use index of episodes.
57
+ email: ruby-tapas-downloader@leafac.com
58
+ executables:
59
+ - ruby-tapas-downloader
60
+ extensions: []
61
+ extra_rdoc_files:
62
+ - README.md
63
+ files:
64
+ - License.txt
65
+ - README.md
66
+ - bin/ruby-tapas-downloader
67
+ - config/urls.yml
68
+ - lib/ruby-tapas-downloader.rb
69
+ - lib/ruby_tapas_downloader/cli.rb
70
+ - lib/ruby_tapas_downloader/config.rb
71
+ - lib/ruby_tapas_downloader/downloadable.rb
72
+ - lib/ruby_tapas_downloader/downloadables.rb
73
+ - lib/ruby_tapas_downloader/downloadables/catalog.rb
74
+ - lib/ruby_tapas_downloader/downloadables/episode.rb
75
+ - lib/ruby_tapas_downloader/downloadables/file.rb
76
+ - lib/ruby_tapas_downloader/extractor.rb
77
+ - lib/ruby_tapas_downloader/extractors.rb
78
+ - lib/ruby_tapas_downloader/extractors/catalog.rb
79
+ - lib/ruby_tapas_downloader/extractors/episode.rb
80
+ - lib/ruby_tapas_downloader/extractors/files.rb
81
+ - lib/ruby_tapas_downloader/feed_fetcher.rb
82
+ - lib/ruby_tapas_downloader/login.rb
83
+ - lib/ruby_tapas_downloader/version.rb
84
+ homepage: https://github.com/leafac/ruby-tapas-downloader
85
+ licenses:
86
+ - wtfpl
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.2.2
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Downloader for Avdi Grimm Ruby Tapas
108
+ test_files: []