alexandria-book-collection-manager 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.hound.yml +2 -0
- data/.rubocop.yml +4 -4
- data/.rubocop_todo.yml +55 -82
- data/CHANGELOG.md +17 -0
- data/INSTALL.md +3 -3
- data/README.md +1 -2
- data/Rakefile +14 -33
- data/TODO.md +9 -1
- data/alexandria-book-collection-manager.gemspec +4 -3
- data/bin/alexandria +21 -34
- data/doc/FAQ +2 -5
- data/lib/alexandria.rb +4 -16
- data/lib/alexandria/book_providers.rb +5 -7
- data/lib/alexandria/book_providers/adlibris.rb +5 -3
- data/lib/alexandria/book_providers/amazon_aws.rb +3 -1
- data/lib/alexandria/book_providers/amazon_ecs_util.rb +8 -0
- data/lib/alexandria/book_providers/barnes_and_noble.rb +7 -4
- data/lib/alexandria/book_providers/douban.rb +1 -1
- data/lib/alexandria/book_providers/proxis.rb +11 -27
- data/lib/alexandria/book_providers/renaud.rb +9 -3
- data/lib/alexandria/book_providers/siciliano.rb +7 -4
- data/lib/alexandria/book_providers/thalia.rb +5 -3
- data/lib/alexandria/book_providers/web.rb +11 -29
- data/lib/alexandria/book_providers/worldcat.rb +7 -5
- data/lib/alexandria/book_providers/z3950.rb +10 -7
- data/lib/alexandria/console.rb +5 -18
- data/lib/alexandria/execution_queue.rb +2 -1
- data/lib/alexandria/export_format.rb +47 -0
- data/lib/alexandria/export_library.rb +72 -180
- data/lib/alexandria/import_library.rb +14 -23
- data/lib/alexandria/import_library_csv.rb +3 -6
- data/lib/alexandria/library_collection.rb +78 -0
- data/lib/alexandria/library_sort_order.rb +43 -0
- data/lib/alexandria/library_store.rb +222 -0
- data/lib/alexandria/logging.rb +2 -0
- data/lib/alexandria/models/book.rb +8 -16
- data/lib/alexandria/models/library.rb +26 -308
- data/lib/alexandria/preferences.rb +7 -24
- data/lib/alexandria/scanners/cuecat.rb +3 -1
- data/lib/alexandria/smart_library.rb +32 -67
- data/lib/alexandria/ui/builder_base.rb +6 -26
- data/lib/alexandria/ui/callbacks.rb +8 -34
- data/lib/alexandria/ui/completion_models.rb +2 -1
- data/lib/alexandria/ui/dialogs/about_dialog.rb +35 -47
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +14 -30
- data/lib/alexandria/ui/dialogs/alert_dialog.rb +8 -17
- data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +10 -24
- data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +17 -18
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +25 -41
- data/lib/alexandria/ui/dialogs/export_dialog.rb +48 -56
- data/lib/alexandria/ui/dialogs/import_dialog.rb +31 -51
- data/lib/alexandria/ui/dialogs/keep_bad_isbn_dialog.rb +33 -0
- data/lib/alexandria/ui/dialogs/misc_dialogs.rb +12 -25
- data/lib/alexandria/ui/dialogs/new_book_dialog.rb +57 -94
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +24 -42
- data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +9 -21
- data/lib/alexandria/ui/dialogs/preferences_dialog.rb +27 -32
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +5 -3
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +15 -12
- data/lib/alexandria/ui/icons.rb +11 -22
- data/lib/alexandria/ui/init.rb +3 -3
- data/lib/alexandria/ui/libraries_combo.rb +1 -0
- data/lib/alexandria/ui/listview.rb +5 -21
- data/lib/alexandria/ui/multi_drag_treeview.rb +20 -32
- data/lib/alexandria/ui/sidepane.rb +9 -24
- data/lib/alexandria/ui/ui_manager.rb +36 -60
- data/lib/alexandria/undo_manager.rb +1 -0
- data/lib/alexandria/version.rb +2 -2
- data/lib/alexandria/web_themes.rb +1 -0
- data/po/cs.po +0 -4
- data/po/cy.po +0 -4
- data/po/de.po +0 -4
- data/po/el.po +0 -4
- data/po/es.po +0 -4
- data/po/fr.po +0 -4
- data/po/ga.po +0 -4
- data/po/gl.po +0 -4
- data/po/it.po +0 -4
- data/po/ja.po +0 -4
- data/po/mk.po +0 -4
- data/po/nb.po +0 -4
- data/po/nl.po +0 -4
- data/po/pl.po +0 -4
- data/po/pt.po +0 -4
- data/po/pt_BR.po +0 -4
- data/po/ru.po +0 -4
- data/po/sk.po +0 -4
- data/po/sv.po +0 -4
- data/po/uk.po +0 -4
- data/po/zh_TW.po +0 -4
- data/schemas/alexandria.schemas +1 -1
- data/share/alexandria/glade/acquire_dialog__builder.glade +14 -11
- data/share/alexandria/glade/book_properties_dialog__builder.glade +170 -298
- data/share/alexandria/glade/main_app__builder.glade +22 -16
- data/share/alexandria/glade/new_book_dialog__builder.glade +26 -58
- data/share/alexandria/glade/preferences_dialog__builder.glade +249 -289
- data/share/gnome/help/alexandria/C/introduction.xml +0 -4
- data/share/gnome/help/alexandria/ja/introduction.xml +0 -4
- data/spec/alexandria/book_providers_spec.rb +1 -20
- data/spec/alexandria/console_spec.rb +32 -0
- data/spec/alexandria/export_library_spec.rb +141 -0
- data/spec/alexandria/library_spec.rb +24 -80
- data/spec/alexandria/library_store_spec.rb +37 -0
- data/spec/alexandria/smart_library_spec.rb +27 -22
- data/spec/alexandria/ui/dialogs/new_book_dialog_spec.rb +22 -0
- data/spec/alexandria/ui/dialogs_spec.rb +104 -38
- data/spec/end_to_end/basic_run_spec.rb +69 -0
- data/spec/spec_helper.rb +13 -25
- data/tasks/spec.rake +15 -2
- data/util/rake/fileinstall.rb +1 -0
- metadata +38 -16
- data/lib/alexandria/book_providers/deastore.rb +0 -265
- data/lib/alexandria/book_providers/mcu.rb +0 -182
@@ -74,7 +74,7 @@ module Alexandria
|
|
74
74
|
end
|
75
75
|
return results
|
76
76
|
end
|
77
|
-
rescue
|
77
|
+
rescue StandardError
|
78
78
|
raise NoResultsError
|
79
79
|
end
|
80
80
|
end
|
@@ -87,9 +87,10 @@ module Alexandria
|
|
87
87
|
private
|
88
88
|
|
89
89
|
NO_BOOKS_FOUND_REGEXP =
|
90
|
-
/<strong class="Promotion">Aucun article trouv. selon les crit.res demand.s<\/strong
|
90
|
+
/<strong class="Promotion">Aucun article trouv. selon les crit.res demand.s<\/strong>/.freeze
|
91
91
|
HYPERLINK_SCAN_REGEXP =
|
92
|
-
/"(Jeune|Lire)Hyperlien" href.*><strong>([-,'\(\)&\#;\w\s#{ACCENTUATED_CHARS}]*)<\/strong><\/a><br
|
92
|
+
/"(Jeune|Lire)Hyperlien" href.*><strong>([-,'\(\)&\#;\w\s#{ACCENTUATED_CHARS}]*)<\/strong><\/a><br>/.
|
93
|
+
freeze
|
93
94
|
|
94
95
|
def to_books(data)
|
95
96
|
data = CGI.unescapeHTML(data)
|
@@ -101,6 +102,7 @@ module Alexandria
|
|
101
102
|
titles << md[1].strip
|
102
103
|
}
|
103
104
|
raise if titles.empty?
|
105
|
+
|
104
106
|
authors = []
|
105
107
|
data.scan(/Nom_Auteur.*><i>([,'.&\#;\w\s#{ACCENTUATED_CHARS}]*)<\/i>/).each { |md|
|
106
108
|
authors2 = []
|
@@ -110,11 +112,13 @@ module Alexandria
|
|
110
112
|
authors << authors2
|
111
113
|
}
|
112
114
|
raise if authors.empty?
|
115
|
+
|
113
116
|
isbns = []
|
114
117
|
data.scan(/ISBN : ?<\/td><td>(\d+)/).each { |md|
|
115
118
|
isbns << md[0].strip
|
116
119
|
}
|
117
120
|
raise if isbns.empty?
|
121
|
+
|
118
122
|
editions = []
|
119
123
|
publish_years = []
|
120
124
|
data.scan(/Parution : <br>(\d{4,}-\d{2,}-\d{2,})/).each { |md|
|
@@ -122,11 +126,13 @@ module Alexandria
|
|
122
126
|
publish_years << md[0].strip.split(/-/)[0].to_i
|
123
127
|
}
|
124
128
|
raise if editions.empty? || publish_years.empty?
|
129
|
+
|
125
130
|
publishers = []
|
126
131
|
data.scan(/diteur : ([,'.&\#;\w\s#{ACCENTUATED_CHARS}]*)<\/span><br>/).each { |md|
|
127
132
|
publishers << md[0].strip
|
128
133
|
}
|
129
134
|
raise if publishers.empty?
|
135
|
+
|
130
136
|
book_covers = []
|
131
137
|
data.scan(/(\/ImagesEditeurs\/[\d]*\/([\dX]*-f.(jpg|gif))
|
132
138
|
|\/francais\/suggestion\/images\/livre\/livre.gif)/x).each { |md|
|
@@ -90,8 +90,8 @@ module Alexandria
|
|
90
90
|
private
|
91
91
|
|
92
92
|
def create_search_uri(search_type, search_term, trying_again = false)
|
93
|
-
(search_type_code = { SEARCH_BY_ISBN
|
94
|
-
SEARCH_BY_TITLE
|
93
|
+
(search_type_code = { SEARCH_BY_ISBN => 'G',
|
94
|
+
SEARCH_BY_TITLE => 'A',
|
95
95
|
SEARCH_BY_AUTHORS => 'B',
|
96
96
|
SEARCH_BY_KEYWORD => 'X' }[search_type]) || 'X'
|
97
97
|
search_term_encoded = if search_type == SEARCH_BY_ISBN
|
@@ -148,7 +148,7 @@ module Alexandria
|
|
148
148
|
result[:url] = "#{SITE}#{slash}#{link_to_description}"
|
149
149
|
|
150
150
|
book_search_results << result
|
151
|
-
rescue => ex
|
151
|
+
rescue StandardError => ex
|
152
152
|
trace = ex.backtrace.join("\n> ")
|
153
153
|
log.error { "Failed parsing Siciliano search page #{ex.message}\n#{trace}" }
|
154
154
|
end
|
@@ -163,6 +163,7 @@ module Alexandria
|
|
163
163
|
# title
|
164
164
|
title_div = doc % 'div#conteudo//div.titulo'
|
165
165
|
raise NoResultsError unless title_div
|
166
|
+
|
166
167
|
title_h = title_div % 'h2'
|
167
168
|
title = title_h.inner_text if title_h
|
168
169
|
# title = first_non_empty_text_node(title_div)
|
@@ -193,6 +194,7 @@ module Alexandria
|
|
193
194
|
image_urls = []
|
194
195
|
(doc / 'script').each do |script|
|
195
196
|
next if script.children.nil?
|
197
|
+
|
196
198
|
script.children.each do |ch|
|
197
199
|
ch_text = ch.to_s
|
198
200
|
if ch_text =~ /ImgSrc\[[\d]\]="(.+)";/
|
@@ -204,7 +206,7 @@ module Alexandria
|
|
204
206
|
book = Book.new(title, authors, isbn, publisher, publish_year, binding)
|
205
207
|
result = [book, image_urls.first]
|
206
208
|
result
|
207
|
-
rescue => ex
|
209
|
+
rescue StandardError => ex
|
208
210
|
trace = ex.backtrace.join("\n> ")
|
209
211
|
log.error { "Failed parsing Siciliano product page #{ex.message}\n#{trace}" }
|
210
212
|
nil
|
@@ -214,6 +216,7 @@ module Alexandria
|
|
214
216
|
text = ''
|
215
217
|
elem.children.each do |node|
|
216
218
|
next unless node.text?
|
219
|
+
|
217
220
|
text = node.to_s.strip
|
218
221
|
break unless text.empty?
|
219
222
|
end
|
@@ -55,15 +55,16 @@ module Alexandria
|
|
55
55
|
else
|
56
56
|
results = parse_search_result_data(html_data.body)
|
57
57
|
raise NoResultsError if results.empty?
|
58
|
+
|
58
59
|
results.map { |result| get_book_from_search_result(result) }
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
63
|
def create_search_uri(search_type, search_term)
|
63
64
|
(search_type_code = {
|
64
|
-
SEARCH_BY_ISBN
|
65
|
+
SEARCH_BY_ISBN => 'sq',
|
65
66
|
SEARCH_BY_AUTHORS => 'sa', # Autor
|
66
|
-
SEARCH_BY_TITLE
|
67
|
+
SEARCH_BY_TITLE => 'st', # Titel
|
67
68
|
SEARCH_BY_KEYWORD => 'ssw' # Schlagwort
|
68
69
|
}[search_type]) || ''
|
69
70
|
search_type_code = CGI.escape(search_type_code)
|
@@ -120,6 +121,7 @@ module Alexandria
|
|
120
121
|
# list
|
121
122
|
return
|
122
123
|
end
|
124
|
+
|
123
125
|
# ISBN-lookup results in multiple results (trying to be
|
124
126
|
# useful, such as for new editions e.g. 9780974514055
|
125
127
|
# "Programming Ruby" )
|
@@ -182,7 +184,7 @@ module Alexandria
|
|
182
184
|
return [book, image_url]
|
183
185
|
|
184
186
|
end
|
185
|
-
rescue => ex
|
187
|
+
rescue StandardError => ex
|
186
188
|
trace = ex.backtrace.join("\n> ")
|
187
189
|
log.warn {
|
188
190
|
'Failed parsing search results for Thalia ' \
|
@@ -1,24 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# This file is part of Alexandria.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# Copyright (C) 2014 Matijs van Zuijlen
|
7
|
-
#
|
8
|
-
# Alexandria is free software; you can redistribute it and/or
|
9
|
-
# modify it under the terms of the GNU General Public License as
|
10
|
-
# published by the Free Software Foundation; either version 2 of the
|
11
|
-
# License, or (at your option) any later version.
|
12
|
-
#
|
13
|
-
# Alexandria is distributed in the hope that it will be useful,
|
14
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
16
|
-
# General Public License for more details.
|
17
|
-
#
|
18
|
-
# You should have received a copy of the GNU General Public
|
19
|
-
# License along with Alexandria; see the file COPYING. If not,
|
20
|
-
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
21
|
-
# Fifth Floor, Boston, MA 02110-1301 USA.
|
5
|
+
# See the file README.md for authorship and licensing information.
|
22
6
|
|
23
7
|
require 'hpricot'
|
24
8
|
require 'htmlentities'
|
@@ -42,19 +26,17 @@ module Alexandria
|
|
42
26
|
def text_of(node)
|
43
27
|
if node.nil?
|
44
28
|
nil
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
node_text.strip.squeeze(' ')
|
54
|
-
end
|
29
|
+
elsif node.text?
|
30
|
+
node.to_html
|
31
|
+
elsif node.elem?
|
32
|
+
if node.children.nil?
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
node_text = node.children.map { |n| text_of(n) }.join
|
36
|
+
node_text.strip.squeeze(' ')
|
55
37
|
end
|
56
|
-
# node.inner_html.strip
|
57
38
|
end
|
39
|
+
# node.inner_html.strip
|
58
40
|
end
|
59
41
|
end
|
60
42
|
end
|
@@ -70,7 +70,7 @@ module Alexandria
|
|
70
70
|
|
71
71
|
def url(book)
|
72
72
|
create_search_uri(SEARCH_BY_ISBN, book.isbn)
|
73
|
-
rescue => ex
|
73
|
+
rescue StandardError => ex
|
74
74
|
log.warn { "Cannot create url for book #{book}; #{ex.message}" }
|
75
75
|
nil
|
76
76
|
end
|
@@ -78,9 +78,9 @@ module Alexandria
|
|
78
78
|
private
|
79
79
|
|
80
80
|
def create_search_uri(search_type, search_term)
|
81
|
-
(search_type_code = { SEARCH_BY_ISBN
|
81
|
+
(search_type_code = { SEARCH_BY_ISBN => 'isbn:',
|
82
82
|
SEARCH_BY_AUTHORS => 'au:',
|
83
|
-
SEARCH_BY_TITLE
|
83
|
+
SEARCH_BY_TITLE => 'ti:',
|
84
84
|
SEARCH_BY_KEYWORD => '' }[search_type]) || ''
|
85
85
|
search_type_code = CGI.escape(search_type_code)
|
86
86
|
search_term_encoded = if search_type == SEARCH_BY_ISBN
|
@@ -106,6 +106,7 @@ module Alexandria
|
|
106
106
|
result_cells.each do |td|
|
107
107
|
type_icon = (td % 'div.type/img.icn')
|
108
108
|
next unless type_icon && type_icon['src'] =~ /icon-bks/
|
109
|
+
|
109
110
|
name_div = td % 'div.name'
|
110
111
|
title = name_div.inner_text
|
111
112
|
anchor = name_div % :a
|
@@ -117,7 +118,7 @@ module Alexandria
|
|
117
118
|
|
118
119
|
book_search_results << result
|
119
120
|
end
|
120
|
-
rescue => ex
|
121
|
+
rescue StandardError => ex
|
121
122
|
trace = ex.backtrace.join("\n> ")
|
122
123
|
log.warn {
|
123
124
|
'Failed parsing search results for WorldCat ' \
|
@@ -235,8 +236,9 @@ module Alexandria
|
|
235
236
|
image_url = nil # hm, it's on the website, but uses JavaScript...
|
236
237
|
|
237
238
|
return [book, image_url]
|
238
|
-
rescue => ex
|
239
|
+
rescue StandardError => ex
|
239
240
|
raise ex if ex.instance_of? NoResultsError
|
241
|
+
|
240
242
|
trace = ex.backtrace.join("\n> ")
|
241
243
|
log.warn {
|
242
244
|
'Failed parsing search results for WorldCat ' \
|
@@ -62,6 +62,7 @@ module Alexandria
|
|
62
62
|
resultset = search_records(criterion, type, conn_count)
|
63
63
|
log.debug { "total #{resultset.length}" }
|
64
64
|
raise NoResultsError if resultset.length == 0
|
65
|
+
|
65
66
|
results = books_from_marc(resultset, isbn)
|
66
67
|
type == SEARCH_BY_ISBN ? results.first : results
|
67
68
|
end
|
@@ -75,12 +76,12 @@ module Alexandria
|
|
75
76
|
def marc_to_book(marc_txt)
|
76
77
|
begin
|
77
78
|
marc = MARC::Record.new_from_marc(marc_txt, forgiving: true)
|
78
|
-
rescue => ex
|
79
|
+
rescue StandardError => ex
|
79
80
|
log.error { ex.message }
|
80
81
|
log.error { ex.backtrace.join("> \n") }
|
81
82
|
begin
|
82
83
|
marc = MARC::Record.new(marc_txt)
|
83
|
-
rescue => ex2
|
84
|
+
rescue StandardError => ex2
|
84
85
|
log.error { ex2.message }
|
85
86
|
log.error { ex2.backtrace.join("> \n") }
|
86
87
|
raise ex2
|
@@ -132,7 +133,7 @@ module Alexandria
|
|
132
133
|
# failing that, try the genuine MARC parser
|
133
134
|
book = marc_to_book(marc_txt)
|
134
135
|
end
|
135
|
-
rescue => ex
|
136
|
+
rescue StandardError => ex
|
136
137
|
log.warn { ex }
|
137
138
|
log.warn { ex.backtrace }
|
138
139
|
end
|
@@ -183,7 +184,7 @@ module Alexandria
|
|
183
184
|
end
|
184
185
|
end
|
185
186
|
conn.search(pqf)
|
186
|
-
rescue => ex
|
187
|
+
rescue StandardError => ex
|
187
188
|
if /1005/ =~ ex.message
|
188
189
|
if prefs.variable_named('piggyback') && prefs['piggyback']
|
189
190
|
log.error { "Z39.50 search failed:: #{ex.message}" }
|
@@ -221,7 +222,7 @@ module Alexandria
|
|
221
222
|
def url(book)
|
222
223
|
'http://catalog.loc.gov/cgi-bin/Pwebrecon.cgi?DB=local&CNT=25+records+per+page&CMD=isbn+' +
|
223
224
|
Library.canonicalise_isbn(book.isbn)
|
224
|
-
rescue => ex
|
225
|
+
rescue StandardError => ex
|
225
226
|
log.warn { "Cannot create url for book #{book}; #{ex.message}" }
|
226
227
|
nil
|
227
228
|
end
|
@@ -262,13 +263,14 @@ module Alexandria
|
|
262
263
|
resultset = search_records(criterion, type, conn_count)
|
263
264
|
log.debug { "total #{resultset.length}" }
|
264
265
|
raise NoResultsError if resultset.length == 0
|
266
|
+
|
265
267
|
results = books_from_sutrs(resultset)
|
266
268
|
type == SEARCH_BY_ISBN ? results.first : results
|
267
269
|
end
|
268
270
|
|
269
271
|
def url(book)
|
270
272
|
'http://copac.ac.uk/openurl?isbn=' + Library.canonicalise_isbn(book.isbn)
|
271
|
-
rescue => ex
|
273
|
+
rescue StandardError => ex
|
272
274
|
log.warn { "Cannot create url for book #{book}; #{ex.message}" }
|
273
275
|
nil
|
274
276
|
end
|
@@ -346,6 +348,7 @@ module Alexandria
|
|
346
348
|
resultset = search_records(criterion, type, 0)
|
347
349
|
log.debug { "total #{resultset.length}" }
|
348
350
|
raise NoResultsError if resultset.length == 0
|
351
|
+
|
349
352
|
results = books_from_marc(resultset, isbn)
|
350
353
|
type == SEARCH_BY_ISBN ? results.first : results
|
351
354
|
end
|
@@ -359,7 +362,7 @@ module Alexandria
|
|
359
362
|
'%253C%2FFONT%253E%2522&session=&zurl=opac&zquery=%281%3D7+4%3D2+2%3D3+5%3D100+6%3D1+3%3D3+%22' +
|
360
363
|
canonicalise_isbn_with_dashes(book.isbn) +
|
361
364
|
'%22%29&language=it&maxentries=10&target=0&position=1'
|
362
|
-
rescue => ex
|
365
|
+
rescue StandardError => ex
|
363
366
|
log.warn { "Cannot create url for book #{book}; #{ex.message}" }
|
364
367
|
nil
|
365
368
|
end
|
data/lib/alexandria/console.rb
CHANGED
@@ -1,27 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# This file is part of Alexandria.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# modify it under the terms of the GNU General Public License as
|
7
|
-
# published by the Free Software Foundation; either version 2 of the
|
8
|
-
# License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# Alexandria is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU General Public
|
16
|
-
# License along with Alexandria; see the file COPYING. If not,
|
17
|
-
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
18
|
-
# Fifth Floor, Boston, MA 02110-1301 USA.
|
5
|
+
# See the file README.md for authorship and licensing information.
|
19
6
|
|
20
7
|
module Alexandria
|
21
8
|
def self.list_books_on_console(_title = true, authors = true)
|
22
|
-
|
23
|
-
|
24
|
-
libraries =
|
9
|
+
collection = Alexandria::LibraryCollection.instance
|
10
|
+
collection.reload
|
11
|
+
libraries = collection.all_regular_libraries
|
25
12
|
output_string = ''
|
26
13
|
@books = libraries.flatten
|
27
14
|
@books.each do |book|
|
@@ -41,7 +41,7 @@ module Alexandria
|
|
41
41
|
|
42
42
|
def self.current
|
43
43
|
@@current_queue
|
44
|
-
rescue
|
44
|
+
rescue StandardError
|
45
45
|
nil
|
46
46
|
end
|
47
47
|
|
@@ -60,6 +60,7 @@ module Alexandria
|
|
60
60
|
break @pending_calls.pop
|
61
61
|
end
|
62
62
|
return if ary.nil?
|
63
|
+
|
63
64
|
id, procedure, args, need_retval = ary
|
64
65
|
retval = procedure.call(*args)
|
65
66
|
if need_retval
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of Alexandria.
|
4
|
+
#
|
5
|
+
# See the file README.md for authorship and licensing information.
|
6
|
+
|
7
|
+
module Alexandria
|
8
|
+
class ExportFormat
|
9
|
+
attr_reader :name, :ext, :message
|
10
|
+
|
11
|
+
include GetText
|
12
|
+
include Logging
|
13
|
+
extend GetText
|
14
|
+
bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
|
15
|
+
|
16
|
+
def self.all
|
17
|
+
[
|
18
|
+
new(_('Archived ONIX XML'), 'onix.tbz2', :export_as_onix_xml_archive),
|
19
|
+
new(_('Archived Tellico XML'), 'tc', :export_as_tellico_xml_archive),
|
20
|
+
new(_('BibTeX'), 'bib', :export_as_bibtex),
|
21
|
+
new(_('CSV list'), 'csv', :export_as_csv_list),
|
22
|
+
new(_('ISBN List'), 'txt', :export_as_isbn_list),
|
23
|
+
new(_('iPod Notes'), nil, :export_as_ipod_notes),
|
24
|
+
new(_('HTML Web Page'), nil, :export_as_html, true)
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
def invoke(library, sort_order, filename, *args)
|
29
|
+
sorted = ExportLibrary.new(library, sort_order)
|
30
|
+
log.debug { "Exporting library sorted by #{sort_order}" }
|
31
|
+
sorted.send(@message, filename, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def needs_preview?
|
35
|
+
@needs_preview
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def initialize(name, ext, message, needs_preview = false)
|
41
|
+
@name = name
|
42
|
+
@ext = ext
|
43
|
+
@message = message
|
44
|
+
@needs_preview = needs_preview
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,77 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
# Copyright (C) 2007 Cathal Mc Ginley
|
5
|
-
# Copyright (C) 2014, 2016 Matijs van Zuijlen
|
3
|
+
# This file is part of Alexandria.
|
6
4
|
#
|
7
|
-
#
|
8
|
-
# modify it under the terms of the GNU General Public License as
|
9
|
-
# published by the Free Software Foundation; either version 2 of the
|
10
|
-
# License, or (at your option) any later version.
|
11
|
-
#
|
12
|
-
# Alexandria is distributed in the hope that it will be useful,
|
13
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15
|
-
# General Public License for more details.
|
16
|
-
#
|
17
|
-
# You should have received a copy of the GNU General Public
|
18
|
-
# License along with Alexandria; see the file COPYING. If not,
|
19
|
-
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
20
|
-
# Fifth Floor, Boston, MA 02110-1301 USA.
|
21
|
-
|
22
|
-
# Export sorting added 23 Oct 2007 by Cathal Mc Ginley
|
23
|
-
# Classes LibrarySortOrder and SortedLibrary, and changed ExportFormat#invoke
|
24
|
-
# iPod Notes support added 20 January 2008 by Tim Malone
|
25
|
-
# require 'cgi'
|
5
|
+
# See the file README.md for authorship and licensing information.
|
26
6
|
|
27
7
|
require 'csv'
|
28
8
|
require 'image_size'
|
9
|
+
require 'tmpdir'
|
29
10
|
|
30
11
|
module Alexandria
|
31
|
-
class
|
32
|
-
include Logging
|
33
|
-
|
34
|
-
def initialize(book_attribute, ascending = true)
|
35
|
-
@book_attribute = book_attribute
|
36
|
-
@ascending = ascending
|
37
|
-
end
|
38
|
-
|
39
|
-
def sort(library)
|
40
|
-
sorted = library.sort_by do |book|
|
41
|
-
book.send(@book_attribute)
|
42
|
-
end
|
43
|
-
sorted.reverse! unless @ascending
|
44
|
-
sorted
|
45
|
-
rescue => ex
|
46
|
-
log.warn { "Could not sort library by #{@book_attribute.inspect}: #{ex.message}" }
|
47
|
-
library
|
48
|
-
end
|
49
|
-
|
50
|
-
def to_s
|
51
|
-
"#{@book_attribute} #{@ascending ? '(ascending)' : '(descending)'}"
|
52
|
-
end
|
53
|
-
|
54
|
-
class Unsorted < LibrarySortOrder
|
55
|
-
def initialize; end
|
56
|
-
|
57
|
-
def sort(library)
|
58
|
-
library
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_s
|
62
|
-
'default order'
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
class SortedLibrary < Library
|
12
|
+
class ExportLibrary
|
68
13
|
def initialize(library, sort_order)
|
69
|
-
super(library.name)
|
70
14
|
@library = library
|
71
|
-
sorted = sort_order.sort(library)
|
72
|
-
sorted.each do |book|
|
73
|
-
self << book
|
74
|
-
end
|
15
|
+
@sorted = sort_order.sort(library)
|
75
16
|
end
|
76
17
|
|
77
18
|
def cover(book)
|
@@ -85,53 +26,15 @@ module Alexandria
|
|
85
26
|
def copy_covers(dest)
|
86
27
|
@library.copy_covers(dest)
|
87
28
|
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class ExportFormat
|
91
|
-
attr_reader :name, :ext, :message
|
92
29
|
|
93
|
-
|
94
|
-
|
95
|
-
extend GetText
|
96
|
-
bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
|
97
|
-
|
98
|
-
def self.all
|
99
|
-
[
|
100
|
-
new(_('Archived ONIX XML'), 'onix.tbz2', :export_as_onix_xml_archive),
|
101
|
-
new(_('Archived Tellico XML'), 'tc', :export_as_tellico_xml_archive),
|
102
|
-
new(_('BibTeX'), 'bib', :export_as_bibtex),
|
103
|
-
new(_('CSV list'), 'csv', :export_as_csv_list),
|
104
|
-
new(_('ISBN List'), 'txt', :export_as_isbn_list),
|
105
|
-
new(_('iPod Notes'), nil, :export_as_ipod_notes),
|
106
|
-
new(_('HTML Web Page'), nil, :export_as_html, true)
|
107
|
-
]
|
30
|
+
def name
|
31
|
+
@library.name
|
108
32
|
end
|
109
33
|
|
110
|
-
def
|
111
|
-
|
112
|
-
sorted = SortedLibrary.new(library, sort_order)
|
113
|
-
log.debug { "Exporting library sorted by #{sort_order}" }
|
114
|
-
sorted.send(@message, filename, *args)
|
115
|
-
else
|
116
|
-
library.send(@message, filename, *args)
|
117
|
-
end
|
34
|
+
def each(&block)
|
35
|
+
@sorted.each(&block)
|
118
36
|
end
|
119
37
|
|
120
|
-
def needs_preview?
|
121
|
-
@needs_preview
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
def initialize(name, ext, message, needs_preview = false)
|
127
|
-
@name = name
|
128
|
-
@ext = ext
|
129
|
-
@message = message
|
130
|
-
@needs_preview = needs_preview
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
module Exportable
|
135
38
|
def export_as_onix_xml_archive(filename)
|
136
39
|
File.open(File.join(Dir.tmpdir, 'onix.xml'), 'w') do |io|
|
137
40
|
to_onix_document.write(io, 0)
|
@@ -149,7 +52,7 @@ module Alexandria
|
|
149
52
|
File.open(File.join(Dir.tmpdir, 'tellico.xml'), 'w') do |io|
|
150
53
|
begin
|
151
54
|
to_tellico_document.write(io, 0)
|
152
|
-
rescue => ex
|
55
|
+
rescue StandardError => ex
|
153
56
|
puts ex.message
|
154
57
|
puts ex.backtrace
|
155
58
|
raise ex
|
@@ -252,7 +155,7 @@ module Alexandria
|
|
252
155
|
header.add_element('SentDate').text = format('%.4d%.2d%.2d%.2d%.2d',
|
253
156
|
now.year, now.month, now.day, now.hour, now.min)
|
254
157
|
header.add_element('MessageNote').text = name
|
255
|
-
each_with_index do |book, idx|
|
158
|
+
@sorted.each_with_index do |book, idx|
|
256
159
|
# fields that are missing: edition and rating.
|
257
160
|
prod = msg.add_element('Product')
|
258
161
|
prod.add_element('RecordReference').text = idx
|
@@ -325,7 +228,7 @@ module Alexandria
|
|
325
228
|
# collection fields
|
326
229
|
field1.add_attribute('name', '_default')
|
327
230
|
images = collection.add_element('images')
|
328
|
-
each_with_index do |book, idx|
|
231
|
+
@sorted.each_with_index do |book, idx|
|
329
232
|
entry = collection.add_element('entry')
|
330
233
|
new_index = (idx + 1).to_s
|
331
234
|
entry.add_attribute('id', new_index)
|
@@ -350,9 +253,9 @@ module Alexandria
|
|
350
253
|
image = images.add_element('image')
|
351
254
|
image.add_attribute('id', final_cover(book))
|
352
255
|
image_s = ImageSize.new(IO.read(cover(book)))
|
353
|
-
image.add_attribute('height', image_s.
|
354
|
-
image.add_attribute('width', image_s.
|
355
|
-
image.add_attribute('format', image_s.
|
256
|
+
image.add_attribute('height', image_s.height.to_s)
|
257
|
+
image.add_attribute('width', image_s.width.to_s)
|
258
|
+
image.add_attribute('format', image_s.format)
|
356
259
|
end
|
357
260
|
end
|
358
261
|
doc
|
@@ -370,95 +273,91 @@ module Alexandria
|
|
370
273
|
|
371
274
|
def to_xhtml(css)
|
372
275
|
generator = 'Alexandria ' + Alexandria::DISPLAY_VERSION
|
373
|
-
xhtml = ''
|
374
|
-
xhtml <<
|
375
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
376
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
377
|
-
|
378
|
-
<html>
|
379
|
-
<head>
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
</head>
|
388
|
-
<body>
|
389
|
-
<h1 class="library_name">#{xhtml_escape(name)}</h1>
|
390
|
-
EOS
|
276
|
+
xhtml = +''
|
277
|
+
xhtml << <<~EOS
|
278
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
279
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
280
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
281
|
+
<html>
|
282
|
+
<head>
|
283
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
284
|
+
<meta name="Author" content="#{Etc.getlogin}"/>
|
285
|
+
<meta name="Description" content="List of books"/>
|
286
|
+
<meta name="Keywords" content="books"/>
|
287
|
+
<meta name="Generator" content="#{xhtml_escape(generator)}"/>
|
288
|
+
<title>#{xhtml_escape(name)}</title>
|
289
|
+
<link rel="stylesheet" href="#{xhtml_escape(css)}" type="text/css"/>
|
290
|
+
</head>
|
291
|
+
<body>
|
292
|
+
<h1 class="library_name">#{xhtml_escape(name)}</h1>
|
293
|
+
EOS
|
391
294
|
|
392
295
|
each do |book|
|
393
|
-
xhtml <<
|
394
|
-
<div class="book">
|
395
|
-
|
396
|
-
EOS
|
296
|
+
xhtml << <<~EOS
|
297
|
+
<div class="book">
|
298
|
+
<p class="book_isbn">#{book.isbn}</p>
|
299
|
+
EOS
|
397
300
|
|
398
301
|
if File.exist?(cover(book))
|
399
|
-
xhtml << <<EOS
|
400
|
-
<img class="book_cover"
|
401
|
-
src="#{File.join('pixmaps', final_cover(book))}"
|
402
|
-
alt="Cover file for '#{xhtml_escape(book.title)}'"
|
403
|
-
EOS
|
404
302
|
image_s = ImageSize.new(IO.read(cover(book)))
|
405
|
-
xhtml <<
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
303
|
+
xhtml << <<~EOS
|
304
|
+
<img class="book_cover"
|
305
|
+
src="#{File.join('pixmaps', final_cover(book))}"
|
306
|
+
alt="Cover file for '#{xhtml_escape(book.title)}'"
|
307
|
+
height="#{image_s.height}" width="#{image_s.width}"
|
308
|
+
/>
|
309
|
+
EOS
|
411
310
|
else
|
412
|
-
xhtml <<
|
413
|
-
<div class="no_book_cover"></div>
|
414
|
-
EOS
|
311
|
+
xhtml << <<~EOS
|
312
|
+
<div class="no_book_cover"></div>
|
313
|
+
EOS
|
415
314
|
end
|
416
315
|
|
417
316
|
unless book.title.nil?
|
418
|
-
xhtml <<
|
419
|
-
<p class="book_title">#{xhtml_escape(book.title)}</p>
|
420
|
-
EOS
|
317
|
+
xhtml << <<~EOS
|
318
|
+
<p class="book_title">#{xhtml_escape(book.title)}</p>
|
319
|
+
EOS
|
421
320
|
end
|
422
321
|
|
423
322
|
unless book.authors.empty?
|
424
323
|
xhtml << '<ul class="book_authors">'
|
425
324
|
book.authors.each do |author|
|
426
|
-
xhtml <<
|
427
|
-
<li class="book_author">#{xhtml_escape(author)}</li>
|
428
|
-
EOS
|
325
|
+
xhtml << <<~EOS
|
326
|
+
<li class="book_author">#{xhtml_escape(author)}</li>
|
327
|
+
EOS
|
429
328
|
end
|
430
329
|
xhtml << '</ul>'
|
431
330
|
end
|
432
331
|
|
433
332
|
unless book.edition.nil?
|
434
|
-
xhtml <<
|
435
|
-
<p class="book_binding">#{xhtml_escape(book.edition)}</p>
|
436
|
-
EOS
|
333
|
+
xhtml << <<~EOS
|
334
|
+
<p class="book_binding">#{xhtml_escape(book.edition)}</p>
|
335
|
+
EOS
|
437
336
|
end
|
438
337
|
|
439
338
|
unless book.publisher.nil?
|
440
|
-
xhtml <<
|
441
|
-
<p class="book_publisher">#{xhtml_escape(book.publisher)}</p>
|
442
|
-
EOS
|
339
|
+
xhtml << <<~EOS
|
340
|
+
<p class="book_publisher">#{xhtml_escape(book.publisher)}</p>
|
341
|
+
EOS
|
443
342
|
end
|
444
343
|
|
445
|
-
xhtml <<
|
446
|
-
</div>
|
447
|
-
EOS
|
344
|
+
xhtml << <<~EOS
|
345
|
+
</div>
|
346
|
+
EOS
|
448
347
|
end
|
449
|
-
xhtml <<
|
450
|
-
<p class="copyright">
|
451
|
-
|
452
|
-
|
453
|
-
</p>
|
454
|
-
</body>
|
455
|
-
</html>
|
456
|
-
EOS
|
348
|
+
xhtml << <<~EOS
|
349
|
+
<p class="copyright">
|
350
|
+
Generated on #{xhtml_escape(Date.today.to_s)}
|
351
|
+
by <a href="#{xhtml_escape(Alexandria::WEBSITE_URL)}">#{xhtml_escape(generator)}</a>.
|
352
|
+
</p>
|
353
|
+
</body>
|
354
|
+
</html>
|
355
|
+
EOS
|
457
356
|
end
|
458
357
|
|
459
358
|
def to_bibtex
|
460
359
|
generator = 'Alexandria ' + Alexandria::DISPLAY_VERSION
|
461
|
-
bibtex = ''
|
360
|
+
bibtex = +''
|
462
361
|
bibtex << "\%Generated on #{Date.today} by: #{generator}\n"
|
463
362
|
bibtex << "\%\n"
|
464
363
|
bibtex << "\n"
|
@@ -493,6 +392,7 @@ EOS
|
|
493
392
|
|
494
393
|
def latex_escape(str)
|
495
394
|
return '' if str.nil?
|
395
|
+
|
496
396
|
my_str = str.dup
|
497
397
|
my_str.gsub!(/%/, '\\%')
|
498
398
|
my_str.gsub!(/~/, '\\textasciitilde')
|
@@ -506,12 +406,4 @@ EOS
|
|
506
406
|
my_str
|
507
407
|
end
|
508
408
|
end
|
509
|
-
|
510
|
-
class Library
|
511
|
-
include Exportable
|
512
|
-
end
|
513
|
-
|
514
|
-
class SmartLibrary
|
515
|
-
include Exportable
|
516
|
-
end
|
517
409
|
end
|