ebook_renamer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.ruby-version +1 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/Guardfile +23 -0
- data/LICENSE +22 -0
- data/README.md +76 -0
- data/Rakefile +31 -0
- data/bin/ebook_renamer +29 -0
- data/ebook_renamer.gemspec +31 -0
- data/lib/ebook_renamer.rb +7 -0
- data/lib/ebook_renamer/cli.rb +54 -0
- data/lib/ebook_renamer/configuration.rb +32 -0
- data/lib/ebook_renamer/constant.rb +19 -0
- data/lib/ebook_renamer/helpers.rb +171 -0
- data/lib/ebook_renamer/logger.rb +10 -0
- data/lib/ebook_renamer/options.rb +62 -0
- data/lib/ebook_renamer/version.rb +3 -0
- data/test/fixtures/ebooks/demo1.pdf +0 -0
- data/test/fixtures/ebooks/demo2.epub +0 -0
- data/test/fixtures/ebooks/subdir/demo3.pdf +0 -0
- data/test/fixtures/ebooks/subdir/demo4.epub +0 -0
- data/test/lib/ebook_renamer/configuration_test.rb +22 -0
- data/test/lib/ebook_renamer/helpers_test.rb +123 -0
- data/test/lib/ebook_renamer/version_test.rb +6 -0
- data/test/test_helper.rb +6 -0
- metadata +221 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 244dbff6a80aa66460a633d9ca1e77d7c2d11054
|
4
|
+
data.tar.gz: 7db39857c19b170eb76ea19d62cd85a444505c77
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b1d5b29908281d629b7e6dbdab770b8a59902caf6c985332e5cce282389b963c0359d321683194c698a3a74b90d5b9899e02b3a3350f06c7303a8b9c5e274bf8
|
7
|
+
data.tar.gz: 57536decad2872912f5c507de4de1fb04d7fa2b5b590353ce6255a78a5128688d841a48a58fb44a4a3988dc4dce8f4b9ef4b567734d04923d84a8418ee75fe6e
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.1
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
guard 'minitest' do
|
4
|
+
# with Minitest::Unit
|
5
|
+
watch(%r|^test/(.*)\/?test_(.*)\.rb|)
|
6
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
7
|
+
watch(%r|^test/test_helper\.rb|) { "test" }
|
8
|
+
|
9
|
+
# with Minitest::Spec
|
10
|
+
# watch(%r|^spec/(.*)_spec\.rb|)
|
11
|
+
# watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
# watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
13
|
+
|
14
|
+
# Rails 3.2
|
15
|
+
# watch(%r|^app/controllers/(.*)\.rb|) { |m| "test/controllers/#{m[1]}_test.rb" }
|
16
|
+
# watch(%r|^app/helpers/(.*)\.rb|) { |m| "test/helpers/#{m[1]}_test.rb" }
|
17
|
+
# watch(%r|^app/models/(.*)\.rb|) { |m| "test/unit/#{m[1]}_test.rb" }
|
18
|
+
|
19
|
+
# Rails
|
20
|
+
# watch(%r|^app/controllers/(.*)\.rb|) { |m| "test/functional/#{m[1]}_test.rb" }
|
21
|
+
# watch(%r|^app/helpers/(.*)\.rb|) { |m| "test/helpers/#{m[1]}_test.rb" }
|
22
|
+
# watch(%r|^app/models/(.*)\.rb|) { |m| "test/unit/#{m[1]}_test.rb" }
|
23
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Burin Choomnuan
|
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,76 @@
|
|
1
|
+
## EbookRenamer
|
2
|
+
|
3
|
+
Simple utility to perform bulk rename of the ebooks(epub,mobi,pdf) based on
|
4
|
+
the metadata within the ebook itself (if available).
|
5
|
+
|
6
|
+
### Installation
|
7
|
+
|
8
|
+
* You will need to install the [Calibre](http://www.calibre-ebook.com/) and
|
9
|
+
[Calibre CLI](http://manual.calibre-ebook.com/cli/cli-index.html)
|
10
|
+
|
11
|
+
* Then install the gem
|
12
|
+
|
13
|
+
```sh
|
14
|
+
$bundle
|
15
|
+
$gem install ebook_renamer
|
16
|
+
```
|
17
|
+
|
18
|
+
### Usage
|
19
|
+
|
20
|
+
Run the following command from the directory that contain the file(s) that
|
21
|
+
you want to rename.
|
22
|
+
|
23
|
+
```sh
|
24
|
+
# cd to the directory containing the file you like to rename
|
25
|
+
cd ~/Dropbox/ebooks/
|
26
|
+
|
27
|
+
# or specify the directory as an option
|
28
|
+
ebook_renamer --base-dir ~/Dropbox/ebooks/samples
|
29
|
+
|
30
|
+
# If you like to see the usage try
|
31
|
+
ebook_renamer --help
|
32
|
+
|
33
|
+
# Run the command without to see what will be changed without making any changes (dry-run)
|
34
|
+
ebook_rename --recursive
|
35
|
+
|
36
|
+
# Once you are happy with what you see, then
|
37
|
+
ebook_renamer --recusive --commit
|
38
|
+
```
|
39
|
+
|
40
|
+
### Output of `ebook_renamer --help`
|
41
|
+
|
42
|
+
```
|
43
|
+
Usage: ebook_renamer [options]
|
44
|
+
|
45
|
+
Examples:
|
46
|
+
|
47
|
+
1) $ebook_renamer
|
48
|
+
|
49
|
+
2) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
50
|
+
|
51
|
+
3) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
52
|
+
--recursive
|
53
|
+
|
54
|
+
4) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
55
|
+
--recursive
|
56
|
+
|
57
|
+
5) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
58
|
+
--recursive
|
59
|
+
--commit
|
60
|
+
Options:
|
61
|
+
|
62
|
+
-b, --base-dir directory Starting directory [default - current directory]
|
63
|
+
-r, --recursive Process the files recursively [default - false]
|
64
|
+
-c, --commit Perform the actual rename [default - false]
|
65
|
+
-v, --version Display version number
|
66
|
+
-h, --help Display this screen
|
67
|
+
```
|
68
|
+
|
69
|
+
### Contributing
|
70
|
+
|
71
|
+
1. Fork it
|
72
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
73
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
74
|
+
4. Make sure that you add the tests and ensure that all tests are passed
|
75
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs << 'lib/ebook_renamer'
|
6
|
+
t.test_files = FileList['test/lib/ebook_renamer/*_test.rb']
|
7
|
+
t.verbose = true
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
## see: http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
|
13
|
+
task :irb do
|
14
|
+
require 'irb'
|
15
|
+
require 'awesome_print'
|
16
|
+
require 'irb/completion'
|
17
|
+
require 'ebook_renamer'
|
18
|
+
include EbookRenamer
|
19
|
+
ARGV.clear
|
20
|
+
IRB.start
|
21
|
+
end
|
22
|
+
|
23
|
+
## see: http://lucapette.com/pry/pry-everywhere/
|
24
|
+
task :pry do
|
25
|
+
require 'pry'
|
26
|
+
require 'awesome_print'
|
27
|
+
require 'ebook_renamer'
|
28
|
+
include EbookRenamer
|
29
|
+
ARGV.clear
|
30
|
+
Pry.start
|
31
|
+
end
|
data/bin/ebook_renamer
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
begin
|
3
|
+
require 'pry'
|
4
|
+
require 'ebook_renamer'
|
5
|
+
rescue LoadError
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
require 'ebook_renamer'
|
8
|
+
end
|
9
|
+
|
10
|
+
include EbookRenamer
|
11
|
+
include EbookRenamer::Options
|
12
|
+
require 'ostruct'
|
13
|
+
|
14
|
+
begin
|
15
|
+
options = parse_options()
|
16
|
+
EbookRenamer.logger.info "Your options: #{options}"
|
17
|
+
# Note: if we need to adjust the path to the executable
|
18
|
+
EbookRenamer.configure do |config|
|
19
|
+
config.meta_binary = '/usr/bin/ebook-meta'
|
20
|
+
end
|
21
|
+
|
22
|
+
cli = EbookRenamer::CLI.new(EbookRenamer.configuration)
|
23
|
+
cli.rename(options[:base_dir], options)
|
24
|
+
exit 0
|
25
|
+
rescue ArgumentError => e
|
26
|
+
puts e
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
# vim: ft=ruby
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ebook_renamer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ebook_renamer"
|
8
|
+
spec.version = EbookRenamer::VERSION
|
9
|
+
spec.authors = ["Burin Choomnuan"]
|
10
|
+
spec.email = ["agilecreativity@gmail.com"]
|
11
|
+
spec.description = %q{Bulk rename of ebook files based on available metadata}
|
12
|
+
spec.summary = %q{Rename multiple ebook files (epub, mobi, pdf) based on existing metadata in the file}
|
13
|
+
spec.homepage = "https://github.com/agilecreativity/ebook_renamer"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
# Generated dependencies
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
# additional dependencies
|
23
|
+
spec.add_development_dependency "minitest-spec-context", "~> 0.0.3"
|
24
|
+
spec.add_development_dependency "guard-minitest", "~> 2.2"
|
25
|
+
spec.add_development_dependency "minitest", "~> 4.2"
|
26
|
+
spec.add_development_dependency "awesome_print", "~> 1.2"
|
27
|
+
spec.add_development_dependency "guard", "~> 2.6"
|
28
|
+
spec.add_development_dependency "pry", "~> 0.9"
|
29
|
+
spec.add_development_dependency "gem-ctags", "~> 1.0"
|
30
|
+
spec.add_development_dependency "yard", "~> 0.8"
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module EbookRenamer
|
2
|
+
class CLI
|
3
|
+
|
4
|
+
attr_accessor :config
|
5
|
+
|
6
|
+
# Constructor for the class
|
7
|
+
#
|
8
|
+
# @param [Configuration] config the configuration class
|
9
|
+
def initialize(config = Configuration.new)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
# Rename the file from the given directory
|
14
|
+
# Using the with the argurment options as follow
|
15
|
+
# :recursive - perform the rename recursively (true|false)
|
16
|
+
# :commit - make the rename permanent (true|false)
|
17
|
+
# :exts - list of extensions to be processed default to ['epub,mobi,pdf']
|
18
|
+
# @param base_dir [String] base directory default to current directory
|
19
|
+
# @param args [Hash<Symbol, Object>] options argument
|
20
|
+
def rename(base_dir = Dir.pwd, args = {})
|
21
|
+
options = {
|
22
|
+
recursive: false,
|
23
|
+
commit: false,
|
24
|
+
exts: %w(epub mobi pdf).join(",")
|
25
|
+
}.merge(args)
|
26
|
+
|
27
|
+
input_files = Helpers.files(base_dir, options).sort
|
28
|
+
|
29
|
+
input_files.each do |file|
|
30
|
+
puts "Input :#{file}"
|
31
|
+
extension = File.extname(file)
|
32
|
+
begin
|
33
|
+
hash = Helpers.meta_to_hash(Helpers.meta(file, config.ebook_meta_binary))
|
34
|
+
formatted_name = Helpers.formatted_name(hash, sep_char: " by ")
|
35
|
+
formatted_name = "#{formatted_name}#{extension}"
|
36
|
+
new_name = "#{File.dirname(file)}/#{Helpers.sanitize_filename(formatted_name, '.')}"
|
37
|
+
puts "Output:#{new_name}"
|
38
|
+
# skip if the filename is too long '228'
|
39
|
+
# see: https://github.com/rails/rails/commit/ad95a61b62e70b839567c2e91e127fc2a1acb113
|
40
|
+
# @todo find out the max file size
|
41
|
+
max_allowed_file_size = 220
|
42
|
+
if new_name && new_name.size > max_allowed_file_size
|
43
|
+
puts "FYI: skip file name too long [#{new_name.size}] : #{new_name}"
|
44
|
+
next
|
45
|
+
end
|
46
|
+
FileUtils.mv(file,new_name) if options[:commit] && file != new_name && new_name.size < max_allowed_file_size
|
47
|
+
rescue RuntimeError => e
|
48
|
+
puts e.backtrace
|
49
|
+
next
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module EbookRenamer
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
attr_accessor :meta_binary
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@meta_binary = '/usr/bin/ebook-meta'
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
<<-END.gsub(/^\s+\|/, '')
|
12
|
+
| ebook-meta : #{ebook_meta_binary}
|
13
|
+
END
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_writer :configuration
|
19
|
+
|
20
|
+
def configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@configuration = Configuration.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure
|
29
|
+
yield(configuration)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EbookRenamer
|
2
|
+
# The Calibre metadata extraction tool (epub, mobi)
|
3
|
+
CALIBRE_CLI_BINARY = '/usr/bin/ebook-meta'
|
4
|
+
|
5
|
+
# The Calibre metadata extraction tool
|
6
|
+
CALIBRE_META_CLI = '/usr/bin/ebook-meta'
|
7
|
+
|
8
|
+
# Support URL for Calibre's CLI tool
|
9
|
+
CALIBRE_CLI_URL = 'http://manual.calibre-ebook.com/cli/cli-index.html'
|
10
|
+
|
11
|
+
# Exiftool binary executable (for pdf, also `mdls` on OSX)
|
12
|
+
EXIFTOOL_BINARY = '/usr/local/bin/exiftool'
|
13
|
+
|
14
|
+
# On OSX, `brew install exiftool`
|
15
|
+
EXIFTOOL_URL = 'http://www.sno.phy.queensu.ca/~phil/exiftool/'
|
16
|
+
|
17
|
+
# Attribute keys
|
18
|
+
META_KEYS = %w[title authors(s) publisher languages published rights identifiers]
|
19
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module EbookRenamer
|
6
|
+
class Helpers
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Extract meta data from the input file using the ebook-meta tool
|
10
|
+
#
|
11
|
+
# @param [String] filename the input file name
|
12
|
+
# @param [String] binary the executable for use to extract the metadata
|
13
|
+
# @return [String] result of the output from running the command
|
14
|
+
def meta(filename, binary = 'ebook-meta')
|
15
|
+
command = [
|
16
|
+
binary,
|
17
|
+
Shellwords.escape(filename)
|
18
|
+
]
|
19
|
+
|
20
|
+
stdout_str, stderr_str, status = Open3.capture3(command.join(" "))
|
21
|
+
raise "Problem processing #{filename}" unless status.success?
|
22
|
+
stdout_str
|
23
|
+
end
|
24
|
+
|
25
|
+
# Convert the output string to hash
|
26
|
+
#
|
27
|
+
# @param [String] text output string from the 'ebook-meta' command
|
28
|
+
# @return [Hash<String,String>] hash pair for the input string
|
29
|
+
def meta_to_hash(text)
|
30
|
+
hash = {}
|
31
|
+
return hash if text.nil?
|
32
|
+
result_list = []
|
33
|
+
|
34
|
+
text.split(/\n/).each do |meta|
|
35
|
+
# split by the first ':' string
|
36
|
+
list = meta.split /^(.*?):/
|
37
|
+
|
38
|
+
# ignore the empty string element
|
39
|
+
list.delete_at(0)
|
40
|
+
|
41
|
+
unless list.empty?
|
42
|
+
list.map(&:strip!)
|
43
|
+
# downcase the first item to make it easy
|
44
|
+
result_list << [list[0].downcase, list[1]]
|
45
|
+
hash = Hash[*result_list.flatten]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
|
51
|
+
# Clean the filename to remove the special characters
|
52
|
+
#
|
53
|
+
# @param [String] filename input file
|
54
|
+
# @param [String] sep_char separator character to use
|
55
|
+
#
|
56
|
+
# @return [String] the new file name with special characters replaced or removed.
|
57
|
+
def sanitize_filename(filename, sep_char = nil)
|
58
|
+
dot = "."
|
59
|
+
|
60
|
+
# Note exclude the '.' (dot)
|
61
|
+
filename.gsub!(/[^0-9A-Za-z\-_ ]/, dot)
|
62
|
+
|
63
|
+
# replace multiple occurrences of a given char with a dot
|
64
|
+
['-','_',' '].each do |c|
|
65
|
+
filename.gsub!(/#{Regexp.quote(c)}+/, dot)
|
66
|
+
end
|
67
|
+
|
68
|
+
# replace multiple occurrence of dot with one dot
|
69
|
+
filename.gsub!(/#{Regexp.quote(dot)}+/, dot)
|
70
|
+
|
71
|
+
if sep_char
|
72
|
+
# File.basename("demo.txt", ".*") #=> "demo"
|
73
|
+
name_only = File.basename(filename, ".*")
|
74
|
+
# File.extname("demo.txt") #=> ".txt"
|
75
|
+
ext_only = File.extname(filename)
|
76
|
+
name_only.gsub!(/#{Regexp.quote(dot)}+/, sep_char)
|
77
|
+
return "#{name_only}#{ext_only}"
|
78
|
+
end
|
79
|
+
|
80
|
+
filename.strip
|
81
|
+
end
|
82
|
+
|
83
|
+
# Cross-platform way of finding an executable in the $PATH.
|
84
|
+
#
|
85
|
+
# @param command [String] the command to look up
|
86
|
+
# @return [String, NilClass] full path to the executable file or nil if the
|
87
|
+
# executable is not valid or available.
|
88
|
+
# Example:
|
89
|
+
# which('ruby') #=> /usr/bin/ruby
|
90
|
+
# which('bad-executable') #=> nil
|
91
|
+
def which(command)
|
92
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
93
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
94
|
+
exts.each { |ext|
|
95
|
+
exe = File.join(path, "#{command}#{ext}")
|
96
|
+
return exe if File.executable? exe
|
97
|
+
}
|
98
|
+
end
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return formatted file name using the metadata values
|
103
|
+
#
|
104
|
+
# @param [Hash<Symbol,String>] meta_hash output from the program 'ebook-meta' or 'exiftoo'
|
105
|
+
# @param [Hash<Symbol,String>] fields list of fields that will be used to set the name
|
106
|
+
def formatted_name(meta_hash = {}, fields = {})
|
107
|
+
if hash.nil? || fields.nil?
|
108
|
+
raise ArgumentError.new("Argument must not be nil")
|
109
|
+
end
|
110
|
+
|
111
|
+
# The keys that we get from the 'mdls' or 'exiftool'
|
112
|
+
args = {
|
113
|
+
keys: ['title',
|
114
|
+
'author(s)'],
|
115
|
+
sep_char: ' '
|
116
|
+
}.merge(fields)
|
117
|
+
|
118
|
+
keys = args[:keys]
|
119
|
+
sep_char = args[:sep_char]
|
120
|
+
|
121
|
+
# Note: only show if we have the value for title
|
122
|
+
result = []
|
123
|
+
if meta_hash.fetch('title', nil)
|
124
|
+
keys.each do |k|
|
125
|
+
value = meta_hash.fetch(k, nil)
|
126
|
+
# Note: don't add 'Author(s)' => 'Unknown' to keep the result clean
|
127
|
+
if value && value.downcase != 'unknown'
|
128
|
+
result << meta_hash[k]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
return result.join(sep_char)
|
132
|
+
end
|
133
|
+
# Note: if no title we choose to return empty value for result
|
134
|
+
return ""
|
135
|
+
end
|
136
|
+
|
137
|
+
# Ensure that the values in hash are sanitized
|
138
|
+
#
|
139
|
+
# @param [Hash<Symbol,String>] hash input hash to be sanitized
|
140
|
+
# @return [Hash<Symbol,String>] original hash with values sanitized
|
141
|
+
# @see #sanitize_filename
|
142
|
+
def sanitize_values(hash = {})
|
143
|
+
hash.each do |key, value|
|
144
|
+
hash[key] = sanitize_filename(value, " ")
|
145
|
+
end
|
146
|
+
hash
|
147
|
+
end
|
148
|
+
|
149
|
+
# List files base on given options
|
150
|
+
# options:
|
151
|
+
# :recursive - process the directory recursively (default false)
|
152
|
+
# :exts - list of extensions to be search (default ['epub','mobi','pdf'])
|
153
|
+
#
|
154
|
+
# @param base_dir [String] the starting directory
|
155
|
+
# @param options [Hash<Symbol,Object>] the options to be used
|
156
|
+
# @return [List<String>] list of matching files or empty list if none are found
|
157
|
+
def files(base_dir = Dir.pwd, options = {})
|
158
|
+
args = {
|
159
|
+
recursive: false,
|
160
|
+
exts: %w(epub mobi pdf).join(',')
|
161
|
+
}.merge(options)
|
162
|
+
|
163
|
+
raise ArgumentError.new("Invalid directory #{base_dir}") unless File.directory?(base_dir)
|
164
|
+
|
165
|
+
wildcard = args[:recursive] ? '**' : ''
|
166
|
+
patterns = File.join(base_dir, wildcard, "*.{#{args[:exts]}}")
|
167
|
+
Dir.glob(patterns) || []
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module EbookRenamer::Options
|
4
|
+
|
5
|
+
def parse_options()
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
option_parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = <<-END.gsub(/^\s+\|/, '')
|
10
|
+
|
|
11
|
+
| Usage: ebook_renamer [options]
|
12
|
+
|
|
13
|
+
| Examples:
|
14
|
+
|
|
15
|
+
| 1) $ebook_renamer
|
16
|
+
|
|
17
|
+
| 2) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
18
|
+
|
|
19
|
+
| 3) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
20
|
+
| --recursive
|
21
|
+
|
|
22
|
+
| 4) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
23
|
+
| --recursive
|
24
|
+
|
|
25
|
+
| 5) $ebook_renamer --base-dir ~/Dropbox/ebooks
|
26
|
+
| --recursive
|
27
|
+
| --commit
|
28
|
+
|
|
29
|
+
| Options:
|
30
|
+
|
|
31
|
+
END
|
32
|
+
|
33
|
+
options[:base_dir] ||= Dir.pwd
|
34
|
+
opts.on('-b', '--base-dir directory', 'Starting directory [default - current directory]') do |base_dir|
|
35
|
+
options[:base_dir] = base_dir
|
36
|
+
end
|
37
|
+
|
38
|
+
options[:recursive] = false
|
39
|
+
opts.on('-r', '--recursive', 'Process the files recursively [default - false]') do
|
40
|
+
options[:recursive] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
options[:commit] = false
|
44
|
+
opts.on('-c', '--commit', 'Perform the actual rename [default - false]') do
|
45
|
+
options[:commit] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-v', '--version', 'Display version number') do
|
49
|
+
puts EbookRenamer::VERSION
|
50
|
+
exit 0
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-h', '--help', 'Display this screen') do
|
54
|
+
puts opts
|
55
|
+
exit 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
option_parser.parse!
|
60
|
+
options
|
61
|
+
end
|
62
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
describe EbookRenamer do
|
3
|
+
|
4
|
+
before :each do
|
5
|
+
EbookRenamer.configure do |config|
|
6
|
+
config.meta_binary = '/usr/bin/ebook-meta'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
after :each do
|
11
|
+
EbookRenamer.reset
|
12
|
+
end
|
13
|
+
|
14
|
+
it "uses the updated configuration" do
|
15
|
+
EbookRenamer.configuration.meta_binary.must_equal '/usr/bin/ebook-meta'
|
16
|
+
config = EbookRenamer.configure do |config|
|
17
|
+
config.meta_binary = "ebook-meta"
|
18
|
+
end
|
19
|
+
EbookRenamer.configuration.meta_binary.must_equal 'ebook-meta'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
describe EbookRenamer do
|
3
|
+
|
4
|
+
subject { EbookRenamer::Helpers }
|
5
|
+
|
6
|
+
before do
|
7
|
+
@sample = EbookRenamer::Helpers.meta("./test/fixtures/ebooks/demo1.pdf")
|
8
|
+
end
|
9
|
+
|
10
|
+
context "#meta" do
|
11
|
+
it "raises error on invalid input" do
|
12
|
+
->{ subject.meta("invalid-filename") }.must_raise RuntimeError
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns valid valid input" do
|
16
|
+
@sample.wont_be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#meta_to_hash" do
|
22
|
+
it 'returns empty hash' do
|
23
|
+
subject.meta_to_hash(nil).must_be_empty
|
24
|
+
end
|
25
|
+
it 'returns non-empty hash' do
|
26
|
+
hash = EbookRenamer::Helpers.meta_to_hash(@sample)
|
27
|
+
hash.wont_be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'invalid format' do
|
31
|
+
it 'return empty hash' do
|
32
|
+
subject.meta_to_hash('aa bb').must_equal({})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'valid format' do
|
37
|
+
# extract list of 'key' : 'value' from the input
|
38
|
+
let(:sample) {
|
39
|
+
<<-END
|
40
|
+
Aaaa : BBbb
|
41
|
+
CcCc : DddD
|
42
|
+
EeeE : FFFF:gggg
|
43
|
+
hhhh : iiii:JJJJ:kkkk
|
44
|
+
END
|
45
|
+
}
|
46
|
+
|
47
|
+
let(:result) { subject.meta_to_hash(sample) }
|
48
|
+
|
49
|
+
it "returns proper type for result" do
|
50
|
+
result.must_be_instance_of Hash
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'uses lowercase for result keys' do
|
54
|
+
result.keys.must_equal ['aaaa','cccc','eeee','hhhh']
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'retains original cases for result values' do
|
58
|
+
result.values.must_equal ['BBbb','DddD','FFFF:gggg','iiii:JJJJ:kkkk']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "#sanitize_filename" do
|
64
|
+
it "must be defined" do
|
65
|
+
subject.must_respond_to :sanitize_filename
|
66
|
+
end
|
67
|
+
|
68
|
+
it "replaces multiple valid chars with one" do
|
69
|
+
subject.sanitize_filename('Valid- -fil3_name......___ .txt').must_equal('Valid.fil3.name.txt')
|
70
|
+
end
|
71
|
+
|
72
|
+
it "replaces multiple valid chars with one" do
|
73
|
+
subject.sanitize_filename('valid filename_.txt').must_equal('valid.filename.txt')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "uses sepc_char correctly" do
|
77
|
+
subject.sanitize_filename('valid.file name.txt','_').must_equal('valid_file_name.txt')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context '#which' do
|
82
|
+
describe 'valid executable' do
|
83
|
+
it 'works with valid executable' do
|
84
|
+
subject.which('ruby').wont_be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
describe 'invalid executable' do
|
88
|
+
it 'works with invalid executable' do
|
89
|
+
subject.which('@not-a-valid-executable!').must_be_nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context '#formatted_name' do
|
95
|
+
describe 'invalid parameters' do
|
96
|
+
it 'raises exception on nil arguments' do
|
97
|
+
-> { subject.formatted_name({}, nil)}.must_raise ArgumentError
|
98
|
+
end
|
99
|
+
it 'returns nil on empty hash' do
|
100
|
+
subject.formatted_name({}, {}).must_be_empty
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'valid parameters' do
|
105
|
+
it 'returns result based on single key' do
|
106
|
+
subject.formatted_name({'title' => 'The Firm',
|
107
|
+
'author' => 'John Grisham',
|
108
|
+
'page count' => 399 },
|
109
|
+
keys: ['title']).must_equal 'The Firm'
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns result based for multiple keys' do
|
113
|
+
subject.formatted_name({'title' => 'The Firm',
|
114
|
+
'author' => 'John Grisham',
|
115
|
+
'page count' => '399' },
|
116
|
+
sep_char: ':',
|
117
|
+
keys: ['title',
|
118
|
+
'author',
|
119
|
+
'page count']).must_equal 'The Firm:John Grisham:399'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ebook_renamer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Burin Choomnuan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-spec-context
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.3
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: awesome_print
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.6'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.6'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.9'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.9'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: gem-ctags
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: yard
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.8'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.8'
|
153
|
+
description: Bulk rename of ebook files based on available metadata
|
154
|
+
email:
|
155
|
+
- agilecreativity@gmail.com
|
156
|
+
executables:
|
157
|
+
- ebook_renamer
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".ruby-version"
|
163
|
+
- ".yardopts"
|
164
|
+
- Gemfile
|
165
|
+
- Guardfile
|
166
|
+
- LICENSE
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- bin/ebook_renamer
|
170
|
+
- ebook_renamer.gemspec
|
171
|
+
- lib/ebook_renamer.rb
|
172
|
+
- lib/ebook_renamer/cli.rb
|
173
|
+
- lib/ebook_renamer/configuration.rb
|
174
|
+
- lib/ebook_renamer/constant.rb
|
175
|
+
- lib/ebook_renamer/helpers.rb
|
176
|
+
- lib/ebook_renamer/logger.rb
|
177
|
+
- lib/ebook_renamer/options.rb
|
178
|
+
- lib/ebook_renamer/version.rb
|
179
|
+
- test/fixtures/ebooks/demo1.pdf
|
180
|
+
- test/fixtures/ebooks/demo2.epub
|
181
|
+
- test/fixtures/ebooks/subdir/demo3.pdf
|
182
|
+
- test/fixtures/ebooks/subdir/demo4.epub
|
183
|
+
- test/lib/ebook_renamer/configuration_test.rb
|
184
|
+
- test/lib/ebook_renamer/helpers_test.rb
|
185
|
+
- test/lib/ebook_renamer/version_test.rb
|
186
|
+
- test/test_helper.rb
|
187
|
+
homepage: https://github.com/agilecreativity/ebook_renamer
|
188
|
+
licenses:
|
189
|
+
- MIT
|
190
|
+
metadata: {}
|
191
|
+
post_install_message:
|
192
|
+
rdoc_options: []
|
193
|
+
require_paths:
|
194
|
+
- lib
|
195
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
requirements: []
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 2.2.2
|
208
|
+
signing_key:
|
209
|
+
specification_version: 4
|
210
|
+
summary: Rename multiple ebook files (epub, mobi, pdf) based on existing metadata
|
211
|
+
in the file
|
212
|
+
test_files:
|
213
|
+
- test/fixtures/ebooks/demo1.pdf
|
214
|
+
- test/fixtures/ebooks/demo2.epub
|
215
|
+
- test/fixtures/ebooks/subdir/demo3.pdf
|
216
|
+
- test/fixtures/ebooks/subdir/demo4.epub
|
217
|
+
- test/lib/ebook_renamer/configuration_test.rb
|
218
|
+
- test/lib/ebook_renamer/helpers_test.rb
|
219
|
+
- test/lib/ebook_renamer/version_test.rb
|
220
|
+
- test/test_helper.rb
|
221
|
+
has_rdoc:
|