Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Bug report
description: A command fails or writes a wrong result
labels: [bug]
body:
- type: markdown
attributes:
value: |
Please leave out keys, server addresses, account data and game files: none of them is needed to report a bug.
- type: input
id: version
attributes:
label: nnnotes version
description: the output of `nnnotes --version`
validations:
required: true
- type: input
id: environment
attributes:
label: Environment
description: operating system, Python version, and the vgmstream / FFmpeg / Node.js versions when the command uses them
validations:
required: true
- type: textarea
id: command
attributes:
label: Command
description: the command you ran, with its options
render: shell
validations:
required: true
- type: textarea
id: result
attributes:
label: What happened
description: the error message or the wrong output, and what you expected instead
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration check
description: the output of `nnnotes config check` (it lists where each setting comes from, never a value)
render: text
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Documentation
url: https://metasekailab.github.io/nnnotes/
about: Configuration, commands, output formats and the Python API
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE/feature.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Feature request
description: A new command, option or output
labels: [enhancement]
body:
- type: textarea
id: use
attributes:
label: What you want to do
description: the task, and what nnnotes would need to read or write for it
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposal
description: the command, option or output format you have in mind, if any
11 changes: 11 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Summary

<!-- What changes, and what users see. -->

## Checks

- [ ] `ruff check .`, `ruff format --check .` and `lint-imports` pass
- [ ] `pytest` passes; new behaviour has tests on synthetic inputs
- [ ] A stage whose output changed has a new version and rewritten golden records
- [ ] `docs/cli.md` is regenerated after a command or option change
- [ ] The documentation describes the change
72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing

Thank you for helping with nnnotes. This page describes how to set up a checkout, run the checks and send a change.

## Setup

```sh
git clone https://github.com/MetaSekaiLab/nnnotes
cd nnnotes
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[test,deck,fonts,docs]"
```

Python 3.11 or later. The tests need no game data, no keys and no network.

## Checks

These are the checks CI runs on every pull request:

```sh
ruff check . # lint
ruff format --check . # formatting (line length 120)
lint-imports # the layer contract of pyproject.toml
pytest # the test suite
mkdocs build --strict # the documentation site
```

- **Layers.** Each subpackage imports only the layers below it ([architecture](docs/architecture.md)). A module
that needs something from a higher layer takes it as an argument instead.
- **Golden records.** `tests/golden/` records the output of each stage version. When a change alters a stage's
output, raise that stage's (or converter's) version and rewrite the records with
`GOLDENS_UPDATE=1 pytest tests/pipeline/test_goldens.py`, using the library versions of
`.github/goldens-constraints.txt` (`pip install -c .github/goldens-constraints.txt -e ".[test]"`).
- **Command reference.** `docs/cli.md` is generated from the parser. After changing a command or an option, run
`python -m nnnotes.cli.reference docs/cli.md`; a test fails while the file is out of date.

## Tests and fixtures

Tests build their inputs synthetically: small Unity-like objects, catalogs, master tables and audio made by the tests
themselves (`tests/helpers/`). Fixtures contain no game files, no data extracted from the game and no keys or server
addresses, so the suite runs anywhere. Test a change with the smallest synthetic input that shows the behaviour.

Exports must stay deterministic ([determinism](docs/determinism.md)): sort what the game does not order, keep
times and machine paths out of outputs, and write JSON through `nnnotes.core.jsonio`.

## Commits and pull requests

- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) with a lowercase type
(`feat`, `fix`, `docs`, `refactor`, `test`, `build`, `ci`, `chore`) and an optional scope (`feat(cli): ...`).
The body says what changed and what users see. A breaking change uses `!` after the type or scope and a
`BREAKING CHANGE:` footer. The commit messages and the pull request title are checked in CI.
- One topic per pull request. Pull requests are merged by rebase, so each commit should build and pass the checks.
- `CHANGELOG.md` is generated from the commits by [git-cliff](https://git-cliff.org/) when a version is released;
do not edit it by hand.

## Releases

Versions follow [Semantic Versioning](https://semver.org/) over the command line, the configuration keys, the JSON
schemas and the documented Python API. A release commit sets `__version__` in `src/nnnotes/__init__.py` and
regenerates the changelog:

```sh
git cliff --tag vX.Y.Z -o CHANGELOG.md
```

Pushing the tag `vX.Y.Z` builds the sdist and the wheel, checks them and publishes them to PyPI and the GitHub
release.

## License

By contributing you agree that your contribution is licensed under the [MIT License](LICENSE).
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ The [command reference](https://metasekailab.github.io/nnnotes/cli/) lists every
- Reference: [commands](https://metasekailab.github.io/nnnotes/cli/),
[command outputs](https://metasekailab.github.io/nnnotes/commands/),
[JSON schemas](https://metasekailab.github.io/nnnotes/schemas/),
[Python API](https://metasekailab.github.io/nnnotes/api/),
[contracts](https://metasekailab.github.io/nnnotes/contracts/), [stages](https://metasekailab.github.io/nnnotes/stages/),
[views](https://metasekailab.github.io/nnnotes/views/).
- Explanation: [architecture](https://metasekailab.github.io/nnnotes/architecture/),
Expand All @@ -89,6 +90,11 @@ line endings and non-finite numbers written as `1e999`. Other versions can encod
Versions follow [Semantic Versioning](https://semver.org/) over the command line, the configuration keys, the JSON
schemas and the documented Python API.

## Contributing

Setup, checks and commit conventions are in
[CONTRIBUTING.md](https://github.com/MetaSekaiLab/nnnotes/blob/main/CONTRIBUTING.md).

## License

MIT, see [LICENSE](https://github.com/MetaSekaiLab/nnnotes/blob/main/LICENSE).
6 changes: 6 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ nnnotes site build -o out/site --all --player path/to/ournotes-player
- 参考:[命令](https://metasekailab.github.io/nnnotes/cli/)、
[命令输出](https://metasekailab.github.io/nnnotes/commands/)、
[JSON schema](https://metasekailab.github.io/nnnotes/schemas/)、
[Python API](https://metasekailab.github.io/nnnotes/api/)、
[契约](https://metasekailab.github.io/nnnotes/contracts/)、[stage](https://metasekailab.github.io/nnnotes/stages/)、
[视图](https://metasekailab.github.io/nnnotes/views/)。
- 原理:[架构](https://metasekailab.github.io/nnnotes/architecture/)、
Expand All @@ -85,6 +86,11 @@ Pillow 写出的 PNG 像素相同,字节可能不同)。
每个版本的改动见 [CHANGELOG.md](https://github.com/MetaSekaiLab/nnnotes/blob/main/CHANGELOG.md)。版本号遵循
[语义化版本](https://semver.org/lang/zh-CN/),约束的接口是命令行、配置键、JSON schema 和文档中的 Python API。

## 参与贡献

开发环境、检查与提交规范见
[CONTRIBUTING.md](https://github.com/MetaSekaiLab/nnnotes/blob/main/CONTRIBUTING.md)。

## License

MIT,见 [LICENSE](https://github.com/MetaSekaiLab/nnnotes/blob/main/LICENSE)。
43 changes: 43 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Python API

The command line is built on the modules below; scripts can use them directly. The names each module lists in its
`__all__` are its documented API, and the most used ones are also importable from the top-level package:

```python
from nnnotes import Config, Session

session = Session(Config.load()) # the config file and the NNNOTES_* environment variables
catalog = session.catalog() # the region's catalog, with its bundles
master = session.master_dir() # the decoded master data directory
```

A missing or invalid setting raises `ConfigError`, naming the setting and never its value; a failed game API call
raises `GameApiError`. Other names are internal and can change in any version.

## Configuration and sources

::: nnnotes.source.config

::: nnnotes.source.session

::: nnnotes.source.catalog

::: nnnotes.source.addressables

::: nnnotes.source.master

::: nnnotes.source.gameapi

::: nnnotes.source.jp

## Music data

::: nnnotes.musicdata

::: nnnotes.musicdata.deckdata

## Site and schemas

::: nnnotes.site.webmodel

::: nnnotes.schemas
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ configuration, and the exports stay in the local directories you choose.

- **Reference**

[Commands](cli.md), [command outputs](commands.md), [JSON schemas](schemas.md), [contracts](contracts.md),
[stages](stages.md), [views](views.md), [replay data](replay.md), [UI library](ui.md).
[Commands](cli.md), [command outputs](commands.md), [JSON schemas](schemas.md), [Python API](api.md),
[contracts](contracts.md), [stages](stages.md), [views](views.md), [replay data](replay.md),
[UI library](ui.md).

- **Explanation**

Expand Down
19 changes: 19 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ nav:
- Commands: cli.md
- Command outputs: commands.md
- JSON schemas: schemas.md
- Python API: api.md
- Contracts: contracts.md
- Stages: stages.md
- Views: views.md
Expand All @@ -63,6 +64,24 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.superfences

plugins:
- search
- mkdocstrings:
handlers:
python:
paths: [src]
options:
docstring_style: null
filters: public
heading_level: 3
members_order: source
separate_signature: true
show_root_heading: true
show_root_full_path: true
show_source: false
show_signature_annotations: true
show_symbol_type_heading: true

validation:
links:
anchors: warn
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dynamic = ["version"]
[project.optional-dependencies]
test = ["pytest>=8", "ruff>=0.15", "jsonschema>=4.18", "import-linter>=2.1"]
fonts = ["fonttools>=4.50", "freetype-py>=2.5", "scipy>=1.13"]
docs = ["mkdocs>=1.6,<2", "mkdocs-material>=9.5"]
docs = ["mkdocs>=1.6,<2", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.29"]
# the deck model of music-data: one nnnotes version carries one ournotes-deck release (.github/workflows/deck.yml moves
# the pin when ournotes-deck publishes a newer one)
deck = ["ournotes-sim==0.0.8"]
Expand All @@ -49,7 +49,7 @@ path = "src/nnnotes/__init__.py"
packages = ["src/nnnotes"]

[tool.hatch.build.targets.sdist]
include = ["/src", "/tests", "/docs", "/scripts", "/CHANGELOG.md", "/README.md", "/README.zh-CN.md", "/mkdocs.yml", "/LICENSE"]
include = ["/src", "/tests", "/docs", "/scripts", "/CHANGELOG.md", "/CONTRIBUTING.md", "/README.md", "/README.zh-CN.md", "/mkdocs.yml", "/LICENSE"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down
45 changes: 45 additions & 0 deletions src/nnnotes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,51 @@
Reads Addressables catalogs, fetches and decrypts asset bundles, decodes master data and exports stories, Live2D
models, spots, shaders, CRI audio and live charts as JSON and common file formats. Game files, keys and server
addresses are supplied by the user through the configuration; nothing of the game is included.

The names below are the entry points of the Python API, imported on first use:

from nnnotes import Config, Session

session = Session(Config.load())
catalog = session.catalog()

The subpackages and the names in their `__all__` are the documented API; other names are internal.
"""

from __future__ import annotations

import importlib
from typing import TYPE_CHECKING

__version__ = "0.2.1"

_LAZY = {
"Catalog": "nnnotes.source.catalog",
"Config": "nnnotes.source.config",
"ConfigError": "nnnotes.source.config",
"GameApiError": "nnnotes.source.gameapi",
"MusicDataError": "nnnotes.musicdata",
"Session": "nnnotes.source.session",
}

__all__ = ["Catalog", "Config", "ConfigError", "GameApiError", "MusicDataError", "Session", "__version__"]

if TYPE_CHECKING:
from .musicdata import MusicDataError
from .source.catalog import Catalog
from .source.config import Config, ConfigError
from .source.gameapi import GameApiError
from .source.session import Session


def __getattr__(name: str):
module = _LAZY.get(name)
if module is None:
raise AttributeError(f"module 'nnnotes' has no attribute {name!r}")
value = getattr(importlib.import_module(module), name)
globals()[name] = value
return value


def __dir__() -> list[str]:
return sorted({*globals(), *_LAZY})
Loading
Loading