# -*- coding: utf-8 -*- # Copyright 2015 Fanficdownloader team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import ConfigParser, re import exceptions from ConfigParser import DEFAULTSECT, MissingSectionHeaderError, ParsingError # All of the writers(epub,html,txt) and adapters(ffnet,twlt,etc) # inherit from Configurable. The config file(s) uses ini format: # [sections] with key:value settings. # # [defaults] # titlepage_entries: category,genre, status # [www.whofic.com] # titlepage_entries: category,genre, status,dateUpdated,rating # [epub] # titlepage_entries: category,genre, status,datePublished,dateUpdated,dateCreated # [www.whofic.com:epub] # titlepage_entries: category,genre, status,datePublished # [overrides] # titlepage_entries: category import adapters def re_compile(regex,line): try: return re.compile(regex) except Exception, e: raise exceptions.RegularExpresssionFailed(e,regex,line) formatsections = ['html','txt','epub','mobi'] othersections = ['defaults','overrides'] def get_valid_sections(): sites = adapters.getConfigSections() sitesections = list(othersections) for section in sites: sitesections.append(section) if section.startswith('www.'): # add w/o www if has www sitesections.append(section[4:]) else: # add w/ www if doesn't www sitesections.append('www.%s'%section) allowedsections = [] allowedsections.extend(formatsections) for section in sitesections: allowedsections.append(section) for f in formatsections: allowedsections.append('%s:%s'%(section,f)) return allowedsections def get_valid_list_entries(): return list(['category', 'genre', 'characters', 'ships', 'warnings', 'extratags', 'author', 'authorId', 'authorUrl', 'lastupdate', ]) boollist=['true','false'] def get_valid_set_options(): ''' dict() of names of boolean options, but as a tuple with valid sites, valid formats and valid values (None==all) ''' valdict = {'collect_series':(None,None,boollist), 'include_titlepage':(None,None,boollist), 'include_tocpage':(None,None,boollist), 'is_adult':(None,None,boollist), 'keep_style_attr':(None,None,boollist), 'keep_title_attr':(None,None,boollist), 'make_firstimage_cover':(None,None,boollist), 'never_make_cover':(None,None,boollist), 'nook_img_fix':(None,None,boollist), 'replace_br_with_p':(None,None,boollist), 'replace_hr':(None,None,boollist), 'sort_ships':(None,None,boollist), 'strip_chapter_numbers':(None,None,boollist), 'titlepage_use_table':(None,None,boollist), 'use_ssl_unverified_context':(None,None,boollist), 'add_chapter_numbers':(None,None,boollist+['toconly']), 'check_next_chapter':(['fanfiction.net'],None,boollist), 'tweak_fg_sleep':(['fanfiction.net'],None,boollist), 'fix_fimf_blockquotes':(['fimfiction.net'],None,boollist), 'fail_on_password':(['fimfiction.net'],None,boollist), 'do_update_hook':(['fimfiction.net'],None,boollist), 'force_login':(['phoenixsong.net'],None,boollist), 'non_breaking_spaces':(['fictionmania.tv'],None,boollist), 'universe_as_series':(['storiesonline.net'],None,boollist), 'strip_text_links':(['bloodshedverse.com'],None,boollist), # eFiction Base 'bulk_load':(['fannation.shades-of-moonlight.com', 'fhsarchive.com', 'lotrfanfiction.com', 'themaplebookshelf.com', 'devianthearts.com'],None,boollist), 'include_logpage':(None,['epub'],boollist+['smart']), 'windows_eol':(None,['txt'],boollist), 'include_images':(None,['epub','html'],boollist), 'grayscale_images':(None,['epub','html'],boollist), 'no_image_processing':(None,['epub','html'],boollist), } return dict(valdict) def get_valid_scalar_entries(): return list(['series', 'seriesUrl', 'language', 'status', 'datePublished', 'dateUpdated', 'dateCreated', 'rating', 'numChapters', 'numWords', 'site', 'storyId', 'title', 'storyUrl', 'description', 'formatname', 'formatext', 'siteabbrev', 'version', # internal stuff. 'authorHTML', 'seriesHTML', 'langcode', 'output_css', ]) def get_valid_entries(): return get_valid_list_entries() + get_valid_scalar_entries() # *known* keywords -- or rather regexps for them. def get_valid_keywords(): return list(['(in|ex)clude_metadata_(pre|post)', 'add_chapter_numbers', 'add_genre_when_multi_category', 'allow_unsafe_filename', 'always_overwrite', 'anthology_tags', 'anthology_title_pattern', 'background_color', 'bulk_load', 'chapter_end', 'chapter_start', 'chapter_title_add_pattern', 'chapter_title_strip_pattern', 'check_next_chapter', 'collect_series', 'connect_timeout', 'convert_images_to', 'cover_content', 'cover_exclusion_regexp', 'custom_columns_settings', 'dateCreated_format', 'datePublished_format', 'dateUpdated_format', 'default_cover_image', 'do_update_hook', 'exclude_notes', 'extra_logpage_entries', 'extra_subject_tags', 'extra_titlepage_entries', 'extra_valid_entries', 'extratags', 'extracategories', 'extragenres', 'extracharacters', 'extraships', 'extrawarnings', 'fail_on_password', 'file_end', 'file_start', 'fileformat', 'find_chapters', 'fix_fimf_blockquotes', 'force_login', 'generate_cover_settings', 'grayscale_images', 'image_max_size', 'include_images', 'include_logpage', 'include_subject_tags', 'include_titlepage', 'include_tocpage', 'is_adult', 'join_string_authorHTML', 'keep_style_attr', 'keep_title_attr', 'keep_summary_html', 'logpage_end', 'logpage_entries', 'logpage_entry', 'logpage_start', 'logpage_update_end', 'logpage_update_start', 'make_directories', 'make_firstimage_cover', 'make_linkhtml_entries', 'max_fg_sleep', 'max_fg_sleep_at_downloads', 'min_fg_sleep', 'never_make_cover', 'no_image_processing', 'non_breaking_spaces', 'nook_img_fix', 'output_css', 'output_filename', 'output_filename_safepattern', 'password', 'post_process_cmd', 'remove_transparency', 'replace_br_with_p', 'replace_hr', 'replace_metadata', 'slow_down_sleep_time', 'sort_ships', 'strip_chapter_numbers', 'strip_chapter_numeral', 'strip_text_links', 'titlepage_end', 'titlepage_entries', 'titlepage_entry', 'titlepage_no_title_entry', 'titlepage_start', 'titlepage_use_table', 'titlepage_wide_entry', 'tocpage_end', 'tocpage_entry', 'tocpage_start', 'tweak_fg_sleep', 'universe_as_series', 'use_ssl_unverified_context', 'user_agent', 'username', 'website_encodings', 'wide_titlepage_entries', 'windows_eol', 'wrap_width', 'zip_filename', 'zip_output', ]) # *known* entry keywords -- or rather regexps for them. def get_valid_entry_keywords(): return list(['%s_label', '(default_value|include_in|join_string|keep_in_order)_%s',]) # Moved here for test_config. def make_generate_cover_settings(param): vlist = [] for line in param.splitlines(): if "=>" in line: try: (template,regexp,setting) = map( lambda x: x.strip(), line.split("=>") ) re_compile(regexp,line) vlist.append((template,regexp,setting)) except Exception, e: raise exceptions.PersonalIniFailed(e,line,param) return vlist class Configuration(ConfigParser.SafeConfigParser): def __init__(self, site, fileform): ConfigParser.SafeConfigParser.__init__(self) self.linenos=dict() # key by section or section,key -> lineno self.sectionslist = ['defaults'] if site.startswith("www."): sitewith = site sitewithout = site.replace("www.","") else: sitewith = "www."+site sitewithout = site self.addConfigSection(sitewith) self.addConfigSection(sitewithout) if fileform: self.addConfigSection(fileform) self.addConfigSection(sitewith+":"+fileform) self.addConfigSection(sitewithout+":"+fileform) self.addConfigSection("overrides") self.listTypeEntries = get_valid_list_entries() self.validEntries = get_valid_entries() def addConfigSection(self,section): self.sectionslist.insert(0,section) def isListType(self,key): return key in self.listTypeEntries or self.hasConfig("include_in_"+key) def isValidMetaEntry(self, key): return key in self.getValidMetaList() def getValidMetaList(self): return self.validEntries + self.getConfigList("extra_valid_entries") # used by adapters & writers, non-convention naming style def hasConfig(self, key): return self.has_config(self.sectionslist, key) def has_config(self, sections, key): for section in sections: try: self.get(section,key) #print("found %s in section [%s]"%(key,section)) return True except: try: self.get(section,"add_to_"+key) #print("found add_to_%s in section [%s]"%(key,section)) return True except: pass return False # used by adapters & writers, non-convention naming style def getConfig(self, key, default=""): return self.get_config(self.sectionslist,key,default) def get_config(self, sections, key, default=""): val = default for section in sections: try: val = self.get(section,key) if val and val.lower() == "false": val = False #print "getConfig(%s)=[%s]%s" % (key,section,val) break except (ConfigParser.NoOptionError, ConfigParser.NoSectionError), e: pass for section in sections[::-1]: # 'martian smiley' [::-1] reverses list by slicing whole list with -1 step. try: val = val + self.get(section,"add_to_"+key) #print "getConfig(add_to_%s)=[%s]%s" % (key,section,val) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError), e: pass return val # split and strip each. def get_config_list(self, sections, key): vlist = re.split(r'(?#acolumn # themes=>#bcolumn,a # timeline=>#ccolumn,n # "FanFiction"=>#collection def make_sections(x): return '['+'], ['.join(x)+']' if keyword in valdict: (valsites,valformats,vals)=valdict[keyword] if valsites != None and sitename != None and sitename not in valsites: errors.append((self.get_lineno(section,keyword),"%s not valid in section [%s] -- only valid in %s sections."%(keyword,section,make_sections(valsites)))) if valformats != None and formatname != None and formatname not in valformats: errors.append((self.get_lineno(section,keyword),"%s not valid in section [%s] -- only valid in %s sections."%(keyword,section,make_sections(valformats)))) if value not in vals: errors.append((self.get_lineno(section,keyword),"%s not a valid value for %s"%(value,keyword))) ## skipping output_filename_safepattern ## regex--not used with plugin and this isn't ## used with CLI/web yet. except Exception as e: errors.append((self.get_lineno(section,keyword),"Error:%s in (%s:%s)"%(e,keyword,value))) return errors # extended by adapter, writer and story for ease of calling configuration. class Configurable(object): def __init__(self, configuration): self.configuration = configuration def isListType(self,key): return self.configuration.isListType(key) def isValidMetaEntry(self, key): return self.configuration.isValidMetaEntry(key) def getValidMetaList(self): return self.configuration.getValidMetaList() def hasConfig(self, key): return self.configuration.hasConfig(key) def has_config(self, sections, key): return self.configuration.has_config(sections, key) def getConfig(self, key, default=""): return self.configuration.getConfig(key,default) def get_config(self, sections, key, default=""): return self.configuration.get_config(sections,key,default) def getConfigList(self, key): return self.configuration.getConfigList(key) def get_config_list(self, sections, key): return self.configuration.get_config_list(sections,key)