diff --git a/ebook/__init__.py b/ebook/__init__.py index 136678b..962cb8b 100644 --- a/ebook/__init__.py +++ b/ebook/__init__.py @@ -8,7 +8,7 @@ import html import unicodedata import datetime import requests -import attr +from attrs import define, asdict html_template = ''' @@ -66,17 +66,16 @@ frontmatter_template = ''' ''' -@attr.s +@define class CoverOptions: - fontname = attr.ib(default=None, converter=attr.converters.optional(str)) - fontsize = attr.ib(default=None, converter=attr.converters.optional(int)) - width = attr.ib(default=None, converter=attr.converters.optional(int)) - height = attr.ib(default=None, converter=attr.converters.optional(int)) - wrapat = attr.ib(default=None, converter=attr.converters.optional(int)) - bgcolor = attr.ib(default=None, converter=attr.converters.optional(tuple)) - textcolor = attr.ib( - default=None, converter=attr.converters.optional(tuple)) - cover_url = attr.ib(default=None, converter=attr.converters.optional(str)) + fontname: str = None + fontsize: int = None + width: int = None + height: int = None + wrapat: int = None + bgcolor: tuple = None + textcolor: tuple = None + cover_url: str = None def chapter_html( @@ -187,8 +186,7 @@ def generate_epub(story, cover_options={}, image_options=None, output_filename= 'height', 'wrapat', 'bgcolor', 'textcolor', 'cover_url') cover_options = CoverOptions( **{k: v for k, v in cover_options.items() if k in valid_cover_options}) - cover_options = attr.asdict( - cover_options, filter=lambda k, v: v is not None, retain_collection_types=True) + cover_options = asdict(cover_options, filter=lambda k, v: v is not None) if cover_options and "cover_url" in cover_options: image = make_cover_from_url( diff --git a/sites/__init__.py b/sites/__init__.py index e32f670..bbc62fe 100644 --- a/sites/__init__.py +++ b/sites/__init__.py @@ -4,11 +4,12 @@ import glob import os import random import uuid +import datetime import time import logging import urllib import re -import attr +from attrs import define, field, Factory from bs4 import BeautifulSoup logger = logging.getLogger(__name__) @@ -21,32 +22,32 @@ def _default_uuid_string(self): return str(uuid.UUID(int=rd.getrandbits(8*16), version=4)) -@attr.s +@define class Image: - path = attr.ib() - contents = attr.ib() - content_type = attr.ib() + path: str + contents: str + content_type: str -@attr.s +@define class Chapter: - title = attr.ib() - contents = attr.ib() - date = attr.ib(default=False) - images = attr.ib(default=attr.Factory(list)) + title: str + contents: str + date: datetime.datetime = False + images: list = Factory(list) -@attr.s +@define class Section: - title = attr.ib() - author = attr.ib() - url = attr.ib() - cover_url = attr.ib(default='') - id = attr.ib(default=attr.Factory(_default_uuid_string, takes_self=True), converter=str) - contents = attr.ib(default=attr.Factory(list)) - footnotes = attr.ib(default=attr.Factory(list)) - tags = attr.ib(default=attr.Factory(list)) - summary = attr.ib(default='') + title: str + author: str + url: str + cover_url: str = '' + id: str = Factory(_default_uuid_string, takes_self=True) + contents: list = Factory(list) + footnotes: list = Factory(list) + tags: list = Factory(list) + summary: str = '' def __iter__(self): return self.contents.__iter__() @@ -74,17 +75,17 @@ class Section: yield chapter.date -@attr.s +@define class Site: """A Site handles checking whether a URL might represent a site, and then extracting the content of a story from said site. """ - session = attr.ib() - footnotes = attr.ib(factory=list, init=False) - options = attr.ib(default=attr.Factory( + session: object = field() + footnotes: list = field(factory=list, init=False) + options: dict = Factory( lambda site: site.get_default_options(), - True - )) + takes_self=True + ) @classmethod def site_key(cls): @@ -288,17 +289,17 @@ class Site: return contents -@attr.s(hash=True) +@define(unsafe_hash=True, frozen=True) class SiteSpecificOption: """Represents a site-specific option that can be configured. Will be added to the CLI as a click.option -- many of these fields correspond to click.option arguments.""" - name = attr.ib() - flag_pattern = attr.ib() - type = attr.ib(default=None) - help = attr.ib(default=None) - default = attr.ib(default=None) + name: str + flag_pattern: str + type: object = None + default: bool = False + help: str = None def as_click_option(self): return click.option( diff --git a/sites/arbitrary.py b/sites/arbitrary.py index 2bfa119..f3f9752 100644 --- a/sites/arbitrary.py +++ b/sites/arbitrary.py @@ -1,7 +1,7 @@ #!/usr/bin/python import logging -import attr +from attrs import define import datetime import json import re @@ -24,26 +24,23 @@ Example JSON: """ -@attr.s +@define class SiteDefinition: - url = attr.ib() - title = attr.ib() - author = attr.ib() - content_selector = attr.ib() + url: str + title: str + author: str + content_selector: str # If present, find something within `content` to use a chapter title; if not found, the link text to it will be used - content_title_selector = attr.ib(default=False) + content_title_selector: str = False # If present, find a specific element in the `content` to be the chapter text - content_text_selector = attr.ib(default=False) + content_text_selector: str = False # If present, it looks for chapters linked from `url`. If not, it assumes `url` points to a chapter. - chapter_selector = attr.ib(default=False) + chapter_selector: str = False # If present, use to find a link to the next content page (only used if not using chapter_selector) - next_selector = attr.ib(default=False) + next_selector: str = False # If present, use to filter out content that matches the selector - filter_selector = attr.ib(default=False) - cover_url = attr.ib(default='') - - # If present, use to also download the images and embed them into the epub. - image_selector = attr.ib(default=False) + filter_selector: str = False + cover_url: str = '' @register