Compare commits

..

27 Commits

Author SHA1 Message Date
7460d784b6 add search and back button to docs 2026-01-23 18:06:07 +11:00
c9976b9c08 bump version number 2026-01-23 17:22:53 +11:00
9075342661 removed uneeded print and removed packages from project 2026-01-23 17:21:59 +11:00
904ef85f54 package wip 2026-01-23 16:47:56 +11:00
f6bfa6b8fd Merge branch 'main' of https://chookspace.com/ground/Digpkg 2026-01-23 16:39:42 +11:00
5bc61e30a6 fixed numerous bugs 2026-01-23 16:38:53 +11:00
da59155954 turned digpkg into a pypi package 2026-01-22 21:35:38 +11:00
e75da2f297 removed uneeded printing 2026-01-22 09:27:57 +11:00
6823f64541 "Raises" section in autogenerated markdown, warning for uploading a package ending in _build 2026-01-22 06:08:36 +11:00
d8cddfbbd4 dig docs command 2026-01-21 20:53:24 +11:00
b4fd43fb9b removed uneeded file 2026-01-21 08:38:33 +11:00
941cf8f1f3 removed sqlite3, too complicated and not needed for now 2026-01-20 19:41:34 +11:00
bb3c2f3225 address boundary error not my beloved 2026-01-20 18:52:48 +11:00
0f95dff95f include folder name in build note 2026-01-20 18:28:44 +11:00
93ebd3d8b9 sh bang 2026-01-20 18:05:58 +11:00
90192be2b1 working on bindings for sqlite3 2026-01-20 17:13:04 +11:00
9634750185 list command fix 2026-01-20 07:58:30 +11:00
b60369bf4a extra note in build command 2026-01-20 07:53:52 +11:00
a12990ef65 added the build command 2026-01-20 07:47:15 +11:00
77a7a44804 install dependencies 2026-01-19 21:06:25 +11:00
2f1412a492 force specification of version number
this is cause gitea doesn't allow you to just grab the latest version of packages
2026-01-19 19:16:59 +11:00
4de2819109 install multiple packages at once with the install command 2026-01-19 07:29:55 +11:00
dd7697fcc7 fully removed stuff for the env command 2026-01-19 07:05:56 +11:00
28b56e71ab uninstall command is done 2026-01-19 07:03:51 +11:00
dd204788e3 list command done 2026-01-19 06:46:33 +11:00
57e763a24f create symlinks to installed libraries 2026-01-19 06:32:18 +11:00
81c33d100f made it so anyone can upload packages (who has permission) 2026-01-19 06:27:44 +11:00
16 changed files with 599 additions and 180 deletions

1
dig/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .main import main

133
dig/build.py Normal file
View File

