Skip to content

Commit

Permalink
🚚 Move some files to documents/
Browse files Browse the repository at this point in the history
  • Loading branch information
Freed-Wu committed Oct 20, 2023
1 parent 79a899a commit 5d063c7
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 137 deletions.
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,46 @@ Language server for [ArchLinux](https://archlinux.org)/[Windows Msys2](https://m
[PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD) and
[`*.install`](https://wiki.archlinux.org/title/PKGBUILD#install).

PKGBUILD is a subtype of bash. See
[bash-language-server](https://github.com/bash-lsp/bash-language-server) to get
support of bash language server.
`PKGBUILD` is a subtype of bash.
This language server only provides extra features for `build.sh` which
[bash-language-server](https://github.com/bash-lsp/bash-language-server)
doesn't support:

- [x] document hover
- [x] packages
- [x] builtin variables/functions
- [x] completion
- [x] diagnostic: requires [namcap](https://wiki.archlinux.org/title/Namcap)
- [x] packages
- [x] builtin variables/functions
- [x] diagnostic
- [x] [namcap](https://wiki.archlinux.org/title/Namcap)
- [ ] detect variable type. Such as: `depends` shouldn't be a function
- [ ] format: sort some variables
- [ ] document link: jump to <https://archlinux.org/packages/package_name>
- [ ] code actions
- [ ] generate a template by the name of directory containing `PKGBUILD`, the
templates comes from:
- [ ] customized by user
- [ ] [pip2pkgbuild](https://github.com/wenLiangcan/pip2pkgbuild)
- [ ] [cpan2aur](https://metacpan.org/release/CPANPLUS-Dist-Arch)
- [ ] [gem2arch](https://github.com/anatol/gem2arch)
- [ ] [arch-hs](https://github.com/berberman/arch-hs)
- [ ] update `pkgsums` by [`updpkgsums`](https://gitlab.archlinux.org/pacman/pacman-contrib)
- [ ] update version by a `nvcheck.toml` to tell this
program where to search new version:

```toml
[package_name]
source = "github"
github = "author_name/repo_name"
use_max_tag = true
```

Other features:

- [ ] pre-commit-hooks
- [ ] linter
- [ ] formatter

## Document hover

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ exclude_lines = [
]

[tool.bandit.assert_used]
skips = ["*_test.py", "test_*.py"]
skips = ["*_test.py", "*/test_*.py"]

[tool.cibuildwheel]
archs = ["all"]
Expand Down
2 changes: 1 addition & 1 deletion src/pkgbuild_language_server/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def main():
)
return None
if args.generate_cache:
from .package import generate_cache
from documents.package import generate_cache

try:
generate_cache()
Expand Down
78 changes: 78 additions & 0 deletions src/pkgbuild_language_server/documents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
r"""Documents
=============
"""
import json
import os
from typing import Literal

from platformdirs import user_cache_dir

from .. import CACHE


def check_extension(uri: str) -> Literal["install", "PKGBUILD", ""]:
r"""Check extension.
:param uri:
:type uri: str
:rtype: Literal["install", "PKGBUILD", ""]
"""
if uri.split(os.path.extsep)[-1] == "install":
return "install"
if os.path.basename(uri) == "PKGBUILD":
return "PKGBUILD"
return ""


def get_document(
method: Literal["builtin", "cache", "system"] = "builtin"
) -> dict[str, tuple[str, str, str]]:
r"""Get document. ``builtin`` will use builtin pkgbuild.json. ``cache``
will generate a cache from ``${XDG_CACHE_DIRS:-/usr/share}
/man/man5/PKGBUILD.5.gz``. ``system`` is same as ``cache`` except it
doesn't generate cache. We use ``builtin`` as default.
:param method:
:type method: Literal["builtin", "cache", "system"]
:rtype: dict[str, tuple[str, str, str]]
"""
if method == "builtin":
file = os.path.join(
os.path.join(
os.path.join(
os.path.dirname(os.path.dirname(__file__)), "assets"
),
"json",
),
"pkgbuild.json",
)
with open(file, "r") as f:
document = json.load(f)
elif method == "cache":
from .builtin import init_document

if not os.path.exists(user_cache_dir("pkgbuild.json")):
document = init_document()
with open(user_cache_dir("pkgbuild.json"), "w") as f:
json.dump(document, f)
else:
with open(user_cache_dir("pkgbuild.json"), "r") as f:
document = json.load(f)
else:
from .builtin import init_document

document = init_document()
return document


def get_packages() -> dict[str, str]:
r"""Get packages.
:rtype: dict[str, str]
"""
try:
with open(CACHE, "r") as f:
packages = json.load(f)
except FileNotFoundError:
packages = {}
return packages
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
r"""Api
=======
r"""Builtin
===========
"""
import os
from gzip import decompress
Expand Down
File renamed without changes.
159 changes: 32 additions & 127 deletions src/pkgbuild_language_server/server.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
r"""Server
==========
"""
import json
import os
import re
from typing import Any, Literal, Tuple
from typing import Any, Tuple

from lsprotocol.types import (
INITIALIZE,
Expand All @@ -27,103 +25,10 @@
Range,
TextDocumentPositionParams,
)
from platformdirs import user_cache_dir
from pygls.server import LanguageServer

from . import CACHE


def diagnostic(path: str) -> list[tuple[str, str]]:
r"""Diagnostic.
:param path:
:type path: str
:rtype: list[tuple[str, str]]
"""
try:
from Namcap.rules import all_rules
except ImportError:
return []
from Namcap.package import load_from_pkgbuild
from Namcap.ruleclass import PkgbuildRule
from Namcap.tags import format_message

pkginfo = load_from_pkgbuild(path)
items = []
for value in all_rules.values():
rule = value()
if isinstance(rule, PkgbuildRule):
rule.analyze(pkginfo, "PKGBUILD") # type: ignore
for msg in rule.errors:
items += [(format_message(msg), "Error")]
for msg in rule.warnings:
items += [(format_message(msg), "Warning")]
return items


def check_extension(uri: str) -> Literal["install", "PKGBUILD", ""]:
r"""Check extension.
:param uri:
:type uri: str
:rtype: Literal["install", "PKGBUILD", ""]
"""
if uri.split(os.path.extsep)[-1] == "install":
return "install"
if os.path.basename(uri) == "PKGBUILD":
return "PKGBUILD"
return ""


def get_document(
method: Literal["builtin", "cache", "system"] = "builtin"
) -> dict[str, tuple[str, str, str]]:
r"""Get document. ``builtin`` will use builtin pkgbuild.json. ``cache``
will generate a cache from ``${XDG_CACHE_DIRS:-/usr/share}
/man/man5/PKGBUILD.5.gz``. ``system`` is same as ``cache`` except it
doesn't generate cache. We use ``builtin`` as default.
:param method:
:type method: Literal["builtin", "cache", "system"]
:rtype: dict[str, tuple[str, str, str]]
"""
if method == "builtin":
file = os.path.join(
os.path.join(
os.path.join(os.path.dirname(__file__), "assets"), "json"
),
"pkgbuild.json",
)
with open(file, "r") as f:
document = json.load(f)
elif method == "cache":
from .api import init_document

if not os.path.exists(user_cache_dir("pkgbuild.json")):
document = init_document()
with open(user_cache_dir("pkgbuild.json"), "w") as f:
json.dump(document, f)
else:
with open(user_cache_dir("pkgbuild.json"), "r") as f:
document = json.load(f)
else:
from .api import init_document

document = init_document()
return document


def get_packages() -> dict[str, str]:
r"""Get packages.
:rtype: dict[str, str]
"""
try:
with open(CACHE, "r") as f:
packages = json.load(f)
except FileNotFoundError:
packages = {}
return packages
from .documents import check_extension, get_document, get_packages
from .utils import diagnostic


class PKGBUILDLanguageServer(LanguageServer):
Expand Down Expand Up @@ -163,8 +68,14 @@ def hover(params: TextDocumentPositionParams) -> Hover | None:
"""
if not check_extension(params.text_document.uri):
return None
if check_extension(params.text_document.uri) == "PKGBUILD":
# PKGBUILD contains package_XXX
pat = r"[a-z]+"
else:
# *.install contains pre_install()
pat = r"[a-z_]+"
word = self._cursor_word(
params.text_document.uri, params.position, True
params.text_document.uri, params.position, True, pat
)
if not word:
return self.hover(params)
Expand All @@ -188,7 +99,10 @@ def completions(params: CompletionParams) -> CompletionList:
if not check_extension(params.text_document.uri):
return CompletionList(is_incomplete=False, items=[])
word = self._cursor_word(
params.text_document.uri, params.position, False, True
params.text_document.uri,
params.position,
False,
r"[-0-9_a-z]+",
)
token = "" if word is None else word[0]
items = [
Expand Down Expand Up @@ -255,53 +169,44 @@ def _cursor_line(self, uri: str, position: Position) -> str:
:type position: Position
:rtype: str
"""
doc = self.workspace.get_document(uri)
content = doc.source
line = content.split("\n")[position.line]
return str(line)
document = self.workspace.get_document(uri)
return document.source.splitlines()[position.line]

def _cursor_word(
self,
uri: str,
position: Position,
include_all: bool = True,
package_name: bool = False,
) -> Tuple[str, Range] | None:
r"""Cursor word.
regex: str = r"\w+",
) -> tuple[str, Range]:
"""Cursor word.
:param self:
:param uri:
:type uri: str
:param position:
:type position: Position
:param include_all:
:type include_all: bool
:param package_name:
:type package_name: bool
:rtype: Tuple[str, Range] | None
:param regex:
:type regex: str
:rtype: tuple[str, Range]
"""
if package_name:
pat = r"[-0-9_a-z]+"
else:
if check_extension(uri) == "PKGBUILD":
# PKGBUILD contains package_XXX
pat = r"[a-z]+"
else:
# *.install contains pre_install()
pat = r"[a-z_]+"
line = self._cursor_line(uri, position)
cursor = position.character
for m in re.finditer(pat, line):
end = m.end() if include_all else cursor
if m.start() <= cursor <= m.end():
word = (
for m in re.finditer(regex, line):
if m.start() <= position.character <= m.end():
end = m.end() if include_all else position.character
return (
line[m.start() : end],
Range(
Position(position.line, m.start()),
Position(position.line, end),
),
)
return word
return None
return (
"",
Range(Position(position.line, 0), Position(position.line, 0)),
)

def hover(self, params: TextDocumentPositionParams) -> Hover | None:
r"""Hover.
Expand All @@ -311,7 +216,7 @@ def hover(self, params: TextDocumentPositionParams) -> Hover | None:
:rtype: Hover | None
"""
word = self._cursor_word(
params.text_document.uri, params.position, True, True
params.text_document.uri, params.position, True, r"[-0-9_a-z]+"
)
if not word:
return None
Expand Down
Loading

0 comments on commit 5d063c7

Please sign in to comment.