#!/usr/bin/env bash define() { IFS='\n' read -r -d '' ${1} || true ; } myname="${0##*/}" define pod <<"=cut" =head1 NAME xlate - translate CLI front-end for App::Greple::xlate module =head1 SYNOPSIS xlate [ options ] -t lang file [ greple options ] -h help -v show version -d debug -n dry-run -a use API -c just check translation area -r refresh cache -u force update cache -s silent mode -e # translation engine (*deepl, gpt3, gpt4, gpt4o) -p # pattern to determine translation area -x # file containing mask patterns -w # wrap line by # width -o # output format (*xtxt, cm, ifdef, space, space+, colon) -f # from lang (ignored) -t # to lang (required, no default) -m # max length per API call -l # show library files (XLATE.mk, xlate.el) -- terminate option parsing N.B. default is maked as * Make options -M run make -n dry-run Docker options -G mount git top-level directory -B run in non-interactive (batch) mode -R mount read-only -E # specify environment variable to be inherited -I # docker image or version (default: tecolicom/xlate:version) # -L use the latest version # -V # specify directory to be mounted (default: current directory) -D * run xlate on the container with the rest parameters -C * run following command on the container, or run shell N.B. -D/-C terminates option parsing Control Files: *.LANG translation languates *.FORMAT translation foramt (xtxt, cm, ifdef, colon, space) *.ENGINE translation engine (deepl, gpt3, gpt4, gpt4o) # marked as # options are experimental =head1 VERSION Version 0.41 =cut my_version=$(awk '$1=="Version"{print $2; exit}' <<< "$pod") git_topdir() { [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == true ] || return local dir="$(git rev-parse --show-superproject-working-tree)" [ "$dir" ] || dir="$(git rev-parse --show-toplevel)" echo -n $dir } env_pattern() { local name while (( $# > 0 )) do name+="${name:+\\|}$1" shift done echo -n ${name:+"^\\($name\\)="} } get_ip() { local ip=$(ifconfig 2>/dev/null | awk '/inet /{print $2}' | tail -1) echo -n $ip } repository=tecolicom/xlate hostname=xlate interactive=yes version= workdir=/work mount_mode=rw read_only= display= [ -e ${localtime:=/etc/localtime} ] || localtime= DEFAULT_ENV="$(sed 's/ */\n/g' <<-EOF LANG TZ LESS=-cR LESSANSIENDCHARS=mK HTTP_PROXY HTTPS_PROXY http_proxy https_proxy TERM_PROGRAM TERM_BGCOLOR COLORTERM DEEPL_AUTH_KEY OPENAI_API_KEY EOF )" declare -a ENV while getopts :${EXOPT:=dBI:V:T:LRE:GCD} OPT do case $OPT in d) debug=yes ; set -x ;; B) interactive= ;; V) volume="$OPTARG" ;; L) version=:latest ;; R) mount_mode=ro ;; I) case "$OPTARG" in [0-9]*.[0-9]*) version=:"$OPTARG" ;; *) container="$OPTARG" ;; esac ;; E) DEFAULT_ENV+=$'\n'"$OPTARG" ;; G) [ "$XLATE_RUNNING_ON_DOCKER" ] && break if [ "${topdir:=$(git_topdir)}" ] then if [ "$topdir" != "${PWD:=$(pwd)}" ] then echo "Mount $topdir to $workdir" >&2 volume=$topdir cd_path=${PWD#$topdir} fi fi ;; C|D) [ "$XLATE_RUNNING_ON_DOCKER" ] && break if [ "$DISPLAY" ] && [ "${ip:=$(get_ip)}" ] then display="$ip:0" fi if [ ! "$version" ] then [[ "$my_version" =~ ^[0-9]+\.[0-9_]+$ ]] && version=":$my_version" fi declare -a command case "$OPT" in C) shift $((OPTIND - 1)) ;; D) command=(xlate) ;; esac command+=(${1+"$@"}) [ ${#ENV[@]} -gt 0 ] && ENV_PATTERN="$(env_pattern ${ENV[@]})" exec docker run --rm \ -e XLATE_RUNNING_ON_DOCKER=1 \ ${interactive:+-it} \ ${read_only:+--read-only} \ ${hostname:+--hostname "${hostname}"} \ ${localtime:+-v ${localtime}:/etc/localtime:ro} \ -v "${volume:-$(pwd)}:${workdir}:${mount_mode}" \ -w "${workdir}${cd_path}" \ ${display:+-e DISPLAY="$display"} \ ${DEFAULT_ENV:+--env-file <(echo "$DEFAULT_ENV")} \ ${ENV_PATTERN:+--env-file <(env | grep -e "$ENV_PATTERN")} \ ${container:-${repository}${version}} \ "${command[@]}" exit; ;; esac done OPTIND=1 mod="App-Greple-xlate" share=$(perl -MFile::Share=:all -E "say dist_dir '$mod'") declare -a pattern while getopts "${EXOPT}"'Macrusne:w:o:f:t:m:x:p:l:hv' OPT do case $OPT in [$EXOPT]) : ignore ;; M) run_make=yes ;; a) use_api=yes ;; c) check=yes ;; r) refresh=yes ;; u) update=yes ;; s) silent=yes ;; n) dryrun=yes ;; e) engine="$OPTARG" ;; w) wrap="$OPTARG" ;; o) format="$OPTARG" ;; f) from_lang="$OPTARG" ;; t) to_lang="$OPTARG" ;; m) max="$OPTARG" ;; x) maskfile="$OPTARG" ;; p) pattern+=(--re "$OPTARG") ;; l) file="$share/$OPTARG" if [ -f "$file" ] then cat $file else echo $share ls -1 $share | sed 's/^/\t/' fi exit ;; h) sed -r \ -e '/^$/N' \ -e '/^\n*#/d' \ -e 's/^(\n*)=[a-z]+[0-9]* */\1/' \ -e '/Version/q' \ <<< "$pod" exit ;; v) echo $my_version exit ;; esac done shift $((OPTIND - 1)) : run make if [ "$run_make" == yes ] then declare -a opt for m in "$@" do if [ -f "$m" ] then # GNU Make behaves differently in different versions with # respect to double-quoted strings and spaces within them. files="${files:+$files|||}$m" else opt+=("$m") fi done unset MAKELEVEL exec make -f $share/XLATE.mk \ ${dryrun:+-n} \ XLATE_LANG=\""$to_lang"\" \ XLATE_DEBUG=$debug \ XLATE_MAXLEN=$max \ XLATE_USEAPI=$use_api \ ${engine:+XLATE_ENGINE=\""$engine"\"} \ ${format:+XLATE_FORMAT=\""$format"\"} \ ${files:+XLATE_FILES=\""$files"\"} \ ${opt[@]} exit 1 fi if [ ! "$to_lang" ] then echo "-t option is required." exit 1 fi : ${format:=xtxt} : ${engine:=deepl} if [[ "$format" =~ ^(.+)-fold$ ]] then format=${BASH_REMATCH[1]} : ${wrap:=76} fi declare -a module option module+=(-Mxlate) option+=(--xlate-engine="$engine") option+=(--xlate-to="$to_lang" --xlate-format="$format" --xlate-cache=yes) option+=(--all) [ "$use_api" == yes ] || use_clipboard=yes [ "$check" == yes ] || option+=(--xlate${use_clipboard:+-labor}) [ "$wrap" ] && option+=(--xlate-fold-line --xlate-fold-width=$wrap) [ "$debug" == yes ] && option+=(-dmo) [ "$refresh" == yes ] && option+=(--xlate-cache=clear) [ "$update" == yes ] && option+=(--xlate-update) [ "$silent" == yes ] && option+=(--no-xlate-progress) [ "$max" ] && option+=(--xlate-maxlen="$max") [ "$maskfile" ] && option+=(--xlate-setopt "maskfile=$maskfile") declare -a area case $1 in *.txt) area=(--re '^(.+\n)+') ;; *.md) area=(--re '(?x) ^[-+#].*\n | ^\h+\K.*\n | ^(.+\n)+ ') ;; *.pm|*.pod) module+=(-Mperl) option+=(--pod) option+=(--exclude '^=head\d +(VERSION|AUTHOR|LICENSE|COPYRIGHT|SEE.?ALSO).*\n(?s:.*?)(?=^=|\z)') area=(--re '^([\w\pP].+\n)+') ;; *.doc|*.docx|*.pptx|*.xlsx) module+=(-Mmsdoc) ;& *.stxt) option+=(--exclude '^\[.*\b(doc|docx|pptx|xlsx)\b.*\]\n') area=(--re '^.+\n') ;; *) area=(--re '^(.+\n)+') ;; esac if (( ${#pattern[@]} > 0)) then option+=("${pattern[@]}") else option+=("${area[@]}") fi exec=(greple "${module[@]}" "${option[@]}" ${1+"$@"}) if [ "$dryrun" ] then echo "${exec[@]}" else "${exec[@]}" fi