├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README-old.md ├── README.md ├── async_eel ├── __init__.py ├── __main__.py ├── browsers.py ├── chrome.py ├── edge.py ├── eel.js ├── electron.py └── utils.py ├── examples ├── 01 - hello_world-Edge │ └── hello.py ├── 01 - hello_world │ ├── hello.py │ └── web │ │ ├── favicon.ico │ │ └── hello.html ├── 02 - callbacks │ ├── callbacks.py │ └── web │ │ ├── callbacks.html │ │ └── favicon.ico ├── 03 - sync_callbacks │ ├── sync_callbacks.py │ └── web │ │ ├── favicon.ico │ │ └── sync_callbacks.html ├── 04 - file_access │ ├── Screenshot.png │ ├── file_access.py │ └── web │ │ ├── favicon.ico │ │ └── file_access.html ├── 05 - input │ ├── script.py │ └── web │ │ ├── favicon.ico │ │ └── main.html ├── 06 - jinja_templates │ ├── hello.py │ └── web │ │ ├── favicon.ico │ │ └── templates │ │ ├── base.html │ │ ├── hello.html │ │ └── page2.html ├── 07 - CreateReactApp │ ├── .gitignore │ ├── Demo.png │ ├── README.md │ ├── eel_CRA.py │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ └── serviceWorker.ts │ └── tsconfig.json ├── 08 - Eelectron-quick-start │ ├── .gitignore │ ├── hello.py │ ├── main.js │ ├── package-lock.json │ ├── package.json │ └── web │ │ ├── favicon.ico │ │ └── hello.html └── 08 - disable_cache │ ├── disable_cache.py │ └── web │ ├── disable_cache.html │ ├── dont_cache_me.js │ └── favicon.ico ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | dist 3 | build 4 | Eel.egg-info 5 | .tmp 6 | .DS_Store 7 | *.pyc 8 | *.swp 9 | venv/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | python: 4 | - 2.7 5 | - 3.6 6 | matrix: 7 | allow_failures: 8 | - python: 2.7 9 | install: 10 | #- pip install -r requirements.txt 11 | - pip install flake8 # pytest # add another testing frameworks later 12 | before_script: 13 | # stop the build if there are Python syntax errors or undefined names 14 | - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics 15 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 16 | - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 17 | script: 18 | - true # pytest --capture=sys # add other tests here 19 | notifications: 20 | on_success: change 21 | on_failure: change # `always` will be the setting once code changes slow down 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ### v0.10.4 4 | * Fix PyPi project description. 5 | 6 | ### v0.10.3 7 | * Fix a bug that prevented using Eel without Jinja templating. 8 | 9 | ### v0.10.2 10 | * Only render templates from within the declared jinja template directory. 11 | 12 | ### v0.10.1 13 | * Avoid name collisions when using Electron, so jQuery etc work normally 14 | 15 | ## v0.10.0 16 | * Corrective version bump after new feature included in 0.9.13 17 | * Fix a bug with example 06 for Jinja templating; the `templates` kwarg to `eel.start` takes a filepath, not a bool. 18 | 19 | ### v0.9.13 20 | * Add support for Jinja templating. 21 | 22 | ### Earlier 23 | * No changelog notes for earlier versions. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris Knott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md -------------------------------------------------------------------------------- /README-old.md: -------------------------------------------------------------------------------- 1 | # Eel 2 | 3 | Eel is a little Python library for making simple Electron-like offline HTML/JS GUI apps, with full access to Python capabilities and libraries. 4 | 5 | > **Eel hosts a local webserver, then lets you annotate functions in Python so that they can be called from Javascript, and vice versa.** 6 | 7 | Eel is designed to take the hassle out of writing short and simple GUI applications. If you are familiar with Python and web development, probably just jump to [this example](https://github.com/ChrisKnott/Eel/tree/master/examples/04%20-%20file_access) which picks random file names out of the given folder (something that is impossible from a browser). 8 | 9 |

