Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Provide a sphinxnotes.any.api package
Browse files Browse the repository at this point in the history
SilverRainZ committed Sep 8, 2024
1 parent 04d193b commit 0df99ca
Showing 12 changed files with 219 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',
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',
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',
7 changes: 1 addition & 6 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -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),
12 changes: 6 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
@@ -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:

@@ -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
==================
12 changes: 4 additions & 8 deletions src/sphinxnotes/any/__init__.py
Original file line number Diff line number Diff line change
@@ -10,25 +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
AnyDomain.label = config.any_domain_name
@@ -51,4 +47,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
@@ -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__)

7 changes: 4 additions & 3 deletions src/sphinxnotes/any/domain.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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():
@@ -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)
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
@@ -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__)

155 changes: 2 additions & 153 deletions src/sphinxnotes/any/schema.py → src/sphinxnotes/any/objects.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
sphinxnotes.any.schema
sphinxnotes.any.objects
~~~~~~~~~~~~~~~~~~~~~~
Schema and object implementations.
Object and schema implementations.
:copyright: Copyright 2021 Shengyu Zhang
:license: BSD, see LICENSE for details.
@@ -12,7 +12,6 @@
import dataclasses
import pickle
import hashlib
from time import strptime, strftime
from abc import ABC, abstractmethod

from sphinx.util import logging
@@ -168,156 +167,6 @@ def anchor(self, refval: str) -> str:
raise NotImplementedError


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


# I am Chinese :D
# So the date formats follow Chinese conventions.
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 ''


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]


@dataclasses.dataclass(frozen=True)
class Object(object):
objtype: str

0 comments on commit 0df99ca

Please sign in to comment.