Skip to content

Commit

Permalink
refactor: Provide a sphinxnotes.any.api package (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
SilverRainZ authored Sep 9, 2024
1 parent 04d193b commit 1862856
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 184 deletions.
2 changes: 1 addition & 1 deletion docs/_schemas/cat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from textwrap import dedent
from any import Schema, Field
from any.api import Schema, Field

cat = Schema(
'cat',
Expand Down
2 changes: 1 addition & 1 deletion docs/_schemas/dog2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from textwrap import dedent
from any import Schema, Field
from any.api import Schema, Field

dog = Schema(
'dog',
Expand Down
2 changes: 1 addition & 1 deletion docs/_schemas/tmplvar.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from textwrap import dedent
from any import Schema, Field
from any.api import Schema, Field

tmplvar = Schema(
'tmplvar',
Expand Down
7 changes: 1 addition & 6 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,7 @@

#
# DOG FOOD CONFIGURATION START
from any import Schema, Field as F
from any.schema import YearIndexer, MonthIndexer
sys.path.insert(0, os.path.abspath('.'))

by_year = YearIndexer()
by_month = MonthIndexer()
from any.api import Schema, Field as F, by_year, by_month

version_schema = Schema('version',
name=F(uniq=True, ref=True, required=True, form=F.Forms.LINES),
Expand Down
12 changes: 6 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Defining Schema

The necessary python classes for writing schema are listed here:

.. autoclass:: any.Schema
.. autoclass:: any.api.Schema

Class-wide shared special keys used in template rendering context:

Expand All @@ -26,15 +26,15 @@ The necessary python classes for writing schema are listed here:

|
.. autoclass:: any.Field
.. autoclass:: any.api.Field

|
.. autoclass:: any.Field.Forms
.. autoclass:: any.api.Field.Forms

.. autoattribute:: any.Field.Forms.PLAIN
.. autoattribute:: any.Field.Forms.WORDS
.. autoattribute:: any.Field.Forms.LINES
.. autoattribute:: any.api.Field.Forms.PLAIN
.. autoattribute:: any.api.Field.Forms.WORDS
.. autoattribute:: any.api.Field.Forms.LINES

Documenting Object
==================
Expand Down
11 changes: 4 additions & 7 deletions src/sphinxnotes/any/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,21 @@

from __future__ import annotations
from typing import TYPE_CHECKING
from importlib.metadata import version

from sphinx.util import logging

from .template import Environment as TemplateEnvironment
from .domain import AnyDomain, warn_missing_reference
from .schema import Schema, Field
from .objects import Schema

if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.config import Config

__version__ = '2.3.1'

logger = logging.getLogger(__name__)

# Re-Export
Field = Field
Schema = Schema


def _config_inited(app: Sphinx, config: Config) -> None:
AnyDomain.name = config.any_domain_name
Expand All @@ -51,4 +48,4 @@ def setup(app: Sphinx):
app.connect('config-inited', _config_inited)
app.connect('warn-missing-reference', warn_missing_reference)

return {'version': __version__}
return {'version': version('sphinxnotes.any')}
27 changes: 27 additions & 0 deletions src/sphinxnotes/any/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
sphinxnotes.any.api
~~~~~~~~~~~~~~~~~~~
Public API for building configuration of extension.
(such as object schema, and so on).
:copyright: Copyright 2024 Shengyu Zhang
:license: BSD, see LICENSE for details.
"""

from .objects import Schema, Field
from .indexers import LiteralIndexer, PathIndexer, YearIndexer, MonthIndexer

# Object schema.
Schema = Schema
Field = Field

# Indexers.
LiteralIndexer = LiteralIndexer
PathIndexer = PathIndexer
YearIndexer = YearIndexer
MonthIndexer = MonthIndexer

# Indexer wrappers.
by_year = YearIndexer()
by_month = MonthIndexer()
3 changes: 1 addition & 2 deletions src/sphinxnotes/any/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@
from docutils.nodes import Node, Element, fully_normalize_name
from docutils.statemachine import StringList
from docutils.parsers.rst import directives

from sphinx import addnodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_id, nested_parse_with_titles
from sphinx.util import logging

from .schema import Schema, Object
from .objects import Schema, Object

logger = logging.getLogger(__name__)

Expand Down
7 changes: 4 additions & 3 deletions src/sphinxnotes/any/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from sphinx.util import logging
from sphinx.util.nodes import make_refnode

from .schema import Schema, Object, RefType, Indexer, LiteralIndexer
from .objects import Schema, Object, RefType, Indexer
from .directives import AnyDirective
from .roles import AnyRole
from .indices import AnyIndex
from .indexers import DEFAULT_INDEXER

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand Down Expand Up @@ -200,7 +201,7 @@ def mkindex(reftype: RefType, indexer: Indexer):
# Create all-in-one role and index (do not distinguish reference fields).
reftypes = [RefType(schema.objtype)]
mkrole(reftypes[0])
mkindex(reftypes[0], LiteralIndexer())
mkindex(reftypes[0], DEFAULT_INDEXER)

# Create {field,indexer}-specificed role and index.
for name, field in schema.fields():
Expand All @@ -210,7 +211,7 @@ def mkindex(reftype: RefType, indexer: Indexer):
mkrole(reftype) # create a role to reference object(s)
# Create a fallback indexer, for possible ambiguous reference
# (if field is not unique).
mkindex(reftype, LiteralIndexer())
mkindex(reftype, DEFAULT_INDEXER)

for indexer in field.indexers:
reftype = RefType(schema.objtype, field=name, indexer=indexer.name)
Expand Down
168 changes: 168 additions & 0 deletions src/sphinxnotes/any/indexers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""
sphinxnotes.any.indexers
~~~~~~~~~~~~~~~~~~~~~~~~
:cls:`objects.Indexer` implementations.
:copyright: Copyright 2024 Shengyu Zhang
:license: BSD, see LICENSE for details.
"""

from typing import Iterable, Literal, Callable
from time import strptime, strftime

from .objects import Indexer, Category, Value


class LiteralIndexer(Indexer):
name = 'literal'

def classify(self, objref: Value) -> list[Category]:
entries = []
for v in objref.as_list():
entries.append(Category(main=v))
return entries

def anchor(self, refval: str) -> str:
return refval


DEFAULT_INDEXER = LiteralIndexer()


class PathIndexer(Indexer):
name = 'path'

def __init__(self, sep: str, maxsplit: Literal[1, 2]):
self.sep = sep
self.maxsplit = maxsplit

def classify(self, objref: Value) -> list[Category]:
entries = []
for v in objref.as_list():
comps = v.split(self.sep, maxsplit=self.maxsplit)
category = Category(main=comps[0], extra=v)
if self.maxsplit == 2:
category.sub = v[1] if len(comps) > 1 else None
entries.append(category)
return entries

def anchor(self, refval: str) -> str:
return refval.split(self.sep, maxsplit=self.maxsplit)[0]


# I am Chinese :D
# So the date formats follow Chinese conventions.
# TODO: conf
INPUTFMTS = ['%Y-%m-%d', '%Y-%m', '%Y']
DISPFMTS_Y = '%Y 年'
DISPFMTS_M = '%m 月'
DISPFMTS_YM = '%Y 年 %m 月'
DISPFMTS_MD = '%m 月 %d 日,%a'


class YearIndexer(Indexer):
name = 'year'

def __init__(
self,
inputfmts: list[str] = INPUTFMTS,
dispfmt_y: str = DISPFMTS_Y,
dispfmt_m: str = DISPFMTS_M,
dispfmt_md: str = DISPFMTS_MD,
):
"""*xxxfmt* are date format used by time.strptime/strftime.
.. seealso:: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes"""
self.inputfmts = inputfmts
self.dispfmt_y = dispfmt_y
self.dispfmt_m = dispfmt_m
self.dispfmt_md = dispfmt_md

def classify(self, objref: Value) -> list[Category]:
entries = []
for v in objref.as_list():
for datefmt in self.inputfmts:
try:
t = strptime(v, datefmt)
except ValueError:
continue # try next datefmt
entries.append(
Category(
main=strftime(self.dispfmt_y, t),
sub=strftime(self.dispfmt_m, t),
extra=strftime(self.dispfmt_md, t),
)
)
return entries

def sort(
self, data: Iterable[Indexer._T], key: Callable[[Indexer._T], Category]
) -> list[Indexer._T]:
def sort_by_time(x: Category):
t1 = strptime(x.main, self.dispfmt_y)
t2 = strptime(x.sub, self.dispfmt_m) if x.sub else None
t3 = strptime(x.extra, self.dispfmt_md) if x.extra else None
return (t1, t2, t3)

return sorted(data, key=lambda x: sort_by_time(key(x)), reverse=True)

def anchor(self, refval: str) -> str:
for datefmt in self.inputfmts:
try:
t = strptime(refval, datefmt)
except ValueError:
continue # try next datefmt
anchor = strftime(self.dispfmt_y, t)
return f'cap-{anchor}'
return ''


class MonthIndexer(Indexer):
name = 'month'

def __init__(
self,
inputfmts: list[str] = INPUTFMTS,
dispfmt_ym: str = DISPFMTS_YM,
dispfmt_md: str = DISPFMTS_MD,
):
self.inputfmts = inputfmts
self.dispfmt_ym = dispfmt_ym
self.dispfmt_md = dispfmt_md

def classify(self, objref: Value) -> list[Category]:
entries = []
for v in objref.as_list():
for datefmt in self.inputfmts:
try:
t = strptime(v, datefmt)
except ValueError:
continue # try next datefmt
entries.append(
Category(
main=strftime(self.dispfmt_ym, t),
extra=strftime(self.dispfmt_md, t),
)
)
return entries

def sort(
self, data: Iterable[Indexer._T], key: Callable[[Indexer._T], Category]
) -> list[Indexer._T]:
def sort_by_time(x: Category):
t1 = strptime(x.main, self.dispfmt_ym)
t2 = strptime(x.sub, self.dispfmt_md) if x.sub else None
return (t1, t2)

return sorted(data, key=lambda x: sort_by_time(key(x)), reverse=True)

def anchor(self, refval: str) -> str:
for datefmt in self.inputfmts:
try:
t = strptime(refval, datefmt)
except ValueError:
continue # try next datefmt
anchor = strftime(self.dispfmt_ym, t)
return f'cap-{anchor}'
return ''
6 changes: 3 additions & 3 deletions src/sphinxnotes/any/indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from typing import Iterable, TypeVar
import re

from sphinx.domains import Domain, Index, IndexEntry
from sphinx.util import logging
from docutils import core, nodes
from docutils.parsers.rst import roles
from sphinx.domains import Domain, Index, IndexEntry
from sphinx.util import logging

from .schema import Schema, Value, Indexer, Category, RefType
from .objects import Schema, Value, Indexer, Category, RefType

logger = logging.getLogger(__name__)

Expand Down
Loading

0 comments on commit 1862856

Please sign in to comment.