Skip to content

Instantly share code, notes, and snippets.

@stephenhandley
Created February 7, 2025 06:00
Show Gist options
  • Save stephenhandley/9d9c3c838081c58cddaf06f4efa5073c to your computer and use it in GitHub Desktop.
Save stephenhandley/9d9c3c838081c58cddaf06f4efa5073c to your computer and use it in GitHub Desktop.
Script for creating and updating single file python/uv based scripts
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# ]
# description = "Script for creating and updating single file python/uv based scripts"
# ///
"""
uvscript: Script for creating and updating single file python/uv based scripts
Usage:
uvscript <name_of_new_or_existing_script>
--dependencies <dep1> <dep2> ...
--requires-python "<python_version_string>"
--description "<script description>"
"""
import argparse
import os
import re
def generate(dependencies, requires_python, description):
header = f"""#!/usr/bin/env -S uv run --script
# /// script
# requires-python = "{requires_python}"
# dependencies = [
"""
for dep in dependencies:
header += f'# "{dep}",\n'
header += "# ]\n"
if description:
header += f'# description = "{description}"\n'
header += "# ///\n\n"
# Default main function scaffold with an example argparse argument.
desc_text = description if description else "This is a self-contained python executable using uv"
main_code = f"""def main():
import argparse
import sys
parser = argparse.ArgumentParser(description="{desc_text}")
parser.add_argument("--example", action="store_true", help="This is an example argument")
# Print usage if no arguments are passed
if len(sys.argv) == 1:
parser.print_usage()
sys.exit(1)
args = parser.parse_args()
if args.example:
print("--example argument was used!")
if __name__ == "__main__":
main()
"""
return header + main_code
def update(contents, dependencies, requires_python, description):
"""
Update the header block delimited by "# /// script" and "# ///" in the file.
If the header block is not found, prepend a full scaffold.
"""
pattern = re.compile(r'(^# /// script\s+)(.*?)(^# ///\s*$)', re.MULTILINE | re.DOTALL)
m = pattern.search(contents)
if not m:
# If header not found, prepend new scaffold header.
return generate(dependencies, requires_python, description) + contents
header_block = m.group(2)
# Update requires-python line.
header_block = re.sub(r'#\s*requires-python\s*=\s*".*?"', f'# requires-python = "{requires_python}"', header_block)
# Update dependencies block.
new_deps_str = "# dependencies = [\n"
for dep in dependencies:
new_deps_str += f'# "{dep}",\n'
new_deps_str += "# ]"
header_block = re.sub(r'#\s*dependencies\s*=\s*\[.*?\n#\s*\]', new_deps_str, header_block, flags=re.DOTALL)
# Update description.
if description:
if re.search(r'#\s*description\s*=', header_block):
header_block = re.sub(r'#\s*description\s*=\s*".*?"', f'# description = "{description}"', header_block)
else:
header_block = header_block.rstrip("\n") + f'\n# description = "{description}"\n'
new_header = m.group(1) + header_block + m.group(3)
# Replace the original header block with the updated one.
new_contents = contents[:m.start()] + new_header + contents[m.end():]
# Also update the argparse description in the main code if present.
if description:
new_contents = re.sub(
r'(parser\s*=\s*argparse\.ArgumentParser\(\s*description\s*=\s*")[^"]*(")',
r'\1' + description + r'\2',
new_contents
)
return new_contents
def main():
parser = argparse.ArgumentParser(
description="A tool for creating and updating self-contained executable Python scripts with uv header metadata."
)
parser.add_argument("script", help="The new or existing script file to create/update.")
parser.add_argument("--dependencies", nargs="*", default=[], help="Optional list of dependencies")
parser.add_argument("--requires-python", default=">=3.11", help="Optional python version string")
parser.add_argument("--description", default="", help="Optional script description")
args = parser.parse_args()
script_name = args.script
dependencies = args.dependencies
requires_python = args.requires_python
description = args.description
if not os.path.exists(script_name):
# Generate a new script with the scaffold.
contents = generate(dependencies, requires_python, description)
with open(script_name, "w") as f:
f.write(contents)
os.chmod(script_name, 0o755)
print(f"Created new script: {script_name}")
else:
# Update the header in the existing file.
with open(script_name, "r") as f:
contents = f.read()
contents = update(contents, dependencies, requires_python, description)
with open(script_name, "w") as f:
f.write(contents)
print(f"Updated header in existing script: {script_name}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment