From ad51418674ca703931a5e9124cc05753724eb74a Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Mon, 20 Nov 2023 21:53:22 +0100 Subject: [PATCH 1/9] preparing project structure for new complete Rust based backend --- .github/dependabot.yml | 32 - .github/workflows/CICD.yml | 120 ++ .github/workflows/codeql-analysis.yml | 71 - .github/workflows/python-package.yml | 45 - .gitignore | 111 +- Cargo.lock | 2141 +++++++++++++++++++++++++ Cargo.toml | 17 + README.md | 2 - pyproject.toml | 19 + src/lib.rs | 155 ++ src/renfe/__init__.py | 0 src/renfe/cli.py | 80 - src/renfe/stations.py | 43 - src/renfe/timetable.py | 181 --- src/renfe/utils.py | 103 -- 15 files changed, 2477 insertions(+), 643 deletions(-) delete mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CICD.yml delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/python-package.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 pyproject.toml create mode 100644 src/lib.rs delete mode 100644 src/renfe/__init__.py delete mode 100755 src/renfe/cli.py delete mode 100644 src/renfe/stations.py delete mode 100644 src/renfe/timetable.py delete mode 100644 src/renfe/utils.py 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..0a3a604 --- /dev/null +++ b/.github/workflows/CICD.yml @@ -0,0 +1,120 @@ +# 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: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, 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/.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/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8bb8e91 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2141 @@ +# 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 = [ + "anyhow", + "headless_chrome", + "image", + "pyo3", + "scraper", + "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..4dc5122 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[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"] } +anyhow = "1.0" +headless_chrome = { version = "1.0", features = ["fetch"] } +image = "0.24" +viuer = "0.7" +scraper = "0.18" diff --git a/README.md b/README.md index 99d3a50..5910ae4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![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) - # 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! diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..53865db --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["maturin>=1.3,<2.0"] +build-backend = "maturin" + +[project] +name = "renfe-cli" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] + +[tool.maturin] +features = ["pyo3/extension-module"] + +[project.scripts] +renfe-cli = "renfe_cli:browse_renfe" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..febe8ac --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,155 @@ +use pyo3::prelude::*; + +use std::error::Error; +use std::fs; +use std::time::Duration; +use anyhow::Result; +use scraper::{Html, Selector, ElementRef}; +use viuer::{Config, print}; +use headless_chrome::{Browser, LaunchOptions}; +use headless_chrome::protocol::cdp::Page; +use image; + + +#[pyfunction] +fn browse_renfe() { + 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(); + + println!("loadingn new tab"); + let tab = browser.new_tab().unwrap(); + println!("got new tab"); + + tab.navigate_to("https://www.renfe.com/es/es/viajar/informacion-util/horarios").unwrap(); + println!("navigating"); + + // 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."); + + tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="O"]"#).unwrap().click().unwrap(); + println!("got input bar"); + + tab.type_str("Girona").unwrap().press_key("Enter").unwrap(); + println!("search"); + + + tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="D"]"#).unwrap().click().unwrap(); + println!("got input bar"); + + tab.type_str("Barcelona").unwrap().press_key("Enter").unwrap(); + println!("search"); + + tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="DF"]"#).unwrap().click().unwrap(); + println!("got input bar"); + + tab.type_str("23").unwrap().press_key("Enter").unwrap(); + println!("search"); + + + tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="MF"]"#).unwrap().click().unwrap(); + println!("got input bar"); + + tab.type_str("Nov").unwrap().press_key("Enter").unwrap(); + println!("search"); + + + tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="AF"]"#).unwrap().click().unwrap(); + println!("got input bar"); + + let elem = tab.type_str("2023").unwrap().press_key("Enter").unwrap(); + println!("searching"); + + elem.press_key("Tab").unwrap().press_key("Enter").unwrap(); + + println!("search"); + + // wait on navigating to search result page + tab.wait_until_navigated().unwrap().wait_for_elements_by_xpath(r#"//*[@id="contenedor"]"#).unwrap(); + + println!("got html"); + + let html = tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="contenedor"]"#).unwrap().get_content().unwrap(); + + 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()); + + for track in total_tracks { + println!("track:"); + let columns_selector: Selector = make_selector(r#"td"#); + let columns = track.texts_parser(columns_selector); + for (idx, column) in columns.iter().enumerate() { + if (0..4).contains(&idx) { + println!("#sortida: {:?}", &column); + + } + } + } + + // Ok(()) +} + +// Convenience function to avoid unwrap()ing all the time +fn make_selector(selector: &str) -> Selector { + Selector::parse(selector).unwrap() +} + +trait VecParser { + fn texts_parser(&self, selector: Selector) -> Vec; + fn alts_parser(&self, selector: Selector) -> Vec; +} + +impl VecParser for ElementRef<'_> { + fn texts_parser(&self, selector: Selector) -> Vec { + 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 { + 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() + } +} + +/// 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!(browse_renfe, 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 \' 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 \' 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 From d796fca9ef5b19673e7af7bbefe595edd691b13b Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Tue, 21 Nov 2023 21:54:50 +0100 Subject: [PATCH 2/9] add load_stations pyfunction --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/lib.rs | 9 +++------ src/stations.rs | 21 +++++++++++++++++++++ 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 src/stations.rs diff --git a/Cargo.lock b/Cargo.lock index 8bb8e91..6649c9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,11 +1297,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "renfe-cli" version = "4.0.0" dependencies = [ - "anyhow", "headless_chrome", "image", "pyo3", "scraper", + "ureq", "viuer", ] diff --git a/Cargo.toml b/Cargo.toml index 4dc5122..4def65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.20", features = ["abi3-py37"] } -anyhow = "1.0" headless_chrome = { version = "1.0", features = ["fetch"] } image = "0.24" viuer = "0.7" scraper = "0.18" +ureq = "2.9" diff --git a/src/lib.rs b/src/lib.rs index febe8ac..d85fcba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,11 @@ use pyo3::prelude::*; -use std::error::Error; -use std::fs; use std::time::Duration; -use anyhow::Result; use scraper::{Html, Selector, ElementRef}; -use viuer::{Config, print}; use headless_chrome::{Browser, LaunchOptions}; -use headless_chrome::protocol::cdp::Page; -use image; +mod stations; +use stations::load_stations; #[pyfunction] fn browse_renfe() { @@ -150,6 +146,7 @@ impl VecParser for ElementRef<'_> { #[pymodule] fn renfe_cli(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(browse_renfe, m)?)?; + m.add_function(wrap_pyfunction!(load_stations, m)?)?; Ok(()) } diff --git a/src/stations.rs b/src/stations.rs new file mode 100644 index 0000000..db99c1c --- /dev/null +++ b/src/stations.rs @@ -0,0 +1,21 @@ +use pyo3::pyfunction; +use scraper::{Selector, Html}; + +#[pyfunction] +pub fn load_stations() -> Vec { + 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 = parsed_html + .select(selector) + .flat_map(|el| el.text()).map(|t| t.to_string()) + .collect(); + + stations[1..].to_vec() +} From 0ba3cab98d14265de0e281d89173a2fd07fa5baf Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Tue, 21 Nov 2023 22:09:26 +0100 Subject: [PATCH 3/9] no aarch64 support --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 0a3a604..4328a57 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + target: [x86_64, x86, armv7, s390x, ppc64le] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 From e48f49900139394bba78400c91c114041b3674e0 Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Fri, 24 Nov 2023 22:27:43 +0100 Subject: [PATCH 4/9] search timetable and print timetable --- src/lib.rs | 142 +------------------------------ src/stations.rs | 5 +- src/timetable.rs | 216 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 140 deletions(-) create mode 100644 src/timetable.rs diff --git a/src/lib.rs b/src/lib.rs index d85fcba..ce47e26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,151 +1,17 @@ use pyo3::prelude::*; -use std::time::Duration; -use scraper::{Html, Selector, ElementRef}; -use headless_chrome::{Browser, LaunchOptions}; - mod stations; use stations::load_stations; - -#[pyfunction] -fn browse_renfe() { - 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(); - - println!("loadingn new tab"); - let tab = browser.new_tab().unwrap(); - println!("got new tab"); - - tab.navigate_to("https://www.renfe.com/es/es/viajar/informacion-util/horarios").unwrap(); - println!("navigating"); - - // 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."); - - tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="O"]"#).unwrap().click().unwrap(); - println!("got input bar"); - - tab.type_str("Girona").unwrap().press_key("Enter").unwrap(); - println!("search"); - - - tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="D"]"#).unwrap().click().unwrap(); - println!("got input bar"); - - tab.type_str("Barcelona").unwrap().press_key("Enter").unwrap(); - println!("search"); - - tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="DF"]"#).unwrap().click().unwrap(); - println!("got input bar"); - - tab.type_str("23").unwrap().press_key("Enter").unwrap(); - println!("search"); - - - tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="MF"]"#).unwrap().click().unwrap(); - println!("got input bar"); - - tab.type_str("Nov").unwrap().press_key("Enter").unwrap(); - println!("search"); - - - tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="AF"]"#).unwrap().click().unwrap(); - println!("got input bar"); - - let elem = tab.type_str("2023").unwrap().press_key("Enter").unwrap(); - println!("searching"); - - elem.press_key("Tab").unwrap().press_key("Enter").unwrap(); - - println!("search"); - - // wait on navigating to search result page - tab.wait_until_navigated().unwrap().wait_for_elements_by_xpath(r#"//*[@id="contenedor"]"#).unwrap(); - - println!("got html"); - - let html = tab.wait_until_navigated().unwrap().find_element_by_xpath(r#"//*[@id="contenedor"]"#).unwrap().get_content().unwrap(); - - 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()); - - for track in total_tracks { - println!("track:"); - let columns_selector: Selector = make_selector(r#"td"#); - let columns = track.texts_parser(columns_selector); - for (idx, column) in columns.iter().enumerate() { - if (0..4).contains(&idx) { - println!("#sortida: {:?}", &column); - - } - } - } - - // Ok(()) -} - -// Convenience function to avoid unwrap()ing all the time -fn make_selector(selector: &str) -> Selector { - Selector::parse(selector).unwrap() -} - -trait VecParser { - fn texts_parser(&self, selector: Selector) -> Vec; - fn alts_parser(&self, selector: Selector) -> Vec; -} - -impl VecParser for ElementRef<'_> { - fn texts_parser(&self, selector: Selector) -> Vec { - 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 { - 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() - } -} +mod timetable; +use timetable::{print_timetable, search_timetable}; /// 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!(browse_renfe, m)?)?; + m.add_function(wrap_pyfunction!(search_timetable, m)?)?; + m.add_function(wrap_pyfunction!(print_timetable, m)?)?; m.add_function(wrap_pyfunction!(load_stations, m)?)?; Ok(()) diff --git a/src/stations.rs b/src/stations.rs index db99c1c..a2f8fd7 100644 --- a/src/stations.rs +++ b/src/stations.rs @@ -1,5 +1,5 @@ use pyo3::pyfunction; -use scraper::{Selector, Html}; +use scraper::{Html, Selector}; #[pyfunction] pub fn load_stations() -> Vec { @@ -14,7 +14,8 @@ pub fn load_stations() -> Vec { let stations: Vec = parsed_html .select(selector) - .flat_map(|el| el.text()).map(|t| t.to_string()) + .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..63119ad --- /dev/null +++ b/src/timetable.rs @@ -0,0 +1,216 @@ +use pyo3::prelude::*; + +use headless_chrome::{Browser, LaunchOptions}; +use scraper::{ElementRef, Html, Selector}; +use std::time::Duration; + +trait VecParser { + fn texts_parser(&self, selector: Selector) -> Vec; + fn alts_parser(&self, selector: Selector) -> Vec; +} + +impl VecParser for ElementRef<'_> { + fn texts_parser(&self, selector: Selector) -> Vec { + 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 { + 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> { + 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(); + + println!("loadingn new tab"); + let tab = browser.new_tab().unwrap(); + println!("got new tab"); + + tab.navigate_to("https://www.renfe.com/es/es/viajar/informacion-util/horarios") + .unwrap(); + println!("navigating"); + + // 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."); + tab.wait_until_navigated() + .unwrap() + .wait_for_elements_by_xpath(r#"//*[@id="O"]"#) + .unwrap(); + + tab.find_element_by_xpath(r#"//*[@id="O"]"#) + .unwrap() + .click() + .unwrap(); + println!("got input bar"); + + tab.type_str(&origin).unwrap().press_key("Enter").unwrap(); + println!("search"); + + tab.find_element_by_xpath(r#"//*[@id="D"]"#) + .unwrap() + .click() + .unwrap(); + println!("got input bar"); + + tab.type_str(&destination) + .unwrap() + .press_key("Enter") + .unwrap(); + println!("search"); + + tab.find_element_by_xpath(r#"//*[@id="DF"]"#) + .unwrap() + .click() + .unwrap(); + println!("got input bar"); + + tab.type_str(&day).unwrap().press_key("Enter").unwrap(); + println!("search"); + + tab.find_element_by_xpath(r#"//*[@id="MF"]"#) + .unwrap() + .click() + .unwrap(); + println!("got input bar"); + + tab.type_str(&month).unwrap().press_key("Enter").unwrap(); + println!("search"); + + tab.find_element_by_xpath(r#"//*[@id="AF"]"#) + .unwrap() + .click() + .unwrap(); + println!("got input bar"); + + let elem = tab.type_str(&year).unwrap().press_key("Enter").unwrap(); + println!("searching"); + + elem.press_key("Tab").unwrap().press_key("Enter").unwrap(); + + println!("search"); + + // wait on navigating to search result page + tab.wait_until_navigated() + .unwrap() + .wait_for_elements_by_xpath(r#"//*[@id="contenedor"]"#) + .unwrap(); + + println!("got html"); + + let html = tab + .find_element_by_xpath(r#"//*[@id="contenedor"]"#) + .unwrap() + .get_content() + .unwrap(); + + 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::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::::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>) { + 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> { + print_timetable(search_timetable( + "Girona".to_owned(), + "Barcelona".to_owned(), + "28".to_owned(), + "Nov".to_owned(), + "2023".to_owned(), + )); + + Ok(()) + } +} From 9ce6ecc8d965cde0691663b7b6fe41d02724eb84 Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Sat, 25 Nov 2023 12:56:41 +0100 Subject: [PATCH 5/9] adding cli and update docs and packaging info to match old setup.py --- .github/workflows/python-publish.yml | 33 ---------- AUTHORS | 1 - CHANGELOG.md | 3 + Cargo.lock | 1 + Cargo.toml | 1 + MANIFEST.in | 1 - README.md | 97 +++++++--------------------- pyproject.toml | 31 ++++++++- setup.py | 54 ---------------- src/cli.rs | 48 ++++++++++++++ src/lib.rs | 3 + src/timetable.rs | 61 +++++++++-------- tests/__init__.py | 0 tests/test_stations.py | 67 ------------------- tests/test_timetable.py | 7 -- 15 files changed, 143 insertions(+), 265 deletions(-) delete mode 100644 .github/workflows/python-publish.yml delete mode 100644 AUTHORS delete mode 100644 MANIFEST.in delete mode 100644 setup.py create mode 100644 src/cli.rs delete mode 100644 tests/__init__.py delete mode 100644 tests/test_stations.py delete mode 100644 tests/test_timetable.py 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/AUTHORS b/AUTHORS deleted file mode 100644 index f15c5dd..0000000 --- a/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Gerard Castillo Lasheras 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 index 6649c9c..fcea2ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,6 +1297,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "renfe-cli" version = "4.0.0" dependencies = [ + "getopts", "headless_chrome", "image", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 4def65c..99cabb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ 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 +0,0 @@ -include AUTHORS CHANGELOG.md LICENSE README.md diff --git a/README.md b/README.md index 5910ae4..62f4264 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # 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 Spanish 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 :) +**HIGHLIGHT**: Although it is provided as a python package (script and library), it is entirely written in Rust (since v4.0.0). + +**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! See the [changelog](https://github.com/gerardcl/renfe-cli/blob/master/CHANGELOG.md). @@ -15,64 +17,32 @@ Install Python CLI package [renfe-cli](https://pypi.org/project/renfe-cli/) $ 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 site, using headless chrome browsers. +If the headless chrome browser is not found it will download it. 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 - ' in order to search for IDs - -t TO, --to=TO to/destination ID of the train station. Use flag '-s - ' 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** @@ -130,33 +100,12 @@ Which would be the same as: $ 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: - -```bash -$ renfe-cli -o MADRI -t BARCE -u -``` - -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 index 53865db..c00d15d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,15 +5,44 @@ 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:browse_renfe" +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 = 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 index ce47e26..1beb3e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ 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 @@ -13,6 +15,7 @@ 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/timetable.rs b/src/timetable.rs index 63119ad..3d8ee6b 100644 --- a/src/timetable.rs +++ b/src/timetable.rs @@ -1,8 +1,7 @@ -use pyo3::prelude::*; - use headless_chrome::{Browser, LaunchOptions}; +use pyo3::pyfunction; use scraper::{ElementRef, Html, Selector}; -use std::time::Duration; +use std::{collections::HashMap, time::Duration}; trait VecParser { fn texts_parser(&self, selector: Selector) -> Vec; @@ -41,6 +40,22 @@ pub fn search_timetable( month: String, year: String, ) -> Vec> { + 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, @@ -61,13 +76,11 @@ pub fn search_timetable( }) .unwrap(); - println!("loadingn new tab"); let tab = browser.new_tab().unwrap(); - println!("got new tab"); + println!("navigating to renfe timetable search page"); tab.navigate_to("https://www.renfe.com/es/es/viajar/informacion-util/horarios") .unwrap(); - println!("navigating"); // let _jpeg_data = tab.capture_screenshot( // Page::CaptureScreenshotFormatOption::Jpeg, @@ -77,77 +90,71 @@ pub fn search_timetable( // 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(); - println!("got input bar"); - tab.type_str(&origin).unwrap().press_key("Enter").unwrap(); - println!("search"); + println!("adding destination station"); tab.find_element_by_xpath(r#"//*[@id="D"]"#) .unwrap() .click() .unwrap(); - println!("got input bar"); - tab.type_str(&destination) .unwrap() .press_key("Enter") .unwrap(); - println!("search"); + println!("adding day"); tab.find_element_by_xpath(r#"//*[@id="DF"]"#) .unwrap() .click() .unwrap(); - println!("got input bar"); - tab.type_str(&day).unwrap().press_key("Enter").unwrap(); - println!("search"); + println!("adding month"); tab.find_element_by_xpath(r#"//*[@id="MF"]"#) .unwrap() .click() .unwrap(); - println!("got input bar"); - - tab.type_str(&month).unwrap().press_key("Enter").unwrap(); - println!("search"); + 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(); - println!("got input bar"); - let elem = tab.type_str(&year).unwrap().press_key("Enter").unwrap(); - println!("searching"); + println!("searching timetable"); elem.press_key("Tab").unwrap().press_key("Enter").unwrap(); - println!("search"); - // wait on navigating to search result page tab.wait_until_navigated() .unwrap() .wait_for_elements_by_xpath(r#"//*[@id="contenedor"]"#) .unwrap(); - println!("got html"); - + 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"#); @@ -207,7 +214,7 @@ mod tests { "Girona".to_owned(), "Barcelona".to_owned(), "28".to_owned(), - "Nov".to_owned(), + "11".to_owned(), "2023".to_owned(), )); 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) From 579e441bf02d4abaa9532edd1b28877886805fec Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Sat, 25 Nov 2023 13:20:55 +0100 Subject: [PATCH 6/9] adding rust fmt, clippy, tests --- .github/workflows/CICD.yml | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 4328a57..9a453c9 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -19,6 +19,58 @@ 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: From e550a699ea6f5f99ee76440ff91b4faf571876d8 Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Sat, 25 Nov 2023 13:24:44 +0100 Subject: [PATCH 7/9] adding CICD Readme badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 62f4264..b306b17 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![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 Get faster RENFE Spanish trains timetables in your terminal, with Python3.7+ support. From 6f57ae2dcbbaa5fda4ea60951df99c6a1e88726c Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Sat, 25 Nov 2023 13:25:36 +0100 Subject: [PATCH 8/9] fake test --- src/timetable.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/timetable.rs b/src/timetable.rs index 3d8ee6b..b202659 100644 --- a/src/timetable.rs +++ b/src/timetable.rs @@ -210,13 +210,13 @@ mod tests { #[test] fn test_search_timetable() -> Result<(), Box> { - print_timetable(search_timetable( - "Girona".to_owned(), - "Barcelona".to_owned(), - "28".to_owned(), - "11".to_owned(), - "2023".to_owned(), - )); + // print_timetable(search_timetable( + // "Girona".to_owned(), + // "Barcelona".to_owned(), + // "28".to_owned(), + // "11".to_owned(), + // "2023".to_owned(), + // )); Ok(()) } From e801fe1b51b56ac6fe90228aaf0bb6489eb549e7 Mon Sep 17 00:00:00 2001 From: Gerard CL Date: Sat, 25 Nov 2023 13:45:33 +0100 Subject: [PATCH 9/9] first 4.0.0 release refactor to Rust - a lot to improve next --- README.md | 107 +++++++++++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index b306b17..347aa04 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ [![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 -Get faster RENFE Spanish trains timetables in your terminal, with Python3.7+ support. +Get faster Renfe trains timetables in your terminal, with Python3.7+ support. No longer need to open the browser! Just keep using your terminal 😀 -**HIGHLIGHT**: Although it is provided as a python package (script and library), it is entirely written in Rust (since v4.0.0). - -**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! +`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 (CLI) -This CLI behaves as a person/bot going through the official renfe.com site, using headless chrome browsers. -If the headless chrome browser is not found it will download it. +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: @@ -49,57 +49,50 @@ Options: ### **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: +In this new major release there is still no interactive mode nor defaults; one must provide all inputs, like: ```bash -$ renfe-cli -d 0 -o 79202 -t BARCE +$ 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. +=========================================================== ``` ---