audio_book_creator 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +3 -3
  6. data/README.md +9 -4
  7. data/audio_book_creator.gemspec +3 -3
  8. data/{bin → exe}/audio_book_creator +3 -0
  9. data/lib/audio_book_creator.rb +4 -2
  10. data/lib/audio_book_creator/binder.rb +2 -1
  11. data/lib/audio_book_creator/book_def.rb +2 -2
  12. data/lib/audio_book_creator/cached_hash.rb +1 -1
  13. data/lib/audio_book_creator/cascading_array.rb +8 -8
  14. data/lib/audio_book_creator/chapter.rb +1 -1
  15. data/lib/audio_book_creator/cli.rb +36 -29
  16. data/lib/audio_book_creator/conductor.rb +5 -3
  17. data/lib/audio_book_creator/defaulter.rb +41 -0
  18. data/lib/audio_book_creator/editor.rb +2 -3
  19. data/lib/audio_book_creator/page_db.rb +14 -8
  20. data/lib/audio_book_creator/page_def.rb +7 -15
  21. data/lib/audio_book_creator/runner.rb +5 -3
  22. data/lib/audio_book_creator/speaker.rb +1 -1
  23. data/lib/audio_book_creator/spider.rb +9 -28
  24. data/lib/audio_book_creator/surfer_def.rb +1 -5
  25. data/lib/audio_book_creator/url_filter.rb +1 -1
  26. data/lib/audio_book_creator/version.rb +1 -1
  27. data/lib/audio_book_creator/web_page.rb +49 -0
  28. data/run_mutant +89 -0
  29. data/spec/audio_book_creator/binder_spec.rb +3 -3
  30. data/spec/audio_book_creator/book_creator_spec.rb +2 -3
  31. data/spec/audio_book_creator/book_def_spec.rb +33 -22
  32. data/spec/audio_book_creator/cached_hash_spec.rb +4 -0
  33. data/spec/audio_book_creator/cli_spec.rb +189 -122
  34. data/spec/audio_book_creator/conductor_spec.rb +17 -6
  35. data/spec/audio_book_creator/defaulter_spec.rb +154 -0
  36. data/spec/audio_book_creator/editor_spec.rb +7 -7
  37. data/spec/audio_book_creator/page_db_spec.rb +73 -11
  38. data/spec/audio_book_creator/page_def_spec.rb +26 -40
  39. data/spec/audio_book_creator/speaker_spec.rb +2 -2
  40. data/spec/audio_book_creator/spider_spec.rb +10 -15
  41. data/spec/audio_book_creator/surfer_def_spec.rb +1 -4
  42. data/spec/audio_book_creator/url_filter_spec.rb +1 -1
  43. data/spec/audio_book_creator/web_page_spec.rb +65 -0
  44. data/spec/audio_book_creator_spec.rb +23 -0
  45. data/spec/spec_helper.rb +15 -12
  46. metadata +14 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f9f56e4fa63b655b47b0d52b825263b0d755d6ca
4
- data.tar.gz: 501684df9d61ef2fc39573f8e445718c3c61d42b
3
+ metadata.gz: 37bad1a54e431efdc1cb4bbca03b71f0a2a64128
4
+ data.tar.gz: 06d824f006b444f47d6bfb6da12b53d602e623b5
5
5
  SHA512:
6
- metadata.gz: c78b612fe641e112006a348b2e9e196579c8edf6788dbf7d4b421c420285b1bf7b866b869ed8051b41a34684552e99f9eb9352f42b6045155c709a51952341ed
7
- data.tar.gz: ccfddad3fe91416a3f463163d441570e27ab3b4b95926f7e3cbd187f637e2f3e732c25c497de12469578610276ae6302bd8ddebca69027db3f5f2beaa5a839ad
6
+ metadata.gz: 7c055f5ed6ea4f208c0ae3f2d92f4c6658cb25ec3f03557b8552f82c2168ece25c6b7f753b0b797afd77358af1a02492141e766735de128292f5bbcd0ff46d93
7
+ data.tar.gz: 5e3b8fb2bc059bb78ec951ceffeb27ebb6b59f61ada16d5da134ef9b2f887c4ea30e37ee5bfe5705d2b675800f945bf1054c30bf3d6e22b26622c54b9db04a9f
data/.gitignore CHANGED
@@ -21,3 +21,5 @@ tmp
21
21
  *.txt
