diff --git a/pyproject.toml b/pyproject.toml index 6497f68..e77401a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ namespaces = true # ----------------------------------------- Project Metadata ------------------------------------- # [project] -version = "0.0.0.dev338" +version = "0.0.0.dev339" name = "ControlMan" dependencies = [ "packaging >= 23.2, < 24", @@ -26,18 +26,18 @@ dependencies = [ "referencing == 0.35.1", "jsonpath-ng == 1.6.1", "ruamel.yaml == 0.17.40", - "PyLinks == 0.0.0.dev40", - "LoggerMan == 0.0.0.dev56", - "PySerials == 0.0.0.dev30", - "GitTidy == 0.0.0.dev53", + "PyLinks == 0.0.0.dev41", + "LoggerMan == 0.0.0.dev57", + "PySerials == 0.0.0.dev31", + "GitTidy == 0.0.0.dev54", "PkgData == 0.0.0.dev5", - "PyShellMan == 0.0.0.dev17", + "PyShellMan == 0.0.0.dev18", "PySyntax == 0.0.0.dev3", - "ExceptionMan == 0.0.0.dev27", - "MDit == 0.0.0.dev27", - "JSONSchemata == 0.0.0.dev26", + "ExceptionMan == 0.0.0.dev28", + "MDit == 0.0.0.dev28", + "JSONSchemata == 0.0.0.dev27", "VersionMan == 0.0.0.dev248", "HTMP == 0.0.0.dev5", - "LicenseMan == 0.0.0.dev13", + "LicenseMan == 0.0.0.dev14", ] requires-python = ">=3.10" diff --git a/requirements.txt b/requirements.txt index 03f3f0f..aa676f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,16 +4,16 @@ jsonschema >= 4.23, < 5 referencing == 0.35.1 jsonpath-ng == 1.6.1 ruamel.yaml == 0.17.40 -PyLinks == 0.0.0.dev40 -LoggerMan == 0.0.0.dev56 -PySerials == 0.0.0.dev30 -GitTidy == 0.0.0.dev53 +PyLinks == 0.0.0.dev41 +LoggerMan == 0.0.0.dev57 +PySerials == 0.0.0.dev31 +GitTidy == 0.0.0.dev54 PkgData == 0.0.0.dev5 -PyShellMan == 0.0.0.dev17 +PyShellMan == 0.0.0.dev18 PySyntax == 0.0.0.dev3 -ExceptionMan == 0.0.0.dev27 -MDit == 0.0.0.dev27 -JSONSchemata == 0.0.0.dev26 +ExceptionMan == 0.0.0.dev28 +MDit == 0.0.0.dev28 +JSONSchemata == 0.0.0.dev27 VersionMan == 0.0.0.dev248 HTMP == 0.0.0.dev5 -LicenseMan == 0.0.0.dev13 \ No newline at end of file +LicenseMan == 0.0.0.dev14 \ No newline at end of file diff --git a/src/controlman/_data/schema/def/docfile.yaml b/src/controlman/_data/schema/def/docfile.yaml index 1ce7afc..78d5b37 100644 --- a/src/controlman/_data/schema/def/docfile.yaml +++ b/src/controlman/_data/schema/def/docfile.yaml @@ -28,7 +28,7 @@ properties: title: | URL of the GitHub repository's main `README` file (cf. [`doc.github`](#ccc-doc.github)). - default: ${{ repo.url.blob }}/${{ .path }} + default: ${{ repo.url.blob }}$/${{ .path }}$ examples: - "https://github.com/RepoDynamics/PyPackIT/blob/main/README.md" $ref: https://jsonschemata.repodynamics.com/url/https \ No newline at end of file diff --git a/src/controlman/_data/schema/def/entity.yaml b/src/controlman/_data/schema/def/entity.yaml index 0bad0bd..dd1f0a9 100644 --- a/src/controlman/_data/schema/def/entity.yaml +++ b/src/controlman/_data/schema/def/entity.yaml @@ -125,8 +125,8 @@ properties: Define all roles in the project in [`role`](#ccc-role), and use them here with templating. examples: - - [ '${{ role.concept}}' ] - - [ '${{ role.concept}}', '${{ role.dev }}' ] + - [ '${{ role.concept }}$' ] + - [ '${{ role.concept }}$', '${{ role.dev }}$' ] type: array uniqueItems: true items: @@ -181,7 +181,7 @@ properties: url: title: Email URI description: A `mailto` URI for the email address. - default: 'mailto:${{ .id }}' + default: 'mailto:${{ .id }}$' $ref: https://jsonschemata.repodynamics.com/uri/mailto examples: - id: info@repodynamic.com @@ -217,7 +217,7 @@ properties: title: ORCID account URL description: | This is automatically set to `https://orcid.org/{user}`. - default: 'https://orcid.org/${{ .id }}' + default: 'https://orcid.org/${{ .id }}$' $ref: https://jsonschemata.repodynamics.com/url/orcid/user get_pubs: title: Get publications. @@ -251,7 +251,7 @@ properties: $ref: https://jsonschemata.repodynamics.com/id/researchgate url: title: ResearchGate account URL - default: 'https://researchgate.net/profile/${{ .id }}' + default: 'https://researchgate.net/profile/${{ .id }}$' $ref: https://jsonschemata.repodynamics.com/url/researchgate/user linkedin: title: LinkedIn Account @@ -273,7 +273,7 @@ properties: $ref: https://jsonschemata.repodynamics.com/id/linkedin url: title: LinkedIn URL - default: 'https://linkedin.com/in/${{ .id }}' + default: 'https://linkedin.com/in/${{ .id }}$' $ref: https://jsonschemata.repodynamics.com/url/linkedin/user twitter: title: Twitter account @@ -293,7 +293,7 @@ properties: $ref: https://jsonschemata.repodynamics.com/id/twitter url: title: Twitter account URL - default: 'https://twitter.com/${{ .id }}' + default: 'https://twitter.com/${{ .id }}$' $ref: https://jsonschemata.repodynamics.com/url/twitter/user required: [ id ] address: diff --git a/src/controlman/_data/schema/def/env-file.yaml b/src/controlman/_data/schema/def/env-file.yaml index 88ce39b..d8a37f6 100644 --- a/src/controlman/_data/schema/def/env-file.yaml +++ b/src/controlman/_data/schema/def/env-file.yaml @@ -15,16 +15,16 @@ properties: title: Name of the environment. type: string examples: - - ${{ repo.name }}-dev - - ${{ repo.name }}-web - - ${{ pkg.name }}-test + - ${{ repo.name }}$-dev + - ${{ repo.name }}$-web + - ${{ pkg.name }}$-test - my-custom-env path: title: Path to the environment file. $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd examples: - - ${{ dir.pkg.root }}/environment.yaml - - ${{ dir.web.root }}/env.yml + - ${{ dir.pkg.root }}$/environment.yaml + - ${{ dir.web.root }}$/env.yml - .github/dev_env.yaml - environment.yaml pip: @@ -37,8 +37,8 @@ properties: title: Path to the environment file. type: string examples: - - ${{ dir.pkg.root }}/requirements.txt - - ${{ dir.web.root }}/requirements.txt + - ${{ dir.pkg.root }}$/requirements.txt + - ${{ dir.web.root }}$/requirements.txt - .github/dev_req.txt - requirements.txt required: [ conda ] diff --git a/src/controlman/_data/schema/def/gh-form-body.yaml b/src/controlman/_data/schema/def/gh-form-body.yaml index d8430fc..c7936df 100644 --- a/src/controlman/_data/schema/def/gh-form-body.yaml +++ b/src/controlman/_data/schema/def/gh-form-body.yaml @@ -18,8 +18,8 @@ description: | and whose `options` are an array of all the supported package versions (for version) or active release branches (for branch) in the repository. The `options` can be defined dynamically, by setting it equal to - `${‎{ package.releases.package_versions }}` (for version) or - `${‎{ package.releases.branch_names }}` (for branch). + `${‎{ package.releases.package_versions }}$` (for version) or + `${‎{ package.releases.branch_names }}$` (for branch). It makes sense to add version to forms that concern an issue with the package, and branch to forms for package-independent issues. When branch is set, {{ ccc.name }} parses the the user input diff --git a/src/controlman/_data/schema/def/license-component-config.yaml b/src/controlman/_data/schema/def/license-component-config.yaml index b844c51..56054c0 100644 --- a/src/controlman/_data/schema/def/license-component-config.yaml +++ b/src/controlman/_data/schema/def/license-component-config.yaml @@ -5,17 +5,40 @@ type: object properties: plain: type: object + default: { } properties: title: type: [ boolean, string ] default: true copyright_notice: type: [ boolean, string ] - default: ${{ copyright.notice }} + default: ${{ copyright }}$ optionals: type: [ boolean, array ] alts: type: object + default: + copyright: ${{ copyright }}$ + author: ${{ team.owner.name.full }}$ + softwareName: ${{ name }}$ + description: ${{ title | $.name }}$ + contributor: ${{ team.owner.name.full }}$ + copyrightHolderAsIs: ${{ team.owner.name.full }}$ + initialDeveloper: ${{ team.owner.name.full }}$ + copyrightHolderLiability: ${{ team.owner.name.full }}$ + version: ${{ version }}$ + copyrightHolder0: ${{ team.owner.name.full }}$ + copyrightHolder1: ${{ team.owner.name.full }}$ + copyrightHolder2: ${{ team.owner.name.full }}$ + copyrightHolder3: ${{ team.owner.name.full }}$ + creator: ${{ team.owner.name.full }}$ + owner: ${{ team.owner.name.full }}$ + maintainer: ${{ team.owner.name.full }}$ + organization: ${{ team.owner.name.full }}$ + email: ${{ team.owner.email.url }}$ + softwareVersion: ${{ version }}$ + additionalProperties: + type: string line_length: $ref: https://jsonschemata.repodynamics.com/number/non-negative default: 88 diff --git a/src/controlman/_data/schema/def/license-component.yaml b/src/controlman/_data/schema/def/license-component.yaml index 5b79842..63b7001 100644 --- a/src/controlman/_data/schema/def/license-component.yaml +++ b/src/controlman/_data/schema/def/license-component.yaml @@ -22,7 +22,7 @@ properties: text_plain: type: string pattern: ^(OFL|PATENTS|((LICENSE|COPYING)(-[a-zA-Z0-9-.]+)?))(?i:.(txt|md|rst))?$ - default: LICENSE-${{ .id }}.md + default: LICENSE-${{ .id }}$.md examples: - OFL - PATENTS @@ -36,7 +36,7 @@ properties: - LICENSE-MIT.md header_plain: type: string - default: COPYRIGHT-${{ .id }}.md + default: COPYRIGHT-${{ .id }}$.md id: type: string name: @@ -92,7 +92,7 @@ properties: title: URL description: | URL to the repository's license file. - default: ${{ repo.url.blob }}/${{ .path.text_plain }} + default: ${{ repo.url.blob }}$/${{ .path.text_plain }}$ examples: - "https://github.com/RepoDynamics/PyPackIT/blob/main/LICENSE" $ref: https://jsonschemata.repodynamics.com/url/https @@ -100,7 +100,7 @@ properties: title: URL description: | URL to the repository's license header file. - default: ${{ repo.url.blob }}/${{ .path.header_plain }} + default: ${{ repo.url.blob }}$/${{ .path.header_plain }}$ cross_refs: type: array items: diff --git a/src/controlman/_data/schema/def/media-file.yaml b/src/controlman/_data/schema/def/media-file.yaml index 959f20b..4ac1900 100644 --- a/src/controlman/_data/schema/def/media-file.yaml +++ b/src/controlman/_data/schema/def/media-file.yaml @@ -9,7 +9,7 @@ properties: path: title: Absolute Path $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd - default: ${{ web.path.source }}/${{ .path_web }} + default: ${{ web.path.source }}$/${{ .path_web }}$ path_web: title: Web Path $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd @@ -19,4 +19,4 @@ properties: url: title: URL $ref: https://jsonschemata.repodynamics.com/url/https - default: ${{ repo.url.raw }}/${{ .path }} \ No newline at end of file + default: ${{ repo.url.raw }}$/${{ .path }}$ \ No newline at end of file diff --git a/src/controlman/_data/schema/def/pkg-entry.yaml b/src/controlman/_data/schema/def/pkg-entry.yaml index ef280b7..42c5689 100644 --- a/src/controlman/_data/schema/def/pkg-entry.yaml +++ b/src/controlman/_data/schema/def/pkg-entry.yaml @@ -15,10 +15,10 @@ additionalProperties: type: string examples: - main: - name: ${{ pkg.import_name }} + name: ${{ pkg.import_name }}$ description: Main CLI entry of the package. - ref: "${{ pkg.import_name }}:__main__" + ref: "${{ pkg.import_name }}$:__main__" - gui_main: name: entry_point_1 description: Main GUI entry of the package. - ref: "${{ pkg.import_name}}:gui" \ No newline at end of file + ref: "${{ pkg.import_name }}$:gui" \ No newline at end of file diff --git a/src/controlman/_data/schema/def/pkg.yaml b/src/controlman/_data/schema/def/pkg.yaml index 1bfbd1b..d6554ab 100644 --- a/src/controlman/_data/schema/def/pkg.yaml +++ b/src/controlman/_data/schema/def/pkg.yaml @@ -37,10 +37,10 @@ properties: default: src source: $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd - default: ${{ .root }}/${{ .source_rel }} + default: ${{ .root }}$/${{ .source_rel }}$ import: type: string - default: ${{ .source }}/${{ ..import_name }} + default: ${{ .source }}$/${{ ..import_name }}$ name: title: Name of the Python distribution package. description: | @@ -103,6 +103,14 @@ properties: :::: type: string pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])$ + default: | + #{{ + import re + + project_name = "${{ name }}$" + project_name_normalized = re.sub(r"[^a-zA-Z0-9._-]", "-", project_name) + return re.sub(r"^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$", "", project_name_normalized) + }}# import_name: title: Name of the top-level Python import package. description: | @@ -133,6 +141,14 @@ properties: your package `My-Package`, and the import name will be `my_package`. type: string pattern: ^[a-zA-Z_]|[a-zA-Z_][a-zA-Z0-9_]*$ + default: | + #{{ + import re + + pkg_name = "${{ .name }}$" + import_name_raw = re.sub(r"[^a-zA-Z0-9]", "_", pkg_name.lower()) + return re.sub(r"^[0-9]+", "", import_name_raw) + }}# description: title: Package Description description: | @@ -147,7 +163,7 @@ properties: to provide a brief overview of the package. For example, it is displayed on the package's homepage on PyPI and TestPyPI. ::: - default: ${{ title }} + default: ${{ title }}$ $ref: https://jsonschemata.repodynamics.com/string/oneline keywords: title: Keywords @@ -164,7 +180,7 @@ properties: For example, they are displayed on the package's homepage on PyPI and TestPyPI, and can be used to filter packages in search results. ::: - default: ${{ keywords }} + default: ${{ keywords }}$ $ref: https://jsonschemata.repodynamics.com/array/unique-strings classifiers: title: | @@ -209,10 +225,10 @@ properties: - `issue`, `bug`, `tracker`, `report` - `sponsor`, `funding`, `donate`, `donation` default: - Homepage: ${{ web.url.home }} - Source: ${{ repo.url.home }} - Download: ${{ repo.url.releases.home }} - Issue-Tracker: ${{ repo.url.issues.home }} + Homepage: ${{ web.url.home }}$ + Source: ${{ repo.url.home }}$ + Download: ${{ repo.url.releases.home }}$ + Issue-Tracker: ${{ repo.url.issues.home }}$ type: object additionalProperties: title: URL @@ -227,7 +243,8 @@ properties: authors: title: Authors description: | - Core author(s) of the package, ordered by contribution (highest first). + Core authors of the package, ordered by contribution (highest first). + Authors are defined by their [team](#ccc-team)-member IDs. :::{admonition} Usage :class: dropdown note @@ -238,23 +255,33 @@ properties: to provide credit to the authors of the package. For example, they are displayed on the package's homepage on PyPI and TestPyPI. ::: - default: ${{ citation.authors }} - $ref: https://controlman.repodynamics.com/schema/entities + $ref: https://jsonschemata.repodynamics.com/array/unique-strings + examples: + - ${{ citation.authors }}$ + - [ owner, some_author_id ] + maintainers: + title: Maintainers + description: | + Core maintainers of the package, ordered by contribution (highest first). + Maintainers are defined by their [team](#ccc-team)-member IDs. + + :::{admonition} Usage + :class: dropdown note + + Maintainers are added to the package metadata (see the + [`project.maintainers`](https://packaging.python.org/en/latest/specifications/pyproject-toml/#authors-maintainers) + key of the `pyproject.toml` file) and are used by indexing services and package managers + to provide credit to the maintainers of the package. + For example, they are displayed on the package's homepage on PyPI and TestPyPI. + ::: + description_default: All maintainers defined in `$.maintainers`. + $ref: https://jsonschemata.repodynamics.com/array/unique-strings examples: - - ${{ citation.authors }} - - [ '${{ team.owner }}', '${{ team.some_author }}' ] - - - name: - first: Jane - last: Doe - email: - id: jane@doe.com - - name: - legal: XYZ Organization - email: - id: contact@xyz.org + - ${{ citation.authors }}$ + - [ owner, some_author_id ] license: type: string - default: ${{ license.expression }} + default: ${{ license.expression }}$ typed: description: Whether the package is typed type: boolean @@ -336,14 +363,21 @@ properties: title: Cibuild configurations. $ref: https://controlman.repodynamics.com/schema/pkg-os-cibuild required: [ name, runner ] - independent: - type: boolean + os_independent: + type: boolean + default: | + #{{ + os_data = ${{ .os }}$ + pure_python = ${{ .python.pure }}$ + return pure_python and len(os_data) == 3 + }}# python: title: Python configurations for the package. description: | This is only required for the package, and is automatically reused for the test suite. type: object default: { } + required: [ version ] properties: version: title: Python version requirements. @@ -418,6 +452,11 @@ properties: required: [ spec ] pure: type: boolean + default: | + #{{ + os_data = ${{ ..os }}$ + return not any("ci_build" in os for os in os_data.values()) + }}# implementation: type: string enum: @@ -428,7 +467,6 @@ properties: - micropython - stackless default: cpython - required: [ version ] entry: type: object default: { } @@ -438,9 +476,9 @@ properties: description: Python API entry points for the project default: main: - name: ${{ ....import_name }} + name: ${{ ....import_name }}$ description: Main Python API entry of the package. - ref: '${{ ....import_name }}:__init__' + ref: '${{ ....import_name }}$:__init__' $ref: https://controlman.repodynamics.com/schema/pkg-entry cli: description: Scripts for the project @@ -500,10 +538,10 @@ properties: $ref: https://controlman.repodynamics.com/schema/env-file default: conda: - name: ${{ ....import_name }} - path: ${{ ....path.root }}/conda_env.yaml + name: ${{ ....import_name }}$ + path: ${{ ....path.root }}$/conda_env.yaml pip: - path: ${{ ....path.root }}/requirements.txt + path: ${{ ....path.root }}$/requirements.txt file: title: Package documentation settings type: object @@ -521,7 +559,7 @@ properties: to include in the source distribution. type: array examples: - - - graft ${{ dir.pkg.import }} + - - graft ${{ dir.pkg.import }}$ - global-exclude *.py[cod] __pycache__ *.so uniqueItems: true items: @@ -599,19 +637,19 @@ properties: default: git match: type: array - default: [ '${{tag.version.prefix}}[0-9]*.[0-9]*.[0-9]*'] + default: [ '${{ tag.version.prefix }}$[0-9]*.[0-9]*.[0-9]*'] items: type: string default-tag: type: string - default: ${{ tag.version.prefix }}0.0.0 + default: ${{ tag.version.prefix }}$0.0.0 tag2version: type: object default: { } properties: rmprefix: type: string - default: ${{ tag.version.prefix }} + default: ${{ tag.version.prefix }}$ format: type: object default: {} @@ -641,10 +679,10 @@ properties: default: true source-file: type: string - default: '${{ .....path.source_rel }}/${{ .....import_name }}/__init__.py' + default: '${{ .....path.source_rel }}$/${{ .....import_name }}$/__init__.py' build-file: type: string - default: '${{ .....import_name }}/__init__.py' + default: '${{ .....import_name }}$/__init__.py' replacement: type: string default: | @@ -669,7 +707,7 @@ properties: properties: home: title: Homepage URL of the PyPI package. - default: https://pypi.org/project/${{ ...name }} + default: https://pypi.org/project/${{ ...name }}$ $ref: https://jsonschemata.repodynamics.com/url/http-ftp-sftp conda: title: Conda Recipe @@ -693,7 +731,7 @@ properties: readme: title: README file of the Conda package. default: - path: ${{ ...path.root }}/README_conda.md + path: ${{ ...path.root }}$/README_conda.md content: id: pypackit $ref: https://controlman.repodynamics.com/schema/docfile @@ -704,7 +742,7 @@ properties: properties: home: title: Homepage URL of the Conda package. - default: https://anaconda.org/conda-forge/${{ ...name }} + default: https://anaconda.org/conda-forge/${{ ...name }}$ $ref: https://jsonschemata.repodynamics.com/url/http-ftp-sftp examples: - about: @@ -726,12 +764,3 @@ properties: recipe-maintainers: - jakirkham feedstock-name: scipy - python_api: - title: Python API - description: | - Python API settings. - The Python API is the interface of the package that is used by other Python packages. - It is defined by the package's public modules, classes, functions, and methods. - The API is used by other packages to interact with the package, and is the part of the package - that is considered stable and should not change between versions. - type: diff --git a/src/controlman/_data/schema/def/reference.yaml b/src/controlman/_data/schema/def/reference.yaml index 0aafd17..ea9811d 100644 --- a/src/controlman/_data/schema/def/reference.yaml +++ b/src/controlman/_data/schema/def/reference.yaml @@ -23,8 +23,8 @@ properties: items: title: An author of the work. examples: - - '${{ team.owner }}' - - ${{ team.some_author_id }} + - '${{ team.owner }}$' + - ${{ team.some_author_id }}$ - name: first: John last: Doe diff --git a/src/controlman/_data/schema/def/sphinx-theme-pydata.yaml b/src/controlman/_data/schema/def/sphinx-theme-pydata.yaml index 27a2e36..eedea94 100644 --- a/src/controlman/_data/schema/def/sphinx-theme-pydata.yaml +++ b/src/controlman/_data/schema/def/sphinx-theme-pydata.yaml @@ -36,22 +36,22 @@ properties: examples: - name: Conda Distribution icon: fa-custom fa-conda - url: ${{ pkg.conda.url.home }} + url: ${{ pkg.conda.url.home }}$ - name: Email icon: fa-regular fa-envelope - url: ${{ team.owner.email.url }} + url: ${{ team.owner.email.url }}$ - name: Twitter icon: fa-brands fa-twitter - url: ${{ team.owner.twitter.url }} + url: ${{ team.owner.twitter.url }}$ - name: LinkedIn icon: fa-brands fa-linkedin - url: ${{ team.owner.linkedin.url }} + url: ${{ team.owner.linkedin.url }}$ - name: ResearchGate icon: fa-brands fa-researchgate - url: ${{ team.owner.researchgate.url }} + url: ${{ team.owner.researchgate.url }}$ - name: ORCiD icon: fa-brands fa-orcid - url: ${{ team.owner.orcid.url }} + url: ${{ team.owner.orcid.url }}$ properties: name: title: Label for the icon. @@ -84,7 +84,7 @@ properties: title: Quicklinks for the navbar. type: array uniqueItems: true - default: ${{ path.web.subsections }} + default: ${{ path.web.subsections }}$ items: type: object additionalProperties: false diff --git a/src/controlman/_data/schema/main.yaml b/src/controlman/_data/schema/main.yaml index bb55d1d..225d1ef 100644 --- a/src/controlman/_data/schema/main.yaml +++ b/src/controlman/_data/schema/main.yaml @@ -64,9 +64,13 @@ properties: that is as similar as possible to the distribution and import name of the package**. This helps to avoid confusion and makes it easier for others to find, install, and use your package. ::: - default_auto: | - repository name (hyphens replaced with spaces) - templating: load + description_default: Repository name (hyphens replaced with spaces) + required_stage: post-load + default: | + #{{ + repo_name = "${{ repo.name }}$" + return repo_name.replace("-", " ") + }}# examples: - MyProject - My Project @@ -263,13 +267,16 @@ properties: $ref: https://controlman.repodynamics.com/schema/license-component config: type: object + default: { } properties: text: + default: { } $ref: https://controlman.repodynamics.com/schema/license-component-config header: + default: { } $ref: https://controlman.repodynamics.com/schema/license-component-config - notice: - title: Notice + header: + title: Header description: | Custom license notice. $ref: https://jsonschemata.repodynamics.com/string/nonempty @@ -288,61 +295,35 @@ properties: copyright: title: Copyright description: | - Copyright information of the project. + Copyright notice of the project. :::{admonition} Usage :class: dropdown note - By default, the copyright [`notice`](#ccc-copyright-notice) + By default, the copyright notice is included in several places, - such as the license texts (when a pre-defined license is used), + such as license texts, the [main docstring](#ccc-pkg-file---init---py-docstring) of the package, and the [footer](#ccc-theme-footer) of website and documentation files. ::: - examples: - - notice: ${{ copyright.period }} ${{ team.owner.name.full }} - - notice: ${{ copyright.period }} ${{ team.owner.name.full }} - year_start: 2020 - - notice: ${{ copyright.period }} John Doe - - notice: John Doe - year_start: 2020 - type: object - default: { } - additionalProperties: false - required: [ notice ] - properties: - notice: - title: Notice - description: | - Copyright notice of the project. - This is the final form of the notice that will be displayed. - default: ${{ copyright.period }} ${{ team.owner.name.full }} - $ref: https://jsonschemata.repodynamics.com/string/oneline - examples: - - ${{ copyright.period }} John Doe - - A Custom Copyright Notice - period: - title: Period - description: | - Year period to use in the copyright notice. - If not provided, it will be automatically set - using the value of [`start_year`](#ccc-copyright-start_year), as follows: - - If `start_year` is the same as the current year, - the period is set to the current year, e.g., `2024`. - - If `start_year` is before the current year, - the period is set to `{start_year}–{CURRENT-YEAR}`, e.g., `2023–2024`. - - If `start_year` is after the current year, an error is raised. - $ref: https://jsonschemata.repodynamics.com/string/oneline - start_year: - title: Start Year - description: | - Start year of the project. - This can be used to dynamically set the [`period`](#ccc-copyright-period). - If not provided, it is automatically set to the repository creation year. - default_auto: repository creation year - type: integer - minimum: 1980 - maximum: 2040 + $ref: https://jsonschemata.repodynamics.com/string/oneline + default: | + #{{ + from datetime import datetime + + repo_creation_date = "${{ repo.created_at }}$" + owner = """${{ team.owner.name.full }}$""" + start_year = datetime.strptime(repo_creation_date, "%Y-%m-%d").year + current_year = datetime.today().year + period = f"{start_year}–{current_year}" if start_year < current_year else f"{current_year}" + return f"© {period} {owner}" + }}# + description_default: | + Set to `© {period} {owner}`, where `{owner}` is the repository owner's name + and period is calculated as follows: + - If repository creation year is the same as the current year, + the period is set to the current year, e.g., `2024`. + - Otherwise, the period is set to the repository creation year and the current year, e.g., `2024–2026`. role: title: Roles description: | @@ -435,9 +416,7 @@ properties: description: | Maintainers for different parts of the project, such as pull request reviewers, issue assignees, and discussion moderators. - Each maintainer must be defined as an [`Entity`](#ccc-defs-entity) object. - It is recommended to define all maintainers in [`$.team`](#ccc-team), - and reference them here (and elsewhere) using templating. + Maintainers are defined by their [team](#ccc-team)-member ID. :::{admonition} Usage :class: dropdown note @@ -447,29 +426,29 @@ properties: website, citation, README, and community files, as well as package metadata. ::: type: object + default: { } additionalProperties: false properties: code_owners: title: Code Owners description: | - Pull request reviewers of the project. - + Pull request reviewers for different parts of the project. This is used to automatically maintain the [CODEOWNERS](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) file (cf. [`doc.code_owners`](#ccc-doc.code_owners)), which will then be used to automatically assign reviewers to pull requests - based on the modified files. + based on the glob pattern of the modified files. type: object additionalProperties: false - required: [ owners, path ] + required: [ entries, path ] properties: - owners: + entries: title: Owners Definitions description: | Code owner definitions for different parts of the project. For each filepath or glob pattern, add a key-value pair, where the key is the path or glob pattern, - and the value is an array of code owners, i.e., [`Entities`](#ccc-defs-entities). + and the value is an array of [`team`](#ccc-defs-entities)-member IDs. :::{admonition} Order of Entries :class: dropdown important @@ -481,22 +460,41 @@ properties: minItems: 1 uniqueItems: true examples: - - - "*": [ '${{ team.owner }}' ] # Default owners for all unassigned files. - - "/*": [ '${{ team.a_maintainer }}' ] # All files directly under root directory. - - "/.github/": [ '${{ team.a_maintainer}}', '${{ team.another_maintainer }}' ] # All files in `.github` directory. + - - "*": [ owner ] # Default owners for all unassigned files. + - "/*": [ a_maintainer ] # All files directly under root directory. + - "/.github/": [ a_maintainer, another_maintainer ] # All files in `.github` directory. items: title: Owners Definition description: Code owners defined for a specific path or glob pattern. type: object - minProperties: 1 - maxProperties: 1 - additionalProperties: - title: Owners - description: Code owners of the path or glob pattern. - examples: - - [ '${{ team.owner }}' ] - - [ '${{ team.owner }}', '${{ team.a_maintainer }}' ] - $ref: https://controlman.repodynamics.com/schema/entities + additionalProperties: false + required: [ glob, owners ] + properties: + description: + title: Description + description: Description of the path or glob pattern. + type: string + minLength: 1 + $ref: https://jsonschemata.repodynamics.com/string/nonempty + glob: + title: Glob Pattern + description: Glob pattern to match the files. + type: string + minLength: 1 + examples: + - "*" + - "/*" + - "/.github/" + $ref: https://jsonschemata.repodynamics.com/string/nonempty + owners: + title: Owners + description: | + Code owners of the path or glob pattern, + defined by their [`team`](#ccc-defs-entities)-member IDs. + examples: + - [ owner ] + - [ owner, a_maintainer ] + $ref: https://jsonschemata.repodynamics.com/array/unique-strings path: title: File Path Path to the @@ -514,7 +512,7 @@ properties: url: title: URL description: URL of the repository's `CODEOWNERS` file. - default: ${{ repo.url.blob }}/${{ maintainer.code_owners.path }} + default: ${{ repo.url.blob }}$/${{ maintainer.code_owners.path }}$ examples: - "https://github.com/RepoDynamics/PyPackIT/blob/main/CODEOWNERS" $ref: https://jsonschemata.repodynamics.com/url/https @@ -523,16 +521,40 @@ properties: description: | Assignees of project issues. For each issue form defined in [`$.issue.forms`](#ccc-issue-forms), - add a key-value pair where the key is the issue form's [`id`](#ccc-issue-forms--wildcard--id), - and the value is an array of assignees, i.e., [`Entities`](#ccc-defs-entities). + add a key-value pair where the key is the issue form's [`id`](#ccc-issue-forms--wildcard--id). examples: - - app_bug_api: [ '${{ team.owner }}' ] - app_bug_cli: [ '${{ team.owner }}', '${{ team.a_maintainer }}' ] + - app_bug_api: [ owner ] + app_bug_cli: [ owner, a_maintainer ] type: object + default: | + #{{ + issue_forms_matches = $[[ issue.forms ]]$ + if not issue_forms_matches: + return {} + issue_forms = issue_forms_matches[0] + issue_maintainers = {} + for issue_form in issue_forms: + issue_maintainers[issue_form["id"]] = {"ticket": ["owner"], "pull": ["owner"]} + return issue_maintainers + }}# additionalProperties: title: Form Assignees - description: Assignees of a specific issue form. - $ref: https://controlman.repodynamics.com/schema/entities + description: | + Assignees of a specific issue form. + This can be either an array of [`team`](#ccc-defs-entities)-member IDs, + or a mapping with keys `ticket` and `pull` for issue and pull request assignees, respectively. + type: object + required: [ ticket, pull ] + additionalProperties: false + properties: + ticket: + title: Issue Assignees + description: Assignees of project issues. + $ref: https://jsonschemata.repodynamics.com/array/unique-strings + pull: + title: Pull Request Assignees + description: Assignees of project pull requests. + $ref: https://jsonschemata.repodynamics.com/array/unique-strings discussion: title: Discussion Moderators description: | @@ -542,34 +564,48 @@ properties: add a key-value pair where the key is the category's [`slug`](#ccc-discussion-category), and the value is an array of assignees, i.e., [`Entities`](#ccc-defs-entities). examples: - - announcements: [ '${{ team.some_maintainer_id }}', '${{ team.some-other-maintainer-id }}' ] - questions: [ '${{ team.a_maintainer }}' ] + - announcements: [ some_maintainer_id, some-other-maintainer-id ] + questions: [ team.a_maintainer ] type: object + default: | + #{{ + discussion_categories_matches = $[[ discussion.category ]]$ + if not discussion_categories_matches: + return {} + discussion_categories = discussion_categories_matches[0] + discussion_maintainers = {} + for category_slug in discussion_categories.keys(): + discussion_maintainers[category_slug] = ["owner"] + return discussion_maintainers + }}# additionalProperties: title: Category Moderators description: Moderators of a specific discussion category. - $ref: https://controlman.repodynamics.com/schema/entities + $ref: https://jsonschemata.repodynamics.com/array/unique-strings security: title: Security Adviser description: Project's security adviser and contact person. + $ref: https://jsonschemata.repodynamics.com/string/nonempty + default: owner examples: - - '${{ team.owner }}' - - '${{ team.some_maintainer_id }}' - $ref: https://controlman.repodynamics.com/schema/entity + - owner + - some_maintainer_id code_of_conduct: title: Code of Conduct Supervisor description: Project's code of conduct supervisor and contact person. + $ref: https://jsonschemata.repodynamics.com/string/nonempty + default: owner examples: - - '${{ team.owner }}' - - '${{ team.some_maintainer_id }}' - $ref: https://controlman.repodynamics.com/schema/entity + - owner + - some_maintainer_id support: title: Support Contact description: Project's support contact person. + $ref: https://jsonschemata.repodynamics.com/string/nonempty + default: owner examples: - - '${{ team.owner }}' - - '${{ team.some_maintainer_id }}' - $ref: https://controlman.repodynamics.com/schema/entity + - owner + - some_maintainer_id citation: title: Citation description: | @@ -609,11 +645,10 @@ properties: Title of the citation. This can be your project's [name](#ccc-name), [title](#ccc-title), a combination of both, or any other custom title. - default: '${{ name }}: ${{ title }}' examples: - - '${{ name }}: ${{ title }}' - - ${{ name }} - - ${{ pkg.name }} + - '${{ name }}$: ${{ title }}$' + - ${{ name }}$ + - ${{ pkg.name }}$ - A Custom Title $ref: https://jsonschemata.repodynamics.com/string/nonempty authors: @@ -631,39 +666,20 @@ properties: the [`project.authors`](https://packaging.python.org/en/latest/specifications/pyproject-toml/#authors-maintainers) key of the `pyproject.toml` file). ::: - default: [ '${{ team.owner }}' ] - $ref: https://controlman.repodynamics.com/schema/entities + default: [ owner ] + $ref: https://jsonschemata.repodynamics.com/array/unique-strings examples: - - [ '${{ team.owner }}', '${{ team.some_author }}' ] - - - name: - first: Jane - last: Doe - email: - id: jane@doe.com - - name: - legal: XYZ Organization - website: https://xyz.org - linkedin: - id: xyz-org + - [ owner, some_author_id ] contacts: title: Contacts description: | Project's contact persons. It is recommended to define the persons in [`$.team`](#ccc-team) and reference them here using templating. - default: [ '${{ team.owner }}' ] + default: [ owner ] examples: - - [ '${{ team.owner }}', '${{ team.some_author }}' ] - - - name: - first: Jane - last: Doe - email: - user: janedoe@example.com - - name: - first: John - last: Doe - website: 'https://johndoe.com' - $ref: https://controlman.repodynamics.com/schema/entities + - [ owner, some_author_id ] + $ref: https://jsonschemata.repodynamics.com/array/unique-strings message: title: Message description: | @@ -675,7 +691,7 @@ properties: - If you use this software, please cite it using the metadata from this file. - Please cite this software using these metadata. - Please cite this software using the metadata from 'preferred-citation'. - default: If you use ${{ name }}, please cite it using the metadata from this file. + default: If you use ${{ name }}$, please cite it using this reference. $ref: https://jsonschemata.repodynamics.com/string/nonempty preferred_citation: title: Preferred Citation @@ -736,7 +752,7 @@ properties: description: | A brief description of the project. $ref: https://jsonschemata.repodynamics.com/string/nonempty - default: ${{ abstract }} + default: ${{ abstract }}$ examples: - | A custom description of the project @@ -745,7 +761,7 @@ properties: title: License description: | SPDX [license ID](#ccc-license-id) of the project. - default: ${{ license.expression }} + default: ${{ license.expression }}$ $ref: https://jsonschemata.repodynamics.com/id/spdx-license license_url: title: License URL @@ -755,7 +771,7 @@ properties: title: Keywords description: Keywords categorizing the project. $ref: https://jsonschemata.repodynamics.com/array/unique-strings - default: ${{ keywords }} + default: ${{ keywords }}$ examples: - [ A Custom Keyword, Another Custom Keyword ] repository: @@ -769,11 +785,12 @@ properties: build: title: Build description: URL of the project in a build artifact/binary repository. + default: ${{ pkg.pypi.url.home }}$ $ref: https://jsonschemata.repodynamics.com/url/http-ftp-sftp source: title: Source description: URL of the project in a source code repository. - default: ${{ repo.url.home }} + default: ${{ repo.url.home }}$ $ref: https://jsonschemata.repodynamics.com/url/http-ftp-sftp other: title: Other @@ -782,7 +799,7 @@ properties: url: title: URL description: URL of the project's landing (home) page. - default: ${{ web.url.home }} + default: ${{ web.url.home }}$ $ref: https://jsonschemata.repodynamics.com/url/http-ftp-sftp type: title: Type @@ -810,9 +827,9 @@ properties: allowing them to sponsor your project. ::: examples: - - github: [ '${{ owner.username }}', EXAMPLE-GITHUB-USERNAME ] + - github: [ '${{ owner.username }}$', EXAMPLE-GITHUB-USERNAME ] custom: [ 'https://paypal.me/EXAMPLE-PAYPAL-USERNAME' ] - tideleft: pypi/${{ package.name }} + tideleft: pypi/${{ package.name }}$ type: object additionalProperties: false properties: @@ -932,7 +949,7 @@ properties: # (see [`$.web.sphinx.config.html_static_path`](#ccc-web-sphinx-config-html_static_path)) # and thus can be directly used in the website, and linked to in the documentation. # ::: -# default: ${{ web.path.root }}/${{ web.path.source}}/_media +# default: ${{ web.path.root }}$/${{ web.path.source }}$/_media # $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd color: title: Colors @@ -1295,7 +1312,7 @@ properties: default: source source: $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd - default: ${{ .root }}/${{ .source_rel }} + default: ${{ .root }}$/${{ .source_rel }}$ to_root: title: Relative path from the source directory to the root of the repository. default: ../../.. @@ -1333,15 +1350,15 @@ properties: project: title: Name of the project. type: string - default: ${{ name }} + default: ${{ name }}$ author: title: Author(s) of the project. type: string - default: ${{ team.owner.name.full }} + default: ${{ team.owner.name.full }}$ project_copyright: title: Copyright notice of the project. type: string - default: ${{ copyright.notice }} + default: ${{ copyright }}$ version: title: Public (`major.minor`) version of the project. description: | @@ -1599,24 +1616,38 @@ properties: base: title: Base URL of the website. description: | - If not set, this will be set to `https://${{ web.url.cname }}` if specified, + If not set, this will be set to `https://${{ web.url.cname }}$` if specified, or the default GitHub Pages domain, - which is `https://${{ team.owner.github.user }}.github.io/${{ repo.name }}`, + which is `https://${{ team.owner.github.user }}$.github.io/${{ repo.name }}$`, unless the repository is for a [user page](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme) - (i.e., named `${{ team.owner.github.user }}.github.io`), - in which case the base URL is set to `https://${{ team.owner.github.user }}.github.io`. + (i.e., named `${{ team.owner.github.user }}$.github.io`), + in which case the base URL is set to `https://${{ team.owner.github.user }}$.github.io`. $ref: https://jsonschemata.repodynamics.com/url/http-explicit + default: | + #{{ + custom_domain_matches = $[[ web.url.custom ]]$ + if custom_domain_matches: + custom_domain = custom_domain_matches[0] + protocol = "https" if custom_domain["enforce_https"] else "http" + domain = custom_domain["name"] + return f"{protocol}://{domain}" + repo_name = "${{ repo.name }}$" + owner_github_username = "${{ team.owner.github.id }}$" + if repo_name == f"{owner_github_username}.github.io": + return f"https://{owner_github_username}.github.io" + return f"https://{owner_github_username}.github.io/{repo_name}" + }}# examples: - - 'https://${{ repo.info.owner.login }}.github.io' + - 'https://${{ repo.info.owner.login }}$.github.io' home: title: Homepage URL of the website. description: | This should point to the main landing page (i.e., `index` file) of the website. If not set, this will be set to the base URL. - default: ${{ web.url.base }} + default: ${{ web.url.base }}$ examples: - - ${{ web.url.base }}/some/path + - ${{ web.url.base }}$/some/path $ref: https://jsonschemata.repodynamics.com/url/http-explicit quicklinks: title: Quicklinks @@ -1648,7 +1679,7 @@ properties: path: title: Path to the announcement banner file. $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd - default: ${{ web.path.root }}/announcement.html + default: ${{ web.path.root }}$/announcement.html examples: - docs/announcement.html retention_days: @@ -1658,7 +1689,7 @@ properties: url: title: URL to the announcement banner file. $ref: https://jsonschemata.repodynamics.com/url/http-explicit - default: ${{ repo.url.raw }}/${{ announcement.path }} + default: ${{ repo.url.raw }}$/${{ announcement.path }}$ # Package pkg: title: Package @@ -1705,7 +1736,7 @@ properties: description: Title of the changelog document. $ref: https://jsonschemata.repodynamics.com/string/nonempty examples: - - ${{ name }} Changelog (Public API) + - ${{ name }}$ Changelog (Public API) intro: title: Introduction description: Introduction text of the changelog. @@ -1903,28 +1934,28 @@ properties: description: Label for major release. default: suffix: Major Release - description: ${{ commit.primary.major.description }} + description: ${{ commit.primary.major.description }}$ $ref: https://controlman.repodynamics.com/schema/label-group-element minor: title: Minor Release description: Label for minor release. default: suffix: Minor Release - description: ${{ commit.primary.minor.description }} + description: ${{ commit.primary.minor.description }}$ $ref: https://controlman.repodynamics.com/schema/label-group-element patch: title: Patch Release description: Label for patch release. default: suffix: Patch Release - description: ${{ commit.primary.patch.description }} + description: ${{ commit.primary.patch.description }}$ $ref: https://controlman.repodynamics.com/schema/label-group-element post: title: Post Release description: Label for post release. default: suffix: Post Release - description: ${{ commit.primary.post.description }} + description: ${{ commit.primary.post.description }}$ $ref: https://controlman.repodynamics.com/schema/label-group-element subtype: title: Secondary Type Labels @@ -2215,10 +2246,10 @@ properties: or one of the [primary custom commit types](../commit/index.md#primary-custom-types). This is used to correlate each issue in the repository with a primary commit type: - {{ ccc.name }} automatically adds the corresponding primary type label + {{ ccc.name }}$ automatically adds the corresponding primary type label to each issue that is created using the form. Subsequently, when a development branch is merged into a release branch, - {{ ccc.name }} first determines the corresponding issue of the branch from the branch name, + {{ ccc.name }}$ first determines the corresponding issue of the branch from the branch name, and then ascertains the corresponding primary commit type from the issue's primary type label, in order to decide which actions to perform on the release branch. type: string @@ -2260,9 +2291,9 @@ properties: some of these interfaces, in which case it makes sense to only show the corresponding issue forms when the corresponding interfaces are present. This is achieved by adding a condition to each form. examples: - - if_any: [ "${‎{ package.releases.gui_scripts }}" ] # Display a form only if the package has a GUI interface - - if_any: [ "${‎{ package.releases.cli_scripts }}" ] # Display a form only if the package has a CLI interface - - if_any: [ "${‎{ package.releases.has_scripts }}" ] # Display an element within a form only if the package has scripts + - if_any: [ "${‎{ package.releases.gui_scripts }}$" ] # Display a form only if the package has a GUI interface + - if_any: [ "${‎{ package.releases.cli_scripts }}$" ] # Display a form only if the package has a CLI interface + - if_any: [ "${‎{ package.releases.has_scripts }}$" ] # Display an element within a form only if the package has scripts $ref: https://controlman.repodynamics.com/schema/gh-form-preprocess post_process: description: | @@ -2967,8 +2998,16 @@ properties: and is added to repository metadata to improve project findability and visibility. type: string + default: | + #{{ + project_title_matches = $[[ title ]]$ + if not project_title_matches: + return "" + return project_title_matches[0] + }}# + description_default: Project [title](#ccc-title). examples: - - ${{ title }} + - ${{ title }}$ - Another description different from project title. topics: title: | @@ -2982,7 +3021,19 @@ properties: type: array uniqueItems: true maxItems: 20 - default: ${{ slug.keywords[:20] }} + default: | + #{{ + import pylinks + + project_keywords_matches = $[[ keywords ]]$ + if not project_keywords_matches: + return [] + project_keywords = project_keywords_matches[0] + return [ + pylinks.string.to_slug(keyword) + for keyword in project_keywords if len(keyword) <= 50 + ][:20] + }}# items: title: A project keyword. description: | @@ -2997,7 +3048,7 @@ properties: This is displayed on the GitHub repository's homepage, helping users to easily find your project's website. $ref: https://jsonschemata.repodynamics.com/url/http-explicit - default: ${{ web.url.home }} + default: ${{ web.url.home }}$ secret_scanning: title: Enable secret scanning. type: boolean @@ -3178,19 +3229,19 @@ properties: $ref: https://jsonschemata.repodynamics.com/url/https tree: title: URL of the default branch's tree view. - default: ${{ repo.url.home }}/tree/${{ branch.main.name }} + default: ${{ repo.url.home }}$/tree/${{ branch.main.name }}$ examples: - "https://github.com/RepoDynamics/PyPackIT/tree/main" $ref: https://jsonschemata.repodynamics.com/url/https blob: title: URL of the default branch's blob view. - default: ${{ repo.url.home }}/blob/${{ branch.main.name }} + default: ${{ repo.url.home }}$/blob/${{ branch.main.name }}$ examples: - "https://github.com/RepoDynamics/PyPackIT/blob/main" $ref: https://jsonschemata.repodynamics.com/url/https raw: title: URL of the default branch's raw file view. - default: 'https://raw.githubusercontent.com/${{ repo.full_name }}/${{ branch.main.name }}' + default: 'https://raw.githubusercontent.com/${{ repo.full_name }}$/${{ branch.main.name }}$' examples: - "https://raw.githubusercontent.com/RepoDynamics/PyPackIT/main" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3202,13 +3253,13 @@ properties: properties: home: title: URL of the repository's GitHub Issues homepage. - default: ${{ repo.url.home }}/issues + default: ${{ repo.url.home }}$/issues examples: - "https://github.com/RepoDynamics/PyPackIT/issues" $ref: https://jsonschemata.repodynamics.com/url/https chooser: title: URL of repository's GitHub Issue template chooser. - default: ${{ repo.url.issues.home }}/new/choose + default: ${{ repo.url.issues.home }}$/new/choose examples: - "https://github.com/RepoDynamics/PyPackIT/issues/new/choose" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3220,7 +3271,17 @@ properties: where the key is the [`id`](#ccc-issue.forms[i].id) of the form, and the value is the URL for opening a new issue with that form. type: object - default: { } + default: | + #{{ + issue_forms_matches = $[[ issue.forms ]]$ + if not issue_forms_matches: + return { } + issue_forms = issue_forms_matches[0] + return { + form["id"]: f"${{ repo.url.issues.home }}$/new?template={idx + 1:02}_{form["id"]}.yaml" + for idx, form in enumerate(issue_forms) + } + }}# additionalProperties: title: New Issue URL description: URL for opening a new issue with a specific template. @@ -3234,7 +3295,7 @@ properties: properties: home: title: URL of the repository's GitHub Pull Requests homepage. - default: ${{ repo.url.home }}/pulls + default: ${{ repo.url.home }}$/pulls $ref: https://jsonschemata.repodynamics.com/url/https discussions: title: GitHub Discussions URLs. @@ -3243,7 +3304,7 @@ properties: properties: home: title: URL of the repository's GitHub Discussions homepage. - default: ${{ repo.url.home }}/discussions + default: ${{ repo.url.home }}$/discussions examples: - "https://github.com/RepoDynamics/PyPackIT/discussions" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3255,7 +3316,17 @@ properties: where the key is the same as in `discussion.category` (i.e., the slug of the category name), and the value is the URL for opening a new discussion in that category. type: object - default: { } + default: | + #{{ + discussion_categories_matches = $[[ discussion.category ]]$ + if not discussion_categories_matches: + return { } + discussion_categories = discussion_categories_matches[0] + return { + slug: f"${{ repo.url.discussions.home }}$/new?category={slug}" + for slug in discussion_categories.keys() + } + }}# additionalProperties: title: URL for opening a new discussion in a specific category. examples: @@ -3268,7 +3339,7 @@ properties: properties: home: title: URL of the repository's GitHub Actions homepage. - default: ${{ repo.url.home }}/actions + default: ${{ repo.url.home }}$/actions examples: - "https://github.com/RepoDynamics/PyPackIT/actions" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3279,7 +3350,7 @@ properties: properties: home: title: URL of the repository's GitHub Releases homepage. - default: ${{ repo.url.home }}/releases + default: ${{ repo.url.home }}$/releases examples: - "https://github.com/RepoDynamics/PyPackIT/releases" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3290,20 +3361,20 @@ properties: properties: home: title: URL of the repository's GitHub Security homepage. - default: ${{ repo.url.home }}/security + default: ${{ repo.url.home }}$/security examples: - "https://github.com/RepoDynamics/PyPackIT/security" $ref: https://jsonschemata.repodynamics.com/url/https advisories: title: URL of the repository's security advisories. - default: ${{ repo.url.security.home }}/advisories + default: ${{ repo.url.security.home }}$/advisories examples: - "https://github.com/RepoDynamics/PyPackIT/security/advisories" $ref: https://jsonschemata.repodynamics.com/url/https new_advisory: title: URL for creating a new security advisory. - default: ${{ repo.url.security.advisories }}/new + default: ${{ repo.url.security.advisories }}$/new examples: - "https://github.com/RepoDynamics/PyPackIT/security/advisories/new" $ref: https://jsonschemata.repodynamics.com/url/https @@ -3487,7 +3558,7 @@ properties: $ref: https://jsonschemata.repodynamics.com/number/non-negative url: title: URL of the project's configuration file. - default: ${{ repo.url.raw }}/.github/.control/.metadata.json + default: ${{ repo.url.raw }}$/.github/.control/.metadata.json $ref: https://jsonschemata.repodynamics.com/url/https schedule: title: Configurations for scheduled workflow jobs. @@ -3633,7 +3704,7 @@ properties: description: | This is the slug name of the project. type: string - default: ${{ repo.name }} + default: ${{ repo.name }}$ platform: title: ReadTheDocs hosting platform type. type: string @@ -3731,38 +3802,38 @@ properties: :::{code-block} toml :caption: 🗂 `package_python/build.toml` [tool.setuptools.packages.find] - where = [ "${‎{ dir.pkg.source }}" ] + where = [ "${‎{ dir.pkg.source }}$" ] [tool.versioningit.onbuild] - source-file = "${‎{ dir.pkg.source }}/${‎{ package.name }}/__init__.py" + source-file = "${‎{ dir.pkg.source }}$/${‎{ package.name }}$/__init__.py" ::: :::{code-block} toml :caption: 🗂 `package_python/tools/mypy.toml` [tool.mypy] - cache_dir = "${‎{ dir.local.cache.mypy }}" - any_exprs_report = "${‎{ dir.local.report.mypy }}" - html_report = "${‎{ dir.local.report.mypy }}" - linecount_report = "${‎{ dir.local.report.mypy }}" - linecoverage_report = "${‎{ dir.local.report.mypy }}" - lineprecision_report = "${‎{ dir.local.report.mypy }}" - txt_report = "${‎{ dir.local.report.mypy }}" + cache_dir = "${‎{ dir.local.cache.mypy }}$" + any_exprs_report = "${‎{ dir.local.report.mypy }}$" + html_report = "${‎{ dir.local.report.mypy }}$" + linecount_report = "${‎{ dir.local.report.mypy }}$" + linecoverage_report = "${‎{ dir.local.report.mypy }}$" + lineprecision_report = "${‎{ dir.local.report.mypy }}$" + txt_report = "${‎{ dir.local.report.mypy }}$" ::: :::{code-block} toml :caption: 🗂 `package_python/tools/ruff.toml` [tool.ruff] - cache-dir = "${‎{ dir.local.cache.ruff }}" + cache-dir = "${‎{ dir.local.cache.ruff }}$" ::: :::{code-block} yaml :caption: 🗂 `ui/web.yaml` readthedocs: conda: - environment: ${‎{ dir.web }}/requirements.yaml + environment: ${‎{ dir.web }}$/requirements.yaml sphinx: - configuration: ${‎{ dir.web }}/source/conf.py + configuration: ${‎{ dir.web }}$/source/conf.py ::: :::{code-block} text @@ -3820,13 +3891,6 @@ properties: title: Path to the local report directory. $ref: https://jsonschemata.repodynamics.com/path/posix/absolute-from-cwd default: .local/report - # Custom - custom: - title: User-defined custom metadata and configurations. - description: | - Here you can store custom data that can be dynamically referenced - anywhere else in the project. - type: object # Auto slug: title: Slugs of the project. diff --git a/src/controlman/data_gen/main.py b/src/controlman/data_gen/main.py index f98c943..8c1b2c8 100644 --- a/src/controlman/data_gen/main.py +++ b/src/controlman/data_gen/main.py @@ -42,13 +42,8 @@ def __init__( def generate(self) -> None: self._repo() self._team() - self._name() - self._keywords() self._license() - self._copyright() self._discussion_categories() - self._urls_github() - self._urls_website() return def _repo(self) -> None: @@ -99,32 +94,11 @@ def _repo(self) -> None: return def _team(self) -> None: + self._data.fill("team") for person_id in self._data["team"].keys(): - self._data.fill(f"team.{person_id}") self.fill_entity(self._data[f"team.{person_id}"]) return - def _name(self) -> None: - name = self._data.fill("name") - repo_name = self._data["repo.name"] - if not name: - name = self._data["name"] = repo_name.replace("-", " ") - _logger.info( - f"Project Name", - f"Set to '{name}' from repository name." - ) - self._data["slug.name"] = pylinks.string.to_slug(name) - self._data["slug.repo_name"] = pylinks.string.to_slug(repo_name) - return - - def _keywords(self) -> None: - keywords = self._data.fill("keywords") - if not keywords: - return - slugs = [pylinks.string.to_slug(keyword) for keyword in keywords if len(keyword) <= 50] - self._data["slug.keywords"] = slugs - return - def _license(self): if not self._data["license"]: return @@ -206,31 +180,6 @@ def _license(self): self._data["license.path.headers_plain"] = path_headers return - def _copyright(self): - data = self._data["copyright"] - if not data or "period" in data: - return - current_year = _datetime.date.today().year - start_year = self._data.fill("copyright.start_year") - if not start_year: - data["start_year"] = start_year = _datetime.datetime.strptime( - self._data["repo.created_at"], "%Y-%m-%d" - ).year - else: - if start_year > current_year: - raise _exception.load.ControlManSchemaValidationError( - source="source", - problem=( - f"Project start year ({start_year}) cannot be greater " - f"than current year ({current_year})." - ), - json_path="copyright.start_year", - data=self._data(), - ) - year_range = f"{start_year}{'' if start_year == current_year else f'–{current_year}'}" - data["period"] = year_range - return - def _discussion_categories(self): discussions_info = self._cache.get("repo", f"discussion_categories") if discussions_info: @@ -250,34 +199,6 @@ def _discussion_categories(self): category_obj["name"] = category["name"] return - def _urls_github(self) -> None: - self._data["repo.url.issues.new"] = { - issue_type["id"]: f"{self._data['repo.url.home']}/issues/new?template={idx + 1:02}_{issue_type['id']}.yaml" - for idx, issue_type in enumerate(self._data.get("issue.forms", [])) - } - self._data["repo.url.discussions.new"] = { - slug: f"{self._data['repo.url.home']}/discussions/new?category={slug}" - for slug in self._data.get("discussion.category", {}).keys() - } - return - - def _urls_website(self) -> None: - base_url = self._data.get("web.url.base") - if not base_url: - custom = self._data.fill("web.url.custom") - if custom: - protocol = "https" if custom["enforce_https"] else "http" - domain = custom["name"] - base_url = f"{protocol}://{domain}" - elif self._data["repo.name"] == f"{self._data['team.owner.github.id']}.github.io": - base_url = f"https://{self._data['team.owner.github.user']}.github.io" - else: - base_url = f"https://{self._data['team.owner.github.id']}.github.io/{self._data['repo.name']}" - self._data["web.url.base"] = base_url - if not self._data["web.url.home"]: - self._data["web.url.home"] = base_url - return - def fill_entity(self, data: dict) -> None: """Fill all missing information in an `entity` object.""" diff --git a/src/controlman/data_gen/python.py b/src/controlman/data_gen/python.py index 78b90e3..54352b2 100644 --- a/src/controlman/data_gen/python.py +++ b/src/controlman/data_gen/python.py @@ -30,25 +30,10 @@ def __init__( return def generate(self): - self._package_name() self._package_python_versions() - self._package_operating_systems() self.trove_classifiers() return - def _package_name(self) -> None: - for path in ("pkg.name", "test.name"): - name = self._data.fill(path) - if name: - name_cleaned = _re.sub(r'[^a-zA-Z0-9._-]', '-', name) - self._data[path] = _re.sub(r'^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$', '', name_cleaned) - for path in ("pkg.import_name", "test.import_name"): - import_name = self._data.fill(path) - if import_name: - import_name_cleaned = _re.sub(r'[^a-zA-Z0-9]', '_', import_name.lower()) - self._data[path] = _re.sub(r'^[0-9]+', "", import_name_cleaned) - return - def _package_python_versions(self) -> None: def get_python_releases(): @@ -125,21 +110,6 @@ def get_python_releases(): self._data["test.python.version.spec"] = spec_str return - def _package_operating_systems(self): - data_os = self._data.fill("pkg.os") - if not isinstance(data_os, dict): - raise _exception.load.ControlManSchemaValidationError( - source="source", - before_substitution=True, - problem="The package has not specified any operating systems.", - json_path="pkg.os", - data=self._data(), - ) - pure_python = not any("ci_build" in os for os in data_os.values()) - self._data["pkg.python.pure"] = pure_python - self._data["pkg.os.independent"] = len(data_os) == 3 and pure_python - return - def trove_classifiers(self): def programming_language() -> list[str]: diff --git a/src/controlman/data_gen/web.py b/src/controlman/data_gen/web.py index 382a881..fef50be 100644 --- a/src/controlman/data_gen/web.py +++ b/src/controlman/data_gen/web.py @@ -22,7 +22,6 @@ def generate(self): def _process_frontmatter(self) -> None: pages = {} blog = {} - for md_filepath in self._path.rglob("*.md", case_sensitive=False): if not md_filepath.is_file(): continue diff --git a/src/controlman/file_gen/config.py b/src/controlman/file_gen/config.py index 71308fd..1388979 100644 --- a/src/controlman/file_gen/config.py +++ b/src/controlman/file_gen/config.py @@ -67,18 +67,21 @@ def generate_codeowners(self) -> list[DynamicFile]: "path": self._data[f"{key}.path"], "path_before": self._data_before[f"{key}.path"], } - codeowners = self._data[key]["owners"] + codeowners = self._data[f"{key}.entries"] if not codeowners: return [DynamicFile(**codeowners_files)] # Get the maximum length of patterns to align the columns when writing the file - max_len = max([len(list(codeowner_dic.keys())[0]) for codeowner_dic in codeowners]) - text = "" + max_len = max([len(codeowner_dic["glob"]) for codeowner_dic in codeowners]) + lines = [] for entry in codeowners: - pattern = list(entry.keys())[0] - reviewers_list = entry[pattern] - reviewers = " ".join([f"@{reviewer["github"]["id"]}" for reviewer in reviewers_list]) - text += f"{pattern: <{max_len}} {reviewers}\n" - return [DynamicFile(content=text, **codeowners_files)] + comment = entry.get("description", "") + for comment_line in comment.splitlines(): + lines.append(f"# {comment_line}") + pattern = entry["glob"] + reviewers_list = entry["owners"] + reviewers = " ".join([f"@{self._data["team"][reviewer]["github"]["id"]}" for reviewer in reviewers_list]) + lines.append(f"{pattern: <{max_len}} {reviewers}{"\n" if comment else ''}") + return [DynamicFile(content="\n".join(lines), **codeowners_files)] def _generate_license(self) -> list[DynamicFile]: if self._is_disabled("license"): @@ -397,7 +400,11 @@ def gitattributes(self) -> list[DynamicFile]: def citation(self) -> list[DynamicFile]: - def create_person(entity): + def create_person(entity: str | dict): + if isinstance(entity, str): + entity = self._data["team"].get(entity) + if not entity: + raise ValueError(f"Person '{entity}' not found in team data.") _out = {} if entity["name"].get("legal"): _out["name"] = entity["name"]["legal"] @@ -438,6 +445,7 @@ def create_person(entity): return _out def create_reference(ref: dict): + #TODO: Complete this function out = {} for key in ( "abbreviation", diff --git a/src/controlman/file_gen/python.py b/src/controlman/file_gen/python.py index 7464048..646923d 100644 --- a/src/controlman/file_gen/python.py +++ b/src/controlman/file_gen/python.py @@ -268,51 +268,47 @@ def pyproject_project(self) -> dict: @property def pyproject_project_authors(self) -> list[dict[str, str]]: - authors = self._data["pkg.authors"] - if not authors: - return [] - authors_list = [] - for author in authors: - author_entry = {"name": author["name"]["full"]} - if "email" in author: - author_entry["email"] = author["email"]["id"] - authors_list.append(author_entry) - return authors_list + return [self._make_person_entry(author_id) for author_id in self._pkg.get("authors", [])] + + def _make_person_entry(self, person_id: str) -> dict[str, str]: + person = self._data["team"][person_id] + person_entry = {"name": person["name"]["full"]} + if "email" in person: + person_entry["email"] = person["email"]["id"] + return person_entry @property def pyproject_project_maintainers(self) -> list[dict[str, str]]: + if self._pkg["maintainers"]: + return [self._make_person_entry(maintainer_id) for maintainer_id in self._pkg["maintainers"]] - def update_dict(maintainer, weight: int): - for registered_maintainer in registered_maintainers: - if registered_maintainer[0] == maintainer: - registered_maintainer[1] += weight - break - else: - registered_maintainers.append([maintainer, weight]) - return - - registered_maintainers = [] - - for code_owners_entry in self._data.get("maintainer.code_owners.owners", []): - for code_owners in code_owners_entry.values(): - for code_owner in code_owners: - update_dict(code_owner, 4) - for maintainer_type, weight in (("issue", 3), ("discussion", 2)): - for maintainers in self._data.get(f"maintainer.{maintainer_type}", {}).values(): + maintainer_rank = {} + for code_owners_entry in self._data.get("maintainer.code_owners.entries", []): + for code_owner in code_owners_entry["owners"]: + maintainer_rank[code_owner] = maintainer_rank.get(code_owner, 0) + 1 + for issue_maintainer_data in self._data.get(f"maintainer.issue", {}).values(): + for maintainers in issue_maintainer_data.values(): for maintainer in maintainers: - update_dict(maintainer, weight) + maintainer_rank[maintainer] = maintainer_rank.get(maintainer, 0) + 1 + for discussion_maintainer_data in self._data.get(f"maintainer.discussion", {}).values(): + for maintainer in discussion_maintainer_data: + maintainer_rank[maintainer] = maintainer_rank.get(maintainer, 0) + 1 for maintainer_type in ("security", "code_of_conduct", "support"): maintainer = self._data.get(f"maintainer.{maintainer_type}") if maintainer: - update_dict(maintainer, 1) - maintainers_list = [] - for maintainer in sorted(registered_maintainers, key=lambda x: x[1], reverse=True): - maintainer_info = maintainer[0] - maintainer_entry = {"name": maintainer_info["name"]["full"]} - if "email" in maintainer_info: - maintainer_entry["email"] = maintainer_info["email"]["id"] - maintainers_list.append(maintainer_entry) - return maintainers_list + maintainer_rank[maintainer] = maintainer_rank.get(maintainer, 0) + 1 + + ranked_maintainers = {} + for maintainer, rank in maintainer_rank.items(): + ranked_maintainers.setdefault(rank, []).append(maintainer) + + out = [] + for _, maintainers in sorted(ranked_maintainers.items(), key=lambda x: x[0], reverse=True): + maintainers_list = [ + self._make_person_entry(maintainer_id) for maintainer_id in maintainers + ] + out.extend(sorted(maintainers_list, key=lambda x: x["name"])) + return out @property def pyproject_project_dependencies(self):