mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Port calibredb list_categories
This commit is contained in:
parent
7524b79d5a
commit
f8fbcfdef3
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user