~kris/dots

srice

ref: 2582f0c4173a4ad6a31e0cb759473c893cdc4e93 srice/bin/sthes -rwxr-xr-x 6.3 KiB
2582f0c4 — Kris Yotam sb-cpu: portable temp source (Intel Core 0, AMD Tdie/Tctl, Asahi NVMe Composite fallback) 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/usr/bin/env bash
# sthes — terminal thesaurus lookup
# Inspired by BreadOnPenguins/scripts/define
# Backends: Oxford Dictionaries API (set OD_APP_ID + OD_APP_KEY)
#           sdcv with thesaurus StarDict dict (offline, set STHES_USE_SDCV=1)
#           dictionaryapi.dev synonyms (free fallback)

set -euo pipefail

BOLD='\033[1m'
DIM='\033[2m'
BLUE='\033[34m'
CYAN='\033[36m'
GREEN='\033[32m'
YELLOW='\033[33m'
MAGENTA='\033[35m'
RESET='\033[0m'

usage() {
    echo -e "${BOLD}sthes${RESET} — look up synonyms and antonyms"
    echo ""
    echo -e "  ${BOLD}Usage:${RESET} sthes <word>"
    echo ""
    echo -e "  ${BOLD}Backends (checked in order):${RESET}"
    echo "    1. Oxford Dictionaries API  — set OD_APP_ID and OD_APP_KEY"
    echo "    2. sdcv (offline thesaurus) — set STHES_USE_SDCV=1"
    echo "    3. dictionaryapi.dev        — free, no config needed"
    exit 0
}

die() { echo -e "\033[31merror:\033[0m $1" >&2; exit 1; }

