Skip to content
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
61 changes: 41 additions & 20 deletions docs/fuddly.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -50,46 +50,49 @@ a *-h* and *--help* options to describe their use from the command line

## RUN

*fuddly* run list | script_name [script_args...]
*fuddly* run list | (project script_name [script_args...])

*list*
This option gives you a list of all the available scripts.

*project*
Name of the project from which to run the script. Using *list*
will show all available projects.

*script_name*
Name of the script to launch, the format is a python package like
syntax as given from thte *list* argument.
Name of the script to launch, using *list* will show all available
scripts for that project.

*script_args...*
Arguments to give to the script as if it was its command line arguments.

## NEW

*fuddly* new [OPTIONS] object name
*fuddly* new [OPTIONS] name

*object*
Type of object to create. [dm, data-model, project:<template>]
The "project" object should be followed by ":<template>" where
"<template>" is the name of the project template to use.
*name*
Name to give the created object. This should conform to python's module
naming convention.

There are for now, 2 project template:
- bare
- exemple
*--clone* object_name
Clone an existing data-model, project or target.

The "bare" template creates a barebones project with only the
structure and a few lines of code so you can get on with your work.
The object_name shall be the python module name identifying the object
to clone. Using list instead of an object name will show a list of all
available objects that can be cloned.

The "exemple" template is more complete, and more or less creates the
same project as in the fuddly tutorial with comments to guide you on
where to put what.
The new object will be be created in the path given by *--dest*,
following it's default when missing from the command line.

*name*
Name to give the created object. This should conform to python's module
naming convention.
This option is not compatible with *--pyproject*

NOTE: Due to the way argparse works, the *name* argument is still
required when using the *list* argument to *--clone*.

*--dest* PATH
Directory to create the object in.

if PATH is ommited, a default value will be used.
If PATH is omitted, a default value will be used.
If *--pyproject* was used, the default PATH will be the current working directory.
If not, it will be the appropriate folder in the fuddly data_folder. This can be
either "~/.local/share/fuddly/{user_data_models, user_projects}" or
Expand All @@ -100,6 +103,24 @@ a *-h* and *--help* options to describe their use from the command line
structure. A README.md and pyproject.toml file will be added and
partially filled. The object will be placed in the "src/" directory.

This option is incompatible with the *--clone* option

*--type* object_type
Type of object to create. [dm, data-model, project:<template>]
The "project" object should be followed by ":<template>" where
"<template>" is the name of the project template to use.

There are for now, 2 project template:
- bare
- exemple

The "bare" template creates a barebones project with only the
structure and a few lines of code so you can get on with your work.

The "exemple" template is more complete, and more or less creates the
same project as in the fuddly tutorial with comments to guide you on
where to put what.

## TOOL

*fuddly* tool list | tool_name [tool_args...]
Expand Down
68 changes: 44 additions & 24 deletions src/fuddly/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,31 @@
#
################################################################################

import sys
import argcomplete
import fuddly.cli.argparse_wrapper as argparse
import importlib
import sys
from typing import List

# TODO script_argument_completer will be used once a sub-script argument completion logic is developped
from fuddly.cli.run import script_argument_completer
# TODO tool_argument_completer will be used once a sub-script argument completion logic is developped
from fuddly.cli.tool import tool_argument_completer
from fuddly.cli.utils import (
get_projects,
get_tools,
get_all_objects,
get_project_scripts,
)
from fuddly.cli.error import CliException

import argcomplete
# Import magic
# import fuddly.{obj_type} will find targets, data_models, projects or info
# automagically wethere they are define in an entry point, as part of fuddly's
# core or in the user_data_folder
from fuddly.libs.importer import fuddly_importer_hook
fuddly_importer_hook.setup()

# TODO script_argument_completer will be used once a sub-script argument completion logic is developped
from .run import get_scripts, script_argument_completer
# TODO tool_argument_completer will be used once a sub-script argument completion logic is developped
from .tool import get_tools, tool_argument_completer
from .show import get_projects

from typing import List
from fuddly.cli.error import CliException

def main(argv: List[str] = None):
# This is done so you can call it from python shell if you want to
Expand Down Expand Up @@ -106,18 +111,26 @@ def main(argv: List[str] = None):
)

p.add_argument(
"script",
metavar="script",
help="name of the script to launch, the special value \"list\" list available scripts",
choices=["list", *get_scripts()],
"project",
help="Project who's script to launch",
choices=["list", *map(lambda x: x[0], get_projects())],
)

#p.add_argument(
# "script",
# metavar="script",
# help="name of the script to launch, the special value \"list\" list available scripts",
#).completer = get_project_scripts

# TODO add arg completion for scripts
p.add_argument(
"args",
"script",
metavar="script",
nargs=argparse.REMAINDER,
help="arguments to pass through to the script",
) # .completer = script_argument_completer
help="name of the script to launch and it's arguments, the special value \"list\" list available scripts",
#help="arguments to pass through to the script",
).completer = get_project_scripts
#) # .completer = script_argument_completer

