lknovel 0.0.1 → 0.0.2
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.
- data/CHANGELOG +2 -0
- data/README.md +29 -1
- data/lib/lknovel/app.rb +31 -47
- data/lib/lknovel/chapter.rb +4 -16
- data/lib/lknovel/meta.rb +1 -1
- data/lib/lknovel/templates/chapter.html.erb +3 -3
- data/lib/lknovel/templates/front.html.erb +3 -3
- data/lib/lknovel/utils.rb +43 -0
- data/lib/lknovel/volume.rb +8 -30
- data/test/test_lknovel_chapter.rb +2 -1
- data/test/test_lknovel_volume.rb +5 -1
- metadata +2 -2
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -16,9 +16,37 @@ And then execute:
|
|
16
16
|
|
17
17
|
$ bundle
|
18
18
|
|
19
|
+
## Dependencies
|
20
|
+
|
21
|
+
* Ruby 1.9
|
22
|
+
* [nokogiri](http://nokogiri.org/)
|
23
|
+
* [gepub](https://github.com/skoji/gepub)
|
24
|
+
* [parallel](https://github.com/grosser/parallel)
|
25
|
+
|
26
|
+
Rubygems will solve it for you.
|
27
|
+
|
28
|
+
If you encounter compiling/installing problem about nokogiri, install it
|
29
|
+
manually using the package manager. For Debian/Ubuntu:
|
30
|
+
|
31
|
+
# apt-get install ruby-nokogiri
|
32
|
+
|
19
33
|
## Usage
|
20
34
|
|
21
|
-
$ lknovel
|
35
|
+
$ lknovel -h
|
36
|
+
Usage: lknovel [options] url
|
37
|
+
|
38
|
+
Specific options:
|
39
|
+
-k, --[no-]keep Keep temporary files
|
40
|
+
-q, --[no-]quiet Run quietly
|
41
|
+
|
42
|
+
Common options:
|
43
|
+
-h, --help Show this message
|
44
|
+
-V, --version Show version
|
45
|
+
|
46
|
+
$ lknovel http://lknovel.lightnovel.cn/main/book/2123.html
|
47
|
+
Chapters: 5/5 第3章 魔王和勇者,在笹塚相会。
|
48
|
+
Images: 13/13 20120817193746_95414.jpg
|
49
|
+
Finish: 打工吧!魔王大人 - 第01卷 - 第1卷.epub
|
22
50
|
|
23
51
|
## Contributing
|
24
52
|
|
data/lib/lknovel/app.rb
CHANGED
@@ -22,8 +22,8 @@ module Lknovel
|
|
22
22
|
options.keep = k
|
23
23
|
end
|
24
24
|
|
25
|
-
opts.on('-
|
26
|
-
options.verbose =
|
25
|
+
opts.on('-q', '--[no-]quiet', 'Run quietly') do |q|
|
26
|
+
options.verbose = !q
|
27
27
|
end
|
28
28
|
|
29
29
|
opts.separator "\nCommon options:"
|
@@ -51,11 +51,6 @@ module Lknovel
|
|
51
51
|
|
52
52
|
def initialize(args)
|
53
53
|
@options = self.class.parse(args)
|
54
|
-
begin
|
55
|
-
@console_width = `tput cols`.to_i
|
56
|
-
rescue Exception
|
57
|
-
@console_width = 80
|
58
|
-
end
|
59
54
|
end
|
60
55
|
|
61
56
|
def run
|
@@ -65,60 +60,49 @@ module Lknovel
|
|
65
60
|
end
|
66
61
|
|
67
62
|
def process_volume(url)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
STDERR.write "\rParsing chapters: #{chapter.title}"
|
75
|
-
end
|
76
|
-
end
|
77
|
-
if @options.verbose
|
78
|
-
puts
|
63
|
+
volume = Volume.new(url)
|
64
|
+
|
65
|
+
parallel_verbose(volume.chapters, title: 'Chapters',
|
66
|
+
verbose: @options.verbose) do |chapter|
|
67
|
+
chapter.parse
|
68
|
+
chapter.title
|
79
69
|
end
|
80
70
|
|
81
71
|
FileUtils.mkdir_p(volume.path)
|
82
72
|
Dir.chdir(volume.path) do
|
73
|
+
# generate html
|
74
|
+
FileUtils.mkdir_p(HTML_DIR)
|
75
|
+
Dir.chdir(HTML_DIR) do
|
76
|
+
volume.render(File.join(TEMPLATE_PATH, 'front.html.erb'), 'front.html')
|
77
|
+
erb = File.read(File.join(TEMPLATE_PATH, 'chapter.html.erb'))
|
78
|
+
template = ERB.new(erb, nil, '-')
|
79
|
+
volume.chapters.each_with_index do |chapter, index|
|
80
|
+
chapter.render(template, HTML_FILE_FORMAT % index)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# download images
|
83
85
|
FileUtils.mkdir_p(IMAGE_DIR)
|
84
86
|
Dir.chdir(IMAGE_DIR) do
|
85
87
|
images = []
|
86
88
|
volume.chapters.each do |chapter|
|
87
89
|
chapter.content.each do |content|
|
88
|
-
images
|
90
|
+
images << content if content.is_a?(Image)
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
end
|
95
|
-
STDERR.write "\rDownload images: 0/#{images.length}\t..."
|
96
|
-
Parallel.each(images, :in_threads => 5) do |image|
|
94
|
+
parallel_verbose(images, title: 'Images',
|
95
|
+
verbose: @options.verbose) do |image|
|
97
96
|
image.download
|
98
|
-
|
99
|
-
STDERR.write "\r#{' ' * @console_width}"
|
100
|
-
STDERR.write \
|
101
|
-
"\rDownload images: #{progress}/#{images.length}\t#{image.file}"
|
102
|
-
progress = progress + 1
|
103
|
-
end
|
104
|
-
end
|
105
|
-
if @options.verbose
|
106
|
-
puts
|
97
|
+
image.file
|
107
98
|
end
|
108
99
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
FileUtils.mkdir_p(HTML_DIR)
|
116
|
-
Dir.chdir(HTML_DIR) do
|
117
|
-
volume.html(File.join(TEMPLATE_PATH, 'front.html.erb'), 'front.html')
|
118
|
-
erb = File.read(File.join(TEMPLATE_PATH, 'chapter.html.erb'))
|
119
|
-
template = ERB.new(erb, nil, '-')
|
120
|
-
volume.chapters.each_with_index do |chapter, index|
|
121
|
-
chapter.html(template, HTML_FILE_FORMAT % index)
|
100
|
+
if images.size > 0
|
101
|
+
cover_image = images[0]
|
102
|
+
# crop cover image if width > height * 1.4
|
103
|
+
cropped = cover_image.crop('cover.jpg',
|
104
|
+
'52%x100%+0+0') { |w, h| w > h * 1.4 }
|
105
|
+
volume.cover_image = cropped ? 'cover.jpg' : cover_image.file
|
122
106
|
end
|
123
107
|
end
|
124
108
|
end
|
@@ -136,7 +120,7 @@ module Lknovel
|
|
136
120
|
contributor volume.illustrator, 'ill'
|
137
121
|
|
138
122
|
resources(:workdir => volume.path) {
|
139
|
-
cover_image File.join(IMAGE_DIR, volume.
|
123
|
+
cover_image File.join(IMAGE_DIR, volume.cover_image)
|
140
124
|
file \
|
141
125
|
"#{STYLESHEET_DIR}/default.css" => "#{STYLESHEET_PATH}/default.css"
|
142
126
|
glob "#{IMAGE_DIR}/*"
|
data/lib/lknovel/chapter.rb
CHANGED
@@ -9,12 +9,14 @@ require 'open-uri'
|
|
9
9
|
|
10
10
|
module Lknovel
|
11
11
|
class Chapter
|
12
|
+
include ERBRender
|
12
13
|
|
13
14
|
attr_reader :url, :title, :content
|
14
15
|
|
15
|
-
def initialize(url)
|
16
|
+
def initialize(url, options = {})
|
17
|
+
@options = {:title => nil}.merge(options)
|
16
18
|
@url = url
|
17
|
-
|
19
|
+
@title = @options[:title]
|
18
20
|
end
|
19
21
|
|
20
22
|
def parse
|
@@ -39,19 +41,5 @@ module Lknovel
|
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
def html(erb, path = nil)
|
43
|
-
template = erb.is_a?(ERB) ? erb : ERB.new(File.read(erb), nil, '-')
|
44
|
-
html_content = template.result(binding)
|
45
|
-
if path.nil?
|
46
|
-
html_content
|
47
|
-
else
|
48
|
-
if !File.exists?(path)
|
49
|
-
File.open(path, 'w') do |file|
|
50
|
-
file.puts html_content
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
44
|
end
|
57
45
|
end
|
data/lib/lknovel/meta.rb
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
4
4
|
<head>
|
5
5
|
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
|
6
|
-
<link href="<%= "../#{STYLESHEET_DIR}/default.css" %>" rel="stylesheet" type="text/css"/>
|
6
|
+
<link href="<%= "../#{Lknovel::STYLESHEET_DIR}/default.css" %>" rel="stylesheet" type="text/css"/>
|
7
7
|
<title><%= @title %></title>
|
8
8
|
</head>
|
9
9
|
<body>
|
10
10
|
<h1><%= @title %></h1>
|
11
11
|
<% @content.each do |item| -%>
|
12
|
-
<% if item.is_a?(Image) -%>
|
13
|
-
<img src="<%= "../#{IMAGE_DIR}/#{item.file}" %>">
|
12
|
+
<% if item.is_a?(Lknovel::Image) -%>
|
13
|
+
<img src="<%= "../#{Lknovel::IMAGE_DIR}/#{item.file}" %>">
|
14
14
|
<% else -%>
|
15
15
|
<p><%= item %></p>
|
16
16
|
<% end -%>
|
@@ -2,16 +2,16 @@
|
|
2
2
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
|
3
3
|
<head>
|
4
4
|
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
|
5
|
-
<link href="<%= "../#{STYLESHEET_DIR}/default.css" %>" rel="stylesheet" type="text/css"/>
|
5
|
+
<link href="<%= "../#{Lknovel::STYLESHEET_DIR}/default.css" %>" rel="stylesheet" type="text/css"/>
|
6
6
|
</head>
|
7
7
|
<body>
|
8
|
-
<img class="page-break" src="<%= "../#{IMAGE_DIR}/#{@
|
8
|
+
<img class="page-break" src="<%= "../#{Lknovel::IMAGE_DIR}/#{@cover_image}" %>">
|
9
9
|
<h1>简介</h1>
|
10
10
|
<p><%= @intro %></p>
|
11
11
|
<nav epub:type="toc" id="toc">
|
12
12
|
<ul class="nav">
|
13
13
|
<% @chapters.each_with_index do |chapter, index| -%>
|
14
|
-
<li><a href="<%= HTML_FILE_FORMAT % index %>"><%= chapter.title %></a></li>
|
14
|
+
<li><a href="<%= Lknovel::HTML_FILE_FORMAT % index %>"><%= chapter.title %></a></li>
|
15
15
|
<% end -%>
|
16
16
|
</ul>
|
17
17
|
</nav>
|
data/lib/lknovel/utils.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'parallel'
|
3
|
+
|
1
4
|
# http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/
|
2
5
|
# Options:
|
3
6
|
# * :tries - Number of retries to perform. Defaults to 3.
|
@@ -22,3 +25,43 @@ def retryable(options = {}, &block)
|
|
22
25
|
|
23
26
|
yield
|
24
27
|
end
|
28
|
+
|
29
|
+
def parallel_verbose(items, options = {}, &block)
|
30
|
+
opts = {:title => 'Working', :verbose => true, :threads => 5}.merge(options)
|
31
|
+
if opts[:verbose]
|
32
|
+
progress = 1
|
33
|
+
msg = "\r#{opts[:title]}: 0/#{items.size} ..."
|
34
|
+
STDERR.write msg
|
35
|
+
end
|
36
|
+
Parallel.each_with_index(items, :in_threads => 5) do |item, index|
|
37
|
+
extra_msg = block.call(item, index)
|
38
|
+
if opts[:verbose]
|
39
|
+
# get previous console output length, aware of wide chars
|
40
|
+
size =
|
41
|
+
msg.gsub(/\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/, ' ').size - 1
|
42
|
+
STDERR.write "\r#{' ' * size}"
|
43
|
+
msg = "\r#{opts[:title]}: #{progress}/#{items.size} #{extra_msg}"
|
44
|
+
STDERR.write msg
|
45
|
+
progress = progress + 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if opts[:verbose]
|
49
|
+
puts
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module ERBRender
|
54
|
+
def render(erb, path = nil)
|
55
|
+
template = erb.is_a?(ERB) ? erb : ERB.new(File.read(erb), nil, '-')
|
56
|
+
html_content = template.result(binding)
|
57
|
+
if path.nil?
|
58
|
+
html_content
|
59
|
+
else
|
60
|
+
if !File.exists?(path)
|
61
|
+
File.open(path, 'w') do |file|
|
62
|
+
file.puts html_content
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/lknovel/volume.rb
CHANGED
@@ -4,23 +4,22 @@ require 'lknovel/meta'
|
|
4
4
|
require 'lknovel/utils'
|
5
5
|
require 'nokogiri'
|
6
6
|
require 'open-uri'
|
7
|
-
require 'parallel'
|
8
7
|
|
9
8
|
module Lknovel
|
10
9
|
class Volume
|
10
|
+
include ERBRender
|
11
11
|
|
12
12
|
attr_reader :url, :series, :author, :title, :number_s, :number, :date,
|
13
|
-
:illustrator, :publisher, :intro, :chapters, :path
|
13
|
+
:illustrator, :publisher, :intro, :chapters, :path
|
14
14
|
|
15
|
-
attr_accessor :
|
15
|
+
attr_accessor :cover_image
|
16
16
|
|
17
|
-
def initialize(url
|
17
|
+
def initialize(url)
|
18
18
|
@url = url
|
19
|
-
|
20
|
-
parse(&block)
|
19
|
+
parse
|
21
20
|
end
|
22
21
|
|
23
|
-
def parse
|
22
|
+
def parse
|
24
23
|
page = retryable do
|
25
24
|
Nokogiri::HTML(open(@url))
|
26
25
|
end
|
@@ -51,31 +50,10 @@ module Lknovel
|
|
51
50
|
|
52
51
|
@path = "#{@series} - #{@number_s} - #{@title}"
|
53
52
|
|
54
|
-
@chapters =
|
55
|
-
page.css('ul.lk-chapter-list li.span3'),
|
56
|
-
:in_threads => @threads) do |x|
|
53
|
+
@chapters = page.css('ul.lk-chapter-list li.span3').map do |x|
|
57
54
|
chapter_title = x.text.strip.sub(/\s+/, ' ')
|
58
55
|
chapter_url = URI.join(url, x.css('a')[0]['href']).to_s
|
59
|
-
|
60
|
-
block.call(chapter)
|
61
|
-
chapter
|
62
|
-
end
|
63
|
-
|
64
|
-
@cover_image = @chapters[0].content.find { |x| x.is_a?(Lknovel::Image) }
|
65
|
-
@cover_image_cropped = @cover_image.file
|
66
|
-
end
|
67
|
-
|
68
|
-
def html(erb, path = nil)
|
69
|
-
template = erb.is_a?(ERB) ? erb : ERB.new(File.read(erb), nil, '-')
|
70
|
-
html_content = template.result(binding)
|
71
|
-
if path.nil?
|
72
|
-
html_content
|
73
|
-
else
|
74
|
-
if !File.exists?(path)
|
75
|
-
File.open(path, 'w') do |file|
|
76
|
-
file.puts html_content
|
77
|
-
end
|
78
|
-
end
|
56
|
+
Chapter.new(chapter_url, title: chapter_title)
|
79
57
|
end
|
80
58
|
end
|
81
59
|
|
@@ -5,6 +5,7 @@ require 'lknovel/chapter'
|
|
5
5
|
describe Lknovel::Chapter do
|
6
6
|
|
7
7
|
chapter = Lknovel::Chapter.new('http://lknovel.lightnovel.cn/main/view/1486.html')
|
8
|
+
chapter.parse
|
8
9
|
|
9
10
|
it 'get chapter title' do
|
10
11
|
chapter.title.must_equal '第0章 序章'
|
@@ -19,7 +20,7 @@ describe Lknovel::Chapter do
|
|
19
20
|
end
|
20
21
|
|
21
22
|
it 'generate html' do
|
22
|
-
html = chapter.
|
23
|
+
html = chapter.render(File.join(Lknovel::TEMPLATE_PATH, 'chapter.html.erb'))
|
23
24
|
html.must_match '"../stylesheets/default.css"'
|
24
25
|
html.must_match '"../images/20120808202639_66681.jpg"'
|
25
26
|
end
|
data/test/test_lknovel_volume.rb
CHANGED
@@ -5,6 +5,10 @@ require 'lknovel/volume'
|
|
5
5
|
describe Lknovel::Volume do
|
6
6
|
|
7
7
|
volume = Lknovel::Volume.new('http://lknovel.lightnovel.cn/main/book/2136.html')
|
8
|
+
volume.parse
|
9
|
+
volume.chapters[0].parse
|
10
|
+
cover_image = volume.chapters[0].content.find { |x| x.is_a?(Lknovel::Image) }
|
11
|
+
volume.cover_image = cover_image
|
8
12
|
|
9
13
|
it 'get path' do
|
10
14
|
volume.path.must_equal '打工吧!魔王大人 - 短篇01 - 短篇01'
|
@@ -35,7 +39,7 @@ describe Lknovel::Volume do
|
|
35
39
|
end
|
36
40
|
|
37
41
|
it 'generate front html' do
|
38
|
-
html = volume.
|
42
|
+
html = volume.render(File.join(Lknovel::TEMPLATE_PATH, 'front.html.erb'))
|
39
43
|
html.must_match '<li><a href="000.html">第0章 序章</a></li>'
|
40
44
|
end
|
41
45
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lknovel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|