Skip to content

Commit

Permalink
Update command specs
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo committed Sep 19, 2024
1 parent 0a13739 commit 7b1e855
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 27 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
!.scripts/changelog.py
!.scripts/edit_releases.py
!.scripts/modify_version_for_nightly.py
!.scripts/command_specs.py

.run/*
!.run/Build.run.xml
Expand All @@ -16,6 +17,9 @@ TODO.md
site/
venv/

pyrightconfig.json
ruff.toml

### Generated ###
.gradle/
.kotlin/
Expand Down
116 changes: 116 additions & 0 deletions .scripts/command_specs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "more-itertools",
# "nccsp",
# "pydantic",
# ]
#
# [tool.uv.sources]
# nccsp = { git = "https://github.com/InSyncWithFoo/nccsp", rev = "master" }
# ///

from __future__ import annotations

import json
import subprocess
from itertools import groupby
from pathlib import Path
from typing import Literal, Self

import nccsp
from more_itertools import partition
from pydantic import BaseModel, Field

type _Executable = Literal['ruff', 'uv']


def _is_option(option_or_argument: nccsp.OptionOrArgument) -> bool:
return option_or_argument.name.startswith('-')


class _Command(BaseModel):
name: str
path: list[str]
description: str | None
arguments: list[nccsp.OptionOrArgument]
options: list[nccsp.OptionOrArgument]
subcommands: dict[str, _Command] = Field(default_factory = dict)

@classmethod
def convert(cls, command: nccsp.Command) -> Self:
fragments = command.fragments if len(command.fragments) == 1 else command.fragments[1:]

*path, name = fragments
description = command.description
arguments, options = partition(_is_option, command.options_and_arguments)

return cls(
name = name, path = path,
description = description,
arguments = arguments, # pyright: ignore [reportArgumentType]
options = options # pyright: ignore [reportArgumentType]
)


def _get_version(executable: _Executable) -> str:
return subprocess.check_output([executable, 'version']).decode('utf-8').strip()


def _get_data(executable: _Executable) -> list[nccsp.Command]:
arguments = ['nccsp', 'executable', executable]
output_stream = subprocess.check_output(arguments)
output = output_stream.decode('utf-8')

return [nccsp.Command(**element) for element in json.loads(output)]


def _add_to_tree(ancestor: _Command, descendant: _Command, path: list[str]) -> None:
subtree = ancestor.subcommands

if len(path) == 1:
subtree[descendant.name] = descendant
return

_add_to_tree(subtree[path[0]], descendant, path[1:])


def _convert_flat_to_nested(commands: list[nccsp.Command]) -> _Command:
root = _Command.convert(commands[0])

for command in commands[1:]:
_add_to_tree(root, _Command.convert(command), path = command.fragments[1:])

return root


def _dump_data(version: str, tree: _Command, filename: str) -> None:
json_resources_directory = Path(__file__).parent.parent / 'src' / 'main' / 'resources' / 'commandspecs'
json_resources_directory.mkdir(parents = True, exist_ok = True)

path = json_resources_directory / f'{filename}.json'

with open(path.absolute(), 'w') as file:
data = {
'version': version,
'tree': tree.model_dump()
}
json.dump(data, file)


def _get_and_dump_data(executable: _Executable) -> None:
version = _get_version(executable)
data_grouped_by_tool = groupby(_get_data(executable), lambda command: command.fragments[0])

for tool_name, tool_data in data_grouped_by_tool:
tree = _convert_flat_to_nested(list(tool_data))
_dump_data(version, tree, filename = tool_name)


def main() -> None: # noqa: D103
_get_and_dump_data('ruff')
_get_and_dump_data('uv')


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("UnstableApiUsage")

package insyncwithfoo.ryecharm.common.terminal

import com.intellij.terminal.completion.spec.ShellCommandSpec
Expand All @@ -10,30 +12,18 @@ private inline val Any.classLoader: ClassLoader
get() = this::class.java.classLoader


@Suppress("UnstableApiUsage")
private fun ShellCommandSpec.toInfo() =
ShellCommandSpecInfo.create(this, ShellCommandSpecConflictStrategy.REPLACE)


@Suppress("UnstableApiUsage")
internal class RuffCommandSpecsProvider : ShellCommandSpecsProvider {

override fun getCommandSpecs(): List<ShellCommandSpecInfo> {
val commandSpecInfo = classLoader.loadCommandSpecFrom("commandspecs/ruff.json")?.toInfo()

return listOfNotNull(commandSpecInfo)
}

}


@Suppress("UnstableApiUsage")
internal class UVCommandSpecsProvider : ShellCommandSpecsProvider {
internal class RyeCharmCommandSpecsProvider : ShellCommandSpecsProvider {

override fun getCommandSpecs(): List<ShellCommandSpecInfo> {
val commandSpecInfo = classLoader.loadCommandSpecFrom("commandspecs/uv.json")?.toInfo()
val filenames = listOf("ruff.json", "uv.json", "uvx.json")

return listOfNotNull(commandSpecInfo)
return filenames.mapNotNull { filename ->
classLoader.loadCommandSpecFrom("commandspecs/$filename")?.toInfo()
}
}

}
11 changes: 2 additions & 9 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,9 @@
<extensions defaultExtensionNs="org.jetbrains.plugins.terminal">
<!--suppress PluginXmlValidity -->
<commandSpecsProvider
id="insyncwithfoo.ryecharm.common.terminal.RuffCommandSpecsProvider"
id="insyncwithfoo.ryecharm.common.terminal.RyeCharmCommandSpecsProvider"
order="first"
implementation="insyncwithfoo.ryecharm.common.terminal.RuffCommandSpecsProvider"
/>

<!--suppress PluginXmlValidity -->
<commandSpecsProvider
id="insyncwithfoo.ryecharm.common.terminal.UVCommandSpecsProvider"
order="first"
implementation="insyncwithfoo.ryecharm.common.terminal.UVCommandSpecsProvider"
implementation="insyncwithfoo.ryecharm.common.terminal.RyeCharmCommandSpecsProvider"
/>
</extensions>

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/commandspecs/uv.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/main/resources/commandspecs/uvx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version": "uv 0.4.12 (2545bca69 2024-09-18)", "tree": {"name": "uvx", "path": [], "description": "Run a command provided by a Python package.", "arguments": [], "options": [{"name": "--from", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Use the given package to provide the command"}, {"name": "--with", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Run with the given packages installed"}, {"name": "--with-editable", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Run with the given packages installed as editables"}, {"name": "--with-requirements", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Run with all packages listed in the given `requirements.txt` files"}, {"name": "--isolated", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Run the tool in an isolated virtual environment, ignoring any already-installed tools"}, {"name": "--index-url", "alias": "-i", "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "The URL of the Python package index (by default: <https://pypi.org/simple>)"}, {"name": "--extra-index-url", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Extra URLs of package indexes to use, in addition to `--index-url`"}, {"name": "--find-links", "alias": "-f", "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Locations to search for candidate distributions, in addition to those found in the registry indexes"}, {"name": "--no-index", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via `--find-links`"}, {"name": "--upgrade", "alias": "-U", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Allow package upgrades, ignoring pinned versions in any existing output file. Implies `--refresh`"}, {"name": "--no-upgrade", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--upgrade-package", "alias": "-P", "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies `--refresh-package`"}, {"name": "--reinstall", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Reinstall all packages, regardless of whether they're already installed. Implies `--refresh`"}, {"name": "--no-reinstall", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--reinstall-package", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Reinstall a specific package, regardless of whether it's already installed. Implies `--refresh-package`"}, {"name": "--index-strategy", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["first-index", "unsafe-first-match", "unsafe-best-match"], "description": "The strategy to use when resolving against multiple index URLs"}, {"name": "--keyring-provider", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["disabled", "subprocess"], "description": "Attempt to use `keyring` for authentication for index URLs"}, {"name": "--allow-insecure-host", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Allow insecure connections to a host"}, {"name": "--resolution", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["highest", "lowest", "lowest-direct"], "description": "The strategy to use when selecting between the different compatible versions for a given package requirement"}, {"name": "--prerelease", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["disallow", "allow", "if-necessary", "explicit", "if-necessary-or-explicit"], "description": "The strategy to use when considering pre-release versions"}, {"name": "--pre", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--config-setting", "alias": "-C", "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs"}, {"name": "--no-build-isolation", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Disable isolation when building source distributions"}, {"name": "--no-build-isolation-package", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Disable isolation when building source distributions for a specific package"}, {"name": "--build-isolation", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--exclude-newer", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Limit candidate packages to those that were uploaded prior to the given date"}, {"name": "--link-mode", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["clone", "copy", "hardlink", "symlink"], "description": "The method to use when installing packages from the global cache"}, {"name": "--compile-bytecode", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Compile Python files to bytecode after installation"}, {"name": "--no-compile-bytecode", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--no-sources", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources"}, {"name": "--no-build", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Don't build source distributions"}, {"name": "--build", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--no-build-package", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Don't build source distributions for a specific package"}, {"name": "--no-binary", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Don't install pre-built wheels"}, {"name": "--binary", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--no-binary-package", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Don't install pre-built wheels for a specific package"}, {"name": "--refresh", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Refresh all cached data"}, {"name": "--no-refresh", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--refresh-package", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Refresh cached data for a specific package"}, {"name": "--python", "alias": "-p", "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "The Python interpreter to use to build the run environment."}, {"name": "--show-resolution", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Whether to show resolver and installer output from any environment modifications"}, {"name": "--no-cache", "alias": "-n", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation"}, {"name": "--cache-dir", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Path to the cache directory"}, {"name": "--python-preference", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["only-managed", "managed", "system", "only-system"], "description": "Whether to prefer uv-managed or system Python installations"}, {"name": "--allow-python-downloads", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Allow automatically downloading Python when required. [env: \"UV_PYTHON_DOWNLOADS=auto\"]"}, {"name": "--no-python-downloads", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Disable automatic downloads of Python. [env: \"UV_PYTHON_DOWNLOADS=never\"]"}, {"name": "--python-fetch", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["automatic", "manual", "never"], "description": "Deprecated version of [`Self::python_downloads`]"}, {"name": "--quiet", "alias": "-q", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Do not print any output"}, {"name": "--verbose", "alias": "-v", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Use verbose output"}, {"name": "--no-color", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Disable colors"}, {"name": "--color", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": ["auto", "always", "never"], "description": "Control colors in output"}, {"name": "--native-tls", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Whether to load TLS certificates from the platform's native certificate store"}, {"name": "--no-native-tls", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--offline", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Disable network access"}, {"name": "--no-offline", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--preview", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Whether to enable experimental, preview features"}, {"name": "--no-preview", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": null}, {"name": "--show-settings", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Show the resolved settings for the current command"}, {"name": "--no-progress", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Hide all progress outputs"}, {"name": "--directory", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "Change to the given directory prior to running the command"}, {"name": "--config-file", "alias": null, "variadic": false, "optional": false, "type": "string", "suggestions": null, "description": "The path to a `uv.toml` file to use for configuration"}, {"name": "--no-config", "alias": null, "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Avoid discovering configuration files (`pyproject.toml`, `uv.toml`)"}, {"name": "--help", "alias": "-h", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Display the concise help for this command"}, {"name": "--version", "alias": "-V", "variadic": false, "optional": false, "type": null, "suggestions": null, "description": "Display the uv version"}], "subcommands": {}}}

0 comments on commit 7b1e855

Please sign in to comment.