22
22
  *.m4a
23
23
  *.m4b
24
+ # bundler files
25
+ bin
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
+ cache: bundler
2
3
  rvm:
3
- - 2.1.0
4
- - "ruby-head"
4
+ - 2.3.0
5
5
  addons:
6
6
  code_climate:
7
7
  repo_token:
@@ -0,0 +1,24 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased]
6
+ ### Added
7
+ - **option** --no-verbose (turns off --verbose)
8
+ - 100% mutant coverage
9
+
10
+ ### Changed
11
+ - move 'bin' to 'exe' directory
12
+
13
+ ## [0.0.1] - 2015-05-23
14
+ ### Added
15
+ - **option** --title, --body, --link, --chapter to parse html pages
16
+ - **option** --max to limit the number of pages visited
17
+ - **option** --force-html, --force-audio to regenerate
18
+ - **option** --voice, --rate sets speaker and words per minute
19
+ - **option** --base-dir sets directory to hold files
20
+ - **option** -A, --itunes loads book into itunes
21
+
22
+ [unreleased]: https://github.com/kbrock/html-pipeline-abbr/compare/v0.1.0...HEAD
23
+ [0.1.0]: https://github.com/kbrock/html-pipeline-abbr/compare/v0.0.1...v0.1.0
24
+ [0.0.1]: https://github.com/kbrock/html-pipeline-abbr/commits/v0.0.1
data/Gemfile CHANGED
@@ -3,6 +3,6 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in audio_book_creator.gemspec
4
4
  gemspec
5
5
 
6
- # gem "pry"
7
- # gem "mutant"
8
- # gem "mutant-rspec"
6
+ gem "byebug", require: "byebug"
7
+ gem "mutant", "~> 0.8.0"
8
+ gem "mutant-rspec", "~> 0.8.0"
data/README.md CHANGED
@@ -25,6 +25,13 @@
25
25
  This takes html files and creates a chapterized audiobook.
26
26
  It leverages Apple's speak command and audio book binder
27
27
 
