|
@@ -39,33 +39,15 @@ def slug_to_identifier(slug): |
|
|
return slug |
|
|
return slug |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def usage(script): |
|
|
|
|
|
text = f"""Usage: {script} <command> ... |
|
|
|
|
|
Run commands without arguments for command help. |
|
|
|
|
|
|
|
|
|
|
|
Commands: |
|
|
|
|
|
createplugin <slug> |
|
|
|
|
|
createmodule <module slug> |
|
|
|
|
|
createmanifest |
|
|
|
|
|
""" |
|
|
|
|
|
print(text) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def usage_create_plugin(script): |
|
|
|
|
|
text = f"""Usage: {script} createplugin <slug> |
|
|
|
|
|
|
|
|
|
|
|
A directory <slug> will be created in the current working directory and seeded with initial files. |
|
|
|
|
|
""" |
|
|
|
|
|
print(text) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_plugin(slug): |
|
|
|
|
|
|
|
|
def create_plugin(slug, plugin_dir=None): |
|
|
# Check slug |
|
|
# Check slug |
|
|
if not is_valid_slug(slug): |
|
|
if not is_valid_slug(slug): |
|
|
raise UserException("Slug must only contain ASCII letters, numbers, '-', and '_'.") |
|
|
raise UserException("Slug must only contain ASCII letters, numbers, '-', and '_'.") |
|
|
|
|
|
|
|
|
|
|
|
if not plugin_dir: |
|
|
|
|
|
plugin_dir = os.path.join(slug, '') |
|
|
|
|
|
|
|
|
# Check if plugin directory exists |
|
|
# Check if plugin directory exists |
|
|
plugin_dir = os.path.join(slug, '') |
|
|
|
|
|
if os.path.exists(plugin_dir): |
|
|
if os.path.exists(plugin_dir): |
|
|
raise UserException(f"Directory {plugin_dir} already exists") |
|
|
raise UserException(f"Directory {plugin_dir} already exists") |
|
|
|
|
|
|
|
@@ -74,7 +56,7 @@ def create_plugin(slug): |
|
|
|
|
|
|
|
|
# Create manifest |
|
|
# Create manifest |
|
|
try: |
|
|
try: |
|
|
create_manifest(plugin_dir, slug) |
|
|
|
|
|
|
|
|
create_manifest(slug, plugin_dir) |
|
|
except Exception as e: |
|
|
except Exception as e: |
|
|
os.rmdir(plugin_dir) |
|
|
os.rmdir(plugin_dir) |
|
|
raise e |
|
|
raise e |
|
@@ -120,6 +102,7 @@ using namespace rack; |
|
|
extern Plugin *pluginInstance; |
|
|
extern Plugin *pluginInstance; |
|
|
|
|
|
|
|
|
// Declare each Model, defined in each module source file |
|
|
// Declare each Model, defined in each module source file |
|
|
|
|
|
// extern Model *modelMyModule; |
|
|
""" |
|
|
""" |
|
|
with open(os.path.join(plugin_dir, "src/plugin.hpp"), "w") as f: |
|
|
with open(os.path.join(plugin_dir, "src/plugin.hpp"), "w") as f: |
|
|
f.write(plugin_hpp) |
|
|
f.write(plugin_hpp) |
|
@@ -159,58 +142,57 @@ void init(Plugin *p) { |
|
|
print(f"You may use `make`, `make clean`, `make dist`, `make install`, etc in the {plugin_dir} directory.") |
|
|
print(f"You may use `make`, `make clean`, `make dist`, `make install`, etc in the {plugin_dir} directory.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_manifest(plugin_dir, slug=None): |
|
|
|
|
|
manifest = {} |
|
|
|
|
|
|
|
|
def create_manifest(slug, plugin_dir="."): |
|
|
|
|
|
# Default manifest |
|
|
|
|
|
manifest = { |
|
|
|
|
|
'slug': slug, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# Try to load existing manifest file |
|
|
|
|
|
manifest_filename = os.path.join(plugin_dir, 'plugin.json') |
|
|
|
|
|
try: |
|
|
|
|
|
with open(manifest_filename, "r") as f: |
|
|
|
|
|
manifest = json.load(f) |
|
|
|
|
|
except: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
# Query manifest information |
|
|
# Query manifest information |
|
|
if not slug: |
|
|
|
|
|
slug = input_default("Plugin slug (unique identifier)") |
|
|
|
|
|
manifest['slug'] = slug |
|
|
|
|
|
manifest['name'] = input_default("Plugin name", slug) |
|
|
|
|
|
manifest['version'] = input_default("Version", "1.0.0") |
|
|
|
|
|
manifest['license'] = input_default("License (if open-source, use license identifier from https://spdx.org/licenses/)", "proprietary") |
|
|
|
|
|
manifest['author'] = input_default("Author") |
|
|
|
|
|
manifest['authorEmail'] = input_default("Author email (optional)") |
|
|
|
|
|
manifest['authorUrl'] = input_default("Author website URL (optional)") |
|
|
|
|
|
manifest['pluginUrl'] = input_default("Plugin website URL (optional)") |
|
|
|
|
|
manifest['manualUrl'] = input_default("Manual website URL (optional)") |
|
|
|
|
|
manifest['sourceUrl'] = input_default("Source code URL (optional)") |
|
|
|
|
|
manifest['donateUrl'] = input_default("Donate URL (optional)") |
|
|
|
|
|
manifest['modules'] = [] |
|
|
|
|
|
|
|
|
manifest['name'] = input_default("Plugin name", manifest.get('name', slug)) |
|
|
|
|
|
manifest['version'] = input_default("Version", manifest.get('version', "1.0.0")) |
|
|
|
|
|
manifest['license'] = input_default("License (if open-source, use license identifier from https://spdx.org/licenses/)", manifest.get('license', "proprietary")) |
|
|
|
|
|
manifest['author'] = input_default("Author", manifest.get('author', "")) |
|
|
|
|
|
manifest['authorEmail'] = input_default("Author email (optional)", manifest.get('authorEmail', "")) |
|
|
|
|
|
manifest['authorUrl'] = input_default("Author website URL (optional)", manifest.get('authorUrl', "")) |
|
|
|
|
|
manifest['pluginUrl'] = input_default("Plugin website URL (optional)", manifest.get('pluginUrl', "")) |
|
|
|
|
|
manifest['manualUrl'] = input_default("Manual website URL (optional)", manifest.get('manualUrl', "")) |
|
|
|
|
|
manifest['sourceUrl'] = input_default("Source code URL (optional)", manifest.get('sourceUrl', "")) |
|
|
|
|
|
manifest['donateUrl'] = input_default("Donate URL (optional)", manifest.get('donateUrl', "")) |
|
|
|
|
|
|
|
|
|
|
|
if 'modules' not in manifest: |
|
|
|
|
|
manifest['modules'] = [] |
|
|
|
|
|
|
|
|
# Dump JSON |
|
|
# Dump JSON |
|
|
manifest_filename = os.path.join(plugin_dir, 'plugin.json') |
|
|
|
|
|
with open(manifest_filename, "w") as f: |
|
|
with open(manifest_filename, "w") as f: |
|
|
json.dump(manifest, f, indent="\t") |
|
|
|
|
|
print(f"Manifest created at {manifest_filename}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def usage_create_module(script): |
|
|
|
|
|
text = f"""Usage: {script} createmodule <module slug> |
|
|
|
|
|
|
|
|
json.dump(manifest, f, indent=" ") |
|
|
|
|
|
print(f"Manifest written to {manifest_filename}") |
|
|
|
|
|
|
|
|
Must be called in a plugin directory. |
|
|
|
|
|
A panel file must exist in res/<module slug>.svg. |
|
|
|
|
|
A source file will be created at src/<module slug>.cpp. |
|
|
|
|
|
|
|
|
|
|
|
See https://vcvrack.com/manual/PanelTutorial.html for creating SVG panel files. |
|
|
|
|
|
""" |
|
|
|
|
|
print(text) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_module(slug): |
|
|
|
|
|
|
|
|
def create_module(slug, panel_filename=None, source_filename=None): |
|
|
# Check slug |
|
|
# Check slug |
|
|
if not is_valid_slug(slug): |
|
|
if not is_valid_slug(slug): |
|
|
raise UserException("Slug must only contain ASCII letters, numbers, '-', and '_'.") |
|
|
raise UserException("Slug must only contain ASCII letters, numbers, '-', and '_'.") |
|
|
|
|
|
|
|
|
# Read manifest |
|
|
# Read manifest |
|
|
manifest_filename = 'plugin.json' |
|
|
manifest_filename = 'plugin.json' |
|
|
manifest = None |
|
|
|
|
|
with open(manifest_filename, "r") as f: |
|
|
with open(manifest_filename, "r") as f: |
|
|
manifest = json.load(f) |
|
|
manifest = json.load(f) |
|
|
|
|
|
|
|
|
# Check if module manifest exists |
|
|
# Check if module manifest exists |
|
|
module_manifest = find(lambda m: m['slug'] == slug, manifest['modules']) |
|
|
module_manifest = find(lambda m: m['slug'] == slug, manifest['modules']) |
|
|
if not module_manifest: |
|
|
|
|
|
|
|
|
if module_manifest: |
|
|
|
|
|
print(f"Module {slug} already exists in plugin.json. Edit this file to modify the module manifest.") |
|
|
|
|
|
|
|
|
|
|
|
else: |
|
|
# Add module to manifest |
|
|
# Add module to manifest |
|
|
module_manifest = {} |
|
|
module_manifest = {} |
|
|
module_manifest['slug'] = slug |
|
|
module_manifest['slug'] = slug |
|
@@ -227,50 +209,44 @@ def create_module(slug): |
|
|
|
|
|
|
|
|
# Write manifest |
|
|
# Write manifest |
|
|
with open(manifest_filename, "w") as f: |
|
|
with open(manifest_filename, "w") as f: |
|
|
json.dump(manifest, f, indent="\t") |
|
|
|
|
|
|
|
|
json.dump(manifest, f, indent=" ") |
|
|
|
|
|
|
|
|
print(f"Added {slug} to plugin.json") |
|
|
print(f"Added {slug} to plugin.json") |
|
|
|
|
|
|
|
|
else: |
|
|
|
|
|
print(f"Module {slug} already exists in plugin.json. Edit this file to modify the module manifest.") |
|
|
|
|
|
|
|
|
|
|
|
# Check filenames |
|
|
# Check filenames |
|
|
panel_filename = f"res/{slug}.svg" |
|
|
|
|
|
source_filename = f"src/{slug}.cpp" |
|
|
|
|
|
|
|
|
if panel_filename and source_filename: |
|
|
|
|
|
if not os.path.exists(panel_filename): |
|
|
|
|
|
raise UserException(f"Panel not found at {panel_filename}.") |
|
|
|
|
|
|
|
|
if not os.path.exists(panel_filename): |
|
|
|
|
|
print(f"Panel not found at {panel_filename}. If you wish to automatically generate a source file, run this command with no arguments for instructions for creating a panel file.") |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
print(f"Panel found at {panel_filename}. Generating source file.") |
|
|
|
|
|
|
|
|
print(f"Panel found at {panel_filename}. Generating source file.") |
|
|
|
|
|
|
|
|
if os.path.exists(source_filename): |
|
|
|
|
|
if input_default(f"{source_filename} already exists. Overwrite?", "n").lower() != "y": |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
if os.path.exists(source_filename): |
|
|
|
|
|
if input_default(f"{source_filename} already exists. Overwrite?", "n").lower() != "y": |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
# Read SVG XML |
|
|
|
|
|
tree = xml.etree.ElementTree.parse(panel_filename) |
|
|
|
|
|
|
|
|
# Read SVG XML |
|
|
|
|
|
tree = xml.etree.ElementTree.parse(panel_filename) |
|
|
|
|
|
|
|
|
components = panel_to_components(tree) |
|
|
|
|
|
print(f"Components extracted from {panel_filename}") |
|
|
|
|
|
|
|
|
components = panel_to_components(tree) |
|
|
|
|
|
print(f"Components extracted from {panel_filename}") |
|
|
|
|
|
|
|
|
# Write source |
|
|
|
|
|
source = components_to_source(components, slug) |
|
|
|
|
|
|
|
|
# Write source |
|
|
|
|
|
source = components_to_source(components, slug) |
|
|
|
|
|
|
|
|
with open(source_filename, "w") as f: |
|
|
|
|
|
f.write(source) |
|
|
|
|
|
print(f"Source file generated at {source_filename}") |
|
|
|
|
|
|
|
|
with open(source_filename, "w") as f: |
|
|
|
|
|
f.write(source) |
|
|
|
|
|
print(f"Source file generated at {source_filename}") |
|
|
|
|
|
|
|
|
# Append model to plugin.hpp |
|
|
|
|
|
identifier = slug_to_identifier(slug) |
|
|
|
|
|
|
|
|
# Append model to plugin.hpp |
|
|
|
|
|
identifier = slug_to_identifier(slug) |
|
|
|
|
|
|
|
|
# Tell user to add model to plugin.hpp and plugin.cpp |
|
|
|
|
|
print(f"") |
|
|
|
|
|
print(f"To enable the module, add") |
|
|
|
|
|
print(f"extern Model *model{identifier};") |
|
|
|
|
|
print(f"to plugin.hpp, and add") |
|
|
|
|
|
print(f"p->addModel(model{identifier});") |
|
|
|
|
|
print(f"to the init() function in plugin.cpp.") |
|
|
|
|
|
|
|
|
# Tell user to add model to plugin.hpp and plugin.cpp |
|
|
|
|
|
print(f""" |
|
|
|
|
|
To enable the module, add |
|
|
|
|
|
extern Model *model{identifier}; |
|
|
|
|
|
to plugin.hpp, and add |
|
|
|
|
|
p->addModel(model{identifier}); |
|
|
|
|
|
to the init() function in plugin.cpp.""") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def panel_to_components(tree): |
|
|
def panel_to_components(tree): |
|
@@ -498,24 +474,49 @@ Model *model{identifier} = createModel<{identifier}, {identifier}Widget>("{slug} |
|
|
return source |
|
|
return source |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def usage(script): |
|
|
|
|
|
text = f"""VCV Rack Plugin Helper Utility |
|
|
|
|
|
|
|
|
|
|
|
Usage: {script} <command> ... |
|
|
|
|
|
Commands: |
|
|
|
|
|
|
|
|
|
|
|
createplugin <slug> [plugin dir] |
|
|
|
|
|
|
|
|
|
|
|
A directory will be created and initialized with a minimal plugin template. |
|
|
|
|
|
If no plugin directory is given, the slug is used. |
|
|
|
|
|
|
|
|
|
|
|
createmanifest <slug> [plugin dir] |
|
|
|
|
|
|
|
|
|
|
|
Creates a `plugin.json` manifest file in an existing plugin directory. |
|
|
|
|
|
If no plugin directory is given, the current directory is used. |
|
|
|
|
|
|
|
|
|
|
|
createmodule <module slug> [panel file] [source file] |
|
|
|
|
|
|
|
|
|
|
|
Adds a new module to the plugin manifest in the current directory. |
|
|
|
|
|
If a panel and source file are given, generates a template source file initialized with components from a panel file. |
|
|
|
|
|
Example: |
|
|
|
|
|
{script} createmodule MyModule res/MyModule.svg src/MyModule.cpp |
|
|
|
|
|
|
|
|
|
|
|
See https://vcvrack.com/manual/PanelTutorial.html for creating SVG panel files. |
|
|
|
|
|
""" |
|
|
|
|
|
print(text) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(args): |
|
|
def parse_args(args): |
|
|
if len(args) >= 2: |
|
|
|
|
|
if args[1] == 'createplugin': |
|
|
|
|
|
if len(args) >= 3: |
|
|
|
|
|
create_plugin(args[2]) |
|
|
|
|
|
return |
|
|
|
|
|
usage_create_plugin(args[0]) |
|
|
|
|
|
return |
|
|
|
|
|
if args[1] == 'createmodule': |
|
|
|
|
|
if len(args) >= 3: |
|
|
|
|
|
create_module(args[2]) |
|
|
|
|
|
return |
|
|
|
|
|
usage_create_module(args[0]) |
|
|
|
|
|
return |
|
|
|
|
|
if args[1] == 'createmanifest': |
|
|
|
|
|
create_manifest('.') |
|
|
|
|
|
return |
|
|
|
|
|
usage(args[0]) |
|
|
|
|
|
|
|
|
script = args.pop(0) |
|
|
|
|
|
if len(args) == 0: |
|
|
|
|
|
usage(script) |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
cmd = args.pop(0) |
|
|
|
|
|
if cmd == 'createplugin': |
|
|
|
|
|
create_plugin(*args) |
|
|
|
|
|
elif cmd == 'createmodule': |
|
|
|
|
|
create_module(*args) |
|
|
|
|
|
elif cmd == 'createmanifest': |
|
|
|
|
|
create_manifest(*args) |
|
|
|
|
|
else: |
|
|
|
|
|
print(f"Command not found: {cmd}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
if __name__ == "__main__": |
|
|