epub-search 0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +53 -0
- data/Rakefile +9 -0
- data/app/add.rb +12 -0
- data/app/init.rb +14 -0
- data/app/list.rb +9 -0
- data/app/remove.rb +10 -0
- data/app/search.rb +14 -0
- data/app/server.rb +9 -0
- data/app/watch.rb +91 -0
- data/bin/epub-search +66 -0
- data/epub-search.gemspec +37 -0
- data/lib/epub/search.rb +29 -0
- data/lib/epub/search/database.rb +120 -0
- data/lib/epub/search/formatter.rb +1 -0
- data/lib/epub/search/formatter/cli.rb +34 -0
- data/lib/epub/search/server.rb +33 -0
- data/lib/epub/search/version.rb +5 -0
- data/template/index.html.erb +12 -0
- data/template/result.html.erb +16 -0
- data/test/helper.rb +3 -0
- data/test/test_database.rb +7 -0
- metadata +282 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a0e5eaac93572491de5dfb8b483d996daf3f2798
|
4
|
+
data.tar.gz: 12fd51e95e4ca7291b92ca3d10f8b5f43c76331c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7bab11cd12dc7f248b8dcc3f990e45c3d7c77a798750fa0eb2b88e4c915b7926675b4c6e3d73b79bc9ad21106350cdab9c34e88f6c3e2726c0dcb38ed5cf622f
|
7
|
+
data.tar.gz: 64882ff476229d0841e126b9fb65c986a100e551fd39ccc0651845c0ad443eef0c554047ffac43a3f143d93e96d6fff8b230d9b77d55abba7abadb3754b45ac8
|
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 KITAITI Makoto
|
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.markdown
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
EPUB Search
|
2
|
+
===========
|
3
|
+
|
4
|
+
Search engine for EPUB files on local machine
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
$ gem install epub-search
|
10
|
+
|
11
|
+
Usage
|
12
|
+
-----
|
13
|
+
|
14
|
+
$ epub-search help
|
15
|
+
Commands:
|
16
|
+
epub-search add FILE # Add FILE to database
|
17
|
+
epub-search help [COMMAND] # Describe available commands or one specific command
|
18
|
+
epub-search init [DB_DIR] # Setup database
|
19
|
+
epub-search list # Show list of book titles in database
|
20
|
+
epub-search remove FILE # Remove FILE from database
|
21
|
+
epub-search search WORD [BOOK] # Search WORD in book whose title is like BOOK from database
|
22
|
+
epub-search server # Run server
|
23
|
+
epub-search watch [DIRECTORY [DIRECTORY ...]] # Index all of EPUB files in DIRECTORY
|
24
|
+
|
25
|
+
Options:
|
26
|
+
-c, [--config=CONFIG] # Path to config file
|
27
|
+
|
28
|
+
To watch directories in backend:
|
29
|
+
|
30
|
+
$ epub-search watch >/dev/null 2>&1 &
|
31
|
+
|
32
|
+
Configuration
|
33
|
+
-------------
|
34
|
+
|
35
|
+
EPUB Search(`epub-search` command) detectes configuration file following by this order:
|
36
|
+
|
37
|
+
1. A file specified by `--config` global option
|
38
|
+
2. `.epub-searchrc` in current directory
|
39
|
+
3. `$HOME/.epub-search/config.yaml`
|
40
|
+
|
41
|
+
Configuration file should be written as YAML and you can set properties below:
|
42
|
+
|
43
|
+
* :db_dir: String. Groonga database directory, defaults to $HOME/.epub-search/db
|
44
|
+
* :directories: Array of String. Directories `watch` subcommand watches.
|
45
|
+
|
46
|
+
Contributing
|
47
|
+
------------
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/app/add.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class Add
|
2
|
+
def initialize(db_dir, file_path)
|
3
|
+
@file_path = Pathname.new(file_path)
|
4
|
+
raise "File not readable: #{@file_path}" unless @file_path.readable?
|
5
|
+
@db = EPUB::Search::Database.new(db_dir)
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(update=true)
|
9
|
+
@db.remove @file_path if update
|
10
|
+
@db.add @file_path
|
11
|
+
end
|
12
|
+
end
|
data/app/init.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class Init
|
2
|
+
def initialize(db_dir)
|
3
|
+
@db = EPUB::Search::Database.new(db_dir)
|
4
|
+
end
|
5
|
+
|
6
|
+
def run(force=false)
|
7
|
+
$stderr.puts "create database #{@db.db_file}"
|
8
|
+
@db.init force
|
9
|
+
if force
|
10
|
+
exit_time_file = @db.db_dir.join('../exittime')
|
11
|
+
exit_time_file.delete if exit_time_file.exist?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/app/list.rb
ADDED
data/app/remove.rb
ADDED
data/app/search.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class Search
|
2
|
+
def initialize(db_dir, search_word, book=nil)
|
3
|
+
@word, @book = search_word, book
|
4
|
+
@db = EPUB::Search::Database.new(db_dir)
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(color=$stdout.tty?)
|
8
|
+
highlight = [true, 'always'].include? color
|
9
|
+
highlight = $stdout.tty? if color == 'auto'
|
10
|
+
@db.search @word, @book do |result|
|
11
|
+
EPUB::Search::Formatter::CLI.new(result, @word, highlight).format
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/app/server.rb
ADDED
data/app/watch.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'notify'
|
2
|
+
|
3
|
+
class Watch
|
4
|
+
EPUB_RE = /\.epub\Z/io
|
5
|
+
|
6
|
+
def initialize(db_file, directories)
|
7
|
+
raise ArgumentError, 'specify at least one directory' if directories.empty?
|
8
|
+
@directories = directories
|
9
|
+
@db = EPUB::Search::Database.new(db_file)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
$PROGRAM_NAME = File.basename($PROGRAM_NAME)
|
14
|
+
$stderr.puts 'start to watch:'
|
15
|
+
@directories.each do |dir|
|
16
|
+
$stderr.puts " * #{dir}"
|
17
|
+
end
|
18
|
+
catch_up
|
19
|
+
begin
|
20
|
+
Listen.to *@directories, :filter => EPUB_RE do |modified, added, removed|
|
21
|
+
modified.each do |file_path|
|
22
|
+
next unless file_path =~ EPUB_RE
|
23
|
+
file_path.force_encoding 'UTF-8'
|
24
|
+
begin
|
25
|
+
@db.remove file_path
|
26
|
+
@db.add file_path
|
27
|
+
title = EPUB::Parser.parse(file_path).title
|
28
|
+
notify %Q|UPDATED: #{title}\n#{file_path}|
|
29
|
+
rescue => error
|
30
|
+
$stderr.puts error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
added.each do |file_path|
|
34
|
+
next unless file_path =~ EPUB_RE
|
35
|
+
file_path.force_encoding 'UTF-8'
|
36
|
+
begin
|
37
|
+
@db.add file_path
|
38
|
+
title = EPUB::Parser.parse(file_path).title
|
39
|
+
notify %Q|ADDED: #{title}\n#{file_path}|
|
40
|
+
rescue => error
|
41
|
+
$stderr.puts error
|
42
|
+
end
|
43
|
+
end
|
44
|
+
removed.each do |file_path|
|
45
|
+
next unless file_path =~ EPUB_RE
|
46
|
+
file_path.force_encoding 'UTF-8'
|
47
|
+
begin
|
48
|
+
@db.remove file_path
|
49
|
+
notify %Q|REMOVED:\n#{file_path}|
|
50
|
+
rescue => error
|
51
|
+
$stderr.puts error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
FileUtils.touch exit_time_file
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def exit_time
|
63
|
+
@exittime ||= File.mtime(exit_time_file)
|
64
|
+
end
|
65
|
+
|
66
|
+
def exit_time_file
|
67
|
+
@db.db_dir.join('../exittime').to_path
|
68
|
+
end
|
69
|
+
|
70
|
+
def catch_up
|
71
|
+
@directories.each do |dir|
|
72
|
+
Dir["#{dir}/**/*.epub"].each do |file_path|
|
73
|
+
next if File.file? exit_time_file and File.mtime(file_path) < exit_time
|
74
|
+
begin
|
75
|
+
removed = @db.remove(file_path)
|
76
|
+
@db.add file_path
|
77
|
+
operation = removed.zero? ? 'ADDED' : 'UPDATED'
|
78
|
+
title = EPUB::Parser.parse(file_path).title
|
79
|
+
notify "#{operation}: #{title}\n#{file_path}"
|
80
|
+
rescue => error
|
81
|
+
$stderr.puts error
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def notify(message)
|
88
|
+
$stderr.puts message
|
89
|
+
Notify.notify $PROGRAM_NAME, message
|
90
|
+
end
|
91
|
+
end
|
data/bin/epub-search
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
require 'thor'
|
4
|
+
require 'epub/search'
|
5
|
+
|
6
|
+
Class.new(Thor) {
|
7
|
+
APP_DIR = File.expand_path('../../app', __FILE__)
|
8
|
+
Dir["#{APP_DIR}/*.rb"].each do |path|
|
9
|
+
require path
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def exit_on_failure?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class_option :config, :type => :string, :aliases => '-c', :default => nil, :desc => 'Path to config file'
|
19
|
+
|
20
|
+
method_option :force, :type => :boolean, :aliases => '-f', :default => false, :desc => 'Remove existing database before init'
|
21
|
+
desc 'init [DB_DIR]', 'Setup database'
|
22
|
+
def init(db_dir=config[:db_dir])
|
23
|
+
Init.new(db_dir).run(options[:force])
|
24
|
+
end
|
25
|
+
|
26
|
+
method_option :update, :type => :boolean, :default => true, :desc => 'Remove existing indices of given book before adding'
|
27
|
+
desc 'add FILE', 'Add FILE to database'
|
28
|
+
def add(file)
|
29
|
+
Add.new(config[:db_dir], file).run(options[:update])
|
30
|
+
end
|
31
|
+
|
32
|
+
method_option :color, :type => :string, :aliasis => :colour, :default => 'auto', :desc => 'Color search word in search result. auto, always and never is available. --no-color also is available as never', :banner => 'WHEN'
|
33
|
+
desc 'search WORD [BOOK]', 'Search WORD in book whose title is like BOOK from database'
|
34
|
+
def search(word, book=nil)
|
35
|
+
raise 'Invalid argument for color option' unless [nil, 'auto', 'always', 'never'].include? options[:color]
|
36
|
+
Search.new(config[:db_dir], word, book).run(options[:color])
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'watch [DIRECTORY [DIRECTORY ...]]', 'Index all of EPUB files in DIRECTORY'
|
40
|
+
def watch(*directories)
|
41
|
+
directories = config[:directories] if directories.empty?
|
42
|
+
Watch.new(config[:db_dir], directories).run
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'remove FILE', 'Remove FILE from database'
|
46
|
+
def remove(file)
|
47
|
+
Remove.new(config[:db_dir], file).run
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'server', 'Run server'
|
51
|
+
def server
|
52
|
+
Server.new(config[:db_dir]).run
|
53
|
+
end
|
54
|
+
|
55
|
+
method_option :path, :typ => :boolean, :aliases => '-p', :default => false, :desc => 'Swho book paths'
|
56
|
+
desc 'list', 'Show list of book titles in database'
|
57
|
+
def list
|
58
|
+
List.new(config[:db_dir]).run(options[:path])
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def config
|
64
|
+
EPUB::Search.config(options[:config])
|
65
|
+
end
|
66
|
+
}.start
|
data/epub-search.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'epub/search/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "epub-search"
|
8
|
+
gem.version = EPUB::Search::VERSION
|
9
|
+
gem.authors = ["KITAITI Makoto"]
|
10
|
+
gem.email = ["KitaitiMakoto@gmail.com"]
|
11
|
+
gem.description = %q{Provides tool and library of full text search for EPUB books}
|
12
|
+
gem.summary = %q{Full text search for EPUB books}
|
13
|
+
# gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.has_rdoc = 'yard'
|
20
|
+
|
21
|
+
gem.add_runtime_dependency 'epub-parser'
|
22
|
+
gem.add_runtime_dependency 'rroonga'
|
23
|
+
gem.add_runtime_dependency 'thor'
|
24
|
+
gem.add_runtime_dependency 'rb-inotify'
|
25
|
+
gem.add_runtime_dependency 'listen'
|
26
|
+
gem.add_runtime_dependency 'highline'
|
27
|
+
gem.add_runtime_dependency 'notify'
|
28
|
+
gem.add_runtime_dependency 'rack'
|
29
|
+
gem.add_runtime_dependency 'tilt'
|
30
|
+
|
31
|
+
gem.add_development_dependency 'rake'
|
32
|
+
gem.add_development_dependency 'bundler'
|
33
|
+
gem.add_development_dependency 'test-unit'
|
34
|
+
gem.add_development_dependency 'test-unit-notify'
|
35
|
+
gem.add_development_dependency 'yard'
|
36
|
+
gem.add_development_dependency 'redcarpet'
|
37
|
+
end
|
data/lib/epub/search.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'epub/search/version'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'epub/parser'
|
4
|
+
require 'groonga'
|
5
|
+
require 'listen'
|
6
|
+
require 'highline'
|
7
|
+
|
8
|
+
module EPUB
|
9
|
+
module Search
|
10
|
+
DEFAULT_CONFIG = {
|
11
|
+
:config_path => File.join(Dir.home, '.epub-search/config.yaml'),
|
12
|
+
:db_dir => File.join(Dir.home, '.epub-search/db')
|
13
|
+
}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def config(config_file=nil)
|
17
|
+
return @config if @config
|
18
|
+
config_file = config_file ||
|
19
|
+
(File.file?('.epub-searchrc') ? '.epub-searchrc' : DEFAULT_CONFIG[:config_path])
|
20
|
+
conf = YAML.load_file(config_file) if File.file? config_file
|
21
|
+
@config = DEFAULT_CONFIG.merge(conf || {})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'epub/search/database'
|
28
|
+
require 'epub/search/formatter'
|
29
|
+
require 'epub/search/server'
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module EPUB
|
2
|
+
module Search
|
3
|
+
class Database
|
4
|
+
FILE_NAME = 'epub-search.db'
|
5
|
+
|
6
|
+
attr_reader :db_dir
|
7
|
+
|
8
|
+
def initialize(db_dir)
|
9
|
+
@db_dir = Pathname === db_dir ? db_dir : Pathname.new(db_dir)
|
10
|
+
Groonga::Context.default_options = {:encoding => :utf8}
|
11
|
+
end
|
12
|
+
|
13
|
+
def db_file
|
14
|
+
@db_file ||= @db_dir + FILE_NAME
|
15
|
+
end
|
16
|
+
|
17
|
+
def pages
|
18
|
+
Groonga['Pages']
|
19
|
+
end
|
20
|
+
|
21
|
+
def init(force=false)
|
22
|
+
@db_dir.rmtree if force
|
23
|
+
@db_dir.mkpath
|
24
|
+
Groonga::Database.create :path => db_file.to_path
|
25
|
+
Groonga::Schema.create_table 'Pages', :type => :array
|
26
|
+
Groonga::Schema.change_table 'Pages' do |table|
|
27
|
+
table.text 'location' # file path or URI
|
28
|
+
table.text 'iri' # inner IRI
|
29
|
+
table.text 'book_title'
|
30
|
+
table.text 'page_title'
|
31
|
+
table.text 'metadata'
|
32
|
+
table.text 'content'
|
33
|
+
end
|
34
|
+
Groonga::Schema.create_table 'Terms',
|
35
|
+
:type => :patricia_trie,
|
36
|
+
:normalizer => :NormalizerAuto,
|
37
|
+
:default_tokenizer => 'TokenBigram'
|
38
|
+
Groonga::Schema.change_table 'Terms' do |table|
|
39
|
+
table.index 'Pages.book_title'
|
40
|
+
table.index 'Pages.metadata'
|
41
|
+
table.index 'Pages.content'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [Pathname|String] path path of book
|
46
|
+
# @return [Integer] the number of added recoreds
|
47
|
+
def add(file_path)
|
48
|
+
file_path = Pathname.new(file_path) unless file_path.kind_of? Pathname
|
49
|
+
location = file_path.expand_path
|
50
|
+
book = EPUB::Parser.parse(location)
|
51
|
+
record_count = 0
|
52
|
+
open do
|
53
|
+
book.each_content do |content|
|
54
|
+
next unless content.media_type == 'application/xhtml+xml'
|
55
|
+
doc = Nokogiri.XML(content.read)
|
56
|
+
page_title = doc.search('title').first.text
|
57
|
+
body = Nokogiri.XML(doc.search('body').first.to_xml).content
|
58
|
+
pages.add('location' => location.to_s,
|
59
|
+
'iri' => content.href.to_s,
|
60
|
+
'book_title' => book.title,
|
61
|
+
'page_title' => page_title,
|
62
|
+
'content' => body)
|
63
|
+
record_count += 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
record_count
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Pathname|String] file_path path of book
|
70
|
+
# @return [Integer] the number of removed recoreds
|
71
|
+
def remove(file_path)
|
72
|
+
file_path = Pathname.new(file_path) unless file_path.kind_of? Pathname
|
73
|
+
location = file_path.expand_path.to_path
|
74
|
+
record_count = 0
|
75
|
+
open do
|
76
|
+
records = pages.select {|record|
|
77
|
+
record.location == location
|
78
|
+
}
|
79
|
+
records.each do |record|
|
80
|
+
record.key.delete
|
81
|
+
record_count += 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
record_count
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param [String] word search word
|
88
|
+
# @param [String] book book title
|
89
|
+
# @yieldparam [Hash] result search result.
|
90
|
+
# the key is file path and the value is an array of records
|
91
|
+
def search(word, book=nil)
|
92
|
+
open do
|
93
|
+
result = pages.select {|record|
|
94
|
+
conditions = [record.content =~ word]
|
95
|
+
conditions << (record.book_title =~ book) if book
|
96
|
+
conditions
|
97
|
+
}.group_by(&:location)
|
98
|
+
yield result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def books(path=false)
|
103
|
+
open do
|
104
|
+
pages.group_by(&:location).collect {|location, records|
|
105
|
+
result =records.first.book_title
|
106
|
+
result << " - #{location}" if path
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def open
|
114
|
+
Groonga::Database.open db_file.to_path do |database|
|
115
|
+
yield database
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'epub/search/formatter/cli'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module EPUB
|
2
|
+
module Search
|
3
|
+
module Formatter
|
4
|
+
class CLI
|
5
|
+
def initialize(data, word, highlight=false)
|
6
|
+
@data, @word, @highlight = data, word, highlight
|
7
|
+
end
|
8
|
+
|
9
|
+
def format
|
10
|
+
word_re = /(?<word>#{Regexp.escape(@word)})/io
|
11
|
+
highlighter = HighLine.Style(:red, :bold) if highlight?
|
12
|
+
gray = HighLine.Style(:gray) if highlight?
|
13
|
+
@data.each_pair do |location, records|
|
14
|
+
records.each do |record|
|
15
|
+
record.content.each_line do |line|
|
16
|
+
next unless line =~ word_re
|
17
|
+
result = line.chomp
|
18
|
+
result.gsub! word_re, highlighter.color($~[:word]) if highlight?
|
19
|
+
book_info = "[#{record.page_title}(#{record.book_title}): #{location} - #{record.iri}]"
|
20
|
+
book_info = gray.color(book_info)
|
21
|
+
result << book_info
|
22
|
+
puts result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def highlight?
|
29
|
+
@highlight
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'tilt'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module EPUB
|
6
|
+
module Search
|
7
|
+
class Server
|
8
|
+
include ERB::Util
|
9
|
+
TEMPLATE_DIR = File.join(__dir__, '../../../template')
|
10
|
+
|
11
|
+
# @param [Database] database
|
12
|
+
def initialize(database)
|
13
|
+
@db = database
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
@env = env
|
18
|
+
@request = Rack::Request.new(@env)
|
19
|
+
@response = Rack::Response.new
|
20
|
+
@query = @request['query']
|
21
|
+
if @query
|
22
|
+
@db.search @query do |result|
|
23
|
+
@result = result
|
24
|
+
@search_result = Tilt.new(File.join(TEMPLATE_DIR, 'result.html.erb')).render(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@response.headers['Content-Type'] = 'text/html; charset=UTF-8'
|
28
|
+
@response.body = Tilt.new(File.join(TEMPLATE_DIR, 'index.html.erb')).render(self).each_line
|
29
|
+
@response.finish
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<title>EPUB Search</title>
|
3
|
+
<style type="text/css">
|
4
|
+
em {color: red; font-weight: bold; font-style: normal;}
|
5
|
+
td {border-bottom: solid black thin;}
|
6
|
+
</style>
|
7
|
+
<h1>EPUB Search</h1>
|
8
|
+
<form methdo="get">
|
9
|
+
<input name="query" type="search" value="<%= @query %>">
|
10
|
+
<input type="submit">
|
11
|
+
</form>
|
12
|
+
<%= @search_result %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<table>
|
2
|
+
<tr><th>book</th><th>line</th><th>location</th></tr>
|
3
|
+
<% @result.each_pair do |location, records| %>
|
4
|
+
<% records.each do |record| %>
|
5
|
+
<% record['content'].each_line do |line| %>
|
6
|
+
<% if line =~ /(?<query>#{Regexp.escape(@query)})/io %>
|
7
|
+
<tr>
|
8
|
+
<td><a href="file://<%=h location%>" title="<%=h record['book_title'] %>"><%=h record['book_title'] %></a></td>
|
9
|
+
<td><%=h(line).gsub /(?<query>#{Regexp.escape(@query)})/io, "<em>#{h $~[:query]}</em>" %></td>
|
10
|
+
<td><a href="file://<%=h location %>" title="<%=h record['book_title'] %>"><%=h location %></a></td>
|
11
|
+
</tr>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
16
|
+
</table>
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: epub-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- KITAITI Makoto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: epub-parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rroonga
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rb-inotify
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: listen
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: highline
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: notify
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rack
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: tilt
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rake
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: bundler
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: test-unit
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '>='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: test-unit-notify
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '>='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: yard
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - '>='
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: redcarpet
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - '>='
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - '>='
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
description: Provides tool and library of full text search for EPUB books
|
224
|
+
email:
|
225
|
+
- KitaitiMakoto@gmail.com
|
226
|
+
executables:
|
227
|
+
- epub-search
|
228
|
+
extensions: []
|
229
|
+
extra_rdoc_files: []
|
230
|
+
files:
|
231
|
+
- .gitignore
|
232
|
+
- .yardopts
|
233
|
+
- Gemfile
|
234
|
+
- LICENSE.txt
|
235
|
+
- README.markdown
|
236
|
+
- Rakefile
|
237
|
+
- app/add.rb
|
238
|
+
- app/init.rb
|
239
|
+
- app/list.rb
|
240
|
+
- app/remove.rb
|
241
|
+
- app/search.rb
|
242
|
+
- app/server.rb
|
243
|
+
- app/watch.rb
|
244
|
+
- bin/epub-search
|
245
|
+
- epub-search.gemspec
|
246
|
+
- lib/epub/search.rb
|
247
|
+
- lib/epub/search/database.rb
|
248
|
+
- lib/epub/search/formatter.rb
|
249
|
+
- lib/epub/search/formatter/cli.rb
|
250
|
+
- lib/epub/search/server.rb
|
251
|
+
- lib/epub/search/version.rb
|
252
|
+
- template/index.html.erb
|
253
|
+
- template/result.html.erb
|
254
|
+
- test/helper.rb
|
255
|
+
- test/test_database.rb
|
256
|
+
homepage:
|
257
|
+
licenses: []
|
258
|
+
metadata: {}
|
259
|
+
post_install_message:
|
260
|
+
rdoc_options: []
|
261
|
+
require_paths:
|
262
|
+
- lib
|
263
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
264
|
+
requirements:
|
265
|
+
- - '>='
|
266
|
+
- !ruby/object:Gem::Version
|
267
|
+
version: '0'
|
268
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
269
|
+
requirements:
|
270
|
+
- - '>='
|
271
|
+
- !ruby/object:Gem::Version
|
272
|
+
version: '0'
|
273
|
+
requirements: []
|
274
|
+
rubyforge_project:
|
275
|
+
rubygems_version: 2.0.0
|
276
|
+
signing_key:
|
277
|
+
specification_version: 4
|
278
|
+
summary: Full text search for EPUB books
|
279
|
+
test_files:
|
280
|
+
- test/helper.rb
|
281
|
+
- test/test_database.rb
|
282
|
+
has_rdoc: yard
|