]> git.phdru.name Git - m_librarian.git/blob - scripts/ml-search.py
fe8dc48be67140af19d63108f9b96474be8fcb74
[m_librarian.git] / scripts / ml-search.py
1 #! /usr/bin/env python
2
3 from __future__ import print_function
4 import argparse
5 import sys
6 from sqlobject.sqlbuilder import CONCAT
7
8 from m_lib.defenc import default_encoding
9 from m_librarian.config import get_config
10 from m_librarian.db import Author, Book, Extension, Genre, Language, open_db
11 from m_librarian.download import download
12 from m_librarian.search import mk_search_conditions, \
13     search_authors, search_books, \
14     search_extensions, search_genres, search_languages
15
16 from m_librarian.translations import translations
17 _ = getattr(translations, 'ugettext', None) or translations.gettext
18
19
20 def decode(value):
21     if isinstance(value, bytes):
22         return value.decode(default_encoding)
23     return value
24
25
26 def encode(value):
27     if bytes is str and not isinstance(value, bytes):
28         return value.encode(default_encoding, errors='replace')
29     return value
30
31
32 def _get_values(args, *columns):
33     values = {}
34     for column in columns:
35         value = getattr(args, column)
36         if value:
37             value = decode(value)
38             values[column] = value
39     return values
40
41
42 def _guess_case_sensitivity(values):
43     for value in values.values():
44         if not value.islower():
45             return True
46     return False
47
48
49 def print_count(count):
50     print(encode(_("Found")), ":", count)
51
52
53 def _search_authors(case_sensitive, search_type, args):
54     if (args.surname or args.name or args.misc_name) and args.fullname:
55         sys.stderr.write(
56             "Cannot search by names and full name at the same time\n")
57         main_parser.print_help()
58         sys.exit(1)
59     expressions = []
60     values = _get_values(args, 'surname', 'name', 'misc_name', 'id')
61     if not values:
62         value = args.fullname
63         if value:
64             expressions.append((
65                 CONCAT(Author.q.surname, ' ', Author.q.name, ' ',
66                        Author.q.misc_name),
67                 decode(value)
68             ))
69     if case_sensitive is None:
70         case_sensitive = _guess_case_sensitivity(values)
71     authors = search_authors(search_type, case_sensitive, values, expressions,
72                              orderBy=('surname', 'name', 'misc_name'))
73     if args.count:
74         print_count(authors.count())
75         return
76     count = 0
77     for author in authors:
78         print(encode(author.fullname),
79               encode((u"(%s: %d)" % (_('books'), author.count))), end=' ')
80         if args.verbose >= 1:
81             print("(id=%d)" % author.id, end=' ')
82         print()
83         count += 1
84     print_count(count)
85
86
87 def _search_books(case_sensitive, search_type, args):
88     if args.get and args.get_many:
89         sys.stderr.write(
90             "Cannot get one book and many books at the same time\n")
91         main_parser.print_help()
92         sys.exit(1)
93     join_expressions = []
94     values = _get_values(args, 'title', 'series', 'archive', 'file', 'id')
95     if case_sensitive is None:
96         test_values = values.copy()
97         test_values.update(_get_values(args, 'surname', 'name', 'misc_name',
98                                        'fullname', 'ext', 'gname', 'gtitle',
99                                        'lang'))
100         case_sensitive = _guess_case_sensitivity(test_values)
101     avalues = _get_values(args, 'surname', 'name', 'misc_name', 'fullname')
102     if args.aid:
103         avalues['id'] = args.aid
104     if avalues:
105         if (args.surname or args.name or args.misc_name) and args.fullname:
106             sys.stderr.write(
107                 "Cannot search by names and full name at the same time\n")
108             main_parser.print_help()
109             sys.exit(1)
110         expressions = []
111         join_expressions.append(Book.j.authors)
112         value = args.fullname
113         if value:
114             expressions.append((
115                 CONCAT(Author.q.surname, ' ', Author.q.name, ' ',
116                        Author.q.misc_name),
117                 decode(value)
118             ))
119         conditions = mk_search_conditions(
120             Author, search_type, case_sensitive, avalues, expressions)
121         join_expressions.extend(conditions)
122     evalues = {}
123     if args.ext:
124         evalues['name'] = args.ext
125     if args.eid:
126         evalues['id'] = args.eid
127     if evalues:
128         join_expressions.append(Book.j.extension)
129         conditions = mk_search_conditions(
130             Extension, search_type, case_sensitive, evalues)
131         join_expressions.extend(conditions)
132     gvalues = {}
133     for column in 'name', 'title':
134         value = getattr(args, 'g' + column)
135         if value:
136             gvalues[column] = decode(value)
137     if args.gid:
138         gvalues['id'] = args.gid
139     if gvalues:
140         join_expressions.append(Book.j.genres)
141         conditions = mk_search_conditions(
142             Genre, search_type, case_sensitive, gvalues)
143         join_expressions.extend(conditions)
144     lvalues = {}
145     if args.lang:
146         lvalues['name'] = args.lang
147     if args.lid:
148         lvalues['id'] = args.lid
149     if lvalues:
150         join_expressions.append(Book.j.language)
151         conditions = mk_search_conditions(
152             Language, search_type, case_sensitive, lvalues)
153         join_expressions.extend(conditions)
154     books = search_books(search_type, case_sensitive, values, join_expressions,
155                          orderBy=('series', 'ser_no', 'title'))
156     if args.count:
157         print_count(books.count())
158         return
159     if args.get_many:
160         books = books[:args.get_many]
161     elif args.get:
162         count = books.count()
163         if count != 1:
164             sys.stderr.write("There must be exactly 1 book for --get; "
165                              "(found %d).\n" % count)
166             sys.stderr.write("Use --get-many N to download more than one "
167                              "book.\n")
168             sys.exit(1)
169     count = 0
170     for book in books:
171         print(encode(book.title), end=' ')
172         if args.verbose >= 1:
173             print("(id=%d)" % book.id, end=' ')
174         print()
175         if args.verbose >= 1:
176             print(" ", encode(_("Author(s)")), ":", end=' ')
177             for author in book.authors:
178                 print(encode(author.fullname), end=' ')
179             print()
180             print(" ", encode(_("Genre(s)")), ":", end=' ')
181             for genre in book.genres:
182                 print((encode(genre.title or genre.name)), end=' ')
183             print()
184             if book.series:
185                 print(" ", encode(_("Series")), ":", end=' ')
186                 print(encode(book.series), "(%d)" % book.ser_no)
187
188         if args.verbose >= 2:
189             print(" ", encode(_("Date")), ":", book.date)
190             print(" ", encode(_("Language")), ":", encode(book.language.name))
191
192         if args.verbose >= 3:
193             print(" ", encode(_("Archive")), ":", book.archive)
194             print(" ", encode(_("File")), ":", book.file)
195             print(" ", encode(_("Extension")), ":",
196                   encode(book.extension.name))
197             print(" ", encode(_("Size")), ":",
198                   book.size, encode(_("bytes")))
199             print(" ", encode(_("Deleted")), ":",
200                   encode(_(str(book.deleted))))
201         if args.get or args.get_many:
202             download(book, args.path, args.format)
203         count += 1
204     print_count(count)
205
206
207 def _search_extensions(case_sensitive, search_type, args):
208     values = _get_values(args, 'name', 'id')
209     if case_sensitive is None:
210         case_sensitive = _guess_case_sensitivity(values)
211     else:
212         values = {}
213     extensions = search_extensions(search_type, case_sensitive, values,
214                                    orderBy='name')
215     if args.count:
216         print_count(extensions.count())
217         return
218     count = 0
219     for ext in extensions:
220         print(encode(ext.name),
221               encode((u"(%s: %d)" % (_('books'), ext.count))),
222               end=' ')
223         if args.verbose >= 1:
224             print("(id=%d)" % ext.id, end=' ')
225         print()
226         count += 1
227     print_count(count)
228
229
230 def _search_genres(case_sensitive, search_type, args):
231     values = _get_values(args, 'name', 'title', 'id')
232     if case_sensitive is None:
233         case_sensitive = _guess_case_sensitivity(values)
234     genres = search_genres(search_type, case_sensitive, values, orderBy='name')
235     if args.count:
236         print_count(genres.count())
237         return
238     count = 0
239     for genre in genres:
240         names = filter(None, (genre.name, genre.title))
241         fullname = u' '.join(names)
242         print(encode(fullname),
243               encode((u"(%s: %d)" % (_('books'), genre.count))),
244               end=' ')
245         if args.verbose >= 1:
246             print("(id=%d)" % genre.id, end=' ')
247         print()
248         count += 1
249     print_count(count)
250
251
252 def _search_languages(case_sensitive, search_type, args):
253     values = _get_values(args, 'name', 'id')
254     if case_sensitive is None:
255         case_sensitive = _guess_case_sensitivity(values)
256     else:
257         values = {}
258     languages = search_languages(search_type, case_sensitive, values,
259                                  orderBy='name')
260     if args.count:
261         print_count(languages.count())
262         return
263     count = 0
264     for lang in languages:
265         print(encode(lang.name),
266               encode((u"(%s: %d)" % (_('books'), lang.count))),
267               end=' ')
268         if args.verbose >= 1:
269             print("(id=%d)" % lang.id, end=' ')
270         print()
271         count += 1
272     print_count(count)
273
274
275 if __name__ == '__main__':
276     main_parser = argparse.ArgumentParser(description='Search')
277     main_parser.add_argument('-C', '--config', help='configuration file')
278     main_parser.add_argument('-D', '--database', help='database URI')
279     main_parser.add_argument('-i', '--ignore-case', action='store_true',
280                              help='ignore case '
281                              '(default is to guess)')
282     main_parser.add_argument('-I', '--case-sensitive', action='store_true',
283                              help='don\'t ignore case')
284     main_parser.add_argument('-t', '--start', action='store_true',
285                              help='search substring at the start '
286                              '(this is the default)')
287     main_parser.add_argument('-s', '--substring', action='store_true',
288                              help='search substring anywhere')
289     main_parser.add_argument('-f', '--full', action='store_true',
290                              help='match the entire string')
291     main_parser.add_argument('-c', '--count', action='store_true',
292                              help='count instead of output')
293     main_parser.add_argument('-v', '--verbose', action='count', default=0,
294                              help='output more details about books; '
295                              'repeat for even more details')
296     subparsers = main_parser.add_subparsers(help='Commands')
297
298     parser = subparsers.add_parser('authors', help='Search authors')
299     parser.add_argument('-s', '--surname', help='search by surname')
300     parser.add_argument('-n', '--name', help='search by name')
301     parser.add_argument('-m', '--misc-name', help='search by misc. name')
302     parser.add_argument('--id', type=int, help='search by database id')
303     parser.add_argument('fullname', nargs='?', help='search by full name')
304     parser.set_defaults(func=_search_authors)
305
306     parser = subparsers.add_parser('books', help='Search books')
307     parser.add_argument('-t', '--title', help='search by title')
308     parser.add_argument('-s', '--series', help='search by series')
309     parser.add_argument('-a', '--archive', help='search by archive (zip file)')
310     parser.add_argument('-f', '--file', help='search by file name')
311     parser.add_argument('--id', type=int, help='search by database id')
312     parser.add_argument('--surname', help='search by author\'s surname')
313     parser.add_argument('--name', help='search by author\'s name')
314     parser.add_argument('--misc-name', help='search by author\'s misc. name')
315     parser.add_argument('--fullname', help='search by author\'s full name')
316     parser.add_argument('--aid', type=int, help='search by author\'s id')
317     parser.add_argument('-e', '--ext', help='search by file extension')
318     parser.add_argument('--eid', type=int, help='search by extension\'s id')
319     parser.add_argument('--gname', help='search by genre\'s name')
320     parser.add_argument('--gtitle', help='search by genre\'s title')
321     parser.add_argument('--gid', type=int, help='search by genre\'s id')
322     parser.add_argument('-l', '--lang', help='search by language')
323     parser.add_argument('--lid', type=int, help='search by language\'s id')
324     parser.add_argument('-P', '--path', help='path to the library archives')
325     parser.add_argument('-F', '--format',
326                         help='download format, default is %%f')
327     parser.add_argument('--get', action='store_true',
328                         help='download exactly one book')
329     parser.add_argument('--get-many', type=int,
330                         help='download at most this many books')
331     parser.set_defaults(func=_search_books)
332
333     parser = subparsers.add_parser('ext', help='Search extensions')
334     parser.add_argument('name', nargs='?', help='search by name')
335     parser.add_argument('--id', type=int, help='search by database id')
336     parser.set_defaults(func=_search_extensions)
337
338     parser = subparsers.add_parser('genres', help='Search genres')
339     parser.add_argument('-n', '--name', help='search by name')
340     parser.add_argument('-t', '--title', help='search by title')
341     parser.add_argument('--id', type=int, help='search by database id')
342     parser.set_defaults(func=_search_genres)
343
344     parser = subparsers.add_parser('lang', help='Search languages')
345     parser.add_argument('name', nargs='?', help='search by name')
346     parser.add_argument('--id', type=int, help='search by database id')
347     parser.set_defaults(func=_search_languages)
348
349     args = main_parser.parse_args()
350
351     if args.config:
352         get_config(args.config)  # Get and cache config file
353
354     if args.case_sensitive:
355         if args.ignore_case:
356             sys.stderr.write(
357                 "Cannot search case sensitive and case insensitive "
358                 "at the same time\n")
359             main_parser.print_help()
360             sys.exit(1)
361         else:
362             case_sensitive = True
363     elif args.ignore_case:
364         case_sensitive = False
365     else:
366         case_sensitive = None  # guess case sensitivity
367
368     if int(args.start) + int(args.substring) + int(args.full) > 1:
369         sys.stderr.write(
370             "Cannot search case sensitive and case insensitive "
371             "at the same time\n")
372         main_parser.print_help()
373         sys.exit(1)
374     if args.start:
375         search_type = 'start'
376     elif args.substring:
377         search_type = 'substring'
378     elif args.full:
379         search_type = 'full'
380     else:
381         search_type = 'start'
382
383     open_db(args.database)
384     args.func(case_sensitive, search_type, args)