honyomi 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/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/bin/honyomi +5 -0
- data/honyomi.gemspec +33 -0
- data/lib/honyomi.rb +10 -0
- data/lib/honyomi/cli.rb +142 -0
- data/lib/honyomi/core.rb +144 -0
- data/lib/honyomi/database.rb +104 -0
- data/lib/honyomi/pdf.rb +21 -0
- data/lib/honyomi/util.rb +41 -0
- data/lib/honyomi/version.rb +3 -0
- data/lib/honyomi/web/app.rb +107 -0
- data/lib/honyomi/web/config.ru +6 -0
- data/lib/honyomi/web/public/css/bootstrap.min.css +7 -0
- data/lib/honyomi/web/public/css/honyomi.css +47 -0
- data/lib/honyomi/web/views/index.haml +10 -0
- data/lib/honyomi/web/views/layout.haml +33 -0
- data/lib/honyomi/web/views/raw.haml +3 -0
- data/test/minitest_helper.rb +4 -0
- data/test/test.pdf +127 -0
- data/test/test2.pdf +98 -0
- data/test/test_cli.rb +33 -0
- data/test/test_core.rb +165 -0
- data/test/test_database.rb +140 -0
- data/test/test_honyomi.rb +7 -0
- data/test/test_pdf.rb +16 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8b8abf6e1fd0398fd884d5303704b4002f65f09a
|
4
|
+
data.tar.gz: 755277443d0d80ce650ac5276383313c36fa244d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56be1e2715f5a5a79566b3150fe09a827f936bf1bc0d6c03855eb6d72ed43630b5439f6c06f26ced20174373a35a1030ee5b40e0194d80e6fa43987908759e94
|
7
|
+
data.tar.gz: e8553166f084fb3e560854ba0579e69ac01e5294a10c116d5b4014b7b6f4d9e5e55228f00630e186de3006244963d0a9d79e7d13323f14698228978173b9cdc6
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ongaeshi
|
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,29 @@
|
|
1
|
+
# Honyomi
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'honyomi'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install honyomi
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/honyomi
ADDED
data/honyomi.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'honyomi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "honyomi"
|
8
|
+
spec.version = Honyomi::VERSION
|
9
|
+
spec.authors = ["ongaeshi"]
|
10
|
+
spec.email = ["ongaeshi0621@gmail.com"]
|
11
|
+
spec.summary = %q{e-book (pdf) search engine, command line interface, and web application}
|
12
|
+
spec.description = %q{honyomi is a e-book (pdf) search engine. It have command line interface and web application. It will accelerate the e-book of your life.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'grn_mini', "~> 0.5"
|
22
|
+
spec.add_dependency 'haml'
|
23
|
+
spec.add_dependency 'launchy'
|
24
|
+
spec.add_dependency 'rack'
|
25
|
+
spec.add_dependency 'sinatra'
|
26
|
+
spec.add_dependency 'thin', "< 1.7"
|
27
|
+
spec.add_dependency 'thor'
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "minitest"
|
32
|
+
spec.add_development_dependency 'sinatra-reloader'
|
33
|
+
end
|
data/lib/honyomi.rb
ADDED
data/lib/honyomi/cli.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'honyomi'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module Honyomi
|
5
|
+
class CLI < Thor
|
6
|
+
class_option :help, :type => :boolean, :aliases => '-h', :desc => 'Help message'
|
7
|
+
|
8
|
+
desc "init", "Create database in ENV['HONYOMI_DATABASE_DIR'] or '~/.honyomi'"
|
9
|
+
def init
|
10
|
+
begin
|
11
|
+
core = Core.new
|
12
|
+
core.init_database
|
13
|
+
puts "Create database to \"#{core.db_path}\""
|
14
|
+
rescue Groonga::FileExists
|
15
|
+
puts "Database already exists in \"#{core.db_path}\""
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "add file1 [file2 ...]", "Add pdf files"
|
20
|
+
option :title, :aliases => '-t', :type => :string, :desc => 'Specify title'
|
21
|
+
option :strip, :type => :boolean, :desc => 'Remove spaces'
|
22
|
+
def add(*args)
|
23
|
+
core = Core.new
|
24
|
+
core.load_database
|
25
|
+
|
26
|
+
if options[:title] && args.size > 1
|
27
|
+
puts "Arguments is specified more than once, but there is --title option"
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
args.each do |arg|
|
32
|
+
begin
|
33
|
+
book, status = core.add(arg, options)
|
34
|
+
rescue Errno::ENOENT => e
|
35
|
+
puts "Not found 'pdftotext' command (poppler, xpdf)"
|
36
|
+
exit -1
|
37
|
+
end
|
38
|
+
|
39
|
+
unless book
|
40
|
+
puts "Not exist: #{arg}"
|
41
|
+
next
|
42
|
+
end
|
43
|
+
|
44
|
+
case status
|
45
|
+
when :update
|
46
|
+
puts "U #{book.id.to_s} #{book.title} (#{book.page_num} pages)"
|
47
|
+
when :add
|
48
|
+
puts "A #{book.id.to_s} #{book.title} (#{book.page_num} pages)"
|
49
|
+
else
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# desc "update [book_id1 book_id2 ...]", "Update pdf files"
|
56
|
+
# option :all, :type => :boolean, :desc => 'Update all pdf'
|
57
|
+
# def update(*args)
|
58
|
+
# core = Core.new
|
59
|
+
# core.load_database
|
60
|
+
# core.update(args[0], options)
|
61
|
+
# end
|
62
|
+
|
63
|
+
desc "edit book_id [options]", "Edit book info"
|
64
|
+
option :title, :aliases => '-t', :type => :string, :desc => 'Change title'
|
65
|
+
option :path, :type => :string, :desc => 'Change file path'
|
66
|
+
option :strip, :type => :boolean, :desc => 'Remove spaces'
|
67
|
+
option :no_strip, :type => :boolean, :desc => 'Not remove spaces'
|
68
|
+
option :timestamp, :type => :string, :desc => 'Change timestamp'
|
69
|
+
def edit(*args)
|
70
|
+
core = Core.new
|
71
|
+
core.load_database
|
72
|
+
|
73
|
+
book_id = args[0].to_i
|
74
|
+
core.edit(book_id, options)
|
75
|
+
puts core.list([book_id])
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "remove book_id1 [book_id2 ...]", "Remove books"
|
79
|
+
def remove(*args)
|
80
|
+
core = Core.new
|
81
|
+
core.load_database
|
82
|
+
|
83
|
+
args.each do |id|
|
84
|
+
puts core.list([id.to_i])
|
85
|
+
core.remove(id.to_i)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "search query", "Search pages"
|
90
|
+
def search(*args)
|
91
|
+
core = Core.new
|
92
|
+
core.load_database
|
93
|
+
|
94
|
+
results = core.search(args.join(" "))
|
95
|
+
snippet = GrnMini::Util.text_snippet_from_selection_results(results)
|
96
|
+
|
97
|
+
puts "#{results.size} matches"
|
98
|
+
results.map do |page|
|
99
|
+
puts "--- #{page.book.title} (#{page.page_no} page) ---"
|
100
|
+
snippet.execute(page.text).each do |segment|
|
101
|
+
puts segment.gsub("\n", "")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "list [book_id1 book_id2 ...]", "List books"
|
107
|
+
option :title, :aliases => '-t', :type => :string, :desc => 'Filter title'
|
108
|
+
def list(*args)
|
109
|
+
core = Core.new
|
110
|
+
core.load_database
|
111
|
+
|
112
|
+
puts core.list(args.map{|v| v.to_i }, options)
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "web", "Web search interface"
|
116
|
+
option :no_browser, :type => :boolean, :default => false, :aliases => '-n', :type => :boolean, :desc => 'Do not launch browser.'
|
117
|
+
option :host, :default => '127.0.0.1', :aliases => '-o', :desc => 'Listen on HOST.'
|
118
|
+
option :port, :default => 9295, :aliases => '-p', :desc => 'Use PORT.'
|
119
|
+
option :server, :default => 'thin', :aliases => '-s', :desc => 'Use SERVER.'
|
120
|
+
def web
|
121
|
+
core = Core.new
|
122
|
+
core.load_database
|
123
|
+
core.web(options)
|
124
|
+
end
|
125
|
+
|
126
|
+
no_tasks do
|
127
|
+
# Override method for support -h
|
128
|
+
# defined in /lib/thor/invocation.rb
|
129
|
+
def invoke_command(task, *args)
|
130
|
+
if task.name == "help" && args == [[]]
|
131
|
+
print "honyomi #{Honyomi::VERSION}\n\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
if options[:help]
|
135
|
+
CLI.task_help(shell, task.name)
|
136
|
+
else
|
137
|
+
super
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/honyomi/core.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'honyomi'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'launchy'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
module Honyomi
|
7
|
+
class Core
|
8
|
+
attr_reader :database
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@opts = opts
|
12
|
+
end
|
13
|
+
|
14
|
+
def init_database
|
15
|
+
FileUtils.mkdir_p(db_dir)
|
16
|
+
Groonga::Database.create(path: db_path)
|
17
|
+
@database = Database.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_database
|
21
|
+
Groonga::Database.open(db_path)
|
22
|
+
@database = Database.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(filename, options = {})
|
26
|
+
if File.exist?(filename)
|
27
|
+
options = options.dup
|
28
|
+
pages = Pdf.new(filename).pages
|
29
|
+
pages = pages.map { |page| Util.strip_page(page) } if options[:strip]
|
30
|
+
options[:timestamp] = File.stat(filename).mtime
|
31
|
+
@database.add_book(filename, pages, options)
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update(book_id, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def edit(book_id, options)
|
41
|
+
opts = {}
|
42
|
+
opts[:title] = options[:title] if options[:title]
|
43
|
+
opts[:path] = options[:path] if options[:path]
|
44
|
+
opts[:timestamp] = Time.parse(options[:timestamp]) if options[:timestamp]
|
45
|
+
|
46
|
+
if options[:strip] || options[:no_strip]
|
47
|
+
filename = @database.books[book_id].path
|
48
|
+
opts[:pages] = Pdf.new(filename).pages
|
49
|
+
opts[:pages] = opts[:pages].map { |page| Util.strip_page(page) } if options[:strip]
|
50
|
+
end
|
51
|
+
|
52
|
+
@database.change_book(book_id, opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove(book_id)
|
56
|
+
@database.delete_book(book_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def search(query)
|
60
|
+
@database.search(query)
|
61
|
+
end
|
62
|
+
|
63
|
+
def list(args = [], options = {})
|
64
|
+
books = @database.books
|
65
|
+
books = books.select("title:@#{options[:title]}").map {|record| record.key} if options[:title]
|
66
|
+
|
67
|
+
if args.empty?
|
68
|
+
id_length = books.max { |book| book.id.to_s.length }
|
69
|
+
id_length = id_length ? id_length.id.to_s.length : 0
|
70
|
+
|
71
|
+
books.map do |book|
|
72
|
+
# "#{book.id} #{book.title} (#{book.page_num} pages) #{book.path}"
|
73
|
+
"#{book.id.to_s.rjust(id_length)} #{book.title} (#{book.page_num} pages)"
|
74
|
+
end
|
75
|
+
else
|
76
|
+
results = []
|
77
|
+
|
78
|
+
books.each do |book|
|
79
|
+
if args.include?(book.id)
|
80
|
+
results << <<EOF
|
81
|
+
id: #{book.id.to_s}
|
82
|
+
title: #{book.title}
|
83
|
+
path: #{book.path}
|
84
|
+
pages: #{book.page_num}
|
85
|
+
timestamp: #{book.timestamp}
|
86
|
+
|
87
|
+
EOF
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
results
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def web(options)
|
96
|
+
rack_options = {
|
97
|
+
:environment => ENV['RACK_ENV'] || "development",
|
98
|
+
:pid => nil,
|
99
|
+
:Port => options[:port],
|
100
|
+
:Host => options[:host],
|
101
|
+
:AccessLog => [],
|
102
|
+
:config => "config.ru",
|
103
|
+
# ----------------------------
|
104
|
+
:server => options[:server],
|
105
|
+
}
|
106
|
+
|
107
|
+
# Move to the location of the server script
|
108
|
+
FileUtils.cd(File.join(File.dirname(__FILE__), 'web'))
|
109
|
+
|
110
|
+
# Create Rack Server
|
111
|
+
rack_server = Rack::Server.new(rack_options)
|
112
|
+
|
113
|
+
# Start Rack
|
114
|
+
rack_server.start do
|
115
|
+
unless options[:no_browser]
|
116
|
+
Launchy.open("http://#{options[:host]}:#{options[:port]}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def db_dir
|
122
|
+
File.join(home_dir, 'db')
|
123
|
+
end
|
124
|
+
|
125
|
+
def db_path
|
126
|
+
File.join(db_dir, 'honyomi.db')
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def home_dir
|
132
|
+
unless @home_dir
|
133
|
+
@home_dir = @opts[:home_dir] || ENV['HONYOMI_DATABASE_DIR'] || File.join(default_home, '.honyomi')
|
134
|
+
FileUtils.mkdir_p(@home_dir) unless File.exist?(@home_dir)
|
135
|
+
end
|
136
|
+
|
137
|
+
@home_dir
|
138
|
+
end
|
139
|
+
|
140
|
+
def default_home
|
141
|
+
File.expand_path '~'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'honyomi'
|
2
|
+
require 'grn_mini'
|
3
|
+
|
4
|
+
module Honyomi
|
5
|
+
class Database
|
6
|
+
attr_reader :books
|
7
|
+
attr_reader :pages
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@books = GrnMini::Array.new("Books")
|
11
|
+
@pages = GrnMini::Hash.new("Pages")
|
12
|
+
|
13
|
+
@books.setup_columns(path: "",
|
14
|
+
title: "",
|
15
|
+
author: "",
|
16
|
+
page_num: 0,
|
17
|
+
timestamp: Time.new,
|
18
|
+
)
|
19
|
+
@pages.setup_columns(book: @books,
|
20
|
+
text: "",
|
21
|
+
page_no: 0,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_book(path, pages, options = {})
|
26
|
+
book = book_from_path(path)
|
27
|
+
|
28
|
+
if book
|
29
|
+
# Already exist
|
30
|
+
opts = options.dup
|
31
|
+
opts[:pages] = pages
|
32
|
+
change_book(book.id, opts)
|
33
|
+
return book, :update
|
34
|
+
|
35
|
+
else
|
36
|
+
# New book
|
37
|
+
path = Util.filename_to_utf8(path)
|
38
|
+
title = options[:title] || File.basename(path, File.extname(path))
|
39
|
+
timestamp = options[:timestamp] || Time.now
|
40
|
+
|
41
|
+
book = @books.add(path: path,
|
42
|
+
title: title,
|
43
|
+
page_num: pages.size,
|
44
|
+
timestamp: timestamp,
|
45
|
+
)
|
46
|
+
|
47
|
+
pages.each_with_index do |page, index|
|
48
|
+
@pages["#{book.id}:#{index+1}"] = { book: book, text: page, page_no: index+1 }
|
49
|
+
end
|
50
|
+
|
51
|
+
return book, :add
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def change_book(book_id, options = {})
|
56
|
+
book = @books[book_id]
|
57
|
+
|
58
|
+
book.title = options[:title] if options[:title]
|
59
|
+
book.path = options[:path] if options[:path]
|
60
|
+
book.timestamp = options[:timestamp] if options[:timestamp]
|
61
|
+
|
62
|
+
if options[:pages]
|
63
|
+
pages = options[:pages]
|
64
|
+
book.page_num = pages.size
|
65
|
+
|
66
|
+
@pages.delete do |page|
|
67
|
+
page.book == book
|
68
|
+
end
|
69
|
+
|
70
|
+
pages.each_with_index do |page, index|
|
71
|
+
@pages["#{book.id}:#{index+1}"] = { book: book, text: page, page_no: index+1 }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_book(book_id)
|
77
|
+
book = @books[book_id]
|
78
|
+
|
79
|
+
@pages.delete do |page|
|
80
|
+
page.book == book
|
81
|
+
end
|
82
|
+
|
83
|
+
book.delete
|
84
|
+
end
|
85
|
+
|
86
|
+
def search(query)
|
87
|
+
@pages.select(query, default_column: "text")
|
88
|
+
end
|
89
|
+
|
90
|
+
def book_pages(book_id)
|
91
|
+
@pages.select("book._id:\"#{book_id}\"").sort(["page_no"])
|
92
|
+
end
|
93
|
+
|
94
|
+
def book_from_path(path)
|
95
|
+
r = @books.select("path:\"#{path}\"").first
|
96
|
+
|
97
|
+
if r
|
98
|
+
r.key
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|