feat: supports functions in bash/js/python/ruby (#6)
This commit is contained in:
+199
-35
@@ -3,60 +3,184 @@ set -e
|
||||
|
||||
# @meta dotenv
|
||||
|
||||
BIN_DIR=bin
|
||||
|
||||
LANG_CMDS=( \
|
||||
"sh:bash" \
|
||||
"js:node" \
|
||||
"py:python" \
|
||||
"rb:ruby" \
|
||||
)
|
||||
|
||||
# @cmd Call the function
|
||||
# @arg func![`_choice_func`] The function name
|
||||
# @arg args~[?`_choice_func_args`] The function args
|
||||
call() {
|
||||
"./bin/$argc_func" "${argc_args[@]}"
|
||||
basename="${argc_func%.*}"
|
||||
lang="${argc_func##*.}"
|
||||
func_path="./$lang/$basename.$lang"
|
||||
if [[ ! -e "$func_path" ]]; then
|
||||
_die "error: not found $argc_func"
|
||||
fi
|
||||
if [[ "$lang" == "sh" ]]; then
|
||||
"$func_path" "${argc_args[@]}"
|
||||
else
|
||||
"$(_lang_to_cmd "$lang")" "./cmd/cmd.$lang" "$argc_func"
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Build all artifacts
|
||||
# @cmd Build the project
|
||||
# @option --names-file=functions.txt Path to a file containing function filenames, one per line.
|
||||
# This file specifies which function files to build.
|
||||
# Example:
|
||||
# get_current_weather.sh
|
||||
# may_execute_js_code.js
|
||||
build() {
|
||||
if [[ -f functions.txt ]]; then
|
||||
argc build-declarations-json
|
||||
fi
|
||||
if [[ "$OS" = "Windows_NT" ]]; then
|
||||
argc build-win-shims
|
||||
fi
|
||||
argc build-declarations-json --names-file "${argc_names_file}"
|
||||
argc build-bin --names-file "${argc_names_file}"
|
||||
}
|
||||
|
||||
# @cmd Build declarations for specific functions
|
||||
# @option --output=functions.json <FILE> Specify a file path to save the function declarations
|
||||
# @option --names-file=functions.txt Specify a file containing function names
|
||||
# @arg funcs*[`_choice_func`] The function names
|
||||
# @cmd Build bin dir
|
||||
# @option --names-file=functions.txt Path to a file containing function filenames, one per line.
|
||||
build-bin() {
|
||||
if [[ ! -f "$argc_names_file" ]]; then
|
||||
_die "no found "$argc_names_file""
|
||||
fi
|
||||
mkdir -p "$BIN_DIR"
|
||||
rm -rf "$BIN_DIR"/*
|
||||
names=($(cat "$argc_names_file"))
|
||||
invalid_names=()
|
||||
for name in "${names[@]}"; do
|
||||
basename="${name%.*}"
|
||||
lang="${name##*.}"
|
||||
func_file="$lang/$name"
|
||||
if [[ -f "$func_file" ]]; then
|
||||
if _is_win; then
|
||||
bin_file="$BIN_DIR/$basename.cmd"
|
||||
if [[ "$lang" == sh ]]; then
|
||||
_build_win_sh > "$bin_file"
|
||||
else
|
||||
_build_win_lang $lang "$(_lang_to_cmd "$lang")" > "$bin_file"
|
||||
fi
|
||||
else
|
||||
bin_file="$BIN_DIR/$basename"
|
||||
if [[ "$lang" == sh ]]; then
|
||||
ln -s "$PWD/$func_file" "$bin_file"
|
||||
else
|
||||
ln -s "$PWD/cmd/cmd.$lang" "$bin_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
invalid_names+=("$name")
|
||||
fi
|
||||
done
|
||||
if [[ -n "$invalid_names" ]]; then
|
||||
_die "error: missing following functions: ${invalid_names[*]}"
|
||||
fi
|
||||
echo "Build bin"
|
||||
}
|
||||
|
||||
# @cmd Build declarations.json
|
||||
# @option --output=functions.json <FILE> Path to a json file to save function declarations
|
||||
# @option --names-file=functions.txt Path to a file containing function filenames, one per line.
|
||||
# @arg funcs*[`_choice_func`] The function filenames
|
||||
build-declarations-json() {
|
||||
set +e
|
||||
if [[ "${#argc_funcs[@]}" -gt 0 ]]; then
|
||||
names=("${argc_funcs[@]}" )
|
||||
elif [[ -f "$argc_names_file" ]]; then
|
||||
names=($(cat "$argc_names_file"))
|
||||
fi
|
||||
if [[ -z "$names" ]]; then
|
||||
_die "error: no specific function"
|
||||
_die "error: no function for building declarations.json"
|
||||
fi
|
||||
result=()
|
||||
json_list=()
|
||||
not_found_funcs=()
|
||||
build_failed_funcs=()
|
||||
for name in "${names[@]}"; do
|
||||
result+=("$(build-func-declaration "$name")")
|
||||
lang="${name##*.}"
|
||||
func_file="$lang/$name"
|
||||
if [[ ! -f "$func_file" ]]; then
|
||||
not_found_funcs+=("$name")
|
||||
continue;
|
||||
fi
|
||||
json_data="$("build-single-declaration" "$name")"
|
||||
status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
json_list+=("$json_data")
|
||||
else
|
||||
build_failed_funcs+=("$name")
|
||||
fi
|
||||
done
|
||||
echo "["$(IFS=,; echo "${result[*]}")"]" | jq '.' > "$argc_output"
|
||||
if [[ -n "$not_found_funcs" ]]; then
|
||||
_die "error: not found functions: ${not_found_funcs[*]}"
|
||||
fi
|
||||
if [[ -n "$build_failed_funcs" ]]; then
|
||||
_die "error: invalid functions: ${build_failed_funcs[*]}"
|
||||
fi
|
||||
echo "Build $argc_output"
|
||||
echo "["$(IFS=,; echo "${json_list[*]}")"]" | jq '.' > "$argc_output"
|
||||
}
|
||||
|
||||
# @cmd Build declaration for a single function
|
||||
|
||||
# @cmd Build single declaration
|
||||
# @arg func![`_choice_func`] The function name
|
||||
build-func-declaration() {
|
||||
argc --argc-export bin/$1 | _parse_declaration
|
||||
build-single-declaration() {
|
||||
func="$1"
|
||||
lang="${func##*.}"
|
||||
cmd="$(_lang_to_cmd "$lang")"
|
||||
if [[ "$lang" == sh ]]; then
|
||||
argc --argc-export "$lang/$func" | _parse_argc_declaration
|
||||
else
|
||||
LLM_FUNCTION_DECLARATE=1 "$cmd" "cmd/cmd.$lang" "$func"
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Build shims for the functions
|
||||
# Because Windows OS can't run bash scripts directly, we need to make a shim for each function
|
||||
#
|
||||
# @flag --clear Clear the shims
|
||||
build-win-shims() {
|
||||
funcs=($(_choice_func))
|
||||
for func in "${funcs[@]}"; do
|
||||
echo "Shim bin/${func}.cmd"
|
||||
_win_shim > "bin/${func}.cmd"
|
||||
done
|
||||
# @cmd List functions that can be put into functions.txt
|
||||
# Examples:
|
||||
# argc --list-functions > functions.txt
|
||||
# argc --list-functions --write
|
||||
# argc --list-functions search_duckduckgo.sh >> functions.txt
|
||||
# @flag -w --write Output to functions.txt
|
||||
# @arg funcs*[`_choice_func`] The function filenames, list all available functions if not provided
|
||||
list-functions() {
|
||||
if [[ -n "$argc_write" ]]; then
|
||||
_choice_func > functions.txt
|
||||
echo "Write functions.txt"
|
||||
else
|
||||
_choice_func
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Test the project
|
||||
# @meta require-tools node,python,ruby
|
||||
test() {
|
||||
names_file=functions.txt.test
|
||||
argc list-functions > "$names_file"
|
||||
argc build --names-file "$names_file"
|
||||
argc test-call-functions
|
||||
}
|
||||
|
||||
|
||||
# @cmd Test call functions
|
||||
test-call-functions() {
|
||||
if _is_win; then
|
||||
ext=".cmd"
|
||||
fi
|
||||
"./bin/may_execute_command$ext" --command 'echo "bash works"'
|
||||
argc call may_execute_command.sh --command 'echo "bash works"'
|
||||
|
||||
export LLM_FUNCTION_DATA='{"code":"console.log(\"javascript works\")"}'
|
||||
"./bin/may_execute_js_code$ext"
|
||||
argc call may_execute_js_code.js
|
||||
|
||||
export LLM_FUNCTION_DATA='{"code":"print(\"python works\")"}'
|
||||
"./bin/may_execute_py_code$ext"
|
||||
argc call may_execute_py_code.py
|
||||
|
||||
export LLM_FUNCTION_DATA='{"code":"puts \"ruby works\""}'
|
||||
"./bin/may_execute_rb_code$ext"
|
||||
argc call may_execute_rb_code.rb
|
||||
}
|
||||
|
||||
# @cmd Install this repo to aichat functions_dir
|
||||
@@ -80,7 +204,7 @@ version() {
|
||||
curl --version | head -n 1
|
||||
}
|
||||
|
||||
_parse_declaration() {
|
||||
_parse_argc_declaration() {
|
||||
jq -r '
|
||||
def parse_description(flag_option):
|
||||
if flag_option.describe == "" then
|
||||
@@ -123,26 +247,66 @@ _parse_declaration() {
|
||||
}'
|
||||
}
|
||||
|
||||
_win_shim() {
|
||||
_lang_to_cmd() {
|
||||
match_lang="$1"
|
||||
for item in "${LANG_CMDS[@]}"; do
|
||||
lang="${item%:*}"
|
||||
if [[ "$lang" == "$match_lang" ]]; then
|
||||
echo "${item#*:}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_build_win_sh() {
|
||||
cat <<-'EOF'
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "script_dir=%~dp0"
|
||||
set "bin_dir=%~dp0"
|
||||
for %%i in ("%bin_dir:~0,-1%") do set "script_dir=%%~dpi"
|
||||
set "script_name=%~n0"
|
||||
set "script_name=%script_name%.sh"
|
||||
for /f "delims=" %%a in ('argc --argc-shell-path') do set "_bash_prog=%%a"
|
||||
|
||||
"%_bash_prog%" --noprofile --norc "%script_dir%\%script_name%" %*
|
||||
"%_bash_prog%" --noprofile --norc "%script_dir%sh\%script_name%" %*
|
||||
EOF
|
||||
}
|
||||
|
||||
_build_win_lang() {
|
||||
lang="$1"
|
||||
cmd="$2"
|
||||
cat <<-EOF
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "bin_dir=%~dp0"
|
||||
for %%i in ("%bin_dir:~0,-1%") do set "script_dir=%%~dpi"
|
||||
set "script_name=%~n0"
|
||||
|
||||
$cmd "%script_dir%cmd\cmd.$lang" "%script_name%.$lang" %*
|
||||
EOF
|
||||
}
|
||||
|
||||
_is_win() {
|
||||
if [[ "$OS" == "Windows_NT" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_choice_func() {
|
||||
ls -1 bin | grep -v '\.cmd'
|
||||
for item in "${LANG_CMDS[@]}"; do
|
||||
lang="${item%:*}"
|
||||
ls -1 $lang | grep "\.$lang$"
|
||||
done
|
||||
}
|
||||
|
||||
_choice_func_args() {
|
||||
args=( "${argc__positionals[@]}" )
|
||||
argc --argc-compgen generic "bin/${args[0]}" "${args[@]}"
|
||||
if [[ "${args[0]}" == *.sh ]]; then
|
||||
argc --argc-compgen generic "sh/${args[0]}" "${args[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
_die() {
|
||||
|
||||
Reference in New Issue
Block a user