@@ -0,0 +1,133 @@
from .util import *
import shutil
import os, sys
import subprocess
import configparser
from rich.console import Console
console = Console()
def find_c_files(path: str):
paths = []
for entry in os.listdir(path):
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
find_c_files(full_path)
else:
if full_path.endswith(".c"):
paths.append(full_path)
return paths
def build_mineral(args):
# sanity checks
if not os.path.isdir(args.folder_path):
console.print("[b red]digpkg: failed to build mineral: specified folder doesn't exist![/]")
sys.exit(1)
if os.path.isfile(os.path.join(args.folder_path, "main.so")):
console.print("[b red]digpkg: failed to build mineral: attempt to build compiled mineral![/]")
sys.exit(1)
if not shutil.which("gcc"):
console.print("[b red]digpkg: failed to build mineral: gcc was not found![/]")
sys.exit(1)
# use gcc to compile the mineral
with console.status("Compiling", spinner="bouncingBall", spinner_style="green") as status:
c_files = find_c_files(args.folder_path)
if len(c_files) == 0:
console.print("[b red]digpkg: failed to build mineral: no .c files found in the specified folder, are you sure you're compiling a mineral?[/]")
sys.exit(1)
if subprocess.run([
"gcc",
"-shared",
"-fPIC",
*c_files,
*[f"-{arg}" for arg in (args.gcc_args or [])],
"-o",
"main.so"
], stdout=None).returncode:
console.print("[b red]digpkg: failed to build mineral: gcc exited with non-zero exit code.[/]")
sys.exit(1)
console.print("[:white_check_mark:] Compile success!")
def build(args):
if args.package:
# build the mineral
build_mineral(args)
# create the build dir and throw in the .so file
with console.status("Packaging...", spinner="bouncingBall", spinner_style="green"):
build_dir = f"{os.path.basename(args.folder_path)}_build"
if not os.path.isdir(build_dir):
os.mkdir(build_dir)
# generate a mineral.ini file
config_parser = configparser.ConfigParser()
config_parser["package"] = {
"description": "Your description here",
"version": "1.0.0",
"config_version": "1",
}
config_parser["dependencies"] = {}
# write it to our new mineral
with open(os.path.join(build_dir, "mineral.ini"), "w") as f:
config_parser.write(f)
# generate doc files
docs_folder = os.path.join(build_dir, "docs")
if not os.path.isdir(docs_folder):
os.mkdir(docs_folder)
with open(os.path.join(docs_folder, "my_function.md"), "w") as f:
f.write("# mylib_MyFunction\n")
f.write("Describe your function briefly.\n\n")
f.write("## Arguments\n")
f.write("- myArgument (double): describe your arguments in a list format like this.\n\n")
f.write("## Returns\n")
f.write("result (int): then explain what the function returns\n\n")
f.write("## Raises\n")
f.write("- `ErrorName`: have your error reason here, include this if your function raises any errors\n")
f.write("## Example\n")
f.write("```python\n")
f.write("# then show how you use the function in an example\n")
f.write("call !mylib_MyFunction 123.0 &result\n")
f.write("```\n")
f.write("`Using \"python\" as the syntax highlighting language seems to work well with Ground.`")
with open(os.path.join(build_dir, "SUMMARY.md"), "w") as f:
f.write("# mylib\n")
f.write("Introduce your module here!\n\nThis file will serve as an index page for all your docs.\n\n")
f.write("## Subtitle (use this for categories)\n")
f.write("- [mylib_MyFunction](docs/my_function.md)\n\n")
f.write("## Changelog\n")
f.write("### v0.0.1 (latest)\n")
f.write("- Initial release.\n")
console.print("[:white_check_mark:] Generated a new package for you!")
shutil.move("main.so", os.path.join(build_dir, "main.so"))
console.print("[:white_check_mark:] Put your library into your package!")
else:
check_sudo()
check_ground_libs_path()
# build the mineral and move it straight to the ground libs folder
build_mineral(args)
with console.status("Installing...", spinner="bouncingBall", spinner_style="green"):
shutil.move("main.so", os.path.join(os.getenv("GROUND_LIBS"), f"{os.path.basename(args.folder_path)}.so"))
console.print("[:white_check_mark:] Installed!")

112
dig/docs.py Normal file
View File

@@ -0,0 +1,112 @@
from .util import *
import os, sys
from rich.console import Console
from textual.app import App, ComposeResult
from textual.widgets import MarkdownViewer, Header, Footer, Input, Button
from textual.containers import Horizontal
from textual_autocomplete import PathAutoComplete
from textual import on
console = Console()
class DocsApp(App):
TITLE = "Digpkg Docs"
SUB_TITLE = "made with ❤️ by SpookyDervish"
CSS = """
#search {
margin: 1;
width: 1fr;
}
#back {
margin-top: 1;
margin-left: 1;
}
#bottom {
height: 5;
}
"""
def __init__(self, inital_markdown_path: str):
super().__init__()
self.inital_markdown_path = inital_markdown_path
self.docs_folder = os.path.join(os.path.dirname(self.inital_markdown_path), "docs")
async def on_input_submitted(self, event: Input.Submitted):
if event.input.id == "search":
if not os.path.isdir(self.docs_folder):
return
file_path = os.path.join(self.docs_folder, event.input.value)
if os.path.isfile(file_path):
event.input.clear()
event.input.focus()
await self.query_one(MarkdownViewer).go(file_path)
else:
self.notify("That file wasn't found.", title="Uh oh", severity="error")
async def on_button_pressed(self, event: Button.Pressed):
if event.button.id == "back":
await self.query_one(MarkdownViewer).back()
def compose(self) -> ComposeResult:
with open(self.inital_markdown_path, "r") as f:
markdown = f.read()
yield Header()
yield MarkdownViewer(markdown)
with Horizontal(id="bottom"):
yield Button(label="Back", id="back", flat=True, variant="error")
search_input = Input(id="search", placeholder="Search . . .")
yield search_input
if os.path.isdir(self.docs_folder):
yield PathAutoComplete(
search_input,
path=self.docs_folder
)
else:
search_input.disabled = True
search_input.tooltip = "This mineral doesn't have a docs folder and can't be searched."
yield Footer()
def docs(args):
check_ground_libs_path()
mineral_path = os.path.join(os.getenv("GROUND_LIBS"), args.mineral_name)
if not os.path.isdir(mineral_path):
console.print(f"[b red]digpkg: can't read docs: the mineral [i]{args.mineral_name}[/] was not found!")
sys.exit(1)
docs_path = os.path.join(mineral_path, "docs")
summary_md_file = os.path.join(mineral_path, "SUMMARY.md")
if args.doc_file != None:
if not os.path.isfile(os.path.join(docs_path, f"{args.doc_file}.md")):
console.print(f"[b red]digpkg: can't read docs: the mineral [i]{args.mineral_name}[/] has no doc file named \"{args.doc_file}\".")
sys.exit(1)
os.chdir(docs_path)
app = DocsApp(f"{args.doc_file}.md")
app.run()
sys.exit(0)
if os.path.isfile(summary_md_file):
os.chdir(mineral_path)
app = DocsApp(summary_md_file)
app.run()
sys.exit(0)
else:
console.print(f"[b red]digpkg: can't read docs: the mineral [i]{args.mineral_name}[/] has no file named SUMMARY.md, please use the [i]--doc-file[/] argument to specify a specific doc to open instead.")
sys.exit(1)

116
dig/install.py Normal file
View File

@@ -0,0 +1,116 @@
import requests
import os
import sys
import tempfile
import tarfile
import configparser
import shutil
from rich.console import Console
from rich.progress import SpinnerColumn
from .util import check_ground_libs_path, check_sudo
console = Console()
def install_package(package_name, version, args, is_dependency: bool = False):
retries_left = args.max_retries
with console.status("Downloading tarball...", spinner="bouncingBall", spinner_style="blue") as status:
console.print(f"Installing {package_name} [d]({version})[/]")
while retries_left > 0:
# grab the tar ball
response = requests.get(f"https://chookspace.com/api/packages/ground/generic/{package_name}/{version}/mineral.tar")
# check response code for errors
if response.status_code == 404: # package doesn't exist
console.print(f"[b red]digpkg: mineral \"{package_name}\" was not found. Check to make sure the name and version number are correct.[/]")
sys.exit(1)
elif response.status_code != 200:
retries_left -= 1
console.print(f"[b yellow]digpkg: failed to download mineral \"{package_name}\": {response.content.decode()} ({retries_left} retries left)[/]")
if retries_left == 0:
console.print(f"[b red]digpkg: exceeded max retries while downloading mineral \"{package_name}\"[/]")
sys.exit(1)
continue
response.raise_for_status()
break
# create temporary file for tarball
try:
f = tempfile.TemporaryFile("wb+")
f.write(response.content)
f.flush()
f.seek(0)
console.print("[d][:white_check_mark:] Tarball downloaded![/]")
except KeyboardInterrupt:
console.print("[b yellow]digpkg: operation cancelled by user[/]")
return
# extract the tarball to the GROUND_LIBS folder
status.update("Extracting...")
extract_dir = os.getenv("GROUND_LIBS")
if not os.path.isdir(extract_dir): # gotta ensure the folder exists
os.mkdir(extract_dir)
tar_file = tarfile.open(fileobj=f)
tar_file.extractall(extract_dir)
f.close()
console.print(f"[d][:white_check_mark:] Extracted to {extract_dir}.")
console.status("Finishing up...")
package_folder = os.path.join(extract_dir, f"{package_name}/")
if not os.path.isfile(os.path.join(package_folder, "mineral.ini")):
console.print(f"[b red]digpkg: failed to install {package_name}: the mineral doesn't have a mineral.ini file, please contact the maintainer because they've set up the mineral incorrectly.")
console.print("[d]Cleaning up failed install...[/]")
shutil.rmtree(os.path.join(extract_dir, package_name))
sys.exit(1)
config_parser = configparser.ConfigParser()
config_parser.read(os.path.join(package_folder, "mineral.ini"))
dependencies = config_parser["dependencies"]
# create a symlink from the main.so file to the ground libs folder so ground can find it
symlink_path = os.path.join(extract_dir, f"{package_name}.so") # the path where the symlink is
if not os.path.isfile(symlink_path):
os.symlink(os.path.join(extract_dir, package_name, "main.so"), symlink_path)
if is_dependency:
console.print(f"[d][:white_check_mark:] Done installing dependency: {package_name}[/]")
for dependency, dependency_version in dependencies.items():
install_package(dependency, dependency_version, args, True)
if not is_dependency:
console.print(f"\n[b green][:white_check_mark:] Installed {package_name}![/]")
def install(args):
check_sudo()
check_ground_libs_path()
for package in args.names:
# figure out which version to install
package_name = package
version = "1.0.0"
if "@" in package:
split = package.split("@")
package_name = split[0]
version = split[1]
else:
console.print(f"[b red]digpkg: failed to install package: please specify version to install for {package_name}[/]")
sys.exit(1)
install_package(package_name, version, args)

41
dig/list.py Normal file
View File

@@ -0,0 +1,41 @@
import os
import configparser
from rich import print
from rich.table import Table
from .util import check_ground_libs_path
def list_cmd(args):
check_ground_libs_path()
ground_libs_folder = os.getenv("GROUND_LIBS")
folders = []
if os.path.isdir(ground_libs_folder): # used to prevent errors that are caused by the GROUND_LIBS folder not existing
folders = os.listdir(ground_libs_folder)
table = Table("Name", "Version", "Description", title="Installed")
config_parser = configparser.ConfigParser()
for folder in folders:
full_path = os.path.join(ground_libs_folder, folder)
# skip anything that isnt a folder
if not os.path.isdir(full_path):
continue
# read the mineral.ini file to figure out the version and description
ini_path = os.path.join(full_path, "mineral.ini")
if not os.path.isfile(ini_path):
continue
config_parser.read(ini_path)
table.add_row(
f"[b]{folder}",
f"[blue]{config_parser.get('package', 'version')}",
config_parser.get("package", "description"),
)
print(table)

View File

@@ -1,9 +1,13 @@
#!/usr/bin/env python3
import argparse import argparse
import os, sys import os, sys
from install import install from .install import install
from publish import publish from .publish import publish
from remove import remove from .remove import remove
from .list import list_cmd
from .uninstall import uninstall
from .build import build
def parse_arguments(): def parse_arguments():
@@ -13,7 +17,7 @@ def parse_arguments():
# install command # install command
install_command = sub_parsers.add_parser(name="install", description="install a mineral") install_command = sub_parsers.add_parser(name="install", description="install a mineral")
install_command.add_argument("name", help="name of the mineral to install") install_command.add_argument("names", help="name of the minerals to install", nargs="+")
install_command.add_argument("--max-retries", help="max number of download retries before giving up", default=3, type=int) install_command.add_argument("--max-retries", help="max number of download retries before giving up", default=3, type=int)
# uninstall command # uninstall command
@@ -22,40 +26,33 @@ def parse_arguments():
# list command # list command
list_command = sub_parsers.add_parser(name="list", description="list all minerals installed in the current environment") list_command = sub_parsers.add_parser(name="list", description="list all minerals installed in the current environment")
list_command.add_argument("--env_name", help="list all minerals from a specific environment.", default=None, required=False)
# publish command # publish command
publish_command = sub_parsers.add_parser(name="publish", description="publish a package to the repository") publish_command = sub_parsers.add_parser(name="publish", description="publish a package to the repository")
publish_command.add_argument("name", help="name and version of the package") #publish_command.add_argument("name", help="name and version of the package")
publish_command.add_argument("folder_path", help="path to the folder that will be uploaded") publish_command.add_argument("folder_path", help="path to the folder that will be uploaded")
# remove command # remove command
remove_command = sub_parsers.add_parser(name="remove", description="remove a published package from the repository") remove_command = sub_parsers.add_parser(name="remove", description="remove a published package from the repository")
remove_command.add_argument("name", help="name and version of the package") remove_command.add_argument("name", help="name and version of the package")
# env command # build command
"""env_command = sub_parsers.add_parser(name="env", description="manage Ground environments") build_command = sub_parsers.add_parser(name="build", description="build a folder as a mineral and either install it or prepare it for publishing")
env_sub_parsers = env_command.add_subparsers(dest="env_command") build_command.add_argument("folder_path", help="path to the folder to build")
build_command.add_argument("--gcc-args", nargs="*", help="any extra args you want to give to gcc")
env_new_command = env_sub_parsers.add_parser(name="new", description="create a new environment") build_command.add_argument("--package", action="store_true", help="generate a folder with a mineral.ini and all the other files you need to publish the package")
env_new_command.add_argument("name", help="name of the new environment")
env_new_command.add_argument("--dont-use", help="by default, the newly created environment will be set as the current environment. if this argument is parsed, then it will be created but not activated.", action="store_true")
env_destroy_command = env_sub_parsers.add_parser(name="destroy", description="delete an environment")
env_destroy_command.add_argument("name", help="name of the environment to DESTROYYY")
env_destroy_command.add_argument("--confirm-yes", action="store_true", help="don't ask for confirmation (DANGEROUS)")
env_list_command = env_sub_parsers.add_parser(name="list", description="list all environments")"""
# docs command
docs_command = sub_parsers.add_parser(name="docs", description="read the docs of a mineral")
docs_command.add_argument("mineral_name", help="name of the mineral you want to read the docs of")
docs_command.add_argument("-d", "--doc-file", help="load a specific doc file")
# parse arguments are run the command we chose
args = arg_parser.parse_args() args = arg_parser.parse_args()
if not args.command: if not args.command:
arg_parser.print_help() arg_parser.print_help()
sys.exit(0) sys.exit(0)
if args.command == "env" and not args.env_command:
env_command.print_help()
sys.exit(0)
if args.command == "install": if args.command == "install":
install(args) install(args)
@@ -63,6 +60,15 @@ def parse_arguments():
publish(args) publish(args)
elif args.command == "remove": elif args.command == "remove":
remove(args) remove(args)
elif args.command == "list":
list_cmd(args)
elif args.command == "uninstall":
uninstall(args)
elif args.command == "build":
build(args)
elif args.command == "docs":
from .docs import docs
docs(args)
def main(): def main():
parse_arguments() parse_arguments()

90
dig/publish.py Normal file
View File

@@ -0,0 +1,90 @@
import configparser
import tarfile
import tempfile
import os, sys
import requests
from requests.auth import HTTPBasicAuth
from rich.console import Console
from rich.prompt import Prompt
console = Console()
def publish(args):
mineral_name = os.path.basename(args.folder_path)
# sanity checks
if not os.path.isdir(args.folder_path):
console.print(f"[b red]digpkg: failed to publish mineral: \"{args.folder_path}\" is not a directory")
sys.exit(1)
if not os.path.isfile(os.path.join(args.folder_path, "mineral.ini")):
console.print(f"[b red]digpkg: failed to publish mineral: mineral has no \"mineral.ini\" file")
sys.exit(1)
if os.path.basename(os.path.normpath(args.folder_path)).endswith("_build"):
console.print(f"\n[b yellow]You didn't remove the \"_build\" suffix from your mineral's folder name!\n\nIf this is intentional you can ignore this message, however it is bad practice.\nIf this is not intentional, you will be unable to install your package properly using dig.[/]")
config_parser = configparser.ConfigParser()
config_parser.read(os.path.join(args.folder_path, "mineral.ini"))
version = config_parser["package"]["version"]
# ask for user and pass
console.print("[b]Please authenticate.\n[/]")
try:
username = Prompt.ask("Username", console=console)
password = Prompt.ask("Password (or PAT)", console=console, password=True)
except KeyboardInterrupt:
return
console.print()
with console.status("Authenticating...", spinner="bouncingBall", spinner_style="blue") as status:
# check if we have permission to link the package to the repo
repo_perms_request = requests.get(
url=f"https://chookspace.com/api/v1/users/{username}/orgs/ground/permissions",
auth=HTTPBasicAuth(username, password)
)
if repo_perms_request.status_code == 401:
console.print(f"[b red]digpkg: failed to publish mineral: checking authorization failed: invalid password[/b red]")
sys.exit(1)
elif not repo_perms_request.ok:
console.print(f"[b red]digpkg: failed to publish mineral: checking authorization failed: {repo_perms_request.content.decode()}[/b red]")
sys.exit(1)
# compress to a tar file
console.status("Compressing", spinner_style="green")
f = tempfile.TemporaryFile(mode="wb+")
with tarfile.open(fileobj=f, mode="w:gz") as tar_file:
tar_file.add(args.folder_path, arcname=os.path.basename(args.folder_path))
f.flush()
f.seek(0)
console.print("[d][:white_check_mark:] Compressed![/]")
# send the request
status.update("Uploading...", spinner_style="blue")
response = requests.put(
url=f"https://chookspace.com/api/packages/ground/generic/{mineral_name}/{version}/mineral.tar",
data=f,
auth=HTTPBasicAuth(username, password)
)
f.close()
match response.status_code:
case 401:
console.print("[b red]digpkg: failed to publish mineral: authentication failed[/]")
sys.exit(1)
case 400:
console.print("[b red]digpkg: failed to publish mineral: the package name or version number are invalid[/]")
sys.exit(1)
case 409:
console.print("[b red]digpkg: failed to publish mineral: that version number is already in use[/]")
sys.exit(1)
response.raise_for_status()
console.print("[d][:white_check_mark:] Uploaded![/]")
console.print("[:white_check_mark:] Done!")

