[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n# 4 space indentation\n[*.{py,ini}]\nindent_style = space\nindent_size = 4\n\n# 2 space indentation\n[*.{css,js,html,yml}]\nindent_style = space\nindent_size = 2\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": ".github/workflows/push.yaml",
    "content": "name: CI\n\non:\n  pull_request:\n    branches:\n    - master\n  push:\n    branches:\n      - '**'\n    tags:\n      - 'release-candidate'\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  cleanup:\n    runs-on: ubuntu-20.04\n    steps:\n      - name: Clean Up Release Candiate Tag\n        if: ${{ github.ref == 'refs/tags/release-candidate' }}\n        uses: dev-drprasad/delete-tag-and-release@v0.2.0\n        with:\n          tag_name: release-candidate\n          delete_release: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n  all:\n    runs-on: ubuntu-20.04\n    steps:\n      # Setup\n      - name: Checkout Repository\n        uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.pull_request.head.sha }}\n      - name: Setup Python\n        uses: actions/setup-python@v1\n        with:\n          python-version: 3.8\n      - uses: browser-actions/setup-chrome@latest\n      - name: Setup environment\n        run: |\n          make env.up\n      # Build and Test\n      - name: Run CI jobs\n        run: |\n          make ci\n      # Publish documentation\n      - name: Set default env variables\n        run: |\n          echo \"GH_PAGES_BRANCH=gh-pages-test\" >> $GITHUB_ENV\n      - name: Update env variables for release\n        if: startsWith(github.ref, 'refs/tags/v')\n        run: |\n          echo \"GH_PAGES_BRANCH=gh-pages\" >> $GITHUB_ENV\n      - name: Deploy to GitHub Pages\n        uses: crazy-max/ghaction-github-pages@v2\n        if: ${{ github.event_name != 'pull_request' }}\n        with:\n          target_branch: ${{ env.GH_PAGES_BRANCH }}\n          build_dir: gh-pages-build\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      # Release\n      - name: Check Commit Messages\n        run: |\n          release check-commit-messages\n      - name: Generate Changelog\n        run: |\n          release changelog > changelog.md\n      - name: Delete Previous Master Github Release\n        if: ${{ github.ref == 'refs/heads/master' }}\n        uses: dev-drprasad/delete-tag-and-release@v0.2.0\n        with:\n          tag_name: master\n          delete_release: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Publish Master Github Release\n        if: ${{ github.ref == 'refs/heads/master' }}\n        run: |\n          gh release create master ./dist/*.whl -F changelog.md --prerelease --target master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Publish Github Release\n        if: ${{ github.ref == 'refs/tags/release-candidate' }}\n        run: |\n          gh release create v`release version` ./dist/*.whl -F changelog.md\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Publish PyPI\n        if: ${{ github.ref == 'refs/tags/release-candidate' }}\n        uses: pypa/gh-action-pypi-publish@master\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n          repository_url: https://upload.pypi.org/legacy/\n          skip_existing: false\n"
  },
  {
    "path": ".gitignore",
    "content": "venv\n.idea\n.vscode\n.pytest_cache\n__pycache__\ngh-pages-build\n\ndebug\nexamples/*/screenshot.png\nvuecli/js\nexamples_static\n\nvuepy.egg-info\ndist\nbuild\nvue/__version__.py\nchangelog.md\n"
  },
  {
    "path": ".gitpod.Dockerfile",
    "content": "FROM gitpod/workspace-full\n\nUSER gitpod\n\n# Install Google key\nRUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -\nRUN sudo sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'\n\n# Install custom tools, runtime, etc.\nRUN sudo apt-get update && sudo apt-get install -y google-chrome-stable && sudo rm -rf /var/lib/apt/lists/*\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "image:\n  file: .gitpod.Dockerfile\ntasks:\n- init: make env.up\n  command: make serve\nports:\n  - port: 8000\n    onOpen: open-browser\n  - port: 8001\n    onOpen: ignore\n  - port: 5000\n    onOpen: ignore\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing a PR\nFirst of: Thanks for contributing a PR to this project!!\n\nThere a four main guidelines I try to follow in this projects:\n* Write clean code according to Bob Martins book\n* Have tests!!\n  * Unit tests and selenium tests\n  * In general I try to use the examples in the vue.js documentation as test cases to make sure vue.py works as vue.js\n* If a new feature is implemented, please also provide documentation\n* Each commit needs a certain format `[type] commit message`\n  * This allows a automated generation of the changelog\n  * PRs get squashed and merged, so commit messages in PRs can be arbitrary\n  * type can be one of the following\n    * feature: use when adding new features\n    * bugfix: use when a bug gets fixed but function stays the same\n    * internal: use when refactoring or no user-facing changed are made\n    * docs: use when updating documentation\n    * tooling: use when changing tooling\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Stefan Hoelzl\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\n"
  },
  {
    "path": "Makefile",
    "content": "PYTHONPATH=.:stubs\n\n.PHONY: env.pip\nenv.pip:\n\tpip install -r requirements.txt\n\tpip install -e .\n\n.PHONY: env.chrome\nenv.chrome:\n\tpython tests/selenium/chromedriver.py\n\n.PHONY: env.up\nenv.up: env.pip env.chrome\n\n.PHONY: env.down\nenv.down:\n\tgit clean -xdf --exclude .idea --exclude venv --exclude debug\n\tpip freeze > /tmp/vuepy-delete-requirements.txt\n\tpip uninstall -y -r /tmp/vuepy-delete-requirements.txt\n\n.PHONY: serve\nserve:\n\tpython -m http.server 8000\n\n.PHONY: run\nrun:\n\tcd ${APP} && vue-cli deploy flask\n\n.PHONY: tests.selenium\ntests.selenium:\n\tPYTHONPATH=$(PYTHONPATH) pytest tests/selenium\n\n.PHONY: tests.unit\ntests.unit:\n\tPYTHONPATH=$(PYTHONPATH) pytest tests/unit\n\n.PHONY: tests.cli\ntests.cli:\n\tPYTHONPATH=$(PYTHONPATH) pytest tests/cli\n\n.PHONY: tests\ntests:\n\tPYTHONPATH=$(PYTHONPATH) pytest tests/${TEST}\n\n.PHONY: format\nformat:\n\tblack --target-version py38 .\n\n.PHONY: lint\nlint:\n\tblack --target-version py38 --check .\n\n.PHONY: build\nbuild:\n\tpython setup.py sdist bdist_wheel\n\n.PHONY: docs\ndocs:\n\trm -Rf gh-pages-build\n\tmkdir gh-pages-build\n\n\tcp -Rf docs/* README.md vue gh-pages-build\n\tcp -Rf examples_static gh-pages-build/examples\n\tcp examples/index.md gh-pages-build/examples\n\n\tmkdir gh-pages-build/tests\n\tcp -R tests/selenium/_html/* gh-pages-build/tests\n\n\tmkdir gh-pages-build/js\n\tvue-cli package gh-pages-build/js\n\n.PHONY: ci\nci: lint tests build docs\n"
  },
  {
    "path": "README.md",
    "content": "# vue.py\n[![Build Status](https://github.com/stefanhoelzl/vue.py/workflows/CI/badge.svg)](https://github.com/stefanhoelzl/vue.py/actions)\n[![PyPI](https://img.shields.io/pypi/v/vuepy.svg)](https://pypi.org/project/vuepy/)\n[![License](https://img.shields.io/pypi/l/vuepy.svg)](LICENSE)\n\nuse [Vue.js](https://www.vuejs.org) with pure Python\n\nvue.py provides Python bindings for [Vue.js](https://www.vuejs.org).\nIt uses [brython](https://github.com/brython-dev/brython) to run Python in the browser.\n\nHere is a simple example of an vue.py component\n```python\nfrom browser import alert\nfrom vue import VueComponent\n\nclass HelloVuePy(VueComponent):\n    greeting = \"Hello vue.py\"\n\n    def greet(self, event):\n        alert(self.greeting)\n\n    template = \"\"\"\n    <button @click=\"greet\">click me</button>\n    \"\"\"\n\nHelloVuePy(\"#app\")\n```\n\n## Installation\n```bash\n$ pip install vuepy\n```\n\n\n## Development Status\nThe goal is to provide a solution to write fully-featured Vue applications in pure Python.\n\nTo get an overview what currently is supported, have a look at the [Documentation](https://stefanhoelzl.github.io/vue.py/docs/).\n\nHave a look [here](https://stefanhoelzl.github.io/vue.py/planning.html) to see whats planned!\n\nSee also the [Limitations](https://stefanhoelzl.github.io/vue.py/docs/pyjs_bridge.html)\n\n## Documentation\nDocumentation for the last release is available [here](https://stefanhoelzl.github.io/vue.py/docs/).\n\nDocumentation fo the current master branch can be found [here](https://github.com/stefanhoelzl/vue.py/blob/master/docs/docs/index.md).\n\nExamples can be found [here](https://stefanhoelzl.github.io/vue.py/examples).\nThese are vue.py versions of the [Vue.js examples](https://vuejs.org/v2/examples/)\n\n## Performance\nInitial loading times of `vue.py` apps can be very long.\nEspecially when loading a lot of python files.\nStill figuring out how to solve this.\n\nHave not done any peformance tests, but havent noticed any issues with performance\nas soon as the app was fully loaded.\n\n## Development\n### Getting Started\nOpen in [gitpod.io](https://gitpod.io#github.com/stefanhoelzl/vue.py)\n\nGet the code\n```bash\n$ git clone https://github.com/stefanhoelzl/vue.py.git\n$ cd vue.py\n```\n\nOptionally you can create a [venv](https://docs.python.org/3.8/library/venv.html)\n```bash\n$ python -m venv venv\n$ source venv/bin/activate\n```\n\nInstall required python packages, the chromedriver for selenium and brython\n```bash\n$ make env.up\n```\n\nFormat the code\n```bash\n$ make format\n```\n\nRun tests\n```bash\n$ make tests           # runs all tets\n$ make tests.unit      # runs unit tests\n$ make tests.selenium  # runs selenium tests\n$ make tests.cli       # runs cli tests\n$ make tests TEST=cli/test_provider.py::TestRenderIndex::test_defaults # run explicit test\n```\n\nRun an example\n```bash\n$ make run APP=examples/tree_view  # makes example available on port 5000\n```\n\nReset your development environment\n_(clean up, reinstall packages and redownload needed files)_\n```bash\n$ make env.down\n$ make env.up\n```\n\nPublish a new release\n```bash\n$ release release-candidate\n```\n\n### Contributing\nsee [CONTRIBUTING](https://github.com/stefanhoelzl/vue.py/blob/master/CONTRIBUTING.md)\n\n## License\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/stefanhoelzl/vue.py/blob/master/LICENSE) file for details\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "theme: jekyll-theme-cayman\ntitle: vue.py\ndescription: Pythonic Vue.js\ninclude:\n  - __init__.py\n  - __entry_point__.py\nnavigation:\n  - title: Start\n    link: /\n  - title: Documentation\n    link: docs\n  - title: Gallery\n    link: examples\n  - title: Demo\n    link: https://stefanhoelzl.github.io/mqtt-dashboard/\n"
  },
  {
    "path": "docs/_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ site.lang | default: \"en-US\" }}\">\n  <head>\n    <meta charset=\"UTF-8\">\n\n{% seo %}\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"theme-color\" content=\"#157878\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n    <link rel=\"stylesheet\" href=\"{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}\">\n  </head>\n  <body>\n    <a id=\"skip-to-content\" href=\"#content\">Skip to the content.</a>\n\n    <header class=\"page-header\" role=\"banner\">\n      <h1 class=\"project-name\">{{ site.title | default: site.github.repository_name }}</h1>\n      <h2 class=\"project-tagline\">{{ site.description | default: site.github.project_tagline }}</h2>\n        {%- for nav_item in site.navigation -%}\n          <a href=\"{{ nav_item.link | relative_url }}\" class=\"btn\">{{ nav_item.title }}</a>\n        {%- endfor -%}\n        <a href=\"{{ site.github.repository_url }}\" class=\"btn\">View on GitHub</a>\n    </header>\n\n    <main id=\"content\" class=\"main-content\" role=\"main\">\n      {{ content }}\n\n      <footer class=\"site-footer\">\n        <span class=\"site-footer-owner\"><a href=\"{{ site.github.repository_url }}\">{{ site.github.repository_name }}</a> is maintained by <a href=\"{{ site.github.owner_url }}\">{{ site.github.owner_name }}</a>.</span>\n        <span class=\"site-footer-credits\">This page was generated by <a href=\"https://pages.github.com\">GitHub Pages</a>.</span>\n      </footer>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/docs/index.md",
    "content": "# Documentation\n`vue.py` provides bindings for [Vue.js](https://vuejs.org/).\nIf you are not familiar with [Vue.js](https://vuejs.org/) read the [Vue.js Guide](https://vuejs.org/v2/guide/)\nand then get back here to learn how to use [Vue.js](https://vuejs.org/) with pure Python.\n\n## Installation\n\nInstall `vue.py` via `pip`\n```bash\n$ pip install vuepy\n```\n\nor with flask include to deploy apps\n```bash\n$ pip install vuepy[flask]\n```\n\nor from current master branch\n```bash\n$ pip install git+https://github.com/stefanhoelzl/vue.py@master\n```\n\n## First Application\nCreate a folder for your app\n```bash\n$ mkdir app\n$ cd app\n```\n\nand as last step create a `app.py` where you create your Vue Component\n```python\nfrom vue import *\n\nclass App(VueComponent):\n    msg = \"Hello vue.py!\"\n    template = \"<div>{{msg}}</div>\"\nApp(\"#app\")\n```\n\ndeploy your app\n```bash\n$ vue-cli deploy flask\n```\nNow goto [http://localhost:5000](http://localhost:5000) and see your first vue.py app.\n\n## Demo App\nCheckout the [MQTT-Dashboard](https://github.com/stefanhoelzl/mqtt-dashboard/blob/master/app/app.py).\nIt's a little test project to demonstrate some `vue.py` features:\n* uses the Browsers local storage to implement a vue-plugin in python\n* uses a vue.js plugin\n* uses some vue.js components\n* uses a vuex store\n\n## How to use Vue.js concepts\n* [Instance and Components](vue_concepts/instance_components.md)\n* [Data and Methods](vue_concepts/data_methods.md)\n* [Computed Properties and Watchers](vue_concepts/computed_properties.md)\n* [Props](vue_concepts/props.md)\n* [Lifecycle Hooks](vue_concepts/lifecycle_hooks.md)\n* [Customize V-Model](vue_concepts/custom_vmodel.md)\n* [Filter](vue_concepts/filter.md)\n* [Custom Directives](vue_concepts/custom_directives.md)\n* [Plugins and Mixins](vue_concepts/plugins_mixins.md)\n* [Extend](vue_concepts/extend.md)\n* [Render Function](vue_concepts/render_function.md)\n* [Vuex](vue_concepts/vuex.md)\n* [Vue Router](vue_concepts/vue-router.md)\n\n## Management\n* [Configuration](management/configuration.md)\n* [vue-cli](management/cli.md)\n\n## Python/Javascript Bridge\n[here](pyjs_bridge.md)\n"
  },
  {
    "path": "docs/docs/management/cli.md",
    "content": "# Command Line Interface\n\n`vue.py` provides a command line tool `vue-cli` to deploy your application.\n\n## Deployment\nA `vue.py` application can be deployed via several provider.\n\nGet help about the available provider and their arguments\n```bash\n$ vue-cli deploy -h\n```\n\nThis installs all the required packages for e.g. the flask provider\n```bash\npip install vuepy[flask]\n```\n\n### Flask\nWith a flask live deployment your application is accessible on\n[http://localhost:5000](http://localhost:5000).\n```bash\n$ vue-cli deploy flask\n```\nThis is the best deployment method when debugging.\n\n#### Configuration\n`vuepy.yml` can be used to set `HOST`, `PORT` and [Flask Builtin Configuration Values](https://flask.palletsprojects.com/en/2.0.x/config/#builtin-configuration-values)\n```yaml\nprovider:\n  flask:\n    HOST: \"0.0.0.0\"\n    PORT: 5001\n```\n\n### Static\nWith a static deployment everything your application needs,\ngets packaged into a single folder,\nwhich can be served by your favorite web server.\n```bash\n$ vue-cli deploy static <destination> --package\n```\n* `destination` specifies the path where your application should be deployed to.\n* `--package` (optional) packages the python code into the vuepy.js file.\n"
  },
  {
    "path": "docs/docs/management/configuration.md",
    "content": "# Configuration\n\nYour `vue.py` application can be customized via a `vuepy.yml` file\nlocated in your application folder.\n\n## Stylesheets\nIf you want to use custom CSS stylsheets, add this section to the configuration file:\n```yaml\nstylesheets:\n  - <path of the stylesheet relative to your application folder>\n  - <URL of the stylesheet>\n```\n\n## Scripts\n### Javascript Libraries\nIf you want to use custom javascript libraries, add this section to the configuration file:\n```yaml\nscripts:\n  - <path of the script relative to your application folder>\n  - <URL of the script>\n```\nor if combined with [extensions](#Extensions) or [custom versions](#Custom-Versions)\n```yaml\nscripts:\n  \"local_lib_name\": <path of the script relative to your application folder>\n  \"lib_name\": <URL of the script>\n```\n\n### Extensions\n`vue.py` comes with some vue.js extensions builtin:\n* [vuex](https://vuex.vuejs.org)\n* [vue-router](https://router.vuejs.org)\nThe extensions can be activated as followed:\n```yaml\nscripts:\n  vuex: true\n  vue-router: true\n```\nBy default all extensions are deactivated to avoid loading unnecessary files.\n\n\n### Custom Versions\n`vue.py` comes with vue.js and brython built-in.\nIf different versions can be used as followed:\n```yaml\nscripts:\n  vue: <URL/Path to custom vue.js file>\n  brython: <URL/Path to custom brython_dist.js file>\n  vuex: <URL/Path to custom vuex.js file>\n  vue-router: <URL/Path to custom vue-router.js file>\n```\n\n## EntryPoint\nBy default the `app.py` in your project directory is the entry point for your app.\nIf you want to point to a custom entry point `custom.py`, add this section:\n```yaml\nentry_point: custom\n```\n\n## Templates\nSince writing HTML in python strings can be tedious \nyou can write your templates in .html files \nand link them as your template string.\n```yaml\ntemplates:\n    myhtml: my.html\n```\n\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    template = \"#myhtml\"\n```\n"
  },
  {
    "path": "docs/docs/pyjs_bridge.md",
    "content": "# Python/Javascript Bridge\n## Call Javascript Functions\n`vue.py` provides some utilities to access Javascript libraris.\n\nYou can load Javascript libraries dynamically\n```python\nfrom vue.utils import js_load\nmarked = js_load(\"https://unpkg.com/marked@0.3.6\")\nhtml = marked(\"# Title\")\n```\nThis perfoms an synchrous ajax call and therefore is not recommended for responsive applications.\nFurthermore prevent some browser (e.g. Chrome) load from external ressources with ajax.\n\nTherefore a second method is provided to acces a already loaded Javascript library.\n```html\n<script src=\"https://unpkg.com/marked@0.3.6\"></script>\n```\n```python\nfrom vue.utils import js_lib\nmarked = js_lib(\"marked\")\nhtml = marked(\"# Title\")\n```\nThis uses the optimized methods a Browser uses to load all dependencies.\nAnd provides also access to all object in the Javascript namespace.\n\n## Vue Reactivity\nTo keep the reactivity of Vue.js and at the same time providing a\nPythonic interface, all attribuets of vue.py components are wrapped in\ncustom types.\nThese types provide the same interfaes than native python types, but use\nthe javascript types in the background.\n\nJust making dicts out of Javascript object, method calls, would look\nrather unusual.\n```python\nelement['focus']()\n```\nTo avoid this, wrapped dicts can also access items as attributes, this leads to\nmore readable code\n```python\nelement.focus()\n```\n\nBy wrapping the javascript types, it is also possible\nto improve the original Vue.js behavior. In Vue.js this is forbidden.\n```javascript\nvar vm = new Vue({\n  data: {\n    reactive: {yes: 0}\n  }\n})\n// `vm.reactive.yes` is now reactive\n\nvm.no = 2\n// `vm.reactive.no` is NOT reactive\n```\n\nYour have to use `Vue.set()`. vue.py takes care of this under the hood.\n```python\nclass App(VueComponent):\n    reactive = {\"yes\": 0}\n\napp = App(\"#element\")\n# `vm.reactive.yes` is now reactive\n\napp.reactive[\"also\"] = 2  # `vm.reactive.also` is now also reactive\n```\n\n## Limitations\n## Usable Types\nFor now vue.py only supports basics types (int, float, str, bool, list, dict), since these can be converted fairly simple to their Javascript equivalentive.\nWriting own classes and using them for Component properties may not work.\n\nThis may change in the future, but for now it is not planned to work on this issue.\n\n## Due To Wrapping Types\nDue to restrictions of Brython in combination with the reactivity system in Vue.js are custom wrapper around component data and props neccessary.\nThis is done mostly in the background, there are some limitations to consider.\n\n### When Native Python Types Are Assumed\nThe wrapper around lists and dictionaries provide the same interface than native python types but due to restrictions in Brython, they are no subclasses of `list`/`dict`.\nThis can lead to problems when passing this methods to other native python methods.\nTherefore a helper is provided to convert a wrapped Javascript object into a native python type.\n```python\nimport json\nfrom vue import VueComponent, computed\nfrom vue.bridge import Object\n\n\nclass MyComponent(VueComponent):\n    template = \"<div>{{ content_as_json }}</div>\"\n    content = [{\"a\": 1}]\n\n    @computed\n    def content_as_json(self):\n        # Will break because self.content is not a native python type\n        # json.dumps does not know how to serialize this types\n        return json.dumps(sef.content)\n\n        # vue.py provides a method to convert the wrapper types\n        return json.dumps(Object.to_py(self.content))\n\n```\n**When converting to native python types reactivity may get lost!**\n\n\n### When Native Javascript Types Are Assumed\nA similar problem exists when passing wrapper variables to native javascript methods.\nBrython can convert native Python types like lists and dicts to their javascript equivalent.\nSince the wrapper types are not real lists/dicts Brython cannot convert them.\n```python\nfrom vue import VueComponent, computed\nfrom vue.bridge import Object\nfrom vue.utils import js_lib\n\njs_json = js_lib(\"JSON\")\n\nclass MyComponent(VueComponent):\n    template = \"<div>{{ content_as_json }}</div>\"\n    content = [{\"a\": 1}]\n\n    @computed\n    def content_as_json(self):\n        # Will break because self.content is not a native javascript type\n        # JSON.stringify does not know how to serialize this types\n        return js_json.stringify(self.content)\n\n        # vue.py provides a method to convert the wrapper types\n        return js_json.stringify(Object.to_js(self.content))\n```\n\n### Are These Limitations Forever?\nI hope not!\n\nCurrently the main reason for this limitations is [Brython Issue 893](https://github.com/brython-dev/brython/issues/893).\nWhen this one gets fixed, the wrapper classes can be subclasses of native python types and Brython should be able to do the right conversions under the hood.\n"
  },
  {
    "path": "docs/docs/vue_concepts/computed_properties.md",
    "content": "# Computed Properties\nComputed properties can be defined with the `@computed` decorator\n```python\nfrom vue import VueComponent, computed\n\nclass ComponentWithMethods(VueComponent):\n    message = \"Hallo vue.py\"\n    @computed\n    def reversed(self):\n        return \"\".join(reversed(self.message))\n```\n\ncomputed setters are defined similar to plain python setters\n```python\nclass ComputedSetter(VueComponent):\n    message = \"Hallo vue.py\"\n\n    @computed\n    def reversed_message(self):\n        return self.message[::-1]\n\n    @reversed_message.setter\n    def reversed_message(self, reversed_message):\n        self.message = reversed_message[::-1]\n```\n\n# Watchers\nWatchers can be defined with the `@watch` decorator.\n```python\nfrom vue import VueComponent, watch\n\nclass Watch(VueComponent):\n    message = \"\"\n\n    @watch(\"message\")\n    def log_message_changes(self, new, old):\n        print(\"'message' changed from '{}' to '{}'\".format(old, new)\n```\n\n\n`deep` and `immediate` watchers can be configured via arguments\n\n```python\nfrom vue import VueComponent, watch\n\nclass Watch(VueComponent):\n    message = \"\"\n\n    @watch(\"message\", deep=True, immediate=True)\n    def log_message_changes(self, new, old):\n        print(\"'message' changed from '{}' to '{}'\".format(old, new)\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/custom_directives.md",
    "content": "# Custom Directives\n\n## Local Registration\nFor [function directives](https://vuejs.org/v2/guide/custom-directive.html#Function-Shorthand)\nis just a decorator necessary\n```python\nfrom vue import VueComponent, directive\n\nclass CustomDirective(VueComponent):\n    @staticmethod\n    @directive\n    def custom_focus(el, binding, vnode, old_vnode, *args):\n        pass\n```\n\nTo define custom hook functions, add the directive name as argument to\nthe decorator\n```python\nfrom vue import VueComponent, directive\n\nclass CustomDirective(VueComponent):\n    @staticmethod\n    @directive(\"focus\")\n    def component_updated(el, binding, vnode, old_vnode, *args):\n        # implement 'componentUpdated' hook function here\n        pass\n\n    @staticmethod\n    @directive(\"focus\")\n    def inserted(el, binding, vnode, old_vnode, *args):\n        # implement 'inserted' hook function here\n        pass\n```\n\nTo avoid code duplication when adding the same hook function to different hooks,\nthe hooks can be specified as decorator arguments.\n```python\nfrom vue import VueComponent, directive\n\nclass CustomDirective(VueComponent):\n    @staticmethod\n    @directive(\"focus\", \"component_updated\", \"inserted\")\n    def combined_hook(el, binding, vnode, old_vnode, *args):\n        # implement function for 'componentUpdated' and 'inserted' hook here\n        pass\n```\n\n**The Vue.js hook `componentUpdated` is called `component_updated` to be more pythonic**\n\nThe `@staticmethod` decorator is only necessary to avoid IDE checker errors.\n\nUnderscores in directive names get replaced by dashes, so `custom_focus` gets `v-custom-focus`.\n\n\n## Global Registration\nGlobal directives can be created by sub-classing `VueDirective`.\n```python\nfrom vue import Vue, VueDirective\nclass MyDirective(VueDirective):\n    def bind(el, binding, vnode, old_vnode):\n        pass\n\n    def component_updated(el, binding, vnode, old_vnode):\n        pass\n\nVue.directive(\"my-directive\", MyDirective)\n```\nand for function directives just pass the function to `Vue.directive`\n```python\nfrom vue import Vue\ndef my_directive(el, binding, vnode, old_vnode):\n    pass\n\nVue.directive(\"my-directive\", my_directive)\n```\n`vue.py` offeres a shorthand, if you like to take the **lower-cased** name\nof the function/directive-class as directive name.\n```python\nfrom vue import Vue\ndef my_directive(el, binding, vnode, old_vnode):\n    pass\n\nVue.directive(my_directive) # directive name is 'my_directive'\n```\n\n## Retrieve Global Directives\nGetter for global directives works similar to Vue.js\n```python\nfrom vue import Vue\ndirective = Vue.directive('directive-name')\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/custom_vmodel.md",
    "content": "# Customize V-Model\nTo customize the event and prop used by `v-model` a class variable of the type `Model()` can be defined.\n```python\nfrom vue import VueComponent, Model\n\nclass CustomVModel(VueComponent):\n    model = Model(prop=\"checked\", event=\"change\")\n    checked: bool\n    template = \"\"\"\n    <div>\n        <p id=\"component\">{{ checked }}</p>\n        <input\n            id=\"c\"\n            type=\"checkbox\"\n            :checked=\"checked\"\n            @change=\"$emit('change', $event.target.checked)\"\n        >\n    </div>\n    \"\"\"\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/data_methods.md",
    "content": "# Data\nAll class variables of a `vue.py` component are available as data fields in the Vue instance.\n```python\nclass ComponentWithData(VueComponent):\n    data_field = \"value\"\n```\n\nto initialize a data field with a prop you can use the `@data` decorator\n```python\nfrom vue import VueComponent, data\n\nclass ComponentWithData(VueComponent):\n    prop: str\n    @data\n    def initialized_with_prop(self):\n        return self.prop\n```\n\n# Methods\nSimilar to data fields all methods of the `vue.py` component are available as methods.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithMethods(VueComponent):\n    counter = 0\n\n    def increase(self):\n        self.counter += 1\n```\n\nMethods used as event handler, must have a (optional) argument for the event.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithMethods(VueComponent):\n    def handle(self, ev):\n        print(ev)\n```\n\n## self\nAll attributes of the Vue instance are available as attributes of `self` (e.g. methods, computed properties, props etc.).\n\n"
  },
  {
    "path": "docs/docs/vue_concepts/extend.md",
    "content": "# Extend\nVue.js uses `Vue.extend` or the `extends` Component attribute to extend\ncomponents. Per default `vue.py` sticks to the Pythonic way and uses the\npython class inheritance behavior.\n```python\nfrom vue import VueComponent\n\nclass Base(VueComponent):\n    def created(self):\n        print(\"Base\")\n\nclass Sub(Base):\n    def created(self):\n        super().created()\n        print(\"Sub\")\n```\nwhich outputs on the console\n```\nBase\nSub\n```\n\nTo use the merging strategies Vue.js applies when using `extends`\nin `vue.py` just set the `extends` attribute to `True`\n\n```python\nfrom vue import VueComponent\n\nclass Base(VueComponent):\n    def created(self):\n        print(\"Base\")\n\nclass Sub(Base):\n    extends = True\n\n    def created(self):\n        print(\"Sub\")\n```\nwhich outputs on the console\n```\nBase\nSub\n```\n\nThe `extend` attribute can also be a native Vue.js component to extend from this.\n```\nfrom vue import *\nfrom vue.utils import js_lib\nNativeVueJsComponent = js_lib(\"NativeVueJsComponent\")\n\nclass Sub(VueComponent):\n    extends = NativeVueJsComponent\n```\n\n## Template Slots\nVue.js does not support extending templates out-of-the-box and it is\n[recommended](https://vuejsdevelopers.com/2017/06/11/vue-js-extending-components/)\nto use third-party libraries like [pug](https://pugjs.org/api/getting-started.html).\n\nWith `vue.py` a feature called `template_slots` is included to extend templates.\nA base component can define slots in the template\nand a sub component can fill the slots with the attribute `template_slots`.\nThe base component can also define default values for the slots.\n\n```python\nfrom vue import VueComponent\n\nclass Base(VueComponent):\n    template_slots = {\n        \"heading\": \"Default Heading\",\n        \"footer\": \"Default Footer\",\n    }\n    template = \"\"\"\n    <div>\n        <h1>{heading}</h1>\n        {content}\n        <small>{footer}</small>\n    </div>\n    \"\"\"\n\nclass Sub(Base):\n    template_slots = {\n        \"heading\": \"My Custom Heading\",\n        \"content\": \"content...\"\n    }\n```\nThe `Sub` component gets rendered as\n```html\n<h1>My Custom Heading</h1>\ncontent...\n<small>Default Footer</small>\n```\n\nIf you only have one slot in your component, the `template_slots` attribute\ncan be the template string\n\n```python\nfrom vue import VueComponent\n\nclass Base(VueComponent):\n    template = \"<h1>{}</h1>\"\n\nclass Sub(Base):\n    template_slots = \"heading\"\n```\nThe `Sub` component gets rendered as\n```html\n<h1>heading</h1>\n```\n\nMixing both is also possible\n```python\nclass Base(VueComponent):\n    template_slots = {\"pre\": \"DEFAULT\", \"post\": \"DEFAULT\"}\n    template = \"<p>{pre} {} {post}</p>\"\n\nclass WithSlots(Base):\n    template_slots = {\"pre\": \"PRE\", \"default\": \"SUB\"}\n\nclass WithDefault(Base):\n    template_slots = \"SUB\"\n```\n\nThe `WithSlots` component gets rendered as\n```html\n<p>PRE SUB DEFAULT</p>\n```\n\nThe `WithDefault` component gets rendered as\n```html\n<p>DEFAULT SUB DEFAULT</p>\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/filter.md",
    "content": "# Filter\n\n## Local Registration\nLocal registration of filters is done with the `@filters` decorator.\n```python\nfrom vue import VueComponent, filters\n\nclass ComponentWithFilter(VueComponent):\n    message = \"Message\"\n\n    @filters\n    def lower_case(value):\n        return value.lower()\n\n    template = \"<div id='content'>{{ message | lower_case }}</div>\"\n```\n\nTo avoid errors on source code checking errors in modern IDEs, an additional `@staticmethod` decorator can be added\n```python\nfrom vue import VueComponent, filters\n\nclass ComponentWithFilter(VueComponent):\n    @staticmethod\n    @filters\n    def lower_case(value):\n        return value.lower()\n```\n\n## Global Registration\nGlobal registration of filters works similar to Vue.js\n```python\nfrom vue import Vue\n\nVue.filter(\"capitalize\", str.capitalize)\n```\n\nAdditionally in vue.py it is allowd to only pass a function to `Vue.filter`.\nIn this case the filter gets registered under the function name.\n```python\nfrom vue import Vue\n\ndef my_filter(val):\n    return \"filtered({})\".format(val)\n\nVue.filter(my_filter)\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/instance_components.md",
    "content": "# Components\n## Define\nA Vue component can be defined by writing a sub-class of `VueComponent`\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    pass\n```\n\n## Registration\nEvery component has to be [registered](https://vuejs.org/v2/guide/components-registration.html) to be available in other components.\n### Local Registration\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    components = [\n        MyVuePyComponent,\n        AnotherNativeVueJsComponent,\n    ]\n```\nThe component to register can be either a `vue.py` component or a native\nVue.js component loaded with `js_lib` or `js_import`\n### Global Registration\n```python\nfrom vue import Vue\n\n# For vue.py components or native Vue.js component loaded with js_lib or js_import\nVue.component(MyComponent)\nVue.component(\"my-custom-name\", MyComponent)\n\n# Only for vue.py components\nMyComponent.register()\nMyComponent.register(\"my-custom-name\")\n```\n\n## Template\nThe component html template can be defined with a class variable called `template`\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    template = \"\"\"\n    <div>\n        Hallo vue.py!\n    </div>\n    \"\"\"\n```\n\n`vue.py` templates look the same than Vue.js templates. This means inline expressions must be javascript!!.\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    message = \"Hallo vue.py!\"\n    template = \"\"\"\n    <div>\n        {{ message.split('').reverse().join('') }}\n    </div>\n    \"\"\"\n```\n\n\n# Instance\n## Start\nTo start a component as Vue application, just pass a css selector at initialization\n```\nApp(\"#app\")\n```\n\n## Prop Data\n[propsData](https://vuejs.org/v2/api/#propsData) can be passed in as a dictionary.\n```python\nApp(\"#app\", props_data={\"prop\": \"value\"})\n```\n\n\n## API\n### Dollar Methods\n$-methods like `$emit` can be called by omitting the `$`\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    def created(self):\n        self.emit(\"creation\", \"Arg\")\n```\n\nIn the case your Component has another attribute with the same name, you can use a workaround and directly call `getattr()`\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    emit = \"already used\"\n    def created(self):\n        getattr(self, \"$emit\")(\"creation\", \"Arg\")\n```\n\n"
  },
  {
    "path": "docs/docs/vue_concepts/lifecycle_hooks.md",
    "content": "# Lifecycle Hooks\nCertain names for component methods are reserved, to specify lifecycle hooks.\n```python\nfrom vue import VueComponent\n\nclass ComponentLifecycleHooks(VueComponent):\n    def before_create(self):\n        print(\"on beforeCreate\")\n\n    def created(self):\n        print(\"on created\")\n\n    def before_mount(self):\n        print(\"on beforeMount\")\n\n    def mounted(self):\n        print(\"on mounted\")\n\n    def before_update(self):\n        print(\"on beforeUpdate\")\n\n    def updated(self):\n        print(\"on updated\")\n\n    def before_destroy(self):\n        print(\"on beforeDestroy\")\n\n    def destroyed(self):\n        print(\"on destroyed\")\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/plugins_mixins.md",
    "content": "# Mixins\n\n## Write Mixins\nMixins are created by sub-classing from `VueMixin` and from there it is\nsimilar to write a `VueComponent`\n```python\nfrom vue import VueMixin, computed\n\nclass MyMixin(VueMixin):\n    prop: str\n\n    value = \"default\"\n\n    def created(self):\n        pass\n\n    @computed\n    def upper_value(self):\n        return self.value.upper()\n```\n\n## Use Mixins\n### Local Registration\nLocal registration of a Mixin within a Component works just like in Vue.js.\n```python\nfrom vue import VueComponent\n\nclass MyComponent(VueComponent):\n    mixins = [MyPyMixin, AnotherVueJsMixin]\n```\nMixins wirtten in Vue.js and `vue.py` can be mixed.\n\n### Global Registration\nGlobal registration of a Mixin works also just like in Vue.js.\n```python\nfrom vue import Vue\nVue.mixin(MyMixin)\n```\n\n# Plugins\n## Write Plugins\nA plugin can be written by sub-classing `VuePlugin` and implementing\nthe function `install` similar to Vue.js.\n```python\nfrom vue import Vue, VuePlugin, VueMixin\n\nclass MyPlugin(VuePlugin):\n    class MyMixin(VueMixin):\n        def created(self):\n            pass\n\n        # 4) Within a Mixin, new instance methods can be defined\n        def my_method(self, args):\n            pass\n\n    @staticmethod\n    def global_method():\n        pass\n\n    @staticmethod\n    def install(*args, **kwargs):\n        # 1) Add a global method or property\n        Vue.my_global_method = MyPlugin.global_method\n\n        # 2) Add a global assed\n        Vue.directive(MyDirective)\n\n        # 3) Inject Mixins\n        Vue.mixin(MyPlugin.MyMixin)\n```\n\n## Use Plugins\nUsing plugins works like in Vue.js\n```python\nfrom vue import Vue\n\nVue.use(MyPlugin)\n```\n`vue.py` supports also using native Vue.js plugins.\n"
  },
  {
    "path": "docs/docs/vue_concepts/props.md",
    "content": "# Props\nA prop is defined by adding a type hint to a class variable.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithData(VueComponent):\n    prop: str\n```\n\n## Types\nUnlike Vue.js, vue.py enforces prop types (if not type hint is provided, it is a data field).\n\nThe following types are currently supported:\n* `int`\n* `float`\n* `str`\n* `bool`\n* `list`\n* `dict`\n\n## Default\nBy assigning a value to a prop, the default value can be defined.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithData(VueComponent):\n    prop: str = \"default\"\n```\n\n## Required\nIf no default value is given, the prop is automatically required.\n\n## Validator\nWith the `@validator` decorator are prop validators defined\n```python\nfrom vue import VueComponent, validator\n\nclass ComponentWithData(VueComponent):\n    prop: int\n\n    @validator(\"prop\")\n    def prop_must_be_greater_than_100(self, value):\n        return value > 100\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/render_function.md",
    "content": "# Render Function\nA render function can be defined by overwriting the `render`  method.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithData(VueComponent):\n    def render(self, create_element):\n        return create_element(\"h1\", \"Title\")\n```\n\n## Accessing Slots\nSlots can be accessed as a dictionary.\n```python\nfrom vue import VueComponent\n\nclass ComponentWithSlots(VueComponent):\n    def render(self, create_element):\n        return create_element(f\"div\", self.slots.get(\"default\"))\n```\nIt is recommened to access the dictionary via the `get`-method to avoid failures\nwhen no children for the slot are provided.\n\n## Passing Props\n```python\nfrom vue import VueComponent\n\nclass ComponentWithProps(VueComponent):\n    prop: str = \"p\"\n    template = \"<div :id='prop'></div>\"\n\nComponentWithProps.register()\n\nclass Component(VueComponent):\n    def render(self, create_element):\n        return create_element(\n            \"ComponentWithProps\", {\"props\": {\"prop\": \"p\"}},\n        )\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/vue-router.md",
    "content": "# Vue Router\n## Define\nA vue router can be used by writing a sub-class of `VueRouter`, \nsetting some `VueRoute`s \nand set the `router` parameter when initializing a app.\n```python\nfrom vue import VueComponent, VueRouter, VueRoute\n\nclass Foo(VueComponent):\n    template = \"<div>foo</div>\"\n\nclass Bar(VueComponent):\n    template = \"<div>bar</div>\"\n\nclass Router(VueRouter):\n    routes = [\n        VueRoute(\"/foo\", Foo),\n        VueRoute(\"/bar\", Bar),\n    ]\n\nclass App(VueComponent):\n    template = \"\"\"\n        <div>\n            <p>\n                <router-link to=\"/foo\">Go to Foo</router-link>\n                <router-link to=\"/bar\">Go to Bar</router-link>\n            </p>\n            <router-view></router-view>\n        </div>\n    \"\"\"\n\nApp(\"#app\", router=Router())\n```\n\nenable the vue-router extension in the `vuepy.yml` [config file](../management/configuration.md):\n```yaml\nscripts:\n  vue-router: true\n```\n"
  },
  {
    "path": "docs/docs/vue_concepts/vuex.md",
    "content": "# Vuex\n## Define\nA Vuex store can be defined by writing a sub-class of `VueStore`\nand used by setting the `store` parameter when initializing a app.\n```python\nfrom vue import VueComponent, VueStore\n\nclass Store(VueStore):\n    pass\n\nclass App(VueComponent):\n    pass\n\nApp(\"#app\", store=Store())\n```\n\nenable the vuex extension in the `vuepy.yml` [config file](../management/configuration.md):\n```yaml\nscripts:\n  vuex: true\n```\n\n## State Variables\n```python\nfrom vue import VueComponent, VueStore\n\nclass Store(VueStore):\n    greeting = \"Hello Store\"\n\nclass App(VueComponent):\n    def created(self):\n        print(self.store.greeting)\n\nApp(\"#app\", store=Store())\n```\n\n\n## Getter\n```python\nfrom vue import VueComponent, VueStore, getter\n\nclass Store(VueStore):\n    greeting = \"Hello\"\n\n    @getter\n    def get_greeting(self):\n        return self.greeting\n\n    @getter\n    def personalized_greeting(self, name):\n        return \"{} {}\".format(self.greeting, name)\n\nclass App(VueComponent):\n    def created(self):\n        print(self.store.get_greeting) # \"Hello\"\n        print(self.store.personalized_greeting(\"Store\")) # \"Hello Store\"\n\nApp(\"#app\", store=Store())\n```\n\n## Mutations\nUnlike `Vue.js` mutations in `vue.py` can have multiple arguments and\neven keyword-arguments\n```python\nfrom vue import VueComponent, VueStore, mutation\n\nclass Store(VueStore):\n    greeting = \"\"\n\n    @mutation\n    def set_greeting(self, greeting, name=None):\n        self.greeting = greeting\n        if name:\n            self.greeting += \" \" + name\n\nclass App(VueComponent):\n    def created(self):\n        self.store.commit(\"set_greeting\", \"Hello\", name=\"Store\")\n        print(self.store.greeting) # \"Hello Store\"\n\nApp(\"#app\", store=Store())\n```\n\n## Mutations\nSimilar to mutations actions in `vue.py` can have multiple arguments and\neven keyword-arguments.\n```python\nfrom vue import VueComponent, VueStore, mutation\n\nclass Store(VueStore):\n    @action\n    def greet(self, greeting, name=None):\n        if name:\n            greeting += \" \" + name\n        print(greeting)\n\nclass App(VueComponent):\n    def created(self):\n        self.store.dispatch(\"greet\", \"Hello\", name=\"Store\")\n\nApp(\"#app\", store=Store())\n```\n\n## Plugins\n```python\nclass Plugin(VueStorePlugin):\n    def initialize(self, store):\n        store.message = \"Message\"\n\n    def subscribe(self, mut, *args, **kwargs):\n        print(mut, args, kwargs)\n\nclass Store(VueStore):\n    plugins = [Plugin().install] # list can also contain native vuex plugins\n\n    message = \"\"\n\n    @mutation\n    def msg(self, prefix, postfix=\"\"):\n        pass\n\nclass ComponentUsingGetter(VueComponent):\n    @computed\n    def message(self):\n        return self.store.message\n\n    def created(self):\n        self.store.commit(\"msg\", \"Hallo\", postfix=\"!\")\n\n    template = \"<div id='content'>{{ message }}</div>\"\n```\n"
  },
  {
    "path": "docs/planning.md",
    "content": "# Future Plans\n## Performance\n* How to improve loading times?\n* create benchmarks\n\n## Tools\n* Docker deployment\n\n## Vue.py Universe\n* store synchronization\n  * with local/session storage\n  * over WebSockets with python backend\n* desktop toolkit\n  * based on [pywebview](https://github.com/r0x0r/pywebview) ??\n\n## Vue.js Features\n* full access to Vue object (global configuration etc.)\n* ...\n\n## Internals\n* write tests for decorators\n\n## Docs\n* embed examples in gallery\n"
  },
  {
    "path": "examples/elastic_header/app.py",
    "content": "from vue import VueComponent, data, computed\nfrom vue.bridge import Object\nfrom vue.utils import js_lib\n\n\ndynamics = js_lib(\"dynamics\")\n\n\nclass DraggableHeaderView(VueComponent):\n    template = \"#header-view\"\n\n    dragging = False\n\n    @data\n    def c(self):\n        return {\"x\": 160, \"y\": 160}\n\n    @data\n    def start(self):\n        return {\"x\": 0, \"y\": 0}\n\n    @computed\n    def header_path(self):\n        return f'M0,0 L320,0 320,160Q{self.c[\"x\"]},{self.c[\"y\"]} 0,160'\n\n    @computed\n    def content_position(self):\n        dy = self.c[\"y\"] - 160\n        dampen = 2 if dy > 0 else 4\n        return {\"transform\": f\"translate3d(0,{dy / dampen}px,0)\"}\n\n    def start_drag(self, e):\n        e = e[\"changedTouches\"][0] if \"changedTouches\" in e else e\n        self.dragging = True\n        self.start[\"x\"] = e.pageX\n        self.start[\"y\"] = e.pageY\n\n    def on_drag(self, e):\n        e = e[\"changedTouches\"][0] if \"changedTouches\" in e else e\n        if self.dragging:\n            self.c[\"x\"] = 160 + (e.pageX - self.start[\"x\"])\n            dy = e.pageY - self.start[\"y\"]\n            dampen = 1.5 if dy > 0 else 4\n            self.c[\"y\"] = int(160 + dy / dampen)\n\n    def stop_drag(self, _):\n        if self.dragging:\n            self.dragging = False\n            dynamics.animate(\n                Object.to_js(self.c),\n                {\"x\": 160, \"y\": 160},\n                {\"type\": dynamics.spring, \"duration\": 700, \"friction\": 280},\n            )\n\n\nDraggableHeaderView.register()\n\n\nclass App(VueComponent):\n    template = \"#main\"\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/elastic_header/header-view.html",
    "content": "<div class=\"draggable-header-view\"\n  @mousedown=\"start_drag\" @touchstart=\"start_drag\"\n  @mousemove=\"on_drag\" @touchmove=\"on_drag\"\n  @mouseup=\"stop_drag\" @touchend=\"stop_drag\" @mouseleave=\"stop_drag\">\n  <svg class=\"bg\" width=\"320\" height=\"560\">\n    <path :d=\"header_path\" fill=\"#3F51B5\"></path>\n  </svg>\n  <div class=\"header\" id=\"header\">\n    <slot name=\"header\"></slot>\n  </div>\n  <div class=\"content\" :style=\"content_position\" id=\"content\">\n    <slot name=\"content\"></slot>\n  </div>\n</div>\n"
  },
  {
    "path": "examples/elastic_header/main.html",
    "content": "<draggable-header-view>\n  <template slot=\"header\">\n    <h1>Elastic Draggable SVG Header</h1>\n    <p>\n      with <a href=\"https://stefanhoelzl.github.io/vue.py/\">vue.py</a>\n      + <a href=\"http://dynamicsjs.com\">dynamics.js</a>\n    </p>\n  </template>\n  <template slot=\"content\">\n    <p>\n      Note this is just an effect demo\n      - there are of course many additional details\n        if you want to use this in production,\n        e.g. handling responsive sizes, reload threshold and content scrolling.\n        Those are out of scope for this quick little hack.\n        However, the idea is that you can hide them as internal details\n        of a vue.py component and expose a simple Web-Component-like interface.\n    </p>\n  </template>\n</draggable-header-view>\n"
  },
  {
    "path": "examples/elastic_header/style.css",
    "content": "h1 {\n  font-weight: 300;\n  font-size: 1.8em;\n  margin-top: 0;\n}\na {\n  color: #fff;\n}\n.draggable-header-view {\n  background-color: #fff;\n  box-shadow: 0 4px 16px rgba(0,0,0,.15);\n  width: 320px;\n  height: 560px;\n  overflow: hidden;\n  margin: 30px auto;\n  position: relative;\n  font-family: 'Roboto', Helvetica, Arial, sans-serif;\n  color: #fff;\n  font-size: 14px;\n  font-weight: 300;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.draggable-header-view .bg {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 0;\n}\n.draggable-header-view .header, .draggable-header-view .content {\n  position: relative;\n  z-index: 1;\n  padding: 30px;\n  box-sizing: border-box;\n}\n.draggable-header-view .header {\n  height: 160px;\n}\n.draggable-header-view .content {\n  color: #333;\n  line-height: 1.5em;\n}\n"
  },
  {
    "path": "examples/elastic_header/vuepy.yml",
    "content": "templates:\n  header-view: header-view.html\n  main: main.html\nstylesheets:\n  - style.css\nscripts:\n  - https://unpkg.com/dynamics.js@1.1.5/lib/dynamics.js\n"
  },
  {
    "path": "examples/element_ui/app.py",
    "content": "from vue import VueComponent\nfrom .components import navigation\n\n\nnavigation.register()\n\n\nclass App(VueComponent):\n    template = \"#navigation\"\n\n    navigation_menu = [\n        {\n            \"id\": \"one\",\n            \"title\": \"Navigation One\",\n            \"icon\": \"el-icon-location\",\n            \"children\": [\n                {\"group\": \"Group One\"},\n                {\"id\": \"one.one\", \"title\": \"Item One\"},\n                {\"id\": \"one.two\", \"title\": \"Item Two\"},\n                {\"group\": \"Group Two\"},\n                {\"id\": \"one.hree\", \"title\": \"Item Three\"},\n                {\n                    \"id\": \"one.four\",\n                    \"title\": \"Item Four\",\n                    \"children\": [{\"id\": \"one.four.five\", \"title\": \"Item Five\"}],\n                },\n            ],\n        },\n        {\"id\": \"two\", \"title\": \"Navigation Two\", \"icon\": \"el-icon-menu\"},\n        {\n            \"id\": \"three\",\n            \"title\": \"Navigation Three\",\n            \"icon\": \"el-icon-document\",\n            \"disabled\": True,\n        },\n        {\"id\": \"four\", \"title\": \"Navigation Four\", \"icon\": \"el-icon-setting\"},\n    ]\n\n    def clicked(self, item):\n        print(item)\n        self.notify.info(\n            {\"title\": \"Navigation\", \"message\": item.get(\"title\", \"NO TITLE\")}\n        )\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/element_ui/components/navigation.py",
    "content": "from vue import VueComponent, computed\n\n\nclass NavigationItem(VueComponent):\n    item: dict\n    template = \"\"\"\n    <el-menu-item-group v-if=\"is_group_header\">\n        <span slot=\"title\">{{ item.group }}</span>\n    </el-menu-item-group>\n    <component v-else\n        @click=\"$emit('click', item)\"\n        :is=\"item_tag\" \n        :disabled=\"item.disabled\"\n        :index=\"item.id\">\n        <template v-if=\"is_submenu\">\n            <template slot=\"title\">\n                <i :class=\"item.icon\"></i>\n                <span slot=\"title\">{{ item.title }}</span>\n            </template>\n            <navigation-item \n                v-for=\"sub_item in item.children\"\n                :key=\"sub_item.id\"\n                :item=\"sub_item\"\n                @click=\"$emit('click', $event)\"\n                >\n            </navigation-item>\n        </template>\n        <template v-else>\n            <i :class=\"item.icon\"></i>\n            {{ item.title }}\n        </template>\n    </component>\n    \"\"\"\n\n    @computed\n    def item_tag(self):\n        if self.is_submenu:\n            return \"el-submenu\"\n        return \"el-menu-item\"\n\n    @computed\n    def is_menu_item(self):\n        return not self.is_group_header and not self.is_submenu\n\n    @computed\n    def is_group_header(self):\n        return \"group\" in self.item\n\n    @computed\n    def is_submenu(self):\n        return \"children\" in self.item\n\n\nclass NavigationMenu(VueComponent):\n    content: list\n    template = \"\"\"\n    <div>\n        <el-menu\n            class=\"navigation-menu\">\n            <navigation-item \n                @click=\"$emit('click', $event)\"\n                v-for=\"item in content\"\n                :key=\"item.id\"\n                :item=\"item\"\n                >\n            </navigation-item>\n        </el-menu>\n    </div>\n    \"\"\"\n\n\ndef register():\n    NavigationItem.register()\n    NavigationMenu.register()\n"
  },
  {
    "path": "examples/element_ui/navigation.html",
    "content": "<div>\n  <navigation-menu\n    @click=\"clicked\"\n    :content=\"navigation_menu\">\n  </navigation-menu>\n</div>\n"
  },
  {
    "path": "examples/element_ui/style.css",
    "content": ".navigation-menu:not(.el-menu--collapse) {\n  width: 200px;\n  min-height: 400px;\n}\n"
  },
  {
    "path": "examples/element_ui/vuepy.yml",
    "content": "templates:\n  navigation: navigation.html\nstylesheets:\n  - style.css\n  - https://unpkg.com/element-ui/lib/theme-chalk/index.css\nscripts:\n  - https://unpkg.com/element-ui/lib/index.js\n"
  },
  {
    "path": "examples/github_commits/app.py",
    "content": "from vue import VueComponent, filters, watch\n\nfrom browser import window, ajax\n\nurl = \"https://api.github.com/repos/stefanhoelzl/vue.py/commits?per_page=10&sha={}\"\n\nif window.location.hash == \"#testing\":\n    url = \"data.json\"\n\n\nclass App(VueComponent):\n    template = \"#commits\"\n\n    branches = [\"master\", \"2948e6b\"]\n    current_branch = \"master\"\n    commits = []\n\n    def created(self):\n        self.fetch_data()\n\n    @watch(\"current_branch\")\n    def fetch_data_on_current_branch_change(self, new, old):\n        self.fetch_data()\n\n    @staticmethod\n    @filters\n    def truncate(value):\n        return value.split(\"\\n\", 1)[0]\n\n    @staticmethod\n    @filters\n    def format_date(value):\n        return value.replace(\"T\", \" \").replace(\"Z\", \"\")\n\n    def fetch_data(self):\n        self.commits = []\n\n        req = ajax.ajax()\n        req.open(\"GET\", url.format(self.current_branch), True)\n        req.bind(\"complete\", self.loaded)\n        req.send()\n\n    def loaded(self, ev):\n        self.commits = window.JSON.parse(ev.text)\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/github_commits/commits.html",
    "content": "<div>\n  <h1>Latest vue.py Commits</h1>\n  <template v-for=\"branch in branches\">\n    <input type=\"radio\"\n      :id=\"branch\"\n      :value=\"branch\"\n      name=\"branch\"\n      v-model=\"current_branch\">\n    <label :for=\"branch\">{{ branch }}</label>\n  </template>\n  <p>stefanhoelzl/vue.py@{{ current_branch }}</p>\n  <ul>\n    <li v-for=\"record in commits\">\n      <a :href=\"record.html_url\" target=\"_blank\" class=\"commit\">{{ record.sha.slice(0, 7) }}</a>\n      - <span class=\"message\">{{ record.commit.message | truncate }}</span><br>\n      by <span class=\"author\"><a :href=\"record.author.html_url\" target=\"_blank\">{{ record.commit.author.name }}</a></span>\n      at <span class=\"date\">{{ record.commit.author.date | format_date }}</span>\n    </li>\n  </ul>\n</div>\n"
  },
  {
    "path": "examples/github_commits/data.json",
    "content": "[\n  {\n    \"sha\": \"0a644f825e780555e62d2e4647786d2a3eb06afc\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjBhNjQ0ZjgyNWU3ODA1NTVlNjJkMmU0NjQ3Nzg2ZDJhM2ViMDZhZmM=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T20:53:14Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-21T10:45:58Z\"\n      },\n      \"message\": \"[testing] refactored VueComponentFactory out and separated decorators\",\n      \"tree\": {\n        \"sha\": \"e04efd554a872923b487128493f29046de778387\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/e04efd554a872923b487128493f29046de778387\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/0a644f825e780555e62d2e4647786d2a3eb06afc\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/0a644f825e780555e62d2e4647786d2a3eb06afc\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/0a644f825e780555e62d2e4647786d2a3eb06afc\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/0a644f825e780555e62d2e4647786d2a3eb06afc/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/37fb44c39be49ed40d7dce3d4d5978853e4d82f0\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjM3ZmI0NGMzOWJlNDllZDQwZDdkY2UzZDRkNTk3ODg1M2U0ZDgyZjA=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T20:06:42Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T20:06:42Z\"\n      },\n      \"message\": \"[refactoring] renamed Vue to VueInstance\",\n      \"tree\": {\n        \"sha\": \"f98aa2b72ca181035a1f6a988a02dbb6d8b3d530\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/f98aa2b72ca181035a1f6a988a02dbb6d8b3d530\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/37fb44c39be49ed40d7dce3d4d5978853e4d82f0\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/37fb44c39be49ed40d7dce3d4d5978853e4d82f0/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/5f907e1325d56ba0736f9702d29a523d8a2d00fb\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjVmOTA3ZTEzMjVkNTZiYTA3MzZmOTcwMmQyOWE1MjNkOGEyZDAwZmI=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T19:54:39Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T19:54:39Z\"\n      },\n      \"message\": \"[docs] directives docs updated\",\n      \"tree\": {\n        \"sha\": \"7bf96997338f7352247027ee3dad9af699c9f0cb\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/7bf96997338f7352247027ee3dad9af699c9f0cb\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/5f907e1325d56ba0736f9702d29a523d8a2d00fb\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/5f907e1325d56ba0736f9702d29a523d8a2d00fb/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"94a4affca971fc463dd200affd34e2389aef4c0d\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/94a4affca971fc463dd200affd34e2389aef4c0d\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/94a4affca971fc463dd200affd34e2389aef4c0d\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"94a4affca971fc463dd200affd34e2389aef4c0d\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjk0YTRhZmZjYTk3MWZjNDYzZGQyMDBhZmZkMzRlMjM4OWFlZjRjMGQ=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T19:41:14Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T19:52:42Z\"\n      },\n      \"message\": \"[feature] full local directives supported\",\n      \"tree\": {\n        \"sha\": \"0c2fa597175f483f83b87ae6be83d4938906467f\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/0c2fa597175f483f83b87ae6be83d4938906467f\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/94a4affca971fc463dd200affd34e2389aef4c0d\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/94a4affca971fc463dd200affd34e2389aef4c0d\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/94a4affca971fc463dd200affd34e2389aef4c0d\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/94a4affca971fc463dd200affd34e2389aef4c0d/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/9ad2d7a56ca8cad51366bd317cab9591c65e72dc\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjlhZDJkN2E1NmNhOGNhZDUxMzY2YmQzMTdjYWI5NTkxYzY1ZTcyZGM=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T18:40:26Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T18:40:26Z\"\n      },\n      \"message\": \"[docs] updated informations to py/js bridge\",\n      \"tree\": {\n        \"sha\": \"a33ad0f207db1f77786792d31dc11a8785562210\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/a33ad0f207db1f77786792d31dc11a8785562210\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/9ad2d7a56ca8cad51366bd317cab9591c65e72dc\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/9ad2d7a56ca8cad51366bd317cab9591c65e72dc/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/cd655a5bf24284aa0544e84810f3a5eb298336e7\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOmNkNjU1YTViZjI0Mjg0YWEwNTQ0ZTg0ODEwZjNhNWViMjk4MzM2ZTc=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T18:05:02Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T18:05:02Z\"\n      },\n      \"message\": \"[docs] updated limitations\",\n      \"tree\": {\n        \"sha\": \"40c98949632915ce909caf5abf651f94bebc6002\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/40c98949632915ce909caf5abf651f94bebc6002\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/cd655a5bf24284aa0544e84810f3a5eb298336e7\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cd655a5bf24284aa0544e84810f3a5eb298336e7/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/3f4b7ae5db7c445acade2e5421cc58d508eab755\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjNmNGI3YWU1ZGI3YzQ0NWFjYWRlMmU1NDIxY2M1OGQ1MDhlYWI3NTU=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:46:58Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:46:58Z\"\n      },\n      \"message\": \"[examples] separate app and components\",\n      \"tree\": {\n        \"sha\": \"56d5e522c853a6ff1312ea4c59e030e09b9f005e\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/56d5e522c853a6ff1312ea4c59e030e09b9f005e\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/3f4b7ae5db7c445acade2e5421cc58d508eab755\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/3f4b7ae5db7c445acade2e5421cc58d508eab755/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOmNkZDllYTliZmJiMTk5M2M2ZDYyMzcwNDU2ZjhjZDNmN2ExODJhYWU=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:45:44Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:45:44Z\"\n      },\n      \"message\": \"[feature] wrap types for directives and filter methods\",\n      \"tree\": {\n        \"sha\": \"32d8ffca8bbfc96855e1f1388371f48b9cfac544\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/32d8ffca8bbfc96855e1f1388371f48b9cfac544\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/cdd9ea9bfbb1993c6d62370456f8cd3f7a182aae/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/6ced874bbcc8d6db932b6714f30669bf8c5e5e91\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOjZjZWQ4NzRiYmNjOGQ2ZGI5MzJiNjcxNGYzMDY2OWJmOGM1ZTVlOTE=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:31:31Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:31:31Z\"\n      },\n      \"message\": \"[refactoring] separate wrapping decorator from inject vue instance decorator\",\n      \"tree\": {\n        \"sha\": \"4f264bde73993a57ed958ffee2de060083f68ea1\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/4f264bde73993a57ed958ffee2de060083f68ea1\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/6ced874bbcc8d6db932b6714f30669bf8c5e5e91\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/6ced874bbcc8d6db932b6714f30669bf8c5e5e91/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\"\n      }\n    ]\n  },\n  {\n    \"sha\": \"c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n    \"node_id\": \"MDY6Q29tbWl0MTM5NDg4NjkwOmM5ZjRiODMwOWZhMjg4ZDgyZTMzYjEwZmU3ZGZlY2I3ZTRjYjdlNTU=\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:19:34Z\"\n      },\n      \"committer\": {\n        \"name\": \"Stefan Hoelzl\",\n        \"email\": \"stefan.hoelzl@posteo.de\",\n        \"date\": \"2018-07-20T17:19:34Z\"\n      },\n      \"message\": \"[feature] access dict items also as attributes\",\n      \"tree\": {\n        \"sha\": \"ec4eb5123a1f44f8a8166671de14697ff7c44437\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/trees/ec4eb5123a1f44f8a8166671de14697ff7c44437\"\n      },\n      \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/git/commits/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n      \"comment_count\": 0,\n      \"verification\": {\n        \"verified\": false,\n        \"reason\": \"unsigned\",\n        \"signature\": null,\n        \"payload\": null\n      }\n    },\n    \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n    \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55\",\n    \"comments_url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/c9f4b8309fa288d82e33b10fe7dfecb7e4cb7e55/comments\",\n    \"author\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"committer\": {\n      \"login\": \"stefanhoelzl\",\n      \"id\": 1478183,\n      \"node_id\": \"MDQ6VXNlcjE0NzgxODM=\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1478183?v=4\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/stefanhoelzl\",\n      \"html_url\": \"https://github.com/stefanhoelzl\",\n      \"followers_url\": \"https://api.github.com/users/stefanhoelzl/followers\",\n      \"following_url\": \"https://api.github.com/users/stefanhoelzl/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/stefanhoelzl/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/stefanhoelzl/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/stefanhoelzl/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/stefanhoelzl/orgs\",\n      \"repos_url\": \"https://api.github.com/users/stefanhoelzl/repos\",\n      \"events_url\": \"https://api.github.com/users/stefanhoelzl/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/stefanhoelzl/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false\n    },\n    \"parents\": [\n      {\n        \"sha\": \"f9b50084be79b819be262d2e8a37f68d80a182b2\",\n        \"url\": \"https://api.github.com/repos/stefanhoelzl/vue.py/commits/f9b50084be79b819be262d2e8a37f68d80a182b2\",\n        \"html_url\": \"https://github.com/stefanhoelzl/vue.py/commit/f9b50084be79b819be262d2e8a37f68d80a182b2\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "examples/github_commits/style.css",
    "content": "#demo {\n  font-family: 'Helvetica', Arial, sans-serif;\n}\na {\n  text-decoration: none;\n  color: #f66;\n}\nli {\n  line-height: 1.5em;\n  margin-bottom: 20px;\n}\n.author, .date {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "examples/github_commits/vuepy.yml",
    "content": "templates:\n  commits: commits.html\nstylesheets:\n  - style.css\n"
  },
  {
    "path": "examples/grid_component/app.py",
    "content": "from vue import VueComponent, data, computed, filters\n\n\nclass GridComponent(VueComponent):\n    template = \"#grid\"\n\n    content: list\n    columns: list\n    filter_key: str\n\n    sort_key = \"\"\n\n    @data\n    def sort_orders(self):\n        return {key: False for key in self.columns}\n\n    @computed\n    def filtered_data(self):\n        return list(\n            sorted(\n                filter(\n                    lambda f: self.filter_key.lower() in f[\"name\"].lower(), self.content\n                ),\n                reverse=self.sort_orders.get(self.sort_key, False),\n                key=lambda c: c.get(self.sort_key, self.columns[0]),\n            )\n        )\n\n    @staticmethod\n    @filters\n    def capitalize(value):\n        return value.capitalize()\n\n    def sort_by(self, key):\n        self.sort_key = key\n        self.sort_orders[key] = not self.sort_orders[key]\n\n\nGridComponent.register(\"demo-grid\")\n\n\nclass App(VueComponent):\n    template = \"#form\"\n\n    search_query = \"\"\n    grid_columns = [\"name\", \"power\"]\n    grid_data = [\n        {\"name\": \"Chuck Norris\", \"power\": float(\"inf\")},\n        {\"name\": \"Bruce Lee\", \"power\": 9000},\n        {\"name\": \"Jackie Chan\", \"power\": 7000},\n        {\"name\": \"Jet Li\", \"power\": 8000},\n    ]\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/grid_component/form.html",
    "content": "<div>\n  <form id=\"search\">\n    Search <input id=\"query\" name=\"query\" v-model=\"search_query\">\n  </form>\n  <demo-grid\n    :content=\"grid_data\"\n    :columns=\"grid_columns\"\n    :filter_key=\"search_query\">\n  </demo-grid>\n</div>\n"
  },
  {
    "path": "examples/grid_component/grid.html",
    "content": "<table>\n  <thead>\n    <tr>\n      <th v-for=\"key in columns\"\n        @click=\"sort_by(key)\"\n        :id=\"key\"\n        :class=\"{ active: sort_key == key }\">\n        {{ key | capitalize }}\n        <span class=\"arrow\" :class=\"sort_orders[key] ? 'asc' : 'dsc'\">\n        </span>\n      </th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr v-for=\"entry in filtered_data\">\n      <td v-for=\"key in columns\">\n        {{ entry[key] }}\n      </td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "examples/grid_component/style.css",
    "content": "body {\n  font-family: Helvetica Neue, Arial, sans-serif;\n  font-size: 14px;\n  color: #444;\n}\n\ntable {\n  border: 2px solid #42b983;\n  border-radius: 3px;\n  background-color: #fff;\n}\n\nth {\n  background-color: #42b983;\n  color: rgba(255,255,255,0.66);\n  cursor: pointer;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n\ntd {\n  background-color: #f9f9f9;\n}\n\nth, td {\n  min-width: 120px;\n  padding: 10px 20px;\n}\n\nth.active {\n  color: #fff;\n}\n\nth.active .arrow {\n  opacity: 1;\n}\n\n.arrow {\n  display: inline-block;\n  vertical-align: middle;\n  width: 0;\n  height: 0;\n  margin-left: 5px;\n  opacity: 0.66;\n}\n\n.arrow.asc {\n  border-left: 4px solid transparent;\n  border-right: 4px solid transparent;\n  border-bottom: 4px solid #fff;\n}\n\n.arrow.dsc {\n  border-left: 4px solid transparent;\n  border-right: 4px solid transparent;\n  border-top: 4px solid #fff;\n}\n"
  },
  {
    "path": "examples/grid_component/vuepy.yml",
    "content": "templates:\n  form: form.html\n  grid: grid.html\nstylesheets:\n  - style.css\n"
  },
  {
    "path": "examples/index.md",
    "content": "# Example Gallery\n\n## TodoMVC\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/todo_mvc) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/todo_mvc)\n\n![TodoMVC Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/todo_mvc/screenshot.png)\n\n## SVG Graph\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/svg_graph) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/svg_graph)\n\n![SVG Graph Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/svg_graph/screenshot.png)\n\n\n## Markdown Editor\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/markdown_editor) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/markdown_editor)\n\n![Markdown Editor Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/markdown_editor/screenshot.png)\n\n## GitHub Commits\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/github_commits) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/github_commits)\n\n![GitHub Commits Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/github_commits/screenshot.png)\n\n## Grid Component\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/grid_component) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/grid_component)\n\n![Grid Component Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/grid_component/screenshot.png)\n\n## Tree View\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/tree_view) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/tree_view)\n\n![Tree View Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/tree_view/screenshot.png)\n\n## Modal Component\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/modal_component) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/modal_component)\n\n![Modal Component Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/modal_component/screenshot.png)\n\n## Elastic Header\n[Demo](https://stefanhoelzl.github.io/vue.py/examples/elastic_header) / [Source](https://github.com/stefanhoelzl/vue.py/tree/master/examples/elastic_header)\n\n![Elastic Header Screenshot](https://raw.githubusercontent.com/stefanhoelzl/vue.py/gh-pages/examples/elastic_header/screenshot.png)\n\n\n## Run Examples Local\n\n```bash\n$ git clone https://github.com/stefanhoelzl/vue.py.git\n$ cd vue.py\n$ make env.up\n$ make run APP=examples/<example-name>\n```\nGoto [http://localhost:5000](http://localhost:5000)\n\n"
  },
  {
    "path": "examples/markdown_editor/app.py",
    "content": "from vue import VueComponent, computed\nfrom vue.utils import js_lib\n\nmarked = js_lib(\"marked\")\n\n\nclass App(VueComponent):\n    template = \"#editor-template\"\n\n    input = \"# Editor\"\n\n    @computed\n    def compiled_markdown(self):\n        return marked(self.input, {\"sanitize\": True})\n\n    def update(self, event):\n        self.input = event.target.value\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/markdown_editor/editor.html",
    "content": "<div id=\"editor\">\n  <textarea :value=\"input\" @input=\"update\" id=\"markdown\"></textarea>\n  <div v-html=\"compiled_markdown\"></div>\n</div>\n"
  },
  {
    "path": "examples/markdown_editor/style.css",
    "content": "html, body, #editor {\n  margin: 0;\n  height: 100%;\n  font-family: 'Helvetica Neue', Arial, sans-serif;\n  color: #333;\n}\n\ntextarea, #editor div {\n  display: inline-block;\n  width: 49%;\n  height: 100%;\n  vertical-align: top;\n  box-sizing: border-box;\n  padding: 0 20px;\n}\n\ntextarea {\n  border: none;\n  border-right: 1px solid #ccc;\n  resize: none;\n  outline: none;\n  background-color: #f6f6f6;\n  font-size: 14px;\n  font-family: 'Monaco', courier, monospace;\n  padding: 20px;\n}\n\ncode {\n  color: #f66;\n}\n"
  },
  {
    "path": "examples/markdown_editor/vuepy.yml",
    "content": "templates:\n  editor-template: editor.html\nstylesheets:\n  - style.css\nscripts:\n  - https://unpkg.com/marked@0.3.6\n"
  },
  {
    "path": "examples/modal_component/app.py",
    "content": "from vue import VueComponent\n\n\nclass Modal(VueComponent):\n    template = \"#modal-template\"\n\n\nModal.register()\n\n\nclass App(VueComponent):\n    template = \"#main\"\n    show_modal = False\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/modal_component/main.html",
    "content": "<div>\n  <button id=\"show-modal\" @click=\"show_modal = true\">\n    Show Modal\n  </button>\n  <!-- use the modal component, pass in the prop -->\n  <modal v-if=\"show_modal\" @close=\"show_modal = false\" id=\"modal_view\">\n    <!--\n      you can use custom content here to overwrite\n      default content\n    -->\n    <h3 slot=\"header\">custom header</h3>\n  </modal>\n</div>\n"
  },
  {
    "path": "examples/modal_component/modal-template.html",
    "content": "    <transition name=\"modal\">\n      <div class=\"modal-mask\">\n        <div class=\"modal-wrapper\">\n          <div class=\"modal-container\">\n\n            <div class=\"modal-header\">\n              <slot name=\"header\">\n                default header\n              </slot>\n            </div>\n\n            <div class=\"modal-body\">\n              <slot name=\"body\">\n                default body\n              </slot>\n            </div>\n\n            <div class=\"modal-footer\">\n              <slot name=\"footer\">\n                default footer\n                <button class=\"modal-default-button\" @click=\"$emit('close')\">\n                  OK\n                </button>\n              </slot>\n            </div>\n          </div>\n        </div>\n      </div>\n    </transition>\n"
  },
  {
    "path": "examples/modal_component/style.css",
    "content": ".modal-mask {\n  position: fixed;\n  z-index: 9998;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, .5);\n  display: table;\n  transition: opacity .3s ease;\n}\n\n.modal-wrapper {\n  display: table-cell;\n  vertical-align: middle;\n}\n\n.modal-container {\n  width: 300px;\n  margin: 0px auto;\n  padding: 20px 30px;\n  background-color: #fff;\n  border-radius: 2px;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, .33);\n  transition: all .3s ease;\n  font-family: Helvetica, Arial, sans-serif;\n}\n\n.modal-header h3 {\n  margin-top: 0;\n  color: #42b983;\n}\n\n.modal-body {\n  margin: 20px 0;\n}\n\n.modal-default-button {\n  float: right;\n}\n\n/*\n * The following styles are auto-applied to elements with\n * transition=\"modal\" when their visibility is toggled\n * by Vue.js.\n *\n * You can easily play with the modal transition by editing\n * these styles.\n */\n\n.modal-enter {\n  opacity: 0;\n}\n\n.modal-leave-active {\n  opacity: 0;\n}\n\n.modal-enter .modal-container,\n.modal-leave-active .modal-container {\n  -webkit-transform: scale(1.1);\n  transform: scale(1.1);\n}\n"
  },
  {
    "path": "examples/modal_component/vuepy.yml",
    "content": "templates:\n  modal-template: modal-template.html\n  main: main.html\nstylesheets:\n  - style.css\n"
  },
  {
    "path": "examples/svg_graph/app-template.html",
    "content": "<div>\n  <!-- Use the component -->\n  <svg width=\"200\" height=\"200\">\n    <polygraph :stats=\"stats\"></polygraph>\n  </svg>\n  <!-- controls -->\n  <div v-for=\"stat in stats\">\n    <label>{{stat.label}}</label>\n    <input type=\"range\" v-model=\"stat.value\" min=\"0\" max=\"100\">\n    <span>{{stat.value}}</span>\n    <button @click=\"remove(stat)\"\n            class=\"remove\"\n            :disabled=\"disable_remove\">X</button>\n  </div>\n  <form id=\"add\">\n    <input name=\"new_label\" v-model=\"new_label\">\n    <button @click=\"add\">Add a Stat</button>\n  </form>\n  <pre id=\"raw\">{{ stats }}</pre>\n  <p style=\"font-size:12px\">* input[type=\"range\"] requires IE10 or above.</p>\n</div>\n"
  },
  {
    "path": "examples/svg_graph/app.py",
    "content": "import math\n\nfrom vue import VueComponent, computed\n\n\nstats = [\n    {\"label\": \"A\", \"value\": 100},\n    {\"label\": \"B\", \"value\": 100},\n    {\"label\": \"C\", \"value\": 100},\n    {\"label\": \"D\", \"value\": 100},\n    {\"label\": \"E\", \"value\": 100},\n    {\"label\": \"F\", \"value\": 100},\n]\n\n\ndef value_to_point(value, index, total):\n    x = 0\n    y = -value * 0.8\n    angle = math.pi * 2 / total * index\n    cos = math.cos(angle)\n    sin = math.sin(angle)\n    tx = x * cos - y * sin + 100\n    ty = x * sin + y * cos + 100\n    return tx, ty\n\n\nclass AxisLabel(VueComponent):\n    template = '<text :x=\"point[0]\" :y=\"point[1]\">{{stat.label}}</text>'\n\n    stat: dict\n    index: int\n    total: int\n\n    @computed\n    def point(self):\n        return value_to_point(+int(self.stat[\"value\"]) + 10, self.index, self.total)\n\n\nAxisLabel.register()\n\n\nclass Polygraph(VueComponent):\n    template = \"#polygraph-template\"\n    stats: list\n\n    @computed\n    def points(self):\n        return \" \".join(\n            map(\n                lambda e: \",\".join(\n                    str(p)\n                    for p in value_to_point(int(e[1][\"value\"]), e[0], len(self.stats))\n                ),\n                enumerate(self.stats),\n            )\n        )\n\n\nPolygraph.register()\n\n\nclass App(VueComponent):\n    template = \"#app-template\"\n    new_label = \"\"\n    stats = stats\n\n    @computed\n    def disable_remove(self):\n        return len(self.stats) <= 3\n\n    def add(self, event):\n        event.preventDefault()\n        if self.new_label:\n            self.stats.append({\"label\": self.new_label, \"value\": 100})\n            self.new_label = \"\"\n\n    def remove(self, stat):\n        del self.stats[self.stats.index(stat)]\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/svg_graph/polygraph-template.html",
    "content": "<g>\n  <polygon :points=\"points\"></polygon>\n  <circle cx=\"100\" cy=\"100\" r=\"80\"></circle>\n  <axis-label\n    v-for=\"(stat, index) in stats\"\n    :key=\"index\"\n    :stat=\"stat\"\n    :index=\"index\"\n    :total=\"stats.length\">\n  </axis-label>\n</g>\n"
  },
  {
    "path": "examples/svg_graph/style.css",
    "content": "body {\n    font-family: Helvetica Neue, Arial, sans-serif;\n}\n\npolygon {\n    fill: #42b983;\n    opacity: .75;\n}\n\ncircle {\n    fill: transparent;\n    stroke: #999;\n}\n\ntext {\n    font-family: Helvetica Neue, Arial, sans-serif;\n    font-size: 10px;\n    fill: #666;\n}\n\nlabel {\n    display: inline-block;\n    margin-left: 10px;\n    width: 20px;\n}\n\n#raw {\n    position: absolute;\n    top: 0;\n    left: 300px;\n}\n"
  },
  {
    "path": "examples/svg_graph/vuepy.yml",
    "content": "templates:\n  app-template: app-template.html\n  polygraph-template: polygraph-template.html\nstylesheets:\n  - style.css\n"
  },
  {
    "path": "examples/todo_mvc/app-template.html",
    "content": "<div>\n  <section class=\"todoapp\">\n    <header class=\"header\">\n      <h1>todos</h1>\n      <input class=\"new-todo\"\n        autofocus autocomplete=\"off\"\n        placeholder=\"What needs to be done?\"\n        v-model=\"new_todo\"\n        id=\"title-input\"\n        @keyup.enter=\"add_todo\">\n    </header>\n    <section class=\"main\" v-show=\"todos.length\" v-cloak>\n      <input class=\"toggle-all\" type=\"checkbox\" v-model=\"all_done\">\n      <ul class=\"todo-list\">\n        <li v-for=\"todo in filtered_todos\"\n          class=\"todo\"\n          :key=\"todo.id\"\n          :class=\"{ completed: todo.completed, editing: todo == edited_todo }\">\n          <div class=\"view\">\n            <input class=\"toggle\" type=\"checkbox\" v-model=\"todo.completed\">\n            <label @dblclick=\"edit_todo(todo)\">{{ todo.title }}</label>\n            <button class=\"destroy\" @click=\"remove_todo(todo)\"></button>\n          </div>\n          <input class=\"edit\" type=\"text\"\n            v-model=\"todo.title\"\n            v-todo-focus=\"todo == edited_todo\"\n            @blur=\"done_edit(todo)\"\n            @keyup.enter=\"done_edit(todo)\"\n            @keyup.esc=\"cancel_edit(todo)\">\n        </li>\n      </ul>\n    </section>\n    <footer class=\"footer\" v-show=\"todos.length\" v-cloak>\n      <span class=\"todo-count\">\n        <strong>{{ remaining }}</strong> {{ remaining | pluralize }} left\n      </span>\n      <ul class=\"filters\">\n        <li><a href=\"#all\" :class=\"{ selected: visibility == 'all' }\" id=\"show-all\">All</a></li>\n        <li><a href=\"#active\" :class=\"{ selected: visibility == 'active' }\" id=\"show-active\">Active</a></li>\n        <li><a href=\"#completed\" :class=\"{ selected: visibility == 'completed' }\" id=\"show-acompleted\">Completed</a></li>\n      </ul>\n      <button class=\"clear-completed\" @click=\"remove_completed\" v-show=\"todos.length > remaining\">\n        Clear completed\n      </button>\n    </footer>\n  </section>\n  <footer class=\"info\">\n    <p>Double-click to edit a todo</p>\n    <p>Written by <a href=\"https://github.com/stefanhoelzl\">Stefan Hoelzl</a></p>\n    <p>Original JS verison by <a href=\"http://evanyou.me\">Evan You</a></p>\n  </footer>\n</div>\n"
  },
  {
    "path": "examples/todo_mvc/app.py",
    "content": "from browser.local_storage import storage\nfrom browser import window\nimport json\nfrom vue import VueComponent, computed, filters, watch, directive\nfrom vue.bridge import Object\n\nSTORAGE_KEY = \"todos-vue.py\"\n\n\nclass ToDoStorage:\n    NEXT_UID = 0\n\n    @classmethod\n    def next_uid(cls):\n        uid = cls.NEXT_UID\n        cls.NEXT_UID += 1\n        return uid\n\n    @classmethod\n    def fetch(cls):\n        cls.NEXT_UID = 0\n        todos = json.loads(storage.get(STORAGE_KEY, \"[]\"))\n        return [{\"id\": cls.next_uid(), **todo} for todo in todos]\n\n    @staticmethod\n    def save(todos):\n        storage[STORAGE_KEY] = json.dumps(todos)\n\n\nclass VisibilityFilters:\n    def __new__(cls, visibility):\n        try:\n            return getattr(VisibilityFilters, visibility)\n        except AttributeError:\n            return False\n\n    @staticmethod\n    def all(todos):\n        return [todo for todo in todos]\n\n    @staticmethod\n    def active(todos):\n        return [todo for todo in todos if not todo.get(\"completed\", False)]\n\n    @staticmethod\n    def completed(todos):\n        return [todo for todo in todos if todo.get(\"completed\", False)]\n\n\nclass App(VueComponent):\n    template = \"#app-template\"\n    todos = ToDoStorage.fetch()\n    new_todo = \"\"\n    edited_todo = None\n    edit_cache = \"\"\n    visibility = \"all\"\n\n    @watch(\"todos\", deep=True)\n    def save_todos(self, new, old):\n        ToDoStorage.save(Object.to_py(new))\n\n    @computed\n    def filtered_todos(self):\n        return VisibilityFilters(self.visibility)(self.todos)\n\n    @computed\n    def remaining(self):\n        return len(VisibilityFilters.active(self.todos))\n\n    @computed\n    def all_done(self):\n        return self.remaining == 0\n\n    @all_done.setter\n    def all_done(self, value):\n        for todo in self.todos:\n            todo[\"completed\"] = value\n\n    @staticmethod\n    @filters\n    def pluralize(n):\n        return \"item\" if n == 1 else \"items\"\n\n    def add_todo(self, ev=None):\n        value = self.new_todo.strip()\n        if not value:\n            return\n        self.todos.append(\n            {\"id\": ToDoStorage.next_uid(), \"title\": value, \"completed\": False}\n        )\n        self.new_todo = \"\"\n\n    def remove_todo(self, todo):\n        del self.todos[self.todos.index(todo)]\n\n    def edit_todo(self, todo):\n        self.edit_cache = todo[\"title\"]\n        self.edited_todo = todo\n\n    def done_edit(self, todo):\n        if not self.edited_todo:\n            return\n\n        self.edited_todo = None\n        todo[\"title\"] = todo[\"title\"].strip()\n        if not todo[\"title\"]:\n            self.remove_todo(todo)\n\n    def cancel_edit(self, todo):\n        self.edited_todo = None\n        todo.title = self.edit_cache\n\n    def remove_completed(self, ev=None):\n        self.todos = VisibilityFilters.active(self.todos)\n\n    @staticmethod\n    @directive\n    def todo_focus(el, binding, vnode, old_vnode, *args):\n        if binding.value:\n            el.focus()\n\n\napp = App(\"#app\")\n\n\ndef on_hash_change(ev):\n    visibility = window.location.hash.replace(\"#\", \"\").replace(\"/\", \"\")\n    if VisibilityFilters(visibility):\n        app.visibility = visibility\n    else:\n        window.location.hash = \"\"\n        app.visibility = \"all\"\n\n\nwindow.bind(\"hashchange\", on_hash_change)\n"
  },
  {
    "path": "examples/todo_mvc/style.css",
    "content": "[v-cloak] { display: none; }\n"
  },
  {
    "path": "examples/todo_mvc/vuepy.yml",
    "content": "templates:\n  app-template: app-template.html\nstylesheets:\n  - style.css\n  - https://unpkg.com/todomvc-app-css@2.0.6/index.css\n"
  },
  {
    "path": "examples/tree_view/app-template.html",
    "content": "<div>\n    <p>(You can double click on an item to turn it into a folder.)</p>\n\n    <!-- the demo root element -->\n    <ul id=\"demo\">\n        <tree\n          class=\"tree\"\n          :model=\"tree_data\">\n        </tree>\n    </ul>\n</div>\n"
  },
  {
    "path": "examples/tree_view/app.py",
    "content": "from vue import VueComponent, computed\n\ndemo_data = {\n    \"name\": \"My Tree\",\n    \"children\": [\n        {\"name\": \"hello\"},\n        {\"name\": \"wat\"},\n        {\n            \"name\": \"child folder\",\n            \"children\": [\n                {\n                    \"name\": \"child folder\",\n                    \"children\": [{\"name\": \"hello\"}, {\"name\": \"wat\"}],\n                },\n                {\"name\": \"hello\"},\n                {\"name\": \"wat\"},\n                {\n                    \"name\": \"child folder\",\n                    \"children\": [{\"name\": \"hello\"}, {\"name\": \"wat\"}],\n                },\n            ],\n        },\n    ],\n}\n\n\nclass Tree(VueComponent):\n    template = \"#tree-template\"\n    model: dict\n    open = False\n\n    @computed\n    def is_folder(self):\n        return len(self.model.get(\"children\", ())) > 0\n\n    def toggle(self, ev=None):\n        if self.is_folder:\n            self.open = not self.open\n\n    def change_type(self, ev=None):\n        if not self.is_folder:\n            self.model[\"children\"] = []\n            self.add_child()\n            self.open = True\n\n    def add_child(self, ev=None):\n        self.model[\"children\"].append({\"name\": \"new stuff\"})\n\n\nTree.register()\n\n\nclass App(VueComponent):\n    template = \"#app-template\"\n    tree_data = demo_data\n\n\nApp(\"#app\")\n"
  },
  {
    "path": "examples/tree_view/style.css",
    "content": "body {\n  font-family: Menlo, Consolas, monospace;\n  color: #444;\n}\n.tree {\n  cursor: pointer;\n}\n.bold {\n  font-weight: bold;\n}\nul {\n  padding-left: 1em;\n  line-height: 1.5em;\n  list-style-type: dot;\n}\n"
  },
  {
    "path": "examples/tree_view/tree-template.html",
    "content": "<li>\n  <div\n    :class=\"{bold: is_folder}\"\n    @click=\"toggle\"\n    @dblclick=\"change_type\">\n    {{ model.name }}\n    <span v-if=\"is_folder\">[{{ open ? '-' : '+' }}]</span>\n  </div>\n  <ul v-show=\"open\" v-if=\"is_folder\">\n    <tree\n      class=\"tree\"\n      v-for=\"(model, index) in model.children\"\n      :key=\"index\"\n      :model=\"model\">\n    </tree>\n    <li class=\"add\" @click=\"add_child\">+</li>\n  </ul>\n</li>\n"
  },
  {
    "path": "examples/tree_view/vuepy.yml",
    "content": "templates:\n  tree-template: tree-template.html\n  app-template: app-template.html\nstylesheets:\n  - style.css\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"setuptools\",\n    \"requests\",\n    \"python-project-tools@git+https://github.com/stefanhoelzl/python-project-tools.git\"\n]\n\n[tool.python-project-tools]\nstart-commit = \"f4256454256ddfe54a8be6dea493d3fc915ef1a2\"\n"
  },
  {
    "path": "requirements.txt",
    "content": "# provider\nFlask==2.2.2\n\n# packaging\nwheel==0.38.4\n\n# testing\npytest==7.2.1\nselenium==4.8.0\npyderman==3.3.2\nrequests==2.28.2\n\n# linting\nblack==23.1.0\n\n# releasing\nsemver==2.13.0\ngit+https://github.com/stefanhoelzl/python-project-tools.git\n"
  },
  {
    "path": "setup.py",
    "content": "from pathlib import Path\nfrom setuptools import setup\n\nimport requests\nfrom tools import release\n\n\ndef make_version():\n    version = release.version()\n    Path(\"vue/__version__.py\").write_text(f'__version__ = \"{version}\"\\n')\n    return version\n\n\ndef fetch_vue_cli_js_file(source: str, name: str) -> str:\n    js_data_base = Path(__file__).parent / \"vuecli\" / \"js\"\n    js_data_base.mkdir(exist_ok=True)\n\n    dest_path = js_data_base / name\n    resp = requests.get(source)\n    assert resp.ok\n    dest_path.write_bytes(resp.content)\n\n    return f\"js/{name}\"\n\n\nsetup(\n    name=\"vuepy\",\n    version=make_version(),\n    description=\"Pythonic Vue\",\n    long_description=Path(\"README.md\").read_text(),\n    long_description_content_type=\"text/markdown\",\n    classifiers=[\n        \"Development Status :: 4 - Beta\",\n        \"Environment :: Web Environment\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.6\",\n        \"Operating System :: MacOS :: MacOS X\",\n        \"Operating System :: POSIX :: Linux\",\n        \"Operating System :: Microsoft :: Windows\",\n        \"Topic :: Internet :: WWW/HTTP\",\n        \"Topic :: Software Development\",\n        \"Topic :: Software Development :: Libraries :: Application Frameworks\",\n    ],\n    keywords=\"web reactive gui framework\",\n    url=\"https://stefanhoelzl.github.io/vue.py/\",\n    author=\"Stefan Hoelzl\",\n    author_email=\"stefan.hoelzl@posteo.de\",\n    license=\"MIT\",\n    packages=[\"vuecli\", \"vuecli.provider\", \"vue\", \"vue.bridge\", \"vue.decorators\"],\n    install_requires=[\"brython==3.8.9\", \"Jinja2>=2.10\", \"pyyaml>=5.1\"],\n    extras_require={\"flask\": [\"Flask>=1.0\"]},\n    package_data={\n        \"vuecli\": [\n            \"index.html\",\n            \"loading.gif\",\n            fetch_vue_cli_js_file(\"https://unpkg.com/vue@2.6.14/dist/vue.js\", \"vue.js\"),\n            fetch_vue_cli_js_file(\n                \"https://raw.githubusercontent.com/vuejs/vue/dev/LICENSE\", \"LICENSE_VUE\"\n            ),\n            fetch_vue_cli_js_file(\n                \"https://unpkg.com/vuex@3.6.2/dist/vuex.js\", \"vuex.js\"\n            ),\n            fetch_vue_cli_js_file(\n                \"https://raw.githubusercontent.com/vuejs/vuex/master/LICENSE\",\n                \"LICENSE_VUEX\",\n            ),\n            fetch_vue_cli_js_file(\n                \"https://unpkg.com/vue-router@3.5.1/dist/vue-router.js\", \"vue-router.js\"\n            ),\n            fetch_vue_cli_js_file(\n                \"https://raw.githubusercontent.com/vuejs/vue-router/dev/LICENSE\",\n                \"LICENSE_VUE_ROUTER\",\n            ),\n        ]\n    },\n    entry_points={\n        \"console_scripts\": [\"vue-cli=vuecli.cli:main\"],\n        \"vuecli.provider\": [\n            \"static=vuecli.provider.static:Static\",\n            \"flask=vuecli.provider.flask:Flask\",\n        ],\n    },\n    zip_safe=False,\n)\n"
  },
  {
    "path": "stubs/browser.py",
    "content": "\"\"\"stub to avoid import errors\"\"\"\n\nimport local_storage\n\n\ndef load(path):\n    ...\n\n\ndef bind(target, ev):\n    ...\n\n\nclass window:\n    String = str\n    Number = int\n    Boolean = bool\n\n    class Object:\n        def __init__(self, obj):\n            ...\n\n        @staticmethod\n        def assign(target, *sources):\n            ...\n\n        @staticmethod\n        def keys(obj):\n            ...\n\n    @staticmethod\n    def bind(el, ev):\n        ...\n\n    class location:\n        hash = \"\"\n\n    class Array:\n        def __init__(self, *objs):\n            ...\n\n        @classmethod\n        def isArray(cls, obj):\n            ...\n\n    class Vuex:\n        class Store:\n            @classmethod\n            def new(cls, *args, **kwargs):\n                ...\n\n    class VueRouter:\n        @classmethod\n        def new(cls, *args, **kwargs):\n            ...\n\n    class Vue:\n        @classmethod\n        def new(cls, *args, **kwargs):\n            ...\n\n        @classmethod\n        def component(cls, name, opts=None):\n            ...\n\n        @classmethod\n        def set(cls, obj, key, value):\n            ...\n\n        @classmethod\n        def delete(cls, obj, key):\n            ...\n\n        @classmethod\n        def use(cls, plugin, *args, **kwargs):\n            ...\n\n        @classmethod\n        def directive(cls, name, directive=None):\n            ...\n\n        @classmethod\n        def filter(cls, name, method):\n            ...\n\n        @classmethod\n        def mixin(cls, mixin):\n            ...\n\n\nclass timer:\n    @staticmethod\n    def set_interval(fn, interval):\n        ...\n\n\nclass ajax:\n    class ajax:\n        def open(self, method, url, asnc):\n            ...\n\n        def bind(self, ev, method):\n            ...\n\n        def send(self):\n            ...\n"
  },
  {
    "path": "stubs/javascript.py",
    "content": "\"\"\"stub to avoid import errors\"\"\"\n\n\ndef this():\n    return None\n"
  },
  {
    "path": "stubs/local_storage.py",
    "content": "storage = dict()\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/cli/test_provider.py",
    "content": "from xml.etree import ElementTree\n\nimport yaml\nimport pytest\n\nfrom vuecli.provider.provider import Provider\n\n\n@pytest.fixture\ndef render_index(tmp_path):\n    def render(config=None):\n        tmp_path.joinpath(\"vuepy.yml\").write_text(yaml.dump(config or {}))\n        provider = Provider(tmp_path)\n        return provider.render_index()\n\n    return render\n\n\ndef parse_index(index):\n    et = ElementTree.fromstring(index)\n    return {\n        \"stylesheets\": [e.attrib[\"href\"] for e in et.findall(\"head/link\")],\n        \"scripts\": [e.attrib[\"src\"] for e in et.findall(\"head/script\")],\n        \"templates\": {\n            e.attrib[\"id\"]: e.text.strip()\n            for e in et.findall(\"body/script[@type='x-template']\")\n        },\n        \"brython\": et.find(\"body\").attrib[\"onload\"],\n    }\n\n\nclass TestRenderIndex:\n    def test_defaults(self, render_index):\n        index = render_index()\n        assert parse_index(index) == {\n            \"stylesheets\": [],\n            \"scripts\": [\"vuepy.js\", \"vue.js\"],\n            \"templates\": {},\n            \"brython\": \"brython();\",\n        }\n\n    def test_custom_stylesheets(self, render_index):\n        index = render_index({\"stylesheets\": [\"first.css\", \"second.css\"]})\n        assert parse_index(index)[\"stylesheets\"] == [\"first.css\", \"second.css\"]\n\n    @pytest.mark.parametrize(\n        \"ext, js\", [(\"vuex\", \"vuex.js\"), (\"vue-router\", \"vue-router.js\")]\n    )\n    def test_enable_builtin_script(self, render_index, ext, js):\n        index = render_index({\"scripts\": {ext: True}})\n        assert js in parse_index(index)[\"scripts\"]\n\n    @pytest.mark.parametrize(\"ext\", [\"vue\", \"brython\", \"vuex\", \"vue-router\"])\n    def test_customize_builtin_script(self, render_index, ext):\n        index = render_index({\"scripts\": {ext: \"custom\"}})\n        assert \"custom\" in parse_index(index)[\"scripts\"]\n\n    def test_custom_script(self, render_index):\n        index = render_index({\"scripts\": [\"myscript.js\"]})\n        assert \"myscript.js\" in parse_index(index)[\"scripts\"]\n\n    def test_custom_template(self, render_index, tmp_path):\n        tmp_path.joinpath(\"my.html\").write_text(\"content\")\n        index = render_index({\"templates\": {\"my\": \"my.html\"}})\n        assert parse_index(index)[\"templates\"] == {\"my\": \"content\"}\n\n    def test_custom_brython_args(self, render_index):\n        index = render_index({\"brython_args\": {\"debug\": 10}})\n        assert parse_index(index)[\"brython\"] == \"brython({ debug: 10 });\"\n"
  },
  {
    "path": "tests/pytest.ini",
    "content": "[pytest]\naddopts =\n    --new-first\n    --failed-first\n    --capture=no\n    --tb=short\n"
  },
  {
    "path": "tests/selenium/.gitignore",
    "content": "_html\nchromedriver\n"
  },
  {
    "path": "tests/selenium/chromedriver.py",
    "content": "import re\nfrom subprocess import run\n\nimport pyderman\nimport requests\n\nLatestReleaseUrl = (\n    \"https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{version}\"\n)\n\nchrome_version_output = (\n    run([\"google-chrome\", \"--version\"], capture_output=True)\n    .stdout.decode(\"utf-8\")\n    .strip()\n)\n\nprint(chrome_version_output)\nchrome_major_version = re.search(\"Google Chrome (\\d+)\", chrome_version_output).group(1)\nchromedriver_version = requests.get(\n    LatestReleaseUrl.format(version=chrome_major_version)\n).text.strip()\nprint(f\"Chromedriver Version {chromedriver_version}\")\n\npyderman.install(\n    browser=pyderman.chrome,\n    file_directory=\"tests/selenium\",\n    filename=\"chromedriver\",\n    overwrite=True,\n    version=chromedriver_version,\n)\n"
  },
  {
    "path": "tests/selenium/conftest.py",
    "content": "import re\nimport os\nimport json\nimport inspect\nfrom pathlib import Path\nfrom contextlib import contextmanager\nfrom textwrap import dedent\nfrom threading import Thread\nfrom http.server import HTTPServer, SimpleHTTPRequestHandler\nfrom http.client import HTTPConnection\n\nimport yaml\nimport pytest\n\nfrom vuecli.provider.static import Static\n\nfrom selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.ui import WebDriverWait\nfrom selenium.webdriver.support import expected_conditions as ec\nfrom selenium.webdriver.common.desired_capabilities import DesiredCapabilities\nfrom selenium.common.exceptions import NoSuchElementException\n\nAddress = \"localhost\"\nPort = 8001\nBaseUrl = f\"http://{Address}:{Port}\"\n\nTEST_PATH = Path(__file__).parent\nCHROME_DRIVER_PATH = TEST_PATH / \"chromedriver\"\nHTML_OUTPUT_PATH = TEST_PATH / \"_html\"\nAPP_URL = BaseUrl + \"/{}/{}/deploy\"\nEXAMPLE_URL = BaseUrl + \"/examples_static/{}\"\nEXAMPLE_SCREENSHOT_PATH = \"examples_static/{}/screenshot.png\"\nDEFAULT_TIMEOUT = 5\n\n\n@pytest.fixture(scope=\"session\")\ndef http_server():\n    timeout = 10\n\n    class RequestHandler(SimpleHTTPRequestHandler):\n        protocol_version = \"HTTP/1.0\"\n\n        def log_message(self, *args):\n            pass\n\n    with HTTPServer((Address, Port), RequestHandler) as httpd:\n        thread = Thread(target=httpd.serve_forever, daemon=True)\n        thread.start()\n\n        c = HTTPConnection(Address, Port, timeout=timeout)\n        c.request(\"GET\", \"/\", \"\")\n        assert c.getresponse().status == 200\n        c.close()\n\n        try:\n            yield httpd\n        finally:\n            httpd.shutdown()\n            thread.join(timeout=timeout)\n\n\n@pytest.fixture(scope=\"session\")\ndef selenium_session(http_server):\n    with SeleniumSession() as session:\n        yield session\n\n\n@pytest.fixture()\ndef selenium(selenium_session, request):\n    selenium_session.request = request\n    yield selenium_session\n    selenium_session.request = None\n\n\nclass ErrorLogException(Exception):\n    def __init__(self, errors):\n        formatted_errors = []\n        for error in errors:\n            formatted_error = {**error}\n            formatted_error[\"message\"] = formatted_error[\"message\"].split(\"\\\\n\")\n            formatted_errors.append(formatted_error)\n        super().__init__(json.dumps(formatted_errors, indent=2))\n        self.errors = errors\n\n\nclass SeleniumSession:\n    def __init__(self):\n        self.driver = None\n        self.request = None\n\n        self.allowed_errors = []\n        self.logs = []\n        self._screenshot_file = None\n\n    def __getattr__(self, item):\n        return getattr(self.driver, item)\n\n    def __enter__(self):\n        options = webdriver.ChromeOptions()\n        options.add_argument(\"headless\")\n        options.add_argument(\"disable-gpu\")\n        options.add_argument(\"disable-dev-shm-usage\")\n        options.add_argument(\"no-sandbox\")\n        options.set_capability(\"goog:loggingPrefs\", {\"browser\": \"ALL\"})\n\n        self.driver = webdriver.Chrome(\n            service=webdriver.chrome.service.Service(\n                executable_path=CHROME_DRIVER_PATH,\n            ),\n            options=options,\n        )\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.driver.close()\n\n    def get_logs(self):\n        new_logs = self.driver.get_log(\"browser\")\n        self.logs.extend(new_logs)\n        return new_logs\n\n    def clear_logs(self):\n        self.allowed_errors.clear()\n        self.get_logs()\n        self.logs.clear()\n\n    @contextmanager\n    def url(self, url):\n        self.clear_logs()\n        self.driver.get(url)\n        try:\n            yield\n        finally:\n            self.analyze_logs()\n\n    @contextmanager\n    def app(self, app, config=None, files=None):\n        test_name = self.request.function.__name__\n        self._create_app_content(test_name, app, config or {}, files or {})\n        url_base = str(self._app_output_path.relative_to(Path(\".\").absolute()))\n        url = APP_URL.format(url_base, test_name)\n        with self.url(url):\n            yield\n\n    @property\n    def _app_output_path(self):\n        sub_path = Path(self.request.node.nodeid.split(\"::\", 1)[0])\n        try:\n            sub_path = sub_path.relative_to(\"selenium\")\n        except ValueError:\n            pass  # WORKAROUND when running single tests from PyCharm\n        output_path = HTML_OUTPUT_PATH / sub_path\n        output_path.mkdir(exist_ok=True, parents=True)\n        return output_path\n\n    def _create_app_content(self, test_name, app, config, files):\n        path = self._app_output_path / test_name\n        path.mkdir(exist_ok=True, parents=True)\n\n        code = \"from vue import *\\n\\n\\n\"\n        code += dedent(\"\\n\".join(inspect.getsource(app).split(\"\\n\")))\n        code += \"\"\"\\n\\napp = {}(\"#app\")\\n\"\"\".format(app.__name__)\n        (path / \"app.py\").write_text(code)\n\n        (path / \"vuepy.yml\").write_text(yaml.dump(config))\n\n        for filename, content in files.items():\n            (path / filename).write_text(content)\n\n        provider = Static(path)\n        provider.setup()\n        provider.deploy(path / \"deploy\")\n\n    @contextmanager\n    def example(self, hash_=None):\n        test_name = self.request.function.__name__\n        name = test_name[5:]\n        self._screenshot_file = Path(EXAMPLE_SCREENSHOT_PATH.format(name))\n        url = EXAMPLE_URL.format(name)\n\n        provider = Static(\"examples/{}\".format(name))\n        provider.setup()\n        provider.deploy(\"examples_static/{}\".format(name), package=True)\n\n        if hash_:\n            url = \"{}#{}\".format(url, hash_)\n        with self.url(url):\n            try:\n                yield\n            finally:\n                self.screenshot()\n\n    def screenshot(self):\n        if self._screenshot_file:\n            self.driver.save_screenshot(str(self._screenshot_file))\n        self._screenshot_file = None\n\n    def analyze_logs(self):\n        errors = []\n        exceptions = [\n            r\"[^ ]+ \\d+\"\n            r\" Synchronous XMLHttpRequest on the main thread is deprecated\"\n            r\" because of its detrimental effects to the end user's experience.\"\n            r\" For more help, check https://xhr.spec.whatwg.org/.\",\n            r\"[^ ]+ (\\d+|-) {}\".format(\n                re.escape(\n                    \"Failed to load resource:\"\n                    \" the server responded with a status of 404 (File not found)\"\n                )\n            ),\n        ]\n        self.get_logs()\n        for log in self.logs:\n            if log[\"level\"] != \"INFO\":\n                for exception in exceptions + self.allowed_errors:\n                    if re.match(exception, log[\"message\"]):\n                        break\n                else:\n                    if log[\"source\"] not in [\"deprecation\"]:\n                        errors.append(log)\n        if errors:\n            raise ErrorLogException(errors)\n\n    def element_has_text(self, id_, text, timeout=DEFAULT_TIMEOUT):\n        return WebDriverWait(self.driver, timeout).until(\n            ec.text_to_be_present_in_element((By.ID, id_), text)\n        )\n\n    def element_with_tag_name_has_text(self, tag_name, text, timeout=DEFAULT_TIMEOUT):\n        return WebDriverWait(self.driver, timeout).until(\n            ec.text_to_be_present_in_element((By.TAG_NAME, tag_name), text)\n        )\n\n    def element_present(self, id_, timeout=DEFAULT_TIMEOUT):\n        return WebDriverWait(self.driver, timeout).until(\n            ec.presence_of_element_located((By.ID, id_))\n        )\n\n    def element_with_tag_name_present(self, tag, timeout=DEFAULT_TIMEOUT):\n        return WebDriverWait(self.driver, timeout).until(\n            ec.presence_of_element_located((By.TAG_NAME, tag))\n        )\n\n    def element_not_present(self, id_, timeout=DEFAULT_TIMEOUT):\n        def check(driver_):\n            try:\n                driver_.find_element(by=By.ID, value=id_)\n            except NoSuchElementException:\n                return True\n            return False\n\n        return WebDriverWait(self.driver, timeout).until(check)\n\n    def element_attribute_has_value(\n        self, id_, attribute, value, timeout=DEFAULT_TIMEOUT\n    ):\n        def check(driver_):\n            element = driver_.find_element(by=By.ID, value=id_)\n            if element.get_attribute(attribute) == value:\n                return element\n            else:\n                return False\n\n        return WebDriverWait(self.driver, timeout).until(check)\n"
  },
  {
    "path": "tests/selenium/pytest.ini",
    "content": "[pytest]\naddopts =\n    -s\n    # allow debug console display values\n    --tb=short\n    # do not display standard traceback display of python but a more\n    # compact one\n"
  },
  {
    "path": "tests/selenium/test_api.py",
    "content": "from vue import *\n\n\ndef test_app_with_props_and_data(selenium):\n    def app_with_props_data(el):\n        class App(VueComponent):\n            text: str\n            template = \"\"\"\n            <div id=\"el\">{{ text }}</div>\n            \"\"\"\n\n        return App(el, props_data={\"text\": \"TEXT\"})\n\n    with selenium.app(app_with_props_data):\n        assert selenium.element_has_text(\"el\", \"TEXT\")\n\n\ndef test_emit_method(selenium):\n    def call_emit(el):\n        class Emitter(VueComponent):\n            template = \"<p></p>\"\n\n            def created(self):\n                self.emit(\"creation\", \"YES\")\n\n        Emitter.register()\n\n        class App(VueComponent):\n            text = \"NO\"\n            template = \"\"\"\n            <div>\n                <emitter @creation=\"change\"></emitter>\n                <div id='el'>{{ text }}</div>\n            </div>\n            \"\"\"\n\n            def change(self, ev=None):\n                self.text = ev\n\n        return App(el)\n\n    with selenium.app(call_emit):\n        assert selenium.element_has_text(\"el\", \"YES\")\n\n\ndef test_extend(selenium):\n    def extended_component(el):\n        class Base(VueComponent):\n            template = \"<div id='comps'>{{ components_string }}</div>\"\n            comps = []\n\n            def created(self):\n                self.comps.append(\"BASE\")\n\n            @computed\n            def components_string(self):\n                return \" \".join(self.comps)\n\n        class Sub(Base):\n            extends = True\n\n            def created(self):\n                self.comps.append(\"SUB\")\n\n            @computed\n            def components_string(self):\n                comps = super().components_string()\n                return f\"SUB({comps})\"\n\n        return Sub(el)\n\n    with selenium.app(extended_component):\n        assert selenium.element_has_text(\"comps\", \"SUB(BASE SUB)\")\n\n\ndef test_extend_from_dict(selenium):\n    class Component(VueComponent):\n        template = \"<div id='done'>{{ done }}</div>\"\n        done = \"NO\"\n        extends = {\"created\": lambda: print(\"CREATED BASE\")}\n\n        def created(self):\n            print(\"CREATED SUB\")\n            self.done = \"YES\"\n\n    with selenium.app(Component):\n        assert selenium.element_has_text(\"done\", \"YES\")\n    assert \"CREATED BASE\" in selenium.logs[-2][\"message\"]\n    assert \"CREATED SUB\" in selenium.logs[-1][\"message\"]\n"
  },
  {
    "path": "tests/selenium/test_examples.py",
    "content": "import time\n\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.common.keys import Keys\n\n\ndef test_markdown_editor(selenium):\n    with selenium.example():\n        time.sleep(0.5)\n        element = selenium.element_present(\"markdown\")\n        element.clear()\n        element.send_keys(\"# Title\\n\\n\")\n        element.send_keys(\"* item one\\n\")\n        element.send_keys(\"* item two\\n\")\n        element.send_keys(\"\\n\")\n        element.send_keys(\"_italic_\\n\")\n        element.send_keys(\"\\n\")\n        element.send_keys(\"`some code`\\n\")\n        element.send_keys(\"\\n\")\n        element.send_keys(\"**bold**\\n\")\n        element.send_keys(\"\\n\")\n        element.send_keys(\"## Sub Title\\n\")\n        element.send_keys(\"\\n\")\n        selenium.element_has_text(\"sub-title\", \"Sub Title\")\n\n\ndef test_grid_component(selenium):\n    with selenium.example():\n        time.sleep(0.5)\n        query = selenium.element_present(\"query\")\n        query.clear()\n        query.send_keys(\"j\")\n        power = selenium.element_present(\"power\")\n        power.click()\n        rows = selenium.driver.find_elements(by=By.TAG_NAME, value=\"td\")\n        assert \"Jet Li\" == rows[0].text\n        assert \"8000\" == rows[1].text\n        assert \"Jackie Chan\" == rows[2].text\n        assert \"7000\" == rows[3].text\n\n\ndef test_tree_view(selenium):\n    with selenium.example():\n        time.sleep(0.5)\n        lis = selenium.find_elements(by=By.TAG_NAME, value=\"li\")\n        lis[0].click()\n        lis[3].click()\n        ActionChains(selenium.driver).double_click(lis[8]).perform()\n        assert selenium.find_elements(by=By.TAG_NAME, value=\"li\")[9].text == \"new stuff\"\n\n\ndef test_svg_graph(selenium):\n    with selenium.example():\n        time.sleep(0.5)\n        selenium.find_elements(by=By.TAG_NAME, value=\"button\")[5].click()\n\n        a = selenium.find_elements(by=By.TAG_NAME, value=\"input\")[0]\n        d = selenium.find_elements(by=By.TAG_NAME, value=\"input\")[3]\n        ActionChains(selenium.driver).click_and_hold(a).move_by_offset(\n            20, 0\n        ).release().perform()\n\n        ActionChains(selenium.driver).click_and_hold(d).move_by_offset(\n            5, 0\n        ).release().perform()\n\n        polygon = selenium.find_elements(by=By.TAG_NAME, value=\"polygon\")[0]\n        assert 5 == len(polygon.get_attribute(\"points\").split(\" \"))\n\n\ndef test_modal_component(selenium):\n    with selenium.example():\n        time.sleep(1)\n        show_button = selenium.element_present(\"show-modal\")\n        show_button.click()\n        time.sleep(1)\n        assert selenium.element_present(\"modal_view\", timeout=2)\n\n\ndef test_todo_mvc(selenium):\n    with selenium.example():\n        time.sleep(0.5)\n        title_input = selenium.element_present(\"title-input\")\n        title_input.clear()\n        title_input.send_keys(\"new todo\")\n        title_input.send_keys(Keys.ENTER)\n        title_input.send_keys(\"completed\")\n        title_input.send_keys(Keys.ENTER)\n\n        toggle_buttons = selenium.driver.find_elements(by=By.CLASS_NAME, value=\"toggle\")\n        assert 2 == len(toggle_buttons)\n\n        toggle_buttons[1].click()\n        selenium.element_present(\"show-active\").click()\n        labels = selenium.driver.find_elements(by=By.TAG_NAME, value=\"label\")\n        assert 1 == len(labels)\n        assert \"new todo\" == labels[0].text\n\n        selenium.element_present(\"show-all\").click()\n        labels = selenium.driver.find_elements(by=By.TAG_NAME, value=\"label\")\n        assert 2 == len(labels)\n        assert \"new todo\" == labels[0].text\n        assert \"completed\" == labels[1].text\n\n\ndef test_github_commits(selenium):\n    with selenium.example(hash_=\"testing\"):\n        assert selenium.element_with_tag_name_present(\"ul\")\n        time.sleep(2)\n        assert 10 == len(selenium.driver.find_elements(by=By.TAG_NAME, value=\"li\"))\n\n\ndef test_elastic_header(selenium):\n    with selenium.example():\n        assert selenium.element_present(\"header\")\n        header = selenium.find_element(by=By.ID, value=\"header\")\n        content = selenium.find_element(by=By.ID, value=\"content\")\n\n        assert (\n            content.get_attribute(\"style\") == \"transform:\"\n            \" translate3d(0px, 0px, 0px);\"\n        )\n\n        ActionChains(selenium.driver).click_and_hold(header).move_by_offset(\n            xoffset=0, yoffset=100\n        ).perform()\n        selenium.screenshot()\n        assert (\n            content.get_attribute(\"style\") == \"transform:\"\n            \" translate3d(0px, 33px, 0px);\"\n        )\n\n        ActionChains(selenium.driver).release().perform()\n        time.sleep(1)\n        assert (\n            content.get_attribute(\"style\") == \"transform:\"\n            \" translate3d(0px, 0px, 0px);\"\n        )\n"
  },
  {
    "path": "tests/selenium/test_guide/test_components/test_custom_events.py",
    "content": "from vue import *\n\nfrom selenium.webdriver.common.by import By\n\n\ndef test_customize_v_model(selenium):\n    def app(el):\n        class CustomVModel(VueComponent):\n            model = Model(prop=\"checked\", event=\"change\")\n            checked: bool\n            template = \"\"\"\n            <div>\n                <p id=\"component\">{{ checked }}</p>\n                <input\n                    id=\"c\"\n                    type=\"checkbox\"\n                    :checked=\"checked\"\n                    @change=\"$emit('change', $event.target.checked)\"\n                >\n            </div>\n            \"\"\"\n\n        CustomVModel.register(\"custom-vmodel\")\n\n        class App(VueComponent):\n            clicked = False\n            template = \"\"\"\n            <div>\n                <p id='instance'>{{ clicked }}</p>\n                <custom-vmodel v-model=\"clicked\"></custom-vmodel>\n            </div>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"instance\", \"false\")\n        assert selenium.element_has_text(\"component\", \"false\")\n        selenium.find_element(by=By.ID, value=\"c\").click()\n        assert selenium.element_has_text(\"component\", \"true\")\n        assert selenium.element_has_text(\"instance\", \"true\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_components/test_props.py",
    "content": "import pytest\n\nfrom vue import *\n\n\ndef test_prop_types(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: int\n            content = \"\"\n            template = \"<div>{{ content }}</div>\"\n\n            def created(self):\n                assert isinstance(self.prop, int)\n                self.content = \"text\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\" :prop=\"100\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"component\", \"text\")\n\n\ndef test_prop_default(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: int = 100\n            content = \"\"\n            template = \"<div>{{ content }}</div>\"\n\n            def created(self):\n                assert 100 == self.prop\n                self.content = \"text\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"component\", \"text\")\n\n\ndef test_prop_required(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: int\n            content = \"\"\n            template = \"<div>{{ content }}</div>\"\n\n            def created(self):\n                self.content = \"text\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\">SUB</sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with pytest.raises(Exception) as excinfo:\n        with selenium.app(app):\n            selenium.element_has_text(\"component\", \"text\")\n    assert \"[Vue warn]: Missing required prop:\" in excinfo.value.errors[0][\"message\"]\n\n\ndef test_prop_as_initial_value(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: str\n\n            @data\n            def cnt(self):\n                return self.prop\n\n            template = \"<div>{{ cnt }}</div>\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\" prop=\"text\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"component\", \"text\")\n\n\ndef test_dont_allow_write_prop(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: str\n\n            def created(self):\n                self.prop = \"HALLO\"\n\n            template = \"<div>{{ prop }}</div>\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\" prop=\"text\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with pytest.raises(Exception):\n        with selenium.app(app):\n            with pytest.raises(TimeoutError):\n                selenium.element_has_text(\"component\", \"HALLO\")\n\n\ndef test_prop_validator(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: str\n\n            @validator(\"prop\")\n            def is_text(self, value):\n                return \"text\" == value\n\n            template = \"<div>{{ prop }}</div>\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\" prop=\"not text\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with pytest.raises(Exception) as excinfo:\n        with selenium.app(app):\n            assert selenium.element_has_text(\"component\", \"not text\")\n    assert (\n        \"[Vue warn]: Invalid prop: custom validator check failed for prop\"\n        in excinfo.value.errors[0][\"message\"]\n    )\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_components_basics.py",
    "content": "from vue import *\n\nfrom selenium.webdriver.common.by import By\n\n\ndef test_data_must_be_function(selenium):\n    def app(el):\n        class ClickCounter(VueComponent):\n            count = 0\n            template = \"\"\"\n            <button v-on:click=\"count++\">{{ count }}</button>\n            \"\"\"\n\n        ClickCounter.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <div id=\"components-demo\">\n              <click-counter id=\"btn0\"></click-counter>\n              <click-counter id=\"btn1\"></click-counter>\n            </div>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"btn0\", \"0\")\n        assert selenium.element_has_text(\"btn1\", \"0\")\n        selenium.find_element(by=By.ID, value=\"btn1\").click()\n        assert selenium.element_has_text(\"btn0\", \"0\")\n        assert selenium.element_has_text(\"btn1\", \"1\")\n\n\ndef test_register_with_name(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            template = \"\"\"\n            <div>TEXT</div>\n            \"\"\"\n\n        SubComponent.register(\"another-name\")\n\n        class App(VueComponent):\n            template = \"\"\"\n            <another-name id=\"component\"></another-name>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"component\", \"TEXT\")\n\n\ndef test_passing_data_with_props(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            prop: str\n            template = \"\"\"\n            <div>{{ prop }}</div>\n            \"\"\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component id=\"component\" prop=\"message\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"component\", \"message\")\n\n\ndef test_emit_event(selenium):\n    def app(el):\n        class SubComponent(VueComponent):\n            template = \"\"\"\n            <button @click=\"$emit('my-event', 'value')\"></button>\n            \"\"\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            text = \"\"\n\n            def handler(self, value):\n                self.text = value\n\n            template = \"\"\"\n            <div>\n                <p id=\"content\">{{ text }}</p>\n                <sub-component id=\"component\" @my-event='handler'></sub-component>\n            </div>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(app):\n        assert selenium.element_present(\"component\")\n        selenium.find_element(by=By.ID, value=\"component\").click()\n        assert selenium.element_has_text(\"content\", \"value\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_computed_properties.py",
    "content": "from vue import *\n\n\ndef test_basics(selenium):\n    class ComputedPropertiesBasics(VueComponent):\n        message = \"message\"\n\n        @computed\n        def reversed_message(self):\n            return self.message[::-1]\n\n        template = \"\"\"\n        <div>\n            <p id=\"original\">{{ message }}</p>\n            <p id=\"reversed\">{{ reversed_message }}</p>\n        </div>\n        \"\"\"\n\n    with selenium.app(ComputedPropertiesBasics):\n        assert selenium.element_has_text(\"reversed\", \"egassem\")\n\n\ndef test_watch(selenium):\n    class Watch(VueComponent):\n        message = \"message\"\n        new_val = \"\"\n\n        @watch(\"message\")\n        def _message(self, new, old):\n            self.new_val = new\n\n        def created(self):\n            self.message = \"changed\"\n\n        template = \"\"\"\n        <div>\n            <p id=\"change\">{{ new_val }}</p>\n        </div>\n        \"\"\"\n\n    with selenium.app(Watch):\n        assert selenium.element_has_text(\"change\", \"changed\")\n\n\ndef test_computed_setter(selenium):\n    class ComputedSetter(VueComponent):\n        message = \"\"\n\n        @computed\n        def reversed_message(self):\n            return self.message[::-1]\n\n        @reversed_message.setter\n        def reversed_message(self, reversed_message):\n            self.message = reversed_message[::-1]\n\n        def created(self):\n            self.reversed_message = \"olleh\"\n\n        template = \"\"\"\n        <div>\n            <p id=\"msg\">{{ message }}</p>\n        </div>\n        \"\"\"\n\n    with selenium.app(ComputedSetter):\n        assert selenium.element_has_text(\"msg\", \"hello\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_event_handler.py",
    "content": "from vue import *\n\nfrom selenium.webdriver.common.by import By\n\n\ndef test_inline_handlers(selenium):\n    class InlineHandler(VueComponent):\n        message = \"\"\n\n        def change(self, to):\n            self.message = to\n\n        template = \"\"\"\n        <button @click=\"change('changed')\" id=\"btn\">{{ message }}</button>\n        \"\"\"\n\n    with selenium.app(InlineHandler):\n        assert selenium.element_has_text(\"btn\", \"\")\n        selenium.find_element(by=By.ID, value=\"btn\").click()\n        assert selenium.element_has_text(\"btn\", \"changed\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_instance.py",
    "content": "from vue import *\n\n\ndef test_lifecycle_hooks(selenium):\n    def lifecycle_hooks(el):\n        class ComponentLifecycleHooks(VueComponent):\n            text: str\n            template = \"<div>{{ text }}</div>\"\n\n            def before_create(self):\n                print(\"lh: before_created\", self)\n\n            def created(self):\n                print(\"lh: created\", self)\n\n            def before_mount(self):\n                print(\"lh: before_mount\", self)\n\n            def mounted(self):\n                print(\"lh: mounted\", self)\n\n            def before_update(self):\n                print(\"lh: before_update\", self)\n\n            def updated(self):\n                print(\"lh: updated\", self)\n\n            def before_destroy(self):\n                print(\"lh: before_destroy\", self)\n\n            def destroyed(self):\n                print(\"lh: destroyed\", self)\n\n        ComponentLifecycleHooks.register(\"clh\")\n\n        class App(VueComponent):\n            show = True\n            text = \"created\"\n\n            def mounted(self):\n                self.text = \"mounted\"\n\n            def updated(self):\n                self.show = False\n\n            template = (\n                \"<clh v-if='show' :text='text'></clh>\"\n                \"<div v-else id='after' :text='text'></div>\"\n            )\n\n        return App(el)\n\n    with selenium.app(lifecycle_hooks):\n        selenium.element_present(\"after\")\n        logs = list(filter(lambda l: \"lh: \" in l[\"message\"], selenium.get_logs()))\n        for idx, log_message in enumerate(\n            [\n                \"lh: before_create\",\n                \"lh: created\",\n                \"lh: before_mount\",\n                \"lh: mounted\",\n                \"lh: before_update\",\n                \"lh: updated\",\n                \"lh: before_destroy\",\n                \"lh: destroyed\",\n            ]\n        ):\n            assert log_message in logs[idx][\"message\"]\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_introduction.py",
    "content": "from vue import *\n\nfrom selenium.webdriver.common.by import By\n\n\ndef test_declarative_rendering(selenium):\n    class DeclarativeRendering(VueComponent):\n        message = \"MESSAGE CONTENT\"\n        template = \"<div id='content'>{{ message }}</div>\"\n\n    with selenium.app(DeclarativeRendering):\n        assert selenium.element_has_text(\"content\", \"MESSAGE CONTENT\")\n\n\ndef test_bind_element_title(selenium):\n    class BindElementTitle(VueComponent):\n        title = \"TITLE\"\n        template = \"<div id='withtitle' v-bind:title='title'></div>\"\n\n    with selenium.app(BindElementTitle):\n        assert selenium.element_attribute_has_value(\"withtitle\", \"title\", \"TITLE\")\n\n\ndef test_if_condition(selenium):\n    class IfCondition(VueComponent):\n        show = False\n        template = (\n            \"<div>\"\n            \"    <div id='notpresent' v-if='show'>DONT SHOW</div>\"\n            \"    <div id='present' />\"\n            \"</div>\"\n        )\n\n    with selenium.app(IfCondition):\n        assert selenium.element_present(\"present\")\n        assert selenium.element_not_present(\"notpresent\")\n\n\ndef test_for_loop(selenium):\n    class ForLoop(VueComponent):\n        items = [\"0\", \"1\", \"2\"]\n        template = (\n            \"<ol id='list'>\"\n            \"   <li v-for='item in items' :id='item'>{{ item }}</li>\"\n            \"</ol>\"\n        )\n\n    with selenium.app(ForLoop):\n        for idx in range(3):\n            assert selenium.element_has_text(str(idx), str(idx))\n\n\ndef test_on_click_method(selenium):\n    class OnClickMethod(VueComponent):\n        message = \"message\"\n        template = \"<button @click='reverse' id='btn'>{{ message }}</button>\"\n\n        def reverse(self, event):\n            self.message = \"\".join(reversed(self.message))\n\n    with selenium.app(OnClickMethod):\n        assert selenium.element_has_text(\"btn\", \"message\")\n        selenium.find_element(by=By.ID, value=\"btn\").click()\n        assert selenium.element_has_text(\"btn\", \"egassem\")\n\n\ndef test_v_model(selenium):\n    class VModel(VueComponent):\n        clicked = False\n        template = (\n            \"<div>\"\n            \"    <p id='p'>{{ clicked }}</p>\"\n            \"    <input type='checkbox' id='c' v-model='clicked'>\"\n            \"</div>\"\n        )\n\n    with selenium.app(VModel):\n        assert selenium.element_has_text(\"p\", \"false\")\n        selenium.find_element(by=By.ID, value=\"c\").click()\n        assert selenium.element_has_text(\"p\", \"true\")\n\n\ndef test_component(selenium):\n    def components(el):\n        class SubComponent(VueComponent):\n            template = \"\"\"\n            <h1 id=\"header\">HEADER</h1>\n            \"\"\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(components):\n        assert selenium.element_has_text(\"header\", \"HEADER\")\n\n\ndef test_component_with_props(selenium):\n    def components_with_properties(el):\n        class SubComponent(VueComponent):\n            text: str\n            sub = \"SUB\"\n            template = \"\"\"\n            <div>\n            <h1 id=\"header\">{{ text }}</h1>\n            <h2 id=\"sub\">{{ sub }}</h2>\n            </div>\n            \"\"\"\n\n        SubComponent.register()\n\n        class App(VueComponent):\n            template = \"\"\"\n            <sub-component text=\"TEXT\"></sub-component>\n            \"\"\"\n\n        return App(el)\n\n    with selenium.app(components_with_properties):\n        assert selenium.element_has_text(\"header\", \"TEXT\")\n        assert selenium.element_has_text(\"sub\", \"SUB\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_essentials/test_list_rendering.py",
    "content": "from vue import *\n\n\ndef test_mutation_methods(selenium):\n    class MutationMethods(VueComponent):\n        array = [1, 2, 3]\n\n        template = \"<div id='done' />\"\n\n        def created(self):\n            print(self.array)  # 1,2,3\n            print(self.array.pop())  # 3\n            print(self.array)  # 1,2\n            self.array.append(4)\n            print(self.array)  # 1,2,4\n            print(self.array.pop(0))  # 1\n            print(self.array)  # 2,4\n            self.array[0:0] = [6, 4]\n            print(self.array)  # 6,4,2,4\n            self.array.insert(2, 8)\n            print(self.array)  # 6,4,8,2,4\n            del self.array[3]\n            print(self.array)  # 6,4,8,4\n            self.array.sort(key=lambda a: 0 - a)\n            print(self.array)  # 8,6,4,4\n            self.array.reverse()\n            print(self.array)  # 4,4,6,8\n\n    with selenium.app(MutationMethods):\n        selenium.element_present(\"done\")\n\n        logs = [\n            l[\"message\"].split(\" \", 2)[-1][:-3][1:] for l in selenium.get_logs()[-11:]\n        ]\n        assert logs == [\n            \"[1, 2, 3]\",\n            \"3\",\n            \"[1, 2]\",\n            \"[1, 2, 4]\",\n            \"1\",\n            \"[2, 4]\",\n            \"[6, 4, 2, 4]\",\n            \"[6, 4, 8, 2, 4]\",\n            \"[6, 4, 8, 4]\",\n            \"[8, 6, 4, 4]\",\n            \"[4, 4, 6, 8]\",\n        ]\n"
  },
  {
    "path": "tests/selenium/test_guide/test_reusability_composition/test_filters.py",
    "content": "from vue import *\n\n\ndef test_local_filter(selenium):\n    class ComponentWithFilter(VueComponent):\n        message = \"Message\"\n\n        @staticmethod\n        @filters\n        def lower_case(value):\n            return value.lower()\n\n        template = \"<div id='content'>{{ message | lower_case }}</div>\"\n\n    with selenium.app(ComponentWithFilter):\n        assert selenium.element_has_text(\"content\", \"message\")\n\n\ndef test_global_filter(selenium):\n    def app(el):\n        Vue.filter(\"lower_case\", lambda v: v.lower())\n\n        class ComponentUsesGlobalFilter(VueComponent):\n            message = \"Message\"\n            template = \"<div id='content'>{{ message | lower_case }}</div>\"\n\n        return ComponentUsesGlobalFilter(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"content\", \"message\")\n"
  },
  {
    "path": "tests/selenium/test_guide/test_reusability_composition/test_mixins.py",
    "content": "from vue import *\n\n\ndef test_local_mixin(selenium):\n    def app(el):\n        class MyMixin(VueMixin):\n            def created(self):\n                print(\"created\")\n\n            @staticmethod\n            @filters\n            def lower_case(value):\n                return value.lower()\n\n        class ComponentUsesGlobalFilter(VueComponent):\n            message = \"Message\"\n            mixins = [MyMixin]\n            template = \"<div id='content'>{{ message | lower_case }}</div>\"\n\n        return ComponentUsesGlobalFilter(el)\n\n    with selenium.app(app):\n        assert selenium.element_has_text(\"content\", \"message\")\n    logs = list(filter(lambda l: \"created\" in l[\"message\"], selenium.logs))\n    assert 1 == len(logs)\n"
  },
  {
    "path": "tests/selenium/test_guide/test_reusability_composition/test_render_function.py",
    "content": "from vue import *\n\nfrom selenium.webdriver.common.by import By\n\n\ndef test_basics(selenium):\n    def app(el):\n        class ComponentWithRenderFunction(VueComponent):\n            level = 3\n\n            def render(self, create_element):\n                return create_element(f\"h{self.level}\", \"Title\")\n\n        return ComponentWithRenderFunction(el)\n\n    with selenium.app(app):\n        assert selenium.element_with_tag_name_has_text(\"h3\", \"Title\")\n\n\ndef test_slots(selenium):\n    def app(el):\n        class WithSlots(VueComponent):\n            def render(self, create_element):\n                return create_element(f\"p\", self.slots.get(\"default\"))\n\n        WithSlots.register()\n\n        class Component(VueComponent):\n            template = \"<with-slots><p></p><p></p></with-slots>\"\n\n        return Component(el)\n\n    with selenium.app(app):\n        div = selenium.element_with_tag_name_present(\"p\")\n        assert len(div.find_elements(by=By.TAG_NAME, value=\"p\")) == 2\n\n\ndef test_empty_slots(selenium):\n    def app(el):\n        class WithSlots(VueComponent):\n            def render(self, create_element):\n                return create_element(f\"div\", self.slots.get(\"default\"))\n\n        WithSlots.register()\n\n        class Component(VueComponent):\n            template = \"<with-slots />\"\n\n        return Component(el)\n\n    with selenium.app(app):\n        pass\n\n\ndef test_props(selenium):\n    def app(el):\n        class ComponentWithProps(VueComponent):\n            prop: str = \"p\"\n            template = \"<div :id='prop'></div>\"\n\n        ComponentWithProps.register()\n\n        class ComponentRendersWithAttrs(VueComponent):\n            def render(self, create_element):\n                return create_element(\"ComponentWithProps\", {\"props\": {\"prop\": \"p\"}})\n\n        return ComponentRendersWithAttrs(el)\n\n    with selenium.app(app):\n        assert selenium.element_present(\"p\")\n"
  },
  {
    "path": "tests/selenium/test_vuerouter.py",
    "content": "from selenium.webdriver.common.by import By\n\nVueRouterConfig = {\"scripts\": {\"vue-router\": True}}\n\n\ndef test_routes(selenium):\n    def app(el):\n        from vue import VueComponent, VueRouter, VueRoute\n\n        class Foo(VueComponent):\n            template = '<div id=\"content\">foo</div>'\n\n        class Bar(VueComponent):\n            text = \"bar\"\n            template = '<div id=\"content\">{{ text }}</div>'\n\n        class Router(VueRouter):\n            routes = [VueRoute(\"/foo\", Foo), VueRoute(\"/bar\", Bar)]\n\n        class ComponentUsingRouter(VueComponent):\n            template = \"\"\"\n                <div>\n                    <p>\n                        <router-link to=\"/foo\" id=\"foo\">Go to Foo</router-link>\n                        <router-link to=\"/bar\" id=\"bar\">Go to Bar</router-link>\n                    </p>\n                    <router-view></router-view>\n                </div>\n            \"\"\"\n\n        return ComponentUsingRouter(el, router=Router())\n\n    with selenium.app(app, config=VueRouterConfig):\n        assert selenium.element_present(\"foo\")\n        selenium.find_element(by=By.ID, value=\"foo\").click()\n        assert selenium.element_has_text(\"content\", \"foo\")\n\n        assert selenium.element_present(\"bar\")\n        selenium.find_element(by=By.ID, value=\"bar\").click()\n        assert selenium.element_has_text(\"content\", \"bar\")\n\n\ndef test_dynamic_route_matching(selenium):\n    def app(el):\n        from vue import VueComponent, VueRouter, VueRoute\n\n        class User(VueComponent):\n            template = '<div id=\"user\">{{ $route.params.id }}</div>'\n\n        class Router(VueRouter):\n            routes = [VueRoute(\"/user/:id\", User)]\n\n        class ComponentUsingRouter(VueComponent):\n            template = \"\"\"\n                <div>\n                    <p>\n                        <router-link to=\"/user/123\" id=\"link\">User</router-link>\n                    </p>\n                    <router-view></router-view>\n                </div>\n            \"\"\"\n\n        return ComponentUsingRouter(el, router=Router())\n\n    with selenium.app(app, config=VueRouterConfig):\n        assert selenium.element_present(\"link\")\n        selenium.find_element(by=By.ID, value=\"link\").click()\n        assert selenium.element_has_text(\"user\", \"123\")\n\n\ndef test_named_routes(selenium):\n    def app(el):\n        from vue import VueComponent, VueRouter, VueRoute\n\n        class FooTop(VueComponent):\n            template = '<div id=\"header\">foo top</div>'\n\n        class FooBottom(VueComponent):\n            template = '<div id=\"body\">foo bottom</div>'\n\n        class BarTop(VueComponent):\n            template = '<div id=\"header\">bar top</div>'\n\n        class BarBottom(VueComponent):\n            template = '<div id=\"body\">bar bottom</div>'\n\n        class Router(VueRouter):\n            routes = [\n                VueRoute(\"/foo\", components={\"default\": FooBottom, \"top\": FooTop}),\n                VueRoute(\"/bar\", components={\"default\": BarBottom, \"top\": BarTop}),\n            ]\n\n        class ComponentUsingRouter(VueComponent):\n            template = \"\"\"\n                <div>\n                    <p>\n                        <router-link to=\"/foo\" id=\"foo\">Go to Foo</router-link>\n                        <router-link to=\"/bar\" id=\"bar\">Go to Bar</router-link>\n                    </p>\n                    <router-view name=\"top\"></router-view>\n                    <hr>\n                    <router-view></router-view>\n                </div>\n            \"\"\"\n\n        return ComponentUsingRouter(el, router=Router())\n\n    with selenium.app(app, config=VueRouterConfig):\n        assert selenium.element_present(\"foo\")\n        selenium.find_element(by=By.ID, value=\"foo\").click()\n        assert selenium.element_has_text(\"header\", \"foo top\")\n        assert selenium.element_has_text(\"body\", \"foo bottom\")\n\n        assert selenium.element_present(\"bar\")\n        selenium.find_element(by=By.ID, value=\"bar\").click()\n        assert selenium.element_has_text(\"header\", \"bar top\")\n        assert selenium.element_has_text(\"body\", \"bar bottom\")\n\n\ndef test_nested_routes_and_redirect(selenium):\n    def app(el):\n        from vue import VueComponent, VueRouter, VueRoute\n\n        class UserHome(VueComponent):\n            template = '<div id=\"home\">Home</div>'\n\n        class UserProfile(VueComponent):\n            template = '<div id=\"profile\">Profile</div>'\n\n        class UserPosts(VueComponent):\n            template = '<div id=\"posts\">Posts</div>'\n\n        class ComponentUsingRouter(VueComponent):\n            template = \"\"\"\n                <div>\n                    <p>\n                        <router-link to=\"/user/foo\" id=\"link-home\">/user/foo</router-link>\n                        <router-link to=\"/user/foo/profile\" id=\"link-profile\">/user/foo/profile</router-link>\n                        <router-link to=\"/user/foo/posts\" id=\"link-posts\">/user/foo/posts</router-link>\n                    </p>\n                    <h2>User {{ $route.params.id }}</h2>\n                    <router-view></router-view>\n                </div>\n            \"\"\"\n\n        class Router(VueRouter):\n            routes = [\n                VueRoute(\"/\", redirect=\"/user/foo\"),\n                VueRoute(\n                    \"/user/:id\",\n                    ComponentUsingRouter,\n                    children=[\n                        VueRoute(\"\", UserHome),\n                        VueRoute(\"profile\", UserProfile),\n                        VueRoute(\"posts\", UserPosts),\n                    ],\n                ),\n            ]\n\n        return ComponentUsingRouter(el, router=Router())\n\n    with selenium.app(app, config=VueRouterConfig):\n        assert selenium.element_present(\"link-home\")\n        selenium.find_element(by=By.ID, value=\"link-home\").click()\n        assert selenium.element_has_text(\"home\", \"Home\")\n\n        assert selenium.element_present(\"link-profile\")\n        selenium.find_element(by=By.ID, value=\"link-profile\").click()\n        assert selenium.element_has_text(\"profile\", \"Profile\")\n\n        assert selenium.element_present(\"link-posts\")\n        selenium.find_element(by=By.ID, value=\"link-posts\").click()\n        assert selenium.element_has_text(\"posts\", \"Posts\")\n"
  },
  {
    "path": "tests/selenium/test_vuex.py",
    "content": "from vue import *\n\nVuexConfig = {\"scripts\": {\"vuex\": True}}\n\n\ndef test_state(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"Message\"\n\n        class ComponentUsingStore(VueComponent):\n            @computed\n            def message(self):\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingStore(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_mutation_noargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self):\n                self.message = \"Message\"\n\n        class ComponentUsingMutation(VueComponent):\n            @computed\n            def message(self):\n                self.store.commit(\"mutate_message\")\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingMutation(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_mutation(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self, new_message):\n                self.message = new_message\n\n        class ComponentUsingMutation(VueComponent):\n            @computed\n            def message(self):\n                self.store.commit(\"mutate_message\", \"Message\")\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingMutation(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_mutation_kwargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self, new_message, postfix=\"\"):\n                self.message = new_message + postfix\n\n        class ComponentUsingMutation(VueComponent):\n            @computed\n            def message(self):\n                self.store.commit(\"mutate_message\", \"Message\", postfix=\"!\")\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingMutation(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message!\")\n\n\ndef test_action(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self, new_message):\n                self.message = new_message\n\n            @action\n            def change_message(self, new_message):\n                self.commit(\"mutate_message\", new_message)\n\n        class ComponentUsingAction(VueComponent):\n            def created(self):\n                self.store.dispatch(\"change_message\", \"Message\")\n\n            @computed\n            def message(self):\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingAction(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_action_noargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self, new_message):\n                self.message = new_message\n\n            @action\n            def change_message(self):\n                self.commit(\"mutate_message\", \"Message\")\n\n        class ComponentUsingAction(VueComponent):\n            def created(self):\n                self.store.dispatch(\"change_message\")\n\n            @computed\n            def message(self):\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingAction(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_action_kwargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"\"\n\n            @mutation\n            def mutate_message(self, new_message):\n                self.message = new_message\n\n            @action\n            def change_message(self, new_message, postfix=\"\"):\n                self.commit(\"mutate_message\", new_message + postfix)\n\n        class ComponentUsingAction(VueComponent):\n            def created(self):\n                self.store.dispatch(\"change_message\", \"Message\", postfix=\"!\")\n\n            @computed\n            def message(self):\n                return self.store.message\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingAction(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message!\")\n\n\ndef test_getter_noargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"Message\"\n\n            @getter\n            def msg(self):\n                return self.message\n\n        class ComponentUsingGetter(VueComponent):\n            @computed\n            def message(self):\n                return self.store.msg\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingGetter(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n\n\ndef test_getter_method(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"Message\"\n\n            @getter\n            def msg(self, prefix):\n                return prefix + self.message\n\n        class ComponentUsingGetter(VueComponent):\n            @computed\n            def message(self):\n                return self.store.msg(\"pre\")\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingGetter(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"preMessage\")\n\n\ndef test_getter_kwargs(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"Message\"\n\n            @getter\n            def msg(self, prefix, postfix):\n                return prefix + self.message + postfix\n\n        class ComponentUsingGetter(VueComponent):\n            @computed\n            def message(self):\n                return self.store.msg(\"pre\", \"!\")\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingGetter(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"preMessage!\")\n\n\ndef test_plugin(selenium):\n    def app(el):\n        class Plugin(VueStorePlugin):\n            def initialize(self, store):\n                store.message = \"Message\"\n\n            def subscribe(self, state, mut, *args, **kwargs):\n                print(state.message, mut, args, kwargs)\n\n        class Store(VueStore):\n            plugins = [Plugin().install]\n            message = \"\"\n\n            @mutation\n            def msg(self, prefix, postfix=\"\"):\n                pass\n\n        class ComponentUsingGetter(VueComponent):\n            @computed\n            def message(self):\n                return self.store.message\n\n            def created(self):\n                self.store.commit(\"msg\", \"Hallo\", postfix=\"!\")\n\n            template = \"<div id='content'>{{ message }}</div>\"\n\n        return ComponentUsingGetter(el, store=Store())\n\n    with selenium.app(app, config=VuexConfig):\n        assert selenium.element_has_text(\"content\", \"Message\")\n        last_log_message = selenium.get_logs()[-1][\"message\"]\n        expected_msg = \"Message msg ('Hallo',) {'postfix': '!'}\"\n        assert expected_msg in last_log_message\n\n\ndef test_using_state_within_native_vue_component(selenium):\n    def app(el):\n        class Store(VueStore):\n            message = \"Message\"\n\n        class ComponentUsingNativeComponent(VueComponent):\n            template = \"<native />\"\n\n        return ComponentUsingNativeComponent(el, store=Store())\n\n    config = {\"scripts\": {\"vuex\": True, \"my\": \"my.js\"}}\n    myjs = \"\"\"\n        Vue.component('native', {\n          template: '<div id=\"content\">{{ message }}</div>',\n          computed: {\n            message () {\n              return this.$store.state.message\n            }\n          }\n        });\n    \"\"\"\n    with selenium.app(app, config=config, files={\"my.js\": myjs}):\n        assert selenium.element_has_text(\"content\", \"Message\")\n"
  },
  {
    "path": "tests/test_install.py",
    "content": "import json\nimport subprocess\nimport urllib.request\nimport time\nfrom contextlib import contextmanager\n\nimport pytest\n\nfrom tools.release import version\n\n\ndef _raise_failed_process(proc, error_msg):\n    stdout = proc.stdout if isinstance(proc.stdout, bytes) else proc.stdout.read()\n    stderr = proc.stderr if isinstance(proc.stderr, bytes) else proc.stderr.read()\n    print(f\"return-code: {proc.returncode}\")\n    print(\"stdout\")\n    print(stdout.decode(\"utf-8\"))\n    print(\"stderr\")\n    print(stderr.decode(\"utf-8\"))\n    raise RuntimeError(error_msg)\n\n\ndef shell(*args, env=None, cwd=None):\n    proc = subprocess.run(args, env=env, cwd=cwd, capture_output=True)\n    if proc.returncode:\n        _raise_failed_process(proc, str(args))\n\n\n@pytest.fixture\ndef wheel(scope=\"session\"):\n    shell(\"make\", \"build\")\n    return f\"dist/vuepy-{version()}-py3-none-any.whl\"\n\n\n@pytest.fixture\ndef venv(tmp_path):\n    path = tmp_path / \"venv\"\n    shell(\"python\", \"-m\", \"venv\", str(path))\n    return path\n\n\n@pytest.fixture\ndef install(wheel, venv):\n    def _install(extra=None):\n        extra = f\"[{extra}]\" if extra else \"\"\n        shell(\n            \"pip\",\n            \"install\",\n            f\"{wheel}{extra}\",\n            env={\"PATH\": str(venv / \"bin\"), \"PIP_USER\": \"no\"},\n        )\n\n    return _install\n\n\n@contextmanager\ndef background_task(*args, env=None, cwd=None):\n    proc = subprocess.Popen(\n        args, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE\n    )\n    try:\n        yield\n    finally:\n        if proc.poll() is not None:\n            _raise_failed_process(proc, \"background task finished early\")\n        proc.kill()\n        proc.communicate()\n\n\ndef request(url, retries=0, retry_delay=0):\n    for retry in range(1 + retries):\n        try:\n            with urllib.request.urlopen(url) as response:\n                return response\n        except urllib.request.URLError:\n            if retry >= retries:\n                raise\n        time.sleep(retry_delay)\n\n\n@pytest.fixture\ndef app(tmp_path):\n    app_path = tmp_path / \"app\"\n    app_path.mkdir()\n    return app_path\n\n\n@pytest.fixture\ndef config(app):\n    def _config(values):\n        app.joinpath(\"vuepy.yml\").write_text(json.dumps(values, indent=2))\n\n    return _config\n\n\ndef test_static(install, venv, tmp_path, app):\n    destination = tmp_path / \"destination\"\n    install()\n    shell(\n        \"vue-cli\",\n        \"deploy\",\n        \"static\",\n        str(destination),\n        env={\"PATH\": f\"{venv / 'bin'}\"},\n        cwd=str(app),\n    )\n    assert (destination / \"index.html\").is_file()\n\n\ndef test_flask(install, venv, app):\n    install(extra=\"flask\")\n    with background_task(\n        \"vue-cli\", \"deploy\", \"flask\", env={\"PATH\": f\"{venv / 'bin'}\"}, cwd=str(app)\n    ):\n        assert (\n            request(\"http://localhost:5000\", retries=5, retry_delay=0.5).status == 200\n        )\n\n\ndef test_flask_settings(install, config, venv, app):\n    install(extra=\"flask\")\n    config({\"provider\": {\"flask\": {\"PORT\": 5001}}})\n    with background_task(\n        \"vue-cli\", \"deploy\", \"flask\", env={\"PATH\": f\"{venv / 'bin'}\"}, cwd=str(app)\n    ):\n        assert (\n            request(\"http://localhost:5001\", retries=5, retry_delay=0.5).status == 200\n        )\n"
  },
  {
    "path": "tests/unit/test_bridge/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/test_bridge/mocks.py",
    "content": "class VueMock:\n    @staticmethod\n    def set(obj, key, value):\n        setattr(obj, key, value)\n\n    @staticmethod\n    def delete(obj, key):\n        delattr(obj, key)\n\n\nclass ObjectMock:\n    def __new__(cls, arg):\n        return arg\n\n    @staticmethod\n    def assign(target, *sources):\n        for source in sources:\n            target.attributes.update(source)\n        return target\n\n    @staticmethod\n    def keys(obj):\n        return [k for k in obj]\n\n\nclass ArrayMock:\n    def __init__(self, *items):\n        self._data = list(items)\n\n    def __getitem__(self, item):\n        return self._data[item]\n\n    def __setitem__(self, key, value):\n        self._data[key] = value\n\n    @property\n    def length(self):\n        return len(self._data)\n\n    def push(self, *items):\n        self._data.extend(items)\n\n    def splice(self, index, delete_count=None, *items):\n        delete_count = (\n            delete_count if delete_count is not None else len(self._data) - index\n        )\n        index = index if index >= 0 else len(self._data) + index\n        deleted = self._data[index : index + delete_count]\n        self._data = (\n            self._data[0:index] + list(items) + self._data[index + delete_count :]\n        )\n        return deleted\n\n    def slice(self, index, stop=None):\n        return self._data[index:stop]\n\n    def indexOf(self, obj, start=0):\n        try:\n            return self._data.index(obj, start)\n        except ValueError:\n            return -1\n\n    def reverse(self):\n        self._data.reverse()\n        return self._data\n"
  },
  {
    "path": "tests/unit/test_bridge/test_dict.py",
    "content": "from unittest import mock\n\nimport pytest\n\nfrom tests.unit.test_bridge.mocks import ObjectMock, VueMock\n\nfrom vue.bridge.dict import window, Dict\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef window_object():\n    with mock.patch.object(window, \"Object\", new=ObjectMock), mock.patch.object(\n        window, \"Vue\", new=VueMock\n    ):\n        yield\n\n\nclass JsObjectMock:\n    def __init__(self, attribtes):\n        self.attributes = attribtes\n\n    def __getattr__(self, item):\n        return self.attributes[item]\n\n    def __setattr__(self, key, value):\n        if key == \"attributes\":\n            super().__setattr__(key, value)\n        else:\n            self.attributes[key] = value\n\n    def __delattr__(self, item):\n        del self.attributes[item]\n\n    def __iter__(self):\n        return iter(self.attributes)\n\n\ndef make_dict(dct):\n    return Dict(JsObjectMock(dct))\n\n\nclass TestDict:\n    def test_getitem(self):\n        assert \"value\" == make_dict({\"key\": \"value\"})[\"key\"]\n\n    def test_items(self):\n        assert ((\"a\", 1), (\"b\", 2)) == make_dict({\"a\": 1, \"b\": 2}).items()\n\n    def test_eq(self):\n        assert {\"a\": 1} != make_dict({\"a\": 2})\n        assert {\"a\": 0, \"b\": 1} == make_dict({\"a\": 0, \"b\": 1})\n\n    def test_keys(self):\n        assert (\"a\", \"b\") == make_dict({\"a\": 0, \"b\": 1}).keys()\n\n    def test_iter(self):\n        assert [\"a\", \"b\"] == list(iter(make_dict({\"a\": 0, \"b\": 1})))\n\n    def test_setitem(self):\n        d = make_dict({})\n        d[\"a\"] = 1\n        assert 1 == d[\"a\"]\n\n    def test_contains(self):\n        assert \"a\" in make_dict({\"a\": 0, \"b\": 1})\n\n    def test_setdefault(self):\n        d = make_dict({})\n        assert 1 == d.setdefault(\"a\", 1)\n        assert 1 == d.setdefault(\"a\", 2)\n\n    def test_len(self):\n        assert 2 == len(make_dict({\"a\": 0, \"b\": 1}))\n\n    def test_get(self):\n        assert 1 == make_dict({\"a\": 0, \"b\": 1}).get(\"b\", \"default\")\n        assert \"default\" == make_dict({\"a\": 0, \"b\": 1}).get(\"c\", \"default\")\n\n    def test_values(self):\n        assert (0, 1) == make_dict({\"a\": 0, \"b\": 1}).values()\n\n    def test_repr(self):\n        assert str({\"a\": 0, \"b\": 1}) == str(make_dict({\"a\": 0, \"b\": 1}))\n\n    def test_update(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        d.update(a=2)\n        assert {\"a\": 2, \"b\": 1} == d\n        d.update(c=0)\n        assert {\"a\": 2, \"b\": 1, \"c\": 0} == d\n        d.update({\"c\": 3, \"d\": 0})\n        assert {\"a\": 2, \"b\": 1, \"c\": 3, \"d\": 0} == d\n\n    def test_bool(self):\n        assert not make_dict({})\n        assert make_dict({\"a\": 0})\n\n    def test_delitem(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        del d[\"a\"]\n        assert {\"b\": 1} == d\n\n    def test_pop(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        assert 1 == d.pop(\"b\")\n        assert {\"a\": 0} == d\n\n    def test_pop_default(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        assert \"default\" == d.pop(\"c\", \"default\")\n        assert {\"a\": 0, \"b\": 1} == d\n\n    def test_pop_key_error(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        with pytest.raises(KeyError):\n            d.pop(\"c\")\n\n    def test_popitem(self):\n        d = make_dict({\"a\": 2})\n        assert (\"a\", 2) == d.popitem()\n        assert {} == d\n\n    def test_clear(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        d.clear()\n        assert not d\n\n    def test_set(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        old_id = id(d)\n        d.__set__({\"c\": 1, \"d\": 2})\n        assert old_id == id(d)\n        assert {\"c\": 1, \"d\": 2} == d\n\n    def test_getattr(self):\n        d = make_dict({\"a\": 0, \"b\": 1})\n        assert 1 == d.b\n\n    def test_setattr(self):\n        d = make_dict({\"a\": 1})\n        d.a = 2\n        assert {\"a\": 2} == d\n\n    def test_str_toString(self):\n        d = make_dict({})\n        d.toString = lambda: \"STRING\"\n        assert \"STRING\" == str(d)\n"
  },
  {
    "path": "tests/unit/test_bridge/test_jsobject.py",
    "content": "from unittest import mock\n\nfrom browser import window\n\nfrom vue.bridge import Object\nfrom vue.bridge.vue_instance import VueInstance\n\nfrom .mocks import ArrayMock\n\n\nclass TestJSObjectWrapper:\n    def test_vue(self):\n        class This:\n            def _isVue(self):\n                return True\n\n        assert isinstance(Object.from_js(This()), VueInstance)\n\n    def test_array(self):\n        with mock.patch.object(window.Array, \"isArray\", return_value=True):\n            obj = Object.from_js(ArrayMock(1, 2, 3))\n        assert [1, 2, 3] == obj\n\n    def test_dict(self):\n        assert {\"a\": 1, \"b\": 2} == Object.from_js({\"a\": 1, \"b\": 2})\n"
  },
  {
    "path": "tests/unit/test_bridge/test_list.py",
    "content": "import pytest\n\nfrom vue.bridge.list import List\nfrom .mocks import ArrayMock\n\n\nclass TestList:\n    def test_len(self):\n        assert 3 == len(List(ArrayMock(1, 2, 3)))\n\n    def test_getitem(self):\n        assert 3 == List(ArrayMock(1, 2, 3))[2]\n        assert [2, 3] == List(ArrayMock(1, 2, 3, 4))[1:3]\n        assert 3 == List(ArrayMock(1, 2, 3))[-1]\n        assert [2] == List(ArrayMock(1, 2, 3))[-2:-1]\n\n    def test_delitem(self):\n        l = List(ArrayMock(1, 2, 3))\n        del l[1]\n        assert [1, 3] == l\n\n    def test_delitem_range(self):\n        l = List(ArrayMock(1, 2, 3, 4))\n        del l[1:3]\n        assert [1, 4] == l\n\n    def test_setitem(self):\n        l = List(ArrayMock(1, 2, 3))\n        l[1] = 5\n        assert [1, 5, 3] == l\n\n    def test_setitem_range(self):\n        l = List(ArrayMock(1, 2, 3))\n        l[:] = [5]\n        assert [5] == l\n\n    def test_setitem_negative(self):\n        l = List(ArrayMock(1, 2, 3, 4))\n        l[-3:-1] = [8, 9]\n        assert [1, 8, 9, 4] == l\n\n    def test_iter(self):\n        assert [1, 2, 3] == [i for i in List(ArrayMock(1, 2, 3))]\n\n    def test_eq(self):\n        assert [1, 2, 3] == List(ArrayMock(1, 2, 3))\n\n    def test_mul(self):\n        assert [1, 2, 1, 2, 1, 2] == List(ArrayMock(1, 2)) * 3\n\n    def test_index(self):\n        assert 3 == List(ArrayMock(1, 2, 3, 4)).index(4)\n\n    def test_index_start(self):\n        assert 4 == List(ArrayMock(4, 1, 2, 3, 4)).index(4, start=1)\n\n    def test_index_not_in_list(self):\n        with pytest.raises(ValueError):\n            List(ArrayMock(1, 2, 3)).index(4)\n\n    def test_extend(self):\n        l = List(ArrayMock(1, 2))\n        l.extend([3, 4])\n        assert [1, 2, 3, 4] == l\n\n    def test_contains(self):\n        assert 3 in List(ArrayMock(1, 2, 3))\n\n    def test_count(self):\n        assert 2 == List(ArrayMock(1, 2, 1)).count(1)\n\n    def test_repr(self):\n        assert \"[1, 2, 3]\" == repr(List(ArrayMock(1, 2, 3)))\n\n    def test_str(self):\n        assert \"[1, 2, 3]\" == str(List(ArrayMock(1, 2, 3)))\n\n    def test_append(self):\n        l = List(ArrayMock(1, 2))\n        l.append(3)\n        assert [1, 2, 3] == l\n\n    def test_insert(self):\n        l = List(ArrayMock(1, 3))\n        l.insert(1, 2)\n        assert [1, 2, 3] == l\n\n    def test_remove(self):\n        l = List(ArrayMock(1, 2, 1, 3))\n        l.remove(1)\n        assert [2, 3] == l\n\n    def test_pop(self):\n        l = List(ArrayMock(1, 2, 3))\n        assert 3 == l.pop()\n\n    def test_sort(self):\n        l = List(ArrayMock(4, 3, 6, 1))\n        l.sort()\n        assert [1, 3, 4, 6] == l\n\n    def test_reverse(self):\n        l = List(ArrayMock(4, 3, 6, 1))\n        l.reverse()\n        assert [1, 6, 3, 4] == l\n\n    def test_set(self):\n        l = List(ArrayMock(4, 3, 6, 1))\n        l.__set__([1, 2, 3, 4])\n        assert [1, 2, 3, 4] == l\n"
  },
  {
    "path": "tests/unit/test_bridge/test_vue.py",
    "content": "from unittest import mock\nimport pytest\nfrom .mocks import ArrayMock\nfrom vue.bridge.vue_instance import VueInstance\nfrom browser import window\n\n\nclass TestVue:\n    def test_getattr(self):\n        class This:\n            def __init__(self):\n                self.attribute = \"value\"\n\n        this = This()\n        vue = VueInstance(this)\n        assert \"value\" == vue.attribute\n        this.attribute = \"new_value\"\n        assert \"new_value\" == vue.attribute\n\n    def test_get_dollar_attribute(self):\n        class This:\n            def __getattr__(self, item):\n                if item == \"$dollar\":\n                    return \"DOLLAR\"\n                return super().__getattribute__(item)\n\n        vue = VueInstance(This())\n        assert \"DOLLAR\" == vue.dollar\n        with pytest.raises(AttributeError):\n            assert not vue.no_dollar\n\n    def test_setattr(self):\n        class This:\n            def __init__(self):\n                self.attribute = False\n\n            def __getattr__(self, item):\n                if item == \"$props\":\n                    return ()\n                return self.__getattribute__(item)\n\n        this = This()\n        vue = VueInstance(this)\n        vue.attribute = True\n        assert vue.attribute\n        assert this.attribute\n\n    def test_set_attribute_with_set(self):\n        class This:\n            def __init__(self):\n                self.list = ArrayMock([1, 2, 3, 4])\n\n            def __getattr__(self, item):\n                if item == \"$props\":\n                    return ()\n                return self.__getattribute__(item)\n\n        this = This()\n        vue = VueInstance(this)\n        list_id = id(this.list)\n        with mock.patch.object(window.Array, \"isArray\", return_value=True):\n            vue.list = [0, 1, 2]\n        assert [0, 1, 2] == vue.list._data\n        assert list_id == id(this.list)\n"
  },
  {
    "path": "tests/unit/test_bridge/test_vuex.py",
    "content": "from vue.bridge.vuex_instance import VuexInstance\n\n\nclass Getter:\n    def __init__(self, **kwargs):\n        self.vars = kwargs\n\n    def __getattr__(self, item):\n        return self.vars[item]\n\n\nclass Callable:\n    def __init__(self):\n        self.args = None\n\n    def __call__(self, *args):\n        self.args = args\n\n\ndef test_get_state():\n    vuex = VuexInstance(state=dict(i=1))\n    assert 1 == vuex.i\n\n\ndef test_get_root_state():\n    vuex = VuexInstance(root_state=dict(i=1))\n    assert 1 == vuex.i\n\n\ndef test_set_state():\n    state = dict(i=0)\n    vuex = VuexInstance(state=state)\n    vuex.i = 1\n    assert 1 == state[\"i\"]\n\n\ndef test_set_root_state():\n    state = dict(i=0)\n    vuex = VuexInstance(root_state=state)\n    vuex.i = 1\n    assert 1 == state[\"i\"]\n\n\ndef test_access_getter():\n    vuex = VuexInstance(getters=Getter(i=1))\n    assert 1 == vuex.i\n\n\ndef test_access_root_getter():\n    vuex = VuexInstance(root_getters=Getter(i=1))\n    assert 1 == vuex.i\n\n\ndef test_access_comit():\n    c = Callable()\n    vuex = VuexInstance(commit=c)\n    vuex.commit(\"mutation\", 1, a=0)\n    assert \"mutation\" == c.args[0]\n    assert {\"args\": (1,), \"kwargs\": {\"a\": 0}} == c.args[1]\n\n\ndef test_access_dispatch():\n    c = Callable()\n    vuex = VuexInstance(dispatch=c)\n    vuex.dispatch(\"action\", 1, a=0)\n    assert \"action\" == c.args[0]\n    assert {\"args\": (1,), \"kwargs\": {\"a\": 0}} == c.args[1]\n"
  },
  {
    "path": "tests/unit/test_transformers/conftest.py",
    "content": "from unittest import mock\nfrom vue.bridge.list import window as list_window\nimport pytest\n\n\nclass ArrayMock(list):\n    def __new__(cls, *args):\n        return list(args)\n\n    @staticmethod\n    def isArray(obj):\n        return isinstance(obj, list)\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef window_object():\n    with mock.patch.object(list_window, \"Array\", new=ArrayMock):\n        yield\n"
  },
  {
    "path": "tests/unit/test_transformers/test_component.py",
    "content": "from unittest import mock\n\nfrom vue import *\n\n\ndef test_empty():\n    class Empty(VueComponent):\n        pass\n\n    assert {} == Empty.init_dict()\n\n\ndef test_method():\n    class Component(VueComponent):\n        def do(self, event):\n            return self, event\n\n    vue_dict = Component.init_dict()\n    assert \"do\" in vue_dict[\"methods\"]\n    method = vue_dict[\"methods\"][\"do\"]\n    with mock.patch(\"vue.decorators.base.javascript.this\", return_value=\"THIS\"):\n        assert \"SELF\", \"EVENT\" == method(\"EVENT\")\n\n\ndef test_method_as_coroutine():\n    class Component(VueComponent):\n        async def co(self):\n            return self\n\n    assert \"co\" in Component.init_dict()[\"methods\"]\n\n\ndef test_data():\n    class Component(VueComponent):\n        attribute = 1\n\n    assert {\"attribute\": 1} == Component.init_dict()[\"data\"](\"THIS\")\n\n\ndef test_data_as_property():\n    class Component(VueComponent):\n        @data\n        def attribute(self):\n            return self\n\n    assert {\"attribute\": \"THIS\"} == Component.init_dict()[\"data\"](\"THIS\")\n\n\ndef test_props():\n    class Component(VueComponent):\n        prop: int\n\n    init_dict = Component.init_dict()\n    assert {\"prop\": {\"type\": int, \"required\": True}} == init_dict[\"props\"]\n\n\ndef test_props_with_default():\n    class Component(VueComponent):\n        prop: int = 100\n\n    init_dict = Component.init_dict()\n    props = {\"prop\": {\"type\": int, \"default\": 100}}\n    assert props == init_dict[\"props\"]\n\n\ndef test_props_validator():\n    class Component(VueComponent):\n        prop: int\n\n        @validator(\"prop\")\n        def is_lt_100(self, value):\n            return value < 100\n\n    init_dict = Component.init_dict()\n    assert not init_dict[\"props\"][\"prop\"][\"validator\"](100)\n    assert init_dict[\"props\"][\"prop\"][\"validator\"](99)\n\n\ndef test_template():\n    class Component(VueComponent):\n        template = \"TEMPLATE\"\n\n    init_dict = Component.init_dict()\n    assert \"TEMPLATE\" == init_dict[\"template\"]\n\n\ndef test_lifecycle_hooks():\n    class Component(VueComponent):\n        def before_create(self):\n            return self\n\n        def created(self):\n            return self\n\n        def before_mount(self):\n            return self\n\n        def mounted(self):\n            return self\n\n        def before_update(self):\n            return self\n\n        def updated(self):\n            return self\n\n        def before_destroy(self):\n            return self\n\n        def destroyed(self):\n            return self\n\n    init_dict = Component.init_dict()\n    assert \"beforeCreate\" in init_dict\n    assert \"created\" in init_dict\n    assert \"beforeMount\" in init_dict\n    assert \"mounted\" in init_dict\n    assert \"beforeUpdate\" in init_dict\n    assert \"updated\" in init_dict\n    assert \"beforeDestroy\" in init_dict\n    assert \"destroyed\" in init_dict\n\n\ndef test_customize_model():\n    class Component(VueComponent):\n        model = Model(prop=\"prop\", event=\"event\")\n\n    init_dict = Component.init_dict()\n    assert {\"prop\": \"prop\", \"event\": \"event\"} == init_dict[\"model\"]\n\n\ndef test_filter():\n    class Component(VueComponent):\n        @staticmethod\n        @filters\n        def lower_case(value):\n            return value.lower()\n\n    init_dict = Component.init_dict()\n    assert \"abc\" == init_dict[\"filters\"][\"lower_case\"](\"Abc\")\n\n\ndef test_watch():\n    class Component(VueComponent):\n        @watch(\"data\")\n        def lower_case(self, new, old):\n            return old, new\n\n    init_dict = Component.init_dict()\n    result = init_dict[\"watch\"][\"data\"][\"handler\"](\"new\", \"old\")\n    assert \"new\", \"old\" == result\n\n\ndef test_watch_deep():\n    class Component(VueComponent):\n        @watch(\"data\", deep=True)\n        def lower_case(self, new, old):\n            return new, old\n\n    init_dict = Component.init_dict()\n    assert init_dict[\"watch\"][\"data\"][\"deep\"]\n\n\ndef test_watch_immediate():\n    class Component(VueComponent):\n        @watch(\"data\", immediate=True)\n        def lower_case(self, new, old):\n            return new, old\n\n    init_dict = Component.init_dict()\n    assert init_dict[\"watch\"][\"data\"][\"immediate\"]\n\n\ndef test_function_directive():\n    class Component(VueComponent):\n        @staticmethod\n        @directive\n        def focus(el, binding, vnode, old_vnode):\n            return el, binding, vnode, old_vnode\n\n    init_dict = Component.init_dict()\n    res = [\"el\", \"binding\", \"vnode\", \"old_vnode\"]\n    assert res == init_dict[\"directives\"][\"focus\"](\n        \"el\", \"binding\", \"vnode\", \"old_vnode\"\n    )\n\n\ndef test_full_directive_different_hooks():\n    class Component(VueComponent):\n        @staticmethod\n        @directive(\"focus\")\n        def bind():\n            return \"bind\"\n\n        @staticmethod\n        @directive(\"focus\")\n        def inserted():\n            return \"inserted\"\n\n        @staticmethod\n        @directive(\"focus\")\n        def update():\n            return \"update\"\n\n        @staticmethod\n        @directive(\"focus\")\n        def component_updated():\n            return \"componentUpdated\"\n\n        @staticmethod\n        @directive(\"focus\")\n        def unbind():\n            return \"unbind\"\n\n    init_dict = Component.init_dict()\n    directive_map = init_dict[\"directives\"][\"focus\"]\n    for fn_name in (\"bind\", \"inserted\", \"update\", \"componentUpdated\", \"unbind\"):\n        assert fn_name == directive_map[fn_name]()\n\n\ndef test_full_directive_single_hook():\n    class Component(VueComponent):\n        @staticmethod\n        @directive(\"focus\", \"bind\", \"inserted\", \"update\", \"component_updated\", \"unbind\")\n        def hook():\n            return \"hook\"\n\n    init_dict = Component.init_dict()\n    directive_map = init_dict[\"directives\"][\"focus\"]\n    for fn_name in (\"bind\", \"inserted\", \"update\", \"componentUpdated\", \"unbind\"):\n        assert \"hook\" == directive_map[fn_name]()\n\n\ndef test_directive_replace_dash():\n    class Component(VueComponent):\n        @staticmethod\n        @directive\n        def focus_dashed(el, binding, vnode, old_vnode):\n            return el, binding, vnode, old_vnode\n\n    init_dict = Component.init_dict()\n    assert \"focus-dashed\" in init_dict[\"directives\"]\n\n\ndef test_mixins():\n    class Component(VueComponent):\n        mixins = [{\"created\": \"fn\"}]\n\n    assert [{\"created\": \"fn\"}] == Component.init_dict()[\"mixins\"]\n\n\ndef test_vuepy_mixin():\n    class MyMixin(VueMixin):\n        pass\n\n    class Component(VueComponent):\n        mixins = [MyMixin]\n\n    assert [{}] == Component.init_dict()[\"mixins\"]\n\n\ndef test_render_function():\n    class Component(VueComponent):\n        def render(self, create_element):\n            pass\n\n    assert \"render\" in Component.init_dict()\n\n\ndef test_attributes_from_base_class():\n    class Component(VueComponent):\n        template = \"TEMPLATE\"\n\n    class SubComponent(Component):\n        pass\n\n    assert \"TEMPLATE\" == SubComponent.init_dict()[\"template\"]\n\n\ndef test_extends():\n    class Component(VueComponent):\n        template = \"TEMPLATE\"\n\n    class SubComponent(Component):\n        extends = True\n\n    assert {\"template\": \"TEMPLATE\"} == SubComponent.init_dict()[\"extends\"]\n\n\ndef test_template_merging():\n    class Base(VueComponent):\n        template = \"<p>BASE {}</p>\"\n\n    class Middle(Base):\n        template_slots = \"MIDDLE {}\"\n\n    class Sub(Middle):\n        template_slots = \"SUB\"\n\n    assert \"<p>BASE MIDDLE SUB</p>\" == Sub.init_dict()[\"template\"]\n\n\ndef test_template_merging_with_slots():\n    class Base(VueComponent):\n        template_slots = {\"pre\": \"DEFAULT\", \"post\": \"DEFAULT\"}\n        template = \"<p>{pre} {} {post}</p>\"\n\n    class WithSlots(Base):\n        template_slots = {\"pre\": \"PRE\", \"default\": \"SUB\"}\n\n    class WithDefault(Base):\n        template_slots = \"SUB\"\n\n    assert \"<p>PRE SUB DEFAULT</p>\" == WithSlots.init_dict()[\"template\"]\n    assert \"<p>DEFAULT SUB DEFAULT</p>\" == WithDefault.init_dict()[\"template\"]\n\n\ndef test_components():\n    class Component(VueComponent):\n        components = [{\"created\": \"fn\"}]\n\n    assert [{\"created\": \"fn\"}] == Component.init_dict()[\"components\"]\n\n\ndef test_vuepy_components():\n    class SubComponent(VueComponent):\n        pass\n\n    class Component(VueComponent):\n        components = [SubComponent]\n\n    assert [{}] == Component.init_dict()[\"components\"]\n"
  },
  {
    "path": "tests/unit/test_transformers/test_router.py",
    "content": "from unittest.mock import Mock\nfrom vue import *\n\n\nclass TestVueRoute:\n    def test_path_and_component(self):\n        route = VueRoute(\"/path\", VueComponent)\n        assert route == {\"path\": \"/path\", \"component\": VueComponent.init_dict()}\n\n    def test_path_and_components(self):\n        route = VueRoute(\n            \"/path\", components={\"default\": VueComponent, \"named\": VueComponent}\n        )\n        assert route == {\n            \"path\": \"/path\",\n            \"components\": {\n                \"default\": VueComponent.init_dict(),\n                \"named\": VueComponent.init_dict(),\n            },\n        }\n\n    def test_path_and_components_and_children(self):\n        route = VueRoute(\n            \"/path\",\n            VueComponent,\n            children=[\n                VueRoute(\"/path\", VueComponent),\n                VueRoute(\"/path2\", VueComponent),\n            ],\n        )\n        assert route == {\n            \"path\": \"/path\",\n            \"component\": VueComponent.init_dict(),\n            \"children\": [\n                {\"path\": \"/path\", \"component\": VueComponent.init_dict()},\n                {\"path\": \"/path2\", \"component\": VueComponent.init_dict()},\n            ],\n        }\n\n    def test_path_and_redirect(self):\n        route = VueRoute(\"/path\", redirect=\"/path2\")\n        assert route == {\"path\": \"/path\", \"redirect\": \"/path2\"}\n\n\nclass TestVueRouter:\n    def test_routes(self):\n        class Router(VueRouter):\n            routes = [VueRoute(\"/path\", VueComponent)]\n\n        routes = Router.init_dict()[\"routes\"]\n        assert routes == [{\"path\": \"/path\", \"component\": VueComponent.init_dict()}]\n\n    def test_custom_router(self):\n        router_class_mock = Mock()\n        router_class_mock.new.return_value = \"CustomRouter\"\n\n        class Router(VueRouter):\n            RouterClass = router_class_mock\n\n        router = Router()\n        assert router == \"CustomRouter\"\n        router_class_mock.new.assert_called_with(Router.init_dict())\n"
  },
  {
    "path": "tests/unit/test_transformers/test_store.py",
    "content": "from vue import *\nfrom vue.bridge import VuexInstance\n\n\ndef test_state():\n    class Store(VueStore):\n        attribute = 1\n\n    assert {\"attribute\": 1} == Store.init_dict()[\"state\"]\n\n\ndef test_mutation():\n    class Store(VueStore):\n        @mutation\n        def mutation(self, payload):\n            return self, payload\n\n    store, arg = Store.init_dict()[\"mutations\"][\"mutation\"]({}, {\"args\": (2,)})\n    assert 2 == arg\n    assert isinstance(store, VuexInstance)\n\n\ndef test_action():\n    class Context:\n        def __init__(self):\n            self.state = {}\n            self.getters = {}\n            self.rootState = {}\n            self.rootGetters = {}\n            self.commit = None\n            self.dispatch = None\n\n    class Store(VueStore):\n        @action\n        def action(self, payload):\n            return self, payload\n\n    store, arg = Store.init_dict()[\"actions\"][\"action\"](Context(), {\"args\": (2,)})\n    assert 2 == arg\n    assert isinstance(store, VuexInstance)\n\n\ndef test_getter():\n    class Store(VueStore):\n        @staticmethod\n        @getter\n        def getter(self):\n            return self\n\n    vuex = Store.init_dict()[\"getters\"][\"getter\"]({}, {})\n    assert isinstance(vuex, VuexInstance)\n\n\ndef test_getter_method():\n    class Store(VueStore):\n        @staticmethod\n        @getter\n        def getter(self, value):\n            return self, value\n\n    vuex, value = Store.init_dict()[\"getters\"][\"getter\"]({}, {})(3)\n    assert isinstance(vuex, VuexInstance)\n    assert 3 == value\n\n\ndef test_plugin_registration():\n    class Store(VueStore):\n        plugins = [1]\n\n    assert [1] == Store.init_dict()[\"plugins\"]\n"
  },
  {
    "path": "tests/unit/test_utils.py",
    "content": "import pytest\nfrom unittest import mock\nfrom vue import utils\nfrom vue.utils import *\n\n\n@pytest.fixture\ndef fix_load_and_window():\n    utils.CACHE.clear()\n\n    class WindowMock:\n        def __getattr__(self, item):\n            return item\n\n    class LoadMock:\n        def __init__(self):\n            self.call_count = 0\n\n        def __call__(self, path):\n            self.call_count += 1\n            path, mods = path.split(\";\")\n            mods = mods.split(\",\")\n            for mod in mods:\n                if mod:\n                    setattr(WindowMock, mod, mod)\n\n    with mock.patch.object(utils, \"load\", new=LoadMock()), mock.patch.object(\n        utils, \"window\", new=WindowMock\n    ):\n        yield\n\n\n@pytest.mark.usefixtures(\"fix_load_and_window\")\nclass TestJsLoad:\n    def test_single(self):\n        assert \"a\" == js_load(\"path;a\")\n\n    def test_multiple(self):\n        assert {\"a\": \"a\", \"b\": \"b\"} == js_load(\"path;a,b\")\n\n    def test_different(self):\n        assert \"a\" == js_load(\"first;a\")\n        assert \"b\" == js_load(\"second;b\")\n        assert 2 == utils.load.call_count\n\n    def test_using_cache(self):\n        assert \"a\" == js_load(\"path;a\")\n        assert \"a\" == js_load(\"path;a\")\n        assert 1 == utils.load.call_count\n\n    def test_none(self):\n        assert js_load(\"path;\") is None\n\n    def test_ignore_dollar(self):\n        assert js_load(\"path;$test\") is None\n\n\nclass TestJsLib:\n    def test_getattr_of_window(self):\n        class WindowMock:\n            attribute = \"ATTRIBUTE\"\n\n        with mock.patch.object(utils, \"window\", new=WindowMock):\n            assert \"ATTRIBUTE\" == js_lib(\"attribute\")\n\n    def test_get_default(self):\n        class AttributeWithDefault:\n            default = \"DEFAULT\"\n\n            def __dir__(self):\n                return [\"default\"]\n\n        class WindowMock:\n            attribute = AttributeWithDefault()\n\n        with mock.patch.object(utils, \"window\", new=WindowMock):\n            assert \"DEFAULT\" == js_lib(\"attribute\")\n"
  },
  {
    "path": "tests/unit/test_vue.py",
    "content": "from contextlib import contextmanager\nfrom unittest import mock\n\nimport pytest\n\nfrom vue import Vue, VueComponent, VueDirective, VueMixin\nfrom vue.bridge.dict import window\n\n\n@pytest.fixture(autouse=True)\ndef window_object():\n    with mock.patch.object(window, \"Object\", new=dict):\n        yield\n\n\nclass VueMock(mock.MagicMock):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.init_dict = None\n        self.register_name = None\n        self.directive_name = None\n        self._directive = None\n\n    @contextmanager\n    def new(self):\n        with mock.patch(\"vue.vue.window.Vue.new\", new=self) as new:\n            yield self\n        self.init_dict = new.call_args[0][0]\n\n    @contextmanager\n    def component(self):\n        with mock.patch(\"vue.vue.window.Vue.component\", new=self) as component:\n            component.side_effect = lambda *args, **kwargs: self.init_dict\n            yield self\n        self.init_dict = (\n            component.call_args[0][1] if len(component.call_args[0]) > 1 else component\n        )\n        self.register_name = component.call_args[0][0]\n\n    @contextmanager\n    def directive(self):\n        with mock.patch(\"vue.vue.window.Vue.directive\", new=self) as drctv:\n            drctv.side_effect = lambda *args, **kwargs: self._directive\n            yield self\n        self.directive_name = drctv.call_args[0][0]\n        self._directive = drctv.call_args[0][1] if len(drctv.call_args[0]) > 1 else None\n\n    @contextmanager\n    def filter(self):\n        with mock.patch(\"vue.vue.window.Vue.filter\", new=self) as flt:\n            yield self\n        flt._filter_name = flt.call_args[0][0]\n        flt._filter = flt.call_args[0][1]\n\n    @contextmanager\n    def mixin(self):\n        with mock.patch(\"vue.vue.window.Vue.mixin\", new=self) as mixin:\n            yield self\n        mixin.init_dict = mixin.call_args[0][0]\n\n    @contextmanager\n    def use(self):\n        with mock.patch(\"vue.vue.window.Vue.use\", new=self) as use:\n            yield self\n        use.plugin = use.call_args[0][0]\n\n\nclass TestVueComponent:\n    def test_el(self):\n        class Component(VueComponent):\n            pass\n\n        with VueMock().new() as new:\n            Component(\"app\")\n        assert \"app\" == new.init_dict[\"el\"]\n\n    def test_props_data(self):\n        class Component(VueComponent):\n            prop: str\n\n        with VueMock().new() as new:\n            Component(\"app\", props_data={\"prop\": \"PROP\"})\n        assert {\"prop\": \"PROP\"} == new.init_dict[\"propsData\"]\n\n    def test_register(self):\n        class Component(VueComponent):\n            pass\n\n        with VueMock().component() as component:\n            Component.register()\n        assert \"Component\" == component.register_name\n\n        with VueMock().component() as component:\n            Component.register(\"new-name\")\n        assert \"new-name\" == component.register_name\n\n\nclass TestVue:\n    def test_directive(self):\n        class Drctv(VueDirective):\n            def bind(self):\n                pass\n\n        with VueMock().directive() as dirctv:\n            Vue.directive(\"directive\", Drctv)\n        assert \"directive\" == dirctv.directive_name\n        assert \"bind\" in dirctv._directive\n\n    def test_directive_with_implicit_name(self):\n        class Drctv(VueDirective):\n            def bind(self):\n                pass\n\n        with VueMock().directive() as dirctv:\n            Vue.directive(Drctv)\n        assert \"drctv\" == dirctv.directive_name\n        assert \"bind\" in dirctv._directive\n\n    def test_function_directive(self):\n        def function_directive(a):\n            return a\n\n        with VueMock().directive() as dirctv:\n            Vue.directive(\"my-directive\", function_directive)\n        assert \"my-directive\" == dirctv.directive_name\n        assert \"a\" == dirctv._directive(\"a\")\n\n    def test_function_directive_with_implicit_name(self):\n        def function_directive(a):\n            return a\n\n        with VueMock().directive() as dirctv:\n            Vue.directive(function_directive)\n        assert \"function_directive\" == dirctv.directive_name\n        assert \"a\" == dirctv._directive(\"a\")\n\n    def test_directive_getter(self):\n        with VueMock().directive() as drctv:\n            drctv._directive = \"DIRECTIVE\"\n            drctv.directive_name = \"my-directive\"\n            assert \"DIRECTIVE\" == Vue.directive(\"my-durective\")\n\n    def test_filter(self):\n        with VueMock().filter() as filter_mock:\n            Vue.filter(\"my_filter\", lambda val: \"filtered({})\".format(val))\n        assert \"my_filter\" == filter_mock._filter_name\n        assert \"filtered(value)\" == filter_mock._filter(\"value\")\n\n    def test_filter_use_function_name(self):\n        def flt(v):\n            return \"filtered({})\".format(v)\n\n        with VueMock().filter() as filter_mock:\n            Vue.filter(flt)\n        assert \"flt\" == filter_mock._filter_name\n        assert \"filtered(value)\" == filter_mock._filter(\"value\")\n\n    def test_mixin(self):\n        class Mixin(VueMixin):\n            def created(self):\n                return \"created\"\n\n        with VueMock().mixin() as mixin_mock:\n            Vue.mixin(Mixin)\n        assert \"created\" == mixin_mock.init_dict[\"created\"]()\n\n    def test_use(self):\n        with VueMock().use() as use:\n            Vue.use(\"Plugin\")\n        assert \"Plugin\" == use.plugin\n\n    def test_component(self):\n        with VueMock().component() as component:\n            Vue.component(\"my-component\", {\"a\": 0})\n        assert {\"a\": 0} == component.init_dict\n        assert \"my-component\" == component.register_name\n\n    def test_vuepy_component(self):\n        class MyComponent(VueComponent):\n            pass\n\n        with VueMock().component() as component:\n            Vue.component(\"my-component\", MyComponent)\n        assert {} == component.init_dict\n        assert \"my-component\" == component.register_name\n\n    def test_vuepy_component_implicit_naming(self):\n        class MyComponent(VueComponent):\n            pass\n\n        with VueMock().component() as component:\n            Vue.component(MyComponent)\n        assert {} == component.init_dict\n        assert \"MyComponent\" == component.register_name\n\n    def test_component_getter(self):\n        with VueMock().component() as comp:\n            comp.init_dict = {\"a\": 0}\n            component = Vue.component(\"my-component\")\n        assert {\"a\": 0} == component\n"
  },
  {
    "path": "vue/__init__.py",
    "content": "from .__version__ import __version__\n\ntry:\n    from .vue import VueComponent, VueMixin, Vue, VueDirective, VuePlugin\n    from .store import VueStore, VueStorePlugin\n    from .router import VueRouter, VueRoute\n    from .decorators import (\n        computed,\n        validator,\n        directive,\n        filters,\n        watch,\n        data,\n        Model,\n        custom,\n        mutation,\n        action,\n        getter,\n    )\nexcept ModuleNotFoundError:\n    pass\n"
  },
  {
    "path": "vue/bridge/__init__.py",
    "content": "from .object import Object\nfrom .list import List\nfrom .dict import Dict\nfrom .vue_instance import VueInstance\nfrom .vuex_instance import VuexInstance\n"
  },
  {
    "path": "vue/bridge/dict.py",
    "content": "from browser import window\nfrom .object import Object\n\n\nclass Dict(Object):\n    @staticmethod\n    def __unwraps__():\n        return dict\n\n    @staticmethod\n    def __can_wrap__(obj):\n        return (str(type(obj)) == \"<undefined>\") or (\n            obj.__class__.__name__ == \"JSObject\"\n            and not callable(obj)\n            and not isinstance(obj, dict)\n        )\n\n    def __eq__(self, other):\n        return other == {k: v for k, v in self.items()}\n\n    def __getitem__(self, item):\n        return Object.from_js(getattr(self._js, item))\n\n    def __iter__(self):\n        return (k for k in self.keys())\n\n    def pop(self, k, default=...):\n        if k not in self and not isinstance(default, type(Ellipsis)):\n            return default\n        item = self[k]\n        del self[k]\n        return item\n\n    def popitem(self):\n        key = self.keys()[0]\n        return key, self.pop(key)\n\n    def setdefault(self, k, default=None):\n        if k not in self:\n            self[k] = default\n        return self[k]\n\n    def __len__(self):\n        return len(self.items())\n\n    def __contains__(self, item):\n        return Object.to_js(item) in self.keys()\n\n    def __delitem__(self, key):\n        window.Vue.delete(self._js, Object.to_js(key))\n\n    def __setitem__(self, key, value):\n        if key not in self:\n            window.Vue.set(self._js, Object.to_js(key), Object.to_js(value))\n        else:\n            setattr(self._js, Object.to_js(key), Object.to_js(value))\n\n    def get(self, k, default=None):\n        if k not in self:\n            return default\n        return self[k]\n\n    def values(self):\n        return tuple(self[key] for key in self)\n\n    def update(self, _m=None, **kwargs):\n        if _m is None:\n            _m = {}\n            _m.update(kwargs)\n        window.Object.assign(self._js, Object.to_js(_m))\n\n    def clear(self):\n        while len(self) > 0:\n            self.popitem()\n\n    @classmethod\n    def fromkeys(cls, seq):\n        raise NotImplementedError()\n\n    def copy(self):\n        raise NotImplementedError()\n\n    def items(self):\n        return tuple((key, self[key]) for key in self)\n\n    def keys(self):\n        return tuple(Object.from_js(key) for key in window.Object.keys(self._js))\n\n    def __str__(self):\n        if hasattr(self, \"toString\") and callable(self.toString):\n            return self.toString()\n        return repr(self)\n\n    def __repr__(self):\n        return \"{{{}}}\".format(\n            \", \".join(\"{!r}: {!r}\".format(k, v) for k, v in self.items())\n        )\n\n    def __set__(self, new):\n        self.clear()\n        self.update(new)\n\n    def __bool__(self):\n        return len(self) > 0\n\n    def __getattr__(self, item):\n        try:\n            return self[item]\n        except KeyError:\n            raise AttributeError(item)\n\n    def __setattr__(self, key, value):\n        if key in [\"_js\"]:\n            return super().__setattr__(key, value)\n        self[key] = value\n\n    def __py__(self):\n        return {Object.to_py(k): Object.to_py(v) for k, v in self.items()}\n\n    def __js__(self):\n        if isinstance(self, dict):\n            return window.Object(\n                {Object.to_js(k): Object.to_js(v) for k, v in self.items()}\n            )\n        return self._js\n\n\nObject.Default = Dict\n"
  },
  {
    "path": "vue/bridge/list.py",
    "content": "from browser import window\nfrom .object import Object\n\n\nclass List(Object):\n    @staticmethod\n    def __unwraps__():\n        return list, tuple\n\n    @staticmethod\n    def __can_wrap__(obj):\n        return window.Array.isArray(obj) and not isinstance(obj, list)\n\n    def _slice(self, slc):\n        if isinstance(slc, int):\n            if slc < 0:\n                slc = len(self) + slc\n            return slc, slc + 1\n        start = slc.start if slc.start is not None else 0\n        stop = slc.stop if slc.stop is not None else len(self)\n        return start, stop\n\n    def __eq__(self, other):\n        return other == [i for i in self]\n\n    def __mul__(self, other):\n        return [i for i in self] * other\n\n    def index(self, obj, start=0, _stop=-1):\n        index = self._js.indexOf(Object.to_js(obj), start)\n        if index == -1:\n            raise ValueError(\"{} not in list\".format(obj))\n        return index\n\n    def extend(self, iterable):\n        self._js.push(*(i for i in iterable))\n\n    def __len__(self):\n        return self._js.length\n\n    def __contains__(self, item):\n        try:\n            self.index(item)\n            return True\n        except ValueError:\n            return False\n\n    def __imul__(self, other):\n        raise NotImplementedError()\n\n    def count(self, obj):\n        return [i for i in self].count(obj)\n\n    def reverse(self):\n        self._js.reverse()\n\n    def __delitem__(self, key):\n        start, stop = self._slice(key)\n        self._js.splice(start, stop - start)\n\n    def __setitem__(self, key, value):\n        start, stop = self._slice(key)\n        value = value if isinstance(value, list) else [value]\n        self._js.splice(start, stop - start, *value)\n\n    def __getitem__(self, item):\n        start, stop = self._slice(item)\n        value = self._js.slice(start, stop)\n        if isinstance(item, int):\n            return Object.from_js(value[0])\n        return [Object.from_js(i) for i in value]\n\n    def __reversed__(self):\n        raise NotImplementedError()\n\n    def __rmul__(self, other):\n        raise NotImplemented()\n\n    def append(self, obj):\n        self._js.push(Object.to_js(obj))\n\n    def insert(self, index, obj):\n        self._js.splice(index, 0, Object.to_js(obj))\n\n    def remove(self, obj):\n        index = self._js.indexOf(Object.to_js(obj))\n        while index != -1:\n            del self[self._js.indexOf(Object.to_js(obj))]\n            index = self._js.indexOf(Object.to_js(obj))\n\n    def __iadd__(self, other):\n        raise NotImplemented()\n\n    def __iter__(self):\n        def _iter(lst):\n            for i in range(lst.__len__()):\n                yield lst[i]\n\n        return _iter(self)\n\n    def pop(self, index=-1):\n        return Object.from_js(self._js.splice(index, 1)[0])\n\n    def sort(self, key=None, reverse=False):\n        self[:] = sorted(self, key=key, reverse=reverse)\n\n    def __add__(self, other):\n        raise NotImplemented()\n\n    def clear(self):\n        raise NotImplemented()\n\n    def copy(self):\n        raise NotImplemented()\n\n    def __set__(self, new):\n        self[:] = new\n\n    def __repr__(self):\n        return \"[{}]\".format(\", \".join(repr(i) for i in self))\n\n    def __py__(self):\n        return [Object.to_py(item) for item in self]\n\n    def __js__(self):\n        if isinstance(self, (list, tuple)):\n            return window.Array(*[Object.to_js(item) for item in self])\n        return self._js\n\n\nObject.SubClasses.append(List)\n"
  },
  {
    "path": "vue/bridge/object.py",
    "content": "class Object:\n    SubClasses = []\n    Default = None\n\n    @classmethod\n    def sub_classes(cls):\n        return cls.SubClasses + ([cls.Default] if cls.Default else [])\n\n    @classmethod\n    def from_js(cls, jsobj):\n        for sub_class in cls.sub_classes():\n            if sub_class.__can_wrap__(jsobj):\n                return sub_class(jsobj)\n        return jsobj\n\n    @classmethod\n    def to_js(cls, obj):\n        if isinstance(obj, Object):\n            return obj.__js__()\n        for sub_class in cls.sub_classes():\n            if isinstance(obj, sub_class.__unwraps__()):\n                return sub_class.__js__(obj)\n        return obj\n\n    @classmethod\n    def to_py(cls, obj):\n        obj = Object.from_js(obj)\n        if isinstance(obj, Object):\n            return obj.__py__()\n        for sub_class in cls.sub_classes():\n            if isinstance(obj, sub_class.__unwraps__()):\n                return sub_class.__py__(obj)\n        return obj\n\n    @staticmethod\n    def __can_wrap__(obj):\n        return False\n\n    @staticmethod\n    def __unwraps__():\n        return ()\n\n    def __init__(self, js):\n        self._js = js\n\n    def __js__(self):\n        return self._js\n\n    def __py__(self):\n        return self\n"
  },
  {
    "path": "vue/bridge/vue_instance.py",
    "content": "from .object import Object\nfrom .vuex_instance import VuexInstance\n\n\nclass VueInstance(Object):\n    @staticmethod\n    def __can_wrap__(obj):\n        return hasattr(obj, \"_isVue\") and obj._isVue\n\n    @property\n    def store(self):\n        store = self.__getattr__(\"store\")\n        return VuexInstance(\n            state=store.state,\n            getters=store.getters,\n            commit=store.commit,\n            dispatch=store.dispatch,\n        )\n\n    def __getattr__(self, item):\n        try:\n            return Object.from_js(getattr(self._js, item))\n        except AttributeError:\n            if not item.startswith(\"$\"):\n                return self.__getattr__(\"${}\".format(item))\n            raise\n\n    def __setattr__(self, key, value):\n        if key in [\"_js\"]:\n            object.__setattr__(self, key, value)\n        elif hasattr(getattr(self, key), \"__set__\"):\n            getattr(self, key).__set__(value)\n        else:\n            if key not in dir(getattr(self._js, \"$props\", [])):\n                setattr(self._js, key, value)\n\n\nObject.SubClasses.append(VueInstance)\n"
  },
  {
    "path": "vue/bridge/vuex_instance.py",
    "content": "class VuexInstance:\n    def __init__(\n        self,\n        state=None,\n        getters=None,\n        root_state=None,\n        root_getters=None,\n        commit=None,\n        dispatch=None,\n    ):\n        self.__state__ = state if state else {}\n        self.__getter__ = getters\n        self.__root_getter__ = root_getters\n        self.__root_state__ = root_state if root_state else {}\n        self.__commit__ = commit\n        self.__dispatch__ = dispatch\n\n    def __getattr__(self, item):\n        item = item.replace(\"$\", \"\")\n        if item in [\"__state__\", \"__root_state__\"]:\n            return {}\n        if item in self.__state__:\n            return self.__state__[item]\n        if hasattr(self.__getter__, item):\n            return getattr(self.__getter__, item)\n        if item in self.__root_state__:\n            return self.__root_state__[item]\n        if hasattr(self.__root_getter__, item):\n            return getattr(self.__root_getter__, item)\n        return super().__getattribute__(item)\n\n    def __setattr__(self, key, value):\n        key = key.replace(\"$\", \"\")\n        if key in self.__state__:\n            self.__state__[key] = value\n        elif key in self.__root_state__:\n            self.__root_state__[key] = value\n        else:\n            super().__setattr__(key, value)\n\n    def commit(self, mutation_name, *args, **kwargs):\n        self.__commit__(mutation_name, {\"args\": args, \"kwargs\": kwargs})\n\n    def dispatch(self, mutation_name, *args, **kwargs):\n        self.__dispatch__(mutation_name, {\"args\": args, \"kwargs\": kwargs})\n"
  },
  {
    "path": "vue/decorators/__init__.py",
    "content": "from .computed import computed\nfrom .prop import validator\nfrom .directive import directive, DirectiveHook\nfrom .filters import filters\nfrom .watcher import watch\nfrom .data import data\nfrom .model import Model\nfrom .custom import custom\nfrom .mutation import mutation\nfrom .action import action\nfrom .getter import getter\n"
  },
  {
    "path": "vue/decorators/action.py",
    "content": "from .base import pyjs_bridge, VueDecorator\nfrom vue.bridge import VuexInstance\n\n\nclass Action(VueDecorator):\n    def __init__(self, name, value):\n        self.__key__ = f\"actions.{name}\"\n        self.__value__ = value\n\n\ndef action(fn):\n    def wrapper(context, *payload):\n        payload = payload[0] if payload else {\"args\": (), \"kwargs\": {}}\n        return fn(\n            VuexInstance(\n                state=context.state,\n                getters=context.getters,\n                root_state=context.rootState,\n                root_getters=context.rootGetters,\n                commit=context.commit,\n                dispatch=context.dispatch,\n            ),\n            *payload.get(\"args\", ()),\n            **payload.get(\"kwargs\", {}),\n        )\n\n    return Action(fn.__name__, pyjs_bridge(wrapper))\n"
  },
  {
    "path": "vue/decorators/base.py",
    "content": "from vue.bridge import Object\nimport javascript\n\n\nclass VueDecorator:\n    __key__ = None\n    __value__ = None\n\n\ndef pyjs_bridge(fn, inject_vue_instance=False):\n    def wrapper(*args, **kwargs):\n        args = (javascript.this(), *args) if inject_vue_instance else args\n        args = tuple(Object.from_js(arg) for arg in args)\n        kwargs = {k: Object.from_js(v) for k, v in kwargs.items()}\n        return Object.to_js(fn(*args, **kwargs))\n\n    wrapper.__name__ = fn.__name__\n    return wrapper\n"
  },
  {
    "path": "vue/decorators/components.py",
    "content": "from .base import VueDecorator\n\n\nclass Components(VueDecorator):\n    __key__ = \"components\"\n\n    def __init__(self, *mixins):\n        self.__value__ = list(mixins)\n"
  },
  {
    "path": "vue/decorators/computed.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Computed(VueDecorator):\n    def __init__(self, fn):\n        self.__key__ = f\"computed.{fn.__name__}\"\n        self.__name__ = fn.__name__\n        self.__call__ = pyjs_bridge(fn)\n        self._setter = None\n\n    def setter(self, fn):\n        self._setter = pyjs_bridge(fn, inject_vue_instance=True)\n        return self\n\n    @property\n    def __value__(self):\n        vue_object = {\"get\": self.__call__}\n        if self._setter:\n            vue_object[\"set\"] = self._setter\n        return vue_object\n\n\ndef computed(fn):\n    return Computed(fn)\n"
  },
  {
    "path": "vue/decorators/custom.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Custom(VueDecorator):\n    def __init__(self, fn, key, name=None, static=False):\n        self.__key__ = f\"{key}.{name if name is not None else fn.__name__}\"\n        self.__value__ = pyjs_bridge(fn, inject_vue_instance=not static)\n\n\ndef custom(key, name=None, static=False):\n    def wrapper(fn):\n        return Custom(fn, key, name, static)\n\n    return wrapper\n"
  },
  {
    "path": "vue/decorators/data.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Data(VueDecorator):\n    def __init__(self, name, value):\n        self.__key__ = f\"data.{name}\"\n        self.__value__ = value\n\n\ndef data(fn):\n    return Data(fn.__name__, pyjs_bridge(fn))\n"
  },
  {
    "path": "vue/decorators/directive.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\ndef map_hook(hook_name):\n    if hook_name == \"component_updated\":\n        return \"componentUpdated\"\n    return hook_name\n\n\nclass DirectiveHook(VueDecorator):\n    def __init__(self, fn, hooks=(), name=None):\n        name = name if name else fn.__name__\n        self.__key__ = f\"directives.{name.replace('_', '-')}\"\n        self.__value__ = pyjs_bridge(fn)\n\n        if hooks:\n            self.__value__ = {map_hook(hook): self.__value__ for hook in hooks}\n\n\ndef _directive_hook(name, hooks):\n    def wrapper(fn):\n        _hooks = (fn.__name__,) if not hooks else hooks\n        return DirectiveHook(fn, hooks=_hooks, name=name)\n\n    return wrapper\n\n\ndef directive(fn, *hooks):\n    if callable(fn):\n        return DirectiveHook(fn)\n    return _directive_hook(fn, hooks)\n"
  },
  {
    "path": "vue/decorators/extends.py",
    "content": "from .base import VueDecorator\n\n\nclass Extends(VueDecorator):\n    __key__ = \"extends\"\n\n    def __init__(self, init_dict):\n        self.__value__ = init_dict\n"
  },
  {
    "path": "vue/decorators/filters.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Filter(VueDecorator):\n    def __init__(self, fn, name):\n        self.name = name\n        self.__key__ = f\"filters.{name}\"\n        self.__value__ = pyjs_bridge(fn)\n\n\ndef filters(fn):\n    return Filter(fn, fn.__name__)\n"
  },
  {
    "path": "vue/decorators/getter.py",
    "content": "from .base import pyjs_bridge, VueDecorator\nfrom vue.bridge import VuexInstance\n\n\nclass Getter(VueDecorator):\n    def __init__(self, name, value):\n        self.__key__ = f\"getters.{name}\"\n        self.__value__ = value\n\n\ndef getter(fn):\n    def wrapper(state, getters, *args):\n        if fn.__code__.co_argcount == 1:\n            return fn(VuexInstance(state=state, getters=getters))\n        else:\n\n            def getter_method(*args_, **kwargs):\n                return fn(VuexInstance(state=state, getters=getters), *args_, **kwargs)\n\n            return getter_method\n\n    return Getter(fn.__name__, pyjs_bridge(wrapper))\n"
  },
  {
    "path": "vue/decorators/lifecycle_hook.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass LifecycleHook(VueDecorator):\n    mapping = {\n        \"before_create\": \"beforeCreate\",\n        \"created\": \"created\",\n        \"before_mount\": \"beforeMount\",\n        \"mounted\": \"mounted\",\n        \"before_update\": \"beforeUpdate\",\n        \"updated\": \"updated\",\n        \"before_destroy\": \"beforeDestroy\",\n        \"destroyed\": \"destroyed\",\n    }\n\n    def __init__(self, name, fn):\n        self.__key__ = self.mapping[name]\n        self.__value__ = pyjs_bridge(fn, inject_vue_instance=True)\n\n\ndef lifecycle_hook(name):\n    def wrapper(fn):\n        return LifecycleHook(name, fn)\n\n    return wrapper\n"
  },
  {
    "path": "vue/decorators/method.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Method(VueDecorator):\n    def __init__(self, fn):\n        if hasattr(fn, \"__coroutinefunction__\"):\n            fn = coroutine(fn)\n        self.__value__ = pyjs_bridge(fn, inject_vue_instance=True)\n        self.__key__ = f\"methods.{fn.__name__}\"\n\n\ndef coroutine(_coroutine):\n    def wrapper(*args, **kwargs):\n        import asyncio\n\n        return asyncio.ensure_future(_coroutine(*args, **kwargs))\n\n    wrapper.__name__ = _coroutine.__name__\n    return wrapper\n\n\ndef method(_method):\n    if hasattr(_method, \"__coroutinefunction__\"):\n        _method = coroutine(_method)\n    return Method(_method)\n"
  },
  {
    "path": "vue/decorators/mixins.py",
    "content": "from .base import VueDecorator\n\n\nclass Mixins(VueDecorator):\n    __key__ = \"mixins\"\n\n    def __init__(self, *mixins):\n        self.__value__ = list(mixins)\n"
  },
  {
    "path": "vue/decorators/model.py",
    "content": "from .base import VueDecorator\n\n\nclass Model(VueDecorator):\n    __key__ = \"model\"\n\n    def __init__(self, prop=\"value\", event=\"input\"):\n        self.prop = prop\n        self.event = event\n\n    @property\n    def __value__(self):\n        return {\"prop\": self.prop, \"event\": self.event}\n"
  },
  {
    "path": "vue/decorators/mutation.py",
    "content": "from vue.bridge import VuexInstance\nfrom .base import pyjs_bridge, VueDecorator\n\n\nclass Mutation(VueDecorator):\n    def __init__(self, name, value):\n        self.__key__ = f\"mutations.{name}\"\n        self.__value__ = value\n\n\ndef mutation(fn):\n    def wrapper(state, payload):\n        return fn(\n            VuexInstance(state=state),\n            *payload.get(\"args\", ()),\n            **payload.get(\"kwargs\", {}),\n        )\n\n    return Mutation(fn.__name__, pyjs_bridge(wrapper))\n"
  },
  {
    "path": "vue/decorators/plugin.py",
    "content": "from .base import VueDecorator\n\n\nclass Plugin(VueDecorator):\n    __key__ = \"plugins\"\n\n    def __init__(self, plugins):\n        self.__value__ = list(plugins)\n"
  },
  {
    "path": "vue/decorators/prop.py",
    "content": "from browser import window\nfrom .base import pyjs_bridge, VueDecorator\n\n\nclass Prop(VueDecorator):\n    type_map = {\n        int: window.Number,\n        float: window.Number,\n        str: window.String,\n        bool: window.Boolean,\n        list: window.Array,\n        object: window.Object,\n        dict: window.Object,\n        None: None,\n    }\n\n    def __init__(self, name, typ, mixin=None):\n        mixin = mixin if mixin else {}\n        self.__key__ = f\"props.{name}\"\n        self.__value__ = {\"type\": self.type_map[typ], **mixin}\n\n\nclass Validator(VueDecorator):\n    def __init__(self, prop, fn):\n        self.__key__ = f\"props.{prop}.validator\"\n        self.__value__ = pyjs_bridge(fn, inject_vue_instance=True)\n\n\ndef validator(prop):\n    def decorator(fn):\n        return Validator(prop, fn)\n\n    return decorator\n"
  },
  {
    "path": "vue/decorators/render.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Render(VueDecorator):\n    __key__ = \"render\"\n\n    def __init__(self, fn):\n        self.__value__ = pyjs_bridge(fn, inject_vue_instance=True)\n"
  },
  {
    "path": "vue/decorators/routes.py",
    "content": "from .base import VueDecorator\n\n\nclass Routes(VueDecorator):\n    __key__ = \"routes\"\n\n    def __init__(self, routes):\n        self.__value__ = list(routes)\n"
  },
  {
    "path": "vue/decorators/state.py",
    "content": "from .base import VueDecorator\n\n\nclass State(VueDecorator):\n    def __init__(self, name, value):\n        self.__key__ = f\"state.{name}\"\n        self.__value__ = value\n"
  },
  {
    "path": "vue/decorators/template.py",
    "content": "from .base import VueDecorator\n\n\nclass Template(VueDecorator):\n    __key__ = \"template\"\n\n    def __init__(self, template):\n        self.__value__ = template\n"
  },
  {
    "path": "vue/decorators/watcher.py",
    "content": "from .base import pyjs_bridge, VueDecorator\n\n\nclass Watcher(VueDecorator):\n    def __init__(self, name, fn, deep=False, immediate=False):\n        self.__key__ = f\"watch.{name}\"\n        self._fn = pyjs_bridge(fn, inject_vue_instance=True)\n        self._deep = deep\n        self._immediate = immediate\n\n    @property\n    def __value__(self):\n        return {\"handler\": self._fn, \"deep\": self._deep, \"immediate\": self._immediate}\n\n\ndef watch(name, deep=False, immediate=False):\n    def decorator(fn):\n        return Watcher(name, fn, deep, immediate)\n\n    return decorator\n"
  },
  {
    "path": "vue/router.py",
    "content": "from browser import window\nfrom .transformers import Transformable, VueRouterTransformer\n\n\nclass VueRouter(Transformable):\n    RouterClass = None\n\n    @classmethod\n    def init_dict(cls):\n        return VueRouterTransformer.transform(cls)\n\n    def __new__(cls):\n        router_class = cls.RouterClass or window.VueRouter\n        return router_class.new(cls.init_dict())\n\n\nclass VueRoute:\n    def __new__(cls, path, component=None, components=None, **kwargs):\n        route = {\"path\": path, **kwargs}\n\n        if component is not None:\n            route[\"component\"] = component.init_dict()\n        elif components is not None:\n            route[\"components\"] = {\n                name: component.init_dict() for name, component in components.items()\n            }\n\n        return route\n"
  },
  {
    "path": "vue/store.py",
    "content": "from browser import window\nfrom .transformers import Transformable, VueStoreTransformer\nfrom .bridge import Object\nfrom .bridge.vuex_instance import VuexInstance\n\n\nclass VueStore(Transformable):\n    @classmethod\n    def init_dict(cls):\n        return VueStoreTransformer.transform(cls)\n\n    def __new__(cls):\n        return Object.from_js(window.Vuex.Store.new(cls.init_dict()))\n\n\nclass VueStorePlugin:\n    def initialize(self, store):\n        raise NotImplementedError()\n\n    def subscribe(self, state, mutation, *args, **kwargs):\n        raise NotImplementedError()\n\n    def __subscribe__(self, muation_info, state):\n        self.subscribe(\n            VuexInstance(state=state),\n            muation_info[\"type\"],\n            *muation_info[\"payload\"][\"args\"],\n            **muation_info[\"payload\"][\"kwargs\"],\n        )\n\n    def install(self, store):\n        self.initialize(\n            VuexInstance(\n                state=store.state,\n                getters=store.getters,\n                commit=store.commit,\n                dispatch=store.dispatch,\n            )\n        )\n        store.subscribe(self.__subscribe__)\n"
  },
  {
    "path": "vue/transformers.py",
    "content": "\"\"\"\nTransformers are used to create dictionaries to initialize Vue-Objects from Python classes.\n\ne.g.\n```python\nclass Component(VueComponent):\n    prop: str\n\n    def method(self):\n        ...\n\n    @computed\n    def computed_value(self):\n        ...\n```\n\nwill be transformed into\n\n```javascript\n{\n    props: {\n        prop: {\n            type: window.String,\n        }\n    },\n    method: // wrapper calling Component.method,\n    computed: {\n        computed_value: {\n            get: // wrapper calling Component.computed_value\n        }\n    }\n}\n```\n\"\"\"\n\nfrom .decorators.base import VueDecorator\nfrom .decorators.prop import Prop\nfrom .decorators.data import Data\nfrom .decorators.computed import Computed\nfrom .decorators.lifecycle_hook import LifecycleHook\nfrom .decorators.method import Method\nfrom .decorators.render import Render\nfrom .decorators.mixins import Mixins\nfrom .decorators.template import Template\nfrom .decorators.directive import DirectiveHook\nfrom .decorators.extends import Extends\nfrom .decorators.components import Components\nfrom .decorators.state import State\nfrom .decorators.plugin import Plugin\nfrom .decorators.routes import Routes\n\n\ndef _merge_templates(sub):\n    def get_template_slots(cls):\n        template_slots = getattr(cls, \"template_slots\", {})\n        if isinstance(template_slots, str):\n            template_slots = {\"default\": template_slots}\n        return template_slots\n\n    base = sub.__bases__[0]\n    template_merging = hasattr(base, \"template\") and getattr(\n        sub, \"template_slots\", False\n    )\n    if template_merging:\n        base_template = _merge_templates(base)\n        base_slots = get_template_slots(base)\n        sub_slots = get_template_slots(sub)\n        slots = dict(tuple(base_slots.items()) + tuple(sub_slots.items()))\n        default = slots.get(\"default\")\n        return base_template.format(default, **slots)\n    return getattr(sub, \"template\", \"{}\")\n\n\nclass _TransformableType(type):\n    pass\n\n\nclass Transformable(metaclass=_TransformableType):\n    pass\n\n\nclass ClassAttributeDictTransformer:\n    \"\"\"\n    Takes all attributes of a class and creates a dictionary out of it.\n\n    For each attribute a decorator is retrieved.\n    The decorator is given by the `decorate` method wich should be overriden by sub-classes.\n    The decorator has a `__key__` attribute to determine where in the resulting dictionary\n    the value given by the `__value__` attribute should be stored.\n\n    The resulting dictionaries can be used to be passed to Vue as initializers.\n    \"\"\"\n\n    @classmethod\n    def transform(cls, transformable):\n        if not isinstance(transformable, _TransformableType):\n            return transformable\n\n        result = {}\n        for attribute_name, attribute_value in cls._iter_attributes(transformable):\n            decorator = cls.decorate(transformable, attribute_name, attribute_value)\n            if decorator is not None:\n                cls._inject_into(result, decorator.__key__, decorator.__value__)\n        return result\n\n    @classmethod\n    def decorate(cls, transformable, attribute_name, attribute_value):\n        if isinstance(attribute_value, VueDecorator):\n            return attribute_value\n        return None\n\n    @classmethod\n    def _iter_attributes(cls, transformable):\n        all_objects = set(dir(transformable))\n        all_objects.update(getattr(transformable, \"__annotations__\", {}).keys())\n        own_objects = (\n            all_objects - set(dir(cls._get_base(transformable))) - {\"__annotations__\"}\n        )\n        for attribute_name in own_objects:\n            yield attribute_name, getattr(transformable, attribute_name, None)\n\n    @classmethod\n    def _get_base(cls, transformable):\n        base = transformable.__bases__[0]\n        if base is Transformable:\n            return transformable\n        return cls._get_base(base)\n\n    @classmethod\n    def _inject_into(cls, destination, key, value):\n        keys = key.split(\".\")\n        value_key = keys.pop()\n\n        for key in keys:\n            destination = destination.setdefault(key, {})\n\n        if isinstance(destination.get(value_key), dict):\n            destination[value_key].update(value)\n        else:\n            destination[value_key] = value\n\n\nclass VueComponentTransformer(ClassAttributeDictTransformer):\n    \"\"\"\n    Takes a VueComponent-class and transforms it into a dictionary\n    which can be passed to e.g. window.Vue.new or window.Vue.component\n    \"\"\"\n\n    @classmethod\n    def transform(cls, transformable):\n        init_dict = super().transform(transformable)\n        _data = init_dict.get(\"data\", None)\n\n        if not _data:\n            return init_dict\n\n        def get_initialized_data(this):\n            initialized_data = {}\n            for name, date in _data.items():\n                initialized_data[name] = date(this) if callable(date) else date\n            return initialized_data\n\n        init_dict.update(data=get_initialized_data)\n        return init_dict\n\n    @classmethod\n    def decorate(cls, transformable, attribute_name, attribute_value):\n        decorated = super().decorate(transformable, attribute_name, attribute_value)\n        if decorated is not None:\n            return decorated\n\n        if attribute_name in LifecycleHook.mapping:\n            return LifecycleHook(attribute_name, attribute_value)\n        if attribute_name == \"template\":\n            return Template(_merge_templates(transformable))\n        if attribute_name == \"extends\" and attribute_value:\n            if not attribute_value:\n                return None\n            extends = (\n                transformable.__bases__[0]\n                if isinstance(attribute_value, bool)\n                else attribute_value\n            )\n            return Extends(VueComponentTransformer.transform(extends))\n        if attribute_name == \"mixins\":\n            return Mixins(\n                *(VueComponentTransformer.transform(m) for m in attribute_value)\n            )\n        if attribute_name == \"components\":\n            return Components(\n                *(VueComponentTransformer.transform(m) for m in attribute_value)\n            )\n        if attribute_name == \"render\":\n            return Render(attribute_value)\n        if callable(attribute_value):\n            return Method(attribute_value)\n        if attribute_name in getattr(transformable, \"__annotations__\", {}):\n            mixin = {\"required\": True}\n            if attribute_name in dir(transformable):\n                mixin = {\"default\": getattr(transformable, attribute_name)}\n\n            return Prop(\n                attribute_name,\n                transformable.__annotations__[attribute_name],\n                mixin,\n            )\n        return Data(attribute_name, attribute_value)\n\n\nclass VueDirectiveTransformer(ClassAttributeDictTransformer):\n    \"\"\"\n    Takes a VueDirective-class and transforms it into a dictionary\n    which can be passed to window.Vue.directive\n    \"\"\"\n\n    @classmethod\n    def transform(cls, transformable):\n        default = {transformable.name: {}}\n        dct = super().transform(transformable)\n        return dct.get(\"directives\", default).popitem()[1]\n\n    @classmethod\n    def decorate(cls, transformable, attribute_name, attribute_value):\n        if callable(attribute_value):\n            attribute_value = DirectiveHook(\n                attribute_value, hooks=(attribute_name,), name=transformable.name\n            )\n        return super().decorate(transformable, attribute_name, attribute_value)\n\n\nclass VueStoreTransformer(ClassAttributeDictTransformer):\n    \"\"\"\n    Takes a VueStore-class and transforms it into a dictionary\n    which can be passed to window.Vuex.Store.new\n    \"\"\"\n\n    @classmethod\n    def decorate(cls, transformable, attribute_name, attribute_value):\n        if attribute_name == \"plugins\":\n            return Plugin(attribute_value)\n        decorated = super().decorate(transformable, attribute_name, attribute_value)\n        if decorated is None:\n            return State(attribute_name, attribute_value)\n        return decorated\n\n\nclass VueRouterTransformer(ClassAttributeDictTransformer):\n    \"\"\"\n    Takes a VueStore-class and transforms it into a dictionary\n    which can be passed to window.VueRouter\n    \"\"\"\n\n    @classmethod\n    def decorate(cls, transformable, attribute_name, attribute_value):\n        if attribute_name == \"routes\":\n            return Routes(attribute_value)\n        return super().decorate(transformable, attribute_name, attribute_value)\n"
  },
  {
    "path": "vue/utils.py",
    "content": "from browser import window, load\n\nCACHE = {}\n\n\ndef js_load(path):\n    if path in CACHE:\n        return CACHE[path]\n    before = dir(window)\n    load(path)\n    after = dir(window)\n    diff = set(after) - set(before)\n    mods = {module: getattr(window, module) for module in diff if \"$\" not in module}\n    if len(mods) == 0:\n        mods = None\n    elif len(mods) == 1:\n        mods = mods.popitem()[1]\n    CACHE[path] = mods\n    return mods\n\n\ndef js_lib(name):\n    attr = getattr(window, name)\n    if dir(attr) == [\"default\"]:\n        return attr.default\n    return attr\n"
  },
  {
    "path": "vue/vue.py",
    "content": "from browser import window\nfrom .transformers import (\n    VueComponentTransformer,\n    Transformable,\n    VueDirectiveTransformer,\n)\nfrom .bridge import Object\nfrom .decorators.directive import DirectiveHook\nfrom .decorators.filters import Filter\n\n\nclass Vue:\n    @staticmethod\n    def directive(name, directive=None):\n        if directive is None and isinstance(name, str):\n            return window.Vue.directive(name)\n\n        if directive is None:\n            directive = name\n            name = directive.__name__.lower()\n\n        if not isinstance(directive, type):\n\n            class FunctionDirective(VueDirective):\n                d = DirectiveHook(directive)\n\n            directive = FunctionDirective\n\n        window.Vue.directive(name, VueDirectiveTransformer.transform(directive))\n\n    @staticmethod\n    def filter(method_or_name, method=None):\n        if not method:\n            method = method_or_name\n            name = method_or_name.__name__\n        else:\n            method = method\n            name = method_or_name\n        flt = Filter(method, name)\n        window.Vue.filter(flt.name, flt.__value__)\n\n    @staticmethod\n    def mixin(mixin):\n        window.Vue.mixin(VueComponentTransformer.transform(mixin))\n\n    @staticmethod\n    def use(plugin, *args, **kwargs):\n        window.Vue.use(plugin, *args, kwargs)\n\n    @staticmethod\n    def component(component_or_name, component=None):\n        if isinstance(component_or_name, str) and component is None:\n            return window.Vue.component(component_or_name)\n        if component is not None:\n            name = component_or_name\n        else:\n            component = component_or_name\n            name = component.__name__\n        window.Vue.component(name, VueComponentTransformer.transform(component))\n\n\nclass VueComponent(Transformable):\n    @classmethod\n    def init_dict(cls):\n        return VueComponentTransformer.transform(cls)\n\n    def __new__(cls, el, **kwargs):\n        init_dict = cls.init_dict()\n        init_dict.update(el=el)\n        for key, value in kwargs.items():\n            if key == \"props_data\":\n                key = \"propsData\"\n            init_dict.update({key: value})\n        return Object.from_js(window.Vue.new(Object.to_js(init_dict)))\n\n    @classmethod\n    def register(cls, name=None):\n        if name:\n            Vue.component(name, cls)\n        else:\n            Vue.component(cls)\n\n\nclass VueMixin(Transformable):\n    pass\n\n\nclass VueDirective(Transformable):\n    name = None\n\n\nclass VuePlugin:\n    @staticmethod\n    def install(*args, **kwargs):\n        raise NotImplementedError()\n"
  },
  {
    "path": "vuecli/__init__.py",
    "content": ""
  },
  {
    "path": "vuecli/cli.py",
    "content": "import sys\nimport argparse\nfrom tempfile import TemporaryDirectory as TempDir\nfrom pathlib import Path\n\nfrom vuecli.provider import RegisteredProvider\nfrom vuecli.provider.static import Static as StaticProvider\nfrom vue import __version__\n\n\ndef deploy(provider_class, arguments):\n    if provider_class is None:\n        print(\n            f\"'pip install vuepy[{arguments.deploy}]' to use this provider\",\n            file=sys.stderr,\n        )\n        sys.exit(1)\n\n    deploy_arguments = {\n        name.strip(\"-\"): getattr(arguments, name.strip(\"-\"), False)\n        for name in provider_class.Arguments\n    }\n\n    provider = provider_class(arguments.src)\n    provider.setup()\n    provider.deploy(**deploy_arguments)\n\n\ndef package(destination, app):\n    with TempDir() as apptemp, TempDir() as deploytemp:\n        appdir = Path(app if app else apptemp)\n        deploydir = Path(deploytemp)\n\n        provider = StaticProvider(appdir)\n        provider.setup()\n        provider.deploy(deploydir, package=True)\n\n        Path(destination, \"vuepy.js\").write_text(\n            (deploydir / \"vuepy.js\").read_text(encoding=\"utf-8\")\n        )\n\n\ndef main():\n    cli = argparse.ArgumentParser(description=\"vue.py command line interface\")\n    cli.add_argument(\"--version\", action=\"version\", version=f\"vue.py {__version__}\")\n\n    command = cli.add_subparsers(title=\"commands\", dest=\"cmd\")\n\n    deploy_cmd = command.add_parser(\"deploy\", help=\"deploy application\")\n    provider_cmd = deploy_cmd.add_subparsers(help=\"Provider\")\n    for name, provider in RegisteredProvider.items():\n        sp = provider_cmd.add_parser(name)\n        sp.set_defaults(deploy=name)\n        if provider is not None:\n            for arg_name, config in provider.Arguments.items():\n                if isinstance(config, str):\n                    config = {\"help\": config}\n                sp.add_argument(arg_name, **config)\n    deploy_cmd.add_argument(\n        \"--src\",\n        default=\".\",\n        nargs=\"?\",\n        help=\"Path of the application to deploy (default: '.')\",\n    )\n\n    package_cmd = command.add_parser(\"package\", help=\"create vuepy.js\")\n    package_cmd.add_argument(\n        \"destination\", default=\".\", nargs=\"?\", help=\"(default: current directory)\"\n    )\n    package_cmd.add_argument(\n        \"--app\", nargs=\"?\", default=False, help=\"include application in package\"\n    )\n\n    args = cli.parse_args()\n    if args.cmd == \"deploy\":\n        deploy(RegisteredProvider[args.deploy], args)\n    elif args.cmd == \"package\":\n        package(args.destination, \".\" if args.app is None else args.app)\n    else:\n        cli.print_help()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "vuecli/index.html",
    "content": "<html>\n<head>\n  {% for script in scripts.values() %}\n    <script src=\"{{ script }}\"></script>\n  {% endfor %}\n\n  {% for stylesheet in stylesheets %}\n    <link rel=\"stylesheet\" media=\"screen\" href=\"{{ stylesheet }}\" />\n  {% endfor %}\n</head>\n<body onload=\"brython({{ brython_args }});\">\n  {% for id, content in templates.items() %}\n    <script type=\"x-template\" id=\"{{ id }}\">\n      {{ content }}\n    </script>\n  {% endfor %}\n\n  <div id=\"app\">\n    <img style=\"display: block; margin: auto;\" src=\"loading.gif\" height=\"50%\" />\n  </div>\n  <script type=\"text/python\" src=\"__entry_point__.py\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "vuecli/provider/__init__.py",
    "content": "import pkg_resources as _pkgres\n\n\ndef _load(ep):\n    try:\n        return ep.load()\n    except ModuleNotFoundError:\n        return None\n\n\nRegisteredProvider = {\n    entry_point.name: _load(entry_point)\n    for entry_point in _pkgres.iter_entry_points(\"vuecli.provider\")\n}\n"
  },
  {
    "path": "vuecli/provider/flask.py",
    "content": "import os\nfrom pathlib import Path\n\nfrom flask import Flask as FlaskApp, send_file, abort\n\nfrom .provider import Provider\n\n\nclass Flask(Provider):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.app = FlaskApp(__name__)\n\n    def content(self, endpoint, route, content):\n        self.app.add_url_rule(route, endpoint, content)\n\n    def directory(self, endpoint, route, path, deep=False):\n        def view_func(filename):\n            full_path = Path(path, filename)\n            if not full_path.exists():\n                abort(404)\n            return send_file(str(full_path.absolute()))\n\n        flask_route = os.path.join(\n            route, \"<{}filename>\".format(\"path:\" if deep else \"\")\n        )\n        self.app.add_url_rule(flask_route, endpoint, view_func)\n\n    def deploy(self):\n        flask_config = self.config.get(\"provider\", {}).get(\"flask\", {})\n        host = flask_config.pop(\"HOST\", None)\n        port = flask_config.pop(\"PORT\", None)\n        for key, value in flask_config.items():\n            self.app.config[key] = value\n        self.app.run(host=host, port=port)\n"
  },
  {
    "path": "vuecli/provider/provider.py",
    "content": "from pkg_resources import resource_filename, resource_string\nfrom functools import partial\nfrom pathlib import Path\n\nimport yaml\nfrom jinja2 import Template\n\n\nVuePath = resource_filename(\"vue\", \"\")\nIndexTemplate = resource_string(\"vuecli\", \"index.html\")\nStaticContents = {\n    \"/loading.gif\": resource_string(\"vuecli\", \"loading.gif\"),\n    \"/vuepy.js\": b\"\\n\".join(\n        [\n            resource_string(\"brython\", \"data/brython.js\"),\n            resource_string(\"brython\", \"data/brython_stdlib.js\"),\n        ]\n    ),\n    \"/vue.js\": resource_string(\"vuecli\", \"js/vue.js\"),\n    \"/vuex.js\": resource_string(\"vuecli\", \"js/vuex.js\"),\n    \"/vue-router.js\": resource_string(\"vuecli\", \"js/vue-router.js\"),\n}\n\n\nclass Provider:\n    Arguments = {}\n\n    def __init__(self, path=None):\n        self.path = Path(path if path else \".\")\n        self.config = self.load_config()\n\n    @staticmethod\n    def _normalize_config(config):\n        default_scripts = {\n            \"vuepy\": \"vuepy.js\",\n            \"vue\": \"vue.js\",\n            \"vuex\": \"vuex.js\",\n            \"vue-router\": \"vue-router.js\",\n        }\n        scripts = {\"vuepy\": True, \"vue\": True}\n        custom_scripts = config.get(\"scripts\", {})\n        if isinstance(custom_scripts, list):\n            custom_scripts = {k: k for k in custom_scripts}\n        scripts.update(custom_scripts)\n        config[\"scripts\"] = {\n            k: default_scripts[k] if v is True else v for k, v in scripts.items() if v\n        }\n\n    def load_config(self):\n        config_file = Path(self.path, \"vuepy.yml\")\n        config = {}\n        if config_file.exists():\n            with open(config_file, \"r\") as fh:\n                config = yaml.safe_load(fh.read()) or config\n        self._normalize_config(config)\n        return config\n\n    def render_index(self):\n        brython_args = self.config.get(\"brython_args\", {})\n        if brython_args:\n            joined = \", \".join(f\"{k}: {v}\" for k, v in brython_args.items())\n            brython_args = f\"{{ {joined} }}\"\n        else:\n            brython_args = \"\"\n\n        return Template(IndexTemplate.decode(\"utf-8\")).render(\n            stylesheets=self.config.get(\"stylesheets\", []),\n            scripts=self.config.get(\"scripts\", {}),\n            templates={\n                id_: Path(self.path, template).read_text(\"utf-8\")\n                for id_, template in self.config.get(\"templates\", {}).items()\n            },\n            brython_args=brython_args,\n        )\n\n    def setup(self):\n        self.directory(\"application\", \"/\", Path(self.path), deep=True)\n        self.directory(\"vuepy\", \"/vue\", VuePath, deep=True)\n\n        entry_point = self.config.get(\"entry_point\", \"app\")\n        self.content(\n            \"entry_point\", \"/__entry_point__.py\", lambda: f\"import {entry_point}\\n\"\n        )\n        self.content(\"index\", \"/\", lambda: self.render_index())\n        for route in StaticContents:\n            self.content(route, route, partial(StaticContents.get, route))\n\n    def content(self, endpoint, route, content):\n        raise NotImplementedError()\n\n    def directory(self, endpoint, route, path, deep=False):\n        raise NotImplementedError()\n\n    def deploy(self, **kwargs):\n        raise NotImplementedError()\n"
  },
  {
    "path": "vuecli/provider/static.py",
    "content": "import os\nimport sys\nimport shutil\nfrom tempfile import TemporaryDirectory as TempDir\nimport subprocess\nfrom pathlib import Path\n\nfrom .provider import Provider\n\n\ndef copytree(src, dst, deep=True):\n    if not dst.exists():\n        dst.mkdir()\n\n    for item in os.listdir(src):\n        s = Path(src, item)\n        d = Path(dst, item)\n        if s.is_dir() and deep:\n            d.mkdir()\n            copytree(s, d)\n        elif s.is_file():\n            shutil.copy2(str(s), str(d))\n\n\nclass Static(Provider):\n    Arguments = {\n        \"destination\": \"Path where the application should be deployed to\",\n        \"--package\": {\"action\": \"store_true\", \"help\": \"adds application to vuepy.js\"},\n    }\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._tempdir = TempDir()\n\n    @property\n    def temppath(self):\n        return self._tempdir.name\n\n    def content(self, endpoint, route, content):\n        path = self.temppath / Path(route).relative_to(\"/\")\n        if path.is_dir():\n            path = path / \"index.html\"\n\n        content = content()\n        mode = \"w+\" if isinstance(content, str) else \"wb+\"\n        with open(path, mode) as dest_file:\n            dest_file.write(content)\n\n    def directory(self, endpoint, route, path, deep=False):\n        dest = self.temppath / Path(route).relative_to(\"/\")\n        copytree(Path(path), dest, deep=deep)\n\n    def deploy(self, destination, package=False):\n        try:\n            rel_depolypath = (\n                Path(destination).absolute().relative_to(Path(self.path).absolute())\n            )\n        except ValueError:\n            pass\n        else:\n            shutil.rmtree(str(Path(self.temppath) / rel_depolypath), ignore_errors=True)\n\n        if package:\n            self._create_package()\n\n        shutil.rmtree(destination, ignore_errors=True)\n        shutil.copytree(self.temppath, Path(destination))\n        self._tempdir.cleanup()\n\n    def _create_package(self):\n        self._brython(\"--make_package\", \"app\")\n        Path(self.temppath, \"vuepy.js\").write_text(\n            Path(self.temppath, \"vuepy.js\").read_text(encoding=\"utf-8\")\n            + \"\\n\"\n            + Path(self.temppath, \"app.brython.js\").read_text(encoding=\"utf-8\")\n        )\n\n    def _brython(self, *args):\n        completed_process = subprocess.run(\n            [sys.executable, \"-m\", \"brython\", *args],\n            cwd=str(self.temppath),\n            stdout=subprocess.PIPE,\n        )\n        if completed_process.returncode:\n            raise RuntimeError(completed_process.returncode)\n"
  }
]