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