Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure versioned documentation #2008

Merged
merged 1 commit into from
May 10, 2023
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
93 changes: 71 additions & 22 deletions .announce
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/bin/bash -e

# project.version
if [ "$VERSION" == "" ]; then exit 1; fi
if [ "$VERSION" == "" ]; then
echo "VERSION has not been set"
exit 1;
fi

if [ "$1" = "-y" ]; then
AUTOACCEPT=true
Expand All @@ -23,43 +26,89 @@ fi

echo "Announcing $PREVIOUS_VERSION -> $VERSION"

COMMIT_MESSAGE="Updated refs to latest ($VERSION) release"
DOCUMENTATION_DIR="documentation"
RELEASE_DOCS_DIR="${DOCUMENTATION_DIR}/release-latest"
SNAPSHOT_DOCS_DIR="${DOCUMENTATION_DIR}/snapshot"

if [ "$(git status --porcelain=v1 docs/install/cli.md docs/install/integrations.md)" != "" ]; then
echo "ERROR: To proceed, cli.md and integrations.md must not contain uncommitted changes"
if [ "$(git status --porcelain=v1 $DOCUMENTATION)" != "" ]; then
echo "ERROR: To proceed, the current branch must not contain uncommitted changes in directory '${DOCUMENTATION_DIR}'"
# ask for user confirmation
if [[ "$AUTOACCEPT" = false ]]; then
read -p "revert changes? (y/n)? " -n 1 -r; echo; if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; else git checkout docs/install/cli.md docs/install/integrations.md; fi
read -p "revert changes? (y/n)? " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
git checkout ${DOCUMENTATION_DIR}
else
exit 1
fi
else
echo "Reverting changes to cli.md and integrations.md"
git checkout docs/install/cli.md docs/install/integrations.md
echo "Reverting changes in directory '${DOCUMENTATION_DIR}'"
git checkout ${DOCUMENTATION_DIR}
fi
fi

escape_for_sed() { echo "$1" | sed -e 's/[]\/$*.^|[]/\\&/g'; }
#escape_for_sed() { echo "$1" | sed -e 's/[]\/$*.^|[]/\\&/g'; }

# update Docs

sed -i -e "s/$PREVIOUS_VERSION/$VERSION/g" docs/install/cli.md
sed -i -e "s/$PREVIOUS_VERSION/$VERSION/g" docs/install/integrations.md
git --no-pager diff docs/install/cli.md docs/install/integrations.md

# ask for user confirmation
if [[ "$AUTOACCEPT" = false ]]; then
read -p "commit & push (y/n)? " -n 1 -r; echo; if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
fi
# Make a separate branch because master branch is protected

BRANCH="$VERSION-update-refs"

if [ "$(git show-ref refs/heads/$BRANCH)" != "" ]; then
echo "ERROR: Branch $BRANCH already exists."
if [[ "$AUTOACCEPT" = false ]]; then
read -p "Delete local branch? (y/n)? " -n 1 -r; echo; if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; else git branch -D $BRANCH; fi
read -p "Delete local branch? (y/n)? " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
else
git branch -D $BRANCH
fi
else
echo "Deleting local branch $BRANCH"
git branch -D $BRANCH
fi
# Checkout local master branch so that changes to this script can be tested without pushing the script to the remote branch
git checkout --track master -b $BRANCH
else
git checkout --track origin/master -b $BRANCH
fi

# Make a separate branch because master branch is protected
git checkout --track origin/master -b $BRANCH && git commit -m "$COMMIT_MESSAGE" docs/install/cli.md docs/install/integrations.md && git push origin $BRANCH
# update version number in snapshot docs

echo "Updating version numbers in (snapshot) installation documentation"
# Use "sed -i '' ..." instead of "sed -i -e ..." as the latter creates a new file on OSX
sed -i '' "s/$PREVIOUS_VERSION/$VERSION/g" ${SNAPSHOT_DOCS_DIR}/docs/install/cli.md
sed -i '' "s/$PREVIOUS_VERSION/$VERSION/g" ${SNAPSHOT_DOCS_DIR}/docs/install/integrations.md
git --no-pager diff ${DOCUMENTATION_DIR}

