476 lines
10 KiB
Bash
Executable file
476 lines
10 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# FIELD_TYPES_STR=(string str text)
|
|
# FIELD_TYPES_INT=(integer int num number)
|
|
# FIELD_TYPES_RAW=(raw data bytes)
|
|
# works but we do not want to show all to the user
|
|
# IFS=" " read -r -a FIELD_TYPES <<< "${FIELD_TYPES_INT[*]} ${FIELD_TYPES_STR[*]} ${FIELD_TYPES_RAW[*]}"
|
|
# IFS=$'\n'
|
|
# FIELD_TYPES=(int str raw)
|
|
|
|
declare -A FIELD_TYPES=(
|
|
[int]='int integer num number'
|
|
[string]='string str text'
|
|
[raw]='raw data bytes'
|
|
[bool]='bool boolean'
|
|
)
|
|
|
|
function show_help() {
|
|
echo "usage: twnet [action] [options]"
|
|
echo "options:"
|
|
echo " --help | -h show this help"
|
|
echo "actions:"
|
|
echo " generate | g generate a ruby file. See $(tput bold)twnet g --help$(tput sgr0)"
|
|
}
|
|
|
|
# TODO: move those file helpers to a lib file and also use them in hooks.sh
|
|
function replace_line() {
|
|
local filename="$1"
|
|
local search_ln="$2"
|
|
local replace_str="$3"
|
|
_edit_line_in_file replace 0 "$filename" "$search_ln" "$replace_str"
|
|
}
|
|
|
|
# replace 'num_lines' after match
|
|
function replace_lines_from() {
|
|
local filename="$1"
|
|
local search_ln="$2"
|
|
local replace_str="$3"
|
|
local num_lines="$4"
|
|
_edit_line_in_file replace "$num_lines" "$filename" "$search_ln" "$replace_str"
|
|
}
|
|
|
|
function append_line() {
|
|
local filename="$1"
|
|
local search_ln="$2"
|
|
local replace_str="$3"
|
|
_edit_line_in_file append 0 "$filename" "$search_ln" "$replace_str"
|
|
}
|
|
|
|
function _edit_line_in_file() {
|
|
local mode="$1"
|
|
local num_lines_from="$2"
|
|
local filename="$3"
|
|
local search_ln="$4"
|
|
local replace_str="$5"
|
|
local repl_ln
|
|
local from_ln
|
|
local to_ln
|
|
if [[ ! "$num_lines_from" =~ ^[0-9]+$ ]]
|
|
then
|
|
echo "Error: _edit_line_in_file num_lines_from '$num_lines_from' has to be numeric"
|
|
exit 1
|
|
fi
|
|
repl_ln="$(grep -nF "$search_ln" "$filename" | cut -d':' -f1)"
|
|
if [ "$mode" == "append" ]
|
|
then
|
|
from_ln="$((repl_ln+1))"
|
|
to_ln="$repl_ln"
|
|
elif [ "$mode" == "replace" ]
|
|
then
|
|
from_ln="$((repl_ln-1))"
|
|
to_ln="$((repl_ln+1+num_lines_from))"
|
|
else
|
|
echo "Error: _edit_line_in_file expectes mode replace or append"
|
|
exit 1
|
|
fi
|
|
if [ "$repl_ln" == "" ]
|
|
then
|
|
echo "Error: failed to get line of '$search_ln' in file '$filename'"
|
|
exit 1
|
|
fi
|
|
{
|
|
head -n "$from_ln" "$filename"
|
|
echo -ne "$replace_str"
|
|
tail -n +"$to_ln" "$filename"
|
|
} > "$filename".tmp
|
|
mv "$filename".tmp "$filename"
|
|
}
|
|
|
|
|
|
function is_camel_case() {
|
|
[[ "$1" =~ ^([A-Z][a-z]+)+$ ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
function is_snake_case() {
|
|
[[ "$1" =~ ^[a-z][a-z_]*[a-z]$ ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
function split_camel_case() {
|
|
local str="$1"
|
|
local words=()
|
|
local word=''
|
|
while read -n1 -r c
|
|
do
|
|
if [[ "$c" =~ [A-Z] ]]
|
|
then
|
|
if [ "$word" != "" ]
|
|
then
|
|
words+=("${word,}")
|
|
word=''
|
|
fi
|
|
fi
|
|
word+="$c"
|
|
done < <(echo -n "$str")
|
|
if [ "$word" != "" ]
|
|
then
|
|
words+=("${word,}")
|
|
word=''
|
|
fi
|
|
echo "${words[*]}"
|
|
}
|
|
|
|
function split_snake_case() {
|
|
local str="$1"
|
|
local words=()
|
|
local word=''
|
|
while read -n1 -r c
|
|
do
|
|
if [[ "$c" == '_' ]]
|
|
then
|
|
if [ "$word" != "" ]
|
|
then
|
|
words+=("${word,}")
|
|
word=''
|
|
fi
|
|
else
|
|
word+="$c"
|
|
fi
|
|
done < <(echo -n "$str")
|
|
if [ "$word" != "" ]
|
|
then
|
|
words+=("${word,}")
|
|
word=''
|
|
fi
|
|
echo "${words[*]}"
|
|
}
|
|
|
|
function camel_to_snake_case() {
|
|
local camel="$1"
|
|
local w
|
|
local snake=''
|
|
for w in $(split_camel_case "$camel")
|
|
do
|
|
snake+="_$w"
|
|
done
|
|
echo "${snake:1}"
|
|
}
|
|
|
|
function snake_to_camel_case() {
|
|
local snake="$1"
|
|
local w
|
|
local camel=''
|
|
for w in $(split_snake_case "$snake")
|
|
do
|
|
camel+="${w^}"
|
|
done
|
|
echo "$camel"
|
|
}
|
|
|
|
function action_generate_help() {
|
|
echo "usage: twnet generate <type> <name> [fields..]"
|
|
echo "options:"
|
|
echo " --help | -h show this help"
|
|
echo "type:"
|
|
echo " server_packet | srv_pck | sp packet sent from server to client"
|
|
echo " client_packet | cl_pck | cp packet sent from client to server"
|
|
echo "name:"
|
|
echo " will be the packet name use UpperCamelCase"
|
|
echo " for example ClSay"
|
|
echo "fields:"
|
|
echo " the formate is field_name:field_type"
|
|
echo " the allowed types are: ${!FIELD_TYPES[*]}"
|
|
echo " examples:"
|
|
echo " target_id:int"
|
|
echo " message:str"
|
|
echo "examples:"
|
|
echo " generate a cl_say.rb file describing a packet that"
|
|
echo " is sent from the client to the server has the class name ClSay"
|
|
echo " and two fields first target_id (integer) then message (string)"
|
|
echo ""
|
|
tput bold
|
|
echo " twnet generate cp ClSay target_id:int message:str"
|
|
tput sgr0
|
|
}
|
|
|
|
function replace_str() {
|
|
local filename="$1"
|
|
local search="$2"
|
|
local replace="$3"
|
|
sed "s/$search/$replace/" "$filename" > "$filename".tmp
|
|
mv "$filename".tmp "$filename"
|
|
}
|
|
|
|
function action_generate() {
|
|
local arg
|
|
local arg_type=''
|
|
local arg_name=''
|
|
local name_camel
|
|
local name_snake
|
|
local field_name
|
|
local field_type
|
|
# associative array does not work
|
|
# because it does not keep order
|
|
local fields=()
|
|
local valid_type
|
|
if [ "$#" -eq "0" ]
|
|
then
|
|
action_generate_help
|
|
exit 1
|
|
fi
|
|
while true
|
|
do
|
|
[[ "$#" -eq "0" ]] && break
|
|
|
|
arg="$1"
|
|
shift
|
|
|
|
if [ "${arg::1}" == "-" ]
|
|
then
|
|
if [ "$arg" == "--help" ] || [ "$arg" == "-h" ]
|
|
then
|
|
action_generate_help
|
|
exit 0
|
|
fi
|
|
else
|
|
if [ "$arg_type" == "" ]
|
|
then
|
|
if [ "$arg" == "server_packet" ] ||
|
|
[ "$arg" == "srv_pck" ] ||
|
|
[ "$arg" == "sv_pck" ] ||
|
|
[ "$arg" == "sp" ] ||
|
|
[ "$arg" == "sv" ]
|
|
then
|
|
arg_type=server
|
|
elif [ "$arg" == "client_packet" ] ||
|
|
[ "$arg" == "cl_pck" ] ||
|
|
[ "$arg" == "cp" ] ||
|
|
[ "$arg" == "cl" ]
|
|
then
|
|
arg_type=client
|
|
else
|
|
echo "Error: invalid packet type '$arg'"
|
|
echo " expected: server_packet or client_packet"
|
|
exit 1
|
|
fi
|
|
elif [ "$arg_name" == "" ]
|
|
then
|
|
arg_name="$arg"
|
|
# tried to be smart with regex and failed
|
|
# if [[ "$arg_name" =~ ([A-Z][a-z]*)+ ]]
|
|
# then
|
|
# for b in "${BASH_REMATCH[@]}"
|
|
# do
|
|
# echo "b: $b"
|
|
# done
|
|
# else
|
|
# echo "Error: name '$arg_name' has to be UpperCamelCase"
|
|
# exit 1
|
|
# fi
|
|
if ! is_camel_case "$arg_name"
|
|
then
|
|
echo "Error: name '$arg_name' has to be UpperCamelCase"
|
|
exit 1
|
|
fi
|
|
name_camel="$arg_name"
|
|
name_snake="$(camel_to_snake_case "$arg_name")"
|
|
elif [[ "$arg" =~ (.*):(.*) ]]
|
|
then
|
|
field_name="${BASH_REMATCH[1]}"
|
|
field_type="${BASH_REMATCH[2]}"
|
|
local valid=0
|
|
for valid_type in "${!FIELD_TYPES[@]}"
|
|
do
|
|
local tt
|
|
for tt in ${FIELD_TYPES[$valid_type]}
|
|
do
|
|
[[ "$tt" == "$field_type" ]] || continue
|
|
|
|
field_type="$valid_type"
|
|
valid=1
|
|
done
|
|
done
|
|
if [ "$valid" == "0" ]
|
|
then
|
|
echo "Error: '$field_type' is not a valid field type"
|
|
echo " valid types are: ${FIELD_TYPES[*]}"
|
|
exit 1
|
|
fi
|
|
if ! is_snake_case "$field_name"
|
|
then
|
|
echo "Error: '$field_name' is not a valid field name"
|
|
echo " field names have to be lower_snake_case"
|
|
exit 1
|
|
fi
|
|
fields+=("$field_name:$field_type")
|
|
else
|
|
echo "Error: unkown argument '$arg' try $(tput bold)twnet g --help$(tput sgr0)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$arg_type" == "" ]
|
|
then
|
|
echo "Error: type can not be empty $(tput bold)twnet g --help$(tput sgr0)"
|
|
exit 1
|
|
fi
|
|
if [ "$arg_name" == "" ]
|
|
then
|
|
echo "Error: name can not be empty $(tput bold)twnet g --help$(tput sgr0)"
|
|
exit 1
|
|
fi
|
|
local tmpdir
|
|
tmpdir=scripts/tmp
|
|
mkdir -p scripts/tmp
|
|
local tmpfile
|
|
tmpfile="$tmpdir/$name_snake.rb"
|
|
cp scripts/packet_template.rb "$tmpfile"
|
|
|
|
replace_str "$tmpfile" PacketName "$name_camel"
|
|
replace_str "$tmpfile" SENDER "${arg_type^}"
|
|
if [ "$arg_type" == "client" ]
|
|
then
|
|
replace_str "$tmpfile" RECEIVER Server
|
|
else
|
|
replace_str "$tmpfile" RECEIVER Client
|
|
fi
|
|
|
|
local accessors=''
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
accessors+=", :$field_name"
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
replace_line "$tmpfile" attr_accessor " attr_accessor${accessors:1}\n"
|
|
|
|
local unpacks=''
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
if [ "$field_type" == "bool" ]
|
|
then
|
|
unpacks+="\n @$field_name = u.get_int"
|
|
else
|
|
unpacks+="\n @$field_name = u.get_$field_type"
|
|
fi
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
replace_line "$tmpfile" Unpacker.new " u = Unpacker.new(data)$unpacks\n"
|
|
|
|
local hashs=''
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
if [ "$field_type" == "raw" ]
|
|
then
|
|
# TODO: pick nice raw default
|
|
# after this is closed
|
|
# https://github.com/ChillerDragon/teeworlds_network/issues/15
|
|
hashs+="\n @$field_name = attr[:$field_name] || 'RAAAAAAAAAW DATA !!!!!!'"
|
|
elif [ "$field_type" == "int" ]
|
|
then
|
|
hashs+="\n @$field_name = attr[:$field_name] || 0"
|
|
elif [ "$field_type" == "bool" ]
|
|
then
|
|
hashs+="\n @$field_name = attr[:$field_name] || false"
|
|
else # string or other
|
|
hashs+="\n @$field_name = attr[:$field_name] || 'TODO: fill default'"
|
|
fi
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
replace_line "$tmpfile" "@foo = attr[:foo] || 0" "${hashs:2}\n"
|
|
|
|
hashs=' {'
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
hashs+="\n $field_name: @$field_name,"
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
hashs="${hashs::-1}"
|
|
hashs+="\n }"
|
|
replace_line "$tmpfile" "{ foo: @foo," "${hashs:2}\n"
|
|
|
|
local packs=''
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
packs+=" "
|
|
if [ "$field_type" == "raw" ]
|
|
then
|
|
packs+="Packer.pack_raw(@$field_name) +\n"
|
|
elif [ "$field_type" == "int" ] || [ "$field_type" == "bool" ]
|
|
then
|
|
packs+="Packer.pack_int(@$field_name) +\n"
|
|
else # string or other
|
|
packs+="Packer.pack_str(@$field_name) +\n"
|
|
fi
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
replace_line "$tmpfile" Packer.pack_int "${packs:2:-4}\n"
|
|
|
|
local bools=''
|
|
while IFS=':' read -r field_name field_type
|
|
do
|
|
[[ "$field_type" == "bool" ]] || continue
|
|
|
|
bools+=" def $field_name?\n"
|
|
bools+=" !@$field_name.zero?\n"
|
|
bools+=" end\n"
|
|
bools+="\n"
|
|
done < <(echo "${fields[@]}" | tr ' ' '\n')
|
|
IFS=$'\n'
|
|
|
|
if [ "$bools" == "" ]
|
|
then
|
|
replace_lines_from "$tmpfile" 'def foo?' '' 3
|
|
else
|
|
replace_lines_from "$tmpfile" 'def foo?' "${bools::-2}\n" 3
|
|
fi
|
|
|
|
local destfile
|
|
destfile="lib/messages/$(basename "$tmpfile")"
|
|
if [ -f "$destfile" ]
|
|
then
|
|
echo "Error: file already exists '$destfile'"
|
|
exit 1
|
|
fi
|
|
mv "$tmpfile" "$destfile"
|
|
}
|
|
|
|
function parse_args() {
|
|
local arg
|
|
while true
|
|
do
|
|
[[ "$#" -eq "0" ]] && break
|
|
|
|
arg="$1"
|
|
shift
|
|
|
|
if [ "${arg::1}" == "-" ]
|
|
then
|
|
if [ "$arg" == "--help" ] || [ "$arg" == "-h" ]
|
|
then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
else
|
|
if [ "$arg" == "generate" ] || [ "$arg" == "g" ]
|
|
then
|
|
action_generate "$@"
|
|
exit
|
|
else
|
|
echo "Error: unkown action '$arg'"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
if [ "$#" -eq "0" ]
|
|
then
|
|
show_help
|
|
exit 1
|
|
fi
|
|
|
|
parse_args "$@"
|
|
|