├── .flake8
├── .gitignore
├── .pre-commit-config.yaml
├── .pylintrc
├── .travis.yml
├── .yamllint
├── README.md
├── build
├── docopt.py
└── packageWorkflow.py
├── docs
├── catalina1.png
├── catalina2.png
├── complexsearch.png
├── ecount.png
├── settings.png
├── taco.png
└── tones.png
├── icon.png
├── images
└── Checkmark.png
├── img
└── .gitignore
├── info.plist
├── mypy.ini
├── na.png
├── pytest.ini
├── release.py
├── release.sh
├── requirements.txt
├── src
├── __init__.py
├── bg_downloader.py
├── buildDataFiles.py
├── downloadDataFiles.py
├── esearch.py
├── libs
│ ├── __init__.py
│ ├── bs4
│ │ ├── __init__.py
│ │ ├── builder
│ │ │ ├── __init__.py
│ │ │ ├── _html5lib.py
│ │ │ ├── _htmlparser.py
│ │ │ └── _lxml.py
│ │ ├── dammit.py
│ │ ├── diagnose.py
│ │ ├── element.py
│ │ ├── formatter.py
│ │ └── testing.py
│ ├── certifi
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── cacert.pem
│ │ └── core.py
│ ├── charset_normalizer
│ │ ├── __init__.py
│ │ ├── api.py
│ │ ├── assets
│ │ │ └── __init__.py
│ │ ├── cd.py
│ │ ├── cli
│ │ │ ├── __init__.py
│ │ │ └── normalizer.py
│ │ ├── constant.py
│ │ ├── legacy.py
│ │ ├── md.py
│ │ ├── models.py
│ │ ├── py.typed
│ │ ├── utils.py
│ │ └── version.py
│ ├── idna
│ │ ├── __init__.py
│ │ ├── codec.py
│ │ ├── compat.py
│ │ ├── core.py
│ │ ├── idnadata.py
│ │ ├── intranges.py
│ │ ├── package_data.py
│ │ ├── py.typed
│ │ └── uts46data.py
│ ├── requests
│ │ ├── __init__.py
│ │ ├── __version__.py
│ │ ├── _internal_utils.py
│ │ ├── adapters.py
│ │ ├── api.py
│ │ ├── auth.py
│ │ ├── certs.py
│ │ ├── compat.py
│ │ ├── cookies.py
│ │ ├── exceptions.py
│ │ ├── help.py
│ │ ├── hooks.py
│ │ ├── models.py
│ │ ├── packages.py
│ │ ├── sessions.py
│ │ ├── status_codes.py
│ │ ├── structures.py
│ │ └── utils.py
│ ├── soupsieve
│ │ ├── __init__.py
│ │ ├── __meta__.py
│ │ ├── css_match.py
│ │ ├── css_parser.py
│ │ ├── css_types.py
│ │ ├── pretty.py
│ │ ├── py.typed
│ │ └── util.py
│ ├── urllib3
│ │ ├── __init__.py
│ │ ├── _collections.py
│ │ ├── _version.py
│ │ ├── connection.py
│ │ ├── connectionpool.py
│ │ ├── contrib
│ │ │ ├── __init__.py
│ │ │ ├── _appengine_environ.py
│ │ │ ├── _securetransport
│ │ │ │ ├── __init__.py
│ │ │ │ ├── bindings.py
│ │ │ │ └── low_level.py
│ │ │ ├── appengine.py
│ │ │ ├── ntlmpool.py
│ │ │ ├── pyopenssl.py
│ │ │ ├── securetransport.py
│ │ │ └── socks.py
│ │ ├── exceptions.py
│ │ ├── fields.py
│ │ ├── filepost.py
│ │ ├── poolmanager.py
│ │ ├── request.py
│ │ ├── response.py
│ │ └── util
│ │ │ ├── __init__.py
│ │ │ ├── connection.py
│ │ │ ├── proxy.py
│ │ │ ├── queue.py
│ │ │ ├── request.py
│ │ │ ├── response.py
│ │ │ ├── retry.py
│ │ │ ├── ssl_.py
│ │ │ ├── ssl_match_hostname.py
│ │ │ ├── ssltransport.py
│ │ │ ├── timeout.py
│ │ │ ├── url.py
│ │ │ └── wait.py
│ └── workflow
│ │ ├── Notify.tgz
│ │ ├── __init__.py
│ │ ├── background.py
│ │ ├── notify.py
│ │ ├── update.py
│ │ ├── util.py
│ │ ├── workflow.py
│ │ └── workflow3.py
└── magic.py
├── test_emoji.py
└── workflow-build.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | per-file-ignores =
3 | src/*.py: E501,E722
4 | *.py: E501
5 | intellifire4py/__init__.py: F401,
6 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | default_language_version:
2 | python: python3.9
3 | exclude: ^$
4 | repos:
5 | - repo: 'https://github.com/pre-commit/pre-commit-hooks'
6 | rev: v4.1.0
7 | hooks:
8 | - id: trailing-whitespace
9 | exclude: src/libs|build
10 | - id: end-of-file-fixer
11 | exclude: src/libs|build
12 | - id: check-yaml
13 | exclude: src/libs|build
14 | - id: check-added-large-files
15 | exclude: src/libs|build
16 | - id: check-ast
17 | exclude: src/libs|build
18 | - id: check-builtin-literals
19 | exclude: src/libs|build
20 | - id: check-docstring-first
21 | exclude: src/libs|build
22 | - id: debug-statements
23 | exclude: src/libs|build
24 | - repo: 'https://github.com/codespell-project/codespell'
25 | rev: v2.1.0
26 | hooks:
27 | - id: codespell
28 | args:
29 | - >-
30 | --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests
31 | - '--skip="./.*,*.csv,*.json"'
32 | - '--quiet-level=2'
33 | exclude_types:
34 | - csv
35 | - json
36 | exclude: ^tests/fixtures/|src/libs|build
37 | - repo: 'https://github.com/asottile/pyupgrade'
38 | rev: v2.31.1
39 | hooks:
40 | - id: pyupgrade
41 | args:
42 | - '--py39-plus'
43 | exclude: src/libs|build
44 | - repo: 'https://github.com/PyCQA/isort'
45 | rev: 5.10.1
46 | hooks:
47 | - id: isort
48 | args:
49 | - '--profile'
50 | - black
51 | - '--filter-files'
52 | exclude: src/libs|build
53 | - repo: 'https://github.com/psf/black'
54 | rev: 22.3.0
55 | hooks:
56 | - id: black
57 | exclude: src/libs|build
58 | - repo: 'https://github.com/PyCQA/flake8'
59 | rev: 4.0.1
60 | hooks:
61 | - id: flake8
62 | additional_dependencies:
63 | - flake8-docstrings
64 | - pydocstyle
65 | - pycodestyle
66 | - flake8-comprehensions
67 | - flake8-noqa
68 | - mccabe
69 | exclude: src/libs|build|test_emoji.py
70 | - repo: 'https://github.com/pre-commit/mirrors-mypy'
71 | rev: v0.942
72 | hooks:
73 | - id: mypy
74 | types:
75 | - python
76 | additional_dependencies:
77 | - types-requests
78 | - pydantic
79 | exclude: src/libs|build
80 | - repo: 'https://github.com/Lucas-C/pre-commit-hooks-nodejs'
81 | rev: v1.1.2
82 | hooks:
83 | - id: markdown-toc
84 | args:
85 | - '--indent'
86 | - ' '
87 | - '-i'
88 | - repo: https://github.com/pre-commit/pre-commit-hooks
89 | rev: v4.1.0
90 | hooks:
91 | - id: check-executables-have-shebangs
92 | stages: [manual]
93 | - id: check-json
94 | exclude: (.vscode|.devcontainer|src/libs|build)
95 | - repo: 'https://github.com/adrienverge/yamllint.git'
96 | rev: v1.26.3
97 | hooks:
98 | - id: yamllint
99 | - repo: https://github.com/pre-commit/mirrors-prettier
100 | rev: v2.6.2
101 | hooks:
102 | - id: prettier
103 | stages: [manual]
104 | exclude: src/libs|build
105 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [FORMAT]
2 | max-line-length=240
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | install:
5 | - pip install -r requirements.txt
6 | - pip install pytest-travis-fold
7 | script: pytest -l --capture=no --travis-fold
8 | notifications:
9 | email:
10 | recipients:
11 | - jeffstein@gmail.com
12 | - jstein@mitre.org
13 | on_success: never # default: change
14 | on_failure: always # default: always
15 |
--------------------------------------------------------------------------------
/.yamllint:
--------------------------------------------------------------------------------
1 | ignore: |
2 | azure-*.yml
3 | rules:
4 | braces:
5 | level: error
6 | min-spaces-inside: 0
7 | max-spaces-inside: 1
8 | min-spaces-inside-empty: -1
9 | max-spaces-inside-empty: -1
10 | brackets:
11 | level: error
12 | min-spaces-inside: 0
13 | max-spaces-inside: 0
14 | min-spaces-inside-empty: -1
15 | max-spaces-inside-empty: -1
16 | colons:
17 | level: error
18 | max-spaces-before: 0
19 | max-spaces-after: 1
20 | commas:
21 | level: error
22 | max-spaces-before: 0
23 | min-spaces-after: 1
24 | max-spaces-after: 1
25 | comments:
26 | level: error
27 | require-starting-space: true
28 | min-spaces-from-content: 2
29 | comments-indentation:
30 | level: error
31 | document-end:
32 | level: error
33 | present: false
34 | document-start:
35 | level: error
36 | present: false
37 | empty-lines:
38 | level: error
39 | max: 1
40 | max-start: 0
41 | max-end: 1
42 | hyphens:
43 | level: error
44 | max-spaces-after: 1
45 | indentation:
46 | level: error
47 | spaces: 2
48 | indent-sequences: true
49 | check-multi-line-strings: false
50 | key-duplicates:
51 | level: error
52 | line-length: disable
53 | new-line-at-end-of-file:
54 | level: error
55 | new-lines:
56 | level: error
57 | type: unix
58 | trailing-spaces:
59 | level: error
60 | truthy:
61 | level: error
62 | allowed-values: ['true', 'false', 'on']
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jeeftor/EmojiTaco)
2 |
3 |
4 |
5 |
6 | # Emoji Taco 🌮 (for python3 - Version 2.x)
7 |
8 | [**Download the latest release**](https://github.com/jeeftor/EmojiTaco/releases)
9 |
10 |
11 | ## Python 3 Notes
12 |
13 | * Notifications aren't currently working that I'm aware of - but this doesn't really impact users at all
14 | * Code-base *might* be smaller than Python2 version
15 | * Assumes you have a `python3` executable available
16 |
17 |
18 |
19 | ### Unicode website
20 |
21 | If you run in to trouble its possible the unicode website is down. You can check [here](https://downfor.io/unicode.org). _This was an issue 9/10 April 2020_
22 |
23 |
38 | ## About
39 | This emoji workflow scrapes Unicode.org to pull down the latest set of EMOJI!!. On first run you **must** be connected to the Internet so that you can generate the initial Emoji set. As new emoji are released you should be able to re-generate to keep the plugin up to date.
40 |
41 |
47 |
48 |
49 | # To initialize the emoji set type
50 |
51 | **init emoji** this will go to `unicode.org` and scrape the current emoji list. It parses the mega chart pulling out all Emoji that exist in the apple ecosystem.
52 |
53 | # Emoji Searching
54 |
55 | Use the **e** command to search for emoji.
56 | 
57 |
58 | You can use the `-` to exclude items from a search such as:
59 |
60 | 
61 |
62 | # Emoji Count
63 | If there are more than 9 matches a dialog showing the emoji count will appear.
64 | 
65 |
66 |
67 | ### Commands
68 |
69 | * alt/option (**⌥**) - Show Unicode Values
70 | * cmd (**⌘**) - Show Python String
71 | * ctrl (**⌃**) - Show Python String *decoded*
72 | * shift (**⇧**) - Display the image in QuickLook
73 |
74 |
75 |
76 | # Non Apple-supported Emoji
77 |
78 | There are emoji that are not supported on Apple devices and/or not supported on OSX but maybe exist in a beta version of iOS.
79 |
80 | For example the Rainbow Flag emoji currently will render a pride flag on iOS but on OSX (at the time of this writing) it will render a while flag followed by a rainbow symbol 🏳️🌈️. You should still be able to use this and other emoji even if they render strangely on the current OS.
81 |
82 | # Configuration Options
83 | You can change the keyword in the settings of the workflow
84 |
85 | 
86 |
--------------------------------------------------------------------------------
/build/packageWorkflow.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright (c) 2013 deanishe@deanishe.net.
4 | #
5 | # MIT Licence. See http://opensource.org/licenses/MIT
6 | #
7 | # Created on 2013-11-01
8 | #
9 |
10 | """workflow-build [options]
11 |
12 | Build Alfred Workflows.
13 |
14 | Compile contents of to a ZIP file (with extension
15 | `.alfredworkflow`).
16 |
17 | The name of the output file is generated from the workflow name,
18 | which is extracted from the workflow's `info.plist`. If a `version`
19 | file is contained within the workflow directory, it's contents
20 | will be appended to the compiled workflow's filename.
21 |
22 | Usage:
23 | workflow-build [-v|-q|-d] [-f] [-o ] ...
24 | workflow-build (-h|--version)
25 |
26 | Options:
27 | -o, --output= Directory to save workflow(s) to.
28 | Default is current working directory.
29 | -f, --force Overwrite existing files.
30 | -h, --help Show this message and exit.
31 | -V, --version Show version number and exit.
32 | -q, --quiet Only show errors and above.
33 | -v, --verbose Show info messages and above.
34 | -d, --debug Show debug messages.
35 |
36 | """
37 |
38 |
39 | import sys
40 | import os
41 | import logging
42 | import logging.handlers
43 | import plistlib
44 | import semantic_version
45 |
46 | from subprocess import check_call, CalledProcessError
47 |
48 | from docopt import docopt
49 |
50 | __version__ = "0.2"
51 | __author__ = "deanishe@deanishe.net"
52 |
53 | DEFAULT_LOG_LEVEL = logging.WARNING
54 | LOGPATH = os.path.expanduser("~/Library/Logs/MyScripts.log")
55 | LOGSIZE = 1024 * 1024 * 5 # 5 megabytes
56 |
57 |
58 | EXCLUDE_PATTERNS = [
59 | "*.pyc",
60 | "*.log",
61 | ".DS_Store",
62 | "*.acorn",
63 | "*.swp",
64 | "*.sublime-project",
65 | "*.sublime-workflow",
66 | "*.git",
67 | "*.dist-info",
68 | "*.idea",
69 | ".idea",
70 | ".git",
71 | "./eenv/*",
72 | "./img/*.png",
73 | "/build/*",
74 | "/sketch/*",
75 | "*.csv",
76 | "*.alfredworkflow",
77 | ]
78 |
79 |
80 | class TechnicolorFormatter(logging.Formatter):
81 | """
82 | Prepend level name to any message not level logging.INFO.
83 |
84 | Also, colour!
85 |
86 | """
87 |
88 | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
89 |
90 | RESET = "\033[0m"
91 | COLOUR_BASE = "\033[1;{:d}m"
92 | BOLD = "\033[1m"
93 |
94 | LEVEL_COLOURS = {
95 | logging.DEBUG: BLUE,
96 | logging.INFO: WHITE,
97 | logging.WARNING: YELLOW,
98 | logging.ERROR: MAGENTA,
99 | logging.CRITICAL: RED,
100 | }
101 |
102 | def __init__(self, fmt=None, datefmt=None, technicolor=True):
103 | logging.Formatter.__init__(self, fmt, datefmt)
104 | self.technicolor = technicolor
105 | self._isatty = sys.stderr.isatty()
106 |
107 | def format(self, record):
108 | if record.levelno == logging.INFO:
109 | msg = logging.Formatter.format(self, record)
110 | return msg
111 | if self.technicolor and self._isatty:
112 | colour = self.LEVEL_COLOURS[record.levelno]
113 | bold = (False, True)[record.levelno > logging.INFO]
114 | levelname = self.colourise(f"{record.levelname:9s}", colour, bold)
115 | else:
116 | levelname = f"{record.levelname:9s}"
117 | return levelname + logging.Formatter.format(self, record)
118 |
119 | def colourise(self, text, colour, bold=False):
120 | colour = self.COLOUR_BASE.format(colour + 30)
121 | output = []
122 | if bold:
123 | output.append(self.BOLD)
124 | output.append(colour)
125 | output.append(text)
126 | output.append(self.RESET)
127 | return "".join(output)
128 |
129 |
130 | # logfile
131 | logfile = logging.handlers.RotatingFileHandler(LOGPATH, maxBytes=LOGSIZE, backupCount=5)
132 | formatter = logging.Formatter(
133 | "%(asctime)s %(levelname)-8s [%(name)-12s] %(message)s", datefmt="%d/%m %H:%M:%S"
134 | )
135 | logfile.setFormatter(formatter)
136 | logfile.setLevel(logging.DEBUG)
137 |
138 | # console output
139 | console = logging.StreamHandler()
140 | formatter = TechnicolorFormatter("%(message)s")
141 | console.setFormatter(formatter)
142 | console.setLevel(logging.DEBUG)
143 |
144 | log = logging.getLogger("")
145 | log.addHandler(logfile)
146 | log.addHandler(console)
147 |
148 |
149 | def safename(name):
150 | """Make name filesystem-safe."""
151 | name = name.replace("/", "-")
152 | name = name.replace(":", "-")
153 | return name
154 |
155 |
156 | def build_workflow(workflow_dir, outputdir, overwrite=False, verbose=False):
157 | """Create an .alfredworkflow file from the contents of `workflow_dir`."""
158 | curdir = os.curdir
159 | os.chdir(workflow_dir)
160 | version = None
161 | if not os.path.exists("info.plist"):
162 | log.error("info.plist not found")
163 | return False
164 |
165 | if os.path.exists("version"):
166 |
167 | # Read version
168 | with open("version") as fp:
169 | initial_version = semantic_version.Version.coerce(
170 | fp.read().strip().decode("utf-8")
171 | )
172 | fp.close()
173 | version = str(initial_version.next_patch())
174 |
175 | target = open("version", "w")
176 | target.truncate()
177 | target.write(version)
178 | target.close()
179 |
180 | if not version:
181 | with open('info.plist', 'rb') as f:
182 | pl = plistlib.load(f)
183 | initial_version = semantic_version.Version.coerce(pl["version"])
184 | version = str(initial_version.next_patch())
185 |
186 | pl["version"] = version
187 | with open('info.plist', 'wb') as f:
188 | plistlib.dump(pl, f)
189 |
190 | # plistlib.writePlist(pl, "info.plist")
191 | with open('info.plist', 'rb') as f:
192 | pl = plistlib.load(f)
193 | name = safename(pl["name"]).replace(" ", "_")
194 | zippath = os.path.join(outputdir, name)
195 |
196 | if version:
197 | zippath += "-" + version
198 | zippath += ".alfredworkflow"
199 |
200 | if os.path.exists(zippath):
201 | if overwrite:
202 | log.info("Overwriting existing workflow")
203 | os.unlink(zippath)
204 | else:
205 | log.error(f"File '{zippath}' already exists. Use -f to overwrite")
206 | return False
207 |
208 | # build workflow
209 | command = ["zip"]
210 | if not verbose:
211 | command.append("-q")
212 | command.append(zippath)
213 | for root, dirnames, filenames in os.walk("."):
214 | dirnames[:] = [d for d in dirnames if not d in [".git", ".idea"]]
215 | for filename in filenames:
216 | path = os.path.join(root, filename)
217 | command.append(path)
218 | command.append("-x")
219 | command.extend(EXCLUDE_PATTERNS)
220 | log.debug("command : {}".format(" ".join(command)))
221 | try:
222 | check_call(command)
223 | except CalledProcessError as err:
224 | log.error(f"zip returned : {err.returncode}")
225 | os.chdir(curdir)
226 | return False
227 | log.info(f"Wrote {zippath}")
228 | # Return workflow filename and actual filename
229 | print(name, os.path.basename(zippath), version)
230 | os.chdir(curdir)
231 | return True
232 |
233 |
234 | def main(args=None):
235 | """Run CLI."""
236 | args = docopt(__doc__, version=__version__)
237 |
238 | if args.get("--verbose"):
239 | log.setLevel(logging.INFO)
240 | elif args.get("--quiet"):
241 | log.setLevel(logging.ERROR)
242 | elif args.get("--debug"):
243 | log.setLevel(logging.DEBUG)
244 | else:
245 | log.setLevel(DEFAULT_LOG_LEVEL)
246 |
247 | log.debug("Set log level to %s" % logging.getLevelName(log.level))
248 |
249 | log.debug(f"args :\n{args}")
250 |
251 | # Build options
252 | outputdir = os.path.abspath(args.get("--output") or os.curdir)
253 | workflow_dirs = [os.path.abspath(p) for p in args.get("")]
254 | log.debug(f"outputdir : {outputdir}, workflow_dirs : {workflow_dirs}")
255 | errors = False
256 | verbose = False
257 | if log.level == logging.DEBUG:
258 | verbose = True
259 |
260 | # Build workflow(s)
261 | for path in workflow_dirs:
262 | ok = build_workflow(path, outputdir, args.get("--force"), verbose)
263 | if not ok:
264 | errors = True
265 | if errors:
266 | return 1
267 | return 0
268 |
269 |
270 | if __name__ == "__main__":
271 | sys.exit(main(sys.argv[1:]))
272 |
--------------------------------------------------------------------------------
/docs/catalina1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/catalina1.png
--------------------------------------------------------------------------------
/docs/catalina2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/catalina2.png
--------------------------------------------------------------------------------
/docs/complexsearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/complexsearch.png
--------------------------------------------------------------------------------
/docs/ecount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/ecount.png
--------------------------------------------------------------------------------
/docs/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/settings.png
--------------------------------------------------------------------------------
/docs/taco.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/taco.png
--------------------------------------------------------------------------------
/docs/tones.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/docs/tones.png
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/icon.png
--------------------------------------------------------------------------------
/images/Checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/images/Checkmark.png
--------------------------------------------------------------------------------
/img/.gitignore:
--------------------------------------------------------------------------------
1 | *.png
2 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | python_version = 3.9
3 | show_error_codes = true
4 | follow_imports = silent
5 | ignore_missing_imports = true
6 | strict_equality = true
7 | warn_incomplete_stub = true
8 | warn_redundant_casts = true
9 | warn_unused_configs = true
10 | warn_unused_ignores = true
11 | check_untyped_defs = true
12 | disallow_incomplete_defs = true
13 | disallow_subclassing_any = true
14 | disallow_untyped_calls = false
15 | disallow_untyped_decorators = true
16 | disallow_untyped_defs = true
17 | no_implicit_optional = true
18 | warn_return_any = true
19 | warn_unreachable = true
20 | ;plugins = pydantic.mypy
21 | ;
22 | ;[pydantic-mypy]
23 | ;init_forbid_extra = True
24 | ;init_typed = True
25 | ;warn_required_dynamic_aliases = True
26 | ;warn_untyped_fields = True
27 |
--------------------------------------------------------------------------------
/na.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/na.png
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | python_files = test_emoji.py
3 |
--------------------------------------------------------------------------------
/release.py:
--------------------------------------------------------------------------------
1 | """Make a github release.
2 |
3 | Requires that you have a github access token as specified below in your home directory.
4 | """
5 | import json
6 | import os
7 | import sys
8 | import urllib
9 | import urllib.request
10 | from os.path import expanduser
11 | from subprocess import call
12 |
13 | GITHUB_USER = "jeeftor"
14 | GITHUB_REPO = "EmojiTaco"
15 |
16 | # Read github access token out of ~/.github_access_token
17 | home = expanduser("~")
18 | token_file = home + "/.github_access_token"
19 | GITHUB_ACCESS_TOKEN = open(token_file).read()
20 |
21 |
22 | def pp_json(json_thing, sort=True, indents=4):
23 | """Pretty print json."""
24 | if type(json_thing) is str:
25 | print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents))
26 | else:
27 | print(json.dumps(json_thing, sort_keys=sort, indent=indents))
28 | return None
29 |
30 |
31 | print(sys.argv)
32 |
33 | version = sys.argv[1]
34 | file_to_upload = sys.argv[2]
35 |
36 | github_token = str(GITHUB_ACCESS_TOKEN).rstrip()
37 |
38 |
39 | # curl -i -H 'Authorization: token 5b8e3a4d92993282d2a8f20b5fe4910edc9f82dd' https://api.github.com/user/repos
40 |
41 | request_headers = {
42 | "Content-Type": "application/json",
43 | "Authorization": "token %s" % github_token,
44 | }
45 |
46 |
47 | print(request_headers)
48 |
49 | # Release INFO
50 | payload = {
51 | "tag_name": f"v{version}",
52 | "target_commitish": "master",
53 | "name": f"Release {version}",
54 | "body": "Auto Generated Release notes by the `release.py` script",
55 | "draft": True,
56 | "prerelease": False,
57 | }
58 |
59 |
60 | # Make a new release
61 | data = json.dumps(payload)
62 | clen = len(data)
63 | request_headers["Content-Length"] = clen
64 | url = f"https://api.github.com/repos/{GITHUB_USER}/{GITHUB_REPO}/releases"
65 | # url = 'https://api.github.com/repos/jeeftor/EmojiTaco/releases'
66 | print(url)
67 | req = urllib.request.Request(url, data=data, headers=request_headers)
68 | f = urllib.request.urlopen(req)
69 | response = f.read()
70 | f.close()
71 | pp_json(response)
72 | json = json.loads(response)
73 |
74 | # Parse out the upload URL
75 | url = json["upload_url"].split("{")[0]
76 |
77 | # Do more parsing
78 | upload_path = "build/" + file_to_upload
79 | upload_data_len = length = os.path.getsize(upload_path)
80 | upload_data = open(upload_path, "rb")
81 | url = url + f"?name={file_to_upload}"
82 |
83 | # Upload the new workflow file
84 | request = urllib.request.Request(url, data=upload_data, headers=request_headers)
85 | request.add_header("Cache-Control", "no-cache")
86 | request.add_header("Content-Length", "%d" % upload_data_len)
87 | res = urllib.request.urlopen(request).read().strip()
88 |
89 |
90 | # Launch web browser to the Draf release
91 |
92 | call(["open", json["html_url"]])
93 | exit()
94 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | OUTPUT=$(python build/packageWorkflow.py . -o build/)
2 | #OUTPUT="Airport_Search Airport_Search-0.6.22.alfredworkflow 0.6.22"
3 |
4 | TEXT_ARRRAY=($OUTPUT)
5 |
6 |
7 | echo ${TEXT_ARRRAY[0]}
8 | echo ${TEXT_ARRRAY[1]}
9 | echo ${TEXT_ARRRAY[2]}
10 |
11 | echo " ________ __ _______ __ "
12 | echo " / ____/ /_ ____ _____ ____ ____ ____/ / / ____(_) /__ _____ "
13 | echo " / / / __ \/ __ \`/ __ \/ __ \`/ _ \/ __ / / /_ / / / _ \/ ___/ "
14 | echo " / /___/ / / / /_/ / / / / /_/ / __/ /_/ / / __/ / / / __(__ ) "
15 | echo " \____/_/ /_/\__,_/_/ /_/\__, /\___/\__,_/ /_/ /_/_/\___/____/ "
16 | echo " /____/ "
17 | echo ""
18 |
19 | git status -s
20 |
21 | echo " Commit and make a release?"
22 |
23 | read -p "Are you sure? " -n 1 -r
24 | echo # (optional) move to a new line
25 | if [[ $REPLY =~ ^[Yy]$ ]]
26 | then
27 | # do dangerous stuff
28 |
29 | git add -u
30 | git commit -m "Prepping release ${TEXT_ARRRAY[2]}"
31 | git push
32 |
33 | echo "Running..."
34 | echo python3 release.py ${TEXT_ARRRAY[2]} ${TEXT_ARRRAY[1]}
35 | echo ""
36 | python3 release.py ${TEXT_ARRRAY[2]} ${TEXT_ARRRAY[1]}
37 | fi
38 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # pytest==3.0.5
2 | # lxml==4.6.3
3 | # beautifulsoup4==4.5.3
4 | # pytest-catchlog
5 |
6 | bs4
7 | pickle
8 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 | """Package def file."""
2 |
--------------------------------------------------------------------------------
/src/downloadDataFiles.py:
--------------------------------------------------------------------------------
1 | """File Download wrapper."""
2 | from __future__ import annotations
3 |
4 | import os
5 | import sys
6 |
7 | from workflow import Workflow3
8 | from workflow.background import is_running, run_in_background
9 |
10 |
11 | def string_from_percent(pct: float) -> str | None:
12 | """Return a fancy string to show in the workflow from the count item."""
13 | # blue = "\\U0001F535\\U0000FE0F"
14 | white = "\U000026AA\U0000FE0F"
15 | black = "\U000026AB\U0000FE0F"
16 |
17 | ret: str = (
18 | white
19 | + white
20 | + white
21 | + white
22 | + white
23 | + white
24 | + white
25 | + white
26 | + white
27 | + white
28 | + white
29 | + black
30 | + black
31 | + black
32 | + black
33 | + black
34 | + black
35 | + black
36 | + black
37 | + black
38 | + black
39 | )
40 |
41 | mod = 2 * (10 - (int(pct / 10) % 10))
42 |
43 | return ret[mod:][0:20]
44 |
45 |
46 | def build_wf_entry(wf: Workflow3) -> None:
47 | """Build workflow entry."""
48 | if is_running("emoji_init"):
49 | """Update status."""
50 | phase: str = wf.stored_data("phase")
51 | log.info("--dbg:\tIs Running with phase [%s]", phase)
52 | if phase != "done":
53 | wf.rerun = 0.5
54 | if phase == "downloading":
55 | pct = None
56 | while pct is None:
57 | try:
58 | pct = wf.stored_data("download_percent")
59 | except:
60 | pass
61 |
62 | progress = wf.stored_data("download_progress")
63 | file = wf.stored_data("download_file")
64 |
65 | title = f"Downloading {file} [{progress}]"
66 | subtitle = string_from_percent(pct) + " " + str(pct) + "%"
67 | wf.add_item(title, subtitle=subtitle)
68 |
69 | if phase == "processing":
70 |
71 | try:
72 | emoji_count = wf.stored_data("emoji_count")
73 | subtitle = f"Parsed {emoji_count} emoji"
74 | except Exception:
75 | subtitle = "Parsed ... emoji"
76 | pass
77 |
78 | title = "Parsing Emoji"
79 | wf.add_item(title, subtitle=subtitle)
80 |
81 | else:
82 | """Last case"""
83 | wf.add_item(
84 | "Complete",
85 | subtitle="Emoji searching is now ready to use",
86 | icon="images/Checkmark.png",
87 | )
88 |
89 |
90 | def main(wf: Workflow3) -> None:
91 | """Define Main function."""
92 | log.debug("--dbg:\tmain Function")
93 |
94 | try:
95 | count = int(os.environ["count"])
96 | first_time = False
97 | except KeyError:
98 | count = 0
99 | first_time = True
100 |
101 | if is_running("emoji_init"):
102 | first_time = False
103 |
104 | if first_time:
105 | wf.rerun = 0.5
106 | wf.store_data("download_percent", 0)
107 | wf.store_data("phase", "downloading")
108 | wf.store_data("emoji_count", 0)
109 | wf.add_item("Starting background process")
110 | run_in_background(
111 | "emoji_init", ["/usr/bin/python3", wf.workflowfile("src/bg_downloader.py")]
112 | )
113 | log.debug("Launching background task")
114 | else:
115 | build_wf_entry(wf)
116 |
117 | wf.setvar("count", count)
118 |
119 | wf.send_feedback()
120 |
121 |
122 | if __name__ == "__main__":
123 | wf = Workflow3()
124 | log = wf.logger
125 | log.info("Emoji Init Started")
126 | sys.exit(wf.run(main))
127 |
--------------------------------------------------------------------------------
/src/esearch.py:
--------------------------------------------------------------------------------
1 | """Emoji Search."""
2 | from __future__ import annotations
3 |
4 | import os
5 | import sys
6 | from urllib.parse import quote
7 |
8 | from workflow import Workflow3
9 |
10 | base_path = os.path.dirname(os.path.realpath(__file__))
11 |
12 | name_match = []
13 | keyword_match = []
14 |
15 |
16 | def main(wf: Workflow3) -> None:
17 | """Define Main function."""
18 | if os.path.isfile(f"{base_path}/../emoji.tab"):
19 | pass
20 | else:
21 | wf.add_item(
22 | 'Emoji not initialized. Hit enter or type "init emoji".',
23 | "The init script requires you to be connected to the internet",
24 | arg="init emoji",
25 | valid=True,
26 | )
27 | log.info("Not Initialized")
28 | wf.send_feedback()
29 | exit()
30 |
31 | try:
32 | query: str | list[str] = [str(arg) for arg in wf.args[0:]]
33 | log.info("Query=%s", query)
34 | except:
35 | query = ""
36 |
37 | with open(f"{base_path}/../emoji.tab") as f:
38 | for idx, line in enumerate(f, 1):
39 | split_list = line.strip().split("\t")
40 | if len(split_list) != 6:
41 | raise ValueError(
42 | "Line {}: '{}' has {} spaces, expected 1".format(
43 | idx, line.rstrip(), len(split_list) - 1
44 | )
45 | )
46 | else:
47 | img, name, code, raw_code, code_string, keywords = split_list
48 |
49 | in_keywords = True
50 | in_name = True
51 |
52 | for term in query:
53 |
54 | if term.startswith("-"):
55 | in_name &= term[1:] not in name.lower()
56 | in_keywords &= term[1:] not in keywords.lower()
57 | else:
58 | in_name &= term in name.lower()
59 | in_keywords &= term in keywords.lower()
60 |
61 | if in_name:
62 | name_match.append([img, name, raw_code, keywords])
63 | elif in_keywords:
64 | keyword_match.append([img, name, raw_code, keywords])
65 |
66 | imgbase = "file://" + quote(base_path) + "/../img/"
67 |
68 | if len(name_match + keyword_match) == 0:
69 | sad_face = "\U0001F622\U0000FE0F"
70 | wf.add_item(
71 | sad_face + " No Emoji found",
72 | "Please try again",
73 | icon="icon.png",
74 | )
75 | else:
76 | if len(name_match + keyword_match) > 9:
77 | wf.add_item(
78 | str(len(name_match + keyword_match)) + " matches found",
79 | "To remove results use - in front of a term",
80 | icon="icon.png",
81 | valid=False,
82 | )
83 | for array in name_match + keyword_match:
84 | img, title, raw_code, subtitle = array
85 |
86 | ql = imgbase + img
87 |
88 | item = wf.add_item(
89 | f"{title}",
90 | subtitle=subtitle.replace(" ", " "),
91 | icon="img/" + img,
92 | quicklookurl=ql,
93 | arg=f"{raw_code.encode('utf-8').decode('unicode_escape')}", # This is not raw code any more
94 | valid=True,
95 | )
96 |
97 | p_string = raw_code
98 | pd_string = 'u"' + raw_code + '"'
99 |
100 | log.info(p_string)
101 | log.info(pd_string)
102 |
103 | item.add_modifier(
104 | "cmd", subtitle="Python String [" + p_string + "]", arg=p_string, valid=None
105 | )
106 | item.add_modifier(
107 | "alt",
108 | subtitle="Unicode Value [" + code_string + "]",
109 | arg=code_string,
110 | valid=None,
111 | )
112 | item.add_modifier(
113 | "ctrl",
114 | subtitle="Python String (decoded) [" + pd_string + "]",
115 | arg=pd_string,
116 | valid=None,
117 | )
118 |
119 | wf.send_feedback()
120 |
121 |
122 | if __name__ == "__main__":
123 | # wf = Workflow3(update_settings={"github_slug": "jeeftor/EmojiTaco", "frequency": 7})
124 | wf = Workflow3()
125 | log = wf.logger
126 | sys.exit(wf.run(main))
127 |
--------------------------------------------------------------------------------
/src/libs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeeftor/EmojiTaco/a58096cf1af2977efe6dbab8bb8fd7b58c6b922c/src/libs/__init__.py
--------------------------------------------------------------------------------
/src/libs/bs4/diagnose.py:
--------------------------------------------------------------------------------
1 | """Diagnostic functions, mainly for use when doing tech support."""
2 |
3 | # Use of this source code is governed by the MIT license.
4 | __license__ = "MIT"
5 |
6 | import cProfile
7 | from io import StringIO
8 | from html.parser import HTMLParser
9 | import bs4
10 | from bs4 import BeautifulSoup, __version__
11 | from bs4.builder import builder_registry
12 |
13 | import os
14 | import pstats
15 | import random
16 | import tempfile
17 | import time
18 | import traceback
19 | import sys
20 | import cProfile
21 |
22 | def diagnose(data):
23 | """Diagnostic suite for isolating common problems.
24 |
25 | :param data: A string containing markup that needs to be explained.
26 | :return: None; diagnostics are printed to standard output.
27 | """
28 | print(("Diagnostic running on Beautiful Soup %s" % __version__))
29 | print(("Python version %s" % sys.version))
30 |
31 | basic_parsers = ["html.parser", "html5lib", "lxml"]
32 | for name in basic_parsers:
33 | for builder in builder_registry.builders:
34 | if name in builder.features:
35 | break
36 | else:
37 | basic_parsers.remove(name)
38 | print((
39 | "I noticed that %s is not installed. Installing it may help." %
40 | name))
41 |
42 | if 'lxml' in basic_parsers:
43 | basic_parsers.append("lxml-xml")
44 | try:
45 | from lxml import etree
46 | print(("Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))))
47 | except ImportError as e:
48 | print(
49 | "lxml is not installed or couldn't be imported.")
50 |
51 |
52 | if 'html5lib' in basic_parsers:
53 | try:
54 | import html5lib
55 | print(("Found html5lib version %s" % html5lib.__version__))
56 | except ImportError as e:
57 | print(
58 | "html5lib is not installed or couldn't be imported.")
59 |
60 | if hasattr(data, 'read'):
61 | data = data.read()
62 | elif data.startswith("http:") or data.startswith("https:"):
63 | print(('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data))
64 | print("You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup.")
65 | return
66 | else:
67 | try:
68 | if os.path.exists(data):
69 | print(('"%s" looks like a filename. Reading data from the file.' % data))
70 | with open(data) as fp:
71 | data = fp.read()
72 | except ValueError:
73 | # This can happen on some platforms when the 'filename' is
74 | # too long. Assume it's data and not a filename.
75 | pass
76 | print("")
77 |
78 | for parser in basic_parsers:
79 | print(("Trying to parse your markup with %s" % parser))
80 | success = False
81 | try:
82 | soup = BeautifulSoup(data, features=parser)
83 | success = True
84 | except Exception as e:
85 | print(("%s could not parse the markup." % parser))
86 | traceback.print_exc()
87 | if success:
88 | print(("Here's what %s did with the markup:" % parser))
89 | print((soup.prettify()))
90 |
91 | print(("-" * 80))
92 |
93 | def lxml_trace(data, html=True, **kwargs):
94 | """Print out the lxml events that occur during parsing.
95 |
96 | This lets you see how lxml parses a document when no Beautiful
97 | Soup code is running. You can use this to determine whether
98 | an lxml-specific problem is in Beautiful Soup's lxml tree builders
99 | or in lxml itself.
100 |
101 | :param data: Some markup.
102 | :param html: If True, markup will be parsed with lxml's HTML parser.
103 | if False, lxml's XML parser will be used.
104 | """
105 | from lxml import etree
106 | for event, element in etree.iterparse(StringIO(data), html=html, **kwargs):
107 | print(("%s, %4s, %s" % (event, element.tag, element.text)))
108 |
109 | class AnnouncingParser(HTMLParser):
110 | """Subclass of HTMLParser that announces parse events, without doing
111 | anything else.
112 |
113 | You can use this to get a picture of how html.parser sees a given
114 | document. The easiest way to do this is to call `htmlparser_trace`.
115 | """
116 |
117 | def _p(self, s):
118 | print(s)
119 |
120 | def handle_starttag(self, name, attrs):
121 | self._p("%s START" % name)
122 |
123 | def handle_endtag(self, name):
124 | self._p("%s END" % name)
125 |
126 | def handle_data(self, data):
127 | self._p("%s DATA" % data)
128 |
129 | def handle_charref(self, name):
130 | self._p("%s CHARREF" % name)
131 |
132 | def handle_entityref(self, name):
133 | self._p("%s ENTITYREF" % name)
134 |
135 | def handle_comment(self, data):
136 | self._p("%s COMMENT" % data)
137 |
138 | def handle_decl(self, data):
139 | self._p("%s DECL" % data)
140 |
141 | def unknown_decl(self, data):
142 | self._p("%s UNKNOWN-DECL" % data)
143 |
144 | def handle_pi(self, data):
145 | self._p("%s PI" % data)
146 |
147 | def htmlparser_trace(data):
148 | """Print out the HTMLParser events that occur during parsing.
149 |
150 | This lets you see how HTMLParser parses a document when no
151 | Beautiful Soup code is running.
152 |
153 | :param data: Some markup.
154 | """
155 | parser = AnnouncingParser()
156 | parser.feed(data)
157 |
158 | _vowels = "aeiou"
159 | _consonants = "bcdfghjklmnpqrstvwxyz"
160 |
161 | def rword(length=5):
162 | "Generate a random word-like string."
163 | s = ''
164 | for i in range(length):
165 | if i % 2 == 0:
166 | t = _consonants
167 | else:
168 | t = _vowels
169 | s += random.choice(t)
170 | return s
171 |
172 | def rsentence(length=4):
173 | "Generate a random sentence-like string."
174 | return " ".join(rword(random.randint(4,9)) for i in range(length))
175 |
176 | def rdoc(num_elements=1000):
177 | """Randomly generate an invalid HTML document."""
178 | tag_names = ['p', 'div', 'span', 'i', 'b', 'script', 'table']
179 | elements = []
180 | for i in range(num_elements):
181 | choice = random.randint(0,3)
182 | if choice == 0:
183 | # New tag.
184 | tag_name = random.choice(tag_names)
185 | elements.append("<%s>" % tag_name)
186 | elif choice == 1:
187 | elements.append(rsentence(random.randint(1,4)))
188 | elif choice == 2:
189 | # Close a tag.
190 | tag_name = random.choice(tag_names)
191 | elements.append("%s>" % tag_name)
192 | return "" + "\n".join(elements) + ""
193 |
194 | def benchmark_parsers(num_elements=100000):
195 | """Very basic head-to-head performance benchmark."""
196 | print(("Comparative parser benchmark on Beautiful Soup %s" % __version__))
197 | data = rdoc(num_elements)
198 | print(("Generated a large invalid HTML document (%d bytes)." % len(data)))
199 |
200 | for parser in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]:
201 | success = False
202 | try:
203 | a = time.time()
204 | soup = BeautifulSoup(data, parser)
205 | b = time.time()
206 | success = True
207 | except Exception as e:
208 | print(("%s could not parse the markup." % parser))
209 | traceback.print_exc()
210 | if success:
211 | print(("BS4+%s parsed the markup in %.2fs." % (parser, b-a)))
212 |
213 | from lxml import etree
214 | a = time.time()
215 | etree.HTML(data)
216 | b = time.time()
217 | print(("Raw lxml parsed the markup in %.2fs." % (b-a)))
218 |
219 | import html5lib
220 | parser = html5lib.HTMLParser()
221 | a = time.time()
222 | parser.parse(data)
223 | b = time.time()
224 | print(("Raw html5lib parsed the markup in %.2fs." % (b-a)))
225 |
226 | def profile(num_elements=100000, parser="lxml"):
227 | """Use Python's profiler on a randomly generated document."""
228 | filehandle = tempfile.NamedTemporaryFile()
229 | filename = filehandle.name
230 |
231 | data = rdoc(num_elements)
232 | vars = dict(bs4=bs4, data=data, parser=parser)
233 | cProfile.runctx('bs4.BeautifulSoup(data, parser)' , vars, vars, filename)
234 |
235 | stats = pstats.Stats(filename)
236 | # stats.strip_dirs()
237 | stats.sort_stats("cumulative")
238 | stats.print_stats('_html5lib|bs4', 50)
239 |
240 | # If this file is run as a script, standard input is diagnosed.
241 | if __name__ == '__main__':
242 | diagnose(sys.stdin.read())
243 |
--------------------------------------------------------------------------------
/src/libs/bs4/formatter.py:
--------------------------------------------------------------------------------
1 | from bs4.dammit import EntitySubstitution
2 |
3 | class Formatter(EntitySubstitution):
4 | """Describes a strategy to use when outputting a parse tree to a string.
5 |
6 | Some parts of this strategy come from the distinction between
7 | HTML4, HTML5, and XML. Others are configurable by the user.
8 |
9 | Formatters are passed in as the `formatter` argument to methods
10 | like `PageElement.encode`. Most people won't need to think about
11 | formatters, and most people who need to think about them can pass
12 | in one of these predefined strings as `formatter` rather than
13 | making a new Formatter object:
14 |
15 | For HTML documents:
16 | * 'html' - HTML entity substitution for generic HTML documents. (default)
17 | * 'html5' - HTML entity substitution for HTML5 documents, as
18 | well as some optimizations in the way tags are rendered.
19 | * 'minimal' - Only make the substitutions necessary to guarantee
20 | valid HTML.
21 | * None - Do not perform any substitution. This will be faster
22 | but may result in invalid markup.
23 |
24 | For XML documents:
25 | * 'html' - Entity substitution for XHTML documents.
26 | * 'minimal' - Only make the substitutions necessary to guarantee
27 | valid XML. (default)
28 | * None - Do not perform any substitution. This will be faster
29 | but may result in invalid markup.
30 | """
31 | # Registries of XML and HTML formatters.
32 | XML_FORMATTERS = {}
33 | HTML_FORMATTERS = {}
34 |
35 | HTML = 'html'
36 | XML = 'xml'
37 |
38 | HTML_DEFAULTS = dict(
39 | cdata_containing_tags=set(["script", "style"]),
40 | )
41 |
42 | def _default(self, language, value, kwarg):
43 | if value is not None:
44 | return value
45 | if language == self.XML:
46 | return set()
47 | return self.HTML_DEFAULTS[kwarg]
48 |
49 | def __init__(
50 | self, language=None, entity_substitution=None,
51 | void_element_close_prefix='/', cdata_containing_tags=None,
52 | empty_attributes_are_booleans=False,
53 | ):
54 | """Constructor.
55 |
56 | :param language: This should be Formatter.XML if you are formatting
57 | XML markup and Formatter.HTML if you are formatting HTML markup.
58 |
59 | :param entity_substitution: A function to call to replace special
60 | characters with XML/HTML entities. For examples, see
61 | bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
62 | :param void_element_close_prefix: By default, void elements
63 | are represented as (XML rules) rather than
64 | (HTML rules). To get , pass in the empty string.
65 | :param cdata_containing_tags: The list of tags that are defined
66 | as containing CDATA in this dialect. For example, in HTML,
67 |