Package inkfill
Multi-document template engine.
Why?
inkfill
helps you generate multiple documents using jinja2
templates and toml
configuration files.
License
Expand source code
"""Multi-document template engine.
.. include:: ../../README.md
:start-line: 2
"""
from .numerals import commafy
from .numerals import NumFormat
from .numerals import to_alpha
from .numerals import to_cardinal
from .numerals import to_decimal
from .numerals import to_nth
from .numerals import to_ordinal
from .numerals import to_roman
from .filters import compound
from .filters import day_of_month
from .filters import dollars
from .filters import USD
from .xref import Division
from .xref import Ref
from .xref import RefFormat
from .xref import Refs
from .xref import slugify
__version__ = "0.1.0"
__pubdate__ = ""
__all__ = [
"__version__",
"__pubdate__",
# numeric
"commafy",
"NumFormat",
"to_alpha",
"to_cardinal",
"to_decimal",
"to_nth",
"to_ordinal",
"to_roman",
# filters
"compound",
"day_of_month",
"dollars",
"USD",
# xref
"Division",
"Ref",
"RefFormat",
"Refs",
"slugify",
]
Sub-modules
inkfill.filters
-
Common helper functions.
inkfill.numerals
inkfill.registry
-
Name registry.
inkfill.xref
-
Cross-reference helpers.
Functions
def commafy(num: int) ‑> str
-
Return a comma-grouped number.
Expand source code
def commafy(num: int) -> str: """Return a comma-grouped number.""" return f"{num:,}"
def to_alpha(num: int) ‑> str
-
English alphabetical numerals.
Expand source code
def to_alpha(num: int) -> str: """English alphabetical numerals.""" # https://en.wikipedia.org/wiki/English_alphabet result = "" if num < 1: return result while num > 0: num, remainder = divmod(num - 1, 26) result = ALPHABET[remainder] + result return result
def to_cardinal(num: int) ‑> str
-
English cardinal numerals.
Expand source code
def to_cardinal(num: int) -> str: """English cardinal numerals.""" # https://en.wikipedia.org/wiki/Cardinal_numeral # See: https://stackoverflow.com/a/54018199 if num == 0: return "zero" if num < 0: return _space_join("negative", _pos(-num)) return _pos(num)
def to_decimal(num: int) ‑> str
-
Decimal numerals.
Expand source code
def to_decimal(num: int) -> str: """Decimal numerals.""" # https://en.wikipedia.org/wiki/Decimal return str(num)
def to_nth(num: int) ‑> str
-
Numeric ordinal numerals ("1st", "2nd").
Expand source code
def to_nth(num: int) -> str: """Numeric ordinal numerals ("1st", "2nd").""" # https://en.wikipedia.org/wiki/Ordinal_numeral if 10 <= num % 100 <= 20: suffix = "th" else: suffixes = {1: "st", 2: "nd", 3: "rd"} suffix = suffixes.get(num % 10, "th") return str(num) + suffix
def to_ordinal(num: int) ‑> str
-
English ordinal numerals ("first", "second").
Expand source code
def to_ordinal(num: int) -> str: """English ordinal numerals ("first", "second").""" # https://en.wikipedia.org/wiki/Ordinal_numeral cardinal = to_cardinal(num) for check, suffix in ORDINAL_SUFFIXES.items(): if cardinal.endswith(check): return cardinal[: -len(check)] + suffix return cardinal + "th"
def to_roman(num: int) ‑> str
-
Return the Roman numeral.
Expand source code
def to_roman(num: int) -> str: """Return the [Roman numeral](https://en.wikipedia.org/wiki/Roman_numerals).""" result = "" if num < 1: return result idx = 0 while num > 0: for _ in range(num // ROMAN_VALS[idx]): result += ROMAN_SYMS[idx] num -= ROMAN_VALS[idx] idx += 1 return result
def compound(clauses: List[str], conjunction: Literal['for', 'and', 'nor', 'but', 'or', 'yet', 'so'] = 'and', oxford: bool = True) ‑> str
-
Return
clauses
connected withconjunction
and Oxford comma (optional).Expand source code
def compound( clauses: List[str], conjunction: FANBOYS = "and", oxford: bool = True, ) -> str: """Return `clauses` connected with `conjunction` and Oxford comma (optional).""" L = len(clauses) if L == 0: return "" if L == 1: return clauses[0] if L == 2: return f" {conjunction} ".join(clauses) first = ", ".join(clauses[0:-1]) last = clauses[-1] return f"{first}{',' if oxford else ''} {conjunction} {last}"
def day_of_month(dt: datetime.datetime) ‑> str
-
Return date as
{nth} day of {month} {year}
.Expand source code
def day_of_month(dt: datetime) -> str: """Return date as `{nth} day of {month} {year}`.""" return f"{to_nth(dt.day)} day of {dt.strftime('%B %Y')}"
def dollars(num: float) ‑> str
-
Return spelled-out dollars. Commonly used in contracts.
Expand source code
def dollars(num: float) -> str: """Return spelled-out dollars. Commonly used in contracts.""" dec = Decimal(str(num)) whole = int(dec) part = dec % 1 if Decimal("0") < part < Decimal("0.01"): # sub-cent return USD(num, cents=True) result = "" if whole or num == 0: result += f"{say_number(whole)} dollar{'s' if whole != 1 else ''}" if whole and part: result += " and " if part: part *= 100 result += f"{say_number(int(part))} cent{'s' if part != 1 else ''}" elif num != 0: result += " exactly" result += f" ({USD(num, cents=part > 0 or num < 10)})" return result
def USD(num: float, cents: bool = False) ‑> str
-
Return formatted US Dollars.
Expand source code
def USD(num: float, cents: bool = False) -> str: """Return formatted US Dollars.""" if cents: if 0 < num < 0.01: return f"US${num}" return f"US${num:,.2f}" return f"US${num:,}"
def slugify(*names: str) ‑> str
-
Return a slug version of a name.
Expand source code
def slugify(*names: str) -> str: """Return a slug version of a name.""" return RE_REPEATED_DASH.sub( "-", RE_NON_SLUG.sub("-", "-".join(names).lower()) ).strip("-")
Classes
class NumFormat (name: str, numeral: NUMERAL_FUNC, prefix: str = '', suffix: str = '')
-
Numeral formatter.
Expand source code
class NumFormat(Registrable): """Numeral formatter.""" name: str """Numeral system name.""" numeral: NUMERAL_FUNC """Function that converts an integer to a numeral.""" prefix: str = "" """Prefix string when rendering.""" suffix: str = "" """Suffix string when rendering.""" def render(self, num: int, punctuation: bool = False) -> str: """Render the numeral.""" val = self.numeral(num) return val if not punctuation else f"{self.prefix}{val}{self.suffix}" __call__ = render
Ancestors
Class variables
var name : str
-
Inherited from:
Registrable
.name
Name of this object.
var numeral : Callable[[int], str]
-
Function that converts an integer to a numeral.
var prefix : str
-
Prefix string when rendering.
var suffix : str
-
Suffix string when rendering.
var DEFAULT : Registrable
-
Inherited from:
Registrable
.DEFAULT
Default value of this type.
Static methods
def get(name: Union[str, T], default: Optional[T] = None) ‑> ~T
-
Inherited from:
Registrable
.get
Get the requested named object.
Methods
def render(self, num: int, punctuation: bool = False) ‑> str
-
Render the numeral.
Expand source code
def render(self, num: int, punctuation: bool = False) -> str: """Render the numeral.""" val = self.numeral(num) return val if not punctuation else f"{self.prefix}{val}{self.suffix}"
def add(self: T, override: bool = False) ‑> ~T
-
Inherited from:
Registrable
.add
Add this name to the registry.
class Division (name: str, numeral: NumFormat = NumFormat(name='decimal', numeral=<function to_decimal>, prefix='.', suffix=''), define: RefFormat = RefFormat(name='cite-name', render=<function cite_name>), refer: RefFormat = RefFormat(name='kind-cite', render=<function <lambda>>))
-
Document section.
Expand source code
class Division(Registrable): """Document section.""" name: str """Name of this kind.""" numeral: NumFormat = field(default=DECIMAL) """Default numeral formatting for this kind.""" define: RefFormat = field(default=CITE_NAME) """Default formatting for defining a reference of this type.""" refer: RefFormat = field(default=CITE_FULL) """Default formatting for referring to a reference of this type.""" def __str__(self) -> str: return self.name
Ancestors
Class variables
var name : str
-
Inherited from:
Registrable
.name
Name of this object.
var numeral : NumFormat
-
Default numeral formatting for this kind.
var define : RefFormat
-
Default formatting for defining a reference of this type.
var refer : RefFormat
-
Default formatting for referring to a reference of this type.
var DEFAULT : Registrable
-
Inherited from:
Registrable
.DEFAULT
Default value of this type.
Static methods
def get(name: Union[str, T], default: Optional[T] = None) ‑> ~T
-
Inherited from:
Registrable
.get
Get the requested named object.
Methods
def add(self: T, override: bool = False) ‑> ~T
-
Inherited from:
Registrable
.add
Add this name to the registry.
class Ref (name: str = '', slug: str = '', kind: Division = Division(name='Section', numeral=NumFormat(name='decimal', numeral=<function to_decimal>, prefix='.', suffix=''), define=RefFormat(name='cite-name', render=<function cite_name>), refer=RefFormat(name='kind-cite', render=<function <lambda>>)), values: List[int] = <factory>, numerals: List[NumFormat] = <factory>, defines: List[RefFormat] = <factory>, refers: List[RefFormat] = <factory>, is_defined: bool = False)
-
Reference to a term or section of a document.
Expand source code
class Ref: """Reference to a term or section of a document.""" # `name` and `slug` are for referencing # `kind` and formats are for levels/sectioning. name: str = "" """Term or section title.""" slug: str = "" """Unique ID of this reference (should include `kind` + `name`).""" kind: Division = field(default=Section) """What kind of reference is this?""" values: List[int] = field(default_factory=list) """Citation numeral values.""" numerals: List[NumFormat] = field(default_factory=list) """Citation numeral formats.""" defines: List[RefFormat] = field(default_factory=list) """Definition formats.""" refers: List[RefFormat] = field(default_factory=list) """Reference formats.""" is_defined: bool = False """Whether or not this reference has been defined.""" def copy(self) -> Ref: """Return a copy of this reference.""" return Ref( name=self.name, slug=self.slug, kind=self.kind, values=self.values.copy(), numerals=self.numerals.copy(), defines=self.defines.copy(), refers=self.refers.copy(), ) def update_slug(self, slug: str = "") -> Ref: """Update the slug when the name or citation has changed.""" if slug: self.slug = slugify(slug) elif self.name: self.slug = slugify(f"{self.kind}-{self.name}") else: self.slug = slugify(f"{self.kind}-{self.cite}") return self @property def cite(self) -> str: """Return a citation (without the `kind`) to this reference.""" return "".join( num(val, idx > 0) for idx, (val, num) in enumerate(zip(self.values, self.numerals)) ) @property def above_below(self) -> str: """Return 'above' if defined, otherwise 'below'.""" return "above" if self.is_defined else "below" def define(self) -> str: """Return the reference definition.""" if self.is_defined: raise Exception(f"{self.kind} {self.slug} already defined") self.is_defined = True refer = self.kind.refer if len(self.refers) == 0 else self.refers[-1] define = self.kind.define if len(self.defines) == 0 else self.defines[-1] return f"""<span class="def" id="{self.slug}" data-kind="{self.kind}" data-cite="{refer(self).strip()}">{define(self).strip()}</span>""" def refer(self) -> str: """Return a link to the reference definition.""" refer = self.kind.refer if len(self.refers) == 0 else self.refers[-1] return f"""<a class="ref" href="#{self.slug}" data-kind="{self.kind}">{refer(self).strip()}</a>""" # return f"""<a class="ref" href="#{self.slug}" data-kind="{self.kind}"></a>""" __str__ = refer
Class variables
var values : List[int]
-
Citation numeral values.
var numerals : List[NumFormat]
-
Citation numeral formats.
var defines : List[RefFormat]
-
Definition formats.
var refers : List[RefFormat]
-
Reference formats.
var name : str
-
Term or section title.
var slug : str
-
Unique ID of this reference (should include
kind
+name
). var kind : Division
-
What kind of reference is this?
var is_defined : bool
-
Whether or not this reference has been defined.
Instance variables
var cite : str
-
Return a citation (without the
kind
) to this reference.Expand source code
@property def cite(self) -> str: """Return a citation (without the `kind`) to this reference.""" return "".join( num(val, idx > 0) for idx, (val, num) in enumerate(zip(self.values, self.numerals)) )
var above_below : str
-
Return 'above' if defined, otherwise 'below'.
Expand source code
@property def above_below(self) -> str: """Return 'above' if defined, otherwise 'below'.""" return "above" if self.is_defined else "below"
Methods
def copy(self) ‑> Ref
-
Return a copy of this reference.
Expand source code
def copy(self) -> Ref: """Return a copy of this reference.""" return Ref( name=self.name, slug=self.slug, kind=self.kind, values=self.values.copy(), numerals=self.numerals.copy(), defines=self.defines.copy(), refers=self.refers.copy(), )
def update_slug(self, slug: str = '') ‑> Ref
-
Update the slug when the name or citation has changed.
Expand source code
def update_slug(self, slug: str = "") -> Ref: """Update the slug when the name or citation has changed.""" if slug: self.slug = slugify(slug) elif self.name: self.slug = slugify(f"{self.kind}-{self.name}") else: self.slug = slugify(f"{self.kind}-{self.cite}") return self
def define(self) ‑> str
-
Return the reference definition.
Expand source code
def define(self) -> str: """Return the reference definition.""" if self.is_defined: raise Exception(f"{self.kind} {self.slug} already defined") self.is_defined = True refer = self.kind.refer if len(self.refers) == 0 else self.refers[-1] define = self.kind.define if len(self.defines) == 0 else self.defines[-1] return f"""<span class="def" id="{self.slug}" data-kind="{self.kind}" data-cite="{refer(self).strip()}">{define(self).strip()}</span>"""
def refer(self) ‑> str
-
Return a link to the reference definition.
Expand source code
def refer(self) -> str: """Return a link to the reference definition.""" refer = self.kind.refer if len(self.refers) == 0 else self.refers[-1] return f"""<a class="ref" href="#{self.slug}" data-kind="{self.kind}">{refer(self).strip()}</a>""" # return f"""<a class="ref" href="#{self.slug}" data-kind="{self.kind}"></a>"""
class RefFormat (name: str, render: Callable[[Ref], str])
-
Reference format.
Expand source code
class RefFormat(Registrable): """Reference format.""" name: str """Reference format name.""" render: Callable[[Ref], str] """Render the reference.""" def __call__(self, ref: Ref) -> str: """Render the reference.""" return self.render(ref)
Ancestors
Class variables
var name : str
-
Inherited from:
Registrable
.name
Name of this object.
var render : Callable[[Ref], str]
-
Render the reference.
var DEFAULT : Registrable
-
Inherited from:
Registrable
.DEFAULT
Default value of this type.
Static methods
def get(name: Union[str, T], default: Optional[T] = None) ‑> ~T
-
Inherited from:
Registrable
.get
Get the requested named object.
Methods
def add(self: T, override: bool = False) ‑> ~T
-
Inherited from:
Registrable
.add
Add this name to the registry.
class Refs
-
Reference manager.
Construct a new reference manager.
Expand source code
class Refs: """Reference manager.""" stack: List[Ref] """Current document levels.""" store: Dict[str, Ref] """Slugs mapped to references.""" def __init__(self) -> None: """Construct a new reference manager.""" self.reset() def reset(self) -> Refs: """Reset the references.""" self.stack = [] self.store = {} return self @property def undefined(self) -> List[Ref]: """References that were never defined.""" return [ref for ref in self.store.values() if not ref.is_defined] @property def current(self) -> Ref: """Reference to current level.""" return Ref() if len(self.stack) == 0 else self.stack[-1] def push( self, kind: Union[str, Division] = Section, # formatting overrides numeral: Union[str, NumFormat, None] = None, define: Union[str, RefFormat, None] = None, refer: Union[str, RefFormat, None] = None, ) -> Refs: """Add another level.""" level = self.current.copy() level.kind = Division.get(kind) or Section level.values.append(0) level.numerals.append(NumFormat.get(numeral, level.kind.numeral)) level.defines.append(RefFormat.get(define, level.kind.define)) level.refers.append(RefFormat.get(refer, level.kind.refer)) self.stack.append(level) return self def up(self, name: str = "", slug: str = "") -> Ref: """Increment current level.""" ref = self.current.copy() ref.name = name ref.values[-1] += 1 ref.update_slug(slug) # needs name & values updated self.stack[-1] = ref self.store[ref.slug] = ref return ref def pop(self, num: int = 1) -> Refs: """Remove one or more level.""" for _ in range(num): if len(self.stack) == 0: break self.stack.pop() return self ## Short-hand def add(self, ref: Ref) -> Ref: """Add a ref to the store.""" self.store[ref.slug] = ref return ref def term(self, name: str) -> Ref: """Refer to a terms.""" slug = slugify("term", name) if slug in self.store: return self.store[slug] return self.add(Ref(name=name, kind=Term).update_slug()) def see(self, name: str = "", kind: str = "Section", slug: str = "") -> Ref: """Refer to a reference.""" if slug in self.store: return self.store[slug] return self.add(Ref(name=name, kind=Division.get(kind)).update_slug(slug))
Class variables
var stack : List[Ref]
-
Current document levels.
var store : Dict[str, Ref]
-
Slugs mapped to references.
Instance variables
var undefined : List[Ref]
-
References that were never defined.
Expand source code
@property def undefined(self) -> List[Ref]: """References that were never defined.""" return [ref for ref in self.store.values() if not ref.is_defined]
var current : Ref
-
Reference to current level.
Expand source code
@property def current(self) -> Ref: """Reference to current level.""" return Ref() if len(self.stack) == 0 else self.stack[-1]
Methods
def reset(self) ‑> Refs
-
Reset the references.
Expand source code
def reset(self) -> Refs: """Reset the references.""" self.stack = [] self.store = {} return self
def push(self, kind: Union[str, Division] = Division(name='Section', numeral=NumFormat(name='decimal', numeral=<function to_decimal>, prefix='.', suffix=''), define=RefFormat(name='cite-name', render=<function cite_name>), refer=RefFormat(name='kind-cite', render=<function <lambda>>)), numeral: Union[str, NumFormat, None] = None, define: Union[str, RefFormat, None] = None, refer: Union[str, RefFormat, None] = None) ‑> Refs
-
Add another level.
Expand source code
def push( self, kind: Union[str, Division] = Section, # formatting overrides numeral: Union[str, NumFormat, None] = None, define: Union[str, RefFormat, None] = None, refer: Union[str, RefFormat, None] = None, ) -> Refs: """Add another level.""" level = self.current.copy() level.kind = Division.get(kind) or Section level.values.append(0) level.numerals.append(NumFormat.get(numeral, level.kind.numeral)) level.defines.append(RefFormat.get(define, level.kind.define)) level.refers.append(RefFormat.get(refer, level.kind.refer)) self.stack.append(level) return self
def up(self, name: str = '', slug: str = '') ‑> Ref
-
Increment current level.
Expand source code
def up(self, name: str = "", slug: str = "") -> Ref: """Increment current level.""" ref = self.current.copy() ref.name = name ref.values[-1] += 1 ref.update_slug(slug) # needs name & values updated self.stack[-1] = ref self.store[ref.slug] = ref return ref
def pop(self, num: int = 1) ‑> Refs
-
Remove one or more level.
Expand source code
def pop(self, num: int = 1) -> Refs: """Remove one or more level.""" for _ in range(num): if len(self.stack) == 0: break self.stack.pop() return self
def add(self, ref: Ref) ‑> Ref
-
Add a ref to the store.
Expand source code
def add(self, ref: Ref) -> Ref: """Add a ref to the store.""" self.store[ref.slug] = ref return ref
def term(self, name: str) ‑> Ref
-
Refer to a terms.
Expand source code
def term(self, name: str) -> Ref: """Refer to a terms.""" slug = slugify("term", name) if slug in self.store: return self.store[slug] return self.add(Ref(name=name, kind=Term).update_slug())
def see(self, name: str = '', kind: str = 'Section', slug: str = '') ‑> Ref
-
Refer to a reference.
Expand source code
def see(self, name: str = "", kind: str = "Section", slug: str = "") -> Ref: """Refer to a reference.""" if slug in self.store: return self.store[slug] return self.add(Ref(name=name, kind=Division.get(kind)).update_slug(slug))