Skip to content

Commit

Permalink
support xonsh auto completion
Browse files Browse the repository at this point in the history
  • Loading branch information
eliranwong committed Nov 28, 2024
1 parent 41bfa57 commit 769715a
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 22 deletions.
5 changes: 4 additions & 1 deletion docker_setup/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ services:
volumes:
- ${DATA_DIR}:${DATA_DIR}:rw
# To run UniqueBibleApp terminal mode:
# > docker run -v $HOME/UniqueBible:$HOME/UniqueBible -it uniquebible-ubterm
# > docker run --rm --name ubterm -v $HOME/UniqueBible:$HOME/UniqueBible --network uniquebible_default uniquebible-ubterm
# Shell only:
# > docker run -it --rm --name ubterm -v $HOME/UniqueBible:$HOME/UniqueBible --network uniquebible_default uniquebible-ubterm sh
# Remarks: Use `docker network ls` to check the network name. The default network name is [COMPOSE_PROJECT_NAME]_default
ubterm:
build:
container_name: ubterm
Expand Down
2 changes: 1 addition & 1 deletion docker_setup/uniquebible/Dockerfile_ubterm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RUN \

RUN \
apk update && \
apk add doas python3 py3-pip ffmpeg micro nano && \
apk add doas python3 py3-pip ffmpeg micro nano w3m lynx && \
echo 'permit nopass :wheel' >> /etc/doas.conf