28
+ ## Goals
29
+
30
+ 1. create an audio book.
31
+ 2. explore [mutant](https://github.com/mbj/mutant) usage and full test coverage.
32
+ 3. explore [functional core imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell)
33
+ 4. explore local queued components
34
+ 4. explore javascript plugins that leverage local daemons [TODO]
28
35
  ## Installation
29
36
 
30
37
  Download [audio book binder][], link the executable into the path, and install gem. The current app store version of the application does not have the cli available.
@@ -36,14 +43,12 @@ Download [audio book binder][], link the executable into the path, and install g
36
43
 
37
44
  ## Usage
38
45
 
39
- audio_book_creator the_great_book http://bookurl.com/greak_book/
40
-
41
- # **TODO:** provide the command line flags
46
+ audio_book_creator http://bookurl.com/greak_book/ --body '.content p' --link 'a.page' --chapter 'a.chapter'
42
47
 
43
48
  it will spider the files referenced by the url and create the following:
44
49
 
50
+ pages.db
45
51
  the_great_book.mpa
46
- the_great_book/pages.db
47
52
  the_great_book/chapter1.txt
48
53
  the_great_book/chapter1.mpg
49
54
  the_great_book/chapter2.txt
@@ -14,18 +14,18 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "http://github.com/kbrock/audio_book_creator"
15
15
  spec.license = "MIT"
16
16
 
17
+ spec.bindir = 'exe'
17
18
  spec.files = `git ls-files`.split($/)
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
21
  spec.require_paths = ["lib"]
21
22
 
22
- spec.add_runtime_dependency "sqlite3-ruby"
23
+ spec.add_runtime_dependency "sqlite3"
23
24
  spec.add_runtime_dependency "nokogiri"
24
25
 
25
26
  spec.add_development_dependency "bundler", "~> 1.3"
26
27
  spec.add_development_dependency "rake"
27
28
  spec.add_development_dependency "rspec"
28
29
  spec.add_development_dependency "simplecov"
29
- spec.add_development_dependency "coveralls"
30
30
  spec.add_development_dependency "codeclimate-test-reporter"
31
31
  end
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ lib_dir = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
5
+
3
6
  require 'audio_book_creator'
4
7
  require 'audio_book_creator/cli'
5
8
 
@@ -19,11 +19,11 @@ module AudioBookCreator
19
19
  end
20
20
 
21
21
  def self.logger=(val)
22
- @log = val
22
+ @logger = val
23
23
  end
24
24
 
25
25
  def self.logger
26
- @log ||= Logger.new(STDOUT).tap { |log| log.level = Logger::WARN }
26
+ @logger ||= Logger.new(STDOUT).tap { |log| log.level = Logger::WARN }
27
27
  end
28
28
  end
29
29
 
@@ -40,6 +40,7 @@ require "audio_book_creator/speaker_def"
40
40
  require "audio_book_creator/surfer_def"
41
41
 
42
42
  # data models
43
+ require "audio_book_creator/web_page"
43
44
  require "audio_book_creator/chapter"
44
45
  require "audio_book_creator/spoken_chapter"
45
46
 
@@ -57,3 +58,4 @@ require "audio_book_creator/speaker"
57
58
  require "audio_book_creator/binder"
58
59
  require "audio_book_creator/book_creator" # full workflow
59
60
  require "audio_book_creator/conductor" # creates components of flow
61
+ require "audio_book_creator/defaulter" # loads and stores default parameters
@@ -30,6 +30,7 @@ module AudioBookCreator
30
30
 
31
31
  def params(chapters)
32
32
  ret = {
33
+ "-A" => nil,
33
34
  "-a" => book_def.author,
34
35
  "-t" => book_def.title,
35
36
  "-b" => speaker_def.bit_rate,
@@ -42,7 +43,7 @@ module AudioBookCreator
42
43
  # "-C" => "file.png" cover image
43
44
  nil => chapter_params(chapters),
44
45
  }
45
- ret["-A"] = nil if itunes # add audiobook to iTunes
46
+ ret.delete("-A") unless itunes # add audiobook to iTunes
46
47
  ret
47
48
  end
48
49
 
@@ -12,7 +12,7 @@ module AudioBookCreator
12
12
  @base_dir = base_dir
13
13
  @author = author || "Vicki"
14
14
  @urls = urls
15
- @itunes = itunes
15
+ @itunes = itunes.nil? ? true : itunes
16
16
  end
17
17
 
18
18
  def filename
@@ -30,7 +30,7 @@ module AudioBookCreator
30
30
  private
31
31
 
32
32
  def self.sanitize_filename(*filenames)
33
- filenames.compact.join(".").gsub(/[^-._a-z0-9A-Z]/, "-").gsub(/--*/, "-").sub(/-$/, "")
33
+ filenames.join(".").gsub(/[^-._a-z0-9A-Z]/, "-").gsub(/-+/, "-").sub(/-\z/, "")
34
34
  end
35
35
  end
36
36
  end
@@ -14,7 +14,7 @@ module AudioBookCreator
14
14
  end
15
15
 
16
16
  def [](name)
17
- @cache[name] ||= @main[name]
17
+ @cache[name] ||= main[name]
18
18
  end
19
19
  end
20
20
  end
@@ -22,8 +22,8 @@ module AudioBookCreator
22
22
  end
23
23
 
24
24
  def add_chapter(value)
25
- @all << value
26
- @alt << value
25
+ all << value
26
+ alt << value
27
27
  end
28
28
 
29
29
  def add_unique_chapter(value)
@@ -31,8 +31,8 @@ module AudioBookCreator
31
31
  end
32
32
 
33
33
  def add_page(value)
34
- @all << value
35
- @main << value
34
+ all << value
35
+ main << value
36
36
  end
37
37
 
38
38
  def add_unique_page(value)
@@ -41,17 +41,17 @@ module AudioBookCreator
41
41
 
42
42
  def each(&block)
43
43
  return enum_for unless block_given?
44
- @main.each(&block)
45
- @alt.each(&block)
44
+ main.each(&block)
45
+ alt.each(&block)
46
46
  end
47
47
 
48
48
  # note: ever included
49
49
  def include?(value)
50
- @all.include?(value)
50
+ all.include?(value)
51
51
  end
52
52
 
53
53
  def shift
54
- @main.shift || @alt.shift
54
+ main.shift || alt.shift
55
55
  end
56
56
  end
57
57
  end
@@ -4,7 +4,7 @@ module AudioBookCreator
4
4
 
5
5
  def initialize(options = {})
6
6
  options.each { |n, v| public_send("#{n}=", v) }
7
- @body = Array(@body).compact.join("\n\n")
7
+ @body = Array(body).compact.join("\n\n")
8
8
  end
9
9
 
10
10
  def filename
@@ -6,34 +6,13 @@ require 'uri'
6
6
  module AudioBookCreator
7
7
  class Cli
8
8
  include Logging
9
- def initialize
10
- self.verbose = false
11
- page_def.title_path = "h1"
12
- page_def.body_path = "p"
13
- page_def.link_path = "a"
14
- end
15
9
 
16
10
  # stub for testing
17
11
  attr_writer :web
18
12
 
19
- def set_args(argv, usage)
20
- if argv.empty?
21
- puts "please url", usage
22
- exit 2
23
- elsif argv.first.include?("://")
24
- book_def.title = argv.first.split("/").last
25
- book_def.urls = argv
26
- else
27
- book_def.title = argv.shift
28
- book_def.urls = argv
29
- end
30
- surfer_def.cache_filename = database
31
- surfer_def.host = book_def.urls.first
32
- end
33
-
34
- def database
35
- "pages.db"
36
- end
13
+ # true if we are setting the defaults
14
+ attr_accessor :set_defaults
15
+ attr_accessor :skip_defaults
37
16
 
38
17
  def verbose=(val)
39
18
  logger.level = val ? Logger::INFO : Logger::WARN
@@ -43,9 +22,11 @@ module AudioBookCreator
43
22
  options = OptionParser.new do |opts|
44
23
  opts.program_name = "audio_book_creator"
45
24
  opts.version = VERSION
46
- opts.banner = "Usage: audio_book_creator [options] title url [url] [...]"
25
+ opts.banner = "Usage: audio_book_creator [options] [title] url [url] [...]"
47
26
  opt(opts, self) do |o|
48
- o.opt(:verbose, "-v", "--verbose", "Run verbosely")
27
+ o.opt(:verbose, "-v", "--verbose", "--[no-]verbose", "Run verbosely")
28
+ o.opt(:set_defaults, "--default", "Set these parameters as default for this url regular expression")
29
+ o.opt(:skip_defaults, "--skip-defaults", "Skip using defaults")
49
30
  end
50
31
  opt(opts, page_def) do |o|
51
32
  o.opt(:title_path, "--title STRING", "Title css (e.g.: h1)")
@@ -55,7 +36,7 @@ module AudioBookCreator
55
36
  end
56
37
  opt(opts, surfer_def) do |o|
57
38
  o.opt(:max, "--no-max", "Don't limit the number of pages to visit")
58
- o.opt(:max, "--max NUMBER", Integer, "Maximum number of pages to visit (default: 10)")
39
+ o.opt(:max, "--max NUMBER", Integer, "Maximum number of pages to visit")
59
40
  o.opt(:regen_html, "--force-html", "Regerate the audio")
60
41
  end
61
42
  opt(opts, speaker_def) do |o|
@@ -65,7 +46,7 @@ module AudioBookCreator
65
46
  end
66
47
  opt(opts, book_def) do |o|
67
48
  o.opt(:base_dir, "--base-dir STRING", "Directory to hold files")
68
- o.opt(:itunes, "--itunes", "-A", "Load book into itunes")
49
+ o.opt(:itunes, "--[no-]itunes", "-A", "Load book into itunes")
69
50
  end
70
51
  end
71
52
  options.parse!(argv)
@@ -91,12 +72,26 @@ module AudioBookCreator
91
72
  @surfer_def ||= SurferDef.new
92
73
  end
93
74
 
75
+ def defaulter
76
+ @defaulter ||= Defaulter.new(page_def, book_def)
77
+ end
78
+
94
79
  def conductor
95
80
  @conductor ||= Conductor.new(page_def, book_def, speaker_def, surfer_def)
96
81
  end
97
82
 
83
+ # integration method
98
84
  def run
99
- conductor.run
85
+ if set_defaults
86
+ if defaulter.store
87
+ puts "stored for #{defaulter.host}"
88
+ else
89
+ puts "not stored"
90
+ end
91
+ else
92
+ defaulter.load_unset_values unless skip_defaults
93
+ conductor.run
94
+ end
100
95
  end
101
96
 
102
97
  private
@@ -115,5 +110,17 @@ module AudioBookCreator
115
110
  @opts.on(*args) { |v| @model.send("#{value}=", v) }
116
111
  end
117
112
  end
113
+
114
+ def set_args(argv, usage)
115
+ if argv.empty?
116
+ puts "please url", usage
117
+ exit 2
118
+ elsif argv.first.include?("://")
119
+ book_def.title = argv.first.split("/").last
120
+ else
121
+ book_def.title = argv.shift
122
+ end
123
+ book_def.urls = argv
124
+ end
118
125
  end
119
126
  end
@@ -10,6 +10,8 @@ module AudioBookCreator
10
10
  @book_def = book_def
11
11
  @speaker_def = speaker_def
12
12
  @surfer_def = surfer_def
13
+
14
+ page_def.invalid_urls = invalid_urls
13
15
  end
14
16
 
15
17
  # components
@@ -17,7 +19,7 @@ module AudioBookCreator
17
19
  ## spider
18
20
 
19
21
  def page_cache
20
- @page_cache ||= PageDb.new(surfer_def.cache_filename)
22
+ @page_cache ||= PageDb.new("pages.db", "pages", false)
21
23
  end
22
24
 
23
25
  def web
@@ -29,11 +31,11 @@ module AudioBookCreator
29
31
  end
30
32
 
31
33
  def invalid_urls
32
- @invalid_urls ||= UrlFilter.new(surfer_def.host)
34
+ @invalid_urls ||= UrlFilter.new(book_def.urls.first)
33
35
  end
34
36
 
35
37
  def spider
36
- @spider ||= Spider.new(page_def, cached_web, invalid_urls)
38
+ @spider ||= Spider.new(page_def, cached_web)
37
39
  end
38
40
 
39
41
  ##
@@ -0,0 +1,41 @@
1
+ require 'forwardable'
2
+ module AudioBookCreator
3
+ class Defaulter
4
+ extend Forwardable
5
+
6
+ ATTRIBUTES = [:title_path, :body_path, :link_path, :chapter_path]
7
+ def_delegators :page_def, *ATTRIBUTES, *ATTRIBUTES.map { |a| "#{a}="}
8
+
9
+ attr_accessor :page_def
10
+ attr_accessor :book_def
11
+ attr_writer :settings
12
+
13
+ def initialize(page_def, book_def)
14
+ @page_def = page_def
15
+ @book_def = book_def
16
+ end
17
+
18
+ def host
19
+ url = book_def.urls.first
20
+ url && URI.parse(url).host
21
+ end
22
+
23
+ def settings
24
+ # in the future, move into pages
25
+ @settings ||= PageDb.new("settings.db", "settings", true)
26
+ end
27
+
28
+ def load_unset_values
29
+ value = host && settings[host]
30
+ value.each { |n, v| public_send("#{n}=", v) } if value
31
+ end
32
+
33
+ def store
34
+ return unless host
35
+ settings[host] = ATTRIBUTES.each_with_object(settings[host] || {}) do |attr, h|
36
+ v = public_send(attr)
37
+ h[attr] = v if v
38
+ end
39
+ end
40
+ end
41
+ end
@@ -10,9 +10,8 @@ module AudioBookCreator
10
10
  # convert page[] -> chapter[]
11
11
  def parse(pages)
12
12
  pages.each_with_index.map do |page, i|
13
- dom = Nokogiri::HTML(page)
14
- title = page_def.title(dom) || "Chapter #{i + 1}"
15
- body = page_def.body(dom)
13
+ title = page.css(page_def.title_path).first || "Chapter #{i + 1}"
14
+ body = page.css(page_def.body_path)
16
15
  Chapter.new(number: (i + 1), title: title, body: body)
17
16
  end
18
17
  end