Usage¶
Installation¶
To use Calligraphy, first install it using pip:
(.venv) $ pip install calligraphy
Writing Scripts¶
See the Writing section to get started writing scripts in Calligraphy.
Running Scripts¶
To run a script file, you can use the following command:
(.venv) $ calligraphy /path/to/file/to/run arg1 arg2 ...
In addition, you can pass the script source into calligraphy via stdin by using - as your path parameter as follows:
(.venv) $ cat << EOF | calligraphy -
env.MESSAGE = 'Hello world!'
echo "env.MESSAGE"
EOF
Explaining Scripts¶
Calligraphy has the ability to output an annotated version of the source it was provided
in order to show which part of the script it understands to be Python and which part
is Bash. You can view this output by adding the -e or --explain flag to your
command. For example,
(.venv) # cat << EOF | calligraphy -e -
env.MESSAGE = 'Hello world!'
echo "env.MESSAGE"
EOF
Will output
PYTHON | env.MESSAGE = 'Hello world!'
BASH | echo "env.MESSAGE"
Intermediate Output¶
Calligraphy works by transpiling any detected Bash sections of the script into wrapped
Python calls. If you want to view the code that _actually_ is being run after the
transpiling, you can add the -i or --intermediate flag to your calligraphy
command. For example,
cat << EOF | calligraphy -i -
env.MESSAGE = 'Hello world!'
echo "env.MESSAGE"
EOF
Will output
# pylint: disable=W0603
"""
A header module that contains the code required to make transpiled calligraphy
scripts run
"""
import subprocess
import os
import sys
from typing import Union
import importlib.util
sys.argv = ['calligraphy']
class Environment:
"""A class to act as a convient method to access environment variables"""
def __init__(self) -> None:
"""Initialize the Environment object"""
def __getattribute__(self, name: str) -> str:
"""Retrieve an environment variable by name
Args:
name (str): Name of the environment variable to get
Returns:
str: Value of the environment variable accessed
"""
return os.getenv(name)
def __setattr__(self, name: str, value: str) -> None:
"""Set and environment variable to the given value
Args:
name (str): Name of the environment variable to set
value (str): Value to set the environment variable to
"""
os.environ[name] = value
def source_import(calligraphy_path, module_name):
if not calligraphy_path.endswith('.script'):
raise ImportError("Calligraphy only support sourcing other scripts that end with the '.script' extension")
if not os.path.exists(calligraphy_path):
raise FileNotFoundError(f"Sourced script of '{calligraphy_path}' does not exist")
directory, script = os.path.split(calligraphy_path)
python_path = os.path.join(directory, f'.{script[:-7]}.py')
spec = importlib.util.spec_from_file_location(module_name, python_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
RC = 0
env = Environment()
def shell(
cmd: str, get_rc: bool = False, get_stdout: bool = False, silent: bool = False
) -> Union[None, str, int]:
"""Perform a shell call and update the environment with any env variable changes
Args:
cmd (str): The command to run
get_rc (bool, optional): Should the return code of the call be returned.
Defaults to False.
get_stdout (bool, optional): Should the contents of stdout of the call be
returned. Defaults to False.
silent (bool, optional): Should the output to stdout be suppressed when printing
to the terminal. Defaults to False.
Returns:
Union[None, str, int]: Default None, stdout contents if get_stdout is True and
return code if get_rc is True
"""
global RC
global env
cmd = f"echo '{cmd}' | base64 -d | bash"
stdout = []
envout = []
cwd_path = os.getcwd()
with subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE, env=os.environ.copy()
) as proc:
# grab and return the exit code
is_stdout = True
is_env = False
for line in iter(proc.stdout.readline, b""):
str_line = line.decode("utf-8")[:-1]
if str_line == "~~~~START_ENVIRONMENT_HERE~~~~":
if len(stdout) > 1:
if stdout[-2]:
print(stdout[-2])
stdout = stdout[:-1]
is_stdout = False
is_env = True
elif str_line == "~~~~START_CWD_HERE~~~~":
is_env = False
elif is_stdout:
if not silent and len(stdout) > 1:
print(stdout[-2])
stdout.append(str_line)
elif is_env:
envout.append(str_line)
else:
cwd_path = str_line
proc.stdout.close()
proc.wait()
RC = proc.poll()
env.CALLIGRAPHY_RC = str(RC)
os.chdir(cwd_path)
for line in envout:
line = line.strip().split("=")
if len(line) > 1:
os.environ[line[0]] = line[1]
if get_stdout:
return "\n".join(stdout)
if get_rc:
return RC
return None
env.MESSAGE = 'Hello world!'
shell("ZWNobyAiJHtNRVNTQUdFfSIgJiYgZWNobyAnCicgJiYgZWNobyB+fn5+U1RBUlRfRU5WSVJPTk1FTlRfSEVSRX5+fn4gJiYgcHJpbnRlbnYgJiYgZWNobyB+fn5+U1RBUlRfQ1dEX0hFUkV+fn5+ICYmIHB3ZA==")
Reference¶
See the Reference section for an in-depth reference to the parts of the Calligraphy language