diff --git a/packages/snipbank/1.0.3/LICENSE b/packages/snipbank/1.0.3/LICENSE new file mode 100644 index 00000000..7b29b162 --- /dev/null +++ b/packages/snipbank/1.0.3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Dickson Law + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/snipbank/1.0.3/README.md b/packages/snipbank/1.0.3/README.md new file mode 100644 index 00000000..de9ecd82 --- /dev/null +++ b/packages/snipbank/1.0.3/README.md @@ -0,0 +1,61 @@ +# SnipBank + +Easily collect snippets and recall them later with Espanso. + +## Requirements + +- Espanso 2.x or higher +- Python 3.x or higher with Pip +- PyYAML 5.x or higher + +## Installation + +```bash +pip install pyyaml +espanso install snipbank +``` + +## Keyboard Shortcuts + +- Use `,,..` (two commas, two dots) to add a snippet. +- Use `..,,` (two dots, two commas) to remove a snippet. + +## Pressing Tab and Enter in a Snippet + +- Use `^TAB^` in a snippet for pressing the Tab key. +- Use `^ENTER^` in a snippet for pressing the Enter key. +- Use `$|$` in a snippet to bring the cursor there upon inserting it. + +## Specifying a Form + +You may optionally specify a popup form to go with a snippet in order to make +parts of the snippet editable before its insertion. + +- Under "Form", enter the text to show in the popup. Use these to indicate fields: + - Text field: `[[variable_name]]` + - Text area: `[![variable_name]!]` + - Dropdown: `[^[variable_name:opt1|opt2|...]^]` + - List: `[>[variable_name:opt1|opt2|...]<]` +- You may specify a default value after a field name using `=`. Example: +`[[name=Anonymous]]`, `[^[greeting=Hello:Hello|Goodbye]^]` +- Use `{{variable_name}}` in the snippet to reference a form field. + +## Rich Text Snippets + +You may use Markdown or HTML to include rich text in your snippets. + +- `replace`: (Default) Use plain text replacements. +- `markdown`: Markdown can be used in snippets. +- `html`: HTML can be used in snippets. + +Notice that if you choose `markdown` or `html` and your snippet contains form input, the values from your form will also be interpreted as Markdown or HTML respectively. + +## Paste/Silent Mode + +When using `paste` mode (default), the snippet's replacement is printed. This +allows you to use `,,..` to add a new trigger and type at the same time, or +`..,,` as the "paste" step of a disposable clipboard. **If a form is specified, +nothing is printed as the output relies on user input.** + +When using `silent` mode, nothing is printed. This is useful when you just want +to manage your snippets without writing them out. diff --git a/packages/snipbank/1.0.3/_manifest.yml b/packages/snipbank/1.0.3/_manifest.yml new file mode 100644 index 00000000..dadf4bd7 --- /dev/null +++ b/packages/snipbank/1.0.3/_manifest.yml @@ -0,0 +1,7 @@ +author: Dickson Law +description: Easily configure custom snippets without manually editing the Espanso configs directory. +name: snipbank +title: SnipBank +version: 1.0.3 +homepage: "https://github.com/dicksonlaw583/espanso-snipbank" +tags: ["clipboard", "utility"] diff --git a/packages/snipbank/1.0.3/addtrigger.py b/packages/snipbank/1.0.3/addtrigger.py new file mode 100644 index 00000000..299b45f8 --- /dev/null +++ b/packages/snipbank/1.0.3/addtrigger.py @@ -0,0 +1,98 @@ +import re +import sys +import yaml +from common import * + +formPrefix = 'f' + +def getEspansoFormVar(formText, name=formPrefix): + ''' + Return the Espanso form variable specification of the given form text. + [[field=default]]: Regular text field + [![field=default]!]: Text area + [^[field=default:opt0|opt1|opt2|...]^]: Dropdown + [>[field=default:opt0|opt1|opt2|...]<]: Multi-select + ''' + fields = {} + layout = formText + textFieldRegex = re.compile(r'\[\[([\w_]+)(=?[^\]]*)\]\]') + textAreaRegex = re.compile(r'\[\!\[([\w_]+)(=?[^\]]*)\]\!\]') + dropdownRegex = re.compile(r'\[\^\[([\w_]+)(=?[^:]*):([^\]]*)\]\^\]') + listSelectRegex = re.compile(r'\[\>\[([\w_]+)(=?[^:]*):([^\]]*)\]\<\]') + + # Populate fields + for match in textFieldRegex.finditer(formText): + fieldName = match[1] + fieldDefault = match[2][1:] + if fieldDefault: + fields[fieldName] = { + "default": fieldDefault + } + for match in textAreaRegex.finditer(formText): + fieldName = match[1] + fieldDefault = match[2][1:] + fields[fieldName] = { + "multiline": True, + "default": fieldDefault + } + for match in dropdownRegex.finditer(formText): + fieldName = match[1] + fieldDefault = match[2][1:] + fieldChoices = match[3].split('|') + fields[fieldName] = { + "type": "choice", + "default": fieldDefault, + "values": fieldChoices + } + for match in listSelectRegex.finditer(formText): + fieldName = match[1] + fieldDefault = match[2][1:] + fieldChoices = match[3].split('|') + fields[fieldName] = { + "type": "list", + "default": fieldDefault, + "values": fieldChoices + } + + # Replace layout elements + for regex in (textFieldRegex, textAreaRegex, dropdownRegex, listSelectRegex): + layout = regex.sub('[[\\1]]', layout) + + # Form body + body = { + "name": name, + "type": "form", + "params": { + "layout": layout, + "fields": fields + } + } + return body + +def addTrigger(rootFile, trigger, snippet, form, replaceType, reprint=False): + ''' + Add a regular trigger to a match YAML file. + ''' + data = getDefaultData() + rectifiedSnippet = re.sub(r'\{\{([\w_]+)\}\}', '{{' + formPrefix + '.\\1}}', snippet.replace('^TAB^', "\t").replace('^ENTER^', "\n")) + with open(rootFile, 'r') as rootFh: + data = yaml.load(rootFh, Loader=yaml.BaseLoader) + replacement = { + "trigger": trigger, + replaceType: rectifiedSnippet, + } + if form: + replacement['vars'] = [getEspansoFormVar(form)] + data['matches'].append(replacement) + data['matches'].sort(key=lambda m : m['trigger'].upper()) + with open(rootFile, 'w') as rootFh: + yaml.dump(data, rootFh) + if reprint: + print(rectifiedSnippet, end='') + +if __name__ == '__main__': + # Syntax: python3 addtrigger.py
+ sys.stdout.reconfigure(encoding='utf-8') + if not sys.argv[1]: + exit(0) + addTrigger(getSnipBankRootFile(), sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], reprint=sys.argv[5] == 'paste' and not sys.argv[3]) diff --git a/packages/snipbank/1.0.3/common.py b/packages/snipbank/1.0.3/common.py new file mode 100644 index 00000000..022f9bef --- /dev/null +++ b/packages/snipbank/1.0.3/common.py @@ -0,0 +1,27 @@ +import os +import yaml + +def getDefaultData(): + ''' + Return YAML data for an empty match file. + ''' + return {"matches": []} + +def getSnipBankRoot(): + ''' + Return the default root directory, creating it if it does not already exist. + ''' + root = os.path.join(os.path.dirname(__file__), '..', 'snipbank_generated') + if not os.path.isdir(root): + os.mkdir(root) + return root + +def getSnipBankRootFile(): + ''' + Return the default generated file, creating it if it does not already exist. + ''' + rootFile = os.path.join(getSnipBankRoot(), 'generated.yml') + if not os.path.isfile(rootFile): + with open(rootFile, 'w') as rootFh: + yaml.dump(getDefaultData(), rootFh) + return rootFile diff --git a/packages/snipbank/1.0.3/lastused.py b/packages/snipbank/1.0.3/lastused.py new file mode 100644 index 00000000..174e0fec --- /dev/null +++ b/packages/snipbank/1.0.3/lastused.py @@ -0,0 +1,59 @@ +import json +import os, sys + +def getDefaultData(): + ''' + Return JSON data for an empty last-used file. + ''' + return {} + +def getLastUsedDirectory(): + ''' + Return the default last-used directory, creating it if it does not already exist. + ''' + currentDirectoryName = os.path.dirname(os.path.abspath(__file__)) + lastUsed = os.path.join(currentDirectoryName, '..', os.path.basename(currentDirectoryName) + '_lastused') + if not os.path.isdir(lastUsed): + os.mkdir(lastUsed) + return lastUsed + +def getLastUsedRootFile(): + ''' + Return the default last-used file, creating it if it does not already exist. + ''' + lastUsedFile = os.path.join(getLastUsedDirectory(), 'lastused.json') + if not os.path.isfile(lastUsedFile): + with open(lastUsedFile, 'w') as lastUsedFh: + json.dump(getDefaultData(), lastUsedFh) + return lastUsedFile + +def setProperty(property, value, file=getLastUsedRootFile()): + ''' + Set the given property value as last-used and save it. + ''' + data = getDefaultData() + with open(file, 'r') as f: + data = json.load(f) + data[property] = value + with open(file, 'w') as f: + json.dump(data, f) + +def getProperty(property, defaultValue='', file=getLastUsedRootFile()): + ''' + Get the given last-used value. If it doesn't exist yet, return the default value. + ''' + with open(file, 'r') as f: + data = json.load(f) + if property in data: + return data[property] + return defaultValue + + +if __name__ == '__main__': + # Syntax: python3 lastused.py get + sys.stdout.reconfigure(encoding='utf-8') + if sys.argv[1] == 'get': + print(getProperty(sys.argv[2], sys.argv[3]), end='') + # Syntax: python3 lastused.py set + elif sys.argv[1] == 'set': + setProperty(sys.argv[2], sys.argv[3]) diff --git a/packages/snipbank/1.0.3/listtriggers.py b/packages/snipbank/1.0.3/listtriggers.py new file mode 100644 index 00000000..1768b895 --- /dev/null +++ b/packages/snipbank/1.0.3/listtriggers.py @@ -0,0 +1,18 @@ +import sys +import yaml +from common import * + +def listTriggers(rootFile): + ''' + Print all triggers line-by-line. + ''' + data = getDefaultData() + with open(rootFile, 'r') as rootFh: + data = yaml.load(rootFh, Loader=yaml.BaseLoader) + for match in data['matches']: + print(match['trigger']) + +if __name__ == '__main__': + # Syntax: python3 listtriggers.py + sys.stdout.reconfigure(encoding='utf-8') + listTriggers(getSnipBankRootFile()) diff --git a/packages/snipbank/1.0.3/package.yml b/packages/snipbank/1.0.3/package.yml new file mode 100644 index 00000000..8d4d2759 --- /dev/null +++ b/packages/snipbank/1.0.3/package.yml @@ -0,0 +1,113 @@ +global_vars: + - name: snipbank_python + type: echo + params: + echo: "python" # Replace this with the path to your Python instance if automatic detection is failing + +matches: + - trigger: ",,.." + replace: "{{stdout}}" + vars: + - name: "last_choice" + type: script + params: + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/lastused.py" + - get + - mode + - paste + - name: "f" + type: form + params: + layout: "SnipBank: Add Trigger\n\nTrigger:[[trigger]]\nSnippet:[[snippet]]\nType:[[replacetype]]\nMode:[[mode]]\nForm:[[form]]" + fields: + snippet: + multiline: true + form: + multiline: true + mode: + type: choice + default: "{{last_choice}}" + values: + - paste + - silent + replacetype: + type: choice + default: replace + values: + - replace + - markdown + - html + - name: stdout + type: script + params: + trim: false + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/addtrigger.py" + - "{{f.trigger}}" + - "{{f.snippet}}" + - "{{f.form}}" + - "{{f.replacetype}}" + - "{{f.mode}}" + - name: set_last_choice + type: script + params: + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/lastused.py" + - set + - mode + - "{{f.mode}}" + - trigger: "..,," + replace: "{{stdout}}" + vars: + - name: list_triggers + type: script + params: + trim: true + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/listtriggers.py" + - name: "last_choice" + type: script + params: + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/lastused.py" + - get + - mode + - paste + - name: "f" + type: form + params: + layout: "SnipBank: Remove Trigger\n\nTrigger:\n[[trigger]]\nMode:\n[[mode]]" + fields: + trigger: + type: list + values: "{{list_triggers}}" + mode: + type: choice + default: "{{last_choice}}" + values: + - paste + - silent + - name: stdout + type: script + params: + trim: false + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/removetrigger.py" + - "{{f.trigger}}" + - "{{f.mode}}" + - name: set_last_choice + type: script + params: + args: + - "{{snipbank_python}}" + - "%CONFIG%/match/packages/snipbank/lastused.py" + - set + - mode + - "{{f.mode}}" diff --git a/packages/snipbank/1.0.3/removetrigger.py b/packages/snipbank/1.0.3/removetrigger.py new file mode 100644 index 00000000..d3591241 --- /dev/null +++ b/packages/snipbank/1.0.3/removetrigger.py @@ -0,0 +1,38 @@ +import sys +import yaml +from common import * + +def removeTrigger(rootFile, trigger): + ''' + Remove the given trigger from the match YAML file. + ''' + data = getDefaultData() + with open(rootFile, 'r') as rootFh: + data = yaml.load(rootFh, Loader=yaml.BaseLoader) + data['matches'] = [match for match in data['matches'] if match['trigger'] != trigger] + with open(rootFile, 'w') as rootFh: + yaml.dump(data, rootFh) + +def getTriggerReplacement(rootFile, trigger): + ''' + Return the replacement for the given trigger. + ''' + with open(rootFile, 'r') as rootFh: + data = yaml.load(rootFh, Loader=yaml.BaseLoader) + for match in data['matches']: + if 'vars' in match: + return '' + for field in ('markdown', 'html', 'replace'): + if field in match: + return match[field] + return '' + +if __name__ == '__main__': + # Syntax: python3 removetrigger.py + sys.stdout.reconfigure(encoding='utf-8') + if not sys.argv[1].strip(): + exit(0) + rootFile = getSnipBankRootFile() + if sys.argv[2] == 'paste': + print(getTriggerReplacement(rootFile, sys.argv[1]), end='') + removeTrigger(rootFile, sys.argv[1]) diff --git a/packages/snipbank/1.0.3/requirements.txt b/packages/snipbank/1.0.3/requirements.txt new file mode 100644 index 00000000..4818cc54 --- /dev/null +++ b/packages/snipbank/1.0.3/requirements.txt @@ -0,0 +1 @@ +pyyaml \ No newline at end of file