Commit c3bf25cc authored by Roman Alifanov's avatar Roman Alifanov

Refactor stdlib.py to use declarative method definitions

- Import method definitions from methods.py for consistency - Define bash implementations in STRING_IMPLS, ARRAY_IMPLS, DICT_IMPLS - Generate stdlib functions from these definitions - Use context manager (with self.indented()) for cleaner code - Reduce code duplication and improve maintainability
parent 0e7f2d60
"""Standard library code generation.
Generates bash implementations for ContenT stdlib functions.
Uses method definitions from methods.py for consistency.
"""
from .methods import STRING_METHODS, ARRAY_METHODS, DICT_METHODS, FILE_HANDLE_METHODS
# Bash implementations for string methods
STRING_IMPLS = {
"len": '__CT_RET=${#1}; echo "$__CT_RET"',
"substr": '__CT_RET="${1:$2:$3}"; echo "$__CT_RET"',
"index": 'local i="${1%%$2*}"; [[ "$i" == "$1" ]] && __CT_RET=-1 || __CT_RET=${#i}; echo "$__CT_RET"',
"contains": '[[ "$1" == *"$2"* ]] && __CT_RET=true || __CT_RET=false; echo "$__CT_RET"',
"starts": '[[ "$1" == "$2"* ]] && __CT_RET=true || __CT_RET=false; echo "$__CT_RET"',
"ends": '[[ "$1" == *"$2" ]] && __CT_RET=true || __CT_RET=false; echo "$__CT_RET"',
"replace": '__CT_RET="${1//"$2"/"$3"}"; echo "$__CT_RET"',
"split": 'local IFS="$2"; read -ra __CT_RET_ARR <<< "$1"',
"trim": 'local s="$1"; s="${s#"${s%%[![:space:]]*}"}"; __CT_RET="${s%"${s##*[![:space:]]}"}" ; echo "$__CT_RET"',
"upper": '__CT_RET="${1^^}"; echo "$__CT_RET"',
"lower": '__CT_RET="${1,,}"; echo "$__CT_RET"',
"charAt": '__CT_RET="${1:$2:1}"; printf \'%sX\' "$__CT_RET"',
"urlencode": "__CT_RET=$(printf '%s' \"$1\" | jq -sRr @uri); echo \"$__CT_RET\"",
}
# Bash implementations for array methods
ARRAY_IMPLS = {
"push": 'local -n __a=$1; shift; __a+=("$@")',
"pop": "local -n __a=$1; unset '__a[-1]'",
"shift": 'local -n __a=$1; __CT_RET="${__a[0]}"; __a=("${__a[@]:1}"); echo "$__CT_RET"',
"join": 'local -n __a=$1; local sep; printf -v sep \'%b\' "$2"; local IFS="$sep"; __CT_RET="${__a[*]}"; echo "$__CT_RET"',
"len": 'local -n __a=$1; __CT_RET=${#__a[@]}; echo "$__CT_RET"',
"get": 'local -n __a=$1; __CT_RET="${__a[$2]}"; echo "$__CT_RET"',
"set": 'local -n __a=$1; __a[$2]="$3"',
"slice": 'local -n __a=$1; __CT_RET_ARR=("${__a[@]:$2:$3}")',
}
# Bash implementations for dict methods
DICT_IMPLS = {
"set": 'local -n __d="$1"; __d["$2"]="$3"',
"get": 'local -n __d="$1"; __CT_RET="${__d[$2]}"; echo "$__CT_RET"',
"has": 'local -n __d="$1"; [[ -v "__d[$2]" ]] && __CT_RET=true || __CT_RET=false; echo "$__CT_RET"',
"del": 'local -n __d="$1"; unset "__d[$2]"',
"keys": 'local -n __d="$1"; __CT_RET_ARR=("${!__d[@]}")',
"len": 'local -n __d="$1"; __CT_RET=${#__d[@]}; echo "$__CT_RET"',
}
class StdlibMixin:
"""Mixin class for standard library code generation.
"""Mixin class for standard library code generation."""
Provides emit_stdlib() method that generates all ContenT
standard library functions in Bash.
"""
def emit_stdlib(self, used_categories: set = None):
"""Emit the ContenT standard library."""
self.emit("# === ContenT Standard Library ===")
self.emit()
def emit_stdlib (self, used_categories: set = None):
"""Emit the ContenT standard library.
Args:
used_categories: Set of category names to emit. If None, emit all.
"""
self.emit ("# === ContenT Standard Library ===")
self.emit ()
self._emit_core ()
self._emit_core()
if used_categories is None:
self._emit_http ()
self._emit_fs ()
self._emit_json ()
self._emit_object_system ()
self._emit_exception ()
self._emit_logger ()
self._emit_string ()
self._emit_array ()
self._emit_regex ()
self._emit_utils ()
self._emit_awk_wrapper ()
self._emit_math ()
self._emit_dict ()
self._emit_misc ()
self._emit_http()
self._emit_fs()
self._emit_json()
self._emit_object_system()
self._emit_exception()
self._emit_logger()
self._emit_string()
self._emit_array()
self._emit_regex()
self._emit_utils()
self._emit_awk_wrapper()
self._emit_math()
self._emit_dict()
self._emit_misc()
else:
if 'http' in used_categories:
self._emit_http ()
self._emit_http()
if 'fs' in used_categories:
self._emit_fs ()
self._emit_fs()
if 'json' in used_categories:
self._emit_json ()
self._emit_json()
if 'object' in used_categories:
self._emit_object_system ()
self._emit_object_system()
if 'exception' in used_categories:
self._emit_exception ()
self._emit_exception()
if 'logger' in used_categories:
self._emit_logger ()
self._emit_logger()
if 'string' in used_categories:
self._emit_string ()
self._emit_string()
if 'array' in used_categories:
self._emit_array ()
self._emit_array()
if 'regex' in used_categories:
self._emit_regex ()
self._emit_regex()
if 'args' in used_categories:
self._emit_args ()
self._emit_args()
else:
self._emit_utils_minimal ()
self._emit_utils_minimal()
if 'awk' in used_categories:
self._emit_awk_wrapper ()
self._emit_awk_wrapper()
if 'math' in used_categories:
self._emit_math ()
self._emit_math()
if 'dict' in used_categories:
self._emit_dict ()
self._emit_dict()
if 'misc' in used_categories or 'time' in used_categories:
self._emit_misc ()
self._emit_misc()
if 'test' in used_categories:
self._emit_test ()
self._emit_test()
self.emit ("# === End Standard Library ===")
self.emit ()
self.emit("# === End Standard Library ===")
self.emit()
def _emit_core (self):
def _emit_core(self):
"""Core functions: print, range, len."""
self.emit ("exec 3>&1 # Save stdout to FD3 for print()")
self.emit ()
self.emit ("__ct_print () {")
self.indent_level += 1
self.emit ('local msg="$1"')
self.emit ('echo -e "$msg" >&3')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_range () {")
self.indent_level += 1
self.emit ('local start=0 end step=1')
self.emit ('case $# in')
self.indent_level += 1
self.emit ('1) end=$1 ;;')
self.emit ('2) start=$1; end=$2 ;;')
self.emit ('3) start=$1; end=$2; step=$3 ;;')
self.indent_level -= 1
self.emit ('esac')
self.emit ('seq "$start" "$step" "$((end - 1))"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_len () {")
self.indent_level += 1
self.emit ('local -n arr=$1')
self.emit ('echo "${#arr[@]}"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_http (self):
self.emit("exec 3>&1 # Save stdout to FD3 for print()")
self.emit()
self.emit("__ct_print () {")
with self.indented():
self.emit('local msg="$1"')
self.emit('echo -e "$msg" >&3')
self.emit("}")
self.emit()
self.emit("__ct_range () {")
with self.indented():
self.emit('local start=0 end step=1')
self.emit('case $# in')
with self.indented():
self.emit('1) end=$1 ;;')
self.emit('2) start=$1; end=$2 ;;')
self.emit('3) start=$1; end=$2; step=$3 ;;')
self.emit('esac')
self.emit('seq "$start" "$step" "$((end - 1))"')
self.emit("}")
self.emit()
self.emit("__ct_len () {")
with self.indented():
self.emit('local -n arr=$1')
self.emit('echo "${#arr[@]}"')
self.emit("}")
self.emit()
def _emit_http(self):
"""HTTP functions: get, post, put, delete."""
self.emit ("__ct_http_get () {")
self.indent_level += 1
self.emit ('local url="$1"')
self.emit ('local timeout="${2:-30}"')
self.emit ('curl -sS --fail --show-error --max-time "$timeout" "$url"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_http_post () {")
self.indent_level += 1
self.emit ('local url="$1"')
self.emit ('local data="$2"')
self.emit ('local timeout="${3:-30}"')
self.emit ('curl -sS --fail --show-error --max-time "$timeout" -X POST -H "Content-Type: application/json" -d "$data" "$url"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_http_put () {")
self.indent_level += 1
self.emit ('local url="$1"')
self.emit ('local data="$2"')
self.emit ('local timeout="${3:-30}"')
self.emit ('curl -sS --fail --show-error --max-time "$timeout" -X PUT -H "Content-Type: application/json" -d "$data" "$url"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_http_delete () {")
self.indent_level += 1
self.emit ('local url="$1"')
self.emit ('local timeout="${2:-30}"')
self.emit ('curl -sS --fail --show-error --max-time "$timeout" -X DELETE "$url"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_fs (self):
"""Filesystem functions: read, write, append, exists, remove, mkdir, list."""
self.emit ("__ct_fs_read () {")
self.indent_level += 1
self.emit ('cat "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_write () {")
self.indent_level += 1
self.emit ('echo -n "$2" > "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_append () {")
self.indent_level += 1
self.emit ('echo -n "$2" >> "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_exists () {")
self.indent_level += 1
self.emit ('[[ -e "$1" ]] && echo "true" || echo "false"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_remove () {")
self.indent_level += 1
self.emit ('rm -f "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_mkdir () {")
self.indent_level += 1
self.emit ('mkdir -p "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fs_list () {")
self.indent_level += 1
self.emit ('ls -1 "$1" 2>/dev/null || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
http_funcs = [
("get", ['url', 'timeout="${2:-30}"'],
'curl -sS --fail --show-error --max-time "$timeout" "$url"'),
("post", ['url', 'data', 'timeout="${3:-30}"'],
'curl -sS --fail --show-error --max-time "$timeout" -X POST -H "Content-Type: application/json" -d "$data" "$url"'),
("put", ['url', 'data', 'timeout="${3:-30}"'],
'curl -sS --fail --show-error --max-time "$timeout" -X PUT -H "Content-Type: application/json" -d "$data" "$url"'),
("delete", ['url', 'timeout="${2:-30}"'],
'curl -sS --fail --show-error --max-time "$timeout" -X DELETE "$url"'),
]
for name, params, body in http_funcs:
self.emit(f"__ct_http_{name} () {{")
with self.indented():
for i, p in enumerate(params):
if '=' in p:
self.emit(f'local {p}')
else:
self.emit(f'local {p}="${{{i+1}}}"')
self.emit(body)
self.emit("}")
self.emit()
def _emit_fs(self):
"""Filesystem functions."""
fs_simple = [
("read", 'cat "$1"'),
("write", 'echo -n "$2" > "$1"'),
("append", 'echo -n "$2" >> "$1"'),
("exists", '[[ -e "$1" ]] && echo "true" || echo "false"'),
("remove", 'rm -f "$1"'),
("mkdir", 'mkdir -p "$1"'),
("list", 'ls -1 "$1" 2>/dev/null || true'),
]
for name, body in fs_simple:
self.emit(f"__ct_fs_{name} () {{")
with self.indented():
self.emit(body)
self.emit("}")
self.emit()
self._emit_file_handles()
def _emit_file_handles(self):
"""File handle functions for fs.open() / f.close()."""
self.emit ("# File handle system")
self.emit ("declare -gA __ct_file_handles=()")
self.emit ("declare -g __ct_file_fd=10")
self.emit ()
self.emit ("__ct_fs_open () {")
self.indent_level += 1
self.emit ('local path="$1"')
self.emit ('local mode="${2:-r}"')
self.emit ('local fd=$__ct_file_fd')
self.emit ('__ct_file_fd=$((fd + 1))')
self.emit ('local h="__fh_${fd}"')
self.emit ('local __key_path="${h}_path"')
self.emit ('local __key_fd="${h}_fd"')
self.emit ('local __key_mode="${h}_mode"')
self.emit ('__ct_file_handles[$__key_path]="$path"')
self.emit ('__ct_file_handles[$__key_fd]="$fd"')
self.emit ('__ct_file_handles[$__key_mode]="$mode"')
self.emit ('__CT_RET="$h"')
self.emit ('echo "$h"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fh_read () {")
self.indent_level += 1
self.emit ('local h="$1"')
self.emit ('local __key="${h}_path"')
self.emit ('local path="${__ct_file_handles[$__key]}"')
self.emit ('__CT_RET=$(cat "$path")')
self.emit ('echo "$__CT_RET"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fh_readline () {")
self.indent_level += 1
self.emit ('local h="$1"')
self.emit ('local __key_path="${h}_path"')
self.emit ('local __key_pos="${h}_pos"')
self.emit ('local path="${__ct_file_handles[$__key_path]}"')
self.emit ('local pos="${__ct_file_handles[$__key_pos]:-1}"')
self.emit ('__CT_RET=$(sed -n "${pos}p" "$path")')
self.emit ('__ct_file_handles[$__key_pos]=$((pos + 1))')
self.emit ('echo "$__CT_RET"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fh_write () {")
self.indent_level += 1
self.emit ('local h="$1"')
self.emit ('local data="$2"')
self.emit ('local __key_path="${h}_path"')
self.emit ('local __key_mode="${h}_mode"')
self.emit ('local path="${__ct_file_handles[$__key_path]}"')
self.emit ('local mode="${__ct_file_handles[$__key_mode]}"')
self.emit ('if [[ "$mode" == "a" ]]; then')
self.indent_level += 1
self.emit ('echo -n "$data" >> "$path"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('echo -n "$data" > "$path"')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fh_writeln () {")
self.indent_level += 1
self.emit ('local h="$1"')
self.emit ('local data="$2"')
self.emit ('local __key_path="${h}_path"')
self.emit ('local __key_mode="${h}_mode"')
self.emit ('local path="${__ct_file_handles[$__key_path]}"')
self.emit ('local mode="${__ct_file_handles[$__key_mode]}"')
self.emit ('if [[ "$mode" == "a" ]]; then')
self.indent_level += 1
self.emit ('echo "$data" >> "$path"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('echo "$data" > "$path"')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_fh_close () {")
self.indent_level += 1
self.emit ('local h="$1"')
self.emit ('local __key_path="${h}_path"')
self.emit ('local __key_fd="${h}_fd"')
self.emit ('local __key_mode="${h}_mode"')
self.emit ('local __key_pos="${h}_pos"')
self.emit ('unset "__ct_file_handles[$__key_path]"')
self.emit ('unset "__ct_file_handles[$__key_fd]"')
self.emit ('unset "__ct_file_handles[$__key_mode]"')
self.emit ('unset "__ct_file_handles[$__key_pos]"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("# Context manager support for file handles")
self.emit ('__ct_fh___enter__ () { echo "$1"; }')
self.emit ('__ct_fh___exit__ () { __ct_fh_close "$1"; }')
self.emit ()
def _emit_json (self):
"""JSON functions: parse, stringify."""
self.emit ("__ct_json_parse () {")
self.indent_level += 1
self.emit ('local __json="$1"')
self.emit ('local __dict_name="$2"')
self.emit ('eval "declare -gA $__dict_name=()"')
self.emit ('while IFS= read -r __line; do')
self.indent_level += 1
self.emit ('local __key="${__line%%=*}"')
self.emit ('local __val="${__line#*=}"')
self.emit ('[[ -n "$__key" ]] && eval "${__dict_name}[\\"$__key\\"]=\"\\$__val\""')
self.indent_level -= 1
self.emit ('done < <(echo "$__json" | jq -r \'to_entries[] | "\\(.key)=\\(.value)\"\' 2>/dev/null)')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_json_get () {")
self.indent_level += 1
self.emit ('local __json="$1"')
self.emit ('local __path="$2"')
self.emit ('echo "$__json" | jq -r "$__path" 2>/dev/null')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_json_stringify () {")
self.indent_level += 1
self.emit ('local -n __d="$1"')
self.emit ('local __first=1')
self.emit ('printf "{"')
self.emit ('for __k in "${!__d[@]}"; do')
self.indent_level += 1
self.emit ('[[ $__first -eq 1 ]] && __first=0 || printf ","')
self.emit ('printf "\\"%s\\":" "$__k"')
self.emit ('local __v="${__d[$__k]}"')
self.emit ('if [[ "$__v" =~ ^-?[0-9]+$ ]]; then')
self.indent_level += 1
self.emit ('printf "%s" "$__v"')
self.indent_level -= 1
self.emit ('elif [[ "$__v" == "true" || "$__v" == "false" || "$__v" == "null" ]]; then')
self.indent_level += 1
self.emit ('printf "%s" "$__v"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('printf "\\"%s\\"" "$__v"')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ('done')
self.emit ('printf "}\\n"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_object_system (self):
"""Object system: class tracking, method dispatch, field access."""
self.emit ("declare -gA __ct_obj_class=()")
self.emit ("declare -gA __CT_OBJ=()")
self.emit ("declare -g __ct_last_instance=''")
self.emit ()
self.emit ("__ct_call_method () {")
self.indent_level += 1
self.emit ('local __obj="$1"')
self.emit ('local __method="$2"')
self.emit ('shift 2')
self.emit ('local __class="${__ct_obj_class[$__obj]:-}"')
self.emit ('if [[ -z "$__class" ]]; then')
self.indent_level += 1
self.emit ('echo "Error: Unknown object $__obj" >&2')
self.emit ('return 1')
self.indent_level -= 1
self.emit ('fi')
self.emit ('"__ct_class_${__class}_${__method}" "$__obj" "$@"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_get_field () {")
self.indent_level += 1
self.emit ('echo "${__CT_OBJ[$1.$2]}"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("declare -g __CT_RET=''")
self.emit ()
def _emit_exception (self):
self.emit("# File handle system")
self.emit("declare -gA __ct_file_handles=()")
self.emit("declare -g __ct_file_fd=10")
self.emit()
self.emit("__ct_fs_open () {")
with self.indented():
self.emit('local path="$1"')
self.emit('local mode="${2:-r}"')
self.emit('local fd=$__ct_file_fd')
self.emit('__ct_file_fd=$((fd + 1))')
self.emit('local h="__fh_${fd}"')
for key in ['path', 'fd', 'mode']:
self.emit(f'__ct_file_handles["${{h}}_{key}"]="${{{key}}}"')
self.emit('__CT_RET="$h"')
self.emit('echo "$h"')
self.emit("}")
self.emit()
# File handle methods - using FILE_HANDLE_METHODS for names
fh_impls = {
"read": [
'local h="$1"',
'local path="${__ct_file_handles[${h}_path]}"',
'__CT_RET=$(cat "$path")',
'echo "$__CT_RET"',
],
"readline": [
'local h="$1"',
'local path="${__ct_file_handles[${h}_path]}"',
'local pos="${__ct_file_handles[${h}_pos]:-1}"',
'__CT_RET=$(sed -n "${pos}p" "$path")',
'__ct_file_handles[${h}_pos]=$((pos + 1))',
'echo "$__CT_RET"',
],
"write": [
'local h="$1" data="$2"',
'local path="${__ct_file_handles[${h}_path]}"',
'local mode="${__ct_file_handles[${h}_mode]}"',
'[[ "$mode" == "a" ]] && echo -n "$data" >> "$path" || echo -n "$data" > "$path"',
],
"writeln": [
'local h="$1" data="$2"',
'local path="${__ct_file_handles[${h}_path]}"',
'local mode="${__ct_file_handles[${h}_mode]}"',
'[[ "$mode" == "a" ]] && echo "$data" >> "$path" || echo "$data" > "$path"',
],
"close": [
'local h="$1"',
'for k in path fd mode pos; do unset "__ct_file_handles[${h}_$k]"; done',
],
}
for method_name, method_def in FILE_HANDLE_METHODS.items():
if method_name in fh_impls:
self.emit(f"{method_def.bash_func} () {{")
with self.indented():
for line in fh_impls[method_name]:
self.emit(line)
self.emit("}")
self.emit()
self.emit("# Context manager support")
self.emit('__ct_fh___enter__ () { echo "$1"; }')
self.emit('__ct_fh___exit__ () { __ct_fh_close "$1"; }')
self.emit()
def _emit_json(self):
"""JSON functions."""
self.emit("__ct_json_parse () {")
with self.indented():
self.emit('local __json="$1" __dict_name="$2"')
self.emit('eval "declare -gA $__dict_name=()"')
self.emit('while IFS= read -r __line; do')
with self.indented():
self.emit('local __key="${__line%%=*}" __val="${__line#*=}"')
self.emit('[[ -n "$__key" ]] && eval "${__dict_name}[\\"$__key\\"]=\"\\$__val\""')
self.emit("done < <(echo \"$__json\" | jq -r 'to_entries[] | \"\\(.key)=\\(.value)\"' 2>/dev/null)")
self.emit("}")
self.emit()
self.emit("__ct_json_get () {")
with self.indented():
self.emit('echo "$1" | jq -r "$2" 2>/dev/null')
self.emit("}")
self.emit()
self.emit("__ct_json_stringify () {")
with self.indented():
self.emit('local -n __d="$1"')
self.emit('local __first=1')
self.emit('printf "{"')
self.emit('for __k in "${!__d[@]}"; do')
with self.indented():
self.emit('[[ $__first -eq 1 ]] && __first=0 || printf ","')
self.emit('printf "\\"%s\\":" "$__k"')
self.emit('local __v="${__d[$__k]}"')
self.emit('if [[ "$__v" =~ ^-?[0-9]+$ ]] || [[ "$__v" =~ ^(true|false|null)$ ]]; then')
with self.indented():
self.emit('printf "%s" "$__v"')
self.emit('else')
with self.indented():
self.emit('printf "\\"%s\\"" "$__v"')
self.emit('fi')
self.emit('done')
self.emit('printf "}\\n"')
self.emit("}")
self.emit()
def _emit_object_system(self):
"""Object system: class tracking, method dispatch."""
self.emit("declare -gA __ct_obj_class=()")
self.emit("declare -gA __CT_OBJ=()")
self.emit("declare -g __ct_last_instance=''")
self.emit()
self.emit("__ct_call_method () {")
with self.indented():
self.emit('local __obj="$1" __method="$2"')
self.emit('shift 2')
self.emit('local __class="${__ct_obj_class[$__obj]:-}"')
self.emit('[[ -z "$__class" ]] && { echo "Error: Unknown object $__obj" >&2; return 1; }')
self.emit('"__ct_class_${__class}_${__method}" "$__obj" "$@"')
self.emit("}")
self.emit()
self.emit('__ct_get_field () { echo "${__CT_OBJ[$1.$2]}"; }')
self.emit()
self.emit("declare -g __CT_RET=''")
self.emit()
def _emit_exception(self):
"""Exception handling."""
self.emit ("declare -g __ct_exception=''")
self.emit ("declare -g __ct_exception_type=''")
self.emit ()
self.emit ("__ct_throw () {")
self.indent_level += 1
self.emit ('__ct_exception_type="$1"')
self.emit ('__ct_exception="$2"')
self.emit ('return 1')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_logger (self):
"""Logger functions: info, warn, error, debug."""
self.emit ("__ct_logger_info () {")
self.indent_level += 1
self.emit ('echo "[INFO] $1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_logger_warn () {")
self.indent_level += 1
self.emit ('echo "[WARN] $1" >&2')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_logger_error () {")
self.indent_level += 1
self.emit ('echo "[ERROR] $1" >&2')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_logger_debug () {")
self.indent_level += 1
self.emit ('echo "[DEBUG] $1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_string (self):
"""String functions."""
self.emit ("# String functions")
self.emit ("__ct_str_len () { __CT_RET=${#1}; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_substr () { __CT_RET=\"${1:$2:$3}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_index () { local i=\"${1%%$2*}\"; [[ \"$i\" == \"$1\" ]] && __CT_RET=-1 || __CT_RET=${#i}; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_contains () { [[ \"$1\" == *\"$2\"* ]] && __CT_RET=true || __CT_RET=false; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_starts () { [[ \"$1\" == \"$2\"* ]] && __CT_RET=true || __CT_RET=false; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_ends () { [[ \"$1\" == *\"$2\" ]] && __CT_RET=true || __CT_RET=false; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_replace () { __CT_RET=\"${1//\"$2\"/\"$3\"}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_split () { local IFS=\"$2\"; read -ra __CT_RET_ARR <<< \"$1\"; }")
self.emit ("__ct_str_trim () { local s=\"$1\"; s=\"${s#\"${s%%[![:space:]]*}\"}\"; __CT_RET=\"${s%\"${s##*[![:space:]]}\"}\" ; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_upper () { __CT_RET=\"${1^^}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_lower () { __CT_RET=\"${1,,}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_char_at () { __CT_RET=\"${1:$2:1}\"; printf '%sX' \"$__CT_RET\"; }")
self.emit ("__ct_str_urlencode () { __CT_RET=$(printf '%s' \"$1\" | jq -sRr @uri); echo \"$__CT_RET\"; }")
self.emit ("__ct_str_concat () { __CT_RET=\"$1$2\"; echo \"$__CT_RET\"; }")
self.emit ()
self.emit ("# Character code functions")
self.emit ("__ct_str_ord () { __CT_RET=$(printf '%d' \"'$1\"); echo \"$__CT_RET\"; }")
self.emit ("__ct_str_chr () { printf -v __CT_RET '%b' \"\\\\x$(printf '%02x' \"$1\")\"; echo \"$__CT_RET\"; }")
self.emit ()
def _emit_array (self):
"""Array functions."""
self.emit ("# Array functions (optimized - set __CT_RET and echo)")
self.emit ("__ct_arr_push () { local -n __a=$1; shift; __a+=(\"$@\"); }")
self.emit ("__ct_arr_pop () { local -n __a=$1; unset '__a[-1]'; }")
self.emit ("__ct_arr_shift () { local -n __a=$1; __CT_RET=\"${__a[0]}\"; __a=(\"${__a[@]:1}\"); echo \"$__CT_RET\"; }")
self.emit ("__ct_arr_join () { local -n __a=$1; local sep; printf -v sep '%b' \"$2\"; local IFS=\"$sep\"; __CT_RET=\"${__a[*]}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_arr_len () { local -n __a=$1; __CT_RET=${#__a[@]}; echo \"$__CT_RET\"; }")
self.emit ("__ct_arr_get () { local -n __a=$1; __CT_RET=\"${__a[$2]}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_arr_set () { local -n __a=$1; __a[$2]=\"$3\"; }")
self.emit ("__ct_arr_slice () { local -n __a=$1; __CT_RET_ARR=(\"${__a[@]:$2:$3}\"); }")
self.emit ()
self.emit ("# Array map/filter with lambda functions")
self.emit ("__ct_arr_map () {")
self.indent_level += 1
self.emit ('local -n __a=$1')
self.emit ('local __fn=$2')
self.emit ('local -a __result=()')
self.emit ('for __item in "${__a[@]}"; do')
self.indent_level += 1
self.emit ('__result+=("$($__fn "$__item")")')
self.indent_level -= 1
self.emit ('done')
self.emit ('__CT_RET=("${__result[@]}")')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_arr_filter () {")
self.indent_level += 1
self.emit ('local -n __a=$1')
self.emit ('local __fn=$2')
self.emit ('local -a __result=()')
self.emit ('for __item in "${__a[@]}"; do')
self.indent_level += 1
self.emit ('if [[ "$($__fn "$__item")" == "true" ]]; then')
self.indent_level += 1
self.emit ('__result+=("$__item")')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ('done')
self.emit ('__CT_RET=("${__result[@]}")')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("# Fast array functions (no echo - use __CT_RET directly)")
self.emit ("__ct_arr_len_fast () { local -n __a=$1; __CT_RET=${#__a[@]}; }")
self.emit ("__ct_arr_get_fast () { local -n __a=$1; __CT_RET=\"${__a[$2]}\"; }")
self.emit ()
def _emit_regex (self):
self.emit("declare -g __ct_exception=''")
self.emit("declare -g __ct_exception_type=''")
self.emit()
self.emit("__ct_throw () {")
with self.indented():
self.emit('__ct_exception_type="$1"')
self.emit('__ct_exception="$2"')
self.emit('return 1')
self.emit("}")
self.emit()
def _emit_logger(self):
"""Logger functions."""
levels = [("info", "INFO", ""), ("warn", "WARN", " >&2"),
("error", "ERROR", " >&2"), ("debug", "DEBUG", "")]
for name, label, redir in levels:
self.emit(f'__ct_logger_{name} () {{ echo "[{label}] $1"{redir}; }}')
self.emit()
def _emit_string(self):
"""String functions from STRING_METHODS."""
self.emit("# String functions")
for method_name, method_def in STRING_METHODS.items():
if method_name in STRING_IMPLS:
self.emit(f"{method_def.bash_func} () {{ {STRING_IMPLS[method_name]}; }}")
self.emit("__ct_str_concat () { __CT_RET=\"$1$2\"; echo \"$__CT_RET\"; }")
self.emit()
self.emit("# Character code functions")
self.emit("__ct_str_ord () { __CT_RET=$(printf '%d' \"'$1\"); echo \"$__CT_RET\"; }")
self.emit("__ct_str_chr () { printf -v __CT_RET '%b' \"\\\\x$(printf '%02x' \"$1\")\"; echo \"$__CT_RET\"; }")
self.emit()
def _emit_array(self):
"""Array functions from ARRAY_METHODS."""
self.emit("# Array functions")
for method_name, method_def in ARRAY_METHODS.items():
if method_name in ARRAY_IMPLS:
self.emit(f"{method_def.bash_func} () {{ {ARRAY_IMPLS[method_name]}; }}")
self.emit()
self.emit("# Array map/filter with lambda functions")
self.emit("__ct_arr_map () {")
with self.indented():
self.emit('local -n __a=$1')
self.emit('local __fn=$2')
self.emit('local -a __result=()')
self.emit('for __item in "${__a[@]}"; do __result+=("$($__fn "$__item")"); done')
self.emit('__CT_RET=("${__result[@]}")')
self.emit("}")
self.emit()
self.emit("__ct_arr_filter () {")
with self.indented():
self.emit('local -n __a=$1')
self.emit('local __fn=$2')
self.emit('local -a __result=()')
self.emit('for __item in "${__a[@]}"; do')
with self.indented():
self.emit('[[ "$($__fn "$__item")" == "true" ]] && __result+=("$__item")')
self.emit('done')
self.emit('__CT_RET=("${__result[@]}")')
self.emit("}")
self.emit()
self.emit("# Fast array functions (no echo)")
self.emit("__ct_arr_len_fast () { local -n __a=$1; __CT_RET=${#__a[@]}; }")
self.emit("__ct_arr_get_fast () { local -n __a=$1; __CT_RET=\"${__a[$2]}\"; }")
self.emit()
def _emit_regex(self):
"""Regex functions."""
self.emit ("# ngrep - pattern search")
self.emit ("__ct_ngrep () {")
self.indent_level += 1
self.emit ('local pattern="$1"')
self.emit ('local text="$2"')
self.emit ('echo "$text" | grep -n "$pattern" || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_regex_match () {")
self.indent_level += 1
self.emit ('local str="$1"')
self.emit ('local pattern="$2"')
self.emit ('[[ "$str" =~ $pattern ]] && echo true || echo false')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_regex_extract () {")
self.indent_level += 1
self.emit ('local str="$1"')
self.emit ('local pattern="$2"')
self.emit ('if [[ "$str" =~ $pattern ]]; then')
self.indent_level += 1
self.emit ('echo "${BASH_REMATCH[0]}"')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
def _emit_utils (self):
self.emit("# Regex functions")
self.emit("__ct_ngrep () { echo \"$2\" | grep -n \"$1\" || true; }")
self.emit('__ct_regex_match () { [[ "$1" =~ $2 ]] && echo true || echo false; }')
self.emit("__ct_regex_extract () {")
with self.indented():
self.emit('[[ "$1" =~ $2 ]] && echo "${BASH_REMATCH[0]}"')
self.emit("}")
self.emit()
def _emit_utils(self):
"""Utility functions."""
self.emit ("__ct_exit () { exit \"${1:-0}\"; }")
self.emit ()
self.emit ("__ct_is_number () { [[ \"$1\" =~ ^-?[0-9]+$ ]] && echo true || echo false; }")
self.emit ("__ct_is_empty () { [[ -z \"$1\" ]] && echo true || echo false; }")
self.emit ()
self.emit ('__ct_args=("$@")')
self.emit ('__ct_args_count () { echo ${#__ct_args[@]}; }')
self.emit ("__ct_args_get () { printf '%s\\n' \"${__ct_args[$1]}\"; }")
self.emit ()
def _emit_utils_minimal (self):
self.emit('__ct_exit () { exit "${1:-0}"; }')
self.emit()
self.emit('__ct_is_number () { [[ "$1" =~ ^-?[0-9]+$ ]] && echo true || echo false; }')
self.emit('__ct_is_empty () { [[ -z "$1" ]] && echo true || echo false; }')
self.emit()
self.emit('__ct_args=("$@")')
self.emit('__ct_args_count () { echo ${#__ct_args[@]}; }')
self.emit("__ct_args_get () { printf '%s\\n' \"${__ct_args[$1]}\"; }")
self.emit()
def _emit_utils_minimal(self):
"""Minimal utility functions (without args)."""
self.emit ("__ct_exit () { exit \"${1:-0}\"; }")
self.emit ()
self.emit ("__ct_is_number () { [[ \"$1\" =~ ^-?[0-9]+$ ]] && echo true || echo false; }")
self.emit ("__ct_is_empty () { [[ -z \"$1\" ]] && echo true || echo false; }")
self.emit ()
self.emit('__ct_exit () { exit "${1:-0}"; }')
self.emit()
self.emit('__ct_is_number () { [[ "$1" =~ ^-?[0-9]+$ ]] && echo true || echo false; }')
self.emit('__ct_is_empty () { [[ -z "$1" ]] && echo true || echo false; }')
self.emit()
def _emit_args (self):
def _emit_args(self):
"""Args functions only."""
self.emit ("__ct_exit () { exit \"${1:-0}\"; }")
self.emit ()
self.emit ("__ct_is_number () { [[ \"$1\" =~ ^-?[0-9]+$ ]] && echo true || echo false; }")
self.emit ("__ct_is_empty () { [[ -z \"$1\" ]] && echo true || echo false; }")
self.emit ()
self.emit ('__ct_args=("$@")')
self.emit ('__ct_args_count () { echo ${#__ct_args[@]}; }')
self.emit ("__ct_args_get () { printf '%s\\n' \"${__ct_args[$1]}\"; }")
self.emit ()
self._emit_utils()
def _emit_awk_wrapper (self):
def _emit_awk_wrapper(self):
"""AWK wrapper functions."""
self.emit ("# AWK wrapper - uses mawk if available (2-10x faster)")
self.emit ("__ct_awk_cmd=''")
self.emit ("if command -v mawk &>/dev/null; then __ct_awk_cmd=mawk")
self.emit ("elif command -v gawk &>/dev/null; then __ct_awk_cmd=gawk; echo '[WARN] mawk not found, using gawk (slower)' >&2")
self.emit ("elif command -v awk &>/dev/null; then __ct_awk_cmd=awk; echo '[WARN] mawk not found, using awk (slower)' >&2")
self.emit ("else echo '[ERROR] No awk implementation found' >&2; exit 1; fi")
self.emit ('__ct_awk () { "$__ct_awk_cmd" "$@"; }')
self.emit ('__ct_awk_file () { "$__ct_awk_cmd" -f "$1" "$2"; }')
self.emit ()
def _emit_math (self):
self.emit("# AWK wrapper - uses mawk if available (2-10x faster)")
self.emit("__ct_awk_cmd=''")
self.emit("if command -v mawk &>/dev/null; then __ct_awk_cmd=mawk")
self.emit("elif command -v gawk &>/dev/null; then __ct_awk_cmd=gawk; echo '[WARN] mawk not found, using gawk (slower)' >&2")
self.emit("elif command -v awk &>/dev/null; then __ct_awk_cmd=awk; echo '[WARN] mawk not found, using awk (slower)' >&2")
self.emit("else echo '[ERROR] No awk implementation found' >&2; exit 1; fi")
self.emit('__ct_awk () { "$__ct_awk_cmd" "$@"; }')
self.emit('__ct_awk_file () { "$__ct_awk_cmd" -f "$1" "$2"; }')
self.emit()
def _emit_math(self):
"""Math functions."""
self.emit ("# Math functions (fast - no external commands)")
self.emit ("__ct_math_add () { echo $(($1 + $2)); }")
self.emit ("__ct_math_sub () { echo $(($1 - $2)); }")
self.emit ("__ct_math_mul () { echo $(($1 * $2)); }")
self.emit ("__ct_math_div () { echo $(($1 / $2)); }")
self.emit ("__ct_math_mod () { echo $(($1 % $2)); }")
self.emit ("__ct_math_min () { (($1 < $2)) && echo $1 || echo $2; }")
self.emit ("__ct_math_max () { (($1 > $2)) && echo $1 || echo $2; }")
self.emit ("__ct_math_abs () { local n=$1; echo ${n#-}; }")
self.emit ()
def _emit_dict (self):
"""Dict/hash functions."""
self.emit ("# Dict/hash functions (using associative arrays)")
self.emit ('__ct_dict_set () { local -n __d="$1"; __d["$2"]="$3"; }')
self.emit ('__ct_dict_get () { local -n __d="$1"; __CT_RET="${__d[$2]}"; echo "$__CT_RET"; }')
self.emit ('__ct_dict_has () { local -n __d="$1"; [[ -v "__d[$2]" ]] && __CT_RET=true || __CT_RET=false; echo "$__CT_RET"; }')
self.emit ('__ct_dict_del () { local -n __d="$1"; unset "__d[$2]"; }')
self.emit ('__ct_dict_keys () { local -n __d="$1"; __CT_RET_ARR=("${!__d[@]}"); }')
self.emit ('__ct_dict_len () { local -n __d="$1"; __CT_RET=${#__d[@]}; echo "$__CT_RET"; }')
self.emit ()
def _emit_misc (self):
self.emit("# Math functions")
math_ops = [
("add", "+"), ("sub", "-"), ("mul", "*"), ("div", "/"), ("mod", "%")
]
for name, op in math_ops:
self.emit(f"__ct_math_{name} () {{ echo $(($1 {op} $2)); }}")
self.emit("__ct_math_min () { (($1 < $2)) && echo $1 || echo $2; }")
self.emit("__ct_math_max () { (($1 > $2)) && echo $1 || echo $2; }")
self.emit("__ct_math_abs () { local n=$1; echo ${n#-}; }")
self.emit()
def _emit_dict(self):
"""Dict functions from DICT_METHODS."""
self.emit("# Dict functions")
for method_name, method_def in DICT_METHODS.items():
if method_name in DICT_IMPLS:
self.emit(f"{method_def.bash_func} () {{ {DICT_IMPLS[method_name]}; }}")
# len is not in DICT_METHODS, add separately
self.emit(f"__ct_dict_len () {{ {DICT_IMPLS['len']}; }}")
self.emit()
def _emit_misc(self):
"""Miscellaneous functions."""
self.emit ("# Byte/binary functions")
self.emit ("__ct_byte_to_hex () { printf '%02x' \"$1\"; }")
self.emit ("__ct_hex_to_byte () { printf '%d' \"0x$1\"; }")
self.emit ()
self.emit ("# Time functions")
self.emit ("__ct_time_now () { date +%s; }")
self.emit ("__ct_time_ms () { date +%s%3N; }")
self.emit ()
self.emit ("# Random")
self.emit ("__ct_random () { echo $RANDOM; }")
self.emit ("__ct_random_range () { echo $(($1 + RANDOM % ($2 - $1 + 1))); }")
self.emit ()
self.emit ("__CT_NL=$'\\n'")
self.emit ()
def _emit_test (self):
"""Test framework functions: assert, test runner."""
self.emit ("# Test framework")
self.emit ("declare -g __ct_test_passed=0")
self.emit ("declare -g __ct_test_failed=0")
self.emit ("declare -g __ct_test_current=''")
self.emit ("declare -g __ct_test_start_time=0")
self.emit ()
self.emit ("__ct_test_colors () {")
self.indent_level += 1
self.emit ('if [[ -t 1 ]]; then')
self.indent_level += 1
self.emit ('__CT_GREEN="\\033[32m"')
self.emit ('__CT_RED="\\033[31m"')
self.emit ('__CT_YELLOW="\\033[33m"')
self.emit ('__CT_CYAN="\\033[36m"')
self.emit ('__CT_RESET="\\033[0m"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('__CT_GREEN="" __CT_RED="" __CT_YELLOW="" __CT_CYAN="" __CT_RESET=""')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ("__ct_test_colors")
self.emit ()
self.emit ("__ct_test_start () {")
self.indent_level += 1
self.emit ('__ct_test_current="$1"')
self.emit ('__ct_test_start_time=$(date +%s%3N)')
self.emit ('printf "${__CT_CYAN}RUN${__CT_RESET} %s\\n" "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_pass () {")
self.indent_level += 1
self.emit ('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit ('printf "${__CT_GREEN}PASS${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit ('((__ct_test_passed++)) || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_fail () {")
self.indent_level += 1
self.emit ('local msg="$1"')
self.emit ('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit ('printf "${__CT_RED}FAIL${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit ('if [[ -n "$msg" ]]; then')
self.indent_level += 1
self.emit ('printf " ${__CT_RED}%s${__CT_RESET}\\n" "$msg"')
self.indent_level -= 1
self.emit ('fi')
self.emit ('((__ct_test_failed++)) || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_assert () {")
self.indent_level += 1
self.emit ('local condition="$1"')
self.emit ('local msg="${2:-Assertion failed}"')
self.emit ('if eval "$condition"; then')
self.indent_level += 1
self.emit ('return 0')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('__ct_test_fail "$msg"')
self.emit ('return 1')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_assert_eq () {")
self.indent_level += 1
self.emit ('local actual="$1"')
self.emit ('local expected="$2"')
self.emit ("if [[ \"$actual\" != \"$expected\" ]]; then")
self.indent_level += 1
self.emit ("__ct_test_fail \"Expected '$expected' but got '$actual'\"")
self.emit ('return 1')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_summary () {")
self.indent_level += 1
self.emit ('echo')
self.emit ('local total=$((__ct_test_passed + __ct_test_failed))')
self.emit ('if [[ $__ct_test_failed -eq 0 ]]; then')
self.indent_level += 1
self.emit ('printf "${__CT_GREEN}All %d tests passed${__CT_RESET}\\n" "$total"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('printf "${__CT_RED}%d of %d tests failed${__CT_RESET}\\n" "$__ct_test_failed" "$total"')
self.indent_level -= 1
self.emit ('fi')
self.emit ('return $__ct_test_failed')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit("# Byte/binary functions")
self.emit("__ct_byte_to_hex () { printf '%02x' \"$1\"; }")
self.emit("__ct_hex_to_byte () { printf '%d' \"0x$1\"; }")
self.emit()
self.emit("# Time functions")
self.emit("__ct_time_now () { date +%s; }")
self.emit("__ct_time_ms () { date +%s%3N; }")
self.emit()
self.emit("# Random")
self.emit("__ct_random () { echo $RANDOM; }")
self.emit("__ct_random_range () { echo $(($1 + RANDOM % ($2 - $1 + 1))); }")
self.emit()
self.emit("__CT_NL=$'\\n'")
self.emit()
def _emit_test(self):
"""Test framework functions."""
self.emit("# Test framework")
self.emit("declare -g __ct_test_passed=0")
self.emit("declare -g __ct_test_failed=0")
self.emit("declare -g __ct_test_current=''")
self.emit("declare -g __ct_test_start_time=0")
self.emit()
self.emit("__ct_test_colors () {")
with self.indented():
self.emit('if [[ -t 1 ]]; then')
with self.indented():
for c in ['GREEN=32', 'RED=31', 'YELLOW=33', 'CYAN=36']:
name, code = c.split('=')
self.emit(f'__CT_{name}="\\033[{code}m"')
self.emit('__CT_RESET="\\033[0m"')
self.emit('else')
with self.indented():
self.emit('__CT_GREEN="" __CT_RED="" __CT_YELLOW="" __CT_CYAN="" __CT_RESET=""')
self.emit('fi')
self.emit("}")
self.emit("__ct_test_colors")
self.emit()
self.emit("__ct_test_start () {")
with self.indented():
self.emit('__ct_test_current="$1"')
self.emit('__ct_test_start_time=$(date +%s%3N)')
self.emit('printf "${__CT_CYAN}RUN${__CT_RESET} %s\\n" "$1"')
self.emit("}")
self.emit()
self.emit("__ct_test_pass () {")
with self.indented():
self.emit('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit('printf "${__CT_GREEN}PASS${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit('((__ct_test_passed++)) || true')
self.emit("}")
self.emit()
self.emit("__ct_test_fail () {")
with self.indented():
self.emit('local msg="$1"')
self.emit('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit('printf "${__CT_RED}FAIL${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit('[[ -n "$msg" ]] && printf " ${__CT_RED}%s${__CT_RESET}\\n" "$msg"')
self.emit('((__ct_test_failed++)) || true')
self.emit("}")
self.emit()
self.emit("__ct_assert () {")
with self.indented():
self.emit('local condition="$1" msg="${2:-Assertion failed}"')
self.emit('if eval "$condition"; then return 0')
self.emit('else __ct_test_fail "$msg"; return 1; fi')
self.emit("}")
self.emit()
self.emit("__ct_assert_eq () {")
with self.indented():
self.emit('local actual="$1" expected="$2"')
self.emit('[[ "$actual" != "$expected" ]] && { __ct_test_fail "Expected \'$expected\' but got \'$actual\'"; return 1; }')
self.emit("}")
self.emit()
self.emit("__ct_test_summary () {")
with self.indented():
self.emit('echo')
self.emit('local total=$((__ct_test_passed + __ct_test_failed))')
self.emit('if [[ $__ct_test_failed -eq 0 ]]; then')
with self.indented():
self.emit('printf "${__CT_GREEN}All %d tests passed${__CT_RESET}\\n" "$total"')
self.emit('else')
with self.indented():
self.emit('printf "${__CT_RED}%d of %d tests failed${__CT_RESET}\\n" "$__ct_test_failed" "$total"')
self.emit('fi')
self.emit('return $__ct_test_failed')
self.emit("}")
self.emit()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment