Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merriam Webster Backend, Antonyms, and Result Caching #48

Open
wants to merge 42 commits into
base: testing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
90060f5
Added DictionaryAPI (Merriam-Webster) Lookup
Sep 16, 2020
7e173f7
Added back query_result_trunc
Sep 16, 2020
f12da71
Fixed https error in DictionaryAPI
Sep 16, 2020
e44e54d
Tentative output format change
Sep 16, 2020
680c834
Tentative output format change
Sep 16, 2020
f66545c
Removed Test Files
Sep 16, 2020
23ba143
Fixed Dictionary API Parser errors on missing values
Sep 21, 2020
c34f47c
Fixed word guessing for unknown words in Dictionary API
Sep 21, 2020
6794ee3
Fixed - Dictionary API parser returned wrong code for unknown word
Sep 21, 2020
b379849
Fixed - Dictionary API parser did not return full correction list
Sep 21, 2020
849ec76
Fixed - Dictionary API parser returned wrong code for unknown word
Sep 21, 2020
3bd32ad
Updated Readme with Back end info
Sep 21, 2020
cf8d392
Added by_def format to dictionary api backend
Sep 21, 2020
2756b14
dictionary api - removed extra bracked (list) for by_def format
Sep 21, 2020
7fbeb30
dictionary api - default return for to break up synonyms by def
Sep 21, 2020
3657aef
Updated dictionary api parser to handle multiple result sets
Sep 24, 2020
d95f8f6
Updated dictionary api parser to handle multiple result sets
Sep 24, 2020
5e3c2fd
Updated Core code to handle antonyms using a separate list
Sep 24, 2020
fdd1599
Updated Core code to handle antonyms using a separate list
Sep 24, 2020
fda15bf
Updated Core code to handle antonyms using a separate list
Sep 24, 2020
064f0e7
Attempt to fix antonyms not showing
Sep 24, 2020
9521157
Attempt to fix antonyms not showing
Sep 24, 2020
1f03c87
Attempt to fix antonyms not showing
Sep 24, 2020
d7d3433
Attempt to fix antonyms not showing
Sep 24, 2020
247b7e5
Attempt to fix antonyms not showing
Sep 24, 2020
ccb538a
Attempt to fix antonyms not showing
Sep 24, 2020
7668f06
Attempt to fix antonyms not showing
Sep 24, 2020
ba8df57
Attempt to fix antonyms not showing
Sep 24, 2020
c9c45df
Attempt to fix missing query_type variable
Sep 24, 2020
23f54b5
Attempt to fix missing query_type variable
Sep 24, 2020
68daff5
Attempt to fix missing query_type variable
Sep 24, 2020
207ce40
Attempt to fix missing query_type variable
Sep 24, 2020
42cdc99
Updated Readme to reflect antonyms
Sep 24, 2020
eae6023
Edits
Oct 2, 2020
bb75af9
Merge branch 'master' of https://github.com/ahayman/thesaurus_query.vim
Oct 2, 2020
8582be4
Caching, Rename, cleanup
Oct 2, 2020
7cc2f0a
Fixed exceptions in case of missing or incorrect api key for merriam-…
Oct 2, 2020
d25162d
Fixes for newer version of Python/Vim
Dec 18, 2020
1519552
Revert "Fixed exceptions in case of missing or incorrect api key for …
Jan 24, 2021
6a31f25
Fixed iOS need for explicit SSL (not required for later version of Py…
Jan 24, 2021
c47f860
forgot to add SSL import
Jan 24, 2021
c39f4aa
Removed SSL in urlopen, which was causing issues
ahayman Mar 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ required**.

## What's new

Added new English back end: [merriam_webster](https://dictionaryapi.com/). This is the Merriam-Webster API and
as such is a very high-quality back end. However, it does require registration on their website as a developer in order to
gain access to the API Keys. They do explicitly state that it is free for non-commercial use up to 1,000 queries a day, which
should be sufficient for most needs. Make sure you select the `Thesaurus` api key, as that is what you will need in order for this
backend to work.

In order to use this backend, add `merriam_webster` to `g:tq_enabled_backend` and set your api key to `g:tq_merriam_webster_api_key`, ex:

```
let g:tq_merriam_webster_api_key='cxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxa'
```
**This backend cannot work without an API key.**

The backend also returns antonyms using a different command:
```
:ThesaurusQueryReplaceCurrentWordAntonym
```

By default, this is bound to:

```vim
nnoremap <Leader>ca :ThesaurusQueryReplaceCurrentWord<CR>
```

See **Usage** below for changing the bindings.

If the word cannot be found, the API may sometimes return a list of word suggestions. These will be returned as `Unknown word`
and allow you to choose a replacement from the list.

-------

Deleted `thesaurus_com` backend due to the legal warning from Thesaurus.com on
the upstream package [thesarus](https://github.com/Manwholikespie/thesaurus).
For existing users that still have `thesaurus_com` explicitly enabled, the
Expand Down Expand Up @@ -202,6 +233,13 @@ backends is behaving properly.
website didn't provide standard API to use. Hence functionality of this
backend depends on whether the website owner will change the webpage design.
This backend requires `bs4` *BeautifulSoup* dependency.
* **merriam\_webster** is an *English* thesaurus backend. It queries
[dictionaryapi.com](https://dictionaryapi.com/) for both synonym and antonym resources.
The api requires an api key that can be obtained by registering on their [website](https://dictionaryapi.com/).
Registration is free, but limits requests to 1,000 queries a day, which should be fine
for most users. When registering, make sure to select the "Thesaurus" api key. You will need
to assign that api key to `g:tq_merriam_webster_api_key` and manually add the
backend to `g:tq_enabled_backends` in your vimrc file.

The thesaurus query plugin will go through the list `g:tq_enabled_backends` in
sequence until a match is found. Unless user explicitly instruct, Next query
Expand Down Expand Up @@ -239,6 +277,22 @@ originally defined priority, simply invoke command
:ThesaurusQueryReset
```

#### Caching

In order to speed up results and avoid hitting the backend for repeated requests, the results
of a query can be cached. This is off by default.

To enable caching, set the `tq_cache_results` value to some number.

- `let g:tq_cache_results=-1`: (_Default_) Query results are not cached.
- `let g:tq_cache_results=0`: Query results are cached without limit.
- `let g:tq_cache_results=10`: The last 10 results are cached. You can set this to any positive number.

Note: Separate caches are maintained for synonyms and antonyms (if the back end supports it). The cache setting
will apply to each cache separately. So if you specify `let g:tq_cache_results=10`, then the last 10 synonym and
antonym requests will be stored. Also, some back ends (Merriam-Webster, for example) always return both and will thus
be cached.

#### Online Backends Timeout Mechanism

Timeout mechanism (configurable with `g:tq_online_backends_timeout`) is added
Expand Down
20 changes: 16 additions & 4 deletions autoload/thesaurus_query.vim
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ if !exists("g:tq_raise_backend_priority_if_synonym_found")
let g:tq_raise_backend_priority_if_synonym_found=0
endif

" this variable sets whether the results will be cached
" -1: Results will not be cached
" 0: All results will be cached with no limit
" >=1: X results will be cached (Fifo logic)
if !exists("g:tq_cache_results")
let g:tq_cache_results=-1
endif

" this variable is offered by core query handler. It's a list of
" query_backends user want to enable, with the sequence of user prefered
" priority.
Expand Down Expand Up @@ -223,11 +231,15 @@ function! thesaurus_query#Thesaurus_Query_Restore_Handler()
exec s:tq_use_python.'tq_framework.restore_thesaurus_query_handler()'
endfunction

function! thesaurus_query#Thesaurus_Query_Lookup(word, replace) " {{{
function! thesaurus_query#Thesaurus_Query_Lookup(word, replace, query_type) " {{{
" a:word word to be looked up
" a:replace flag:
" 0 - don't replace word under cursor
" 1 - replace word under cursor
" a:query_type flag:
" 0 - synonyms
" 1 - antonyms
let l:query_type = a:query_type
let l:replace = a:replace
let l:trimmed_word = s:Trim(a:word)
let l:word = substitute(tolower(l:trimmed_word), '"', '', 'g')
Expand All @@ -245,17 +257,17 @@ tq_continue_query = 1
while tq_continue_query>0:
vim.command("redraw")
tq_next_query_direction = True if tq_continue_query==1 else False
tq_synonym_result = tq_framework.query(decode_utf_8(vim.eval("l:word")), tq_next_query_direction)
tq_synonym_result = tq_framework.query(decode_utf_8(vim.eval("l:word")), tq_next_query_direction, 0 if vim.eval('l:query_type') == '0' else 1)
# Use Python environment for handling candidate displaying {{{
# mark for exit function if no candidate is found
if not tq_synonym_result:
vim.command("echom \"No synonym found for \\\"{0}\\\".\"".format(vim.eval("l:trimmed_word").replace('\\','\\\\').replace('"','\\"')))
vim.command("echom \"No results found for \\\"{0}\\\".\"".format(vim.eval("l:trimmed_word").replace('\\','\\\\').replace('"','\\"')))
vim.command("let l:syno_found=0")
tq_framework.session_terminate()
tq_continue_query = 0
# if replace flag is on, prompt user to choose after populating candidate list
elif vim.eval('l:replace') != '0':
tq_continue_query = tq_interface.tq_replace_cursor_word_from_candidates(tq_synonym_result, tq_framework.good_backends[-1])
tq_continue_query = tq_interface.tq_replace_cursor_word_from_candidates(tq_synonym_result, tq_framework.good_backends[-1], 0 if vim.eval('l:query_type') == '0' else 1)
else:
tq_continue_query = 0
tq_framework.session_terminate()
Expand Down
89 changes: 89 additions & 0 deletions autoload/thesaurus_query/backends/merriam_webster_lookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# python wrapper for word query from dictionaryapi.com
# Author: Aaron Hayman [[[email protected]][E-mail]]


try:
from urllib2 import urlopen
from urllib2 import URLError, HTTPError
except ImportError:
from urllib.request import urlopen
from urllib.error import URLError, HTTPError
import json
import ssl
import socket
from ..tq_common_lib import fixurl, get_variable

query_result_trunc=100
identifier="merriam_webster"
language="en"

_timeout_period_default = 1.0
time_out_choice = float(get_variable('tq_online_backends_timeout', _timeout_period_default))
api_key = get_variable('tq_merriam_webster_api_key', '')

def query(target):
'''
Queries the Merriam Webster API to retrieve thesaurus results for the target word.
Requires the `tq_merriam_webster_api_key` to be set to an appropriate value.
Returns Status code and two lists: synonyms and antonyms. Both lists are broken up into their
appropriate word definitions.

Note: If no word matches the target, the API may return substitute words. If this happens, both
synonyms and antonyms will list those words under the "Unknown Word" heading.
'''
if not target or target == '':
return [1, [], []]
target=target.replace(u" ", u"+")
result_list=_dictionary_api_wrapper(target)
if result_list == -1:
return [-1, [], []]
elif result_list == 1:
return [1, [], []]
else:
return _parser(result_list)


def _dictionary_api_wrapper(target):
if api_key == '':
return [-1, []]
try:
url = fixurl(u'https://www.dictionaryapi.com/api/v3/references/thesaurus/json/{0}?key={1}'.format(target, api_key)).decode('ASCII')
response = urlopen(url, context=ssl.SSLContext(), timeout = time_out_choice).read()
result_list = json.loads(response.decode('utf-8'))
except HTTPError:
return 1
except URLError as err:
if isinstance(err.reason, socket.timeout):
return 1
print(err)
return -1
except socket.timeout: # timeout only means underperforming
return 1
except ValueError:
return -1
return result_list

def _parseAntonyms(result_dict):
defs = result_dict.get(u'shortdef', [])
ants_list = result_dict.get(u'meta', {}).get(u'ants', [])
length = min(len(defs), len(ants_list))
return [ [ defs[idx]+' ('+ result_dict.get(u'fl', '') +')', ants_list[idx] ] for idx in range(length) if len(ants_list) > 0 ]

def _parseSynonyms(result_dict):
defs = result_dict.get(u'shortdef', [])
syns_list = result_dict.get(u'meta', {}).get(u'syns', [])
length = min(len(defs), len(syns_list))
return [ [ defs[idx]+' ('+ result_dict.get(u'fl', '') +')', syns_list[idx] ] for idx in range(length) if len(syns_list) > 0 ]

def _parser(result):
if result is None or len(result) == 0:
return [1, [], []]
result_dict = result[0]
if not result_dict:
return [1, [], []]
if isinstance(result_dict, str):
return [0, [['Unknown word (did you mean):', result]], [['Unknown word (did you mean):', result]]]
return [ 0,
[pair for r_dict in result for pair in _parseSynonyms(r_dict)],
[pair for r_dict in result for pair in _parseAntonyms(r_dict)]
]
Loading