diff --git a/.gitignore b/.gitignore index ba9c778..3842eec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.opencode + # Dask dask-worker-space diff --git a/src/projspec/content/__init__.py b/src/projspec/content/__init__.py index 48530e7..8b8812d 100644 --- a/src/projspec/content/__init__.py +++ b/src/projspec/content/__init__.py @@ -1,3 +1,5 @@ +"""Contents classes - information declared in project specs""" + from projspec.content.base import BaseContent from projspec.content.data import FrictionlessData, IntakeSource from projspec.content.env_var import EnvironmentVariables diff --git a/src/projspec/proj/__init__.py b/src/projspec/proj/__init__.py index 81b1c86..e626538 100644 --- a/src/projspec/proj/__init__.py +++ b/src/projspec/proj/__init__.py @@ -1,4 +1,8 @@ +"""Project and spec classes""" + from projspec.proj.base import ParseFailed, Project, ProjectSpec, ProjectExtra + +from projspec.proj.ai import AIEnabled from projspec.proj.briefcase import Briefcase from projspec.proj.conda_package import CondaRecipe, RattlerRecipe from projspec.proj.conda_project import CondaProject @@ -11,6 +15,7 @@ from projspec.proj.node import JLabExtension, Node, Yarn from projspec.proj.pixi import Pixi from projspec.proj.poetry import Poetry +from projspec.proj.published import Citation, Zenodo from projspec.proj.pyscript import PyScript from projspec.proj.python_code import PythonCode, PythonLibrary from projspec.proj.rust import Rust, RustPython @@ -22,7 +27,9 @@ "ParseFailed", "Project", "ProjectSpec", + "AIEnabled", "Briefcase", + "Citation", "CondaRecipe", "CondaProject", "Golang", @@ -49,4 +56,5 @@ "Uv", "VSCode", "Yarn", + "Zenodo", ] diff --git a/src/projspec/proj/ai.py b/src/projspec/proj/ai.py new file mode 100644 index 0000000..92c37cb --- /dev/null +++ b/src/projspec/proj/ai.py @@ -0,0 +1,15 @@ +from projspec.proj.base import ProjectSpec + + +class AIEnabled(ProjectSpec): + """This project has text files intended for LLM/AI to read.""" + + spec_doc = "https://agents.md/" + + def match(self) -> bool: + return bool( + {"AGENTS.md", "CLAUDE.md", ".specify"}.intersection(self.proj.basenames) + ) + + def parse(self) -> None: + pass diff --git a/src/projspec/proj/base.py b/src/projspec/proj/base.py index 23c29cf..27859d0 100644 --- a/src/projspec/proj/base.py +++ b/src/projspec/proj/base.py @@ -151,7 +151,8 @@ def resolve( :param types: names of types to allow while parsing. If empty or None, allow all :param xtypes: names of types to disallow while parsing. """ - if types and set(types) - set(registry): + types = set(camel_to_snake(_) for _ in types or ()) + if types and types - set(registry): raise ValueError(f"Unknown types: {set(types) - set(registry)}") # sorting to ensure consistency for name in sorted(registry): @@ -405,6 +406,7 @@ def make(self, qname: str, **kwargs): spec, artifact, name = None, qname, None else: spec, artifact, *name = qname.split(".") + spec = camel_to_snake(spec) specs = [self.specs[spec]] if spec else self.specs.values() art = None for spec in specs: diff --git a/src/projspec/proj/ide.py b/src/projspec/proj/ide.py index 0c5bf60..0166a25 100644 --- a/src/projspec/proj/ide.py +++ b/src/projspec/proj/ide.py @@ -1,5 +1,5 @@ """Code project container config within IDEs""" - +from projspec.artifact import BaseArtifact from projspec.proj import ProjectSpec @@ -12,7 +12,16 @@ def match(self) -> bool: return self.proj.fs.exists(f"{self.proj.url}/.project/spec.yaml") def parse(self) -> None: - ... + from projspec.artifact.process import Process + + # "opens" the project in the sense that it is set as the current context. + # Editing still happens in jupyter/vscode/etc + self.artifacts["set_project"] = Process( + self.proj, cmd=["nvwb", "open", self.proj.url] + ) + + # create: + # https://docs.nvidia.com/ai-workbench/user-guide/latest/reference/user-interface/cli.html#create-project class JetbrainsIDE(ProjectSpec): @@ -20,7 +29,11 @@ def match(self) -> bool: return self.proj.fs.exists(f"{self.proj.url}/.idea") def parse(self) -> None: - ... + from projspec.artifact.process import Process + + self.artifacts["launch"] = Process( + self.proj, cmd=["pycharm", self.proj.url, "nosplash", "dontReopenProjects"] + ) class VSCode(ProjectSpec): @@ -32,7 +45,9 @@ def match(self) -> bool: return self.proj.fs.exists(f"{self.proj.url}/.vscode/settings.json") def parse(self) -> None: - ... + from projspec.artifact.process import Process + + self.artifacts["launch"] = Process(self.proj, cmd=["code", self.proj.url]) class Zed(ProjectSpec): @@ -42,4 +57,6 @@ def match(self) -> bool: return self.proj.fs.exists(f"{self.proj.url}/.zed/settings.json") def parse(self) -> None: - ... + from projspec.artifact.process import Process + + self.artifacts["launch"] = Process(self.proj, cmd=["zed", self.proj.url]) diff --git a/src/projspec/proj/published.py b/src/projspec/proj/published.py new file mode 100644 index 0000000..e4164d6 --- /dev/null +++ b/src/projspec/proj/published.py @@ -0,0 +1,41 @@ +import yaml + +from projspec.proj.base import ProjectSpec + + +class Citation(ProjectSpec): + """A github-specified format to say how this project should be cited.""" + + spec_doc = "https://citation-file-format.github.io/" + + def match(self): + return "CITATION.cff" in self.proj.basenames + + def parse(self) -> None: + from projspec.content.metadata import DescriptiveMetadata + + with self.proj.fs.open(self.proj.basenames["CITATION.cff"], "rt") as f: + meta = yaml.safe_load(f) + self.contents["descriptive_metadata"] = DescriptiveMetadata( + proj=self.proj, meta=meta + ) + + +class Zenodo(ProjectSpec): + """This project has been published on Zenodo""" + + spec_doc = "https://help.zenodo.org/docs/github/describe-software/zenodo-json/" + + def match(self): + # NB: zenodo picks up CITATION.cff too, but this format is more specific + return ".zenodo.json" in self.proj.basenames + + def parse(self) -> None: + from projspec.content.metadata import DescriptiveMetadata + + with self.proj.fs.open(self.proj.basenames[".zenodo.json"], "rt") as f: + meta = yaml.safe_load(f) + # TODO: extract known contents such as license. + self.contents["descriptive_metadata"] = DescriptiveMetadata( + proj=self.proj, meta=meta + ) diff --git a/src/projspec/utils.py b/src/projspec/utils.py index 1114c0d..63678d0 100644 --- a/src/projspec/utils.py +++ b/src/projspec/utils.py @@ -1,6 +1,7 @@ import contextlib import enum import logging +import os import pathlib import re import subprocess