# ask for user confirmation before committing
if [[ "$AUTOACCEPT" = false ]]; then
read -p "Accept changes (y/n)? " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi

# Replace release documentation with current snapshot documentation.

echo "Replace release documentation with current snapshot documentation"
# Support removal of files which still exists in release docs but which are no longer present in snapshot docs
rm -rf ${RELEASE_DOCS_DIR}/docs/
cp -r ${SNAPSHOT_DOCS_DIR}/docs/ ${RELEASE_DOCS_DIR}/docs/
# Note that directory "${SNAPSHOT_DOCS_DIR}/overrides/" should not replace "${RELEASE_DOCS_DIR}/overrides/"
cp -r ${SNAPSHOT_DOCS_DIR}/mkdocs.yml ${RELEASE_DOCS_DIR}
# Add files which previously did not yet exists in the release docs but were present in the snapshot docs
git add --all
# Display sorted list of files changed but do not show contents as that could be a lot
git status --porcelain=v1 documentation | sort

# Commit and push changes
if [[ "$AUTOACCEPT" = false ]]; then
read -p "Commit and push changes (y/n)? " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
git commit -m "Updated refs to latest ($VERSION) release"
git push origin $BRANCH
24 changes: 19 additions & 5 deletions .github/workflows/gradle-docs-publish.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
name: Publish documentation
name: Publish snapshot documentation
on:
push:
branches:
- master
branches: ['master']
paths: ['documentation/snapshot/**']
jobs:
deploy:
runs-on: ubuntu-latest
if: github.repository == 'pinterest/ktlint'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch all commits/branches
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force

- name: Install mkdocs and mike
run: pip install mkdocs-material mike

- name: Config git
run: |
git config user.email "[email protected]"
git config user.name "Ktlint Release Workflow"

- run: |
cd documentation/snapshot
# The dev-snapshot version has no release number as the version will only be tagged when an official release is build
# This also prevents that multiple snapshot versions of the documentation will be published in parallel
mike deploy --push dev-snapshot
4 changes: 2 additions & 2 deletions .github/workflows/gradle-pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: PR Build

on:
push:
branches:
- master
branches: ['master']
paths: ['**/*.kt', '**/*.kts', '**/*.properties', '**/*.toml']
pull_request:
workflow_dispatch:

Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/release-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish release documentation
on:
push:
branches: ['master']
paths: ['documentation/release-latest/**']
jobs:
deploy:
runs-on: ubuntu-latest
if: github.repository == 'pinterest/ktlint'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch all commits/branches
- uses: actions/setup-python@v4
with:
python-version: 3.x

- name: Install mkdocs and mike
run: pip install mkdocs-material mike

- name: Config git
run: |
git config user.email "[email protected]"
git config user.name "Ktlint Release Workflow"

- name: Get last released version
run: echo "version=$(git describe --abbrev=0 --tags)" >> $GITHUB_ENV

- name: Deploy release docs
run: |
echo "Deploy release docs to version ${{ env.version }}"
cd documentation/release-latest
# Release docs are versioned so that user can use relevant docs for the ktlint version they use
mike deploy --push --update-aliases ${{ env.version }} latest

- name: Update default release docs
run: |
cd documentation/release-latest
mike set-default --push latest
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,11 @@ jobs:
SDKMAN_VERSION: ${{ env.version }}
run: ./gradlew :ktlint-cli:sdkMajorRelease

- name: Announce Release
- name: Update Release documentation
if: ${{ success() }}
run: |
git config user.email "<>" |
git config user.name "Ktlint Release Workflow" |
./.announce -y
env:
VERSION: ${{ env.version }}

15 changes: 15 additions & 0 deletions documentation/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Documentation

Two versions of the documentation are kept in the 'master' branch:

* The `snapshot` version of the documentation applies to the SNAPSHOT versions of ktlint. Upon the publication of the next official release of ktlint, the `release-latest` version of the documentation is replaced with the `snapshot` version.
* The `release-latest` version of the documentation applies to the last officially published version of ktlint. Upon the publication of the next official release of ktlint, this version of the documentation is replaced with the `snapshot` version.

Whenever a fix is to be made to the documentation, it should be determined in which version(s) of the documentation is to be fixed. Documentation fixes which only apply to the `SNAPSHOT` version of ktlint may only be fixed in the `snapshot` version of the documentation.

All other kind of documentation fixes most likely needs to be fixed in both the `snapshot` and `release-latest` versions. Only fixing it in `release-latest` may result in the fix being lost upon publication of the next official ktlint version.


Docs can be viewed on the local machine in one of following ways:
* Run script `serve-docs-locally.sh` which starts a docker container running mkdocs
* Run command `mike serve` which requires that `python`, `pip`, `mike` and `mkdocs` have been installed (one time only) before
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions mkdocs.yml → documentation/release-latest/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
site_name: Ktlint
site_url: https://pinterest.github.io/ktlint/

site_dir: build/site
docs_dir: docs

extra:
version:
provider: mike

theme:
name: material
custom_dir: overrides
favicon: assets/images/favicon.ico
palette:
# Palette toggle for light mode
Expand Down
8 changes: 8 additions & 0 deletions documentation/release-latest/overrides/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "base.html" %}