with subparsers.add_parser("new", help="create a new project or data model") as p:
parsers["new"] = p
Expand All @@ -135,13 +148,20 @@ def main(argv: List[str] = None):
action="store_true",
help="create a python package project structure"
)
p.add_argument(
"object",
choices=["dm", "data-model", "project:bare"],
# This one has not yet been create: "project:example"
metavar="object",
help="type of object to create. [dm, data-model, project]",
)
with p.add_mutually_exclusive_group() as g:
g.add_argument(
"--clone",
metavar="object_name",
help="name of the object to clone.",
choices=["list", *get_all_objects()],
)
g.add_argument(
"--type",
choices=["dm", "data-model", "project:bare"],
# This one has not yet been create: "project:example"
metavar="object",
help="type of object to create. [dm, data-model, project:bare]",
)
p.add_argument(
"name",
help="name to give the create object.",
Expand Down
103 changes: 78 additions & 25 deletions src/fuddly/cli/new.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import fuddly.cli.argparse_wrapper as argparse
from pathlib import Path
from importlib import util
from fuddly.framework import global_resources as gr
import string
import os

import fuddly.cli.argparse_wrapper as argparse
from fuddly.cli.error import CliException
from fuddly.cli.utils import get_module_type, get_all_object_names

conf = {}

Expand Down Expand Up @@ -57,18 +60,31 @@ def __eq__(self, str_b):
def start(args: argparse.Namespace):

_conf = dict()
clone = False
# TODO should the template dir be in fuddly_folder so users can define their own templates?
# origin is the __init__.py file of the module so taking "parent" gives us the module folder
src_dir = Path(util.find_spec("fuddly.cli").origin).parent.joinpath("templates")
module_name = args.name

if args.clone is not None and args.pyproject:
print("--pyproject and --clone cannot be used together")
return 1

dest_dir = Path(gr.fuddly_data_folder).absolute()
if args.dest is not None:
dest_dir = Path(args.dest).absolute()
elif args.pyproject:
dest_dir = Path(".").absolute()
else:
if args.object.startswith("project"):
if args.clone is not None:
# This id not ideal, a better solution would be having a list command to show
# all the modules (and scripts for that matter)
if args.clone == "list":
for n in get_all_object_names():
print(n)
return 0
dest_dir = dest_dir / get_module_type(args.clone)
elif args.type.startswith("project"):
dest_dir = dest_dir/"projects"
else:
dest_dir = dest_dir/"data_models"
Expand All @@ -83,30 +99,56 @@ def start(args: argparse.Namespace):
dest_dir = dest_dir/args.name
dest_dir.mkdir(parents=True)

match PartialMatchString(args.object):
case "dm" | "data-model":
create_msg = f"Creating new data-model \"{module_name}\""
_src_dir = src_dir/"data_model"
_conf = conf["dm"]
object_name = "data_model"
case "project:":
args.object, template = args.object.split(':')
create_msg = f"Creating new project \"{args.name}\" based on the \"{template}\" template"
_src_dir = src_dir/template
if not _src_dir.exists():
print(f"The '{template}' project template does not exist.")
return 1
_conf = conf["project"][template]
object_name = args.object
case _:
dest_dir.rmdir()
raise CliException(f"{args.object} is not a valide object name.")
elif args.type is not None:
match PartialMatchString(args.type):
case "dm" | "data-model":
create_msg = f"Creating new data-model \"{module_name}\""
_src_dir = src_dir/"data_model"
_conf = conf["dm"]
object_name = "data_model"
case "project:":
args.type, template = args.type.split(':')
create_msg = f"Creating new project \"{args.name}\" based on the \"{template}\" template"
_src_dir = src_dir/template
if not _src_dir.exists():
print(f"The '{template}' project template does not exist.")
return 1
_conf = conf["project"][template]
object_name = args.type
case _:
dest_dir.rmdir()
raise CliException(f"{args.type} is not a valide object name.")
elif args.clone is not None:
# Retrieve the files
# Copy them to the src of the new object
create_msg = f'Copying "{args.clone}" to "{module_name}"'
_src_dir = Path(util.find_spec(args.clone).origin).parent
if not _src_dir.exists():
print(f"The '{template}' module does not exist. Check your python install")
return 1
clone = True
_conf = []
for (path, dirs, files) in os.walk(_src_dir):
# Removing an elem from dirs will not go down the directory
if "__pycache__" in dirs:
dirs.remove("__pycache__")
for f in files:
p = str(path).removeprefix(str(_src_dir)).removeprefix("/")
_conf.append({
"name": f,
"path": p,
})

# Find what type the object is off to default to a correct folder
object_name = ""
#module_name = args.clone

if args.pyproject:
_create_conf(
dest_dir,
src_dir/"module",
conf["module"],
clone=False,
name=args.name,
object_name=object_name,
module_name=module_name
Expand All @@ -125,25 +167,36 @@ def start(args: argparse.Namespace):
dest_dir,
_src_dir,
conf=_conf,
clone=clone,
# kwargs
modules_name=module_name,
name=args.name,
object_name=object_name,
)


def _create_conf(dstPath: Path, srcPath: Path, conf: dict, **kwargs):
def _create_conf(dstPath: Path, srcPath: Path, conf: dict, clone: bool, **kwargs):
for e in conf:
_srcPath = srcPath
_dstPath = dstPath
if e.get("path") is not None:
if e.get("path") is not None and e["path"] != "":
_dstPath = _dstPath/e["path"]
_srcPath = _srcPath/e["path"]
_dstPath.mkdir(parents=True)
try:
_dstPath.mkdir(parents=True)
# If the directory exist, we don't care to much
except FileExistsError:
pass
_srcPath = (_srcPath/e["name"])
if ".py" == _srcPath.suffix:
# When cloning a template, the python files are suffixed with _ for VCS
# reasons, when cloning this is not the case, we copy everything as they are
if ".py" == _srcPath.suffix and not clone:
_srcPath = _srcPath.with_suffix(".py_")
data = _srcPath.read_text()
f = _dstPath/e["name"]
f.touch()
f.write_text(string.Template(data).substitute(**kwargs))
if clone:
f.write_text(data)
else:
f.write_text(string.Template(data).substitute(**kwargs))

Loading