View File

@@ -30,7 +30,7 @@ def remove(args):
# send the request # send the request
response = requests.delete( response = requests.delete(
url=f"https://chookspace.com/api/packages/{username}/generic/{mineral_name}/{version}", url=f"https://chookspace.com/api/packages/ground/generic/{mineral_name}/{version}",
auth=HTTPBasicAuth(username, password) auth=HTTPBasicAuth(username, password)
) )
@@ -42,4 +42,4 @@ def remove(args):
sys.exit(1) sys.exit(1)
response.raise_for_status() response.raise_for_status()
console.print("[d][:white_check_mark:] Success![/]") console.print("[d][:white_check_mark:] Done![/]")

36
dig/uninstall.py Normal file
View File

@@ -0,0 +1,36 @@
import os, sys
import shutil
from .util import check_ground_libs_path, check_sudo
from rich.console import Console
console = Console()
def uninstall(args):
check_sudo()
check_ground_libs_path()
with console.status(status=f"Looking for [i]{args.name}[/]...", spinner="bouncingBall", spinner_style="green") as status:
mineral_path = os.path.join(os.getenv("GROUND_LIBS"), args.name)
symlink_path = os.path.join(os.getenv("GROUND_LIBS"), f"{args.name}.so")
# check to make sure the mineral is installed
if not os.path.isdir(mineral_path):
console.print(f"[b red]digpkg: failed to uninstall [i]{args.name}[/]: mineral is not installed[/b red]")
sys.exit(1)
# remove the symlink
status.update("Removing symlink...")
if os.path.islink(symlink_path):
os.unlink(symlink_path)
console.print(f"[d][:white_check_mark:] Removed symlink!")
# delete the folder
shutil.rmtree(mineral_path)
console.print(f"[d][:white_check_mark:] Removed mineral folder!")
console.print(f"[:white_check_mark:] Done!")

16
dig/util.py Normal file
View File

@@ -0,0 +1,16 @@
import os, sys
from rich import print
def check_ground_libs_path():
# ensure the GROUND_LIBS var is set
if not os.getenv("GROUND_LIBS"):
print("[d]digpkg: the [i]GROUND_LIBS[/] environment variable is not set, defaulting to /usr/lib/ground/")
os.environ["GROUND_LIBS"] = "/usr/lib/ground/"
def check_sudo():
# check if we are sudo
if os.getuid() != 0:
print("[b red]digpkg: that command requires sudo to run[/]")
sys.exit(1)

BIN
mineral.tar Normal file

Binary file not shown.

View File

@@ -1,2 +1,3 @@
rich setuptools
requests wheel
twine

17
setup.py Normal file
View File

@@ -0,0 +1,17 @@
from setuptools import setup, find_packages
setup(
name="digpkg",
version="1.1",
packages=find_packages(),
install_requires=[
"textual>=7.3.0",
"requests>=2.32.5",
"textual-autocomplete>=4.0.6"
],
entry_points={
"console_scripts": [
"dig = dig:main"
]
}
)

View File