{% block outdated %}
You're not viewing the latest version.
<a href="{{ '../' ~ base_url }}">
<strong>Click here to go to latest.</strong>
</a>
{% endblock %}
File renamed without changes.
6 changes: 6 additions & 0 deletions documentation/snapshot/docs/api/badge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
If you want to display a badge to show that your project is linted and formatted using `'ktlint` than you can add the
[![ktlint](https://img.shields.io/badge/ktlint%20code--style-%E2%9D%A4-FF4081)](https://pinterest.github.io/ktlint/) badge:

```md title="Ktlint code style badge"
[![ktlint](https://img.shields.io/badge/ktlint%20code--style-%E2%9D%A4-FF4081)](https://pinterest.github.io/ktlint/)
```
116 changes: 116 additions & 0 deletions documentation/snapshot/docs/api/custom-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Custom integration

!!! warning
This page is based on Ktlint `0.49.x` which has to be released. Most concepts are also applicable for `0.48.x`.

## Ktlint Rule Engine

The `Ktlint Rule Engine` is the central entry point for custom integrations with the `Ktlint API`. See [basic API Consumer](https://github.com/pinterest/ktlint/blob/master/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt) for a basic example on how to invoke the `Ktlint Rule Engine`. This example also explains how the logging of the `Ktlint Rule Engine` can be configured to your needs.

The `KtLintRuleEngine` instance only needs to be created once for the entire lifetime of your application. Reusing the same instance results in better performance due to caching.

```kotlin title="Creating the KtLintRuleEngine"
val ktLintRuleEngine =
KtLintRuleEngine(
ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS,
)
```

### Rule provider

The `KtLintRuleEngine` must be configured with at least one `RuleProvider`. A `RuleProvider` is a lambda which upon request of the `KtLintRuleEngine` provides a new instance of a specific rule. You can either provide any of the standard rules provided by KtLint or with your own custom rules, or with a combination of both.
```kotlin title="Creating a set of RuleProviders"
val KTLINT_API_CONSUMER_RULE_PROVIDERS =
setOf(
// Can provide custom rules
RuleProvider { NoVarRule() },
// but also reuse rules from KtLint rulesets
RuleProvider { IndentationRule() },
)
```

### Editor config: defaults & overrides

When linting and formatting files, the `KtlintRuleEngine` takes the `.editorconfig` file(s) into account which are found on the path to the file. A property which is specified in the `editorConfigOverride` property of the `KtLintRuleEngine` takes precedence above the value of that same property in the `.editorconfig` file. The `editorConfigDefaults` property of the `KtLintRuleEngine` can be used to specify the fallback values for properties in case that property is not defined in the `.editorconfig` file (or in the `editorConfigOverride` property).

```kotlin title="Specifying the editorConfigOverride"
val ktLintRuleEngine =
KtLintRuleEngine(
ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS,
editorConfigOverride = EditorConfigOverride.from(
INDENT_STYLE_PROPERTY to IndentConfig.IndentStyle.SPACE,
INDENT_SIZE_PROPERTY to 4
)
)
```

The `editorConfigOverride` property takes an `EditorConfigProperty` as key. KtLint defines several such properties, but they can also be defined as part of a custom rule.

The `editorConfigDefaults` property is more cumbersome to define as it is based directly on the data format of the `ec4j` library which is used for parsing the `.editorconfig` file.

The defaults can be loaded from a path or a directory. If a path to a file is specified, the name of the file does not necessarily have to end with `.editorconfig`. If a path to a directory is specified, the directory should contain a file with name `.editorconfig`. Note that the `propertyTypes` have to be derived from the same collection of rule providers that are specified in the `ruleProviders` property of the `KtLintRuleEngine`.

```kotlin title="Specifying the editorConfigDefaults using an '.editorconfig' file"
val ktLintRuleEngine =
KtLintRuleEngine(
ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS,
editorConfigDefaults = EditorConfigDefaults.load(
path = Paths.get("/some/path/to/editorconfig/file/or/directory"),
propertyTypes = KTLINT_API_CONSUMER_RULE_PROVIDERS.propertyTypes(),
)
)
```
If you want to include all RuleProviders of the Ktlint project than you can easily retrieve the collection using `StandardRuleSetProvider().getRuleProviders()`.

The `EditorConfigDefaults` property can also be specified programmatically as is shown below:

```kotlin title="Specifying the editorConfigDefaults programmatically"
val ktLintRuleEngine =
KtLintRuleEngine(
ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS,
editorConfigDefaults = EditorConfigDefaults(
org.ec4j.core.model.EditorConfig
.builder()
// .. add relevant properties
.build()
)
)
```

### Lint & format

Once the `KtLintRuleEngine` has been defined, it is ready to be invoked for each file or code snippet that has to be linted or formatted. The the `lint` and `format` functions take a `Code` instance as parameter. Such an instance can either be created from a file
```kotlin title="Code from file"
val code = Code.fromFile(
File("/some/path/to/file")
)
```
or a code snippet (set `script` to `true` to handle the snippet as Kotlin script):
```kotlin title="Code from snippet"
val code = Code.fromSnippet(
"""
val code = "some-code"
""".trimIndent()
)
```

The `lint` function is invoked with a lambda which is called each time a `LintError` is found and does not return a result.
```kotlin title="Specifying the editorConfigDefaults programmatically"
ktLintRuleEngine
.lint(codeFile) { lintError ->
// handle
}
```

The `format` function is invoked with a lambda which is called each time a `LintError` is found and returns the formatted code as result. Note that the `LintError` should be inspected for errors that could not be autocorrected.
```kotlin title="Specifying the editorConfigDefaults programmatically"
val formattedCode =
ktLintRuleEngine
.format(codeFile) { lintError ->
// handle
}
```

## Logging

Ktlint uses the `io.github.microutils:kotlin-logging` which is a `slf4j` wrapper. As API consumer you can choose which logging framework you want to use and configure that framework to your exact needs. The [basic API Consumer](https://github.com/pinterest/ktlint/blob/master/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt) contains an example with `org.slf4j:slf4j-simple` as logging provider and a customized configuration which shows logging at `DEBUG` level for all classes except one specific class which only displays logging at `WARN` level.
Loading