Repository: gerardcl/renfe-cli (language: python)

Issue:
Refactor keeping functionalities but using Rust as backend
**Is your feature request related to a problem? Please describe.**
We keep seeing a lot of inconsistencies and hard management when being under the python context of this project.
We have gained a lot of motivation with [rodalies-cli](https://github.com/gerardcl/rodalies-cli).

**Describe the solution you'd like**
Refactor the whole python project to use Rust as backend but keep being a python package (library and script).

**Describe alternatives you've considered**
Fight with all inconsistencies within the dependencies required on the current python project. No go.

**Additional context**
We will use [pyo3](https://github.com/PyO3/pyo3) for Rust into python bindings and manage the project with [maturin](https://github.com/PyO3/maturin).

Soporte versiones Chrome superiores a 114
**Describe the bug**
Debido a un cambio donde se almacenan los drivers de Chrome, la librería solo es compatible con Chrome 114 o inferior.

**To Reproduce**
Instalar la versión estable de Chrome a 2023-09-04 (116.0.5845) y ejecutar la aplicación. El error es:

```
ERROR:root:There is no such driver by url https://chromedriver.storage.googleapis.com/LATEST_RELEASE_116.0.5845
```

**Environment (please complete the following information):**
 - OS: Ubuntu 
 - Version: 22.04

**Additional context**
Parece que los desarrolladores de Chrome cambiaron la localización de los drivers. Más contexto  aquí: https://chromedriver.chromium.org/getting-started
Actualizar Selenium y retocar ligeramente el código parece una posible solución:
https://stackoverflow.com/questions/76724939/there-is-no-such-driver-by-url-https-chromedriver-storage-googleapis-com-lates

En mi caso, he probado a descargar el driver manualmente de https://googlechromelabs.github.io/chrome-for-testing/ y añadirlo al `PATH`, sin éxito. Si ha funcionado hacer downgrade de la versión de Chrome a la 114: 
- https://makandracards.com/makandra/486433-how-to-downgrade-google-chrome-in-ubuntu
- https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable?id=202706&page=2




Discussion / hints:
this will fix #173 and #174
hola @albertotb ! tengo pensado hacer migración completa a Rust de este CLI en algun momento, por ahora no seguiré el mantenimiento con python de forma estable, mi expectativa por ahora es que todos vayamos aportando arreglos al código, adelante con propuestas/PRs, los revisaré! saludos!
Ok, a ver si saco algo de tiempo para intentar al menos que funciones. Este cambio parece sencillo, esperemos que solucionar #174 no requiera muchos cambios

Fix patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 3afc0a4..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-version: 2
-updates:
-- package-ecosystem: pip
-  directory: "/"
-  schedule:
-    interval: weekly
-    timezone: Europe/Berlin
-  open-pull-requests-limit: 10
-  allow:
-  - dependency-type: direct
-  - dependency-type: indirect
-  ignore:
-  - dependency-name: setuptools
-    versions:
-    - 52.0.0
-    - 53.0.0
-    - 54.0.0
-    - 54.1.1
-    - 54.2.0
-  - dependency-name: pandas
-    versions:
-    - 1.2.1
-    - 1.2.2
-    - 1.2.3
-  - dependency-name: numpy
-    versions:
-    - 1.19.5
-    - 1.20.0
-    - 1.20.1
-  - dependency-name: pytz
-    versions:
-    - "2020.5"
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml
new file mode 100644
index 0000000..9a453c9
--- /dev/null
+++ b/.github/workflows/CICD.yml
@@ -0,0 +1,172 @@
+# This file is autogenerated by maturin v1.3.2
+# To update, run
+#
+#    maturin generate-ci github
+#
+name: CICD
+
+on:
+  push:
+    branches:
+      - 'main'
+    tags:
+      - '*'
+  pull_request:
+    branches: [ master ]
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+
+  test:
+    name: Tests
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - uses: actions-rs/cargo@v1
+        with:
+          command: test
+
+  fmt:
+    name: Rustfmt
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - run: rustup component add rustfmt
+      - uses: actions-rs/cargo@v1
+        with:
+          command: fmt
+          args: --all -- --check
+
+  clippy:
+    name: Build Clippy
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        rust:
+          - stable
+          - 1.72.0
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: ${{ matrix.rust }}
+          override: true
+      - run: rustup component add clippy
+      - uses: actions-rs/cargo@v1
+        with:
+          command: clippy
+          args: -- -D warnings
+
+  linux:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        target: [x86_64, x86, armv7, s390x, ppc64le]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+      - name: Build wheels
+        uses: PyO3/maturin-action@v1
+        with:
+          target: ${{ matrix.target }}
+          args: --release --out dist --find-interpreter
+          sccache: 'true'
+          manylinux: auto
+      - name: Upload wheels
+        uses: actions/upload-artifact@v3
+        with:
+          name: wheels
+          path: dist
+
+  windows:
+    runs-on: windows-latest
+    strategy:
+      matrix:
+        target: [x64, x86]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+          architecture: ${{ matrix.target }}
+      - name: Build wheels
+        uses: PyO3/maturin-action@v1
+        with:
+          target: ${{ matrix.target }}
+          args: --release --out dist --find-interpreter
+          sccache: 'true'
+      - name: Upload wheels
+        uses: actions/upload-artifact@v3
+        with:
+          name: wheels
+          path: dist
+
+  macos:
+    runs-on: macos-latest
+    strategy:
+      matrix:
+        target: [x86_64, aarch64]
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+      - name: Build wheels
+        uses: PyO3/maturin-action@v1
+        with:
+          target: ${{ matrix.target }}
+          args: --release --out dist --find-interpreter
+          sccache: 'true'
+      - name: Upload wheels
+        uses: actions/upload-artifact@v3
+        with:
+          name: wheels
+          path: dist
+
+  sdist:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Build sdist
+        uses: PyO3/maturin-action@v1
+        with:
+          command: sdist
+          args: --out dist
+      - name: Upload sdist
+        uses: actions/upload-artifact@v3
+        with:
+          name: wheels
+          path: dist
+
+  release:
+    name: Release
+    runs-on: ubuntu-latest
+    if: "startsWith(github.ref, 'refs/tags/')"
+    needs: [linux, windows, macos, sdist]
+    steps:
+      - uses: actions/download-artifact@v3
+        with:
+          name: wheels
+      - name: Publish to PyPI
+        uses: PyO3/maturin-action@v1
+        env:
+          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+        with:
+          command: upload
+          args: --non-interactive --skip-existing *
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index de9e785..0000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL Analysis"
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [ master ]
-  schedule:
-    - cron: '23 12 * * 2'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-    permissions:
-      actions: read
-      contents: read
-      security-events: write
-
-    strategy:
-      fail-fast: false
-      matrix:
-        language: [ 'python' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
-        # Learn more:
-        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file.
-        # Prefix the list here with "+" to use these queries and those in the config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
deleted file mode 100644
index f0dd78b..0000000
--- a/.github/workflows/python-package.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-
-name: "Python Build"
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-  schedule:
-    - cron: '23 22 * * 2'
-
-jobs:
-  build:
-
-    runs-on: ${{ matrix.os }}
-    strategy:
-      fail-fast: false
-      matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.8, 3.9]
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        python -m pip install flake8 pytest pytest-cov
-        python setup.py install
-    - name: Lint with flake8
-      run: |
-        # stop the build if there are Python syntax errors or undefined names
-        flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 src --count --exit-zero --max-complexity=11 --max-line-length=127 --statistics
-    - name: Test with pytest
-      run: |
-        python -m pytest --junitxml=tests.xml -o junit_family=xunit2 --cov-report term-missing --cov-report xml --cov=src -o testpaths=tests
-      env:
-        PYTHONPATH: "src"
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
deleted file mode 100644
index 639fcf2..0000000
--- a/.github/workflows/python-publish.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# This workflow will upload a Python Package using Twine when a release is created
-# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
-
-name: Upload Python Package
-
-on:
-  release:
-    types: [created]
-
-jobs:
-  deploy:
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python
-      uses: actions/setup-python@v2
-      with:
-        python-version: '3.x'
-    - name: Install dependencies
-      run: |
-        sudo apt-get install pandoc
-        python -m pip install --upgrade pip 
-        pip install setuptools wheel twine --upgrade
-        pip install --upgrade pandoc pypandoc
-    - name: Build and publish
-      env:
-        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
-        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
-      run: |
-        python setup.py sdist bdist_wheel
-        twine upload dist/*
diff --git a/.gitignore b/.gitignore
index cff4afd..c8f0442 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,133 +1,72 @@
+/target
+
 # Byte-compiled / optimized / DLL files
 __pycache__/
+.pytest_cache/
 *.py[cod]
-*$py.class
 
 # C extensions
 *.so
 
 # Distribution / packaging
 .Python
+.venv/
+env/
+bin/
 build/
 develop-eggs/
 dist/
-downloads/
 eggs/
-.eggs/
 lib/
 lib64/
 parts/
 sdist/
 var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
+include/
+man/
+venv/
 *.egg-info/
 .installed.cfg
 *.egg
-MANIFEST
-
-# Compiled python modules.
-*.pyc
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
 
 # Installer logs
 pip-log.txt
 pip-delete-this-directory.txt
+pip-selfcheck.json
 
 # Unit test / coverage reports
 htmlcov/
 .tox/
-.nox/
 .coverage
-.coverage.*
 .cache
 nosetests.xml
 coverage.xml
-*.cover
-.hypothesis/
-.pytest_cache/
 
 # Translations
 *.mo
-*.pot
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
 
 # Django stuff:
 *.log
-local_settings.py
-db.sqlite3
-
-# Flask stuff:
-instance/
-.webassets-cache
+*.pot
 
-# Scrapy stuff:
-.scrapy
+.DS_Store
 
 # Sphinx documentation
 docs/_build/
 
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
+# PyCharm
+.idea/
 
-# IPython
-profile_default/
-ipython_config.py
+# VSCode
+.vscode/
 
-# pyenv
+# Pyenv
 .python-version
-
-# pipenv
-#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-#   However, in case of collaboration, if having platform-specific dependencies or dependencies
-#   having no cross-platform support, pipenv may install dependencies that don’t work, or not
-#   install all needed dependencies.
-#Pipfile.lock
-
-# celery beat schedule file
-celerybeat-schedule
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env
-venv
-ENV
-env.bak
-venv.bak
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# sonar
-.sonar_lock
-report-task.txt
-
-# IDE
-.vscode
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index f15c5dd..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,1 +0,0 @@
-Gerard Castillo Lasheras <gerardcl@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6345b0..adc2deb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## v4.0.0 (2023-11-25)
+
+* Refactor to Rust [#175](https://github.com/gerardcl/renfe-cli/issues/175)
 
 ## v3.3.0 (2022-02-19)
 
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..fcea2ea
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2142 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aes"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_colours"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80"
+dependencies = [
+ "rgb",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "auto_generate_cdp"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af08ed49930c50104b2f1699d257e5053fb1809e370647bde9c58b31d65d417"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "ureq",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "console"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+dependencies = [
+ "bitflags 2.4.1",
+ "crossterm_winapi",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "phf 0.11.2",
+ "smallvec",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
+dependencies = [
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+
+[[package]]
+name = "deranged"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
+dependencies = [
+ "derive_builder_core",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "directories"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "dtoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "ego-tree"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591"
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "errno"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "exr"
+version = "1.71.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
+dependencies = [
+ "bit_field",
+ "flume",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flume"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "half"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "headless_chrome"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d77dd062851072678f048478ecd7f10f14e166985ae27e9338da3d9c8722607"
+dependencies = [
+ "anyhow",
+ "auto_generate_cdp",
+ "base64",
+ "derive_builder",
+ "directories",
+ "log",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror",
+ "tungstenite",
+ "ureq",
+ "url",
+ "walkdir",
+ "which",
+ "winreg",
+ "zip",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "http"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "image"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-rational",
+ "num-traits",
+ "png",
+ "qoi",
+ "tiff",
+]
+
+[[package]]
+name = "indoc"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "markup5ever"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
+dependencies = [
+ "log",
+ "phf 0.10.1",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.11.2",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared 0.11.2",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
+dependencies = [
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "png"
+version = "0.17.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pyo3"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "parking_lot",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5"
+dependencies = [
+ "once_cell",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "renfe-cli"
+version = "4.0.0"
+dependencies = [
+ "getopts",
+ "headless_chrome",
+ "image",
+ "pyo3",
+ "scraper",
+ "ureq",
+ "viuer",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "scraper"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1"
+dependencies = [
+ "ahash",
+ "cssparser",
+ "ego-tree",
+ "getopts",
+ "html5ever",
+ "once_cell",
+ "selectors",
+ "tendril",
+]
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "selectors"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
+dependencies = [
+ "bitflags 2.4.1",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "new_debug_unreachable",
+ "phf 0.10.1",
+ "phf_codegen",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "socks"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
+dependencies = [
+ "byteorder",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "string_cache"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared 0.10.0",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
+
+[[package]]
+name = "tempfile"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "tiff"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
+[[package]]
+name = "time"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+dependencies = [
+ "deranged",
+ "powerfmt",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unindent"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7830e33f6e25723d41a63f77e434159dad02919f18f55a512b5f16f3b1d77138"
+dependencies = [
+ "base64",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-webpki",
+ "socks",
+ "url",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "viuer"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec2ede5c8814363f92f862892dfe71a266f6816b649ca435aed1ff5e2cf3454e"
+dependencies = [
+ "ansi_colours",
+ "base64",
+ "console",
+ "crossterm",
+ "image",
+ "lazy_static",
+ "tempfile",
+ "termcolor",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "webpki-roots"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
+
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
+[[package]]
+name = "which"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "winreg"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "zip"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
+dependencies = [
+ "aes",
+ "byteorder",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..99cabb7
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "renfe-cli"
+version = "4.0.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "renfe_cli"
+crate-type = ["cdylib"]
+
+[dependencies]
+pyo3 = { version = "0.20", features = ["abi3-py37"] }
+headless_chrome = { version = "1.0", features = ["fetch"] }
+image = "0.24"
+viuer = "0.7"
+scraper = "0.18"
+ureq = "2.9"
+getopts = "0.2"
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index b518c2f..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,1 +0,0 @@
-include AUTHORS CHANGELOG.md LICENSE README.md
diff --git a/README.md b/README.md
index 99d3a50..347aa04 100644
--- a/README.md
+++ b/README.md
@@ -1,164 +1,106 @@
-[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gerardcl_renfe-cli&metric=alert_status)](https://sonarcloud.io/dashboard?id=gerardcl_renfe-cli) [![Python Build](https://github.com/gerardcl/renfe-cli/actions/workflows/python-package.yml/badge.svg)](https://github.com/gerardcl/renfe-cli/actions/workflows/python-package.yml) [![CodeQL Analysis](https://github.com/gerardcl/renfe-cli/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/gerardcl/renfe-cli/actions/workflows/codeql-analysis.yml)
+[![CICD](https://github.com/gerardcl/renfe-cli/actions/workflows/CICD.yml/badge.svg)](https://github.com/gerardcl/renfe-cli/actions/workflows/CICD.yml)
 
-# RENFE TIMETABLES CLI
+# Renfe Timetables CLI
 
-**NOTE** since I am using more often Rodalies trains I have created [rodalies-cli](https://github.com/gerardcl/rodalies-cli). I hope you like it!
+Get faster Renfe trains timetables in your terminal, with Python3.7+ support.
+No longer need to open the browser! Just keep using your terminal 😀
 
-Get faster RENFE Spanish trains timetables in your terminal, with Python3.8+.
-No longer need to open the browser! Just keep using your terminal :)
+`renfe-cli` is written in [Rust](https://www.rust-lang.org/) (since v4.0.0) and published to [pypi.org](https://pypi.org/project/renfe-cli/) as a Python package (CLI and library).
 
 See the [changelog](https://github.com/gerardcl/renfe-cli/blob/master/CHANGELOG.md).
 
+**NOTE** since I am more often using Rodalies trains I have created [rodalies-cli](https://github.com/gerardcl/rodalies-cli). I hope you like it too!
+
 ## Installation
 
 Install Python CLI package [renfe-cli](https://pypi.org/project/renfe-cli/)
 
 ```bash
-$ pip install renfe-cli --upgrade
+pip install renfe-cli --upgrade
 ```
 
-## Usage
+## Usage (CLI)
 
-This CLI behaves as a person/bot going through the official renfe.com site, using selenium firefox or chrome browsers.
+This CLI behaves as a person/bot going through the official renfe.com search site, using headless chrome browser.
+If the headless chrome browser is not found it will be downloaded.
 
 The navigation through the site happens in the following steps:
 
 1. Writes down and selects origin station
 2. Writes down and selects destination station
-3. Selects the day to search schedules for (by clicking on next day button as much as provided; defaults to 0, therefore today)
-4. Clicks on search button (waits for results to load 3 seconds by default)
-5. Gets and massages data found on the provided timetable
+3. Writes down and selects the day to search for
+4. Writes down and selects the month to search for
+5. Writes down and selects the year to search for
+6. Clicks on search button
+7. Parses the HTML data and prints the timetable
 
 ```bash
 $ renfe-cli -h
 Usage: renfe-cli [options]
 
 Options:
-  -h, --help            show this help message and exit
-  -o ORIGIN, --origin=ORIGIN
-                        from/origin ID of the train station. Use flag '-s
-                        <possible station name>' in order to search for IDs
-  -t TO, --to=TO        to/destination ID of the train station. Use flag '-s
-                        <possible station name>' in order to search for IDs
-  -d DAYS, --days=DAYS  number of days from today to get the timetable
-                        (default: 0 - today)
-  -b BROWSER, --browser=BROWSER
-                        possible browsers are "firefox" and "chrome" (default:
-                        firefox)
-  -s SEARCH, --search=SEARCH
-                        you need to get the stations IDs, searching by names;
-                        in order to apply right inputs for origins and/or
-                        destinations
-  -e SEARCH_TIMEOUT, --search-timeout=SEARCH_TIMEOUT
-                        search timeout in seconds (default to 3 seconds)
-  -l LOGGING_LEVEL, --logging-level=LOGGING_LEVEL
-                        logging level defaults to warning and possible values
-                        are: debug, info, warning, error and critical
-  -f LOGGING_FILE, --logging-file=LOGGING_FILE
-                        logging file name is required if you want to submit an
-                        issue with more information
-  -u, --update-config   change your origin and destination stations to
-                        defaults when loading this flag
-```
-
-### **Searching for IDs of train stations**
-
- When using search functionality, it will provide you with the IDs (to use as an origin or destiation train station) of the stations that are similar to the input text to search. Example:
-
-```bash
-$ renfe-cli -s sil
-Today is: 2021-05-22
-Searching stations like: sil
-SILLA: 64200
-SILS: 79202
-SAN ESTEVO DO SIL: 22003
-SAN PEDRO DO SIL: 22004
-LE PEAGE DE ROUSSILLON: 00245
-SILLE LE GUILLAUME: 00818
+    -f ORIGIN           Set From origin station
+    -t DESTINATION      Set To destination station
+    -d, --day DAY       Set Day to search timetable for
+    -m, --month MONTH   Set Month to search timetable for
+    -y, --year YEAR     Set Year to search timetable for
+    -h, --help          print this help menu
 ```
 
 ### **Getting the timetable**
 
-Timetable defaults to `today`, from `Sils` (ID is `79202`) to `Barcelona Passeig de Gràcia` (ID is `71802`):
-
-```bash
-$ renfe-cli
-Today is: 2021-05-22
-Searching timetable for date: 2021-5-22
-From SILS to BARCELONA-PASSEIG DE GRACIA
-Be patient, navigating through renfe site now...
-=======================TIMETABLE======================
- Train      | Departure  | Arrival    | Duration
-------------------------------------------------------
- MD         | 06.45      | 07.49      | 1 h. 4 min.
-------------------------------------------------------
- MD         | 07.30      | 08.34      | 1 h. 4 min.
-------------------------------------------------------
- REGIONAL   | 08.29      | 09.34      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 09.05      | 10.04      | 59 min.
-------------------------------------------------------
- REGIONAL   | 09.59      | 11.04      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 10.35      | 11.34      | 59 min.
-------------------------------------------------------
- REGIONAL   | 11.59      | 13.04      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 12.35      | 13.34      | 59 min.
-------------------------------------------------------
- REGIONAL   | 13.59      | 15.04      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 14.35      | 15.34      | 59 min.
-------------------------------------------------------
- REGIONAL   | 15.59      | 17.04      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 16.35      | 17.34      | 59 min.
-------------------------------------------------------
- REGIONAL   | 17.59      | 19.04      | 1 h. 5 min.
-------------------------------------------------------
- MD         | 18.35      | 19.34      | 59 min.
-------------------------------------------------------
- MD         | 19.35      | 20.34      | 59 min.
-------------------------------------------------------
- REGIONAL   | 21.09      | 22.14      | 1 h. 5 min.
-------------------------------------------------------
- REGIONAL   | 21.42      | 22.46      | 1 h. 4 min.
-======================================================
-```
-
-Which would be the same as:
-
-```bash
-$ renfe-cli -d 0 -o 79202 -t BARCE
-```
-
-### **Changing default origin and/or destination stations**
-In order to change default timetable stations you just need to add `-u` flag in the CLI, and next time you won't need to add the used `-o` and `-t` params:
+In this new major release there is still no interactive mode nor defaults; one must provide all inputs, like:
 
 ```bash
-$ renfe-cli -o MADRI -t BARCE -u
+$ renfe-cli -f Tarr -t Mad -d 27 -m 11 -y 2023
+loading headless chrome browser
+navigating to renfe timetable search page
+waiting for search page
+adding origin station
+adding destination station
+adding day
+adding month
+adding year
+searching timetable
+got timetable page
+loading timetable
+=========================TIMETABLE=========================
+Train        |   Departure  |   Arrival    | Duration
+-----------------------------------------------------------
+AVE          |    06.25     |    09.10     | 2 h. 45 min.
+-----------------------------------------------------------
+LD-AVE       |    08.22     |    15.35     | 7 h. 13 min.
+-----------------------------------------------------------
+AVE          |    08.34     |    11.12     | 2 h. 38 min.
+-----------------------------------------------------------
+REG.EXP.     |    10.11     |    18.09     | 7 h. 58 min.
+-----------------------------------------------------------
+AVLO         |    10.34     |    13.17     | 2 h. 43 min.
+-----------------------------------------------------------
+LD-AVE       |    10.51     |    15.35     | 4 h. 44 min.
+-----------------------------------------------------------
+AVE          |    12.34     |    15.12     | 2 h. 38 min.
+-----------------------------------------------------------
+AVE INT      |    13.22     |    15.45     | 2 h. 23 min.
+-----------------------------------------------------------
+AVE          |    14.34     |    17.12     | 2 h. 38 min.
+-----------------------------------------------------------
+AVE          |    16.34     |    19.12     | 2 h. 38 min.
+-----------------------------------------------------------
+AVE          |    18.34     |    21.12     | 2 h. 38 min.
+-----------------------------------------------------------
+AVE          |    19.14     |    21.45     | 2 h. 31 min.
+-----------------------------------------------------------
+AVE          |    20.34     |    23.12     | 2 h. 38 min.
+===========================================================
 ```
 
-If changing defaults, a file is created under user's home directory in file `~/.renfe_default_stations.json`
-
 ---
 
-## Issues
-
-If Renfe's website is changed or you find any issue or enhancements, please: [create an issue](https://github.com/gerardcl/renfe-cli/issues)
+## Usage (Library)
 
-## Installation alternatives (getting latest source code)
+TBD
 
-If you want to install latest source code:
+## Contribute or Report with Issues
 
-```bash
-$ pip install git+http://github.com/gerardcl/renfe-cli
-```
-
-or
-
-```bash
-$ git clone git://github.com/gerardcl/renfe-cli
-$ cd renfe-cli
-$ python setup.py install
-```
+If Renfe's website is changed or you find any issue to be fixed or nice enhancements to have, please: [create an issue](https://github.com/gerardcl/renfe-cli/issues).
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..c00d15d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,48 @@
+[build-system]
+requires = ["maturin>=1.3,<2.0"]
+build-backend = "maturin"
+
+[project]
+name = "renfe-cli"
+requires-python = ">=3.7"
+authors = [
+  {name = "Gerard Castillo Lasheras", email = "gerardcl@gmail.com"},
+]
+maintainers = [
+  {name = "Gerard Castillo Lasheras", email = "gerardcl@gmail.com"},
+]
+description = "Get faster Renfe Spanish Trains timetables in your terminal."
+readme = "README.md"
+license = {file = "LICENSE"}
+keywords = ["timetables", "trains", "renfe", "cli", "rust"]
+classifiers = [
+    "Programming Language :: Rust",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Intended Audience :: End Users/Desktop",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: BSD License",
+    "Operating System :: MacOS",
+    "Operating System :: Unix",
+    "Operating System :: POSIX",
+    "Operating System :: Microsoft :: Windows",
+    "Topic :: Utilities",
+    "Topic :: Terminals",
+    "Topic :: Text Processing :: Markup :: HTML",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://github.com/gerardcl/renfe-cli"
+Documentation = "https://github.com/gerardcl/renfe-cli"
+Repository = "https://github.com/gerardcl/renfe-cli"
+Issues = "https://github.com/gerardcl/renfe-cli/issues"
+Changelog = "https://github.com/gerardcl/renfe-cli/blob/master/CHANGELOG.md"
+
+[tool.maturin]
+features = ["pyo3/extension-module"]
+
+[project.scripts]
+renfe-cli = "renfe_cli:main"
diff --git a/setup.py b/setup.py
deleted file mode 100644
index df6641a..0000000
--- a/setup.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from setuptools import setup, find_packages
-
-try:
-    from pypandoc import convert_file
-    read_md = lambda f: convert_file(f, 'rst')
-except ImportError:
-    print("warning: pypandoc module not found, could not convert Markdown to RST")
-    read_md = lambda f: open(f, 'r').read()
-
-setup(
-    name='renfe-cli',
-    version='3.3.0',
-    description='Get faster RENFE Spanish Trains timetables in your terminal',
-    long_description=read_md('README.md'),
-    keywords='Get faster RENFE Spanish Trains timetables terminal',
-    author='Gerard Castillo',
-    author_email='gerardcl@gmail.com',
-    url='https://github.com/gerardcl/renfe-cli',
-    license='BSD',
-    packages = find_packages('src'),
-    package_dir = {'': 'src'},
-    py_modules=['renfe-cli'],
-    include_package_data=True,
-    install_requires=[
-        'setuptools-rust==1.1.2',
-        'setuptools==60.9.3',
-        'beautifulsoup4==4.10.0',
-        'html5lib==1.1',
-        'selenium==4.1.0',
-        'webdriver-manager==3.5.3',
-        'requests==2.27.1'
-    ],
-    entry_points="""
-        [console_scripts]
-        renfe-cli = renfe.cli:main
-        """,
-    classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Environment :: Console',
-        'Intended Audience :: End Users/Desktop',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: BSD License',
-        'Operating System :: MacOS',
-        'Operating System :: Unix',
-        'Operating System :: POSIX',
-        'Operating System :: Microsoft :: Windows',
-        'Programming Language :: Python',
-        'Topic :: Utilities',
-        'Topic :: Terminals',
-        'Topic :: Text Processing :: Markup :: HTML',
-    ],
-    tests_require=['pytest'],
-    test_suite = 'pytest',
-)
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..65c8856
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,48 @@
+use getopts::Options;
+use pyo3::pyfunction;
+use std::env;
+
+use crate::timetable::{print_timetable, search_timetable};
+
+#[pyfunction]
+pub fn main() {
+    let args: Vec<String> = env::args().collect();
+    let program = args[0].clone();
+
+    let mut opts = Options::new();
+    opts.optopt("f", "", "Set From origin station", "ORIGIN");
+    opts.optopt("t", "", "Set To destination station", "DESTINATION");
+    opts.optopt("d", "day", "Set Day to search timetable for", "DAY");
+    opts.optopt("m", "month", "Set Month to search timetable for", "MONTH");
+    opts.optopt("y", "year", "Set Year to search timetable for", "YEAR");
+    opts.optflag("h", "help", "print this help menu");
+    let matches = match opts.parse(&args[1..]) {
+        Ok(m) => m,
+        Err(f) => {
+            panic!("{}", f.to_string())
+        }
+    };
+    if matches.opt_present("h") {
+        print_usage(&program, opts);
+        return;
+    }
+    let origin = matches.opt_str("f");
+    let destination = matches.opt_str("t");
+    let day = matches.opt_str("d");
+    let month = matches.opt_str("m");
+    let year = matches.opt_str("y");
+
+    let timetable = search_timetable(
+        origin.unwrap(),
+        destination.unwrap(),
+        day.unwrap(),
+        month.unwrap(),
+        year.unwrap(),
+    );
+    print_timetable(timetable);
+}
+
+fn print_usage(program: &str, opts: Options) {
+    let brief = format!("Usage: {} [options]", program);
+    print!("{}", opts.usage(&brief));
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..1beb3e1
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,21 @@
+use pyo3::prelude::*;
+
+mod stations;
+use stations::load_stations;
+mod timetable;
+use timetable::{print_timetable, search_timetable};
+mod cli;
+use cli::main;
+
+/// A Python module implemented in Rust. The name of this function must match
+/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
+/// import the module.
+#[pymodule]
+fn renfe_cli(_py: Python, m: &PyModule) -> PyResult<()> {
+    m.add_function(wrap_pyfunction!(search_timetable, m)?)?;
+    m.add_function(wrap_pyfunction!(print_timetable, m)?)?;
+    m.add_function(wrap_pyfunction!(load_stations, m)?)?;
+    m.add_function(wrap_pyfunction!(main, m)?)?;
+
+    Ok(())
+}
diff --git a/src/renfe/__init__.py b/src/renfe/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/renfe/cli.py b/src/renfe/cli.py
deleted file mode 100755
index 757afab..0000000
--- a/src/renfe/cli.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-import logging
-import colorama
-from datetime import date
-
-from renfe.timetable import get_timetable, get_date
-from renfe.stations import get_station_and_key, station_exists, get_station_name
-from renfe.utils import RenfeException, ConfigurationMgmt, parse_args
-
-
-def main():
-    # defaults
-    config = ConfigurationMgmt()
-    today = date.today()
-
-    options = parse_args(config)
-
-    colorama.init(autoreset=True)
-    print(colorama.Fore.GREEN + f"Today is: {today}" + colorama.Fore.RESET)
-
-    if options.search == '':
-        # print timetable for given origin and to stations for a given date
-        if not (station_exists(options.origin) and (options.to)):
-            logging.error(
-                "Please, provide right values for origin and destination station names")
-            exit(1)
-        try:
-            origin_name = get_station_name(options.origin)
-            destination_name = get_station_name(options.to)
-            print(colorama.Fore.GREEN + f"Searching timetable for date: {get_date(int(options.days))}")
-            print(colorama.Fore.GREEN + f"From {origin_name} to {destination_name}" + colorama.Fore.RESET)
-            print(colorama.Fore.GREEN + "Be patient, navigating through renfe site now..." + colorama.Fore.RESET)
-            times = get_timetable(
-                origin_name,
-                destination_name,
-                int(options.days),
-                options.browser,
-                int(options.search_timeout))
-            print(colorama.Fore.GREEN + "=======================TIMETABLE======================")
-            print(colorama.Fore.GREEN + " {:<10} | {:<10} | {:<10} | {:<10} ".format(
-                'Train', 'Departure', 'Arrival', 'Duration'))
-
-            for time in times:
-                print(colorama.Fore.GREEN + "--------------------------------------------------------------------------")
-                print(colorama.Fore.GREEN + " {:<10} | {:<10} | {:<10} | {:<12} | {:<10} ".format(
-                    time[0], time[1], time[2], time[3], time[4]))
-            print(colorama.Fore.GREEN + "==========================================================================" + colorama.Fore.RESET)
-
-            if not times:
-                print(colorama.Fore.YELLOW + "Timetable was empty. \
-                    Maybe no more trains for today? \
-                    Also, try increasing search timeout (-e flag, see help). \
-                    Please, open an issue if problem does persist." + colorama.Fore.RESET)
-
-        except (RenfeException, ValueError) as err:
-            logging.error(err)
-            logging.error(
-                "No timetables found... Check your inputs and enable debug. If the problem persists,"
-                " create an issue at http://www.github.com/gerardcl/renfe-cli/issues")
-            exit(1)
-    else:
-        # search into list of Station names and its Renfe identifiers
-        try:
-            print(colorama.Fore.GREEN + f"Searching stations like: {options.search}")
-            stations_infos = get_station_and_key(options.search)
-            for station_info in stations_infos:
-                print(colorama.Fore.GREEN + f"{station_info}")
-
-            if not stations_infos:
-                print(colorama.Fore.RED + f"Oops! No stations found by key value: {options.search}" + colorama.Fore.RESET)
-        except RenfeException as err:
-            logging.error(err)
-            logging.error(
-                "Error searching stations IDs... Check your inputs and enable debug. If the problem persists,"
-                " create an issue at http://www.github.com/gerardcl/renfe-cli/issues")
-            exit(1)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/src/renfe/stations.py b/src/renfe/stations.py
deleted file mode 100644
index 9819d10..0000000
--- a/src/renfe/stations.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import requests
-import json
-from functools import lru_cache
-from typing import List
-
-from renfe.utils import RenfeException
-
-
-@lru_cache(maxsize=32)
-def get_stations():
-    stations_js = requests.get(
-        'https://www.renfe.com/content/dam/renfe/es/General/buscadores/javascript/estacionesEstaticas.js')
-    if stations_js.status_code != 200 or stations_js.text.strip() == "":
-        raise RenfeException("Looks like renfe web site is down? or maybe something was changed?")
-    stations = json.loads(stations_js.text.split('=')[1].strip(';'))
-
-    return stations
-
-
-def get_station_and_key(search: str) -> List[str]:
-    stations_infos = []
-    try:
-        for station in get_stations():
-            if search.lower() in station['desgEstacion'].lower():
-                stations_infos.append(f"{station['desgEstacion']}: {station['cdgoEstacion']}")
-    except Exception as ex:
-        raise RenfeException(ex)
-
-    return stations_infos
-
-
-def get_station_name(id: str) -> str:
-    for station in get_stations():
-        if id == station['cdgoEstacion']:
-            return station['desgEstacion']
-    raise RenfeException(f"Station id {id} not found!")
-
-
-def station_exists(id: str) -> bool:
-    for station in get_stations():
-        if id == station['cdgoEstacion']:
-            return True
-    return False
diff --git a/src/renfe/timetable.py b/src/renfe/timetable.py
deleted file mode 100644
index a8524b1..0000000
--- a/src/renfe/timetable.py
+++ /dev/null
@@ -1,181 +0,0 @@
-import os
-import re
-from datetime import datetime, timedelta
-from typing import List, Set, Union
-from bs4 import BeautifulSoup
-from selenium import webdriver
-from time import sleep
-from selenium.webdriver.firefox.webdriver import WebDriver as Firefox
-from selenium.webdriver.chrome.webdriver import WebDriver as Chrome
-from selenium.common.exceptions import NoSuchElementException
-
-os.environ['WDM_LOG_LEVEL'] = '0'
-
-
-def get_timetable(
-        origin: str,
-        destination: str,
-        days_from_today: int = 0,
-        browser: str = "firefox",
-        search_timeout: int = 3) -> List[Set]:
-    soup = get_soup(browser, origin, destination, days_from_today, search_timeout)
-    types = get_types(soup)
-    durations = get_durations(soup)
-    departures = get_departures(soup)
-    arrivals = get_arrivals(soup)
-    prices = get_prices(soup)
-
-    return list(zip(types, departures, arrivals, durations, prices))
-
-
-def get_browser(type: str) -> Union[Firefox, Chrome]:
-    global browser
-    try:
-        if type == "firefox":
-            from webdriver_manager.firefox import GeckoDriverManager
-            from selenium.webdriver.firefox.options import Options
-            firefox_options = Options()
-            firefox_options.add_argument("--headless")
-            browser = webdriver.Firefox(executable_path=GeckoDriverManager().install(), options=firefox_options)
-        else:  # chrome
-            from webdriver_manager.chrome import ChromeDriverManager
-            from selenium.webdriver.chrome.options import Options
-            chrome_options = Options()
-            chrome_options.add_argument("--headless")
-            browser = webdriver.Chrome(executable_path=ChromeDriverManager().install(), options=chrome_options)
-
-        browser.implicitly_wait(10)  # wait up to 10 seconds while trying to locate elements
-
-    except ValueError as ex:
-        raise ex
-
-    return browser
-
-
-def get_soup(browser: str, origin: str, destination: str, days_from_today: int, search_timeout: int) -> BeautifulSoup:
-    browser = get_browser(browser)
-    browser.get("https://www.renfe.com/es/es")
-
-    sleep(1)
-
-    # NOTE: temporal solution to avoid popup window
-    try:
-        browser.find_element_by_css_selector("i.rf-ico.icon-close.sc-rf-modal-score").click()
-    except NoSuchElementException:
-        pass
-
-    origin_input = browser.find_element_by_css_selector("rf-awesomplete.rf-input-autocomplete:nth-child(1) \
-> div:nth-child(1) > div:nth-child(2) > input:nth-child(1)")
-    origin_input.send_keys(origin)
-
-    sleep(0.05)
-
-    origin_option = browser.find_element_by_css_selector("#awesomplete_list_1_item_0")
-    origin_option.click()
-
-    destination_input = browser.find_element_by_css_selector("rf-awesomplete.rf-input-autocomplete:nth-child(2) \
-> div:nth-child(1) > div:nth-child(2) > input:nth-child(1)")
-    destination_input.send_keys(destination)
-
-    sleep(0.05)
-
-    destination_option = browser.find_element_by_css_selector("#awesomplete_list_2_item_0")
-    destination_option.click()
-
-    time = browser.find_element_by_css_selector("div.rf-daterange__container-ipt:nth-child(2) > div:nth-child(2) \
-> button:nth-child(2) > i:nth-child(1)")
-
-    while days_from_today > 0:
-        days_from_today = days_from_today - 1
-        time.click()
-
-    search_button = browser.find_element_by_css_selector("#contentPage > div > div > div:nth-child(1) > div > div \
-> div > div > div > div > rf-header > rf-header-top > div.rf-header__wrap-search.grid > rf-search \
-> div > div.rf-search__filters.rf-search__filters--open > div.rf-search__wrapper-button > div.rf-search__button")
-    search_button.click()
-
-    sleep(search_timeout)
-
-    soup = BeautifulSoup(browser.page_source, 'html.parser')
-
-    browser.quit()
-
-    return soup
-
-
-def get_departures(soup) -> List[str]:
-    result = []
-    attrs_departure = {
-            'class': 'booking-list-element-big-font salida displace-text-xs',
-        }
-    departures = soup.find_all('div', attrs=attrs_departure)
-    for departure in departures:
-        result.append(departure.text.strip())
-    return result
-
-
-def get_arrivals(soup) -> List[str]:
-    result = []
-    attrs_arrival = {
-            'class': 'booking-list-element-big-font llegada',
-        }
-    arrivals = soup.find_all('div', attrs=attrs_arrival)
-    for arrival in arrivals:
-        result.append(arrival.text.strip())
-    return result
-
-
-def get_durations(soup) -> List[str]:
-    result = []
-    attrs_duration = {
-        'class': 'purple-font displace-text duracion hidden-xs',
-        'aria-label': 'Duración'
-    }
-    durations = soup.find_all('div', attrs=attrs_duration)
-    for duration in durations:
-        result.append(duration.text.strip())
-    return result
-
-
-def get_types(soup) -> List[str]:
-    result = []
-    attrs_duration = {
-            'class': 'purple-font displace-text duracion hidden-xs',
-            'aria-label': 'Duración'
-        }
-    durations = soup.find_all('div', attrs=attrs_duration)
-    attrs_duration_hidden = {
-            'class': 'purple-font displace-text visible-xs text-nowrap',
-        }
-    durations_hidden = soup.find_all('div', attrs=attrs_duration_hidden)
-    attrs_duration_and_type = {
-            'class': 'displace-text',
-        }
-    durations_and_types = soup.find_all('div', attrs=attrs_duration_and_type)
-    types = [item for item in durations_and_types if item not in durations_hidden + durations]
-    for t in types:
-        result.append(t.text.strip())
-    return result
-
-
-def get_date(days_from_today: int) -> str:
-    day = datetime.today() + timedelta(days=days_from_today)
-    return f"{day.year}-{day.month}-{day.day}"
-
-
-def get_prices(soup) -> List[str]:
-    prices = []
-    attrs_trip = {"class": re.compile("trayectoRow\w*")}
-    trips = soup.find_all("tr", attrs=attrs_trip)
-    for trip in trips:
-        attrs_price = {"class":"precio booking-list-element-big-font"}
-        price = trip.find_all("div", attrs=attrs_price)
-        if len(price)>1:
-            price = " - ".join([p.get_text() for p in price])
-        elif len(price)==1:
-            price = price[0].get_text()
-        else:
-            attrs_train_status = {"class": re.compile("booking-list-element-price\w*")}
-            price = trip.find_all("td", attrs=attrs_train_status)[0].get_text().strip("\n").strip()
-        prices.append(price)
-    return prices
\ No newline at end of file
diff --git a/src/renfe/utils.py b/src/renfe/utils.py
deleted file mode 100644
index 958d75c..0000000
--- a/src/renfe/utils.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import sys
-import os
-import json
-import logging
-import optparse
-from pathlib import Path
-
-
-class RenfeException(Exception):
-    def __init__(self, *args, **kwargs):
-        Exception.__init__(self, *args, **kwargs)
-
-
-class ConfigurationMgmt():
-    _config_location = '.renfe_default_stations.json'
-    _logging_levels = {'critical': logging.CRITICAL,
-                       'error': logging.ERROR,
-                       'warning': logging.WARNING,
-                       'info': logging.INFO,
-                       'debug': logging.DEBUG}
-
-    def __init__(self):
-        if os.path.exists(os.path.join(Path.home(), self._config_location)):
-            self.__dict__ = json.load(open(os.path.join(Path.home(), self._config_location)))
-        else:
-            self.__dict__ = {
-                'origin': '79202',  # SILS
-                'to': '71802'  # BCN PASSEIG DE GRACIA
-            }
-
-    # get_defaults
-    def get_config(self):
-        return self.__dict__
-
-    # set_defaults
-    def set_config(self, new_config):
-        logging.debug(f"setting new defaults to {new_config}")
-        self.__dict__ = new_config
-        json.dump(self.__dict__, open(os.path.join(Path.home(), self._config_location), 'w'))
-
-    def configs_and_checks(self, options):
-        # Logging defaults to warning: critical, error and warning messages.
-        logging.basicConfig(level=self._logging_levels.get(options.logging_level, logging.WARN),
-                            filename=options.logging_file,
-                            format='%(asctime)s %(levelname)s: %(message)s',
-                            datefmt='%Y-%m-%d %H:%M:%S')
-        logging.debug(
-            "params --> browser: {}, days: {}, origin: {}, destination: {}, search: {}, update_config:"
-            " {}, logging_level: {}, logging_file: {} <--"
-            .format(options.browser, options.days, options.origin, options.to, options.search,
-                    options.update_config, options.logging_level, options.logging_file))
-        # Doing some checks
-        if options.origin == options.to:
-            raise RenfeException("Cannot search timetables if both origin and destiantion are the same")
-        if options.browser not in ["firefox", "chrome"]:
-            raise RenfeException("Only accepted browsers are: firefox or chrome")
-        try:
-            int(options.days)
-            if int(options.days) < 0:
-                raise ValueError("Only today or future days can be searched...")
-        except ValueError:
-            logging.debug(sys.exc_info())
-            raise RenfeException("Cannot search timetables if date params are not set with numbers")
-
-        # check and configure input parameters
-        try:
-            if options.update_config:
-                self.set_config({'origin': options.origin, 'to': options.to})
-        except RenfeException as err:
-            logging.error(err)
-            logging.error(
-                "Error when handling configs... Check your inputs and enable debug. If the problem persists,"
-                " create an issue at http://www.github.com/gerardcl/renfe-cli/issues")
-            exit(1)
-
-
-def parse_args(config):
-    stations = config.get_config()
-
-    p = optparse.OptionParser()
-    p.add_option('--origin', '-o', default=stations.get('origin', '79202'),
-                 help='from/origin ID of the train station. Use flag '
-                      '\'-s <possible station name>\' in order to search for IDs')
-    p.add_option('--to', '-t', default=stations.get('to', '71802'),
-                 help='to/destination ID of the train station. Use flag '
-                      '\'-s <possible station name>\' in order to search for IDs')
-    p.add_option('--days', '-d', default=0, help='number of days from today to get the timetable (default: 0 - today)')
-    p.add_option('--browser', '-b', default="firefox", help='possible browsers are "firefox" and "chrome" (default: firefox)')
-    p.add_option('--search', '-s', default='',
-                 help='you need to get the stations IDs, searching by names; '
-                      'in order to apply right inputs for origins and/or destinations')
-    p.add_option('--search-timeout', '-e', default=3,
-                 help='search timeout in seconds (default to 3 seconds)')
-    p.add_option('--logging-level', '-l',
-                 help='logging level defaults to warning and possible values are:'
-                      ' debug, info, warning, error and critical')
-    p.add_option('--logging-file', '-f',
-                 help='logging file name is required if you want to submit an issue with more information')
-    p.add_option('--update-config', '-u', default=False, action='store_true', dest='update_config',
-                 help='change your origin and destination stations to defaults when loading this flag')
-    options, _ = p.parse_args()
-
-    return options
diff --git a/src/stations.rs b/src/stations.rs
new file mode 100644
index 0000000..a2f8fd7
--- /dev/null
+++ b/src/stations.rs
@@ -0,0 +1,22 @@
+use pyo3::pyfunction;
+use scraper::{Html, Selector};
+
+#[pyfunction]
+pub fn load_stations() -> Vec<String> {
+    let response = match ureq::get("https://www.renfe.com/content/renfe/es/en/viajar/informacion-util/horarios/app-horarios.html").call() {
+        Ok(response) => { response },
+        Err(_) => { panic!("something wrong") }
+    };
+
+    let parsed_html = Html::parse_document(&response.into_string().unwrap());
+
+    let selector = &Selector::parse(r#"#O > option"#).unwrap();
+
+    let stations: Vec<String> = parsed_html
+        .select(selector)
+        .flat_map(|el| el.text())
+        .map(|t| t.to_string())
+        .collect();
+
+    stations[1..].to_vec()
+}
diff --git a/src/timetable.rs b/src/timetable.rs
new file mode 100644
index 0000000..b202659
--- /dev/null
+++ b/src/timetable.rs
@@ -0,0 +1,223 @@
+use headless_chrome::{Browser, LaunchOptions};
+use pyo3::pyfunction;
+use scraper::{ElementRef, Html, Selector};
+use std::{collections::HashMap, time::Duration};
+
+trait VecParser {
+    fn texts_parser(&self, selector: Selector) -> Vec<String>;
+    fn alts_parser(&self, selector: Selector) -> Vec<String>;
+}
+
+impl VecParser for ElementRef<'_> {
+    fn texts_parser(&self, selector: Selector) -> Vec<String> {
+        self.select(&selector)
+            .flat_map(|el| el.text())
+            .map(|t| t.to_string())
+            .map(|x| x.trim().to_string())
+            .filter(|x| !x.is_empty())
+            .collect()
+    }
+    fn alts_parser(&self, selector: Selector) -> Vec<String> {
+        self.select(&selector)
+            .flat_map(|el| el.value().attr("alt"))
+            .map(|t| t.to_string())
+            .map(|x| x.trim().to_string())
+            .filter(|x| !x.is_empty())
+            .collect()
+    }
+}
+
+// Convenience function to avoid unwrap()ing all the time
+fn make_selector(selector: &str) -> Selector {
+    Selector::parse(selector).unwrap()
+}
+
+#[pyfunction]
+pub fn search_timetable(
+    origin: String,
+    destination: String,
+    day: String,
+    month: String,
+    year: String,
+) -> Vec<Vec<String>> {
+    let months: HashMap<&str, &str> = HashMap::from([
+        ("1", "Ene"),
+        ("2", "Feb"),
+        ("3", "Mar"),
+        ("4", "Abr"),
+        ("5", "May"),
+        ("6", "Jun"),
+        ("7", "Jul"),
+        ("8", "Ago"),
+        ("9", "Sep"),
+        ("10", "Oct"),
+        ("11", "Nov"),
+        ("12", "Dec"),
+    ]);
+
+    println!("loading headless chrome browser");
+    let browser = Browser::new(LaunchOptions {
+        headless: true,
+        sandbox: true,
+        enable_gpu: false,
+        enable_logging: false,
+        idle_browser_timeout: Duration::from_secs(30),
+        window_size: Some((1920, 1080)),
+        path: None,
+        user_data_dir: None,
+        port: None,
+        ignore_certificate_errors: true,
+        extensions: Vec::new(),
+        process_envs: None,
+        fetcher_options: Default::default(),
+        args: Vec::new(),
+        disable_default_args: false,
+        proxy_server: None,
+    })
+    .unwrap();
+
+    let tab = browser.new_tab().unwrap();
+
+    println!("navigating to renfe timetable search page");
+    tab.navigate_to("https://www.renfe.com/es/es/viajar/informacion-util/horarios")
+        .unwrap();
+
+    // let _jpeg_data = tab.capture_screenshot(
+    //     Page::CaptureScreenshotFormatOption::Jpeg,
+    //     None,
+    //     None,
+    //     true)?;
+
+    // let img = image::load_from_memory(&_jpeg_data).expect("Data from stdin could not be decoded.");
+    // print(&img, &Config::default()).expect("Image printing failed.");
+    println!("waiting for search page");
+    tab.wait_until_navigated()
+        .unwrap()
+        .wait_for_elements_by_xpath(r#"//*[@id="O"]"#)
+        .unwrap();
+
+    println!("adding origin station");
+    tab.find_element_by_xpath(r#"//*[@id="O"]"#)
+        .unwrap()
+        .click()
+        .unwrap();
+    tab.type_str(&origin).unwrap().press_key("Enter").unwrap();
+
+    println!("adding destination station");
+    tab.find_element_by_xpath(r#"//*[@id="D"]"#)
+        .unwrap()
+        .click()
+        .unwrap();
+    tab.type_str(&destination)
+        .unwrap()
+        .press_key("Enter")
+        .unwrap();
+
+    println!("adding day");
+    tab.find_element_by_xpath(r#"//*[@id="DF"]"#)
+        .unwrap()
+        .click()
+        .unwrap();
+    tab.type_str(&day).unwrap().press_key("Enter").unwrap();
+
+    println!("adding month");
+    tab.find_element_by_xpath(r#"//*[@id="MF"]"#)
+        .unwrap()
+        .click()
+        .unwrap();
+    tab.type_str(months[&month.as_str()])
+        .unwrap()
+        .press_key("Enter")
+        .unwrap();
+
+    println!("adding year");
+    tab.find_element_by_xpath(r#"//*[@id="AF"]"#)
+        .unwrap()
+        .click()
+        .unwrap();
+    let elem = tab.type_str(&year).unwrap().press_key("Enter").unwrap();
+
+    println!("searching timetable");
+    elem.press_key("Tab").unwrap().press_key("Enter").unwrap();
+
+    // wait on navigating to search result page
+    tab.wait_until_navigated()
+        .unwrap()
+        .wait_for_elements_by_xpath(r#"//*[@id="contenedor"]"#)
+        .unwrap();
+
+    println!("got timetable page");
+    let html = tab
+        .find_element_by_xpath(r#"//*[@id="contenedor"]"#)
+        .unwrap()
+        .get_content()
+        .unwrap();
+
+    println!("loading timetable");
+
+    let parsed_html = Html::parse_document(&html);
+
+    let resum_selector = make_selector(r#"tr.odd"#);
+    let total_tracks = parsed_html.select(&resum_selector);
+    // println!("#trajectes: {:?}", &total_tracks.count());
+
+    let mut tracks: Vec<Vec<String>> = Vec::new();
+    for track in total_tracks {
+        let columns_selector: Selector = make_selector(r#"td"#);
+        let columns = track.texts_parser(columns_selector);
+        let mut row = Vec::<String>::with_capacity(4);
+        for (idx, column) in columns.iter().enumerate() {
+            if idx == 0 {
+                let train = column
+                    .trim_start_matches(char::is_numeric)
+                    .trim()
+                    .to_owned();
+                // println!("#sortida: {:?}", &train);
+                row.push(train);
+            }
+            if (1..4).contains(&idx) {
+                let timing = column.trim().to_owned();
+                // println!("#sortida: {:?}", &timing);
+                row.push(timing);
+            }
+        }
+        tracks.push(row);
+    }
+
+    tracks
+}
+
+#[pyfunction]
+pub fn print_timetable(tracks: Vec<Vec<String>>) {
+    println!("=========================TIMETABLE=========================");
+    println!(
+        "{0: <12} |   {1: <10} |   {2: <10} | {3: <12}",
+        "Train", "Departure", "Arrival", "Duration"
+    );
+    for track in tracks {
+        println!("-----------------------------------------------------------");
+        println!(
+            "{0: <12} |    {1: <9} |    {2: <9} | {3: <12}",
+            track[0], track[1], track[2], track[3]
+        );
+    }
+    println!("===========================================================");
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{print_timetable, search_timetable};
+
+    #[test]
+    fn test_search_timetable() -> Result<(), Box<dyn std::error::Error>> {
+        // print_timetable(search_timetable(
+        //     "Girona".to_owned(),
+        //     "Barcelona".to_owned(),
+        //     "28".to_owned(),
+        //     "11".to_owned(),
+        //     "2023".to_owned(),
+        // ));
+
+        Ok(())
+    }
+}


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_stations.py b/tests/test_stations.py
deleted file mode 100644
index fb017ac..0000000
--- a/tests/test_stations.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# TODO get more tests!!!
-# test_input = (
-#     # search ok
-#     ('-s sil', None), ('-s barc', None),
-
-#     # search nook
-#     ('-s 123', None),
-
-#     # default
-#     ('', None),
-
-#     # change default
-#     ('-o 79300 -u', None),
-
-#     # new default
-#     ('', None),
-
-#     # same as default
-#     ('-d 0 -o 79202 -t BARCE', None),
-
-#     # wrong inputs
-#     # ('-d notanumber', SystemExit), ('-o BARCE -t BARCE', SystemExit), ('-d -1', SystemExit)
-# )
-import pytest
-from renfe.stations import get_stations, get_station_name, station_exists, get_station_and_key
-from renfe.utils import RenfeException
-
-def test_renfe_cli_should_get_list_of_stations_objects():
-    stations = get_stations()
-
-    assert type(stations) == list
-    assert any(stations)
-
-
-def test_renfe_cli_should_get_name_of_station_from_id():
-    name = get_station_name("79202")
-
-    assert name == "SILS"
-
-
-def test_renfe_cli_should_raise_exception_if_id_does_not_exists():
-    with pytest.raises(RenfeException):
-        get_station_name("123")
-
-
-def test_renfe_cli_should_find_station_by_id():
-    exists = station_exists("79202")
-
-    assert exists == True
-
-
-def test_renfe_cli_should_return_false_when_station_by_id_not_found():
-    exists = station_exists("123")
-
-    assert exists == False
-
-
-def test_renfe_cli_should_return_stations_infos_when_key_is_found():
-    stations_infos = get_station_and_key("sil")
-
-    assert len(stations_infos) > 0
-
-
-def test_renfe_cli_should_return_empty_when_station_by_id_not_found():
-    stations_infos = get_station_and_key("123")
-
-    assert len(stations_infos) == 0
diff --git a/tests/test_timetable.py b/tests/test_timetable.py
deleted file mode 100644
index 8578432..0000000
--- a/tests/test_timetable.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from renfe.timetable import get_timetable
-
-def test_renfe_cli_should_get_non_empty_timetable():
-    tt = get_timetable("MADRI", "BARCE", 1, "firefox", 10)
-
-    assert type(tt) == list
-    assert any(tt)