audio_book_creator 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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