10 | 11 | 12 | 13 | - [Eel](#eel) 14 | - [Intro](#intro) 15 | - [Install](#install) 16 | - [Usage](#usage) 17 | - [Directory Structure](#directory-structure) 18 | - [Starting the app](#starting-the-app) 19 | - [App options](#app-options) 20 | - [Chrome/Chromium flags](#chromechromium-flags) 21 | - [Exposing functions](#exposing-functions) 22 | - [Eello, World!](#eello-world) 23 | - [Return values](#return-values) 24 | - [Callbacks](#callbacks) 25 | - [Synchronous returns](#synchronous-returns) 26 | - [Asynchronous Python](#asynchronous-python) 27 | - [Building distributable binary with PyInstaller](#building-distributable-binary-with-pyinstaller) 28 | - [Microsoft Edge](#microsoft-edge) 29 | 30 | 31 | 32 | ## Intro 33 | 34 | There are several options for making GUI apps in Python, but if you want to use HTML/JS (in order to use jQueryUI or Bootstrap, for example) then you generally have to write a lot of boilerplate code to communicate from the Client (Javascript) side to the Server (Python) side. 35 | 36 | The closest Python equivalent to Electron (to my knowledge) is [cefpython](https://github.com/cztomczak/cefpython). It is a bit heavy weight for what I wanted. 37 | 38 | Eel is not as fully-fledged as Electron or cefpython - it is probably not suitable for making full blown applications like Atom - but it is very suitable for making the GUI equivalent of little utility scripts that you use internally in your team. 39 | 40 | For some reason many of the best-in-class number crunching and maths libraries are in Python (Tensorflow, Numpy, Scipy etc) but many of the best visualization libraries are in Javascript (D3, THREE.js etc). Hopefully Eel makes it easy to combine these into simple utility apps for assisting your development. 41 | 42 | ## Install 43 | 44 | Install from pypi with `pip`: 45 | 46 | ```shell 47 | pip install async-eel 48 | ``` 49 | 50 | ## Usage 51 | 52 | ### Directory Structure 53 | 54 | An Eel application will be split into a frontend consisting of various web-technology files (.html, .js, .css) and a backend consisting of various Python scripts. 55 | 56 | All the frontend files should be put in a single directory (they can be further divided into folders inside this if necessary). 57 | 58 | ``` 59 | my_python_script.py <-- Python scripts 60 | other_python_module.py 61 | static_web_folder/ <-- Web folder 62 | main_page.html 63 | css/ 64 | style.css 65 | img/ 66 | logo.png 67 | ``` 68 | 69 | ### Starting the app 70 | 71 | Suppose you put all the frontend files in a directory called `web`, including your start page `main.html`, then the app is started like this; 72 | 73 | ```python 74 | import eel 75 | eel.init('web') 76 | eel.start('main.html') 77 | ``` 78 | 79 | This will start a webserver on the default settings (http://localhost:8000) and open a browser to http://localhost:8000/main.html. 80 | 81 | If Chrome or Chromium is installed then by default it will open in that in App Mode (with the `--app` cmdline flag), regardless of what the OS's default browser is set to (it is possible to override this behaviour). 82 | 83 | ### App options 84 | 85 | Additional options can be passed to `eel.start()` as keyword arguments. 86 | 87 | Some of the options include the mode the app is in (e.g. 'chrome'), the port the app runs on, the host name of the app, and adding additional command line flags. 88 | 89 | As of Eel 1.0.0, the following options are available to `start()`: 90 | - **mode**, a string specifying what browser to use (e.g. `'chrome'`, `'electron'`, `'edge'`, `'custom'`). Can also be `None` or `False` to not open a window. *Default: `'chrome'`* 91 | - **host**, a string specifying what hostname to use for the Bottle server. *Default: `'localhost'`)* 92 | - **port**, an int specifying what port to use for the Bottle server. Use `0` for port to be picked automatically. *Default: `8000`*. 93 | - **block**, a bool saying whether or not the call to `start()` should block the calling thread. *Default: `True`* 94 | - **jinja_templates**, a string specifying a folder to use for Jinja2 templates, e.g. `my_templates`. *Default: `None`* 95 | - **cmdline_args**, a list of strings to pass to the command to start the browser. For example, we might add extra flags for Chrome; ```eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])```. *Default: `[]`* 96 | - **size**, a tuple of ints specifying the (width, height) of the main window in pixels *Default: `None`* 97 | - **position**, a tuple of ints specifying the (left, top) of the main window in pixels *Default: `None`* 98 | - **geometry**, a dictionary specifying the size and position for all windows. The keys should be the relative path of the page, and the values should be a dictionary of the form `{'size': (200, 100), 'position': (300, 50)}`. *Default: {}* 99 | - **close_callback**, a lambda or function that is called when a websocket to a window closes (i.e. when the user closes the window). It should take two arguments; a string which is the relative path of the page that just closed, and a list of other websockets that are still open. *Default: `None`* 100 | 101 | 102 | 103 | ### Exposing functions 104 | 105 | In addition to the files in the frontend folder, a Javascript library will be served at `/eel.js`. You should include this in any pages: 106 | 107 | ```html 108 | 109 | ``` 110 | 111 | Including this library creates an `eel` object which can be used to communicate with the Python side. 112 | 113 | Any functions in the Python code which are decorated with `@eel.expose` like this... 114 | 115 | ```python 116 | @eel.expose 117 | def my_python_function(a, b): 118 | print(a, b, a + b) 119 | ``` 120 | 121 | ...will appear as methods on the `eel` object on the Javascript side, like this... 122 | 123 | ```javascript 124 | console.log("Calling Python..."); 125 | eel.my_python_function(1, 2); // This calls the Python function that was decorated 126 | ``` 127 | 128 | Similarly, any Javascript functions which are exposed like this... 129 | 130 | ```javascript 131 | eel.expose(my_javascript_function); 132 | function my_javascript_function(a, b, c, d) { 133 | if (a < b) { 134 | console.log(c * d); 135 | } 136 | } 137 | ``` 138 | 139 | can be called from the Python side like this... 140 | 141 | ```python 142 | print('Calling Javascript...') 143 | eel.my_javascript_function(1, 2, 3, 4) # This calls the Javascript function 144 | ``` 145 | 146 | When passing complex objects as arguments, bear in mind that internally they are converted to JSON and sent down a websocket (a process that potentially loses information). 147 | 148 | ### Eello, World! 149 | 150 | > See full example in: [examples/01 - hello_world](https://github.com/ChrisKnott/Eel/tree/master/examples/01%20-%20hello_world) 151 | 152 | Putting this together into a **Hello, World!** example, we have a short HTML page, `web/hello.html`: 153 | 154 | ```html 155 | 156 | 157 | 158 | Hello, World! 159 | 160 | 161 | 162 | 171 | 172 | 173 | 174 | Hello, World! 175 | 176 | 177 | ``` 178 | 179 | and a short Python script `hello.py`: 180 | 181 | ```python 182 | import eel 183 | 184 | # Set web files folder and optionally specify which file types to check for eel.expose() 185 | # *Default allowed_extensions are: ['.js', '.html', '.txt', '.htm', '.xhtml'] 186 | eel.init('web', allowed_extensions=['.js', '.html']) 187 | 188 | @eel.expose # Expose this function to Javascript 189 | def say_hello_py(x): 190 | print('Hello from %s' % x) 191 | 192 | say_hello_py('Python World!') 193 | eel.say_hello_js('Python World!') # Call a Javascript function 194 | 195 | eel.start('hello.html') # Start (this blocks and enters loop) 196 | ``` 197 | 198 | If we run the Python script (`python hello.py`), then a browser window will open displaying `hello.html`, and we will see... 199 | 200 | ``` 201 | Hello from Python World! 202 | Hello from Javascript World! 203 | ``` 204 | 205 | ...in the terminal, and... 206 | 207 | ``` 208 | Hello from Javascript World! 209 | Hello from Python World! 210 | ``` 211 | 212 | ...in the browser console (press F12 to open). 213 | 214 | You will notice that in the Python code, the Javascript function is called before the browser window is even started - any early calls like this are queued up and then sent once the websocket has been established. 215 | 216 | ### Return values 217 | 218 | While we want to think of our code as comprising a single application, the Python interpreter and the browser window run in separate processes. This can make communicating back and forth between them a bit of a mess, especially if we always had to explicitly _send_ values from one side to the other. 219 | 220 | Eel supports two ways of retrieving _return values_ from the other side of the app, which helps keep the code concise. 221 | 222 | #### Callbacks 223 | 224 | When you call an exposed function, you can immediately pass a callback function afterwards. This callback will automatically be called asynchrounously with the return value when the function has finished executing on the other side. 225 | 226 | For example, if we have the following function defined and exposed in Javascript: 227 | 228 | ```javascript 229 | eel.expose(js_random); 230 | function js_random() { 231 | return Math.random(); 232 | } 233 | ``` 234 | 235 | Then in Python we can retrieve random values from the Javascript side like so: 236 | 237 | ```python 238 | def print_num(n): 239 | print('Got this from Javascript:', n) 240 | 241 | # Call Javascript function, and pass explicit callback function 242 | eel.js_random()(print_num) 243 | 244 | # Do the same with an inline lambda as callback 245 | eel.js_random()(lambda n: print('Got this from Javascript:', n)) 246 | ``` 247 | 248 | (It works exactly the same the other way around). 249 | 250 | #### Synchronous returns 251 | 252 | In most situations, the calls to the other side are to quickly retrieve some piece of data, such as the state of a widget or contents of an input field. In these cases it is more convenient to just synchronously wait a few milliseconds then continue with your code, rather than breaking the whole thing up into callbacks. 253 | 254 | To synchronously retrieve the return value, simply pass nothing to the second set of brackets. So in Python we would write: 255 | 256 | ```python 257 | n = eel.js_random()() # This immediately returns the value 258 | print('Got this from Javascript:', n) 259 | ``` 260 | 261 | You can only perform synchronous returns after the browser window has started (after calling `eel.start()`), otherwise obviously the call with hang. 262 | 263 | In Javascript, the language doesn't allow us to block while we wait for a callback, except by using `await` from inside an `async` function. So the equivalent code from the Javascript side would be: 264 | 265 | ```javascript 266 | async function run() { 267 | // Inside a function marked 'async' we can use the 'await' keyword. 268 | 269 | let n = await eel.py_random()(); // Must prefix call with 'await', otherwise it's the same syntax 270 | console.log("Got this from Python: " + n); 271 | } 272 | 273 | run(); 274 | ``` 275 | 276 | ## Asynchronous Python 277 | 278 | Eel is built on Bottle and Gevent, which provide an asynchronous event loop similar to Javascript. A lot of Python's standard library implicitly assumes there is a single execution thread - to deal with this, Gevent can "[monkey patch](https://en.wikipedia.org/wiki/Monkey_patch)" many of the standard modules such as `time`. ~~This monkey patching is done automatically when you call `import eel`~~. If you need monkey patching you should `import gevent.monkey` and call `gevent.monkey.patch_all()` _before_ you `import eel`. Monkey patching can interfere with things like debuggers so should be avoided unless necessary. 279 | 280 | For most cases you should be fine by avoiding using `time.sleep()` and instead using the versions provided by `gevent`. For convenience, the two most commonly needed gevent methods, `sleep()` and `spawn()` are provided directly from Eel (to save importing `time` and/or `gevent` as well). 281 | 282 | In this example... 283 | 284 | ```python 285 | import eel 286 | eel.init('web') 287 | 288 | def my_other_thread(): 289 | while True: 290 | print("I'm a thread") 291 | eel.sleep(1.0) # Use eel.sleep(), not time.sleep() 292 | 293 | eel.spawn(my_other_thread) 294 | 295 | eel.start('main.html', block=False) # Don't block on this call 296 | 297 | while True: 298 | print("I'm a main loop") 299 | eel.sleep(1.0) # Use eel.sleep(), not time.sleep() 300 | ``` 301 | 302 | ...we would then have three "threads" (greenlets) running; 303 | 304 | 1. Eel's internal thread for serving the web folder 305 | 2. The `my_other_thread` method, repeatedly printing **"I'm a thread"** 306 | 3. The main Python thread, which would be stuck in the final `while` loop, repeatedly printing **"I'm a main loop"** 307 | 308 | ## Building distributable binary with PyInstaller 309 | 310 | If you want to package your app into a program that can be run on a computer without a Python interpreter installed, you should use **PyInstaller**. 311 | 312 | 1. Configure a virtualenv with desired Python version and minimum necessary Python packages 313 | 2. Install PyInstaller `pip install PyInstaller` 314 | 3. In your app's folder, run `python -m eel [your_main_script] [your_web_folder]` (for example, you might run `python -m eel hello.py web`) 315 | 4. This will create a new folder `dist/` 316 | 5. Valid PyInstaller flags can be passed through, such as excluding modules with the flag: `--exclude module_name`. For example, you might run `python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography` 317 | 6. When happy that your app is working correctly, add `--onefile --noconsole` flags to build a single executable file 318 | 319 | Consult the [documentation for PyInstaller](http://PyInstaller.readthedocs.io/en/stable/) for more options. 320 | 321 | ## Microsoft Edge 322 | 323 | For Windows 10 users, Microsoft Edge (`eel.start(.., mode='edge')`) is installed by default and a useful fallback if a preferred browser is not installed. See the examples: 324 | 325 | - A Hello World example using Microsoft Edge: [examples/01 - hello_world-Edge/](https://github.com/ChrisKnott/Eel/tree/master/examples/01%20-%20hello_world-Edge) 326 | - Example implementing browser-fallbacks: [examples/07 - CreateReactApp/eel_CRA.py](https://github.com/ChrisKnott/Eel/tree/master/examples/07%20-%20CreateReactApp/eel_CRA.py) 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | async-Eel 2 | ==== 3 | Eel is a little Python library for making simple Electron-like offline HTML/JS GUI apps, 4 | with full access to Python capabilities and libraries. 5 | 6 | About 7 | ---- 8 | * Python**3.6+** 9 | * use `asyncio` 10 | * [original README.md](README-old.md) 11 | * [original repository](https://github.com/ChrisKnott/Eel) 12 | 13 | I design this library for `asyncio` event loop users. 14 | 15 | The original uses `gevent` and both event loops have difficulty in using simultaneously. 16 | 17 | Install 18 | ---- 19 | ```bash 20 | pip install async-eel 21 | ``` 22 | 23 | How to use 24 | ---- 25 | It looks like same usage, but something is different, please look at [example codes](examples). 26 | 27 | I forked [eel_bootstrap(JP)](https://github.com/namuyan/eel_bootstrap) as good example. 28 | 29 | Author 30 | ---- 31 | [@namuyan_mine](https://twitter.com/namuyan_mine) 32 | 33 | Licence 34 | ---- 35 | [MIT](LICENSE) 36 | 37 | -------------------------------------------------------------------------------- /async_eel/__init__.py: -------------------------------------------------------------------------------- 1 | from async_eel.utils import * 2 | from async_eel import browsers 3 | from aiohttp.web_ws import WebSocketResponse 4 | from aiohttp.web import BaseRequest 5 | from aiohttp import web 6 | from expiringdict import ExpiringDict 7 | from typing import Optional, Callable, Dict 8 | from logging import getLogger, ERROR 9 | from random import random 10 | import socket 11 | import asyncio 12 | import json 13 | import re 14 | import sys 15 | import os 16 | 17 | 18 | # logging 19 | getLogger('aiohttp').setLevel(ERROR) 20 | log = getLogger(__name__) 21 | 22 | # inner objects 23 | routes = web.RouteTableDef() 24 | loop = asyncio.get_event_loop() 25 | 26 | # inner vars 27 | _eel_js_file = os.path.join(os.path.dirname(__file__), 'eel.js') 28 | _eel_js = open(_eel_js_file, encoding='utf-8').read() 29 | _websockets = list() 30 | _call_return_futures: Dict[int, asyncio.Future] = ExpiringDict(max_len=5000, max_age_seconds=300) 31 | _call_number = 0 32 | _exposed_functions = dict() 33 | _js_functions = list() 34 | _mock_queue = list() 35 | _mock_queue_done = set() 36 | 37 | # All start() options must provide a default value and explanation here 38 | _start_args = { 39 | 'mode': 'chrome', # What browser is used 40 | 'host': 'localhost', # Hostname use for Bottle server 41 | 'port': 8000, # Port used for Bottle server (use 0 for auto) 42 | 'block': True, # Whether start() blocks calling thread 43 | 'jinja_templates': None, # Folder for jinja2 templates 44 | 'cmdline_args': ['--disable-http-cache'], # Extra cmdline flags to pass to browser start 45 | 'size': None, # (width, height) of main window 46 | 'position': None, # (left, top) of main window 47 | 'geometry': {}, # Dictionary of size/position for all windows 48 | 'close_callback': None, # Callback for when all windows have closed 49 | 'app_mode': True, # (Chrome specific option) 50 | 'all_interfaces': False, # Allow bottle server to listen for connections on all interfaces 51 | 'disable_cache': True, # Sets the no-store response header when serving assets 52 | 'auto_close': True, # Auto close process when websocket connection is zero 53 | } 54 | 55 | # == Temporary (suppressable) error message to inform users of breaking API change for v1.0.0 === 56 | _start_args['suppress_error'] = False 57 | api_error_message = ''' 58 | ---------------------------------------------------------------------------------- 59 | 'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel 60 | To suppress this error, add 'suppress_error=True' to start() call. 61 | This option will be removed in future versions 62 | ---------------------------------------------------------------------------------- 63 | ''' 64 | # =============================================================================================== 65 | 66 | # Public functions 67 | 68 | 69 | def expose(name_or_function=None): 70 | # Deal with '@eel.expose()' - treat as '@eel.expose' 71 | if name_or_function is None: 72 | return expose 73 | 74 | if isinstance(name_or_function, str): # Called as '@eel.expose("my_name")' 75 | name = name_or_function 76 | 77 | def decorator(function): 78 | _expose(name, function) 79 | return function 80 | return decorator 81 | else: 82 | function = name_or_function 83 | _expose(function.__name__, function) 84 | return function 85 | 86 | 87 | def init(path, allowed_extensions=('.js', '.html', '.txt', '.htm', '.xhtml', '.vue')): 88 | global root_path, _js_functions 89 | root_path = _get_real_path(path) 90 | 91 | js_functions = set() 92 | for root, _, files in os.walk(root_path): 93 | for name in files: 94 | if not any(name.endswith(ext) for ext in allowed_extensions): 95 | continue 96 | 97 | try: 98 | with open(os.path.join(root, name), encoding='utf-8') as file: 99 | contents = file.read() 100 | expose_calls = set() 101 | finder = re.findall(r'eel\.expose\(([^\)]+)\)', contents) 102 | for expose_call in finder: 103 | # If name specified in 2nd argument, strip quotes and store as function name 104 | if ',' in expose_call: 105 | expose_call = re.sub(r'["\']', '', expose_call.split(',')[1]) 106 | expose_call = expose_call.strip() 107 | # Verify that function name is valid 108 | msg = "eel.expose() call contains '(' or '='" 109 | assert re.findall(r'[\(=]', expose_call) == [], msg 110 | expose_calls.add(expose_call) 111 | js_functions.update(expose_calls) 112 | except UnicodeDecodeError: 113 | pass # Malformed file probably 114 | 115 | _js_functions = list(js_functions) 116 | for js_function in _js_functions: 117 | _mock_js_function(js_function) 118 | 119 | 120 | async def start(*start_urls, **kwargs): 121 | try: 122 | _start_args.update(kwargs) 123 | 124 | if 'options' in kwargs: 125 | if _start_args['suppress_error']: 126 | _start_args.update(kwargs['options']) 127 | else: 128 | raise RuntimeError(api_error_message) 129 | 130 | if _start_args['port'] == 0: 131 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 132 | sock.bind(('localhost', 0)) 133 | _start_args['port'] = sock.getsockname()[1] 134 | sock.close() 135 | 136 | if _start_args['jinja_templates'] is not None: 137 | from jinja2 import Environment, FileSystemLoader, select_autoescape 138 | templates_path = os.path.join(root_path, _start_args['jinja_templates']) 139 | _start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path), 140 | autoescape=select_autoescape(['html', 'xml'])) 141 | 142 | # Launch the browser to the starting URLs 143 | show(*start_urls) 144 | 145 | if _start_args['all_interfaces'] is True: 146 | HOST = '0.0.0.0' 147 | else: 148 | HOST = _start_args['host'] 149 | # start web server (non blocking) 150 | app = web.Application() 151 | app.add_routes(routes) 152 | runner = web.AppRunner(app) 153 | await runner.setup() 154 | site = web.TCPSite(runner, host=HOST, port=_start_args['port']) 155 | await site.start() 156 | log.info(f"start http server {HOST}:{_start_args['port']}") 157 | except Exception: 158 | log.debug("http server exception", exc_info=True) 159 | 160 | 161 | def show(*start_urls): 162 | browsers.open(start_urls, _start_args) 163 | 164 | 165 | def spawn(function, *args, **kwargs): 166 | if asyncio.iscoroutinefunction(function): 167 | asyncio.ensure_future(function(*args, **kwargs)) 168 | else: 169 | if 0 < len(kwargs): 170 | raise Exception('cannot convey kwargs') 171 | loop.call_soon_threadsafe(function, *args) 172 | 173 | # Bottle Routes 174 | 175 | 176 | @routes.get('/eel.js') 177 | async def _eel(request: BaseRequest): 178 | start_geometry = {'default': {'size': _start_args['size'], 179 | 'position': _start_args['position']}, 180 | 'pages': _start_args['geometry']} 181 | 182 | page = _eel_js.replace('/** _py_functions **/', 183 | '_py_functions: %s,' % list(_exposed_functions.keys())) 184 | page = page.replace('/** _start_geometry **/', 185 | '_start_geometry: %s,' % _safe_json(start_geometry)) 186 | 187 | response = web.Response(text=page, content_type='application/javascript') 188 | _set_response_headers(response) 189 | return response 190 | 191 | 192 | @routes.get('/eel') 193 | async def _websocket(request: BaseRequest): 194 | 195 | ws = await websocket_protocol_check(request) 196 | 197 | for js_function in _js_functions: 198 | _import_js_function(js_function) 199 | 200 | page = request.query.get('page') 201 | if page not in _mock_queue_done: 202 | for call in _mock_queue: 203 | await _repeated_send(ws, _safe_json(call)) 204 | _mock_queue_done.add(page) 205 | 206 | _websockets.append((page, ws)) 207 | 208 | while True: 209 | try: 210 | message = await ws.receive_json(timeout=0.1) 211 | asyncio.create_task(_process_message(message, ws)) 212 | except (asyncio.TimeoutError, TypeError): 213 | if ws.closed: 214 | break 215 | except Exception: 216 | log.debug("WebSocket exception", exc_info=True) 217 | break 218 | # closed 219 | if not ws.closed: 220 | await ws.close() 221 | _websockets.remove((page, ws)) 222 | _websocket_close(page) 223 | 224 | 225 | @routes.get('/{path:.*}') 226 | async def _static(request: BaseRequest): 227 | response = None 228 | try: 229 | path = request.path[1:] 230 | if 'jinja_env' in _start_args and 'jinja_templates' in _start_args: 231 | template_prefix = _start_args['jinja_templates'] + '/' 232 | if path.startswith(template_prefix): 233 | n = len(template_prefix) 234 | template = _start_args['jinja_env'].get_template(path[n:]) 235 | response = web.Response(body=template.render(), content_type='text/html') 236 | else: 237 | file_path = os.path.join(root_path, path) 238 | if not os.path.isfile(file_path): 239 | return web.Response(text=f"not found {path}", status=404) 240 | log.debug(f"static access to '{path}'") 241 | response = web.FileResponse(path=file_path) 242 | except Exception as e: 243 | log.debug("http page exception", exc_info=True) 244 | response = web.Response(text=str(e), status=500) 245 | 246 | _set_response_headers(response) 247 | return response 248 | 249 | 250 | # Private functions 251 | 252 | 253 | def _safe_json(obj): 254 | return json.dumps(obj, default=lambda o: None) 255 | 256 | 257 | async def _repeated_send(ws: WebSocketResponse, msg): 258 | for attempt in range(100): 259 | try: 260 | await ws.send_str(msg) 261 | break 262 | except Exception: 263 | await asyncio.sleep(0.001) 264 | 265 | 266 | async def _process_message(message, ws: WebSocketResponse): 267 | if 'call' in message: 268 | function = _exposed_functions[message['name']] 269 | if asyncio.iscoroutinefunction(function): 270 | return_val = await function(*message['args']) 271 | else: 272 | return_val = function(*message['args']) 273 | await _repeated_send(ws, _safe_json({ 274 | 'return': message['call'], 275 | 'value': return_val, 276 | })) 277 | elif 'return' in message: 278 | call_id = message['return'] 279 | if call_id in _call_return_futures: 280 | future = _call_return_futures[call_id] 281 | if not future.done(): 282 | future.set_result(message['value']) 283 | else: 284 | log.warning('Invalid message received: ', message) 285 | 286 | 287 | def _get_real_path(path): 288 | if getattr(sys, 'frozen', False): 289 | return os.path.join(sys._MEIPASS, path) 290 | else: 291 | return os.path.abspath(path) 292 | 293 | 294 | def _mock_js_function(f): 295 | """add globals awaitable function""" 296 | assert isinstance(f, str) 297 | globals()[f] = lambda *args: _mock_call(f, args) 298 | # exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals()) 299 | 300 | 301 | def _import_js_function(f): 302 | """add globals awaitable function""" 303 | assert isinstance(f, str) 304 | globals()[f] = lambda *args: _js_call(f, args) 305 | # exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals()) 306 | 307 | 308 | def _call_object(name, args): 309 | global _call_number 310 | _call_number += 1 311 | call_id = _call_number + random() 312 | return {'call': call_id, 'name': name, 'args': args} 313 | 314 | 315 | def _mock_call(name, args): 316 | call_object = _call_object(name, args) 317 | _mock_queue.append(call_object) 318 | return _call_return(call_object['call']) 319 | 320 | 321 | def _js_call(name, args): 322 | call_object = _call_object(name, args) 323 | data = _safe_json(call_object) 324 | for _, ws in _websockets: 325 | asyncio.ensure_future(_repeated_send(ws, data)) 326 | return _call_return(call_object['call']) 327 | 328 | 329 | def _call_return(call_id): 330 | future = asyncio.Future() 331 | _call_return_futures[call_id] = future 332 | 333 | async def wait_for_result(callback): 334 | """wait for result and exc callback""" 335 | try: 336 | await asyncio.wait_for(future, timeout=10.0) 337 | args = future.result() 338 | if asyncio.iscoroutinefunction(callback): 339 | await callback(args) 340 | else: 341 | callback(args) 342 | except asyncio.TimeoutError: 343 | log.debug("timeout on wait_for_result") 344 | except Exception: 345 | log.debug("_call_return exception", exc_info=True) 346 | 347 | async def return_func(callback=None): 348 | """return data or task object""" 349 | try: 350 | if callback is None: 351 | await asyncio.wait_for(future, timeout=10.0) 352 | return future.result() 353 | else: 354 | return asyncio.ensure_future(wait_for_result(callback)) 355 | except asyncio.TimeoutError: 356 | log.debug("timeout on return_func") 357 | except Exception: 358 | log.debug("return_func exception", exc_info=True) 359 | return return_func 360 | 361 | 362 | def _expose(name, function): 363 | msg = 'Already exposed function with name "%s"' % name 364 | assert name not in _exposed_functions, msg 365 | _exposed_functions[name] = function 366 | 367 | 368 | def _websocket_close(page): 369 | close_callback: Optional[Callable] = _start_args.get('close_callback') 370 | 371 | if close_callback is not None: 372 | web_sockets = [ws for _, ws in _websockets] 373 | close_callback(page, web_sockets) 374 | else: 375 | # Default behaviour - wait 1s, then quit if all sockets are closed 376 | if _start_args['auto_close'] and len(_websockets) == 0: 377 | loop.call_later(1.0, loop.stop) 378 | 379 | 380 | def _set_response_headers(response): 381 | if _start_args['disable_cache']: 382 | # https://stackoverflow.com/a/24748094/280852 383 | response.headers.add('Cache-Control', 'no-store') 384 | 385 | 386 | __all__ = [ 387 | "expose", 388 | "init", 389 | "start", 390 | "show", 391 | "spawn", 392 | ] 393 | -------------------------------------------------------------------------------- /async_eel/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import pkg_resources as pkg 4 | import PyInstaller.__main__ as pyi 5 | import os 6 | 7 | args = sys.argv[1:] 8 | main_script = args.pop(0) 9 | web_folder = args.pop(0) 10 | 11 | print("Building executable with main script '%s' and web folder '%s'...\n" % 12 | (main_script, web_folder)) 13 | 14 | eel_js_file = pkg.resource_filename('eel', 'eel.js') 15 | js_file_arg = '%s%seel' % (eel_js_file, os.pathsep) 16 | web_folder_arg = '%s%s%s' % (web_folder, os.pathsep, web_folder) 17 | 18 | needed_args = ['--hidden-import', 'bottle_websocket', 19 | '--add-data', js_file_arg, '--add-data', web_folder_arg] 20 | full_args = [main_script] + needed_args + args 21 | 22 | print('Running:\npyinstaller', ' '.join(full_args), '\n') 23 | 24 | pyi.run(full_args) 25 | -------------------------------------------------------------------------------- /async_eel/browsers.py: -------------------------------------------------------------------------------- 1 | import subprocess as sps 2 | import webbrowser as wbr 3 | 4 | import async_eel.chrome as chm 5 | import async_eel.electron as ele 6 | import async_eel.edge as edge 7 | #import async_eel.firefox as ffx TODO 8 | #import async_eel.safari as saf TODO 9 | 10 | _browser_paths = {} 11 | _browser_modules = {'chrome': chm, 12 | 'electron': ele, 13 | 'edge': edge} 14 | 15 | 16 | def _build_url_from_dict(page, options): 17 | scheme = page.get('scheme', 'http') 18 | host = page.get('host', 'localhost') 19 | port = page.get('port', 8000) 20 | path = page.get('path', '') 21 | return '%s://%s:%d/%s' % (scheme, host, port, path) 22 | 23 | 24 | def _build_url_from_string(page, options): 25 | base_url = 'http://%s:%d/' % (options['host'], options['port']) 26 | return base_url + page 27 | 28 | 29 | def _build_urls(start_pages, options): 30 | urls = [] 31 | 32 | for page in start_pages: 33 | method = _build_url_from_dict if isinstance( 34 | page, dict) else _build_url_from_string 35 | url = method(page, options) 36 | urls.append(url) 37 | 38 | return urls 39 | 40 | 41 | def open(start_pages, options): 42 | # Build full URLs for starting pages (including host and port) 43 | start_urls = _build_urls(start_pages, options) 44 | 45 | mode = options.get('mode') 46 | if mode in [None, False]: 47 | # Don't open a browser 48 | pass 49 | elif mode == 'custom': 50 | # Just run whatever command the user provided 51 | sps.Popen(options['cmdline_args'], 52 | stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) 53 | elif mode in _browser_modules: 54 | # Run with a specific browser 55 | browser_module = _browser_modules[mode] 56 | path = _browser_paths.get(mode) 57 | if path is None: 58 | # Don't know this browser's path, try and find it ourselves 59 | path = browser_module.find_path() 60 | _browser_paths[mode] = path 61 | 62 | if path is not None: 63 | browser_module.run(path, options, start_urls) 64 | else: 65 | raise EnvironmentError("Can't find %s installation" % browser_module.name) 66 | else: 67 | # Fall back to system default browser 68 | for url in start_urls: 69 | wbr.open(url) 70 | 71 | 72 | def set_path(browser_name, path): 73 | _browser_paths[browser_name] = path 74 | 75 | 76 | def get_path(browser_name): 77 | return _browser_paths.get(browser_name) 78 | 79 | -------------------------------------------------------------------------------- /async_eel/chrome.py: -------------------------------------------------------------------------------- 1 | import sys, subprocess as sps, os 2 | 3 | # Every browser specific module must define run(), find_path() and name like this 4 | 5 | name = 'Google Chrome/Chromium' 6 | 7 | def run(path, options, start_urls): 8 | if options['app_mode']: 9 | for url in start_urls: 10 | sps.Popen([path, '--app=%s' % url] + 11 | options['cmdline_args'], 12 | stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) 13 | else: 14 | args = options['cmdline_args'] + start_urls 15 | sps.Popen([path, '--new-window'] + args, 16 | stdout=sps.PIPE, stderr=sys.stderr, stdin=sps.PIPE) 17 | 18 | 19 | def find_path(): 20 | if sys.platform in ['win32', 'win64']: 21 | return _find_chrome_win() 22 | elif sys.platform == 'darwin': 23 | return _find_chrome_mac() 24 | elif sys.platform.startswith('linux'): 25 | return _find_chrome_linux() 26 | else: 27 | return None 28 | 29 | 30 | def _find_chrome_mac(): 31 | default_dir = r'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' 32 | if os.path.exists(default_dir): 33 | return default_dir 34 | # use mdfind ci to locate Chrome in alternate locations and return the first one 35 | name = 'Google Chrome.app' 36 | alternate_dirs = [x for x in sps.check_output(["mdfind", name]).decode().split('\n') if x.endswith(name)] 37 | if len(alternate_dirs): 38 | return alternate_dirs[0] + '/Contents/MacOS/Google Chrome' 39 | return None 40 | 41 | 42 | def _find_chrome_linux(): 43 | import whichcraft as wch 44 | chrome_names = ['chromium-browser', 45 | 'chromium', 46 | 'google-chrome', 47 | 'google-chrome-stable'] 48 | 49 | for name in chrome_names: 50 | chrome = wch.which(name) 51 | if chrome is not None: 52 | return chrome 53 | return None 54 | 55 | 56 | def _find_chrome_win(): 57 | import winreg as reg 58 | reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' 59 | 60 | for install_type in reg.HKEY_CURRENT_USER, reg.HKEY_LOCAL_MACHINE: 61 | try: 62 | reg_key = reg.OpenKey(install_type, reg_path, 0, reg.KEY_READ) 63 | chrome_path = reg.QueryValue(reg_key, None) 64 | reg_key.Close() 65 | except WindowsError: 66 | chrome_path = None 67 | else: 68 | break 69 | 70 | return chrome_path 71 | -------------------------------------------------------------------------------- /async_eel/edge.py: -------------------------------------------------------------------------------- 1 | import subprocess as sps 2 | import sys 3 | 4 | name = 'Edge' 5 | 6 | 7 | def run(_path, options, start_urls): 8 | cmd = 'start microsoft-edge:{}'.format(start_urls[0]) 9 | sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE, shell=True) 10 | 11 | 12 | def find_path(): 13 | # Path isn't necessary. Edge is launched with a CLI argument 14 | return True 15 | -------------------------------------------------------------------------------- /async_eel/eel.js: -------------------------------------------------------------------------------- 1 | eel = { 2 | _host: window.location.origin, 3 | 4 | set_host: function (hostname) { 5 | eel._host = hostname 6 | }, 7 | 8 | expose: function(f, name) { 9 | if(name === undefined){ 10 | name = f.toString(); 11 | let i = 'function '.length, j = name.indexOf('('); 12 | name = name.substring(i, j).trim(); 13 | } 14 | 15 | eel._exposed_functions[name] = f; 16 | }, 17 | 18 | guid: function() { 19 | return eel._guid; 20 | }, 21 | 22 | // These get dynamically added by library when file is served 23 | /** _py_functions **/ 24 | /** _start_geometry **/ 25 | 26 | _guid: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => 27 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 28 | ), 29 | 30 | _exposed_functions: {}, 31 | 32 | _mock_queue: [], 33 | 34 | _mock_py_functions: function() { 35 | for(let i = 0; i < eel._py_functions.length; i++) { 36 | let name = eel._py_functions[i]; 37 | eel[name] = function() { 38 | let call_object = eel._call_object(name, arguments); 39 | eel._mock_queue.push(call_object); 40 | return eel._call_return(call_object); 41 | } 42 | } 43 | }, 44 | 45 | _import_py_function: function(name) { 46 | let func_name = name; 47 | eel[name] = function() { 48 | let call_object = eel._call_object(func_name, arguments); 49 | eel._websocket.send(eel._toJSON(call_object)); 50 | return eel._call_return(call_object); 51 | } 52 | }, 53 | 54 | _call_number: 0, 55 | 56 | _call_return_callbacks: {}, 57 | 58 | _call_object: function(name, args) { 59 | let arg_array = []; 60 | for(let i = 0; i < args.length; i++){ 61 | arg_array.push(args[i]); 62 | } 63 | 64 | let call_id = (eel._call_number += 1) + Math.random(); 65 | return {'call': call_id, 'name': name, 'args': arg_array}; 66 | }, 67 | 68 | _sleep: function(ms) { 69 | return new Promise(resolve => setTimeout(resolve, ms)); 70 | }, 71 | 72 | _toJSON: function(obj) { 73 | return JSON.stringify(obj, (k, v) => v === undefined ? null : v); 74 | }, 75 | 76 | _call_return: function(call) { 77 | return function(callback = null) { 78 | if(callback != null) { 79 | eel._call_return_callbacks[call.call] = callback; 80 | } else { 81 | return new Promise(function(resolve) { 82 | eel._call_return_callbacks[call.call] = resolve; 83 | }); 84 | } 85 | } 86 | }, 87 | 88 | _position_window: function(page) { 89 | let size = eel._start_geometry['default'].size; 90 | let position = eel._start_geometry['default'].position; 91 | 92 | if(page in eel._start_geometry.pages) { 93 | size = eel._start_geometry.pages[page].size; 94 | position = eel._start_geometry.pages[page].position; 95 | } 96 | 97 | if(size != null){ 98 | window.resizeTo(size[0], size[1]); 99 | } 100 | 101 | if(position != null){ 102 | window.moveTo(position[0], position[1]); 103 | } 104 | }, 105 | 106 | _init: function() { 107 | eel._mock_py_functions(); 108 | 109 | document.addEventListener("DOMContentLoaded", function(event) { 110 | let page = window.location.pathname.substring(1); 111 | eel._position_window(page); 112 | 113 | let websocket_addr = (eel._host + '/eel').replace('http', 'ws'); 114 | websocket_addr += ('?page=' + page); 115 | eel._websocket = new WebSocket(websocket_addr); 116 | 117 | eel._websocket.onopen = function() { 118 | for(let i = 0; i < eel._py_functions.length; i++){ 119 | let py_function = eel._py_functions[i]; 120 | eel._import_py_function(py_function); 121 | } 122 | 123 | while(eel._mock_queue.length > 0) { 124 | let call = eel._mock_queue.shift(); 125 | eel._websocket.send(eel._toJSON(call)); 126 | } 127 | }; 128 | 129 | eel._websocket.onmessage = function (e) { 130 | let message = JSON.parse(e.data); 131 | if(message.hasOwnProperty('call') ) { 132 | // Python making a function call into us 133 | if(message.name in eel._exposed_functions) { 134 | let return_val = eel._exposed_functions[message.name](...message.args); 135 | eel._websocket.send(eel._toJSON({'return': message.call, 'value': return_val})); 136 | } 137 | } else if(message.hasOwnProperty('return')) { 138 | // Python returning a value to us 139 | if(message['return'] in eel._call_return_callbacks) { 140 | eel._call_return_callbacks[message['return']](message.value); 141 | } 142 | } else { 143 | throw 'Invalid message ' + message; 144 | } 145 | 146 | }; 147 | }); 148 | } 149 | } 150 | 151 | eel._init(); 152 | 153 | if(typeof require !== 'undefined'){ 154 | // Avoid name collisions when using Electron, so jQuery etc work normally 155 | window.nodeRequire = require; 156 | delete window.require; 157 | delete window.exports; 158 | delete window.module; 159 | } 160 | -------------------------------------------------------------------------------- /async_eel/electron.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess as sps 4 | import whichcraft as wch 5 | 6 | name = 'Electron' 7 | 8 | def run(path, options, start_urls): 9 | cmd = [path] + options['cmdline_args'] 10 | cmd += ['.', ';'.join(start_urls)] 11 | sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE) 12 | 13 | 14 | def find_path(): 15 | if sys.platform in ['win32', 'win64']: 16 | # It doesn't work well passing the .bat file to Popen, so we get the actual .exe 17 | bat_path = wch.which('electron') 18 | return os.path.join(bat_path, r'..\node_modules\electron\dist\electron.exe') 19 | elif sys.platform in ['darwin', 'linux']: 20 | # This should work find... 21 | return wch.which('electron') 22 | else: 23 | return None 24 | 25 | -------------------------------------------------------------------------------- /async_eel/utils.py: -------------------------------------------------------------------------------- 1 | from aiohttp.web import BaseRequest 2 | from aiohttp import web 3 | from logging import getLogger 4 | 5 | 6 | log = getLogger(__name__) 7 | 8 | 9 | async def websocket_protocol_check(request: BaseRequest): 10 | """protocol upgrade to WebSocket""" 11 | ws = web.WebSocketResponse() 12 | ws.enable_compression() 13 | available = ws.can_prepare(request) 14 | if not available: 15 | raise TypeError('cannot prepare websocket') 16 | await ws.prepare(request) 17 | log.debug(f"protocol upgrade to websocket protocol {request.remote}") 18 | return ws 19 | 20 | 21 | __all__ = [ 22 | "websocket_protocol_check", 23 | ] 24 | -------------------------------------------------------------------------------- /examples/01 - hello_world-Edge/hello.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | import asyncio 5 | # Use latest version of Eel from parent directory 6 | sys.path.insert(1, '../../') 7 | import async_eel 8 | 9 | 10 | loop = asyncio.get_event_loop() 11 | 12 | 13 | @async_eel.expose # Expose this function to Javascript 14 | async def say_hello_py(x): 15 | print('Hello from %s' % x) 16 | 17 | 18 | async def main(): 19 | # Use the same static files as the original Example 20 | os.chdir(os.path.join('..', '01 - hello_world')) 21 | 22 | # Set web files folder and optionally specify which file types to check for eel.expose() 23 | async_eel.init('web', allowed_extensions=['.js', '.html']) 24 | 25 | # Launch example in Microsoft Edge only on Windows 10 and above 26 | if sys.platform in ['win32', 'win64'] and int(platform.release()) >= 10: 27 | await async_eel.start('hello.html', mode='edge') 28 | else: 29 | raise EnvironmentError('Error: System is not Windows 10 or above') 30 | 31 | # # Launching Edge can also be gracefully handled as a fall back 32 | # try: 33 | # await async_eel.start('hello.html', mode='chrome-app', size=(300, 200)) 34 | # except EnvironmentError: 35 | # # If Chrome isn't found, fallback to Microsoft Edge on Win10 or greater 36 | # if sys.platform in ['win32', 'win64'] and int(platform.release()) >= 10: 37 | # await async_eel.start('hello.html', mode='edge') 38 | # else: 39 | # raise 40 | 41 | await say_hello_py('Python World!') 42 | await async_eel.say_hello_js('Python World!')() # Call a Javascript function 43 | print("OK") 44 | 45 | 46 | if __name__ == '__main__': 47 | asyncio.run_coroutine_threadsafe(main(), loop) 48 | loop.run_forever() 49 | -------------------------------------------------------------------------------- /examples/01 - hello_world/hello.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel 3 | import asyncio 4 | 5 | loop = asyncio.get_event_loop() 6 | 7 | 8 | @async_eel.expose # Expose this function to Javascript 9 | async def say_hello_py(x): 10 | print('Hello from %s' % x) 11 | 12 | 13 | async def main(): 14 | # Set web files folder 15 | async_eel.init('web') 16 | await async_eel.start('hello.html', size=(300, 200)) # Start 17 | 18 | await say_hello_py('Python World!') 19 | await async_eel.say_hello_js('Python World!')() # Call a Javascript function 20 | print("OK") 21 | 22 | 23 | if __name__ == '__main__': 24 | asyncio.run_coroutine_threadsafe(main(), loop) 25 | loop.run_forever() 26 | 27 | -------------------------------------------------------------------------------- /examples/01 - hello_world/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/01 - hello_world/web/favicon.ico -------------------------------------------------------------------------------- /examples/01 - hello_world/web/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, World! 5 | 6 | 7 | 8 | 19 | 20 | 21 | 22 | Hello, World! 23 | 24 | -------------------------------------------------------------------------------- /examples/02 - callbacks/callbacks.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel 3 | import random 4 | import asyncio 5 | 6 | 7 | loop = asyncio.get_event_loop() 8 | 9 | 10 | @async_eel.expose 11 | async def py_random(): 12 | return random.random() 13 | 14 | 15 | async def print_num(n): 16 | """callback of js_random""" 17 | print('Got this from Javascript:', n) 18 | 19 | 20 | async def main(): 21 | try: 22 | async_eel.init('web') 23 | await async_eel.start('callbacks.html', size=(400, 300)) 24 | 25 | # Call Javascript function, and pass explicit callback function 26 | await async_eel.js_random()(print_num) 27 | 28 | # Do the same with an inline callback 29 | await async_eel.js_random()(lambda n: print('2Got this from Javascript:', n)) 30 | except Exception: 31 | import traceback 32 | traceback.print_exc() 33 | 34 | 35 | if __name__ == '__main__': 36 | asyncio.run_coroutine_threadsafe(main(), loop) 37 | loop.run_forever() 38 | -------------------------------------------------------------------------------- /examples/02 - callbacks/web/callbacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Callbacks Demo 5 | 6 | 7 | 25 | 26 | 27 | 28 | Callbacks demo 29 | 30 | -------------------------------------------------------------------------------- /examples/02 - callbacks/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/02 - callbacks/web/favicon.ico -------------------------------------------------------------------------------- /examples/03 - sync_callbacks/sync_callbacks.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel, random 3 | import asyncio 4 | 5 | loop = asyncio.get_event_loop() 6 | 7 | 8 | @async_eel.expose 9 | def py_random(): 10 | return random.random() 11 | 12 | 13 | async def main(): 14 | async_eel.init('web') 15 | await async_eel.start('sync_callbacks.html', block=False, size=(400, 300)) 16 | # Synchronous calls must happen after start() is called 17 | 18 | # Get result returned synchronously by 19 | # passing nothing in second brackets 20 | # v 21 | data = await async_eel.js_random()() 22 | print('Got this from Javascript:', data) 23 | 24 | 25 | if __name__ == '__main__': 26 | asyncio.run_coroutine_threadsafe(main(), loop) 27 | loop.run_forever() 28 | -------------------------------------------------------------------------------- /examples/03 - sync_callbacks/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/03 - sync_callbacks/web/favicon.ico -------------------------------------------------------------------------------- /examples/03 - sync_callbacks/web/sync_callbacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Synchronous callbacks 5 | 6 | 7 | 27 | 28 | 29 | 30 | Synchronous callbacks 31 | 32 | -------------------------------------------------------------------------------- /examples/04 - file_access/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/04 - file_access/Screenshot.png -------------------------------------------------------------------------------- /examples/04 - file_access/file_access.py: -------------------------------------------------------------------------------- 1 | import async_eel, os, random 2 | import asyncio 3 | 4 | 5 | loop = asyncio.get_event_loop() 6 | 7 | 8 | @async_eel.expose 9 | def pick_file(folder): 10 | if os.path.isdir(folder): 11 | return random.choice(os.listdir(folder)) 12 | else: 13 | return 'Not valid folder' 14 | 15 | 16 | async def main(): 17 | async_eel.init('web') 18 | await async_eel.start('file_access.html', size=(320, 120)) 19 | 20 | 21 | if __name__ == '__main__': 22 | asyncio.run_coroutine_threadsafe(main(), loop) 23 | loop.run_forever() 24 | -------------------------------------------------------------------------------- /examples/04 - file_access/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/04 - file_access/web/favicon.ico -------------------------------------------------------------------------------- /examples/04 - file_access/web/file_access.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Eel Demo 5 | 6 | 18 | 19 | 20 | 21 | 22 | Pick random file 23 |
---
24 | 25 | -------------------------------------------------------------------------------- /examples/05 - input/script.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel 3 | import asyncio 4 | 5 | 6 | loop = asyncio.get_event_loop() 7 | 8 | 9 | @async_eel.expose # Expose this function to Javascript 10 | def handleinput(x): 11 | print('%s' % x) 12 | 13 | 14 | async def main(): 15 | try: 16 | async_eel.init('web') # Give folder containing web files 17 | await async_eel.start('main.html', size=(500, 250)) # Start 18 | 19 | await async_eel.say_hello_js('connected!')() # Call a Javascript function 20 | print("OK") 21 | except Exception: 22 | import traceback 23 | traceback.print_exc() 24 | 25 | 26 | if __name__ == '__main__': 27 | asyncio.run_coroutine_threadsafe(main(), loop) 28 | loop.run_forever() 29 | -------------------------------------------------------------------------------- /examples/05 - input/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/05 - input/web/favicon.ico -------------------------------------------------------------------------------- /examples/05 - input/web/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 | 26 | 27 |

Input Example: Enter a value and check python console

28 |
29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/06 - jinja_templates/hello.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel 3 | import asyncio 4 | 5 | 6 | loop = asyncio.get_event_loop() 7 | 8 | 9 | @async_eel.expose # Expose this function to Javascript 10 | def say_hello_py(x): 11 | print('Hello from %s' % x) 12 | 13 | 14 | async def main(): 15 | try: 16 | async_eel.init('web') # Give folder containing web files 17 | await async_eel.start('templates/hello.html', size=(300, 200), jinja_templates='templates') # Start 18 | 19 | say_hello_py('Python World!') 20 | r = await async_eel.say_hello_js('Python World!')() # Call a Javascript function 21 | print("main OK", r) 22 | except Exception: 23 | import traceback 24 | traceback.print_exc() 25 | 26 | 27 | if __name__ == '__main__': 28 | asyncio.run_coroutine_threadsafe(main(), loop) 29 | loop.run_forever() 30 | -------------------------------------------------------------------------------- /examples/06 - jinja_templates/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/06 - jinja_templates/web/favicon.ico -------------------------------------------------------------------------------- /examples/06 - jinja_templates/web/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{% endblock %} 5 | 6 | 7 | 10 | 11 | 12 | {% block content %}{% endblock %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/06 - jinja_templates/web/templates/hello.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Hello, World!{% endblock %} 3 | {% block head_scripts %} 4 | eel.expose(say_hello_js); // Expose this function to Python 5 | function say_hello_js(x) { 6 | console.log("Hello from " + x); 7 | } 8 | 9 | eel.expose(js_random); 10 | function js_random() { 11 | return Math.random(); 12 | } 13 | 14 | function print_num(n) { 15 | console.log('Got this from Python: ' + n); 16 | } 17 | 18 | eel.py_random()(print_num); 19 | 20 | say_hello_js("Javascript World!"); 21 | eel.say_hello_py("Javascript World!"); // Call a Python function 22 | {% endblock %} 23 | {% block content %} 24 | Hello, World! 25 |
26 | Page 2 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /examples/06 - jinja_templates/web/templates/page2.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Hello, World!{% endblock %} 3 | {% block content %} 4 |

This is page 2

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | *.spec 26 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/Demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/07 - CreateReactApp/Demo.png -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/README.md: -------------------------------------------------------------------------------- 1 | > "Eello World example": Create-React-App (CRA) and Eel 2 | 3 | **Table of Contents** 4 | 5 | 6 | 7 | - [07 - CreateReactApp Documentation](#07---createreactapp-documentation) 8 | - [Quick Start](#quick-start) 9 | - [About](#about) 10 | - [Main Files](#main-files) 11 | 12 | 13 | 14 | # 07 - CreateReactApp Documentation 15 | 16 | Eello World example Create-React-App (CRA) with Eel. This particular project was bootstrapped with `npx create-react-app 07_CreateReactApp --typescript` (Typescript enabled), but the below modifications can be implemented in any CRA configuration or CRA version. 17 | 18 | If you run into any issues with this example, open a [new issue](https://github.com/ChrisKnott/Eel/issues/new) and tag @KyleKing 19 | 20 | ## Quick Start 21 | 22 | 1. **Configure:** In the app's directory, run `npm install` and `pip install bottle bottle-websocket future whichcraft pyinstaller` 23 | 2. **Demo:** Build static files with `npm run build` then run the application with `python eel_CRA.py`. A Chrome-app window should open running the built code from `build/` 24 | 3. **Distribute:** (Run `npm run build` first) Build a binary distribution with PyInstaller using `python -m eel eel_CRA.py build --onefile` (See more detailed PyInstaller instructions at bottom of [the main README](https://github.com/ChrisKnott/Eel)) 25 | 4. **Develop:** Open two prompts. In one, run `python eel_CRA.py true` and the other, `npm start`. A browser window should open in your default web browser at: [http://localhost:3000/](http://localhost:3000/). As you make changes to the JavaScript in `src/` the browser will reload. Any changes to `eel_CRA.py` will require a restart to take effect. You may need to refresh the browser window if it gets out of sync with eel. 26 | 27 | ![Demo.png](Demo.png) 28 | 29 | ## About 30 | 31 | > Use `window.eel.expose(func, 'func')` to circumvent `npm run build` code mangling 32 | 33 | `npm run build` will rename variables and functions to minimize file size renaming `eel.expose(funcName)` to something like `D.expose(J)`. The renaming breaks Eel's static JS-code analyzer, which uses a regular expression to look for `eel.expose(*)`. To fix this issue, in your JS code, convert all `eel.expose(funcName)` to `window.eel(funcName, 'funcName')`. This workaround guarantees that 'funcName' will be available to call from Python. 34 | 35 | ## Main Files 36 | 37 | Critical files for this demo 38 | 39 | - `src/App.tsx`: Modified to demonstrate exposing a function from JavaScript and how to use callbacks from Python to update React GUI 40 | - `eel_CRA.py`: Basic `eel` file 41 | - If run without arguments, the `eel` script will load `index.html` from the build/ directory (which is ideal for building with PyInstaller/distribution) 42 | - If any 2nd argument (i.e. `true`) is provided, the app enables a "development" mode and attempts to connect to the React server on port 3000 43 | - `public/index.html`: Added location of `eel.js` file based on options set in eel_CRA.py 44 | 45 | ```html 46 | 47 | 48 | ``` 49 | 50 | - `src/react-app-env.d.ts`: This file declares window.eel as a valid type for tslint. Note: capitalization of `window` 51 | - `src/App.css`: Added some basic button styling 52 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/eel_CRA.py: -------------------------------------------------------------------------------- 1 | """Main Python application file for the EEL-CRA demo.""" 2 | 3 | import os 4 | import platform 5 | import random 6 | import sys 7 | import asyncio 8 | import async_eel 9 | 10 | 11 | loop = asyncio.get_event_loop() 12 | 13 | 14 | # Use latest version of Eel from parent directory 15 | sys.path.insert(1, '../../') 16 | 17 | 18 | @async_eel.expose # Expose function to JavaScript 19 | async def say_hello_py(x): 20 | """Print message from JavaScript on app initialization, then call a JS function.""" 21 | print('Hello from %s' % x) # noqa T001 22 | await async_eel.say_hello_js('Python {from within say_hello_py()}!') 23 | 24 | 25 | @async_eel.expose 26 | def expand_user(folder): 27 | """Return the full path to display in the UI.""" 28 | return '{}/*'.format(os.path.expanduser(folder)) 29 | 30 | 31 | @async_eel.expose 32 | def pick_file(folder): 33 | """Return a random file from the specified folder.""" 34 | folder = os.path.expanduser(folder) 35 | if os.path.isdir(folder): 36 | listFiles = [_f for _f in os.listdir(folder) if not os.path.isdir(os.path.join(folder, _f))] 37 | if len(listFiles) == 0: 38 | return 'No Files found in {}'.format(folder) 39 | return random.choice(listFiles) 40 | else: 41 | return '{} is not a valid folder'.format(folder) 42 | 43 | 44 | async def start_eel(develop): 45 | """Start Eel with either production or development configuration.""" 46 | 47 | if develop: 48 | directory = 'src' 49 | app = None 50 | page = {'port': 3000} 51 | else: 52 | directory = 'build' 53 | app = 'chrome-app' 54 | page = 'index.html' 55 | 56 | async_eel.init(directory, ['.tsx', '.ts', '.jsx', '.js', '.html']) 57 | 58 | try: 59 | # These will be queued until the first connection is made, but won't be repeated on a page reload 60 | await say_hello_py('Python World!') 61 | async_eel.say_hello_js('Python World!') # Call a JavaScript function (must be after `eel.init()`) 62 | except Exception: 63 | import traceback 64 | traceback.print_exc() 65 | 66 | eel_kwargs = dict( 67 | host='localhost', 68 | port=8080, 69 | size=(1280, 800), 70 | ) 71 | try: 72 | await async_eel.start(page, mode=app, **eel_kwargs) 73 | except EnvironmentError: 74 | # If Chrome isn't found, fallback to Microsoft Edge on Win10 or greater 75 | if sys.platform in ['win32', 'win64'] and int(platform.release()) >= 10: 76 | await async_eel.start(page, mode='edge', **eel_kwargs) 77 | else: 78 | raise 79 | 80 | 81 | if __name__ == '__main__': 82 | import sys 83 | 84 | # Pass any second argument to enable debugging 85 | asyncio.run_coroutine_threadsafe(start_eel(develop=len(sys.argv) == 2), loop) 86 | loop.run_forever() 87 | 88 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "07___create-react-app", 3 | "version": "0.1.1", 4 | "private": true, 5 | "dependencies": { 6 | "@types/jest": "24.0.14", 7 | "@types/node": "12.0.8", 8 | "@types/react": "16.8.20", 9 | "@types/react-dom": "16.8.4", 10 | "react": "^16.8.6", 11 | "react-dom": "^16.8.6", 12 | "react-scripts": "^3.0.1", 13 | "typescript": "3.4.5" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/07 - CreateReactApp/public/favicon.ico -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-button { 22 | background-color: #61dafb; 23 | border-radius: 2vmin; 24 | color: #282c34; 25 | font-size: calc(10px + 2vmin); 26 | padding: 2vmin; 27 | } 28 | 29 | 30 | .App-button:hover { 31 | background-color: #7ce3ff; 32 | cursor: pointer; 33 | } 34 | 35 | @keyframes App-logo-spin { 36 | from { 37 | transform: rotate(0deg); 38 | } 39 | to { 40 | transform: rotate(360deg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | // Point Eel web socket to the instance 6 | export const eel = window.eel 7 | eel.set_host( 'ws://localhost:8080' ) 8 | 9 | // Expose the `sayHelloJS` function to Python as `say_hello_js` 10 | function sayHelloJS( x: any ) { 11 | console.log( 'Hello from ' + x ) 12 | } 13 | // WARN: must use window.eel to keep parse-able eel.expose{...} 14 | window.eel.expose( sayHelloJS, 'say_hello_js' ) 15 | 16 | // Test calling sayHelloJS, then call the corresponding Python function 17 | sayHelloJS( 'Javascript World!' ) 18 | eel.say_hello_py( 'Javascript World!' ) 19 | 20 | // Set the default path. Would be a text input, but this is a basic example after all 21 | const defPath = '~' 22 | 23 | interface IAppState { 24 | message: string 25 | path: string 26 | } 27 | 28 | export class App extends Component<{}, {}> { 29 | public state: IAppState = { 30 | message: `Click button to choose a random file from the user's system`, 31 | path: defPath, 32 | } 33 | 34 | public pickFile = () => { 35 | eel.pick_file(defPath)(( message: string ) => this.setState( { message } ) ) 36 | } 37 | 38 | public render() { 39 | eel.expand_user(defPath)(( path: string ) => this.setState( { path } ) ) 40 | return ( 41 |
42 |
43 | logo 44 |

{this.state.message}

45 | 46 |
47 |
48 | ); 49 | } 50 | } 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface Window { 4 | eel: any; 5 | } 6 | 7 | declare var window: Window; 8 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /examples/07 - CreateReactApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/hello.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function # For Py2/3 compatibility 2 | import async_eel 3 | import asyncio 4 | 5 | loop = asyncio.get_event_loop() 6 | 7 | 8 | @async_eel.expose # Expose this function to Javascript 9 | def say_hello_py(x): 10 | print('Hello from %s' % x) 11 | 12 | 13 | options = { 14 | 'mode': 'custom', 15 | 'args': ['node_modules/electron/dist/electron.exe', '.'] 16 | } 17 | 18 | 19 | async def main(): 20 | try: 21 | # Set web files folder 22 | async_eel.init('web') 23 | await async_eel.start('hello.html', options=options) 24 | # await eel.start('hello.html', mode='custom', cmdline_args=['node_modules/electron/dist/electron.exe', '.']) 25 | 26 | say_hello_py('Python World!') 27 | async_eel.say_hello_js('Python World!') # Call a Javascript function 28 | except Exception: 29 | import traceback 30 | traceback.print_exc() 31 | 32 | 33 | if __name__ == '__main__': 34 | asyncio.run_coroutine_threadsafe(main(), loop) 35 | loop.run_forever() 36 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const {app, BrowserWindow} = require('electron') 3 | 4 | // Keep a global reference of the window object, if you don't, the window will 5 | // be closed automatically when the JavaScript object is garbage collected. 6 | let mainWindow 7 | 8 | function createWindow () { 9 | // Create the browser window. 10 | mainWindow = new BrowserWindow({ 11 | width: 800, 12 | height: 600, 13 | webPreferences: { 14 | nodeIntegration: true 15 | } 16 | }) 17 | 18 | // and load the index.html of the app. 19 | mainWindow.loadURL('http://localhost:8000/hello.html'); 20 | 21 | // Open the DevTools. 22 | // mainWindow.webContents.openDevTools() 23 | 24 | // Emitted when the window is closed. 25 | mainWindow.on('closed', function () { 26 | // Dereference the window object, usually you would store windows 27 | // in an array if your app supports multi windows, this is the time 28 | // when you should delete the corresponding element. 29 | mainWindow = null 30 | }) 31 | } 32 | 33 | // This method will be called when Electron has finished 34 | // initialization and is ready to create browser windows. 35 | // Some APIs can only be used after this event occurs. 36 | app.on('ready', createWindow) 37 | 38 | // Quit when all windows are closed. 39 | app.on('window-all-closed', function () { 40 | // On macOS it is common for applications and their menu bar 41 | // to stay active until the user quits explicitly with Cmd + Q 42 | if (process.platform !== 'darwin') app.quit() 43 | }) 44 | 45 | app.on('activate', function () { 46 | // On macOS it's common to re-create a window in the app when the 47 | // dock icon is clicked and there are no other windows open. 48 | if (mainWindow === null) createWindow() 49 | }) 50 | 51 | // In this file you can include the rest of your app's specific main process 52 | // code. You can also put them in separate files and require them here. 53 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.14.7", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.7.tgz", 10 | "integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==", 11 | "dev": true 12 | }, 13 | "ajv": { 14 | "version": "6.10.0", 15 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 16 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 17 | "dev": true, 18 | "requires": { 19 | "fast-deep-equal": "^2.0.1", 20 | "fast-json-stable-stringify": "^2.0.0", 21 | "json-schema-traverse": "^0.4.1", 22 | "uri-js": "^4.2.2" 23 | } 24 | }, 25 | "ansi-regex": { 26 | "version": "2.1.1", 27 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 28 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 29 | "dev": true 30 | }, 31 | "array-find-index": { 32 | "version": "1.0.2", 33 | "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", 34 | "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", 35 | "dev": true 36 | }, 37 | "asn1": { 38 | "version": "0.2.4", 39 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 40 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 41 | "dev": true, 42 | "requires": { 43 | "safer-buffer": "~2.1.0" 44 | } 45 | }, 46 | "assert-plus": { 47 | "version": "1.0.0", 48 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 49 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 50 | "dev": true 51 | }, 52 | "asynckit": { 53 | "version": "0.4.0", 54 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 55 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 56 | "dev": true 57 | }, 58 | "aws-sign2": { 59 | "version": "0.7.0", 60 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 61 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", 62 | "dev": true 63 | }, 64 | "aws4": { 65 | "version": "1.8.0", 66 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 67 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", 68 | "dev": true 69 | }, 70 | "bcrypt-pbkdf": { 71 | "version": "1.0.2", 72 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 73 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 74 | "dev": true, 75 | "requires": { 76 | "tweetnacl": "^0.14.3" 77 | } 78 | }, 79 | "buffer-from": { 80 | "version": "1.1.1", 81 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 82 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 83 | "dev": true 84 | }, 85 | "camelcase": { 86 | "version": "2.1.1", 87 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 88 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", 89 | "dev": true 90 | }, 91 | "camelcase-keys": { 92 | "version": "2.1.0", 93 | "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", 94 | "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", 95 | "dev": true, 96 | "requires": { 97 | "camelcase": "^2.0.0", 98 | "map-obj": "^1.0.0" 99 | } 100 | }, 101 | "caseless": { 102 | "version": "0.12.0", 103 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 104 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", 105 | "dev": true 106 | }, 107 | "code-point-at": { 108 | "version": "1.1.0", 109 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 110 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 111 | "dev": true 112 | }, 113 | "combined-stream": { 114 | "version": "1.0.8", 115 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 116 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 117 | "dev": true, 118 | "requires": { 119 | "delayed-stream": "~1.0.0" 120 | } 121 | }, 122 | "concat-stream": { 123 | "version": "1.6.2", 124 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 125 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 126 | "dev": true, 127 | "requires": { 128 | "buffer-from": "^1.0.0", 129 | "inherits": "^2.0.3", 130 | "readable-stream": "^2.2.2", 131 | "typedarray": "^0.0.6" 132 | }, 133 | "dependencies": { 134 | "isarray": { 135 | "version": "1.0.0", 136 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 137 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 138 | "dev": true 139 | }, 140 | "readable-stream": { 141 | "version": "2.3.6", 142 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 143 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 144 | "dev": true, 145 | "requires": { 146 | "core-util-is": "~1.0.0", 147 | "inherits": "~2.0.3", 148 | "isarray": "~1.0.0", 149 | "process-nextick-args": "~2.0.0", 150 | "safe-buffer": "~5.1.1", 151 | "string_decoder": "~1.1.1", 152 | "util-deprecate": "~1.0.1" 153 | } 154 | }, 155 | "string_decoder": { 156 | "version": "1.1.1", 157 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 158 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 159 | "dev": true, 160 | "requires": { 161 | "safe-buffer": "~5.1.0" 162 | } 163 | } 164 | } 165 | }, 166 | "core-util-is": { 167 | "version": "1.0.2", 168 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 169 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 170 | "dev": true 171 | }, 172 | "currently-unhandled": { 173 | "version": "0.4.1", 174 | "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", 175 | "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", 176 | "dev": true, 177 | "requires": { 178 | "array-find-index": "^1.0.1" 179 | } 180 | }, 181 | "dashdash": { 182 | "version": "1.14.1", 183 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 184 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 185 | "dev": true, 186 | "requires": { 187 | "assert-plus": "^1.0.0" 188 | } 189 | }, 190 | "debug": { 191 | "version": "3.2.6", 192 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 193 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 194 | "dev": true, 195 | "requires": { 196 | "ms": "^2.1.1" 197 | } 198 | }, 199 | "decamelize": { 200 | "version": "1.2.0", 201 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 202 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 203 | "dev": true 204 | }, 205 | "deep-extend": { 206 | "version": "0.6.0", 207 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 208 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 209 | "dev": true 210 | }, 211 | "delayed-stream": { 212 | "version": "1.0.0", 213 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 214 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 215 | "dev": true 216 | }, 217 | "ecc-jsbn": { 218 | "version": "0.1.2", 219 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 220 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 221 | "dev": true, 222 | "requires": { 223 | "jsbn": "~0.1.0", 224 | "safer-buffer": "^2.1.0" 225 | } 226 | }, 227 | "electron": { 228 | "version": "5.0.2", 229 | "resolved": "https://registry.npmjs.org/electron/-/electron-5.0.2.tgz", 230 | "integrity": "sha512-bUHKQhyuOen/q8iHTkrnzqB9CAwBDI+vHbeu21kpq2bqAD+t25yfrmUEcYHaPL4fZOAhk6nnRqskF6/Xd+aZxg==", 231 | "dev": true, 232 | "requires": { 233 | "@types/node": "^10.12.18", 234 | "electron-download": "^4.1.0", 235 | "extract-zip": "^1.0.3" 236 | } 237 | }, 238 | "electron-download": { 239 | "version": "4.1.1", 240 | "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", 241 | "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", 242 | "dev": true, 243 | "requires": { 244 | "debug": "^3.0.0", 245 | "env-paths": "^1.0.0", 246 | "fs-extra": "^4.0.1", 247 | "minimist": "^1.2.0", 248 | "nugget": "^2.0.1", 249 | "path-exists": "^3.0.0", 250 | "rc": "^1.2.1", 251 | "semver": "^5.4.1", 252 | "sumchecker": "^2.0.2" 253 | } 254 | }, 255 | "env-paths": { 256 | "version": "1.0.0", 257 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", 258 | "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", 259 | "dev": true 260 | }, 261 | "error-ex": { 262 | "version": "1.3.2", 263 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 264 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 265 | "dev": true, 266 | "requires": { 267 | "is-arrayish": "^0.2.1" 268 | } 269 | }, 270 | "extend": { 271 | "version": "3.0.2", 272 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 273 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 274 | "dev": true 275 | }, 276 | "extract-zip": { 277 | "version": "1.6.7", 278 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", 279 | "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", 280 | "dev": true, 281 | "requires": { 282 | "concat-stream": "1.6.2", 283 | "debug": "2.6.9", 284 | "mkdirp": "0.5.1", 285 | "yauzl": "2.4.1" 286 | }, 287 | "dependencies": { 288 | "debug": { 289 | "version": "2.6.9", 290 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 291 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 292 | "dev": true, 293 | "requires": { 294 | "ms": "2.0.0" 295 | } 296 | }, 297 | "ms": { 298 | "version": "2.0.0", 299 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 300 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 301 | "dev": true 302 | } 303 | } 304 | }, 305 | "extsprintf": { 306 | "version": "1.3.0", 307 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 308 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", 309 | "dev": true 310 | }, 311 | "fast-deep-equal": { 312 | "version": "2.0.1", 313 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 314 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 315 | "dev": true 316 | }, 317 | "fast-json-stable-stringify": { 318 | "version": "2.0.0", 319 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 320 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 321 | "dev": true 322 | }, 323 | "fd-slicer": { 324 | "version": "1.0.1", 325 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", 326 | "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", 327 | "dev": true, 328 | "requires": { 329 | "pend": "~1.2.0" 330 | } 331 | }, 332 | "find-up": { 333 | "version": "1.1.2", 334 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 335 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 336 | "dev": true, 337 | "requires": { 338 | "path-exists": "^2.0.0", 339 | "pinkie-promise": "^2.0.0" 340 | }, 341 | "dependencies": { 342 | "path-exists": { 343 | "version": "2.1.0", 344 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 345 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 346 | "dev": true, 347 | "requires": { 348 | "pinkie-promise": "^2.0.0" 349 | } 350 | } 351 | } 352 | }, 353 | "forever-agent": { 354 | "version": "0.6.1", 355 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 356 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", 357 | "dev": true 358 | }, 359 | "form-data": { 360 | "version": "2.3.3", 361 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 362 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 363 | "dev": true, 364 | "requires": { 365 | "asynckit": "^0.4.0", 366 | "combined-stream": "^1.0.6", 367 | "mime-types": "^2.1.12" 368 | } 369 | }, 370 | "fs-extra": { 371 | "version": "4.0.3", 372 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", 373 | "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", 374 | "dev": true, 375 | "requires": { 376 | "graceful-fs": "^4.1.2", 377 | "jsonfile": "^4.0.0", 378 | "universalify": "^0.1.0" 379 | } 380 | }, 381 | "get-stdin": { 382 | "version": "4.0.1", 383 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", 384 | "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", 385 | "dev": true 386 | }, 387 | "getpass": { 388 | "version": "0.1.7", 389 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 390 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 391 | "dev": true, 392 | "requires": { 393 | "assert-plus": "^1.0.0" 394 | } 395 | }, 396 | "graceful-fs": { 397 | "version": "4.1.15", 398 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 399 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 400 | "dev": true 401 | }, 402 | "har-schema": { 403 | "version": "2.0.0", 404 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 405 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", 406 | "dev": true 407 | }, 408 | "har-validator": { 409 | "version": "5.1.3", 410 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 411 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 412 | "dev": true, 413 | "requires": { 414 | "ajv": "^6.5.5", 415 | "har-schema": "^2.0.0" 416 | } 417 | }, 418 | "hosted-git-info": { 419 | "version": "2.7.1", 420 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", 421 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", 422 | "dev": true 423 | }, 424 | "http-signature": { 425 | "version": "1.2.0", 426 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 427 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 428 | "dev": true, 429 | "requires": { 430 | "assert-plus": "^1.0.0", 431 | "jsprim": "^1.2.2", 432 | "sshpk": "^1.7.0" 433 | } 434 | }, 435 | "indent-string": { 436 | "version": "2.1.0", 437 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", 438 | "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", 439 | "dev": true, 440 | "requires": { 441 | "repeating": "^2.0.0" 442 | } 443 | }, 444 | "inherits": { 445 | "version": "2.0.3", 446 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 447 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 448 | "dev": true 449 | }, 450 | "ini": { 451 | "version": "1.3.5", 452 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 453 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 454 | "dev": true 455 | }, 456 | "is-arrayish": { 457 | "version": "0.2.1", 458 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 459 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 460 | "dev": true 461 | }, 462 | "is-finite": { 463 | "version": "1.0.2", 464 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 465 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 466 | "dev": true, 467 | "requires": { 468 | "number-is-nan": "^1.0.0" 469 | } 470 | }, 471 | "is-fullwidth-code-point": { 472 | "version": "1.0.0", 473 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 474 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 475 | "dev": true, 476 | "requires": { 477 | "number-is-nan": "^1.0.0" 478 | } 479 | }, 480 | "is-typedarray": { 481 | "version": "1.0.0", 482 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 483 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 484 | "dev": true 485 | }, 486 | "is-utf8": { 487 | "version": "0.2.1", 488 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 489 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", 490 | "dev": true 491 | }, 492 | "isarray": { 493 | "version": "0.0.1", 494 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 495 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 496 | "dev": true 497 | }, 498 | "isstream": { 499 | "version": "0.1.2", 500 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 501 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 502 | "dev": true 503 | }, 504 | "jsbn": { 505 | "version": "0.1.1", 506 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 507 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 508 | "dev": true 509 | }, 510 | "json-schema": { 511 | "version": "0.2.3", 512 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 513 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", 514 | "dev": true 515 | }, 516 | "json-schema-traverse": { 517 | "version": "0.4.1", 518 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 519 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 520 | "dev": true 521 | }, 522 | "json-stringify-safe": { 523 | "version": "5.0.1", 524 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 525 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 526 | "dev": true 527 | }, 528 | "jsonfile": { 529 | "version": "4.0.0", 530 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 531 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 532 | "dev": true, 533 | "requires": { 534 | "graceful-fs": "^4.1.6" 535 | } 536 | }, 537 | "jsprim": { 538 | "version": "1.4.1", 539 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 540 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 541 | "dev": true, 542 | "requires": { 543 | "assert-plus": "1.0.0", 544 | "extsprintf": "1.3.0", 545 | "json-schema": "0.2.3", 546 | "verror": "1.10.0" 547 | } 548 | }, 549 | "load-json-file": { 550 | "version": "1.1.0", 551 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 552 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 553 | "dev": true, 554 | "requires": { 555 | "graceful-fs": "^4.1.2", 556 | "parse-json": "^2.2.0", 557 | "pify": "^2.0.0", 558 | "pinkie-promise": "^2.0.0", 559 | "strip-bom": "^2.0.0" 560 | } 561 | }, 562 | "loud-rejection": { 563 | "version": "1.6.0", 564 | "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", 565 | "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", 566 | "dev": true, 567 | "requires": { 568 | "currently-unhandled": "^0.4.1", 569 | "signal-exit": "^3.0.0" 570 | } 571 | }, 572 | "map-obj": { 573 | "version": "1.0.1", 574 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", 575 | "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", 576 | "dev": true 577 | }, 578 | "meow": { 579 | "version": "3.7.0", 580 | "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", 581 | "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", 582 | "dev": true, 583 | "requires": { 584 | "camelcase-keys": "^2.0.0", 585 | "decamelize": "^1.1.2", 586 | "loud-rejection": "^1.0.0", 587 | "map-obj": "^1.0.1", 588 | "minimist": "^1.1.3", 589 | "normalize-package-data": "^2.3.4", 590 | "object-assign": "^4.0.1", 591 | "read-pkg-up": "^1.0.1", 592 | "redent": "^1.0.0", 593 | "trim-newlines": "^1.0.0" 594 | } 595 | }, 596 | "mime-db": { 597 | "version": "1.40.0", 598 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 599 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", 600 | "dev": true 601 | }, 602 | "mime-types": { 603 | "version": "2.1.24", 604 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 605 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 606 | "dev": true, 607 | "requires": { 608 | "mime-db": "1.40.0" 609 | } 610 | }, 611 | "minimist": { 612 | "version": "1.2.0", 613 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 614 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 615 | "dev": true 616 | }, 617 | "mkdirp": { 618 | "version": "0.5.1", 619 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 620 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 621 | "dev": true, 622 | "requires": { 623 | "minimist": "0.0.8" 624 | }, 625 | "dependencies": { 626 | "minimist": { 627 | "version": "0.0.8", 628 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 629 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 630 | "dev": true 631 | } 632 | } 633 | }, 634 | "ms": { 635 | "version": "2.1.1", 636 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 637 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 638 | "dev": true 639 | }, 640 | "normalize-package-data": { 641 | "version": "2.5.0", 642 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 643 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 644 | "dev": true, 645 | "requires": { 646 | "hosted-git-info": "^2.1.4", 647 | "resolve": "^1.10.0", 648 | "semver": "2 || 3 || 4 || 5", 649 | "validate-npm-package-license": "^3.0.1" 650 | } 651 | }, 652 | "nugget": { 653 | "version": "2.0.1", 654 | "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", 655 | "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", 656 | "dev": true, 657 | "requires": { 658 | "debug": "^2.1.3", 659 | "minimist": "^1.1.0", 660 | "pretty-bytes": "^1.0.2", 661 | "progress-stream": "^1.1.0", 662 | "request": "^2.45.0", 663 | "single-line-log": "^1.1.2", 664 | "throttleit": "0.0.2" 665 | }, 666 | "dependencies": { 667 | "debug": { 668 | "version": "2.6.9", 669 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 670 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 671 | "dev": true, 672 | "requires": { 673 | "ms": "2.0.0" 674 | } 675 | }, 676 | "ms": { 677 | "version": "2.0.0", 678 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 679 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 680 | "dev": true 681 | } 682 | } 683 | }, 684 | "number-is-nan": { 685 | "version": "1.0.1", 686 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 687 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 688 | "dev": true 689 | }, 690 | "oauth-sign": { 691 | "version": "0.9.0", 692 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 693 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 694 | "dev": true 695 | }, 696 | "object-assign": { 697 | "version": "4.1.1", 698 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 699 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 700 | "dev": true 701 | }, 702 | "object-keys": { 703 | "version": "0.4.0", 704 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", 705 | "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", 706 | "dev": true 707 | }, 708 | "parse-json": { 709 | "version": "2.2.0", 710 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 711 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 712 | "dev": true, 713 | "requires": { 714 | "error-ex": "^1.2.0" 715 | } 716 | }, 717 | "path-exists": { 718 | "version": "3.0.0", 719 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 720 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 721 | "dev": true 722 | }, 723 | "path-parse": { 724 | "version": "1.0.6", 725 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 726 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 727 | "dev": true 728 | }, 729 | "path-type": { 730 | "version": "1.1.0", 731 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 732 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 733 | "dev": true, 734 | "requires": { 735 | "graceful-fs": "^4.1.2", 736 | "pify": "^2.0.0", 737 | "pinkie-promise": "^2.0.0" 738 | } 739 | }, 740 | "pend": { 741 | "version": "1.2.0", 742 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 743 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", 744 | "dev": true 745 | }, 746 | "performance-now": { 747 | "version": "2.1.0", 748 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 749 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 750 | "dev": true 751 | }, 752 | "pify": { 753 | "version": "2.3.0", 754 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 755 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 756 | "dev": true 757 | }, 758 | "pinkie": { 759 | "version": "2.0.4", 760 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 761 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 762 | "dev": true 763 | }, 764 | "pinkie-promise": { 765 | "version": "2.0.1", 766 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 767 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 768 | "dev": true, 769 | "requires": { 770 | "pinkie": "^2.0.0" 771 | } 772 | }, 773 | "pretty-bytes": { 774 | "version": "1.0.4", 775 | "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", 776 | "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", 777 | "dev": true, 778 | "requires": { 779 | "get-stdin": "^4.0.1", 780 | "meow": "^3.1.0" 781 | } 782 | }, 783 | "process-nextick-args": { 784 | "version": "2.0.0", 785 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 786 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 787 | "dev": true 788 | }, 789 | "progress-stream": { 790 | "version": "1.2.0", 791 | "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", 792 | "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", 793 | "dev": true, 794 | "requires": { 795 | "speedometer": "~0.1.2", 796 | "through2": "~0.2.3" 797 | } 798 | }, 799 | "psl": { 800 | "version": "1.1.31", 801 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", 802 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", 803 | "dev": true 804 | }, 805 | "punycode": { 806 | "version": "2.1.1", 807 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 808 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 809 | "dev": true 810 | }, 811 | "qs": { 812 | "version": "6.5.2", 813 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 814 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", 815 | "dev": true 816 | }, 817 | "rc": { 818 | "version": "1.2.8", 819 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 820 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 821 | "dev": true, 822 | "requires": { 823 | "deep-extend": "^0.6.0", 824 | "ini": "~1.3.0", 825 | "minimist": "^1.2.0", 826 | "strip-json-comments": "~2.0.1" 827 | } 828 | }, 829 | "read-pkg": { 830 | "version": "1.1.0", 831 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 832 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 833 | "dev": true, 834 | "requires": { 835 | "load-json-file": "^1.0.0", 836 | "normalize-package-data": "^2.3.2", 837 | "path-type": "^1.0.0" 838 | } 839 | }, 840 | "read-pkg-up": { 841 | "version": "1.0.1", 842 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 843 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 844 | "dev": true, 845 | "requires": { 846 | "find-up": "^1.0.0", 847 | "read-pkg": "^1.0.0" 848 | } 849 | }, 850 | "readable-stream": { 851 | "version": "1.1.14", 852 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 853 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 854 | "dev": true, 855 | "requires": { 856 | "core-util-is": "~1.0.0", 857 | "inherits": "~2.0.1", 858 | "isarray": "0.0.1", 859 | "string_decoder": "~0.10.x" 860 | } 861 | }, 862 | "redent": { 863 | "version": "1.0.0", 864 | "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", 865 | "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", 866 | "dev": true, 867 | "requires": { 868 | "indent-string": "^2.1.0", 869 | "strip-indent": "^1.0.1" 870 | } 871 | }, 872 | "repeating": { 873 | "version": "2.0.1", 874 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 875 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 876 | "dev": true, 877 | "requires": { 878 | "is-finite": "^1.0.0" 879 | } 880 | }, 881 | "request": { 882 | "version": "2.88.0", 883 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 884 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 885 | "dev": true, 886 | "requires": { 887 | "aws-sign2": "~0.7.0", 888 | "aws4": "^1.8.0", 889 | "caseless": "~0.12.0", 890 | "combined-stream": "~1.0.6", 891 | "extend": "~3.0.2", 892 | "forever-agent": "~0.6.1", 893 | "form-data": "~2.3.2", 894 | "har-validator": "~5.1.0", 895 | "http-signature": "~1.2.0", 896 | "is-typedarray": "~1.0.0", 897 | "isstream": "~0.1.2", 898 | "json-stringify-safe": "~5.0.1", 899 | "mime-types": "~2.1.19", 900 | "oauth-sign": "~0.9.0", 901 | "performance-now": "^2.1.0", 902 | "qs": "~6.5.2", 903 | "safe-buffer": "^5.1.2", 904 | "tough-cookie": "~2.4.3", 905 | "tunnel-agent": "^0.6.0", 906 | "uuid": "^3.3.2" 907 | } 908 | }, 909 | "resolve": { 910 | "version": "1.11.0", 911 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", 912 | "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", 913 | "dev": true, 914 | "requires": { 915 | "path-parse": "^1.0.6" 916 | } 917 | }, 918 | "safe-buffer": { 919 | "version": "5.1.2", 920 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 921 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 922 | "dev": true 923 | }, 924 | "safer-buffer": { 925 | "version": "2.1.2", 926 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 927 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 928 | "dev": true 929 | }, 930 | "semver": { 931 | "version": "5.7.0", 932 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 933 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 934 | "dev": true 935 | }, 936 | "signal-exit": { 937 | "version": "3.0.2", 938 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 939 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 940 | "dev": true 941 | }, 942 | "single-line-log": { 943 | "version": "1.1.2", 944 | "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", 945 | "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", 946 | "dev": true, 947 | "requires": { 948 | "string-width": "^1.0.1" 949 | } 950 | }, 951 | "spdx-correct": { 952 | "version": "3.1.0", 953 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 954 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 955 | "dev": true, 956 | "requires": { 957 | "spdx-expression-parse": "^3.0.0", 958 | "spdx-license-ids": "^3.0.0" 959 | } 960 | }, 961 | "spdx-exceptions": { 962 | "version": "2.2.0", 963 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 964 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 965 | "dev": true 966 | }, 967 | "spdx-expression-parse": { 968 | "version": "3.0.0", 969 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 970 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 971 | "dev": true, 972 | "requires": { 973 | "spdx-exceptions": "^2.1.0", 974 | "spdx-license-ids": "^3.0.0" 975 | } 976 | }, 977 | "spdx-license-ids": { 978 | "version": "3.0.4", 979 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", 980 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", 981 | "dev": true 982 | }, 983 | "speedometer": { 984 | "version": "0.1.4", 985 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", 986 | "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", 987 | "dev": true 988 | }, 989 | "sshpk": { 990 | "version": "1.16.1", 991 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 992 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 993 | "dev": true, 994 | "requires": { 995 | "asn1": "~0.2.3", 996 | "assert-plus": "^1.0.0", 997 | "bcrypt-pbkdf": "^1.0.0", 998 | "dashdash": "^1.12.0", 999 | "ecc-jsbn": "~0.1.1", 1000 | "getpass": "^0.1.1", 1001 | "jsbn": "~0.1.0", 1002 | "safer-buffer": "^2.0.2", 1003 | "tweetnacl": "~0.14.0" 1004 | } 1005 | }, 1006 | "string-width": { 1007 | "version": "1.0.2", 1008 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1009 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1010 | "dev": true, 1011 | "requires": { 1012 | "code-point-at": "^1.0.0", 1013 | "is-fullwidth-code-point": "^1.0.0", 1014 | "strip-ansi": "^3.0.0" 1015 | } 1016 | }, 1017 | "string_decoder": { 1018 | "version": "0.10.31", 1019 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1020 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 1021 | "dev": true 1022 | }, 1023 | "strip-ansi": { 1024 | "version": "3.0.1", 1025 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1026 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1027 | "dev": true, 1028 | "requires": { 1029 | "ansi-regex": "^2.0.0" 1030 | } 1031 | }, 1032 | "strip-bom": { 1033 | "version": "2.0.0", 1034 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 1035 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 1036 | "dev": true, 1037 | "requires": { 1038 | "is-utf8": "^0.2.0" 1039 | } 1040 | }, 1041 | "strip-indent": { 1042 | "version": "1.0.1", 1043 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", 1044 | "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", 1045 | "dev": true, 1046 | "requires": { 1047 | "get-stdin": "^4.0.1" 1048 | } 1049 | }, 1050 | "strip-json-comments": { 1051 | "version": "2.0.1", 1052 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1053 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1054 | "dev": true 1055 | }, 1056 | "sumchecker": { 1057 | "version": "2.0.2", 1058 | "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", 1059 | "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", 1060 | "dev": true, 1061 | "requires": { 1062 | "debug": "^2.2.0" 1063 | }, 1064 | "dependencies": { 1065 | "debug": { 1066 | "version": "2.6.9", 1067 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1068 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1069 | "dev": true, 1070 | "requires": { 1071 | "ms": "2.0.0" 1072 | } 1073 | }, 1074 | "ms": { 1075 | "version": "2.0.0", 1076 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1077 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1078 | "dev": true 1079 | } 1080 | } 1081 | }, 1082 | "throttleit": { 1083 | "version": "0.0.2", 1084 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", 1085 | "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", 1086 | "dev": true 1087 | }, 1088 | "through2": { 1089 | "version": "0.2.3", 1090 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", 1091 | "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", 1092 | "dev": true, 1093 | "requires": { 1094 | "readable-stream": "~1.1.9", 1095 | "xtend": "~2.1.1" 1096 | } 1097 | }, 1098 | "tough-cookie": { 1099 | "version": "2.4.3", 1100 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 1101 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 1102 | "dev": true, 1103 | "requires": { 1104 | "psl": "^1.1.24", 1105 | "punycode": "^1.4.1" 1106 | }, 1107 | "dependencies": { 1108 | "punycode": { 1109 | "version": "1.4.1", 1110 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1111 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 1112 | "dev": true 1113 | } 1114 | } 1115 | }, 1116 | "trim-newlines": { 1117 | "version": "1.0.0", 1118 | "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", 1119 | "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", 1120 | "dev": true 1121 | }, 1122 | "tunnel-agent": { 1123 | "version": "0.6.0", 1124 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1125 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1126 | "dev": true, 1127 | "requires": { 1128 | "safe-buffer": "^5.0.1" 1129 | } 1130 | }, 1131 | "tweetnacl": { 1132 | "version": "0.14.5", 1133 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1134 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1135 | "dev": true 1136 | }, 1137 | "typedarray": { 1138 | "version": "0.0.6", 1139 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1140 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 1141 | "dev": true 1142 | }, 1143 | "universalify": { 1144 | "version": "0.1.2", 1145 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 1146 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 1147 | "dev": true 1148 | }, 1149 | "uri-js": { 1150 | "version": "4.2.2", 1151 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1152 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1153 | "dev": true, 1154 | "requires": { 1155 | "punycode": "^2.1.0" 1156 | } 1157 | }, 1158 | "util-deprecate": { 1159 | "version": "1.0.2", 1160 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1161 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1162 | "dev": true 1163 | }, 1164 | "uuid": { 1165 | "version": "3.3.2", 1166 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 1167 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 1168 | "dev": true 1169 | }, 1170 | "validate-npm-package-license": { 1171 | "version": "3.0.4", 1172 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1173 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1174 | "dev": true, 1175 | "requires": { 1176 | "spdx-correct": "^3.0.0", 1177 | "spdx-expression-parse": "^3.0.0" 1178 | } 1179 | }, 1180 | "verror": { 1181 | "version": "1.10.0", 1182 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1183 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1184 | "dev": true, 1185 | "requires": { 1186 | "assert-plus": "^1.0.0", 1187 | "core-util-is": "1.0.2", 1188 | "extsprintf": "^1.2.0" 1189 | } 1190 | }, 1191 | "xtend": { 1192 | "version": "2.1.2", 1193 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", 1194 | "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", 1195 | "dev": true, 1196 | "requires": { 1197 | "object-keys": "~0.4.0" 1198 | } 1199 | }, 1200 | "yauzl": { 1201 | "version": "2.4.1", 1202 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", 1203 | "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", 1204 | "dev": true, 1205 | "requires": { 1206 | "fd-slicer": "~1.0.1" 1207 | } 1208 | } 1209 | } 1210 | } 1211 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Eelectron-quick-start", 3 | "version": "1.0.0", 4 | "description": "A minimal Eelectron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "repository": "https://github.com/electron/electron-quick-start", 10 | "keywords": [ 11 | "Electron", 12 | "quick", 13 | "start", 14 | "tutorial", 15 | "demo" 16 | ], 17 | "author": "GitHub", 18 | "license": "CC0-1.0", 19 | "devDependencies": { 20 | "electron": "^5.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/08 - Eelectron-quick-start/web/favicon.ico -------------------------------------------------------------------------------- /examples/08 - Eelectron-quick-start/web/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, World! 5 | 6 | 7 | 8 | 19 | 20 | 21 | 22 | Hello, World! 23 | 24 | -------------------------------------------------------------------------------- /examples/08 - disable_cache/disable_cache.py: -------------------------------------------------------------------------------- 1 | import async_eel 2 | import asyncio 3 | 4 | 5 | loop = asyncio.get_event_loop() 6 | 7 | 8 | async def main(): 9 | # Set web files folder and optionally specify which file types to check for eel.expose() 10 | async_eel.init('web') 11 | 12 | # disable_cache now defaults to True so this isn't strictly necessary. Set it to False to enable caching. 13 | await async_eel.start('disable_cache.html', size=(300, 200), disable_cache=True) # Start 14 | 15 | 16 | if __name__ == '__main__': 17 | asyncio.run_coroutine_threadsafe(main(), loop) 18 | loop.run_forever() 19 | 20 | -------------------------------------------------------------------------------- /examples/08 - disable_cache/web/disable_cache.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, World! 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Cache Proof! 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/08 - disable_cache/web/dont_cache_me.js: -------------------------------------------------------------------------------- 1 | console.log("Check the network activity to see that this file isn't getting cached."); 2 | -------------------------------------------------------------------------------- /examples/08 - disable_cache/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuyan/async-Eel/141e49e2ea7f1d33565df281bac418526b9d86cd/examples/08 - disable_cache/web/favicon.ico -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | whichcraft==0.4.1 2 | aiohttp 3 | expiringdict 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | from setuptools import setup 3 | 4 | setup( 5 | name='async-Eel', 6 | version='1.0-b4', 7 | author='namuyang', 8 | author_email='thhjuuyahoo.co.jp', 9 | packages=['async_eel'], 10 | package_data={ 11 | 'async_eel': ['eel.js'], 12 | }, 13 | install_requires=['aiohttp', 'future', 'whichcraft'], 14 | python_requires='>=3.6', 15 | description='For little HTML GUI applications, with easy Python/JS interop', 16 | long_description=open('README.md', encoding='utf-8').read(), 17 | long_description_content_type='text/markdown', 18 | keywords=['gui', 'html', 'javascript', 'electron'], 19 | url='https://github.com/namuyan/async-Eel', 20 | ) 21 | --------------------------------------------------------------------------------