franklin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7b7efde29c026dbc795cbe81417a627ef35ca556
4
+ data.tar.gz: 71cc18d43c0e669942f9133e2326ee66b3060279
5
+ SHA512:
6
+ metadata.gz: 46bd872e98f7190a6e3cf69bffad1acbd49e816a2ea58e1f8e51d681b850030aa92c4702f26c59fb90f87098208f8f1b504437e7f07d243a463b3d8656501bb2
7
+ data.tar.gz: 2788cc2a039330ea05d811028266d67754d3dc9d306f1b0570a96c9d003ce38781d14c0052462629c6a89152d0603c1ebdf5d60d85fc4330e4b25824e3802fa9
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in franklin.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ylan Segal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Franklin
2
+
3
+ Franklin is a command line utility, written in ruby, that allows searching public libraries powered by Overdrive. It supports multiple library searching for those that have accounts in more than one library (city, county, state, etc.)
4
+
5
+ ## Installation
6
+
7
+ Install gem:
8
+
9
+ $ gem install franklin
10
+
11
+ ## Configuration
12
+
13
+ Franklin needs to be configured with information about the Overdrive libraries it will search. It expects a file in YAML format to exist in your home directory called `.franklin`. The contents of the file should look like:
14
+
15
+ ``` yml
16
+ ---
17
+ :libraries:
18
+ - :name: San Francisco Public Library
19
+ :url: http://sfpl.lib.overdrive.com
20
+ - :name: San Diego Public Library
21
+ :url: http://sdpl.lib.overdrive.com
22
+ ```
23
+
24
+ There needs to be a minimum of one library, but there is no maximum. The `name` can be anything and will be included when the search results are presented. The `url` should point to the domain of the public library. It can be obtained by visiting Overdrive's site for each library, copying the url and striping everything after the domain name.
25
+
26
+ ## Usage
27
+
28
+ Once the library has been installed and configured, the `franklin` executable will be available. It can be called from the command line with the list of terms to be searched for:
29
+
30
+ ```
31
+ $ franklin chamber of secrets
32
+ Searched for: chamber of secrets
33
+ ======================================================
34
+ Harry Potter and the Chamber of Secrets
35
+ By J.K. Rowling
36
+ Format: Audiobook
37
+ Availability:
38
+ 2.8 people/copy @ San Francisco Public Library
39
+ 15.5 people/copy @ San Diego Public Library
40
+ 1.3 people/copy @ San Diego County Library
41
+ 5.4 people/copy @ Los Angeles County Library
42
+ ======================================================
43
+ Harry Potter and the Chamber of Secrets
44
+ By J.K. Rowling
45
+ Format: eBook
46
+ Availability:
47
+ 0.9 people/copy @ San Francisco Public Library
48
+ Available @ San Diego Public Library
49
+ 0.6 people/copy @ San Diego County Library
50
+ 0.0 people/copy @ Los Angeles County Library
51
+ ======================================================
52
+ Harry Potter
53
+ By Chris Peacock
54
+ Format: eBook
55
+ Availability:
56
+ 2.5 people/copy @ San Francisco Public Library
57
+ 1.0 people/copy @ San Diego Public Library
58
+ ======================================================
59
+ Room at Heron's Inn
60
+ By Ginger Chambers
61
+ Format: eBook
62
+ Availability:
63
+ Available @ San Francisco Public Library
64
+ ======================================================
65
+ The Secret of the Fiery Chamber
66
+ By Carolyn Keene
67
+ Format: eBook
68
+ Availability:
69
+ Available @ San Diego County Library
70
+ ```
71
+
72
+ ## Development
73
+
74
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
75
+
76
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
77
+
78
+ ## Contributing
79
+
80
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ylansegal/franklin.
81
+
82
+ ## License
83
+
84
+ This software is provided under the terms of the MIT license. See LICENSE.txt for more information
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "franklin"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/exe/franklin ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "franklin"
4
+
5
+ begin
6
+ Franklin.run(nil, ARGV.join(" "), STDOUT)
7
+ rescue ArgumentError => ex
8
+ puts <<-ERROR.gsub(/^ /, "")
9
+ Oops! #{ex.message}"
10
+ Usage: franklin search_term [other_term..]"
11
+ ERROR
12
+ rescue Errno::ENOENT
13
+ puts <<-ERROR.gsub(/^ /, "")
14
+ Oops! It looks like you have not configured franklin
15
+ Please add a configuration file in $HOME/.franklin that looks like this:
16
+ ---
17
+ :libraries:
18
+ - :name: San Francisco Public Library
19
+ :url: http://sfpl.lib.overdrive.com
20
+ - :name: San Diego Public Library
21
+ :url: http://sdpl.lib.overdrive.com
22
+ ERROR
23
+ end
data/franklin.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "franklin/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "franklin"
8
+ spec.version = Franklin::VERSION
9
+ spec.license = "MIT"
10
+ spec.authors = ["Ylan Segal"]
11
+ spec.email = ["ylan@segal-family.com"]
12
+
13
+ spec.summary = "CLI utility for searching Overdrive-powered public libraries"
14
+ spec.homepage = "https://github.com/ylansegal/franklin"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "mechanize", "~> 2.7"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.10"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.4"
26
+ spec.add_development_dependency "pry", "~> 0.10"
27
+ spec.add_development_dependency "vcr", "~> 3.0"
28
+ spec.add_development_dependency "webmock", "~> 1.22"
29
+ end
@@ -0,0 +1,3 @@
1
+ module Franklin
2
+ Availability = Struct.new(:library, :total_copies, :available_copies, :wait_list_size)
3
+ end
@@ -0,0 +1,25 @@
1
+ module Franklin
2
+ class AvailabilityDescription < SimpleDelegator
3
+ def to_s
4
+ "#{copies_information} @ #{library.name}"
5
+ end
6
+
7
+ private
8
+
9
+ def copies_information
10
+ available? ? "Available" : "#{copies_per_person} people/copy"
11
+ end
12
+
13
+ def available?
14
+ available_copies > 0
15
+ end
16
+
17
+ def copies_per_person
18
+ wait_list_size? ? (wait_list_size.to_f / total_copies.to_f).round(1) : "Unknown"
19
+ end
20
+
21
+ def wait_list_size?
22
+ wait_list_size > 0
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require "set"
2
+
3
+ module Franklin
4
+ class Collate
5
+ def perform(search_results)
6
+ items = Set.new(search_results.flat_map(&:keys))
7
+ items.each_with_object({}) { |item, collation|
8
+ collation[item] = search_results.map { |result| result[item] }.compact
9
+ }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "library"
2
+ require "yaml"
3
+
4
+ module Franklin
5
+ class Config
6
+ DEFAULT_CONFIG_FILE = File.join(Dir.home, ".franklin").freeze
7
+ attr_reader :libraries
8
+
9
+ def initialize(data)
10
+ @libraries = data.fetch(:libraries).map { |library|
11
+ Library.new(library.fetch(:name), library.fetch(:url))
12
+ }
13
+ end
14
+
15
+ class << self
16
+ def from_yaml(yaml)
17
+ new(YAML.load(yaml))
18
+ end
19
+
20
+ def load_from_file(file_path = nil)
21
+ from_yaml(File.read(file_path || DEFAULT_CONFIG_FILE))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ require "erb"
2
+ require "franklin/availability_description"
3
+
4
+ module Franklin
5
+ class ConsoleReport
6
+ def initialize(search_terms, collated_results)
7
+ @search_terms = search_terms
8
+ @collated_results = collated_results
9
+ end
10
+
11
+ def print_to_out(io)
12
+ io.tap { |out|
13
+ out.puts template.result(binding)
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :search_terms, :collated_results
20
+
21
+ def template
22
+ @template ||= ERB.new(template_source_file, safe_level, trim_value)
23
+ end
24
+
25
+ def template_source_file
26
+ File.read(File.join(__dir__, "templates", "console_report.erb"))
27
+ end
28
+
29
+ def safe_level
30
+ nil
31
+ end
32
+
33
+ def trim_value
34
+ "-"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Franklin
2
+ Item = Struct.new(:id, :title, :author, :format)
3
+ end
@@ -0,0 +1,3 @@
1
+ module Franklin
2
+ Library = Struct.new(:name, :url)
3
+ end
@@ -0,0 +1,58 @@
1
+ require "franklin/item"
2
+ require "franklin/availability"
3
+ require "mechanize"
4
+
5
+ module Franklin
6
+ class Search
7
+ def initialize(library)
8
+ @library = library
9
+ end
10
+
11
+ def perform(search_term)
12
+ results = search_library(search_term)
13
+ parse_results(results)
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :library
19
+
20
+ def search_library(search_term)
21
+ form = prepare_form(search_term)
22
+ form.submit
23
+ end
24
+
25
+ def prepare_form(search_term)
26
+ ::Mechanize.new.get(library.url).forms.first.tap { |form|
27
+ form.FullTextCriteria = search_term
28
+ }
29
+ end
30
+
31
+ def parse_results(results)
32
+ results.search("div.containAll").each_with_object({}) { |container, result|
33
+ result[parse_item(container)] = parse_availability(container)
34
+ }
35
+ end
36
+
37
+ def parse_item(container)
38
+ item_info = container.css("a.share-links").first.attributes
39
+ id = item_info["data-sharecrid"].value
40
+ title = item_info["data-sharetitle"].value
41
+ author = item_info["data-sharecreator"].value
42
+
43
+ format = container.css("span.tcc-icon-span").first.attributes["data-iconformat"].value
44
+
45
+ Item.new(id, title, author, format)
46
+ end
47
+
48
+ def parse_availability(container)
49
+ copies_info = container.css("div.img-and-info-contain.title-data").first.attributes
50
+
51
+ total_copies = copies_info["data-copiestotal"].value.to_i
52
+ available_copies = copies_info["data-copiesavail"].value.to_i
53
+ wait_list_size = copies_info["data-numwaiting"].value.to_i
54
+
55
+ Availability.new(library, total_copies, available_copies, wait_list_size)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ Searched for: <%= search_terms %>
2
+ <% collated_results.each do |item, availabilities| -%>
3
+ ======================================================
4
+ <%= item.title %>
5
+ By <%= item.author %>
6
+ Format: <%= item.format %>
7
+ Availability:
8
+ <% availabilities.each do |availability| -%>
9
+ <%= AvailabilityDescription.new(availability) %>
10
+ <% end -%>
11
+ <% end -%>
@@ -0,0 +1,19 @@
1
+ require "franklin/search"
2
+ require "franklin/collate"
3
+
4
+ module Franklin
5
+ class ThreadedSearch
6
+ attr_accessor :searchers
7
+
8
+ def initialize(libraries)
9
+ @searchers = libraries.map { |library| Search.new(library) }
10
+ end
11
+
12
+ def perform(search_terms)
13
+ fail ArgumentError, "Please provide at least one search_term" if search_terms.empty?
14
+ threads = searchers.map { |search| Thread.new { search.perform(search_terms) } }
15
+ threads.join
16
+ Collate.new.perform(threads.map(&:value))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Franklin
2
+ VERSION = "0.1.0"
3
+ end
data/lib/franklin.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "franklin/version"
2
+ require "franklin/config"
3
+ require "franklin/threaded_search"
4
+ require "franklin/console_report"
5
+
6
+ module Franklin
7
+ def run(config_path, search_terms, out)
8
+ config = Config.load_from_file(config_path)
9
+ results = ThreadedSearch.new(config.libraries).perform(search_terms)
10
+ ConsoleReport.new(search_terms, results).print_to_out(out)
11
+ end
12
+
13
+ module_function :run
14
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: franklin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ylan Segal
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-25 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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.22'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.22'
111
+ description:
112
+ email:
113
+ - ylan@segal-family.com
114
+ executables:
115
+ - franklin
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/setup
127
+ - exe/franklin
128
+ - franklin.gemspec
129
+ - lib/franklin.rb
130
+ - lib/franklin/availability.rb
131
+ - lib/franklin/availability_description.rb
132
+ - lib/franklin/collate.rb
133
+ - lib/franklin/config.rb
134
+ - lib/franklin/console_report.rb
135
+ - lib/franklin/item.rb
136
+ - lib/franklin/library.rb
137
+ - lib/franklin/search.rb
138
+ - lib/franklin/templates/console_report.erb
139
+ - lib/franklin/threaded_search.rb
140
+ - lib/franklin/version.rb
141
+ homepage: https://github.com/ylansegal/franklin
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.4.5.1
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: CLI utility for searching Overdrive-powered public libraries
165
+ test_files: []