Port calibredb list_categories

This commit is contained in:
Kovid Goyal 2017-05-02 12:08:00 +05:30
parent 7524b79d5a
commit f8fbcfdef3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 194 additions and 7 deletions

View File

@ -52,6 +52,18 @@ class Tag(object):
def __repr__(self): def __repr__(self):
return str(self) return str(self)
__calibre_serializable__ = True
def as_dict(self):
return {k: getattr(self, k) for k in self.__slots__}
@classmethod
def from_dict(cls, d):
ans = cls('')
for k in cls.__slots__:
setattr(ans, k, d[k])
return ans
def find_categories(field_metadata): def find_categories(field_metadata):
for category, cat in field_metadata.iteritems(): for category, cat in field_metadata.iteritems():
@ -102,6 +114,7 @@ def clean_user_categories(dbcache):
pass pass
return new_cats return new_cats
category_sort_keys = {True:{}, False: {}} category_sort_keys = {True:{}, False: {}}
category_sort_keys[True]['popularity'] = category_sort_keys[False]['popularity'] = \ category_sort_keys[True]['popularity'] = category_sort_keys[False]['popularity'] = \
lambda x:(-getattr(x, 'count', 0), sort_key(x.sort or x.name)) lambda x:(-getattr(x, 'count', 0), sort_key(x.sort or x.name))

View File

@ -4,19 +4,185 @@
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
readonly = False import csv
import sys
from textwrap import TextWrapper
from io import BytesIO
from calibre import prints
readonly = True
version = 0 # change this if you change signature of implementation() version = 0 # change this if you change signature of implementation()
def implementation(db, notify_changes, *args): def implementation(db, notify_changes):
is_remote = notify_changes is not None return db.get_categories(), db.field_metadata
is_remote
def option_parser(get_parser, args): def option_parser(get_parser, args):
pass parser = get_parser(
_(
'''\
%prog list_categories [options]
Produce a report of the category information in the database. The
information is the equivalent of what is shown in the tags pane.
'''
)
)
parser.add_option(
'-i',
'--item_count',
default=False,
action='store_true',
help=_(
'Output only the number of items in a category instead of the '
'counts per item within the category'
)
)
parser.add_option(
'-c', '--csv', default=False, action='store_true', help=_('Output in CSV')
)
parser.add_option(
'--dialect',
default='excel',
choices=csv.list_dialects(),
help=_('The type of CSV file to produce. Choices: {}')
.format(', '.join(csv.list_dialects()))
)
parser.add_option(
'-r',
'--categories',
default='',
dest='report',
help=_("Comma-separated list of category lookup names. "
"Default: all")
)
parser.add_option(
'-w',
'--width',
default=-1,
type=int,
help=_(
'The maximum width of a single line in the output. '
'Defaults to detecting screen size.'
)
)
return parser
def do_list(fields, data, opts):
from calibre.utils.terminal import geometry, ColoredStream
separator = ' '
widths = list(map(lambda x: 0, fields))
for i in data:
for j, field in enumerate(fields):
widths[j] = max(widths[j], max(len(field), len(unicode(i[field]))))
screen_width = geometry()[0]
if not screen_width:
screen_width = 80
field_width = screen_width // len(fields)
base_widths = map(lambda x: min(x + 1, field_width), widths)
while sum(base_widths) < screen_width:
adjusted = False
for i in range(len(widths)):
if base_widths[i] < widths[i]:
base_widths[i] += min(
screen_width - sum(base_widths), widths[i] - base_widths[i]
)
adjusted = True
break
if not adjusted:
break
widths = list(base_widths)
titles = map(
lambda x, y: '%-*s%s' % (x - len(separator), y, separator), widths, fields
)
with ColoredStream(sys.stdout, fg='green'):
prints(''.join(titles))
wrappers = map(lambda x: TextWrapper(x - 1), widths)
for record in data:
text = [
wrappers[i].wrap(unicode(record[field]))
for i, field in enumerate(fields)
]
lines = max(map(len, text))
for l in range(lines):
for i, field in enumerate(text):
ft = text[i][l] if l < len(text[i]) else ''
filler = '%*s' % (widths[i] - len(ft) - 1, '')
print(ft.encode('utf-8') + filler.encode('utf-8'), end=separator)
print()
def do_csv(fields, data, opts):
buf = BytesIO()
csv_print = csv.writer(buf, opts.dialect)
csv_print.writerow(fields)
for d in data:
row = [d[f] for f in fields]
csv_print.writerow([
x if isinstance(x, bytes) else unicode(x).encode('utf-8') for x in row
])
print(buf.getvalue())
def main(opts, args, dbctx): def main(opts, args, dbctx):
raise NotImplementedError('TODO: implement this') category_data, field_metadata = dbctx.run('list_categories')
data = []
report_on = [c.strip() for c in opts.report.split(',') if c.strip()]
def category_metadata(k):
return field_metadata.get(k)
categories = [
k for k in category_data.keys()
if category_metadata(k)['kind'] not in ['user', 'search'] and
(not report_on or k in report_on)
]
categories.sort(
cmp=lambda x, y: cmp(x if x[0] != '#' else x[1:], y if y[0] != '#' else y[1:])
)
def fmtr(v):
v = v or 0
ans = '%.1f' % v
if ans.endswith('.0'):
ans = ans[:-2]
return ans
if not opts.item_count:
for category in categories:
is_rating = category_metadata(category)['datatype'] == 'rating'
for tag in category_data[category]:
if is_rating:
tag.name = unicode(len(tag.name))
data.append({
'category': category,
'tag_name': tag.name,
'count': unicode(tag.count),
'rating': fmtr(tag.avg_rating),
})
else:
for category in categories:
data.append({
'category': category,
'tag_name': _('CATEGORY ITEMS'),
'count': unicode(len(category_data[category])),
'rating': ''
})
fields = ['category', 'tag_name', 'count', 'rating']
func = do_csv if opts.csv else do_list
func(fields, data, opts)
return 0 return 0

View File

@ -30,6 +30,7 @@ def encoder(obj, for_json=False):
if getattr(obj, '__calibre_serializable__', False): if getattr(obj, '__calibre_serializable__', False):
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.library.field_metadata import FieldMetadata, fm_as_dict from calibre.library.field_metadata import FieldMetadata, fm_as_dict
from calibre.db.categories import Tag
if isinstance(obj, Metadata): if isinstance(obj, Metadata):
from calibre.ebooks.metadata.book.serialize import metadata_as_dict from calibre.ebooks.metadata.book.serialize import metadata_as_dict
return encoded( return encoded(
@ -37,6 +38,8 @@ def encoder(obj, for_json=False):
) )
elif isinstance(obj, FieldMetadata): elif isinstance(obj, FieldMetadata):
return encoded(3, fm_as_dict(obj), for_json) return encoded(3, fm_as_dict(obj), for_json)
elif isinstance(obj, Tag):
return encoded(4, obj.as_dict(), for_json)
raise TypeError('Cannot serialize objects of type {}'.format(type(obj))) raise TypeError('Cannot serialize objects of type {}'.format(type(obj)))
@ -67,9 +70,14 @@ def decode_field_metadata(x, for_json):
return fm_from_dict(x) return fm_from_dict(x)
def decode_category_tag(x, for_json):
from calibre.db.categories import Tag
return Tag.from_dict(x)
decoders = ( decoders = (
lambda x, fj: parse_iso8601(x, assume_utc=True), lambda x, fj: set(x), lambda x, fj: parse_iso8601(x, assume_utc=True), lambda x, fj: set(x),
decode_metadata, decode_field_metadata, decode_metadata, decode_field_metadata, decode_category_tag
) )