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