[[ $# -lt 1 || "$1" == "-h" || "$1" == "--help" ]] && usage
word="$1"
[[ "$word" =~ [^a-zA-Z-] ]] && die "invalid word: $word"

# helper: wrap a comma-separated list at terminal width
wrap_list() {
    local color="$1"
    shift
    local words=("$@")
    local line="    "
    local term_width
    term_width=$(tput cols 2>/dev/null || echo 80)

    for w in "${words[@]}"; do
        if (( ${#line} + ${#w} + 2 > term_width )); then
            echo -e "${color}${line}${RESET}"
            line="    ${w}, "
        else
            line+="${w}, "
        fi
    done
    # trim trailing comma+space
    line="${line%, }"
    [[ -n "$line" ]] && echo -e "${color}${line}${RESET}"
}

# --- Backend 1: Oxford Dictionaries API (Thesaurus endpoint) ---
oxford_lookup() {
    local resp
    resp=$(curl -sf --connect-timeout 5 --max-time 10 \
        -H "app_id: $OD_APP_ID" \
        -H "app_key: $OD_APP_KEY" \
        "https://od-api-v2.oxforddictionaries.com/api/v2/thesaurus/en/${word,,}") || return 1

    echo -e "${BOLD}${BLUE}${word}${RESET}"
    echo ""

    # Extract synonyms and antonyms by part of speech
    echo "$resp" | jq -r '
        .results[].lexicalEntries[] |
        .lexicalCategory.id as $pos |
        .entries[].senses[] |
        {
            pos: $pos,
            syns: [.synonyms[]?.text] | join("|"),
            ants: [.antonyms[]?.text] | join("|")
        } |
        "\(.pos)~\(.syns)~\(.ants)"
    ' 2>/dev/null | while IFS='~' read -r pos syns ants; do
        echo -e "  ${GREEN}${pos}${RESET}"

        if [[ -n "$syns" ]]; then
            echo -e "  ${BOLD}synonyms:${RESET}"
            IFS='|' read -ra syn_arr <<< "$syns"
            wrap_list "$CYAN" "${syn_arr[@]}"
        fi

        if [[ -n "$ants" ]]; then
            echo -e "  ${BOLD}antonyms:${RESET}"
            IFS='|' read -ra ant_arr <<< "$ants"
            wrap_list "$MAGENTA" "${ant_arr[@]}"
        fi
        echo ""
    done
}

# --- Backend 2: sdcv (offline thesaurus) ---
sdcv_lookup() {
    command -v sdcv >/dev/null 2>&1 || return 1
    # Try Collins Thesaurus or Moby Thesaurus if available
    local result
    result=$(sdcv -n -u "Collins Thesaurus" "$word" 2>/dev/null) \
        || result=$(sdcv -n -u "Moby Thesaurus" "$word" 2>/dev/null) \
        || result=$(sdcv -n "$word" 2>/dev/null) \
        || return 1
    [[ "$result" == *"Nothing similar"* ]] && return 1
    echo -e "${BOLD}${BLUE}${word}${RESET}"
    echo ""
    echo "$result" | sed 's/^/  /'
    echo ""
}

# --- Backend 3: Free Dictionary API (fallback — synonyms from meanings) ---
free_lookup() {
    local resp
    resp=$(curl -sf --connect-timeout 5 --max-time 10 \
        "https://api.dictionaryapi.dev/api/v2/entries/en_US/${word}") || return 1
    [[ "$resp" == *"No Definitions Found"* ]] && return 1

    echo -e "${BOLD}${BLUE}${word}${RESET}"
    echo ""

    local found_any=0

    echo "$resp" | jq -r '
        .[].meanings[] |
        .partOfSpeech as $pos |
        {
            pos: $pos,
            def: (.definitions[0].definition // ""),
            syns: ([.synonyms[]?] + [.definitions[].synonyms[]?] | unique | join("|")),
            ants: ([.antonyms[]?] + [.definitions[].antonyms[]?] | unique | join("|"))
        } |
        "\(.pos)~\(.def)~\(.syns)~\(.ants)"
    ' 2>/dev/null | while IFS='~' read -r pos def syns ants; do
        [[ -z "$syns" && -z "$ants" ]] && continue
        echo -e "  ${GREEN}${pos}${RESET}  ${DIM}${def}${RESET}"

        if [[ -n "$syns" ]]; then
            echo -e "  ${BOLD}synonyms:${RESET}"
            IFS='|' read -ra syn_arr <<< "$syns"
            wrap_list "$CYAN" "${syn_arr[@]}"
            found_any=1
        fi

        if [[ -n "$ants" ]]; then
            echo -e "  ${BOLD}antonyms:${RESET}"
            IFS='|' read -ra ant_arr <<< "$ants"
            wrap_list "$MAGENTA" "${ant_arr[@]}"
            found_any=1
        fi
        echo ""
    done

    # If free API had no synonyms, try datamuse as last resort
    if [[ "$found_any" == "0" ]]; then
        local dm
        dm=$(curl -sf --connect-timeout 5 --max-time 10 \
            "https://api.datamuse.com/words?rel_syn=${word}&max=20") || return 1
        local dm_words
        dm_words=$(echo "$dm" | jq -r '.[].word' 2>/dev/null)
        [[ -z "$dm_words" ]] && return 1

        echo -e "  ${BOLD}synonyms:${RESET}"
        local arr=()
        while IFS= read -r w; do arr+=("$w"); done <<< "$dm_words"
        wrap_list "$CYAN" "${arr[@]}"
        echo ""

        # also get antonyms from datamuse
        local dm_ant
        dm_ant=$(curl -sf --connect-timeout 5 --max-time 10 \
            "https://api.datamuse.com/words?rel_ant=${word}&max=20") || true
        local ant_words
        ant_words=$(echo "$dm_ant" | jq -r '.[].word' 2>/dev/null)
        if [[ -n "$ant_words" ]]; then
            echo -e "  ${BOLD}antonyms:${RESET}"
            local ant_arr=()
            while IFS= read -r w; do ant_arr+=("$w"); done <<< "$ant_words"
            wrap_list "$MAGENTA" "${ant_arr[@]}"
            echo ""
        fi
    fi

    echo -e "  ${DIM}source: dictionaryapi.dev / datamuse${RESET}"
}

# --- Dispatch ---
if [[ -n "${OD_APP_ID:-}" && -n "${OD_APP_KEY:-}" ]]; then
    oxford_lookup && exit 0
    echo -e "${DIM}Oxford API failed, trying fallback...${RESET}" >&2
fi

if [[ "${STHES_USE_SDCV:-0}" == "1" ]]; then
    sdcv_lookup && exit 0
    echo -e "${DIM}sdcv failed, trying fallback...${RESET}" >&2
fi

free_lookup || die "no results found for '${word}'"