From 3ea639199653b6552bd019f0b15aa64686088d15 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 8 Mar 2009 14:49:43 -0700 Subject: [PATCH] pluginize installs again. Also working framework for ebook-convert --- src/calibre/customize/builtins.py | 4 +- src/calibre/customize/conversion.py | 2 +- src/calibre/ebooks/conversion/cli.py | 69 ++++++++++++--------- src/calibre/ebooks/conversion/plumber.py | 4 +- src/calibre/ebooks/epub/from_any.py | 2 +- src/calibre/ebooks/epub/pages.py | 2 +- src/calibre/ebooks/epub/split.py | 5 +- src/calibre/ebooks/lrf/html/convert_from.py | 5 +- src/calibre/ebooks/mobi/writer.py | 2 +- src/calibre/ebooks/oeb/base.py | 9 --- src/calibre/library/database2.py | 2 +- src/calibre/linux.py | 48 ++------------ src/calibre/web/feeds/news.py | 5 +- src/calibre/web/fetch/simple.py | 5 +- 14 files changed, 60 insertions(+), 104 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index ca21bbb215..b6a6141612 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -245,11 +245,11 @@ class MOBIMetadataWriter(MetadataWriterPlugin): from calibre.ebooks.epub.input import EPUBInput from calibre.ebooks.mobi.input import MOBIInput from calibre.ebooks.oeb.output import OEBOutput -from calibre.customize.profiles import input_profiles +from calibre.customize.profiles import input_profiles, output_profiles plugins = [HTML2ZIP, EPUBInput, MOBIInput, OEBOutput] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ x.__name__.endswith('MetadataReader')] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ x.__name__.endswith('MetadataWriter')] -plugins += input_profiles \ No newline at end of file +plugins += input_profiles + output_profiles \ No newline at end of file diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index f20cc4ae85..10e5a44ddd 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -24,7 +24,7 @@ class ConversionOption(object): self.choices = choices if self.long_switch is None: - self.long_switch = '--'+self.name.replace('_', '-') + self.long_switch = self.name.replace('_', '-') self.validate_parameters() diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 174fa87a5d..83bcb453e9 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -12,28 +12,29 @@ input_file output_file [options] Convert an ebook from one format to another. -input_file is the input and output_file is the output. Both must be +input_file is the input and output_file is the output. Both must be \ specified as the first two arguments to the command. -The output ebook format is guessed from the file extension of -output_file. output_file can also be of the special format .EXT where -EXT is the output file extension. In this case, the name of the output -file is derived the name of the input file. Note that the filenames must -not start with a hyphen. Finally, if output_file has no extension, then -it is treated as a directory and an "open ebook" (OEB) consisting of HTML files -is written to that directory. These files are the files that would normally -have been passed to the output plugin. +The output ebook format is guessed from the file extension of \ +output_file. output_file can also be of the special format .EXT where \ +EXT is the output file extension. In this case, the name of the output \ +file is derived the name of the input file. Note that the filenames must \ +not start with a hyphen. Finally, if output_file has no extension, then \ +it is treated as a directory and an "open ebook" (OEB) consisting of HTML \ +files is written to that directory. These files are the files that would \ +normally have been passed to the output plugin. - -After specifying the input -and output file you can customize the conversion by specifying various -options, listed below. +After specifying the input \ +and output file you can customize the conversion by specifying various \ +options. the available options depend on the input and output file types. \ +To get help on them specify the input and output file and then use the -h \ +option. For full documentation of the conversion system see - ''') + 'http://calibre.kovidgoyal.net/user_manual/conversion.html' import sys, os +from optparse import OptionGroup, Option from calibre.utils.config import OptionParser from calibre.utils.logging import Log @@ -68,10 +69,11 @@ def check_command_line_options(parser, args, log): def option_recommendation_to_cli_option(add_option, rec): opt = rec.option - switches = [opt.short_switch] if opt.short_switch else [] - switches.append(opt.long_switch) - add_option(opt.name, switches=switches, help=opt.help, + switches = ['-'+opt.short_switch] if opt.short_switch else [] + switches.append('--'+opt.long_switch) + attrs = dict(dest=opt.name, help=opt.help, choices=opt.choices, default=rec.recommended_value) + add_option(Option(*switches, **attrs)) def add_input_output_options(parser, plumber): input_options, output_options = \ @@ -82,12 +84,18 @@ def add_input_output_options(parser, plumber): option_recommendation_to_cli_option(group, opt) if input_options: - io = parser.add_group(plumber.input_fmt.upper() + ' ' + _('OPTIONS')) - add_options(io, input_options) + title = plumber.input_fmt.upper() + ' ' + _('OPTIONS') + io = OptionGroup(parser, title, _('Options to control the processing' + ' of the input file')) + add_options(io.add_option, input_options) + parser.add_option_group(io) if output_options: - oo = parser.add_group(plumber.output_fmt.upper() + ' ' + _('OPTIONS')) - add_options(oo, output_options) + title = plumber.output_fmt.upper() + ' ' + _('OPTIONS') + oo = OptionGroup(parser, title, _('Options to control the processing' + ' of the output file')) + add_options(oo.add_option, output_options) + parser.add_option_group(oo) def add_pipeline_options(parser, plumber): groups = { @@ -106,27 +114,28 @@ def add_pipeline_options(parser, plumber): } + group_order = ['', 'DEBUG'] - for group, spec in groups.items(): - desc, options = spec + for group in group_order: + desc, options = groups[group] if group: - group = parser.add_option_group(group, desc) - add_option = group if group != '' else parser.add_option + group = OptionGroup(parser, group, desc) + parser.add_option_group(group) + add_option = group.add_option if group != '' else parser.add_option for name in options: rec = plumber.get_option_by_name(name) if rec.level < rec.HIGH: option_recommendation_to_cli_option(add_option, rec) - - - def main(args=sys.argv): log = Log() parser = OptionParser(usage=USAGE) - fargs = parser.parse_args(args)[1] + if len(args) < 3: + print_help(parser, log) + return 1 - input, output = check_command_line_options(parser, fargs, log) + input, output = check_command_line_options(parser, args, log) from calibre.ebooks.conversion.plumber import Plumber diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 742653251d..bd4d365af8 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -50,10 +50,10 @@ OptionRecommendation(name='output_profile', input_fmt = os.path.splitext(input)[1] if not input_fmt: - raise ValueError('Input file must have and extension') + raise ValueError('Input file must have an extension') input_fmt = input_fmt[1:].lower() - output_fmt = os.path.splitext(input)[1] + output_fmt = os.path.splitext(output)[1] if not output_fmt: output_fmt = '.oeb' output_fmt = output_fmt[1:].lower() diff --git a/src/calibre/ebooks/epub/from_any.py b/src/calibre/ebooks/epub/from_any.py index 9a8e251108..b3e5281525 100644 --- a/src/calibre/ebooks/epub/from_any.py +++ b/src/calibre/ebooks/epub/from_any.py @@ -12,7 +12,7 @@ from contextlib import nested from calibre import extract, walk from calibre.ebooks import DRMError -from calibre.ebooks.epub import config as common_config, process_encryption +from calibre.ebooks.epub import config as common_config from calibre.ebooks.epub.from_html import convert as html2epub, find_html_index from calibre.ptempfile import TemporaryDirectory from calibre.ebooks.metadata import MetaInformation diff --git a/src/calibre/ebooks/epub/pages.py b/src/calibre/ebooks/epub/pages.py index 1ab5edde86..4737107a6c 100644 --- a/src/calibre/ebooks/epub/pages.py +++ b/src/calibre/ebooks/epub/pages.py @@ -11,7 +11,7 @@ __docformat__ = 'restructuredtext en' import os, re from itertools import count, chain from calibre.ebooks.oeb.base import XHTML, XHTML_NS -from calibre.ebooks.oeb.base import OEBBook, DirWriter +from calibre.ebooks.oeb.base import OEBBook from lxml import etree, html from lxml.etree import XPath diff --git a/src/calibre/ebooks/epub/split.py b/src/calibre/ebooks/epub/split.py index 9814c40df5..c3099c1682 100644 --- a/src/calibre/ebooks/epub/split.py +++ b/src/calibre/ebooks/epub/split.py @@ -15,7 +15,7 @@ from lxml.cssselect import CSSSelector from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.epub import tostring, rules -from calibre import CurrentDir, LoggingInterface +from calibre import CurrentDir XPath = functools.partial(_XPath, namespaces={'re':'http://exslt.org/regular-expressions'}) content = functools.partial(os.path.join, 'content') @@ -32,10 +32,9 @@ class SplitError(ValueError): -class Splitter(LoggingInterface): +class Splitter(object): def __init__(self, path, opts, stylesheet_map, opf): - LoggingInterface.__init__(self, logging.getLogger('htmlsplit')) self.setup_cli_handler(opts.verbose) self.path = path self.always_remove = not opts.preserve_tag_structure or \ diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 2bd63d1d8f..056666b301 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -31,7 +31,7 @@ from calibre.ebooks.lrf import option_parser as lrf_option_parser from calibre.ebooks import ConversionError from calibre.ebooks.lrf.html.table import Table from calibre import filename_to_utf8, setup_cli_handlers, __appname__, \ - fit_image, LoggingInterface, preferred_encoding + fit_image, preferred_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.devices.interface import Device from calibre.ebooks.lrf.html.color_map import lrs_color @@ -78,7 +78,7 @@ def tag_regex(tagname): return dict(open=r'(?:<\s*%(t)s\s+[^<>]*?>|<\s*%(t)s\s*>)'%dict(t=tagname), \ close=r''%dict(t=tagname)) -class HTMLConverter(object, LoggingInterface): +class HTMLConverter(object): SELECTOR_PAT = re.compile(r"([A-Za-z0-9\-\_\:\.]+[A-Za-z0-9\-\_\:\.\s\,]*)\s*\{([^\}]*)\}") PAGE_BREAK_PAT = re.compile(r'page-break-(?:after|before)\s*:\s*(\w+)', re.IGNORECASE) IGNORED_TAGS = (Comment, Declaration, ProcessingInstruction) @@ -209,7 +209,6 @@ class HTMLConverter(object, LoggingInterface): ''' # Defaults for various formatting tags object.__setattr__(self, 'options', options) - LoggingInterface.__init__(self, logger) self.fonts = fonts #: dict specifying font families to use # Memory self.scaled_images = {} #: Temporary files with scaled version of images diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index fdabfaa618..86224488c0 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -24,7 +24,7 @@ from calibre.ebooks.oeb.base import XML_NS, XHTML, XHTML_NS, OEB_DOCS, \ OEB_RASTER_IMAGES from calibre.ebooks.oeb.base import xpath, barename, namespace, prefixname from calibre.ebooks.oeb.base import urlnormalize -from calibre.ebooks.oeb.base import Logger, OEBBook +from calibre.ebooks.oeb.base import OEBBook from calibre.ebooks.oeb.profile import Context from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 2e160d1571..f7c472320e 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -15,7 +15,6 @@ from urlparse import urldefrag, urlparse, urlunparse from urllib import unquote as urlunquote from lxml import etree, html import calibre -from calibre import LoggingInterface from calibre.translations.dynamic import translate from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.oeb.entitydefs import ENTITYDEFS @@ -212,14 +211,6 @@ class FauxLogger(object): def __call__(self, message): print message -class Logger(LoggingInterface, object): - """A logging object which provides both the standard `logging.Logger` and - calibre-specific interfaces. - """ - def __getattr__(self, name): - return object.__getattribute__(self, 'log_' + name) - - class NullContainer(object): """An empty container. diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index f8b63f1124..cb823e6c73 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -15,7 +15,7 @@ from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock from PyQt4.QtGui import QApplication, QImage __app = None -from calibre.library import title_sort +from calibre.ebooks.metadata import title_sort from calibre.library.database import LibraryDatabase from calibre.library.sqlite import connect, IntegrityError from calibre.utils.search_query_parser import SearchQueryParser diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 369dfa3d2c..e08222ed3a 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -1,9 +1,8 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' ''' Post installation script for linux ''' -import sys, os, re, shutil +import sys, os, shutil from subprocess import check_call, call -from tempfile import NamedTemporaryFile from calibre import __version__, __appname__ from calibre.devices import devices @@ -18,16 +17,8 @@ entry_points = { 'console_scripts': [ \ 'ebook-device = calibre.devices.prs500.cli.main:main', 'ebook-meta = calibre.ebooks.metadata.cli:main', - 'ebook-convert = calibre.ebooks.convert.cli:main', - 'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main', - 'html2lrf = calibre.ebooks.lrf.html.convert_from:main', - 'html2oeb = calibre.ebooks.html:main', - 'html2epub = calibre.ebooks.epub.from_html:main', - 'odt2oeb = calibre.ebooks.odt.to_oeb:main', + 'ebook-convert = calibre.ebooks.conversion.cli:main', 'markdown-calibre = calibre.ebooks.markdown.markdown:main', - 'lit2lrf = calibre.ebooks.lrf.lit.convert_from:main', - 'epub2lrf = calibre.ebooks.lrf.epub.convert_from:main', - 'rtf2lrf = calibre.ebooks.lrf.rtf.convert_from:main', 'web2disk = calibre.web.fetch.simple:main', 'feeds2disk = calibre.web.feeds.main:main', 'calibre-server = calibre.library.server:main', @@ -35,22 +26,10 @@ entry_points = { 'feeds2epub = calibre.ebooks.epub.from_feeds:main', 'feeds2mobi = calibre.ebooks.mobi.from_feeds:main', 'web2lrf = calibre.ebooks.lrf.web.convert_from:main', - 'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main', - 'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main', - 'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main', - 'any2lrf = calibre.ebooks.lrf.any.convert_from:main', - 'any2epub = calibre.ebooks.epub.from_any:main', - 'any2lit = calibre.ebooks.lit.from_any:main', - 'any2mobi = calibre.ebooks.mobi.from_any:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', - 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', 'isbndb = calibre.ebooks.metadata.isbndb:main', 'librarything = calibre.ebooks.metadata.library_thing:main', - 'mobi2oeb = calibre.ebooks.mobi.reader:main', - 'oeb2mobi = calibre.ebooks.mobi.writer:main', - 'lit2oeb = calibre.ebooks.lit.reader:main', - 'oeb2lit = calibre.ebooks.lit.writer:main', 'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main', 'comic2epub = calibre.ebooks.epub.from_comic:main', 'comic2mobi = calibre.ebooks.mobi.from_comic:main', @@ -61,7 +40,6 @@ entry_points = { 'calibre-parallel = calibre.parallel:main', 'calibre-customize = calibre.customize.ui:main', 'pdftrim = calibre.ebooks.pdf.pdftrim:main' , - 'any2pdf = calibre.ebooks.pdf.from_any:main', ], 'gui_scripts' : [ __appname__+' = calibre.gui2.main:main', @@ -172,25 +150,16 @@ def setup_completion(fatal_errors): from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop - from calibre.ebooks.mobi.reader import option_parser as mobioeb - from calibre.ebooks.lit.reader import option_parser as lit2oeb from calibre.web.feeds.main import option_parser as feeds2disk from calibre.web.feeds.recipes import titles as feed_titles from calibre.ebooks.lrf.feeds.convert_from import option_parser as feeds2lrf from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop - from calibre.ebooks.epub.from_html import option_parser as html2epub - from calibre.ebooks.html import option_parser as html2oeb - from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub from calibre.ebooks.mobi.from_feeds import option_parser as feeds2mobi - from calibre.ebooks.epub.from_any import option_parser as any2epub - from calibre.ebooks.lit.from_any import option_parser as any2lit from calibre.ebooks.epub.from_comic import option_parser as comic2epub - from calibre.ebooks.mobi.from_any import option_parser as any2mobi - from calibre.ebooks.mobi.writer import option_parser as oeb2mobi - from calibre.gui2.main import option_parser as guiop + from calibre.gui2.main import option_parser as guiop any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', - 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] + 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] f = open_file('/etc/bash_completion.d/libprs500') f.close() os.remove(f.name) @@ -210,16 +179,10 @@ def setup_completion(fatal_errors): f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf'])) f.write(opts_and_exts('any2lrf', htmlop, any_formats)) f.write(opts_and_exts('calibre', guiop, any_formats)) - f.write(opts_and_exts('any2epub', any2epub, any_formats)) - f.write(opts_and_exts('any2lit', any2lit, any_formats)) - f.write(opts_and_exts('any2mobi', any2mobi, any_formats)) - f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf'])) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('ebook-meta', metaop, list(meta_filetypes()))) f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf'])) f.write(opts_and_exts('pdfrelow', pdfhtmlop, ['pdf'])) - f.write(opts_and_exts('mobi2oeb', mobioeb, ['mobi', 'prc'])) - f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit'])) f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2mobi', comic2epub, ['cbz', 'cbr'])) @@ -228,9 +191,6 @@ def setup_completion(fatal_errors): f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles)) f.write(opts_and_words('feeds2epub', feeds2epub, feed_titles)) f.write(opts_and_words('feeds2mobi', feeds2mobi, feed_titles)) - f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf'])) - f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml'])) - f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt'])) f.write(''' _prs500_ls() { diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 4773d551c3..7d61cead5b 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -17,7 +17,7 @@ from PyQt4.Qt import QApplication, QFile, Qt, QPalette, QSize, QImage, QPainter, from PyQt4.QtWebKit import QWebPage -from calibre import browser, __appname__, iswindows, LoggingInterface, \ +from calibre import browser, __appname__, iswindows, \ strftime, __version__, preferred_encoding from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag from calibre.ebooks.metadata.opf2 import OPFCreator @@ -32,7 +32,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2 import images_rc # Needed for default cover -class BasicNewsRecipe(object, LoggingInterface): +class BasicNewsRecipe(object): ''' Abstract base class that contains logic needed in all feed fetchers. ''' @@ -444,7 +444,6 @@ class BasicNewsRecipe(object, LoggingInterface): :param parser: Command line option parser. Used to intelligently merge options. :param progress_reporter: A Callable that takes two arguments: progress (a number between 0 and 1) and a string message. The message should be optional. ''' - LoggingInterface.__init__(self, logging.getLogger('feeds2disk')) if not isinstance(self.title, unicode): self.title = unicode(self.title, 'utf-8', 'replace') diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index 4da3f4019c..51a4554a50 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -15,7 +15,7 @@ from PIL import Image from cStringIO import StringIO from calibre import setup_cli_handlers, browser, sanitize_file_name, \ - relpath, LoggingInterface, unicode_path + relpath, unicode_path from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag from calibre.ebooks.chardet import xml_to_unicode from calibre.utils.config import OptionParser @@ -80,7 +80,7 @@ class DummyLock(object): def __enter__(self, *args): return self def __exit__(self, *args): pass -class RecursiveFetcher(object, LoggingInterface): +class RecursiveFetcher(object): LINK_FILTER = tuple(re.compile(i, re.IGNORECASE) for i in ('.exe\s*$', '.mp3\s*$', '.ogg\s*$', '^\s*mailto:', '^\s*$')) #ADBLOCK_FILTER = tuple(re.compile(i, re.IGNORECASE) for it in @@ -93,7 +93,6 @@ class RecursiveFetcher(object, LoggingInterface): DUMMY_LOCK = DummyLock() def __init__(self, options, logger, image_map={}, css_map={}, job_info=None): - LoggingInterface.__init__(self, logger) self.base_dir = os.path.abspath(os.path.expanduser(options.dir)) if not os.path.exists(self.base_dir): os.makedirs(self.base_dir)