@@ -1,81 +0,0 @@
import requests
import os
import sys
import tempfile
import tarfile
from rich.console import Console
from rich.progress import SpinnerColumn
console = Console()
def install(args):
# check if we are sudo
if os.getuid() != 0:
console.print("[b red]digpkg: the install command requires sudo to run[/]")
sys.exit(1)
# ensure the GROUND_LIBS var is set
if not os.getenv("GROUND_LIBS"):
console.print("digpkg: the [i]GROUND_LIBS[/] environment variable is not set, defaulting to /usr/lib/ground/")
os.environ["GROUND_LIBS"] = "/usr/lib/ground/"
# figure out which version to install
package_name = args.name
version = "latest"
if "@" in args.name:
split = package_name.split("@")
package_name = split[0]
version = split[1]
retries_left = args.max_retries
with console.status("Downloading tarball...", spinner="bouncingBall", spinner_style="blue") as status:
while retries_left > 0:
# grab the tar ball
response = requests.get(f"https://chookspace.com/api/packages/SpookyDervish/generic/{package_name}/{version}/mineral.tar")
# check response code for errors
if response.status_code == 404: # package doesn't exist
console.print(f"[b red]digpkg: mineral \"{package_name}\" was not found. Check to make sure the name and version number are correct.[/]")
sys.exit(1)
elif response.status_code != 200:
retries_left -= 1
console.print(f"[b yellow]digpkg: failed to download mineral \"{package_name}\": {response.content.decode()} ({retries_left} retries left)[/]")
if retries_left == 0:
console.print(f"[b red]digpkg: exceeded max retries while downloading mineral \"{package_name}\"[/]")
sys.exit(1)
continue
response.raise_for_status()
break
# create temporary file for tarball
try:
f = tempfile.TemporaryFile("wb+")
f.write(response.content)
f.flush()
f.seek(0)
console.print("[d][:white_check_mark:] Tarball downloaded![/]")
except KeyboardInterrupt:
console.print("[b yellow]digpkg: operation cancelled by user[/]")
return
# extract the tarball to the GROUND_LIBS folder
status.update("Extracting...")
extract_dir = os.getenv("GROUND_LIBS")
if not os.path.isdir(extract_dir): # gotta ensure the folder exists
os.mkdir(extract_dir)
tar_file = tarfile.open(fileobj=f)
tar_file.extractall(extract_dir)
f.close()
console.print(f"[d][:white_check_mark:] Extracted to {extract_dir}.")

View File

@@ -1,73 +0,0 @@
import tarfile
import tempfile
import os, sys
import requests
from requests.auth import HTTPBasicAuth
from rich.console import Console
from rich.prompt import Prompt
console = Console()
def publish(args):
if not "@" in args.name:
console.print(f"[b red]digpkg: failed to publish mineral: please include the version number in the package name. e.g: request@1.0.0")
sys.exit(1)
split_name = args.name.split("@")
mineral_name = split_name[0]
version = split_name[1]
# sanity checks
if not os.path.isdir(args.folder_path):
console.print(f"[b red]digpkg: failed to publish mineral: \"{args.folder_path}\" is not a directory")
sys.exit(1)
if not os.path.isfile(os.path.join(args.folder_path, "mineral.ini")):
console.print(f"[b red]digpkg: failed to publish mineral: mineral has no \"mineral.ini\" file")
sys.exit(1)
# ask for user and pass
console.print("[b]Please authenticate.\n[/]")
try:
username = Prompt.ask("Username", console=console)
password = Prompt.ask("Password (or PAT)", console=console, password=True)
except KeyboardInterrupt:
return
console.print()
with console.status("Compressing...", spinner="bouncingBall", spinner_style="blue") as status:
# compress to a tar file
f = tempfile.TemporaryFile(mode="wb+")
with tarfile.open(fileobj=f, mode="w:gz") as tar_file:
tar_file.add(args.folder_path, arcname=os.path.basename(args.folder_path))
f.flush()
f.seek(0)
console.print("[d][:white_check_mark:] Compressed![/]")
# send the request
status.update("Uploading...")
response = requests.put(
url=f"https://chookspace.com/api/packages/{username}/generic/{mineral_name}/{version}/mineral.tar",
data=f,
auth=HTTPBasicAuth(username, password)
)
f.close()
if response.status_code == 401:
console.print("[b red]digpkg: failed to publish mineral: authentication failed[/]")
sys.exit(1)
elif response.status_code == 400:
console.print("[b red]digpkg: failed to publish mineral: the package name or version number are invalid[/]")
sys.exit(1)
elif response.status_code == 409:
console.print("[b red]digpkg: failed to publish mineral: that version number is already in use[/]")
sys.exit(1)
response.raise_for_status()
console.print("[d][:white_check_mark:] Uploaded![/]")

4
test.grnd Normal file
View File

@@ -0,0 +1,4 @@
extern "math"
call !math_RandomDouble 1.0 10.0 &var
println $var