From c3d275a7145db03c7b1bd415215ae0756c549bcc Mon Sep 17 00:00:00 2001 From: l3n Date: Sat, 28 Dec 2019 16:42:24 +0100 Subject: [PATCH] Initial commit. Script and Readme. --- README.org | 64 +++++++++++++++ trans.sh | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 README.org create mode 100755 trans.sh diff --git a/README.org b/README.org new file mode 100644 index 0000000..2ef6dfe --- /dev/null +++ b/README.org @@ -0,0 +1,64 @@ +#+TITLE: Ponsapi-cli +#+DATE: Sat Dec 28 12:48:54 CET 2019 +#+AUTHOR: Lio Novelli +#+LICENSE: GPLv3.0 +#+VERSION: 0.9 + +* Introduction + + There was no nice command line tool for translations to be found so I've + written my own. I was frustrated by opening a new tab in browser whenever I + wasn't able to remember a translation of a word. I use terminal a lot and + ponsapi-cli is one of the micro optimisations that at the end of the day + sum up (I hope). + + I gave a lot of attention to pretty printing results, but + not so much care was spent to make the translation script fast. It is + something that will be addressed in the future versions but until then + all you get is "pretty printing". + +* Usage + + ~trans [flags] ~ + + To get help use flag ~-h~. + +* Dependencies + + This script uses following tools: + - curl + - jq + - recode + - sed + +* Installation + + First you have to register at pons.com [[https://login.pons.com/login?return_to=https%253A%252F%252Fen.pons.com%252Fopen_dict%252Fpublic_api][api registration form]]. + + Once you get your API secret key, create a file named ponsapi.config in one + of these paths: ~$HOME, $HOME/.config or $HOME/.config/ponsapi-cli~. + + Besides ~PONS_SECRET=""~ line in your config file, you can + specify other default parameters there as well (~PONS_DICT~ for dictionary - + you can see a list of those by calling trans script with flag ~-o~ and + ~PONS_LANG~ to specify in which language is the word you are looking + translation for). + + Make your script executable and crate a symlink to it in your execution + path. + +* How it works + + Script creates a curl request to pons api and parses json response with jq. + Json response can have 2 different structures depending on a results found in + their dictionary. This complicates parsing a little bit but script has + two functions written for parsing each of those two different structures + and creates a unified json which is then used for pretty printing results. + + You can see raw results with ~-r~ flag. + +* Development + - [[https://en.pons.com/p/files/uploads/API/API_Documentation.pdf][Pons api documentation]] + + I will rewrite this application in haskell once I found more time. Even before + that I'll implement some error handling. diff --git a/trans.sh b/trans.sh new file mode 100755 index 0000000..bbc6fff --- /dev/null +++ b/trans.sh @@ -0,0 +1,235 @@ +#!/bin/bash +## Pons api request wrapper for commandline +## +## 0. Read api secreet from configuration file. +## You can store default options in your config file. +## 1. Create curl request with parameters (word to translate, languages, +## fuzzy matching, number of results, language of results, return type) +## 2. Parse results with jq! +## 3. Pretty print results. +## + +# PONS_LANG='sl' # # jq +PONS_URI="https://api.pons.com/v1/" +PONS_DICT='ensl' # curl +PONS_FUZZY=0 # curl +# PONS_LIMIT=1000 +PONS_RAW=0 # bash +PONS_HELP=0 # bash +PONS_CONFIG='ponsapi.config' #name of the config file +PONS_HITS=0 #true if type of hit is translation and not entry +PONS_COUNT=0 + +# Check if arguments hold. +pons_error() { + echo "Error check!" +} + +# Build a request query. +pons_request() { + PONS_QUERY="dictionary?l=$PONS_DICT&q=$PONS_WORD" + if [ "$PONS_FUZZY" == "1" ] ; then + PONS_QUERY="$PONS_QUERY&fm=$PONS_FUZZY" + fi + if [ ! -z "$PONS_LANG" ] ; then + PONS_QUERY="$PONS_QUERY&in=$PONS_LANG" + fi + if [ ! -z "$PONS_REF" ] ; then + PONS_QUERY="$PONS_QUERY&ref=true" + fi + #echo "$PONS_QUERY" + PONS_JSON=$( curl -s --header "X-Secret: $PONS_SECRET" $PONS_URI$PONS_QUERY ) + # check if type of hit is translation + pons_hits +} + +pons_hits() { + PONS_HITS=$( jq '.[] | .hits[] | .type' <<< $PONS_JSON | grep -c "translation" ) +} + +# Build a jq filter +pons_filter() { + PONS_FILTER=".[]" + # if [ ! -z $PONS_LANG ] ; then + # PONS_FILTER="$PONS_FILTER | select( .lang == \"$PONS_LANG\")" + # fi + if [ ! -z $PONS_RAW -a "$PONS_RAW" == "1" ] ; then + # echo "Raw json output." + PONS_FILTER="[$PONS_FILTER ]" + else + if [ $PONS_HITS -gt 0 ] ; then + PONS_FILTER="[$PONS_FILTER | {\"language_p\": .lang, \"hits_p\": [.hits[] | {\"headword_p\": \"\", \"wordclass_p\": \"\", \"arabs_p\": [ {\"header_p\": \"\", \"translations_p\": [ {\"source_p\": .source, \"target_p\": .target }] } ] }] }]" + else + PONS_FILTER="[$PONS_FILTER | {\"language_p\": .lang, \"hits_p\": [.hits[] | .roms | .[] | {\"headword_p\": .headword, \"wordclass_p\": .wordclass, \"arabs_p\": [ .arabs | .[] | {\"header_p\": .header, \"translations_p\": [.translations[] | {\"source_p\": .source, \"target_p\": .target }] } ] }] }]" + fi + fi + #echo $PONS_FILTER +} + +pons_tags() { + source_t="$1" + #echo "$source_t\n" + source_t=$( recode html..latin1 <<< $source_t ) + old_color="$2" + spn_color="$3" + source_t=$( sed -e "s/]*>/$(tput setaf ${spn_color})/g" -e "s/]*>/$(tput bold)/g" -e "s/]*>/$(tput dim)/g" -re "s/(<\/acronym>|<\/strong>|<\/span>)+/$(tput sgr0)$(tput setaf ${old_color})/g" <<< $source_t ) + printf " $(tput setaf ${old_color})%b$(tput sgr0)" "$source_t" +} + +# 1. get language as variable and loop through it +# 2. get headword, wordclass and loop through arabs +pons_parse() { + COLS=$(( $(tput cols) / 2 - 4 )) + PONS_COUNT=0 + for lang_key in $(jq -r '.| keys | .[]' <<< $PONS_JSON); do + cur_lang=$(echo $PONS_JSON | jq ".[$lang_key] | .language_p" | sed -e 's/^"//' -e 's/"$//') + sub_lang=$(echo $PONS_JSON | jq ".[$lang_key]"); + if [ ! -z $PONS_LIMIT ] && [ "$PONS_COUNT" -gt "$PONS_LIMIT" ] ; then + break + fi + for hit_key in $(jq ".hits_p | keys | .[]" <<< $sub_lang); do + if [ ! -z $PONS_LIMIT ] && [ "$PONS_COUNT" -gt "$PONS_LIMIT" ] ; then + break + fi + sub_hit=$(jq -r ".hits_p | .[$hit_key]" <<< $sub_lang); + hw=$(jq -r '"\(.headword_p)"' <<< $sub_hit ) #| sed -e 's/^"//' -e 's/"$//') + cw=$(jq -r '"\(.wordclass_p)"' <<< $sub_hit) + echo -e "\033[1;36m$hw\033[0m\t$cur_lang\t\033[0;34m$cw\033[0m" + PONS_COUNT=$((PONS_COUNT + 1)) + for ara_key in $(jq ".arabs_p | keys | .[]" <<< $sub_hit); do + sub_ara=$(jq ".arabs_p | .[$ara_key]" <<< $sub_hit) + hd=$(jq -r " .header_p" <<< $sub_ara | sed 's/\(.*\)<\/span>/\\033\[0;34m\1\\033\[0;32m/g' | sed 's/\(.*\)<\/strong>/\\033\[1;32m\1\\033\[0;32m/g') + if [ ! -z "$hd" ] ; then + echo -e " $hd" + fi + for tra_key in $(jq -c ".translations_p | keys | .[] " <<< $sub_ara); do + sub_tra=$(jq ".translations_p | .[$tra_key] " <<< $sub_ara) + source_p=$(jq -r ".source_p " <<< $sub_tra) + target_p=$(jq -r ".target_p " <<< $sub_tra) + source_p=$( pons_tags "$source_p" '2' '3' ) + target_p=$( pons_tags "$target_p" '5' '6' ) + printf "$(tput sc) %b$(tput rc)$(tput cuf $COLS) %b\n" "$source_p" "$target_p" + done + done + done + done +} + +# Pons api has this insufficiency that it can return json in different form +# Type of hit is translation. +pons_parse_translate() { + COLS=$(( $(tput cols) / 2 - 4 )) + for lang_key in $(jq -r '.| keys | .[]' <<< $PONS_JSON); do + cur_lang=$(echo $PONS_JSON | jq ".[$lang_key] | .language_p" | sed -e 's/^"//' -e 's/"$//') + sub_lang=$(echo $PONS_JSON | jq ".[$lang_key]"); + if [ ! -z $PONS_LIMIT ] && [ "$PONS_COUNT" -gt "$PONS_LIMIT" ] ; then + break + fi + for hit_key in $(jq ".hits_p | keys | .[]" <<< $sub_lang); do + sub_hit=$(jq -r ".hits_p | .[$hit_key]" <<< $sub_lang); + hw=$(jq -r '"\(.headword_p)"' <<< $sub_hit ) #| sed -e 's/^"//' -e 's/"$//') + PONS_COUNT=$((PONS_COUNT + 1)) + for ara_key in $(jq ".arabs_p | keys | .[]" <<< $sub_hit); do + sub_ara=$(jq ".arabs_p | .[$ara_key]" <<< $sub_hit) + for tra_key in $(jq -c ".translations_p | keys | .[] " <<< $sub_ara); do + if [ ! -z $PONS_LIMIT ] && [ "$PONS_COUNT" -gt "$PONS_LIMIT" ] ; then + break + fi + sub_tra=$(jq ".translations_p | .[$tra_key] " <<< $sub_ara) + source_p=$(jq -r ".source_p " <<< $sub_tra) + target_p=$(jq -r ".target_p " <<< $sub_tra) + source_p=$( pons_tags "$source_p" '2' '3' ) + target_p=$( pons_tags "$target_p" '5' '6' ) + printf "$(tput sc)%s %b$(tput rc)$(tput cuf $COLS) %b\n" "$cur_lang" "$source_p" "$target_p" + done + done + done + done +} + +pons_config() { + #echo "Check if file exists: .ponsapi.config in home dir and ponsapi.config in home/.config dir." + if [ -f "$HOME/.$PONS_CONFIG" ] ; then + source $HOME/.$PONS_CONFIG + elif [ -f "$HOME/.config/$PONS_CONFIG" ] ; then + source $HOME/.config/$PONS_CONFIG + elif [ -f "$HOME/.config/ponsapi-cli/$PONS_CONFIG" ] ; then + source $HOME/.config/ponsapi-cli/$PONS_CONFIG + else + echo "ERROR: Config file not found!" + fi +} + +pons_help () { + echo "This is a pons api wrapper for command line." + echo -e "Basic usage:\n\tpons -[ldfnrthoc] word." + echo -e "\nOptions:" + echo -e "\t-h :: Help; This help." + echo -e "\t-o :: Options; List of language options." + echo -e "\t-l [lang] :: Language; Specify language of the word (sl, en, de, hu). Results will be of another language." + echo -e "\t-d [dict] :: Dictonary; Select language pair to translate in (ensl, desl, husl ...). For full list call with [-o] flag." + echo -e "\t-f :: Fuzzy matching; Enable fuzzy matching results." + echo -e "\t-e :: Enable REference ; Not quite sure what this does yet." + echo -e "\t-n [num] :: Number; Limits the number of results." + echo -e "\t-r :: Raw; Return raw result from pons." + # echo -e "\t-t [type] :: Type; Return results in this type, txt by default (json, csv)." # long term plan + echo -e "\t-c :: Count; Count number of results." +} + +pons_options () { + echo -e "Supported languages are: de, el, en, es, fr, it, pl, pt, ru, sl, th, zh;\n" + if [ ! -z $PONS_LANG ] ; then + PONS_QUERY="dictionaries?language=$PONS_LANG" + else + PONS_QUERY="dictionaries?language=sl" + fi + echo "List of dicitionaries:" + curl -s -k $PONS_URI$PONS_QUERY | jq 'map({(.key): .simple_label}) | add' +} + +# FLAGS SETUP +while getopts "fl:d:n:t:rhoec" option +do + case "${option}" + in + l) PONS_LANG=${OPTARG};; + d) PONS_DICT=${OPTARG};; + f) PONS_FUZZY=1;; + n) PONS_LIMIT=${OPTARG};; + t) PONS_TYPE=${OPTARG};; + r) PONS_RAW=1;; + h) PONS_HELP=1;; + o) PONS_HELP=2;; + c) PONS_COUNT=1;; + e) PONS_REF=1;; + esac +done +shift $((OPTIND -1)) +PONS_WORD=$1 + +if [ "$PONS_HELP" == "1" ] ; then + pons_help + + #return 0; +elif [ "$PONS_HELP" == "2" ] ; then + pons_options +elif [ "$PONS_RAW" == "1" ] ; then + pons_config + pons_request + pons_filter + PONS_JSON=$(echo $PONS_JSON | jq "$PONS_FILTER") + echo -e $PONS_JSON | jq . +else + pons_config + pons_request + pons_filter + PONS_JSON=$(echo $PONS_JSON | jq "$PONS_FILTER") + #echo -e $PONS_JSON | jq . + # pons_parse + if [ "$PONS_HITS" -gt 0 ] ; then + pons_parse_translate + else + pons_parse + fi +fi +