mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix #7090 (Saving to disk with ISBN in the file name). Fix template 'shorten' function to permit 0-length trailers. Add calibredb list_categories, a command that dumps the data shown in the tags pane. Fix clearing custom series columns. They should be cleared to None, not the empty string
This commit is contained in:
commit
8834c4cb5a
@ -38,6 +38,7 @@ class SafeFormat(TemplateFormatter):
|
||||
|
||||
def get_value(self, key, args, kwargs):
|
||||
try:
|
||||
key = field_metadata.search_term_to_field_key(key.lower())
|
||||
b = self.book.get_user_metadata(key, False)
|
||||
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
|
||||
v = ''
|
||||
@ -221,6 +222,11 @@ class Metadata(object):
|
||||
v = _data.get(attr, None)
|
||||
if v is not None:
|
||||
result[attr] = v
|
||||
# separate these because it uses the self.get(), not _data.get()
|
||||
for attr in TOP_LEVEL_CLASSIFIERS:
|
||||
v = self.get(attr, None)
|
||||
if v is not None:
|
||||
result[attr] = v
|
||||
for attr in _data['user_metadata'].iterkeys():
|
||||
v = self.get(attr, None)
|
||||
if v is not None:
|
||||
|
@ -299,7 +299,9 @@ class Series(Base):
|
||||
val, s_index = self.gui_val
|
||||
val = self.normalize_ui_val(val)
|
||||
if val != self.initial_val or s_index != self.initial_index:
|
||||
if s_index == 0.0:
|
||||
if val == '':
|
||||
val = s_index = None
|
||||
elif s_index == 0.0:
|
||||
if tweaks['series_index_auto_increment'] == 'next':
|
||||
s_index = self.db.get_next_cc_series_num_for(val,
|
||||
num=self.col_id)
|
||||
@ -488,7 +490,7 @@ class BulkSeries(BulkBase):
|
||||
|
||||
def commit(self, book_ids, notify=False):
|
||||
val, update_indices, force_start, at_value, clear = self.gui_val
|
||||
val = '' if clear else self.normalize_ui_val(val)
|
||||
val = None if clear else self.normalize_ui_val(val)
|
||||
if clear or val != '':
|
||||
extras = []
|
||||
next_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)
|
||||
|
@ -743,6 +743,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
val = qt_to_dt(val, as_utc=False)
|
||||
elif typ == 'series':
|
||||
val, s_index = parse_series_string(self.db, label, value.toString())
|
||||
if not val:
|
||||
val = s_index = None
|
||||
elif typ == 'composite':
|
||||
tmpl = unicode(value.toString()).strip()
|
||||
disp = cc['display']
|
||||
|
@ -956,12 +956,6 @@ def command_check_library(args, dbpath):
|
||||
print_one(checker, check)
|
||||
|
||||
|
||||
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||
'show_metadata', 'set_metadata', 'export', 'catalog',
|
||||
'saved_searches', 'add_custom_column', 'custom_columns',
|
||||
'remove_custom_column', 'set_custom', 'restore_database',
|
||||
'check_library')
|
||||
|
||||
def restore_database_option_parser():
|
||||
parser = get_parser(_(
|
||||
'''
|
||||
@ -1015,6 +1009,134 @@ def command_restore_database(args, dbpath):
|
||||
prints('Some errors occurred. A detailed report was '
|
||||
'saved to', name)
|
||||
|
||||
def list_categories_option_parser():
|
||||
from calibre.library.check_library import CHECKS
|
||||
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('-q', '--quote', default='"',
|
||||
help=_('The character to put around the category value in CSV mode. '
|
||||
'Default is quotes (").'))
|
||||
parser.add_option('-r', '--categories', default=None, dest='report',
|
||||
help=_("Comma-separated list of category lookup names.\n"
|
||||
"Default: all"))
|
||||
parser.add_option('-w', '--line-width', default=-1, type=int,
|
||||
help=_('The maximum width of a single line in the output. '
|
||||
'Defaults to detecting screen size.'))
|
||||
parser.add_option('-s', '--separator', default=',',
|
||||
help=_('The string used to separate fields in CSV mode. '
|
||||
'Default is a comma.'))
|
||||
return parser
|
||||
|
||||
def command_list_categories(args, dbpath):
|
||||
parser = list_categories_option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
if len(args) != 0:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
if opts.library_path is not None:
|
||||
dbpath = opts.library_path
|
||||
|
||||
if isbytestring(dbpath):
|
||||
dbpath = dbpath.decode(preferred_encoding)
|
||||
|
||||
db = LibraryDatabase2(dbpath)
|
||||
category_data = db.get_categories()
|
||||
data = []
|
||||
categories = [k for k in category_data.keys()
|
||||
if db.metadata_for_field(k)['kind'] != 'user']
|
||||
|
||||
categories.sort(cmp=lambda x,y: cmp(x if x[0] != '#' else x[1:],
|
||||
y if y[0] != '#' else y[1:]))
|
||||
if not opts.item_count:
|
||||
for category in categories:
|
||||
is_rating = db.metadata_for_field(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':unicode(tag.avg_rating)})
|
||||
else:
|
||||
for category in categories:
|
||||
data.append({'category':category,
|
||||
'tag_name':_('CATEGORY ITEMS'),
|
||||
'count': len(category_data[category]), 'rating': 0.0})
|
||||
|
||||
fields = ['category', 'tag_name', 'count', 'rating']
|
||||
|
||||
def do_list():
|
||||
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 = terminal_controller.COLS if opts.line_width < 0 else opts.line_width
|
||||
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)
|
||||
print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL
|
||||
|
||||
wrappers = map(lambda x: TextWrapper(x-1), widths)
|
||||
o = cStringIO.StringIO()
|
||||
|
||||
for record in data:
|
||||
text = [wrappers[i].wrap(unicode(record[field]).encode('utf-8')) 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, '')
|
||||
o.write(ft)
|
||||
o.write(filler+separator)
|
||||
print >>o
|
||||
print o.getvalue()
|
||||
|
||||
def do_csv():
|
||||
lf = '{category},"{tag_name}",{count},{rating}'
|
||||
lf = lf.replace(',', opts.separator).replace(r'\t','\t').replace(r'\n','\n')
|
||||
lf = lf.replace('"', opts.quote)
|
||||
for d in data:
|
||||
print lf.format(**d)
|
||||
|
||||
if opts.csv:
|
||||
do_csv()
|
||||
else:
|
||||
do_list()
|
||||
|
||||
|
||||
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||
'show_metadata', 'set_metadata', 'export', 'catalog',
|
||||
'saved_searches', 'add_custom_column', 'custom_columns',
|
||||
'remove_custom_column', 'set_custom', 'restore_database',
|
||||
'check_library', 'list_categories')
|
||||
|
||||
|
||||
def option_parser():
|
||||
parser = OptionParser(_(
|
||||
'''\
|
||||
|
@ -59,10 +59,10 @@ class TemplateFormatter(string.Formatter):
|
||||
return value_if_empty
|
||||
|
||||
def _shorten(self, val, leading, center_string, trailing):
|
||||
l = int(leading)
|
||||
t = int(trailing)
|
||||
l = max(0, int(leading))
|
||||
t = max(0, int(trailing))
|
||||
if len(val) > l + len(center_string) + t:
|
||||
return val[0:l] + center_string + val[-t:]
|
||||
return val[0:l] + center_string + ('' if t == 0 else val[-t:])
|
||||
else:
|
||||
return val
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user