USER ${USER}
Expand Down
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
# https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
setup(
name=package,
version="0.2.0",
version="0.2.3",
python_requires=">=3.8, <3.13",
description=f"UniqueBible App is a cross-platform & offline bible application, integrated with high-quality resources and unique features. Developers: Eliran Wong and Oliver Tseng",
long_description=long_description,
Expand Down Expand Up @@ -563,9 +563,11 @@
},
entry_points={
"console_scripts": [
f"{package}={package}.uba:main",
f"{package}={package}.uba:main", # gui in a subprocess
f"ubgui={package}.uba:gui", # gui in a single process
f"ub={package}.uba:stream",
f"ubapi={package}.uba:api",
f"uba={package}.uba:api", # essentailly the same as ubapi, just quicker to type
f"ubhttp={package}.uba:http",
f"ubssh={package}.uba:ssh",
f"ubtelnet={package}.uba:telnet",
Expand Down
8 changes: 8 additions & 0 deletions uniquebible/latest_changes.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
PIP package:

0.2.1-0.2.3

* added support of xonsh auto-completions

* improved cli options

* fixed vlc loop issue

0.2.0

* fixed fresh installation
Expand Down
4 changes: 3 additions & 1 deletion uniquebible/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ opencc-python-reimplemented
mistralai
openai
groq
packaging
packaging
haversine
xonsh[full]
39 changes: 38 additions & 1 deletion uniquebible/startup/nonGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ def getApiOutput(command: str):
def multiturn_api_output(apiCommandSuggestions=None):
from uniquebible.util.prompt_shared_key_bindings import prompt_shared_key_bindings
from uniquebible.util.uba_command_prompt_key_bindings import api_command_prompt_key_bindings
from uniquebible.util.PromptValidator import NumberValidator
from prompt_toolkit import prompt
from prompt_toolkit.key_binding import merge_key_bindings
from prompt_toolkit.shortcuts import set_title, clear_title
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
Expand All @@ -246,6 +248,34 @@ def multiturn_api_output(apiCommandSuggestions=None):
from prompt_toolkit.completion import WordCompleter, NestedCompleter, ThreadedCompleter, FuzzyCompleter
import webbrowser

def simplePrompt(default="", numberOnly=False, inputIndicator=">>> "):
promptStyle = Style.from_dict({
"": config.terminalCommandEntryColor2,
"indicator": config.terminalPromptIndicatorColor2,
})
inputIndicator = [
("class:indicator", inputIndicator),
]
if numberOnly:
userInput = prompt(inputIndicator, style=promptStyle, default=default, validator=NumberValidator()).strip()
else:
userInput = prompt(inputIndicator, style=promptStyle, default=default).strip()
return userInput

def changeSettings():
print("# Chaning web API endpoint ...") # config.web_api_endpoint
if configuration := simplePrompt(config.web_api_endpoint):
config.web_api_endpoint = configuration
ConfigUtil.save()
print("# Chaning web API timeout ...") # config.web_api_timeout
if configuration := simplePrompt(str(config.web_api_timeout), True):
config.web_api_timeout = int(configuration)
ConfigUtil.save()
print("# Chaning web API private key ...") # config.web_api_private
if configuration := simplePrompt(config.web_api_private):
config.web_api_private = configuration
ConfigUtil.save()

# startup
set_title("Unique Bible App API-Client")
print("Running Unique Bible App api-client ...")
Expand All @@ -260,7 +290,11 @@ def multiturn_api_output(apiCommandSuggestions=None):

# initiate main prompt session
initiateMainPrompt()
command_completer = FuzzyCompleter(ThreadedCompleter(NestedCompleter.from_nested_dict(apiCommandSuggestions))) if apiCommandSuggestions is not None else None
if apiCommandSuggestions is None:
apiCommandSuggestions = {}
for i in (".quit", ".help", ".settings"):
apiCommandSuggestions[i] = None
command_completer = FuzzyCompleter(ThreadedCompleter(NestedCompleter.from_nested_dict(apiCommandSuggestions)))
auto_suggestion=AutoSuggestFromHistory()
toolbar = " [ctrl+q] .quit [escape+h] .help "
style = Style.from_dict({
Expand Down Expand Up @@ -295,6 +329,9 @@ def multiturn_api_output(apiCommandSuggestions=None):
if command:
if command.lower() == ".quit":
break
elif command.lower() == ".settings":
changeSettings()
continue
elif command.lower() == ".help":
webbrowser.open("https://github.com/eliranwong/UniqueBibleAPI")
continue
Expand Down
4 changes: 4 additions & 0 deletions uniquebible/uba.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ def desktopFileContent():
else:
subprocess.Popen([python, mainFile, initialCommand] if initialCommand else [python, mainFile])

def gui():
sys.argv.insert(1, "gui")
main()

def stream():
sys.argv.insert(1, "stream")
main()
Expand Down
25 changes: 13 additions & 12 deletions uniquebible/util/ConfigUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def updateModules(module, isInstalled):
# Check installed and latest versions on startup.""",
True)

# UBA Web API
# UBA Web API (work with GUI)
setConfig("uniquebible_api_endpoint", """
# UBA Web API server API endpoint""",
"https://bible.gospelchurch.uk/html")
Expand All @@ -178,6 +178,18 @@ def updateModules(module, isInstalled):
# UBA Web API server API key to access private data""",
"")

# UBA Web API (work with API client mode on console)
# Start of api-client mode setting
setConfig("web_api_endpoint", """
# UniqueBible App web API endpoint.""",
"https://bible.gospelchurch.uk/plain")
setConfig("web_api_timeout", """
# UniqueBible App web API timeout.""",
10)
setConfig("web_api_private", """
# UniqueBible App web API private key.""",
"")

# start of groq chat setting
# config.llm_backend
# config.addBibleQnA
Expand Down Expand Up @@ -313,17 +325,6 @@ def updateModules(module, isInstalled):
# When there is no bible reference found in the entry, after trying with the default command, the original command will be prefixed with the value of `config.secondDefaultCommand` and executed with it.""",
"REGEXSEARCH:::")

# Start of api-client mode setting
setConfig("web_api_endpoint", """
# UniqueBible App web API endpoint.""",
"https://bible.gospelchurch.uk/plain")
setConfig("web_api_timeout", """
# UniqueBible App web API timeout.""",
10)
setConfig("web_api_private", """
# UniqueBible App web API private key.""",
"")

# Start of terminal mode setting
setConfig("terminalWrapWords", """
# Wrap words in terminal mode.""",
Expand Down
4 changes: 2 additions & 2 deletions uniquebible/util/TextCommandParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1751,13 +1751,13 @@ def getHideOutputSuffix():
model_config_path = f"""{model_path}.json"""
if os.path.isfile(model_path):
if shutil.which("cvlc"):
cmd = f'''"{shutil.which("piper")}" --model "{model_path}" --config "{model_config_path}" --output-raw | cvlc --play-and-exit --rate {config.vlcSpeed} --demux=rawaud --rawaud-channels=1 --rawaud-samplerate=22050 -{getHideOutputSuffix()}'''
cmd = f'''"{shutil.which("piper")}" --model "{model_path}" --config "{model_config_path}" --output-raw | cvlc --no-loop --play-and-exit --rate {config.vlcSpeed} --demux=rawaud --rawaud-channels=1 --rawaud-samplerate=22050 -{getHideOutputSuffix()}'''
elif shutil.which("aplay"):
cmd = f'''"{shutil.which("piper")}" --model "{model_path}" --config "{model_config_path}" --output-raw | aplay -r 22050 -f S16_LE -t raw -{getHideOutputSuffix()}'''
else:
print("[Downloading voice ...] ")
if shutil.which("cvlc"):
cmd = f'''"{shutil.which("piper")}" --model {config.piperVoice} --download-dir "{model_dir}" --data-dir "{model_dir}" --output-raw | cvlc --play-and-exit --rate {config.vlcSpeed} --demux=rawaud --rawaud-channels=1 --rawaud-samplerate=22050 -{getHideOutputSuffix()}'''
cmd = f'''"{shutil.which("piper")}" --model {config.piperVoice} --download-dir "{model_dir}" --data-dir "{model_dir}" --output-raw | cvlc --no-loop --play-and-exit --rate {config.vlcSpeed} --demux=rawaud --rawaud-channels=1 --rawaud-samplerate=22050 -{getHideOutputSuffix()}'''
elif shutil.which("aplay"):
cmd = f'''"{shutil.which("piper")}" --model {config.piperVoice} --download-dir "{model_dir}" --data-dir "{model_dir}" --output-raw | aplay -r 22050 -f S16_LE -t raw -{getHideOutputSuffix()}'''
pydoc.pipepager(text, cmd=cmd)
Expand Down
4 changes: 2 additions & 2 deletions uniquebible/util/VlcUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def playMediaFileVlcNoGui(filePath, vlcSpeed):
command = f'''"{config.windowsVlc}" --intf dummy --play-and-exit --rate {vlcSpeed} "{filePath}"'''
# vlc on other platforms
elif VlcUtil.isPackageInstalled("cvlc"):
command = f'''cvlc --play-and-exit --rate {vlcSpeed} "{filePath}" &> /dev/null'''
command = f'''cvlc --no-loop --play-and-exit --rate {vlcSpeed} "{filePath}" &> /dev/null'''
# use .communicate() to wait for the playback to be completed as .wait() or checking pid existence does not work
subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
except:
Expand All @@ -104,7 +104,7 @@ def playMediaFileVlcGui(filePath, vlcSpeed):
command = f'''"{config.windowsVlc}" --play-and-exit --rate {vlcSpeed} "{filePath}"'''
# vlc on other platforms
elif VlcUtil.isPackageInstalled("vlc"):
command = f'''vlc --play-and-exit --rate {vlcSpeed} "{filePath}" &> /dev/null'''
command = f'''vlc --no-loop --play-and-exit --rate {vlcSpeed} "{filePath}" &> /dev/null'''
# use .communicate() to wait for the playback to be completed as .wait() or checking pid existence does not work
subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
except:
Expand Down
1 change: 1 addition & 0 deletions uniquebible/xonsh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
To work with auto suggestions and completions on Xonsh, copy the content in completer.py to ~/.xonshrc
81 changes: 81 additions & 0 deletions uniquebible/xonsh/completer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from xonsh.completers.tools import *

# UniqueBible App command keywords

bibleKeywords = ["text", "studytext", "_chapters", "_bibleinfo", "_vnsc", "_vndc", "readchapter", "readverse", "readword", "readlexeme", "compare", "comparechapter", "count", "search", "andsearch", "orsearch", "advancedsearch", "regexsearch", "bible", "chapter", "main", "study", "read", "readsync", "_verses", "_biblenote"]
lexiconKeywords = ["lexicon", "searchlexicon", "reverselexicon"]
commentaryKeywords = ["_commentarychapters", "_commentaryinfo", "commentary", "_commentaryverses", "_commentary"]
referenceKeywords = ["_book", "book", "searchbook", "searself.crossPlatformchbookchapter"]
thirdPartyDictKeywords = ["thirddictionary", "searchthirddictionary", "s3dict", "3dict"]

# Get UniqueBible App local resources

from uniquebible import config as ubaconfig
from uniquebible.util.ConfigUtil import ConfigUtil
ConfigUtil.setup()
ubaconfig.noQt=True
from uniquebible.util.CrossPlatform import CrossPlatform
crossPlatform = CrossPlatform()
crossPlatform.setupResourceLists()
resources = crossPlatform.resources

@non_exclusive_completer
@contextual_completer
def ub_completer(context):
if context.command and context.command.args and context.command.args[0].value == "ub" and context.command.prefix:
check = " ".join([i.value for i in context.command.args[1:] if hasattr(i, "value")]) + context.command.prefix
if re.search(f"^({'|'.join(bibleKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("bibleListAbb", [])])
elif re.search("^concordance:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("strongBibleListAbb", [])])
elif re.search(f"^({'|'.join(lexiconKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("lexiconList", [])])
elif re.search("^data:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("dataList", [])])
elif re.search(f"^({'|'.join(commentaryKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("commentaryListAbb", [])])
elif re.search("^dictionary:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("dictionaryListAbb", [])])
elif re.search("^encyclopedia:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("encyclopediaListAbb", [])])
elif re.search(f"^({'|'.join(referenceKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("referenceBookList", [])])
elif re.search(f"^({'|'.join(thirdPartyDictKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("thirdPartyDictionaryList", [])])
elif re.search("^searchtool:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in resources.get("searchToolList", [])])
completer add "ub_completer" ub_completer "start"

# Get UniqueBible App API resources
import requests, re
url = f"https://bible.gospelchurch.uk/json?cmd=.resources"
response = requests.get(url)
response.encoding = "utf-8"
api_resources = response.json()

@non_exclusive_completer
@contextual_completer
def ubapi_completer(context):
if context.command and context.command.args and context.command.args[0].value in ('ubapi', 'uba') and context.command.prefix:
check = " ".join([i.value for i in context.command.args[1:] if hasattr(i, "value")]) + context.command.prefix
if re.search(f"^({'|'.join(bibleKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("bibleListAbb", [])])
elif re.search("^concordance:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("strongBibleListAbb", [])])
elif re.search(f"^({'|'.join(lexiconKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("lexiconList", [])])
elif re.search("^data:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("dataList", [])])
elif re.search(f"^({'|'.join(commentaryKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("commentaryListAbb", [])])
elif re.search("^dictionary:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("dictionaryListAbb", [])])
elif re.search("^encyclopedia:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("encyclopediaListAbb", [])])
elif re.search(f"^({'|'.join(referenceKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("referenceBookList", [])])
elif re.search(f"^({'|'.join(thirdPartyDictKeywords)}):::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("thirdPartyDictionaryList", [])])
elif re.search("^searchtool:::", check, re.IGNORECASE):
return set([context.command.prefix+i for i in api_resources.get("searchToolList", [])])
completer add "ubapi_completer" ubapi_completer "start"

0 comments on commit 769715a

Please sign in to comment.