eza-0.18.2/.cargo_vcs_info.json0000644000000001360000000000100117030ustar { "git": { "sha1": "cc9924937fc82e0d3a187a87d21f47eb7367b459" }, "path_in_vcs": "" }eza-0.18.2/.envrc000064400000000000000000000000421046102023000116050ustar 00000000000000if has nix; then use flake . fi eza-0.18.2/.git-blame-ignore-revs000064400000000000000000000022641046102023000145770ustar 00000000000000# This file contains a list of commits that are not likely what you # are looking for in a blame, such as mass reformatting or renaming. # You can set this file as a default ignore file for blame by running # the following command. # # $ git config blame.ignoreRevsFile .git-blame-ignore-revs # # To temporarily not use this file add # --ignore-revs-file="" # to your blame command. # # The ignoreRevsFile can't be set globally due to blame failing if the file isn't present. # To not have to set the option in every repository it is needed in, # save the following script in your path with the name "git-bblame" # now you can run # $ git bblame $FILE # to use the .git-blame-ignore-revs file if it is present. # # #!/usr/bin/env bash # repo_root=$(git rev-parse --show-toplevel) # if [[ -e $repo_root/.git-blame-ignore-revs ]]; then # git blame --ignore-revs-file="$repo_root/.git-blame-ignore-revs" $@ # else # git blame $@ # fi # treewide rustfmt https://github.com/eza-community/eza/pull/405 0e06409b07f060e3afe1c099c4c54e6504847ee0 # initial powertests https://github.com/eza-community/eza/pull/644/commits 2273e29bc006baeb76795ae40c7b8b76f61c6f26 4f949fc9bbb1e387b489ad841af56e7be448bef3 eza-0.18.2/.github/CODEOWNERS000064400000000000000000000024611046102023000134310ustar 00000000000000# Lines starting with a '#' are comments. # Each line is a file pattern followed by one or more owners. # These owners will be the default owners for everything in the repository. # * @global-owner1 @global-owner2 # The 'docs/*' pattern will match files like # 'docs/getting-started.md' but not further nested files like # 'docs/build-app/troubleshooting.md'. # docs/* @doc-owner-team # You can also use email addresses if the user isn't on GitHub. # *.py admin@example.com # You can use a '*' at the end of a pattern to match all files # of a particular type. # *.* @all-file-types-owner # Order is important. The last matching pattern has the most precedence. # This means if a pull request touches both *.js and *.css files, # it will only request a review from @js-owner, not @css-owner. # *.js @js-owner # *.css @css-owner # Nix files *.nix @cafkafk # Documentation *.md @cafkafk # Configuration *.toml @cafkafk # Version bumps Cargo.lock @cafkafk # Testing, builds, etc devtools/* @cafkafk tests/* @cafkafk xtests/* @cafkafk # Debian files devtools/deb-package.sh @gierens deb.asc @gierens # Nushell completions completions/nush/* @PThorpe92 # Grid + Output src/output/grid.rs @PThorpe92 src/output/grid_details.rs @PThorpe92 src/output/file_name.rs @PThorpe92 # Options parsing src/options/* @MartinFillon eza-0.18.2/.github/FUNDING.yml000064400000000000000000000000201046102023000136400ustar 00000000000000github: cafkafk eza-0.18.2/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000012071046102023000167100ustar 00000000000000--- name: Bug report about: Report a crash, runtime error, or invalid output in eza title: 'bug: ' labels: errors assignees: '' --- If eza does something unexpected, or its output looks wrong, or it displays an error on the screen, or if it outright crashes, then please include the following information in your report: - The version of eza being used (`eza --version`) - The command-line arguments you are using - Your operating system and hardware platform If it’s a crash, please include the full text of the crash that gets printed to the screen. If you’re seeing unexpected behaviour, a screenshot of the issue will help a lot. --- eza-0.18.2/.github/ISSUE_TEMPLATE/compilation_error.md000064400000000000000000000011241046102023000202650ustar 00000000000000--- name: Compilation error about: Report a problem compiling eza title: 'ci: ' labels: '' assignees: '' --- If eza fails to compile, or if there is a problem during the build process, then please include the following information in your report: - The exact eza commit you are building (`git rev-parse --short HEAD`) - The version of rustc you are compiling it with (`rustc --version`) - Your operating system and hardware platform - The Rust build target (the _exact_ output of `rustc --print cfg`) If you are seeing compilation errors, please include the output of the build process. --- eza-0.18.2/.github/ISSUE_TEMPLATE/config.yml000064400000000000000000000000331046102023000162020ustar 00000000000000blank_issues_enabled: true eza-0.18.2/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000001721046102023000177430ustar 00000000000000--- name: Feature request about: Request a feature or enhancement to eza title: 'feat: ' labels: '' assignees: '' --- eza-0.18.2/.github/ISSUE_TEMPLATE/question.md000064400000000000000000000002161046102023000164060ustar 00000000000000--- name: Question about: Ask a question about eza title: '' labels: question assignees: '' --- This should be posted in Q&A in discussions eza-0.18.2/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md000064400000000000000000000015711046102023000222570ustar 00000000000000 ##### Description ##### How Has This Been Tested? eza-0.18.2/.github/dependabot.yml000064400000000000000000000012541046102023000146650ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "github-actions" # Workflow files stored in the # default location of `.github/workflows` directory: "/" schedule: interval: "weekly" eza-0.18.2/.github/workflows/apt.yml000064400000000000000000000016461046102023000154060ustar 00000000000000name: Apt Installation on: schedule: - cron: '0 0 * * *' push: branches: [ main ] paths: - 'deb.asc' - '.github/workflows/apt.yml' pull_request: branches: [ main ] paths: - 'deb.asc' - '.github/workflows/apt.yml' concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: apt_installation: runs-on: ubuntu-latest steps: - name: Install eza via apt repo run: | wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo tee /etc/apt/trusted.gpg.d/gierens.asc && \ echo "deb http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list && \ sudo apt update && \ sudo apt install -y eza - name: Run eza run: eza - name: Open man page run: man eza | cat eza-0.18.2/.github/workflows/audit.yml000064400000000000000000000005401046102023000157200ustar 00000000000000name: Security audit on: schedule: - cron: '0 0 * * *' push: paths: - '**/Cargo.toml' - '**/Cargo.lock' jobs: security_audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: taiki-e/install-action@cargo-deny - name: Scan for vulnerabilities run: cargo deny check advisories eza-0.18.2/.github/workflows/conventional-commits.yml000064400000000000000000000006531046102023000207670ustar 00000000000000name: Conventional Commits on: push: branches: [ main ] pull_request: branches: [ main ] concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: build: name: Conventional Commits runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: webiny/action-conventional-commits@v1.3.0 eza-0.18.2/.github/workflows/flake.yml000064400000000000000000000032231046102023000156750ustar 00000000000000on: workflow_dispatch: pull_request: branches: [ main ] paths: - '.github/workflows/flake.yml' - 'src/**' - 'Cargo.*' - "*.toml" - "flake.*" - "*.nix" - "*.rs" push: branches: [main] paths: - '.github/workflows/flake.yml' - 'src/**' - 'Cargo.*' - "*.toml" - "flake.*" - "*.nix" - "*.rs" concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: flake-checker: name: Flake Checker runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Check Nix flake inputs uses: DeterminateSystems/flake-checker-action@v5 check: name: Check Nix Flake runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@v9 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@main - name: Nix Flake Check run: nix flake check --all-systems build: name: Build Nix package # if cross compilation is desired add 'aarch64-linux', 'x86_64-darwin' and 'aarch64-darwin' and fix the flake to support cross compilation. strategy: matrix: target: [x86_64-linux] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@v9 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@main - name: Nix Build run: nix build .#packages.${{ matrix.target }}.default eza-0.18.2/.github/workflows/publish-flake.yml000064400000000000000000000016611046102023000173450ustar 00000000000000name: "Publish flake" on: push: tags: - "v?[0-9]+.[0-9]+.[0-9]+" - "v?[0-9]+.[0-9]+.[0-9]+*" workflow_dispatch: inputs: tag: description: "Existing tag to publish" type: "string" required: true jobs: publish-flakestry: name: "Publish to `flakestry.dev`" runs-on: ubuntu-latest permissions: id-token: "write" contents: "read" steps: - uses: flakestry/flakestry-publish@main with: version: "${{ inputs.tag || github.ref_name }}" publish-flakehub: name: "Publish to `flakehub.com`" runs-on: ubuntu-latest permissions: id-token: "write" contents: "read" steps: - uses: "actions/checkout@v4" - uses: "DeterminateSystems/nix-installer-action@main" - uses: "DeterminateSystems/flakehub-push@main" with: visibility: "public" tag: "${{ inputs.tag || github.ref_name }}" eza-0.18.2/.github/workflows/unit-tests.yml000064400000000000000000000024611046102023000167350ustar 00000000000000name: Unit tests on: push: branches: [ main ] paths: - '.github/workflows/unit-tests.yml' - 'src/**' - 'Cargo.*' - build.rs pull_request: branches: [ main ] paths: - '.github/workflows/unit-tests.yml' - 'src/**' - 'Cargo.*' - build.rs concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUSTFLAGS: --deny warnings jobs: unit-tests: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.rust == 'nightly' }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [1.70.0, stable, beta, nightly] steps: - name: Checkout repository uses: actions/checkout@v4 - run: rustup toolchain install ${{ matrix.rust }} --profile minimal - uses: Swatinem/rust-cache@v2 - name: Install cargo-hack uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: cargo install cargo-hack - name: Run rustfmt checks run: cargo fmt --check - name: Run clippy lints run: cargo clippy -- -D warnings - name: Run unit tests run: cargo hack test eza-0.18.2/.github/workflows/winget.yml000064400000000000000000000005161046102023000161120ustar 00000000000000name: Publish to Winget on: release: types: [released] jobs: publish: runs-on: ubuntu-latest steps: - uses: vedantmgoyal2009/winget-releaser@v2 with: identifier: eza-community.eza installers-regex: '-pc-windows-gnu\.zip$' token: ${{ secrets.WINGET_TOKEN }} eza-0.18.2/.gitignore000064400000000000000000000007271046102023000124710ustar 00000000000000# Nix Flake stuff result # Generated by nix-pre-commit-hooks /.pre-commit-config.yaml # Direnv .direnv # Rust stuff target # Vagrant stuff .vagrant *.log # Compiled artifacts # (see devtools/*-package-for-*.sh) /eza-linux-x86_64 /eza-linux-x86_64-*.zip /eza-macos-x86_64 /eza-macos-x86_64-*.zip /eza_*.deb /MD5SUMS /SHA1SUMS # Snap stuff parts prime stage *.snap # VHS testing stuff out.gif tests/tmp ## Dynamically generated tests/test_dir # Miscenallous .idea eza-0.18.2/CHANGELOG.md000064400000000000000000000776321046102023000123230ustar 00000000000000# Changelog ## [0.18.2] - 2024-02-08 ### Bug Fixes - Update libgit2 to 1.7.2 ## [0.18.1] - 2024-02-08 ### Bug Fixes - Change shasum for main commit ### Documentation - Add manual installation section ### Miscellaneous Tasks - Release eza v0.18.1 ### Refactor - Replace scoped_threadpool with rayon ### Build - Add empty rustfmt to ensure project specific settings - Bump libc from 0.2.151 to 0.2.152 - Bump nick-fields/retry from 2 to 3 - Bump palette from 0.7.3 to 0.7.4 - Bump webiny/action-conventional-commits from 1.2.0 to 1.3.0 ## [0.18.0] - 2024-02-01 ### Features - [**breaking**] Add --classify=always,auto,never ### Miscellaneous Tasks - Remove rustfmt config file that has a nightly only option in favor of rustfmt skip directive which is already in place - Fix small typo in pull request template - Release eza v0.18.0 ### Refactor - Change cast to coertion, remove rustfmt skip and clippy lint ignore directives ### Testing - Regenerate classification related tests ### Build - Change flake inputs ## [0.17.3] - 2024-01-25 ### Bug Fixes - Remove version testing ### Miscellaneous Tasks - Avoid `unwrap()` by changing filter-then-map to `filter_map` - Release eza v0.17.3 ### Build - Bump shlex from 1.2.0 to 1.3.0 - Bump chrono from 0.4.31 to 0.4.33 - Bump trycmd from 0.14.19 to 0.14.20 ## [0.17.2] - 2024-01-20 ### Bug Fixes - Crash using --git-repos on unreadable dir - Crash using --git-repos on unreadable dir ### Miscellaneous Tasks - Release eza v0.17.2 ### Build - Add cargo-bump for releasing ## [0.17.1] - 2024-01-11 ### Bug Fixes - Offset widths in grid mode with utf8 filenames - Format the code - Unformat the code where needed - Format the code correctly this time - Redo everything from scratch - Stack overflow when '-laaR' are used - Stack overflow when '-laaR' is used ### Features - Add Fortran icons ### Miscellaneous Tasks - Adding blake3 to checksums - Release eza v0.17.1 ### Testing - Regenerate version tests... and others - Updated tests to fit new features ### Build - Add b3sum to devshell deps ## [0.17.0] - 2023-12-13 ### Bug Fixes - Add color scale mode to the bash completions - Add color scale mode to the fish completions - Quote symbolic links correctly when their destinations contain spaces ### Documentation - Modify documentation about custom time style ### Features - Add BSD file flags - Add Windows file attributes - [**breaking**] Support different custom time style for non-recent/recent files ### Miscellaneous Tasks - Release eza v0.17.0 ### Testing - Regen powertests - Regenerate - Add tests for non-recent/recent custom time style - Update powertest expected help message output ### Build - Update `flake.lock` - Bump DeterminateSystems/nix-installer-action from 8 to 9 - Bump once_cell from 1.18.0 to 1.19.0 - Bump libc from 0.2.150 to 0.2.151 ### Ci - Remove labeler ## [0.16.3] - 2023-12-07 ### Bug Fixes - Add bare git_repos fn if feature git is disabled - Fixing color of size unit - Color-scale broked size for colors ### Miscellaneous Tasks - Release eza v0.16.3 ### Testing - Fix powertests post-release ### Build - Bump percent-encoding from 2.3.0 to 2.3.1 - Bump actions/labeler from 4 to 5 ## [0.16.2] - 2023-11-30 ### Bug Fixes - Calculate width correctly when using grid icons & classify - Fix the windows build ### Miscellaneous Tasks - Release eza v0.16.2 ### Testing - Fix version tests ### Build - Bump webiny/action-conventional-commits from 1.1.0 to 1.2.0 - Bump DeterminateSystems/nix-installer-action from 7 to 8 - Bump windows-sys from 0.48.0 to 0.52.0 ## [0.16.1] - 2023-11-23 ### Bug Fixes - Don't panic with todo!() on inaccessible dir - Don't panic if the btime of a file is Err - Lifetime annotations and manpage/shell completion nits - Reflow help ### Features - Handle formatting and display of binary extended attributes. - Add netbsd and freebsd support for extended attributes ### Miscellaneous Tasks - Update flake inputs - Release eza v0.16.1 ### Testing - Vars mocking - Display and meta options - Filtering and sorting - Long view options - Regenerate `--help` tests ### Build - Sign release tags ## [0.16.0] - 2023-11-16 ### Bug Fixes - Fix cross compilation - Snap requires a base - Move `--smart-group` to long view options - Colo[u]r-scale didn't have a base value - Fix snapcraft.yaml formatting ### Documentation - Add comments for bzip variants - Added the fact that total-size is unix only ### Features - Add some file extensions - Abort on panic (saving 0.1 M) - Add powertest ### Miscellaneous Tasks - Ignore blame from #644 - Stabilize powertest version - Release eza v0.16.0 ### Testing - Implements tests using the generated directory - Powertests using generated testdirs - Add hashed versions of powertests ## [0.15.3] - 2023-11-09 ### Bug Fixes - [**breaking**] Remove Repo column when using --git-repos when no git repo - Reformat `help.rs` - Allow unused macro rule arms ### Documentation - Improve CONTRIBUTING.md, README.md - Improve README.md - Introduce INSTALL.md ### Features - Create EZA_ICONS_AUTO environment variable - Create EZA_ICONS_AUTO environment variable - Demo gif and gif generation recipe - Add ocaml icon filetypes - Add PRQL - Add `--color-scale` ### Miscellaneous Tasks - Add to CODEOWNERS file to make sure I get ping'd on files being touched - Add myself to codeowners to watch modifications on parsing - Improve the PR template - Release eza v0.15.3 ### Refactor - Remove commented out test code - Finalize `decay` -> `color_scale` ### Build - Refactor flake - Bump DeterminateSystems/nix-installer-action from 4 to 7 - Bump libc from 0.2.149 to 0.2.150 - Bump rustix from 0.38.13 to 0.38.21 ### Ci - Refactor pre-commit-hooks - Refactor publish workflow ## [0.15.2] - 2023-11-02 ### Bug Fixes - Correct width when --no-quotes is used - Clippy lint and add option to grid-details - Changed quote in --almost-all completion - --smart-group only works for current user ### Features - Add Typst to the recognized files ### Miscellaneous Tasks - Release eza v0.15.2 ### Refactor - Replace `lazy_static` with `once_cell` - Replace plain values with TextColours ### Testing - Added more content to the dir generator - Changed size of one of the files ## [0.15.1] - 2023-10-26 ### Bug Fixes - Don’t display target’s size if we’re not dereferencing - Updated match indents - Changed flag name - Only store top-level recursive dir size - Changed windows methods - Underscored unused windows variables - Added device for filesystem to hashmap - Display offset for filenames with spaces - Fix clippy warnings - Fix doc-tests on RecursiveSize - Fix dead_code warnings on Windows ### Documentation - Fix doc-tests formatting and address other documentation review requests ### Features - Support for displaying blocksize on directories ### Miscellaneous Tasks - Release eza v0.15.1 ### Refactor - Move total-size calculations to File - Add RecursiveSize type to simplify total-size calculation ## [0.15.0] - 2023-10-19 ### Bug Fixes - Reenable debug symbols in debug builds - Fmt, windows, and nix fixes - Clippy lint - Merge conflict with main - Reverted autofmt changes ### Documentation - Correct color option spellings - Added flag to readme - Added flag to man ### Features - Add option --smart-group - Add completions, man for --smart-group - Added recursive directory parser - Add icons=always,auto,never. dont display icons in a tty|piped - Fix auto value for colors and icons + documentation - Added flag to completions - [**breaking**] Remove --no-icons in favor of --icons=always,auto,never. default is auto - Add a new filetype for source code files - Add a new icons for source code files and other files ### Miscellaneous Tasks - Upgrade to uutils_term_grid from unmaintained term_grid - Release eza v0.15.0 ### Build - Bump DeterminateSystems/nix-installer-action from 5 to 6 ### Ci - Remove stalebot, is super annoying - Adjust test case to icons=auto (no icons should show due to tty) ## [0.14.2] - 2023-10-12 ### Bug Fixes - Comment out redundant static musl build - Refactor sed command to build manpages - Update additional completions for help, almost-all, dereference - Fix zsh completions ### Documentation - Add missing options to man page and CLI --help info ### Features - Add missing nu shell completions - Adding the EZA_OVERRIDE_GIT env var ### Miscellaneous Tasks - Release eza v0.14.2 ### Refactor - Use musl target for amd64 deb package - Directly use one "big" awk command ### Styling - Remove trailing spaces and trailing line ### Build - Bump libc from 0.2.148 to 0.2.149 - Bump DeterminateSystems/nix-installer-action from 4 to 5 ## [0.14.1] - 2023-10-08 ### Bug Fixes - Replace left-over exa in fish completion - Diabling static linked binaries due to segfault - Make os error 13 fail loud - Adjust change width calculations for hyperlink and classify - Root group not painted as expected when eza used by root ### Documentation - Fix typos - Add zsh with homebrew part to completions section - Installation on fedora updated ### Features - Add basic nushell completion file - Add codeowner for nu completions - Readded musl static bin as it works ### Miscellaneous Tasks - Release eza v0.14.1 ### Refactor - Align completions - Do not match for numbers and remove single-use fn - Consistent argument order ### Testing - Classify-hyperlink test case for width 50 - Move classify tests to local ### Build - Make checksums easier to copy-paste - Bump trycmd from 0.14.17 to 0.14.19 - Improve release automation - Fix version bump - Fix double echo - Automate gh release - Add `codegen-units = 1` and remove `opt-level = 3` - Add back `opt-level = 3` ### Ci - Treat warnings as errors ## [0.14.0] - 2023-10-02 ### Bug Fixes - Merge conflict with main - Merge conflict with main - Avoid unstable inner attributes - Query stdout terminal size to see if the output gose to a tty. - Use windows-specific API for terminal size query on windows - Add `windows-sys` dependency for targeting windows - Fix manpage generation of default package - Use `std::io::IsTerminal` to eliminate compatibility issue - Changed dll icon - Changed readme and Added README icon - New R lang icon - Terminal size query should only check `stdout` - Prefix unused binding name with underscore - Fix large_enum_variant warning with explanation - README is sorted and formatted ### Documentation - Add completions + manpage for --no-quotes flag - Leave nix install instructions open-ended - Leave nix install instructions open-ended - Documenting custom time-style - Added the new colors option to the man - Adding termux section - Time-format supporting custom formats - Description of `--color` in README, manpage, and completions - Change `color` to `colo[u]r` in the option description. - Updated man to add new colors - Correct CONTRIBUTING.md on commit message type ### Features - Add quotations around filenames with spaces. exa pr#1165 - Add rustfmt.toml file to prevent flags.rs fmt on save - [**breaking**] Separated root from other users - Added statically linked binaries - Replace hardcoded version by version variable - Add header to colors-explanation page - Revise man rule to use for loop and insert version - New Rust icon - Added bdf,psf icons - Added lib icon - Added Contacts,Favorites icons - Added home icon - Added fdmdownload icon - Adding the possibility to change git-repos colors ### Miscellaneous Tasks - Release 0.14.0 ### Refactor - Ignore options/flags.rs - Renamed and reintended some code - Reformatted a line ### Styling - Format some parts correctly ### Build - Bump unicode-width from 0.1.10 to 0.1.11 - Bump git2 from 0.18.0 to 0.18.1 - Temporarily disable aarch64-unknown-linux-gnu - Name static binaries ## [0.13.1] - 2023-09-25 ### Bug Fixes - Typo `this` -> `that` - Error for missed semicolon - More than 3 bools in a struct - Major and minor device on MacOS - Linux uses u32 for major/minor device numbers - Respect spec on Windows and make it for with Konsole - Don’t show color when color is disabled - Enable rustfmt by removing .rustfmt.toml which disables it - Replace rustfmt::skip on expressions because experimental - Remove unnecessary rustfmt::skip's in windows code - Add src/options/flags.rs to rustfmt.excludes - Left-over merge conflict in src/output/table - Ignore refs for blame ### Documentation - Update README.md - Update --mounts option to include MacOS - Documenting --only-files ### Features - Listing files only using '--only-files' flag - Add EXA_COLOR bindings for un-themed items - Add EZA_ environment variables with fallback to EXA_ - Add rustfmt check to unit-tests workflow ### Miscellaneous Tasks - Add completion for --only-fies (zsh,fish) - Release 0.13.1 ### Refactor - Fix rustfmt issues and place skips where needed - Reorder unit-tests to fmt, clippy and tests ### Styling - Formatted using treefmt - Fix clippy warning after rustfmt - Fix treefmt issues in options module - Reapply rustfmt after rebase from main ### Testing - Test for listing files only - Add unit tests for new style abbreviations - Regen git_repos_no_status ### Build - Bump actions/checkout from 2 to 4 - Bump chrono from 0.4.30 to 0.4.31 - Bump timeago from 0.4.1 to 0.4.2 - Bump libc from 0.2.147 to 0.2.148 - Bump terminal_size from 0.2.6 to 0.3.0 ### Ci - Added formatters to treefmt - Make various improvements - Only apply labels when opening a PR ## [0.13.0] - 2023-09-18 ### Bug Fixes - Crate can't contain broken symlink - Remove executable flag from fish completion file - Use proc_mounts only on linux - Hotfix harmful documentation - Fix hyperlinks on Windows - Nix flake check also builds the package - [**breaking**] Change number_huge and unit_huge to match the man page short codes ### Documentation - Added cafkafk suggestions - Fix codeblocks in zsh completions - Update README.md - Document filetypes theme and rename trait - Link directly to space - Add Mac support for the --mount option in the man page - Add SAFETY comments to unsafe code blocks - Update deb instructions to use keyring - Fix chmod in deb installation instructions - Add potential gpg install to deb installation instructions - Document character style pairs in the code and match with man page - Add install instructions for Void Linux - Documentation of 'sn' and 'sb' conflicted with later docs - Document dimmed and italic style codes ### Features - Add completion files in deb packaging script - Adds filtering for Windows hidden files - Support --mount option on Mac - Support --mount option on Mac - Lazy loading of a files extended attributes and absolute path ### Miscellaneous Tasks - Augment gitter size in README - Release 0.13.0 ### Performance - Add criterion for benchmarking ### Refactor - Refactor just in crossfile - DRY up justfile - Ignore missing MSVC docker image - Removed unused imports, mark mods as allow unused - Format code - Move ALL_MOUNTS to fs::mounts - Migrate ALL_MOUNTS from lazy_static to OnceLock ### Testing - Stabalised unit-tests.yml - Autogenerate testing dir - Autogenerate test dirs - Generate device files - Add unit tests that test both exa and ls style codes together - Address variable names ### Build - Add musl binary for linux - Fix checksums - Add TODOs to targets - Set optlevel to 3 ### Ci - Add nix Flake check to flake.yml - Removed nix build in favor of nix flake check - Include bash completion script in treefmt and fixed shellcheck formatting in completion script - Fix windows build - Fix spelling attemps -> attempts ## [0.12.0] - 2023-09-14 ### Bug Fixes - Expand `--all` help - RUSTSEC-2020-0071 - Generalize gitignore to ignore all eza deb packages - Canonicalize errors when the destination of a symbolic link is bad - Handle other canonicalize errors in hyperlinks and git - Fix windows build when canonicalize returns an error - Change trycmd config to use test/itest folder for testing - Revert to old apt install command suggestion and add hint - Remove stray backslashes - Is_some_and is an unstable Rust feature until 1.70 - Revert "Support for Windows Hidden Files" - Shellcheck warnings - Revert "Support for Windows Hidden Files" - Shellcheck warnings - Exit 13 on os error 13 - Rewrite comment - Improve trace strings - Tracing typo ### Documentation - Expand `--all` documentation - Add pthorpe92 gist - Remove xtests section from readme - Add deprecation warning to xtests/readme - Add deprecation warning to just xtest commands - Add deprecation warning to vagrantfile - Add MacPorts install info - Add gentoo - Fix gentoo install - Add docs for --git-repos & --git-repos-no-status - Fix gpg armor flag for deb release in readme - Add better explanation of git repos + no status - Add scoop install info - Add Winget install info - Added instructions to install completions of eza to the readme - Remove color specifications. change unknown git repo status to `~` - Fix missing color specification from man page - Remove license from developemnt section - Update rust badge - Add missing man page for debian release ### Features - Add audit workflow - Add trycmd as dev-dependency - Add minimal trycmd binary - Add a few trycmd tests as example - Document and change output for --git-repos - Add apt installation workflow - Adds filtering on Windows hidden files - Adds filtering on Windows hidden files - Adds filtering on Windows hidden files - Added shellcheck to treefmt - Adds filtering on Windows hidden files - Add PERMISSION_DENIED exit code ### Miscellaneous Tasks - Bump chrono from 0.4.27 to 0.4.30 - Removal of xtests - Removal of vagrant - Remove deprecated devtools - [**breaking**] MSRV 1.70 - Run spellcheck - Release 0.12.0 ### Refactor - Rename FileType::Immediate to more obvious FileType::Build - Over-engineer deb-package.sh - Hide xtests folder - Split trycmd into tests for all, unix and windows - Limit unit-tests run on workflow change to unit-tests itself - Moved generateTest.sh to devtools/ - Renamed the file - Add tracing to various code parts - Make std::process::exit global ### Revert - "Support for Windows Hidden Files" ### Styling - Remove TODO message on the absolute_path property - Fix shellcheck issues in deb-package.sh - Fix shellcheck issues in deb-package.sh - Fix shellcheck issues in deb-package.sh ### Testing - Remove vhs from flake - Remove vhs-runner files - Dump trycmd from nix sandbox - Fix name of trydump - Add trycmd - Add nix feature - Add example long tests for sandbox - Set itests files to unix epoch - Set itest files to unix epoch - Refactor setting unix epoch - Auto discard old definitions - Fix test reference - Add long_all_nix.toml - Add long_blocksize_nix.toml - Add long_extended_nix.toml - Add long_git_nix.toml - Add long_git_repos_nix.toml - Add long_git_repos_no_status_nix.toml - Add long_grid_nix.toml - Add long_header_nix.toml - Add long_icons_nix.toml - Add long_octal_nix.toml - Add long_time_style_relative_nix.toml - Freeze nix tests - Fix trydump when no files to delete - Adding more content to test - Modified unix and all tests - Regenerate nix tests - Convert windows tests with new itest dir - Fixed windows tests being wrong - Added a test generator - Add more unix_tests - Fixed unix tests to remove any distro specific - Removed git test breaking on nix - Remove non-deterministic test ### Build - Add compression, checksum gen for bin - Update flake.lock, cargo.lock - Add deny.toml - Remove org warnings - Remove itest - Update flake.lock - Add itest, idump - Make trycmd part of checks ### Ci - Don't use nix feature on ci - Add Winget Releaser workflow - 1.65 -> 1.70 - Enforce conventional commits - Enforce conventional commits ## [0.11.1] - 2023-09-11 ### Bug Fixes - Add vendored-libgit2 feature to git2 dependency - Filename escaping (last character lost sometimes, no hyperlink) - Build for Windows with chrono - Needless_borrow ### Documentation - Explain vendored-libgit2 - Add homebrew, misc fixes - Fix code of conduct link - Update archlinux - Add star history - Add informaton about lazy_static - Remove broken dependabot link - Add bright color options in man pages - Add bright color support in readme changelog - Document new file type two letter codes in man page ### Features - Add highlighting of mounted directories (Linux only) - Add backlog of icons from various exa pull requests and others - Mark `.git` as ignored, which hides it when using `--git-ignore` - Add backlog of icons from various exa issues - Expose git2 feature vendored-libgit2 - Add build commands to deb-package.sh - Support the MSRV of Rust (1.65.0) - Add bright colour options, change punctuation default - Use chrono crate to handle datetime-related features - Make file types themeable ### Miscellaneous Tasks - Bump actions/checkout from 3 to 4 - Bump uzers to v0.11.3 - Release 0.11.1 ### Testing - Stabilize testing without sandbox - Disable gif rendering ### Build - Add release binaries - Fix binary gen - Add armhf binary ### Deps - Change ansi_term to ansiterm from rustadopt ## [0.11.0] - 2023-09-04 ### Bug Fixes - Add windows implementation of is_empty_dir - Re-align `--git-ignore` in help message - Avoid direnv error if nix isn't installed ### Documentation - Empty dir functions - Document is_empty_dir functions - Add function documentation for get_file_type and icon_for_file. ### Features - Optimize checking for empty directories when a directory has subdirectories - Use perfect hash tables for file types and icons ### Miscellaneous Tasks - Bump git2 from 0.17.2 to 0.18.0 - Bump uzers from 0.11.1 to 0.11.2 - Bump glob from 0.3.0 to 0.3.1 - Bump DeterminateSystems/nix-installer-action from 3 to 4 - Bump terminal_size from 0.1.16 to 0.2.6 - Bump timeago from 0.3.1 to 0.4.1 - Release 0.11.0 ### Refactor - Use phf macros instead of codegen to create icon and filetype tables - Add constants for most of the commonly used icons - Add constants for the rest of icons used multiple times - Rename class FileExtension to FileTypeClassifier to better reflect the purpose - Move get_file_type to FileType enum ### Styling - Is_empty_dir() was put between the unix size() and windows size() ### Build - Use rust stable - Add unstable package - Disable clippy check 'unreadable_literal' in generated files ## [0.10.9] - 2023-08-28 ### Bug Fixes - Respect git-repos flags ### Documentation - Add badge for eza gitter/matrix room - Fix matrix link - Add ignored flags to readme - Add ignored flags to manual - Add ignored flags to help - Add ignored flags to xtest ### Features - `--no-git` option ### Miscellaneous Tasks - Bump DeterminateSystems/flake-checker-action from 4 to 5 - Add funding.yml - Release 0.10.9 ### Tree-wide - Fix Windows build ### Build - Add convco to dev ### Ci - Create flakehub-publish-tagged.yml - Add workflow_dispatch to flakehub-pub - Edit workflow_dispath - Refactor workflow_dispath - Refactor workflow_dispath - Remove broken dispatch - Add flakehub-backfill - Add codeowners - Add gierens as .deb codeowner - Add windows to CI ### Deps - Change users depedency to uzers ## [0.10.8] - 2023-08-22 ### Bug Fixes - TextCell building of detailed grid view for hyperlink and icon options - Block's Colours trait as for file sizes - --blocksize completion, new description - Option.views unit tests use --blocksize - Add missing colon before -w/--width - Replace exa by eza in help string - Change exa to eza in invalid option error - Add missing name section to eza_colors-explanation manpage - Replace exa by eza in .gitignore ### Documentation - Update issue templates - Add git-ignore style/color information to manpage - --blocksize, new description - --blocksize, new description - --blocksize, new description - Cafkafk -> eza-community - Add gpg public key for the deb repository - Add section about debian and ubuntu installation - Add guidelines for commit messages ### Features - Add `just` and `pandoc` to devShell bc they are necessary for man - Add `.envrc` so direnv automatically opens the nix dev environment - Add git-ignored color/style option - Match folder icon to reflect contents - Match folder icon to reflect contents - --blocksize completion, new description - Add script deb-package.sh ### Miscellaneous Tasks - Bump libc from 0.2.93 to 0.2.147 - Bump num_cpus from 1.13.0 to 1.16.0 - Bump git2 from 0.16.1 to 0.17.2 - Bump unicode-width from 0.1.8 to 0.1.10 - Release 0.10.8 ### Refactor - Fs::fields::Blocks - File::blocks() name, revise calculation - Rendering Blocksize like file sizes - Rename Blocks column to Blocksize - Use -S/--blocksize and, var BLOCKSIZE - Unit tests for output.render.blocks - Flip if (as suggested/demanded by clippy) - Migrate to uzers lib ### Build - Add charm to nix develop - Add tests/tmp to gitignore - Add initial tape - Add test runner sketch - Add test runner to justfile - Add out.gif to .gitignore - Add run_tests NAME arg - Add reference main.txt - Add gen_test - Fix typo - Handle arbitrary NAMES - Remove commented out code - Fix code formatting - Add vhs-runner main function - Gen_test support automatic gen - Automatic tape detection - Add print_msg with ansi color - Slight documentation/refactor - Use ansi output on all output - Disable vhs publish ad - Add better tracing - Remove defective sed - Add color variables - Add eza-long test - Add itest testing dir - Add parallel runner ### Ci - Help text in xtests - Nix flake check - Add labeler for flake - Add flake description ### Git - Add deb package to .gitignore ## [0.10.7] - 2023-08-13 ### Bug Fixes - Broken zsh completion syntax - Respect GIT_CEILING_DIRECTORIES - MacOS flake support ### Documentation - Create SECURITY.md - Create CONTRIBUTING.md ### Features - Add gitlab-ci.yml - Improve icon for Earthfile - Better.ps1, add .psd1, .psm1 icons - Replace .bat icon by windows cli icon - Use TeX icons and add .bib, .bst icon - Use Ocaml logo, add .mli, .mll, .mly - Add many more icons - Add -w/--width to help string - Add -w/--width to README - Add -w/--width to flags - Add -w/--width to manpage - Fish -w/--width - Zsh -w/--width ### Miscellaneous Tasks - Add PR template - Bump actions/stale from 5 to 8 - Bump log from 0.4.14 to 0.4.20 - Release 0.10.7 ### Refactor - GIT_DIR handling - Turn unused var into value - Fix borrowed trait implements required - Simplify format strings - Consistent style - Clippy::explicit_auto_deref - Clippy::explicit_auto_deref - Clippy::redundant_else - Clippy::manual_map - Clippy::semicolon_if_nothing_returned - Clippy::extra_unused_lifetimes - Allow clippy::wildcard_in_or_patterns - Clippy::uninlined_format_args - Allow Colours::new call with self - Clippy::explicit_iter_loop - Clippy::uninlined_format_args - Clippy::needless_late_init - Clippy::useless_conversion - Clippy::implicit_clone - Clippy::uninlined_format_args - Clippy::into-iter-on-ref - Clippy::semicolon_if_nothing_returned - Clippy::into_iter_on_ref - Clippy::needless_lifetimes - Clippy::uninlined_format_args - Trivial clippy lints - Clippy::semicolon_if_nothing_returned - Clippy::semicolon_if_nothing_returned - Clippy::manual_let_else - Clippy::semicolon_if_nothing_returned - Clippy::semicolon_if_nothing_returned - Clippy::uninlined_format_args - Clippy::manual_let_else - Clippy::manual_let_else - Clippy::manual_let_else - Clippy::manual_let_else - Clippy::manual_let_else - Fix trivial cast - Clippy::needless-borrow - TerminalWidth::deduce to -w/--width ### Ci - Create pull_request_template.md - Add clippy check - Add dependabot updater ## [0.10.6] - 2023-08-07 ### Bug Fixes - Rename eza-colors-explanation - Exa -> eza in manpage ### Documentation - Adding --git-repos to help. - Add aur, nixpkgs installation ### Features - Use GIT_DIR env var to find the repo - Add color explanations ### Miscellaneous Tasks - Release 0.10.6 ## [0.10.5] - 2023-08-03 ### Bug Fixes - Output wraps in terminal - Respect icon spacing ### Miscellaneous Tasks - Release 0.10.5 ## [0.10.4] - 2023-08-02 ### Bug Fixes - Dereferencing linksfile size. - Dereferencing links users. - Dereferencing links groups. - Dereferencing links permissions. - Dereferencing links timestamps. - Syntax error ### Documentation - Add -X/--dereference flag ### Features - Add symlink dereferencing flag - Add -X/--dereference completions - Add -X/--dereference completions - Added ".out" files for latex - Add changelog generation ### Miscellaneous Tasks - Release 0.10.4 ## [0.10.3] - 2023-07-31 ### Bug Fixes - More JPG extensions - Add compression icon to .tXX files #930 - Fish completion for -i/--inode option - Typo - Use eprintln instead - Use stderr on no timezone info - Bump openssl-src from 111.15.0+1.1.1k to 111.26.0+1.1.1u - Bump openssl-src from 111.15.0+1.1.1k to 111.26.0+1.1.1u - Changed bin name via cargo.toml - Change man pages to reffer to new binary name - Change completions to new binary name - Change completion file names - Change name to eza - Bump git2 from 0.13.20 to 0.16.1 - Fixed grid bug - Fixed grid bug - Bump rust to 1.71.0 - Take -a and -A equally serious - Changed default folder icon - Add clippy as part of the toolchain - Change license icon - Change gpg icons to keys - Add icon for ocaml (.ml extension) - .ipynb icon comment - Better license icon - Replace obsolete icons - Add Svelte icon - Add Emacs icon for .el and org-mode for .org - Added icons for .rmeta - Add icon support for .mjs, .cjs, .mts, .cts files - Add webpack.config.cjs to immediate files list - Removed result - Update --version info - Update snapscraft.yaml - Sort is_immediate - Add flake, autoconf, cargo lock - Added trailing commas - Remove accidentally commited test files ### Documentation - Add hint how to install exa on Android / Termux - Change name in README.md - Add `nix run` to readme - Fix flow issue - Fix typos - Add mandatory snowflake emoji - Document nix flake development - Document nix flakew - Update README.md - Update README.md - Update README.md - Update README.md - Update README.md - Readme change screenshot to eza - Add CoC badge to readme - Add CODE_OF_CONDUCT.md - Add crates.io badge, license badge - Fix links - Update README.md - Update README.md ### Feat - Add JPF to image filetype ### Features - Add support Typescript and ReasonML projects - New Icons and CLI argument to suppress icons - Add sty file - Add julia file extension icon - Add status for git repos - Add selinux contexts support - Add -o shorcut to --octal-permissions - Hyperlink flag - Update Cargo.toml to optimise binaries for size - Update Cargo.toml to optimise binaries for size - Add git-status-.* completions - Zsh add git-status-.* completions - Add git-status-.* completions - Add Zig module icons - Add icon for Vagrantfile - Add git icon to .gitignore_global file - Left align relative time - Add support for --time-style=relative - Add vim icon - Symlinks report their target's valid size - Add justfile - Add pxm - Add compressed types - Add compressed icons ### Fixup - Split prefix tests by property ### Improve - Vim icon ### Makefile - Be compatible with BSD and OS X ### Miscellaneous Tasks - Update zoneinfo_compiled, datetime to 0.5 - Update users to 0.10 - PR feedback - Bump to v0.10.2 - Bump to v0.10.3 - Update cargo lock ### Refactor - Use shorthand fields - Removed commented code - Sorted file types, color table ### StatResult - :Path -> Dir ### Styling - Add icon for reStructuredText (src) files ### Testing - Change to /usr/bin/env bash ### ToStr - :to_str -> ToString::to_string ### Add - Mp2 audio format icon ### Build - Use binary name only ### Ci - Remove unused .github files - Remove unused .github files - Create unit-tests.yml - Create unit-tests.yml - Add trivial nix flake - Add treefmt, rust-toolchain, nixfmt - Add .#test, .#clippy, .#check - Add nix flake - Change branch - Bump rust to 1.71.0 - Automatically mark issues/PRs stale - Run tests when building with nix - Moving actions to dtolnay's version - Update Cargo.toml - Create labeler.yml - Add snap to labeler.yml - Add filetype.rs autolabel ### Details - `filter` is only used when recursing ### Git - Use GIT_DIR env var to find the repo - Use open_from_env before discover ### Git-feature - Display if a file is updated but unmerged (conflicted) ### Icons - Add Gentoo for .ebuild ### Io - :Result -> IOResult ### Src/main.rs - Remove clippy::unnested_or_patterns ### Vagrant - Update apt before installing eza-0.18.2/CODE_OF_CONDUCT.md000064400000000000000000000125671046102023000133050ustar 00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at: matrix: @cafkafk:nixos.dev All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations eza-0.18.2/CONTRIBUTING.md000064400000000000000000000176251046102023000127370ustar 00000000000000# Contributing to eza If you'd like to contribute to eza, there are several things you should make sure to familiarize yourself with first. - Code of conduct [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md) - Requirement of conformance to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) - Requirement of conformance to [Semantic Versioning](https://semver.org/) - The [Security Policy](SECURITY.md) - [Free and Open Source (FOSS) software](https://www.gnu.org/philosophy/free-sw.en.html) ## Hacking on eza It is strongly recommended that you install Nix for hacking on eza. We leverage nix as a way to easily test and distribute eza to many users, and it allows us to provide multiple tools easily for developers. Instead of having to install each dependency manually and setting up a development environment, Nix allows you to use the same environment as the devs use. Therefore, it is useful that you have a version of Nix installed with the "experimental" feature flakes enabled. Further, to make hacking on eza as easy as possible for yourself, you'd do yourself a favor to install [direnv](https://direnv.net/). When you enter the eza repository, if you have `direnv` installed, you'll be prompted to allow it with `direnv allow`. Doing this will save you from having to manually enter the development environment each time you open the folder. If you don't have direnv installed however, you can run `nix develop` in a pinch, to enter the direnv. The development environment includes basic checks of conformance to conventional commits, cargo clippy lints, and much more. It also contains a pre-commit-hook making it a lot easier not to make potential mistakes that will unnecessarily delay getting your PRs accepted. Most importantly, it ensures your commits are conforming to conventional commits. Some useful commands include: - `nix flake check`: checks everything is correct. - `nix build`: build eza. - `nix build .#test`: runs eza's cargo tests - `nix build .#clippy`: runs clippy on eza - `nix fmt`: automatically formats your code as required by flake cheks and pre-commit-hooks.nix - `just itest`: runs integration tests The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`. Run `just --list` to get an overview of what’s available. To compile the manual pages, you will need [pandoc](https://pandoc.org/), which the nix flake should have installed for you. The `just man` command will compile the Markdown into manual pages, which it will place in the `target/man` directory. eza depends on [libgit2](https://github.com/rust-lang/git2-rs) for certain features. If you’re unable to compile libgit2, you can opt out of Git support by running `cargo build --no-default-features`. Again, the nix flake should have taken care of this for you, if not, please file an issue. If you intend to compile for musl, you will need to use the flag `vendored-openssl` if you want to get the Git feature working. The full command is `cargo build --release --target=x86_64-unknown-linux-musl --features vendored-openssl,git`. ## Creating a PR First, use the pull request template. Please make sure that the thing you worked on... actually works. Make sure to also add how you ensured this in the PR description. Further, it's expected that you do your best to check for regressions. If your PR introduces a flag, you MUST: - Add completions for bash, zsh, fish, nushell - Add documentation to the man page - Add your option to the help flag - Add your option to the README.md Before submitting, you SHOULD have run `nix flake check` and ensured that all issues are addressed. For formatting issues, `nix fmt` will format the code for you. Most clippy issues can be resolved with `cargo clippy --fix` (although it might be educational to fix them yourself). If you have reuse issues, you can run the following command to annotate your code: Here are the absolute basics: - your commit summary MUST follow conventional commits. - your commits SHOULD be separated into small, logical chunks. - reviewers may ask you to rebase your commits into more sensible chunks. - your PR will need to pass CI and local `cargo test`. - you may be asked to refactor parts of your code by reviewers. Remember that no one here is an employee, and treat everyone with respect, as the code of conduct specifies. Also remember to be patient if it takes a while to get a response on your PR. Usually it doesn't, but there's only so many hours in a day, and if possible, there would be no delay. The delay alone is evidence of it's own necessity. ## Commit Messages A common commit message contains at least a summary and reference with closing action to the corresponding issue if any, and may also include a description and signature. ### Summary For you commit messages, please use the first line for a brief summary what the commit changes. Try to stay within the 72 char limit and prepend what type of change. See the following list for some guidance: - feat: adds a new feature to eza - feat(zsh): adds something to zsh completion - refactor: revises parts of the code - docs(readme): revise the README - docs(man): revision of the man pages - fix: bugfix in the code base - fix(ci): bugfix in the continuous integration - ... Note that this list is not complete and there may be cases where a commit could be characterized by different types, so just try to make your best guess. This spares the maintainers a lot of work when merging your PR. ### Description If you commit warrants it due to complexity or external information required to follow it, you should add a more detailed description of the changes, reasoning and also link external documentation if necessary. This description should go two lines below the summary and except for links stay in the 80 char limit. ### Issue Reference If the commit resolves an issue add: `Resolves: #abc` where `abc` is the issue number. In case of a bugfix you can also use `Fixes: #abc`. ### Signature You may add a signature at the end two lines below the description or issue reference. ### Example Here is an example of a commit message for a breaking change that follows these rules: ``` fix(hyperlinks)!: TextCell building of detailed grid view, hyperlink, icon options The hyperlink option adds an escape sequence which in the normal TextCell creation also becomes part of the length calculation. This patch applies the same logic the normal grid already did, by using the filenames bare width when a hyperlink is embedded. It also respects the ShowIcons option just like the normal grid view. BREAKING CHANGE: The style codes for huge file and units where documented to be `nt` and `ut` but the code was using `nh` and `uh`. The code has been updated to match the documented style codes. EXA_COLORS using style codes `nh` and `uh` will need to be updated to use `nt` and `ut`. Resolves: #129 Ref: #473, #319 Co-authored-by: 9glenda Signed-off-by: Christina Sørensen ``` ### Additional Examples - feat: add column selection - fix(output): fix width issue with columns - test(fs): add tests for filesystem metadata - feat!: breaking change / feat(config)!: implement config file - chore(deps): update dependencies ### Commit types - build: Changes that affect the build system or external dependencies (example libgit2) - ci: Changes to CI configuration files and scripts (example scopes: Nix, Vagrant, Docker) - chore: Changes which do not change source code or tests (example: changes to the build process, auxiliary tools, libraries) - docs: Documentation, README, completions, manpage only - feat: A new feature - fix: A bug fix - perf: A code change that improves or addresses a performance issue - refactor: A code change that neither fixes a bug nor adds a feature - revert: Revert something - style: Changes that do not affect the meaning of the code (example: clippy) - test: Adding missing tests or correcting existing tests eza-0.18.2/Cargo.lock0000644000001221320000000000100076570ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi_colours" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80" [[package]] name = "ansiterm" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ab587f5395da16dd2e6939adf53dede583221b320cadfb94e02b5b7b9bf24cc" dependencies = [ "ansi_colours", "winapi", ] [[package]] name = "anstream" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[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 = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "windows-targets 0.52.0", ] [[package]] name = "ciborium" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "content_inspector" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" dependencies = [ "memchr", ] [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[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 = "datetime" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ "libc", "redox_syscall 0.1.57", "winapi", ] [[package]] name = "deranged" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ "powerfmt", ] [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "eza" version = "0.18.2" dependencies = [ "ansiterm", "chrono", "criterion", "git2", "glob", "libc", "locale", "log", "natord", "number_prefix", "once_cell", "palette", "percent-encoding", "phf", "plist", "proc-mounts", "rayon", "terminal_size", "timeago", "trycmd", "unicode-width", "uutils_term_grid", "uzers", "windows-sys 0.52.0", "zoneinfo_compiled", ] [[package]] name = "fast-srgb8" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "filetime" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", "redox_syscall 0.3.5", "windows-sys 0.48.0", ] [[package]] name = "form_urlencoded" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", "percent-encoding", ] [[package]] name = "git2" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b3ba52851e73b46a4c3df1d89343741112003f0f6f13beb0dfac9e457c3fdcd" dependencies = [ "bitflags 2.4.0", "libc", "libgit2-sys", "log", "openssl-sys", "url", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "humantime-serde" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ "humantime", "serde", ] [[package]] name = "iana-time-zone" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys 0.48.0", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[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.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libgit2-sys" version = "0.16.2+1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" dependencies = [ "cc", "libc", "libz-sys", "openssl-sys", "pkg-config", ] [[package]] name = "libz-sys" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "line-wrap" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" dependencies = [ "safemem", ] [[package]] name = "linux-raw-sys" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "locale" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" dependencies = [ "libc", ] [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "natord" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl-src" version = "111.26.0+1.1.1u" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" dependencies = [ "autocfg", "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "os_pipe" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" dependencies = [ "libc", "windows-sys 0.48.0", ] [[package]] name = "palette" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d38e6e5ca1612e2081cc31188f08c3cba630ce4ba44709a153f1a0f38d678f2" dependencies = [ "approx", "fast-srgb8", "palette_derive", ] [[package]] name = "palette_derive" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05d1c929301fee6830dafa764341118829b2535c216b0571e3821ecac5c885b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "partition-identity" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa925f9becb532d758b0014b472c576869910929cf4c3f8054b386f19ab9e21" dependencies = [ "thiserror", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", ] [[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.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "plist" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ "base64", "indexmap", "line-wrap", "quick-xml", "time", ] [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "proc-mounts" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d652f8435d0ab70bf4f3590a6a851d59604831a458086541b95238cc51ffcf2" dependencies = [ "partition-identity", ] [[package]] name = "quick-xml" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] [[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 = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[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 = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "snapbox" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73145a30df4935f50a7b13c1882bce7d194d7071ad0bcc36e7cacbf9ef16e3ec" dependencies = [ "anstream", "anstyle", "content_inspector", "dunce", "filetime", "libc", "normalize-line-endings", "os_pipe", "similar", "snapbox-macros", "tempfile", "wait-timeout", "walkdir", "windows-sys 0.52.0", ] [[package]] name = "snapbox-macros" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ccde059aad940984ff696fe8c280900f7ea71a6fb45fce65071a3f2c40b667" dependencies = [ "anstream", ] [[package]] name = "syn" version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", "windows-sys 0.48.0", ] [[package]] name = "terminal_size" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", "windows-sys 0.48.0", ] [[package]] name = "thiserror" version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "timeago" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6" [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tinyvec" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "trycmd" version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ad3a033f38ca4d9eedf36ba792622027119c61b62b57970c5bed42cfd0c40d" dependencies = [ "glob", "humantime", "humantime-serde", "rayon", "serde", "shlex", "snapbox", "toml_edit", ] [[package]] name = "unicode-bidi" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", "idna", "matches", "percent-encoding", ] [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uutils_term_grid" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b389452a568698688dda38802068378a16c15c4af9b153cdd99b65391292bbc7" dependencies = [ "unicode-width", ] [[package]] name = "uzers" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63" dependencies = [ "libc", "log", ] [[package]] name = "vcpkg" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[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 = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 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" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets 0.48.5", ] [[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-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[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-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[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_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[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_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[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_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[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_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "zoneinfo_compiled" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fbebe65e899530f43bd760b23fda8f141118f4db49952b02998cbd0907a5de" dependencies = [ "byteorder", "datetime", ] eza-0.18.2/Cargo.toml0000644000000073060000000000100077070ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70.0" name = "eza" version = "0.18.2" authors = ["Christina Sørensen "] exclude = [ "/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png", "/tests", ] description = "A modern replacement for ls" homepage = "https://github.com/eza-community/eza" readme = "README.md" categories = ["command-line-utilities"] license = "MIT" repository = "https://github.com/eza-community/eza" [package.metadata.deb] assets = [ [ "target/release/eza", "/usr/bin/eza", "0755", ], [ "target/release/../man/eza.1", "/usr/share/man/man1/eza.1", "0644", ], [ "target/release/../man/eza_colors.5", "/usr/share/man/man5/eza_colors.5", "0644", ], [ "target/release/../man/eza_colors-explanation.5", "/usr/share/man/man5/eza_colors-explanation.5", "0644", ], [ "completions/bash/eza", "/usr/share/bash-completion/completions/eza", "0644", ], [ "completions/zsh/_eza", "/usr/share/zsh/site-functions/_eza", "0644", ], [ "completions/fish/eza.fish", "/usr/share/fish/vendor_completions.d/eza.fish", "0644", ], ] depends = "$auto" extended-description = """ eza is a modern, maintained replacement for ls """ license-file = [ "LICENCE", "4", ] priority = "optional" section = "utils" [profile.release] opt-level = 3 lto = true codegen-units = 1 panic = "abort" strip = true [[bin]] name = "eza" [[bench]] name = "my_benchmark" harness = false [dependencies.ansiterm] version = "0.12.2" features = ["ansi_colours"] [dependencies.chrono] version = "0.4.33" features = ["clock"] default-features = false [dependencies.git2] version = "0.18" optional = true default-features = false [dependencies.glob] version = "0.3" [dependencies.libc] version = "0.2" [dependencies.locale] version = "0.2" [dependencies.log] version = "0.4" [dependencies.natord] version = "1.0" [dependencies.number_prefix] version = "0.4" [dependencies.once_cell] version = "1.19.0" [dependencies.palette] version = "0.7.4" features = ["std"] default-features = false [dependencies.percent-encoding] version = "2.3.1" [dependencies.phf] version = "0.11.2" features = ["macros"] [dependencies.plist] version = "1.6.0" default-features = false [dependencies.rayon] version = "1.8.1" [dependencies.terminal_size] version = "0.3.0" [dependencies.timeago] version = "0.4.2" default-features = false [dependencies.unicode-width] version = "0.1" [dependencies.uutils_term_grid] version = "0.3" [dependencies.zoneinfo_compiled] version = "0.5.1" [dev-dependencies.criterion] version = "0.5.1" features = ["html_reports"] [dev-dependencies.trycmd] version = "0.14" [build-dependencies.chrono] version = "0.4.33" features = ["clock"] default-features = false [features] default = ["git"] git = ["git2"] nix = [] nix-generated = [] nix-local = [] powertest = [] vendored-libgit2 = ["git2/vendored-libgit2"] vendored-openssl = ["git2/vendored-openssl"] [target."cfg(target_os = \"linux\")".dependencies.proc-mounts] version = "0.3" [target."cfg(target_os = \"windows\")".dependencies.windows-sys] version = "0.52.0" features = [ "Win32_System_Console", "Win32_Foundation", ] [target."cfg(unix)".dependencies.uzers] version = "0.11.3" eza-0.18.2/Cargo.toml.orig000064400000000000000000000061331046102023000133650ustar 00000000000000[package] name = "eza" description = "A modern replacement for ls" authors = ["Christina Sørensen "] categories = ["command-line-utilities"] edition = "2021" rust-version = "1.70.0" exclude = [ "/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png", "/tests", ] readme = "README.md" homepage = "https://github.com/eza-community/eza" license = "MIT" repository = "https://github.com/eza-community/eza" version = "0.18.2" [package.metadata.deb] license-file = ["LICENCE", "4"] depends = "$auto" extended-description = """ eza is a modern, maintained replacement for ls """ section = "utils" priority = "optional" assets = [ [ "target/release/eza", "/usr/bin/eza", "0755", ], [ "target/release/../man/eza.1", "/usr/share/man/man1/eza.1", "0644", ], [ "target/release/../man/eza_colors.5", "/usr/share/man/man5/eza_colors.5", "0644", ], [ "target/release/../man/eza_colors-explanation.5", "/usr/share/man/man5/eza_colors-explanation.5", "0644", ], [ "completions/bash/eza", "/usr/share/bash-completion/completions/eza", "0644", ], [ "completions/zsh/_eza", "/usr/share/zsh/site-functions/_eza", "0644", ], [ "completions/fish/eza.fish", "/usr/share/fish/vendor_completions.d/eza.fish", "0644", ], ] [[bin]] name = "eza" [dependencies] ansiterm = { version = "0.12.2", features = ["ansi_colours"] } chrono = { version = "0.4.33", default-features = false, features = ["clock"] } glob = "0.3" libc = "0.2" locale = "0.2" log = "0.4" natord = "1.0" number_prefix = "0.4" palette = { version = "0.7.4", default-features = false, features = ["std"] } once_cell = "1.19.0" percent-encoding = "2.3.1" phf = { version = "0.11.2", features = ["macros"] } plist = { version = "1.6.0", default-features = false } uutils_term_grid = "0.3" terminal_size = "0.3.0" timeago = { version = "0.4.2", default-features = false } unicode-width = "0.1" zoneinfo_compiled = "0.5.1" rayon = "1.8.1" [dependencies.git2] version = "0.18" optional = true default-features = false [target.'cfg(target_os = "linux")'.dependencies] proc-mounts = "0.3" [target.'cfg(unix)'.dependencies] uzers = "0.11.3" [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { version = "0.52.0", features = [ "Win32_System_Console", "Win32_Foundation", ] } [build-dependencies] chrono = { version = "0.4.33", default-features = false, features = ["clock"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } trycmd = "0.14" [features] default = ["git"] git = ["git2"] vendored-openssl = ["git2/vendored-openssl"] vendored-libgit2 = ["git2/vendored-libgit2"] # Should only be used inside of flake.nix nix = [] # Should only be used inside of flake.nix locally (not on CI) nix-local = [] # Should only be used inside of flake.nix # Shouldn't ever be used in CI (slow!) powertest = [] nix-generated = [] # use LTO for smaller binaries (that take longer to build) [profile.release] lto = true strip = true opt-level = 3 codegen-units = 1 panic = 'abort' [[bench]] name = "my_benchmark" harness = false eza-0.18.2/INSTALL.md000064400000000000000000000150071046102023000121260ustar 00000000000000# Installation eza is available for Windows, macOS and Linux. ### Cargo (crates.io) ![Crates.io](https://img.shields.io/crates/v/eza?link=https%3A%2F%2Fcrates.io%2Fcrates%2Feza) If you already have a Rust environment set up, you can use the `cargo install` command: cargo install eza Cargo will build the `eza` binary and place it in `$HOME/.local/share/cargo/bin/eza`. ### Cargo (git) If you already have a Rust environment set up, you can use the `cargo install` command in your local clone of the repo: git clone https://github.com/eza-community/eza.git cd eza cargo install --path . Cargo will build the `eza` binary and place it in `$HOME/.cargo`. ### Arch Linux [![Arch Linux package](https://repology.org/badge/version-for-repo/arch/eza.svg)](https://repology.org/project/eza/versions) Eza is available in the [\[extra\]](https://archlinux.org/packages/extra/x86_64/eza/) repository of Arch Linux. ```bash pacman -S eza ``` ### Debian and Ubuntu Eza is available from [deb.gierens.de](http://deb.gierens.de). The GPG public key is in this repo under [deb.asc](/deb.asc). First make sure you have the `gpg` command, and otherwise install it via: ```bash sudo apt update sudo apt install -y gpg ``` Then install eza via: ```bash sudo mkdir -p /etc/apt/keyrings wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list sudo apt update sudo apt install -y eza ``` ### Nix (Linux, MacOS) [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/eza.svg)](https://repology.org/project/eza/versions) > **Note** > Installing packages imperatively isn't idiomatic Nix, as this can lead to [many issues](https://stop-using-nix-env.privatevoid.net/). Eza is available from [Nixpkgs](https://github.com/NixOS/nixpkgs) and from the flake in this repository. For `nix profile` users: ```shell nix profile install nixpkgs#eza ``` For `nix-env` users: ```shell nix-env -i eza ``` **Declarative Nix Installations** - Simple NixOS installation: [rfaulhaber/dotfiles](https://github.com/rfaulhaber/dotfiles/blob/a8d084d178efd0592b7ac02d34a450fb58913aca/nix/modules/programs/eza/default.nix#L15) - Using the flake via NixOS: [hallettj/home.nix](https://github.com/hallettj/home.nix/blob/a8388483e5d78e110be73c5af0e7f0e3ca8f8aa3/flake.nix#L19) - Using home-manager on NixOS: [Misterio77/nix-config](https://github.com/Misterio77/nix-config/blob/6867d66a2fe7899c608b9c8e5a8f9aee279d188b/home/misterio/features/cli/fish.nix#L6) ### Gentoo [![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/eza.svg)](https://repology.org/project/eza/versions) On Gentoo, eza is available as a package [`sys-apps/eza`](https://packages.gentoo.org/packages/sys-apps/eza): ```bash emerge --ask sys-apps/eza ``` ### openSUSE Eza is available at [openSUSE:Factory/eza](https://build.opensuse.org/package/show/openSUSE:Factory/eza): ```bash zypper ar https://download.opensuse.org/tumbleweed/repo/oss/ factory-oss zypper in eza ``` The preceding repository also contains the Bash, Fish, and Zsh completions. ### Fedora [![Fedora package](https://repology.org/badge/version-for-repo/fedora_39/rust:eza.svg)](https://repology.org/project/eza/versions) Eza is available as the [eza](https://packages.fedoraproject.org/pkgs/rust-eza/eza/) package in the official Fedora repository. ```bash sudo dnf install eza ``` ### Void Linux [![Void Linux package](https://repology.org/badge/version-for-repo/void_x86_64/eza.svg)](https://repology.org/project/eza/versions) Eza is available as the [eza](https://github.com/void-linux/void-packages/tree/master/srcpkgs/eza) package in the official Void Linux repository. ```bash sudo xbps-install eza ``` ### Termux Eza is available as the [eza](https://github.com/termux/termux-packages/tree/master/packages/eza) package in the official Termux repository. ```bash pkg install eza ``` ### Manual (Linux) Example is for x86_64 GNU, replaces the file names if downloading for a different arch. ```shell wget -c https://github.com/eza-community/eza/releases/latest/download/eza_x86_64-unknown-linux-gnu.tar.gz -O - | tar xz sudo chmod +x eza sudo chown root:root eza sudo mv eza /usr/local/bin/eza ``` If `exa` was install before, replace it will `eza`: ```shell sudo rm -f /usr/local/bin/exa sudo ln -s /usr/local/bin/eza /usr/local/bin/exa ``` ### Brew (MacOS) [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/eza.svg)](https://repology.org/project/eza/versions) Eza is available from [Homebrew](https://formulae.brew.sh/formula/eza#default). To install eza, run: ```shell brew install eza ``` ### MacPorts (macOS) [![MacPorts port](https://repology.org/badge/version-for-repo/macports/eza.svg)](https://repology.org/project/eza/versions) On macOS, eza is also available via [MacPorts](https://ports.macports.org/port/eza/). To install eza, run: ```shell sudo port install eza ``` ### Winget (Windows) [![Windows package](https://repology.org/badge/version-for-repo/winget/eza.svg)](https://repology.org/project/eza/versions) Eza is available on Winget. To install eza, run: ```shell winget install eza-community.eza ``` ### Scoop (Windows) [![Windows package](https://repology.org/badge/version-for-repo/scoop/eza.svg)](https://repology.org/project/eza/versions) Eza is available from [Scoop](https://scoop.sh/#/apps?q=eza&id=a52070d25f94bbcc884f80bef53eb47ed1268198). To install eza, run: ```shell scoop install eza ``` ### Completions #### For zsh: > **Note** > Change `~/.zshrc` to your preferred zsh config file. ##### Clone the repository: ```sh git clone https://github.com/eza-community/eza.git ``` ##### Add the completion path to your zsh configuration: Replace `` with the actual path where you cloned the `eza` repository. ```sh echo 'export FPATH="/completions/zsh:$FPATH"' >> ~/.zshrc ``` ##### Reload your zsh configuration: ```sh source ~/.zshrc ``` #### For zsh with homebrew: In case zsh completions don't work out of the box with homebrew, add the following to your `~/.zshrc`: ```bash if type brew &>/dev/null; then FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}" autoload -Uz compinit compinit fi ``` For reference: - https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh - https://github.com/Homebrew/brew/issues/8984 eza-0.18.2/LICENCE000064400000000000000000000020701046102023000114570ustar 00000000000000The MIT License (MIT) Copyright (c) 2014 Benjamin Sago Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. eza-0.18.2/README.md000064400000000000000000000173111046102023000117550ustar 00000000000000
# eza A modern, maintained replacement for ls. Gitter [![Built with Nix](https://img.shields.io/badge/Built_With-Nix-5277C3.svg?logo=nixos&labelColor=73C3D5)](https://nixos.org) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![Unit tests](https://github.com/eza-community/eza/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/eza-community/eza/actions/workflows/unit-tests.yml) ![Crates.io](https://img.shields.io/crates/v/eza?link=https%3A%2F%2Fcrates.io%2Fcrates%2Feza) ![Crates.io](https://img.shields.io/crates/l/eza?link=https%3A%2F%2Fgithub.com%2Feza-community%2Feza%2Fblob%2Fmain%2FLICENCE)
![eza demo gif](docs/images/screenshots.png) --- **eza** is a modern, maintained replacement for the venerable file-listing command-line program `ls` that ships with Unix and Linux operating systems, giving it more features and better defaults. It uses colours to distinguish file types and metadata. It knows about symlinks, extended attributes, and Git. And it’s **small**, **fast**, and just **one single binary**. By deliberately making some decisions differently, eza attempts to be a more featureful, more user-friendly version of `ls`. --- **eza** features not in exa (non-exhaustive): - Fixes [“The Grid Bug”](https://github.com/eza-community/eza/issues/66#issuecomment-1656758327) introduced in exa 2021. - Hyperlink support. - Mount point details. - Selinux context output. - Git repo status output. - Human readable relative dates. - Several security fixes. - Support for `bright` terminal colours. - Many smaller bug fixes/changes! ...and like, so much more that it became exhausting to update this all the time. Like seriously, we have a lot of good stuff. ---

Try it!

### Nix ❄️ If you already have Nix setup with flake support, you can try out eza with the `nix run` command: nix run github:eza-community/eza Nix will build eza and run it. If you want to pass arguments this way, use e.g. `nix run github:eza-community/eza -- -ol`. # Installation eza is available for Windows, macOS and Linux. Platform and distribution specific installation instructions can be found in [INSTALL.md](INSTALL.md). [![Packaging status](https://repology.org/badge/vertical-allrepos/eza.svg)](https://repology.org/project/eza/versions) --- Click sections to expand.
Command-line options

Command-line options

eza’s options are almost, but not quite, entirely unlike `ls`’s. ### Display options - **-1**, **--oneline**: display one entry per line - **-G**, **--grid**: display entries as a grid (default) - **-l**, **--long**: display extended details and attributes - **-R**, **--recurse**: recurse into directories - **-T**, **--tree**: recurse into directories as a tree - **-x**, **--across**: sort the grid across, rather than downwards - **-F**, **--classify=(when)**: display type indicator by file names (always, auto, never) - **--colo[u]r=(when)**: when to use terminal colours (always, auto, never) - **--colo[u]r-scale=(field)**: highlight levels of `field` distinctly(all, age, size) - **--color-scale-mode=(mode)**: use gradient or fixed colors in --color-scale. valid options are `fixed` or `gradient` - **--icons=(when)**: when to display icons (always, auto, never) - **--hyperlink**: display entries as hyperlinks - **-w**, **--width=(columns)**: set screen width in columns ### Filtering options - **-a**, **--all**: show hidden and 'dot' files - **-d**, **--list-dirs**: list directories like regular files - **-L**, **--level=(depth)**: limit the depth of recursion - **-r**, **--reverse**: reverse the sort order - **-s**, **--sort=(field)**: which field to sort by - **--group-directories-first**: list directories before other files - **-D**, **--only-dirs**: list only directories - **-f**, **--only-files**: list only files - **--git-ignore**: ignore files mentioned in `.gitignore` - **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore Pass the `--all` option twice to also show the `.` and `..` directories. ### Long view options These options are available when running with `--long` (`-l`): - **-b**, **--binary**: list file sizes with binary prefixes - **-B**, **--bytes**: list file sizes in bytes, without any prefixes - **-g**, **--group**: list each file’s group - **-h**, **--header**: add a header row to each column - **-H**, **--links**: list each file’s number of hard links - **-i**, **--inode**: list each file’s inode number - **-m**, **--modified**: use the modified timestamp field - **-M**, **--mounts**: Show mount details (Linux and MacOS only). - **-S**, **--blocksize**: show size of allocated file system blocks - **-t**, **--time=(field)**: which timestamp field to use - **-u**, **--accessed**: use the accessed timestamp field - **-U**, **--created**: use the created timestamp field - **-X**, **--dereference**: dereference symlinks for file information - **-Z**, **--context**: list each file’s security context - **-@**, **--extended**: list each file’s extended attributes and sizes - **--changed**: use the changed timestamp field - **--git**: list each file’s Git status, if tracked or ignored - **--git-repos**: list each directory’s Git status, if tracked - **--git-repos-no-status**: list whether a directory is a Git repository, but not its status (faster) - **--no-git**: suppress Git status (always overrides `--git`, `--git-repos`, `--git-repos-no-status`) - **--time-style**: how to format timestamps. valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, ‘`full-iso`’, ‘`relative`’, or a custom style ‘`+`’ (E.g., ‘`+%Y-%m-%d %H:%M`’ => ‘`2023-09-30 13:00`’. For more specifications on the format string, see the _`eza(1)` manual page_ and [chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).). - **--total-size**: show recursive directory size - **--no-permissions**: suppress the permissions field - **-o**, **--octal-permissions**: list each file's permission in octal format - **--no-filesize**: suppress the filesize field - **--no-user**: suppress the user field - **--no-time**: suppress the time field - **--stdin**: read file names from stdin Some of the options accept parameters: - Valid **--colo\[u\]r** options are **always**, **automatic** (or **auto** for short), and **never**. - Valid sort fields are **accessed**, **changed**, **created**, **extension**, **Extension**, **inode**, **modified**, **name**, **Name**, **size**, **type**, and **none**. Fields starting with a capital letter sort uppercase before lowercase. The modified field has the aliases **date**, **time**, and **newest**, while its reverse has the aliases **age** and **oldest**. - Valid time fields are **modified**, **changed**, **accessed**, and **created**. - Valid time styles are **default**, **iso**, **long-iso**, **full-iso**, and **relative**.
# Hacking on eza If you wanna contribute to eza, firstly, you're expected to follow our [code of conduct](https://github.com/eza-community/eza/blob/main/CODE_OF_CONDUCT.md). After having understood the code of conduct, you can have a look at our [CONTRIBUTING.md](https://github.com/eza-community/eza/blob/main/CONTRIBUTING.md) for more info about actual hacking. [![Star History Chart](https://api.star-history.com/svg?repos=eza-community/eza&type=Date)](https://star-history.com/#eza-community/eza&Date) eza-0.18.2/SECURITY.md000064400000000000000000000007711046102023000122710ustar 00000000000000# Security Policy ## Supported Versions This section shows which versions of eza are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | latest | :white_check_mark: | | < 0.10.6 | :x: | ## Reporting a Vulnerability Please email all vulnerabilities to christina@cafkafk.com, with PGP encryption and signature, and ideally send along plaintext public key or instructions on where to find public key (keyserver etc.). eza-0.18.2/benches/my_benchmark.rs000064400000000000000000000005611046102023000151110ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("logger", |b| { b.iter(|| { eza::logger::configure(black_box(std::env::var_os(eza::options::vars::EZA_DEBUG))) }) }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); eza-0.18.2/build.rs000064400000000000000000000074411046102023000121460ustar 00000000000000/// The version string isn’t the simplest: we want to show the version, /// current Git hash, and compilation date when building *debug* versions, but /// just the version for *release* versions so the builds are reproducible. /// /// This script generates the string from the environment variables that Cargo /// adds (http://doc.crates.io/environment-variables.html) and runs `git` to /// get the SHA1 hash. It then writes the string into a file, which exa then /// includes at build-time. /// /// - https://stackoverflow.com/q/43753491/3484614 /// - https://crates.io/crates/vergen use std::env; use std::fs::File; use std::io::{self, Write}; use std::path::PathBuf; use chrono::prelude::*; /// The build script entry point. fn main() -> io::Result<()> { #![allow(clippy::write_with_newline)] let tagline = "eza - A modern, maintained replacement for ls"; let url = "https://github.com/eza-community/eza"; let ver = if is_debug_build() { format!( "{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), url ) } else if is_development_version() { format!( "{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), git_hash(), build_date(), url ) } else { format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, version_string(), url) }; // We need to create these files in the Cargo output directory. let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let path = &out.join("version_string.txt"); // Bland version text let mut f = File::create(path).unwrap_or_else(|_| panic!("{}", path.to_string_lossy().to_string())); writeln!(f, "{}", strip_codes(&ver))?; Ok(()) } /// Removes escape codes from a string. fn strip_codes(input: &str) -> String { input .replace("\\0m", "") .replace("\\1;31m", "") .replace("\\1;4;34m", "") } /// Retrieve the project’s current Git hash, as a string. fn git_hash() -> String { use std::process::Command; String::from_utf8_lossy( &Command::new("git") .args(["rev-parse", "--short", "HEAD"]) .output() .unwrap() .stdout, ) .trim() .to_string() } /// Whether we should show pre-release info in the version string. /// /// Both weekly releases and actual releases are --release releases, /// but actual releases will have a proper version number. fn is_development_version() -> bool { cargo_version().ends_with("-pre") || env::var("PROFILE").unwrap() == "debug" } /// Whether we are building in debug mode. fn is_debug_build() -> bool { env::var("PROFILE").unwrap() == "debug" } /// Retrieves the [package] version in Cargo.toml as a string. fn cargo_version() -> String { env::var("CARGO_PKG_VERSION").unwrap() } /// Returns the version and build parameters string. fn version_string() -> String { let mut ver = cargo_version(); let feats = nonstandard_features_string(); if !feats.is_empty() { ver.push_str(&format!(" [{}]", &feats)); } ver } /// Finds whether a feature is enabled by examining the Cargo variable. fn feature_enabled(name: &str) -> bool { env::var(format!("CARGO_FEATURE_{}", name)) .map(|e| !e.is_empty()) .unwrap_or(false) } /// A comma-separated list of non-standard feature choices. fn nonstandard_features_string() -> String { let mut s = Vec::new(); if feature_enabled("GIT") { s.push("+git"); } else { s.push("-git"); } s.join(", ") } /// Formats the current date as an ISO 8601 string. fn build_date() -> String { let now = Local::now(); now.date_naive().format("%Y-%m-%d").to_string() } eza-0.18.2/cliff.toml000064400000000000000000000047331046102023000124620ustar 00000000000000# git-cliff ~ default configuration file # https://git-cliff.org/docs/configuration # # Lines starting with "#" are comments. # Configuration options are organized into tables and keys. # See documentation for more information on available options. [changelog] # changelog header header = """ # Changelog\n """ # template for the changelog body # https://tera.netlify.app/docs body = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ ## [unreleased] {% endif %}\ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} {% for commit in commits %} - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ {% endfor %} {% endfor %}\n """ # remove the leading and trailing whitespace from the template trim = true # changelog footer footer = """ """ [git] # parse the commits based on https://www.conventionalcommits.org conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true # process each line of a commit as an individual commit split_commits = false # regex for preprocessing the commit messages commit_preprocessors = [ # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers ] # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, { message = "^doc", group = "Documentation" }, { message = "^perf", group = "Performance" }, { message = "^refactor", group = "Refactor" }, { message = "^style", group = "Styling" }, { message = "^test", group = "Testing" }, { message = "^chore\\(release\\): prepare for", skip = true }, { message = "^chore", group = "Miscellaneous Tasks" }, { body = ".*security", group = "Security" }, ] # protect breaking changes from being skipped due to matching a skipping commit_parser protect_breaking_commits = false # filter out the commits that are not matched by commit parsers filter_commits = false # glob pattern for matching git tags tag_pattern = "v[0-9]*" # regex for skipping tags skip_tags = "v0.1.0-beta.1" # regex for ignoring tags ignore_tags = "" # sort the tags topologically topo_order = false # sort the commits inside sections by oldest/newest order sort_commits = "oldest" # limit the number of commits included in the changelog. # limit_commits = 42 eza-0.18.2/completions/bash/eza000064400000000000000000000044251046102023000144530ustar 00000000000000# shellcheck shell=bash _eza() { cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} case "$prev" in --help|-v|--version|--smart-group) return ;; --colour) mapfile -t COMPREPLY < <(compgen -W 'always automatic auto never' -- "$cur") return ;; --icons) mapfile -t COMPREPLY < <(compgen -W 'always automatic auto never' -- "$cur") return ;; -L|--level) mapfile -t COMPREPLY < <(compgen -W '{0..9}' -- "$cur") return ;; -s|--sort) mapfile -t COMPREPLY < <(compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- "$cur") return ;; -t|--time) mapfile -t COMPREPLY < <(compgen -W 'modified changed accessed created --' -- "$cur") return ;; --time-style) mapfile -t COMPREPLY < <(compgen -W 'default iso long-iso full-iso relative +FORMAT --' -- "$cur") return ;; --color-scale) mapfile -t COMPREPLY < <(compgen -W 'all age size --' -- "$cur") return ;; --color-scale-mode) mapfile -t COMPREPLY < <(compgen -W 'fixed gradient --' -- "$cur") return ;; esac case "$cur" in # _parse_help doesn’t pick up short options when they are on the same line than long options --*) # colo[u]r isn’t parsed correctly so we filter these options out and add them by hand parse_help=$(eza --help | grep -oE ' (--[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\--colo') completions=$(echo '--color --colour --color-scale --colour-scale --color-scale-mode --colour-scale-mode' "$parse_help") mapfile -t COMPREPLY < <(compgen -W "$completions" -- "$cur") ;; -*) completions=$(eza --help | grep -oE ' (-[[:alnum:]@])' | tr -d ' ') mapfile -t COMPREPLY < <(compgen -W "$completions" -- "$cur") ;; *) _filedir ;; esac } && complete -o filenames -o bashdefault -F _eza eza eza-0.18.2/completions/fish/eza.fish000064400000000000000000000141721046102023000154170ustar 00000000000000# Meta-stuff complete -c eza -s v -l version -d "Show version of eza" complete -c eza -l help -d "Show list of command-line options" # Display options complete -c eza -s 1 -l oneline -d "Display one entry per line" complete -c eza -s l -l long -d "Display extended file metadata as a table" complete -c eza -s G -l grid -d "Display entries in a grid" complete -c eza -s x -l across -d "Sort the grid across, rather than downwards" complete -c eza -s R -l recurse -d "Recurse into directories" complete -c eza -s T -l tree -d "Recurse into directories as a tree" complete -c eza -s X -l dereference -d "Dereference symbolic links when displaying information" complete -c eza -s F -l classify -d "Display type indicator by file names" complete -c eza -l color \ -l colour -d "When to use terminal colours" -x -a " always\t'Always use colour' auto\t'Use colour if standard output is a terminal' automatic\t'Use colour if standard output is a terminal' never\t'Never use colour' " complete -c eza -l color-scale \ -l colour-scale -d "Highlight levels 'field' distinctly" -x -a " all\t'' age\t'' size\t'' " complete -c eza -l color-scale-mode \ -l colour-scale-mode \ -d "Use gradient or fixed colors in --color-scale" -x -a " fixed\t'Highlight based on fixed colors' gradient\t'Highlight based \'field\' in relation to other files' " complete -c eza -l icons -d "When to display icons" -x -a " always\t'Always display icons' auto\t'Display icons if standard output is a terminal' automatic\t'Display icons if standard output is a terminal' never\t'Never display icons' " complete -c eza -l no-quotes -d "Don't quote file names with spaces" complete -c eza -l hyperlink -d "Display entries as hyperlinks" complete -c eza -l smart-group -d "Only show group if it has a different name from owner" # Filtering and sorting options complete -c eza -l group-directories-first -d "Sort directories before other files" complete -c eza -l git-ignore -d "Ignore files mentioned in '.gitignore'" complete -c eza -s a -l all -d "Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories" complete -c eza -s A -l almost-all -d "Equivalent to --all; included for compatibility with `ls -A`" complete -c eza -s d -l list-dirs -d "List directories like regular files" complete -c eza -s L -l level -d "Limit the depth of recursion" -x -a "1 2 3 4 5 6 7 8 9" complete -c eza -s w -l width -d "Limits column output of grid, 0 implies auto-width" complete -c eza -s r -l reverse -d "Reverse the sort order" complete -c eza -s s -l sort -d "Which field to sort by" -x -a " accessed\t'Sort by file accessed time' age\t'Sort by file modified time (newest first)' changed\t'Sort by changed time' created\t'Sort by file modified time' date\t'Sort by file modified time' ext\t'Sort by file extension' Ext\t'Sort by file extension (uppercase first)' extension\t'Sort by file extension' Extension\t'Sort by file extension (uppercase first)' filename\t'Sort by filename' Filename\t'Sort by filename (uppercase first)' inode\t'Sort by file inode' modified\t'Sort by file modified time' name\t'Sort by filename' Name\t'Sort by filename (uppercase first)' newest\t'Sort by file modified time (newest first)' none\t'Do not sort files at all' oldest\t'Sort by file modified time' size\t'Sort by file size' time\t'Sort by file modified time' type\t'Sort by file type' " complete -c eza -s I -l ignore-glob -d "Ignore files that match these glob patterns" -r complete -c eza -s D -l only-dirs -d "List only directories" complete -c eza -s f -l only-files -d "List only files" # Long view options complete -c eza -s b -l binary -d "List file sizes with binary prefixes" complete -c eza -s B -l bytes -d "List file sizes in bytes, without any prefixes" complete -c eza -s g -l group -d "List each file's group" complete -c eza -s h -l header -d "Add a header row to each column" complete -c eza -s H -l links -d "List each file's number of hard links" complete -c eza -s i -l inode -d "List each file's inode number" complete -c eza -s S -l blocksize -d "List each file's size of allocated file system blocks" complete -c eza -s t -l time -d "Which timestamp field to list" -x -a " modified\t'Display modified time' changed\t'Display changed time' accessed\t'Display accessed time' created\t'Display created time' " complete -c eza -s X -l dereference -d "dereference symlinks for file information" complete -c eza -s m -l modified -d "Use the modified timestamp field" complete -c eza -s n -l numeric -d "List numeric user and group IDs." complete -c eza -l changed -d "Use the changed timestamp field" complete -c eza -s u -l accessed -d "Use the accessed timestamp field" complete -c eza -s U -l created -d "Use the created timestamp field" complete -c eza -l time-style -d "How to format timestamps" -x -a " default\t'Use the default time style' iso\t'Display brief ISO timestamps' long-iso\t'Display longer ISO timestamps, up to the minute' full-iso\t'Display full ISO timestamps, up to the nanosecond' relative\t'Display relative timestamps' +FORMAT\t'Use custom time style' " complete -c eza -l total-size -d "Show recursive directory size (unix only)" complete -c eza -l no-permissions -d "Suppress the permissions field" complete -c eza -s o -l octal-permissions -d "List each file's permission in octal format" complete -c eza -l no-filesize -d "Suppress the filesize field" complete -c eza -l no-user -d "Suppress the user field" complete -c eza -l no-time -d "Suppress the time field" complete -c eza -s M -l mounts -d "Show mount details" complete -c eza -l stdin -d "When piping to eza. Read file names from stdin" # Optional extras complete -c eza -l git -d "List each file's Git status, if tracked" complete -c eza -l no-git -d "Suppress Git status" complete -c eza -l git-repos -d "List each git-repos status and branch name" complete -c eza -l git-repos-no-status -d "List each git-repos branch name (much faster)" complete -c eza -s '@' -l extended -d "List each file's extended attributes and sizes" complete -c eza -s Z -l context -d "List each file's security context" eza-0.18.2/completions/nush/eza.nu000064400000000000000000000100751046102023000151320ustar 00000000000000export extern "eza" [ --version(-v) # Show version of eza --help # Show list of command-line options --oneline(-1) # Display one entry per line --long(-l) # Display extended file metadata as a table --grid(-G) # Display entries in a grid --across(-x) # Sort the grid across, rather than downwards --recurse(-R) # Recurse into directories --tree(-T) # Recurse into directories as a tree --dereference(-X) # Dereference symbolic links when displaying information --classify(-F) # Display type indicator by file names --color # When to use terminal colours --colour # When to use terminal colours --color-scale # Highlight levels of file sizes distinctly --colour-scale # Highlight levels of file sizes distinctly --color-scale-mode # Use gradient or fixed colors in --color-scale --colour-scale-mode # Use gradient or fixed colors in --colour-scale --icons # When to display icons --no-quotes # Don't quote file names with spaces --hyperlink # Display entries as hyperlinks --group-directories-first # Sort directories before other files --git-ignore # Ignore files mentioned in '.gitignore' --all(-a) # Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories --almost-all(-A) # Equivalent to --all; included for compatibility with `ls -A` --list-dirs(-d) # List directories like regular files --level(-L): string # Limit the depth of recursion --width(-w) # Limits column output of grid, 0 implies auto-width --reverse(-r) # Reverse the sort order --sort(-s) # Which field to sort by --only-dirs(-D) # List only directories --only-files(-f) # List only files --binary(-b) # List file sizes with binary prefixes --bytes(-B) # List file sizes in bytes, without any prefixes --group(-g) # List each file's group --header(-h) # Add a header row to each column --links(-H) # List each file's number of hard links --inode(-i) # List each file's inode number --blocksize(-S) # List each file's size of allocated file system blocks --time(-t) -d # Which timestamp field to list --dereference(-X) # dereference symlinks for file information --modified(-m) # Use the modified timestamp field --numeric(-n) # List numeric user and group IDs. --changed # Use the changed timestamp field --accessed(-u) # Use the accessed timestamp field --created(-U) # Use the created timestamp field --time-style # How to format timestamps --total-size # Show recursive directory size (unix only) --no-permissions # Suppress the permissions field --octal-permissions(-o) # List each file's permission in octal format --no-filesize # Suppress the filesize field --no-user # Suppress the user field --no-time # Suppress the time field --mounts(-M) # Show mount details --git # List each file's Git status, if tracked --no-git # Suppress Git status --git-repos # List each git-repos status and branch name --git-repos-no-status # List each git-repos branch name (much faster) --extended(-@) # List each file's extended attributes and sizes --context(-Z) # List each file's security context --smart-group # Only show group if it has a different name from owner --stdin # When piping to eza. Read file paths from stdin ] eza-0.18.2/completions/zsh/_eza000064400000000000000000000110421046102023000144720ustar 00000000000000#compdef eza # Save this file as _eza in /usr/local/share/zsh/site-functions or in any # other folder in $fpath. E.g. save it in a folder called ~/.zfunc and add a # line containing `fpath=(~/.zfunc $fpath)` somewhere before `compinit` in your # ~/.zshrc. __eza() { # Give completions using the `_arguments` utility function with # `-s` for option stacking like `eza -ab` for `eza -a -b` and # `-S` for delimiting options with `--` like in `eza -- -a`. _arguments -s -S \ "(- *)"{-v,--version}"[Show version of eza]" \ "(- *)"--help"[Show list of command-line options]" \ {-1,--oneline}"[Display one entry per line]" \ {-l,--long}"[Display extended file metadata as a table]" \ {-G,--grid}"[Display entries as a grid]" \ {-x,--across}"[Sort the grid across, rather than downwards]" \ {-R,--recurse}"[Recurse into directories]" \ {-T,--tree}"[Recurse into directories as a tree]" \ {-X,--dereference}"[Dereference symbolic links when displaying information]" \ {-F,--classify}"[Display type indicator by file names]:(when):(always auto automatic never)" \ --colo{,u}r="[When to use terminal colours]:(when):(always auto automatic never)" \ --colo{,u}r-scale"[highlight levels of 'field' distinctly]:(fields):(all age size)" \ --colo{,u}r-scale-mode"[Use gradient or fixed colors in --color-scale]:(mode):(fixed gradient)" \ --icons="[When to display icons]:(when):(always auto automatic never)" \ --no-quotes"[Don't quote filenames with spaces]" \ --hyperlink"[Display entries as hyperlinks]" \ --group-directories-first"[Sort directories before other files]" \ --git-ignore"[Ignore files mentioned in '.gitignore']" \ {-a,--all}"[Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories]" \ {-A,--almost-all}"[Equivalent to --all; included for compatibility with \'ls -A\']" \ {-d,--list-dirs}"[List directories like regular files]" \ {-D,--only-dirs}"[List only directories]" \ {-f,--only-files}"[List only files]" \ {-L,--level}"+[Limit the depth of recursion]" \ {-w,--width}"+[Limits column output of grid, 0 implies auto-width]" \ {-r,--reverse}"[Reverse the sort order]" \ {-s,--sort}="[Which field to sort by]:(sort field):(accessed age changed created date extension Extension filename Filename inode modified oldest name Name newest none size time type)" \ {-I,--ignore-glob}"[Ignore files that match these glob patterns]" \ {-b,--binary}"[List file sizes with binary prefixes]" \ {-B,--bytes}"[List file sizes in bytes, without any prefixes]" \ --changed"[Use the changed timestamp field]" \ {-g,--group}"[List each file's group]" \ {-h,--header}"[Add a header row to each column]" \ {-H,--links}"[List each file's number of hard links]" \ {-i,--inode}"[List each file's inode number]" \ {-m,--modified}"[Use the modified timestamp field]" \ {-n,--numeric}"[List numeric user and group IDs.]" \ {-S,--blocksize}"[List each file's size of allocated file system blocks.]" \ {-t,--time}="[Which time field to show]:(time field):(accessed changed created modified)" \ --time-style="[How to format timestamps]:(time style):(default iso long-iso full-iso relative +FORMAT)" \ --total-size="[Show recursive directory size (unix only)]" \ --no-permissions"[Suppress the permissions field]" \ {-o,--octal-permissions}"[List each file's permission in octal format]" \ --no-filesize"[Suppress the filesize field]" \ --no-user"[Suppress the user field]" \ --no-time"[Suppress the time field]" \ {-u,--accessed}"[Use the accessed timestamp field]" \ {-U,--created}"[Use the created timestamp field]" \ {-X,--dereference}"[dereference symlinks for file information]" \ --git"[List each file's Git status, if tracked]" \ --no-git"[Suppress Git status]" \ --git-repos"[List each git-repos status and branch name]" \ --git-repos-no-status"[List each git-repos branch name (much faster)]" \ {-@,--extended}"[List each file's extended attributes and sizes]" \ {-Z,--context}"[List each file's security context]" \ {-M,--mounts}"[Show mount details (long mode only)]" \ '*:filename:_files' \ --smart-group"[Only show group if it has a different name from owner]" \ --stdin"[When piping to eza. Read file names from stdin]" } __eza eza-0.18.2/deb.asc000064400000000000000000000032141046102023000117150ustar 00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGTejj8BEAC3Qdi6up8rkFvekeuZiGpuC5OTic+Nd/x6zacrtDKJwdVa6fw3 tVydFW1ELcw33ifWDztvgENqvgMuhcB/lnoDnaAhK8nzT0l+r0gQ7JptPH+8XsZx PuIFRxgUkS6M45jrZApu/c3/PX3akiBuBnibd+sik72pVSi9pYm00I/yY/+u9Vvo u4vy/PG/y4Kp1+ewbVyOnaTQoiJXgqceUYqNfhShcN7dssa/Td6G0xPhS1XeQZ81 QWwPNEzGWomGJ/igZPcm31qtIwcHXn3m8UZbCxUHHzseZ0hOOYqVSrSIj+U1RIma rmcbdAAi1wQei0P142/Gkq1fLdscrZPc0b0L8JbZnymAvg2WWE6SQ3/7Ux0Y9hc3 wsXuiwU38Qg0EaDMSXyUKAbK+4/tuP9mbx4PyKuDUmjJnPTCvbJxe2kC8rdZrOgn 4qbwHY7FCEBpG28VhV07dwueZcFC/VCqGrbmrJC7rl6avT5xpOIRslUTJGmifx3Z 0hmL6rzTQz00FVNSXyxLnz1XYUTLm7QyzywTv8HTTfy5o21N2eOHqbMkBA9MJiic lnVSH1vCn/5rd+fwRQ/7yLKAdeUcRyxkdVWTUYDnVtQF199MAuXWv4nudjvS3046 Y9WpJGiZEi9PqeIxmYYHOZ/jYVF1RpMOyVnZhzRhn4Yy+uzJtUeW45nGyQARAQAB tDZTYW5kcm8tQWxlc3NpbyBHaWVyZW5zIChhcHRseSBrZXkpIDxzYW5kcm9AZ2ll cmVucy5kZT6JAk4EEwEKADgWIQQVSLyKS00miPmw2vfsKeIJDOP9QwUCZN6OPwIb LwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDsKeIJDOP9Q26qEACkbm29cF5f 7J95TAMOzNfjwfgEEPTZt9YUQxmYYlO3LGOShx/hogoWGhpw6qXQP0/lRlXcmNWD 3J5rN+YmQSIYRP0VvOVJ/XYpgsoS90JwmukbJte4Wu1Goomh1dPv1z/ag4jToSzu faSbwoUa46BmdRBOrOH/w/Ro7U6jLhH9saIivJfexksYeMprXahgoWsFGurl7mt0 OaN3C6YSMkoJjpufCTfHKagBRT4ZYRw1JpTF+oap0VZgKtg33pnxVvSKdZJEY9BG su47/eZM0bOCMll1upitF8TQ5DFING6+SRrmT8xAyUOCQBIid/9hBxX5mTYTjKW2 ZVteOkCowJwMwx134BffTMWMAMRw27Vxtuuugn6a9yd9pbK95aug62VpbvO8xWM+ RocqeuQvh0Ii8kpH+sLjdpceMf1c01cYu97DjtdQ54FGtI4r1kOyh/BO7mI0omoJ hFunhQKKM8q1xUyIs3RvYALuM5VzEWCHwXBYdiNWYDVBC/sNje67A8SXXXjJLhcr 9cCpJ5AUmkrLoQvgdewMpuIlmtPRbdv8hkmOUDPkD58AcDirvggXp63IiWlFYQ9C EoDLfUYQ6aJmw1fRI8/QsP3Q50aN6dkZRsDCcpxoNX9YzuU6+o1ha+ZNRpmfJvZg ULP33eq15gJlsGrxo2HZ+f6w4pFFy4juDQ== =MX5x -----END PGP PUBLIC KEY BLOCK----- eza-0.18.2/deny.toml000064400000000000000000000301721046102023000123320ustar 00000000000000# This template contains all of the possible sections and their default values # Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration # Root options # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ # The triple can be any string, but only the target triples built in to # rustc (as of 1.40) can be checked against actual config expressions #{ triple = "x86_64-unknown-linux-musl" }, # You can also specify which target_features you promise are enabled for a # particular target. target_features are currently not validated against # the actual valid features supported by the target architecture. #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] # When creating the dependency graph used as the source of truth when checks are # executed, this field can be used to prune crates from the graph, removing them # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate # is pruned from the graph, all of its dependencies will also be pruned unless # they are connected to another crate in the graph that hasn't been pruned, # so it should be used with care. The identifiers are [Package ID Specifications] # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) #exclude = [] # If true, metadata will be collected with `--all-features`. Note that this can't # be toggled off if true, if you want to conditionally enable `--all-features` it # is recommended to pass `--all-features` on the cmd line instead all-features = false # If true, metadata will be collected with `--no-default-features`. The same # caveat with `all-features` applies no-default-features = false # If set, these feature will be enabled when collecting metadata. If `--features` # is specified on the cmd line they will take precedence over this option. #features = [] # When outputting inclusion graphs in diagnostics that include features, this # option can be used to specify the depth at which feature edges will be added. # This option is included since the graphs can be quite large and the addition # of features from the crate(s) to all of the graph roots can be far too verbose. # This option can be overridden via `--feature-depth` on the cmd line feature-depth = 1 # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for security vulnerabilities vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "warn" # The lint level for crates that have been yanked from their source registry yanked = "warn" # The lint level for crates with security notices. Note that as of # 2019-12-17 there are no security notice advisories in # https://github.com/rustsec/advisory-db notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ #"RUSTSEC-0000-0000", ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories # will still output a note when they are encountered. # * None - CVSS Score 0.0 # * Low - CVSS Score 0.1 - 3.9 # * Medium - CVSS Score 4.0 - 6.9 # * High - CVSS Score 7.0 - 8.9 # * Critical - CVSS Score 9.0 - 10.0 #severity-threshold = # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. # See Git Authentication for more information about setting up git authentication. #git-fetch-with-cli = true # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "MIT", "Unicode-DFS-2016", "Apache-2.0", #"Apache-2.0 WITH LLVM-exception", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. deny = [ #"Nokia", ] # Lint level for licenses considered copyleft copyleft = "warn" # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses # * both - The license will be approved if it is both OSI-approved *AND* FSF # * either - The license will be approved if it is either OSI-approved *OR* FSF # * osi - The license will be approved if it is OSI approved # * fsf - The license will be approved if it is FSF Free # * osi-only - The license will be approved if it is OSI-approved *AND NOT* FSF # * fsf-only - The license will be approved if it is FSF *AND NOT* OSI-approved # * neither - This predicate is ignored and the default lint level is used allow-osi-fsf-free = "neither" # Lint level used when no other predicates are matched # 1. License isn't in the allow or deny lists # 2. License isn't copyleft # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], name = "adler32", version = "*" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The name of the crate the clarification applies to #name = "ring" # The optional version constraint for the crate #version = "*" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # The default lint level for `default` features for crates that are members of # the workspace that is being checked. This can be overridden by allowing/denying # `default` on a crate-by-crate basis if desired. workspace-default-features = "allow" # The default lint level for `default` features for external crates that are not # members of the workspace. This can be overridden by allowing/denying `default` # on a crate-by-crate basis if desired. external-default-features = "allow" # List of crates that are allowed. Use with care! allow = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # List of crates to deny deny = [ # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. #{ name = "ansi_term", version = "=0.11.0" }, # # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, ] # List of features to allow/deny # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. #[[bans.features]] #name = "reqwest" # Features to not allow #deny = ["json"] # Features to allow #allow = [ # "rustls", # "__rustls", # "__tls", # "hyper-rustls", # "rustls", # "rustls-pemfile", # "rustls-tls-webpki-roots", # "tokio-rustls", # "webpki-roots", #] # If true, the allowed features must exactly match the enabled feature set. If # this is set there is no point setting `deny` #exact = true # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite. skip-tree = [ #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "warn" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for # github = [""] # # 1 or more gitlab.com organizations to allow git sources for # gitlab = [""] # # 1 or more bitbucket.org organizations to allow git sources for # bitbucket = [""] eza-0.18.2/docs/images/screenshots.png000064400000000000000000040326471046102023000157560ustar 00000000000000PNG  IHDR *DmiCCPicc(uKBQjPCDEBHc"!fբׯ˽JHk4D-} APm}-!D˹{8̮(Ft>]xo8f곑HիWo*mj`s 5( 47ʺ-H  PF*6S΁]f8Z( ֜G};]\Z:${(:y%}u%h֩b#KN~Q+5-5#zZO IDATxKH`̞p1L05`nYk8+`@aQ>\K<ϠB@o~f3)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)5S)f뗓;}IX%w/;!%pN*z \|GLR `>~R n]I%|4(R ՞`y_Bsj.]H%qx8Q,6a_K {x c#~VK%|FI%>Dwn|x^xѝ>ӥIv֤H%:9" .t ~G xy۹i7swa.;Zd]ĽnG*˶D\97y\j80SsgtD|uz5Mr99šTR+|4'\ǥbrS6G^.eJJA<kWp:Sciav\5ud`b=Qb16]LR O I8EK ||#ZW 2 KD{]*q.\TbFJ#zm*5q+ԙ槆uO*~@2Ӧ刪Mq:TE*{M)5q?[-DdנPРq56:‹DG<eJJ ? [LN3mM +1{ gm   u&ħB}g悩jwxd4lCÁ);1#tOpb5$YڰckO6sßlK%$rTBQ7s75Өw~أ o'ѻ8:ړ)7'DpnI*1I%\RQ}TB!RGNij8<2;cȱ/P%ss'n4qfJH%`DGM\A<odrj[w:0t'9m #MʾcI*&J\$h/|MzM~JM͜:ᨼpe+F49bF{/s9u{u}I%0d&Sj"FS;>PgڔΏ y 0Ӟ|qg?JVmD0,"u&RG[&Rjb] 3L #ÎS}͌Pr g5rGkmIզA!w雠ix:Tb'E*qT"+5e^˟Rm5Sgj ) wZ 'eE\9z S6}uc#%TJ4S+WB+7: SzDwn.(#;{nskuQLgu@R!Dp^RjﷱV#SguR8FNhmlD'O36v6E_(^􁽦FELRi[O*Tj!_REG4uۙY]('+:rāզx=ɔM_&L\rgI*T"Qm0e^IMc'h:4*5:ֺE@(>c-A}ZcsFX'GTm~=H%@*}"#lkR2yƺL#*8Q<۴H2d0x֪M.a< #w/>šT;Kw*qt$'PmbFӭLO)l .<<;=I@D~ nocH*v!quיRMW3I%~pSR J̬65q&эQ4Wgړ Dk9QAqu&D{jS651RMpM;LG:һ\* t\*ٚvdU[;(GTmLRkM[6365-c̸xOoFSML@nTd8j \uv "q{x8ǰAtZkitLR 7e&>q 4udpxj86NWZ FI ߦ%NiwOL8ڨA֤H%,RQ{JG[>e^׸R'HΔJpgd؞pu-ϴ?}ܓ3F}4/Jr{!զ4.>{|=ΔEI*k.੤>jHB9>׉Q43MN Dd ̊"b|w}A9#C$޺KiΔ5yDI$Ĉx&H%6+clkJCMzMqj4E7 XȰ#,<(#t'9(HQb<ߚ"kL4pigM]gJ"@_*Ľoǻ]:_W^}16DLrNW1VjHN"k:AS3O.1^T8_gKig|8|v&xz-"_kWd^ FV#Dݎ ҈FTM%kkypoL-exX|8'xi뀈{C!; Sitk v"1aWML430GbjNX||M/EיVIR S6jgaT[~ɚLM4TyFSMśu!" ϐ#.sM?od\=#jO+{6h̼H%XVZ #Q4"L JM4hzq1K][K^7݋W"ϓߛBĔ=TO(19BQ|Mv V;QD 8ZbI%(Z}O[55 K[{M5=_WMwbkΔ`y.ׯo߾-vxǏc}ݯG忯vYx ŵ,㍦&h*H%H% ,^E I|_}k|bQ )a26FPc|F^wLً IDATZF"mן \x?ݨFSp@CAt|qʦ=cLPl%leuܔern>\m۷|72rő ߇6xܔM4NmʦV3lG6ގ"ë#'hJ!VȔ:N$5}⟵u52_ɝnM­pMwWnsyJ|Dx)U(1o֏JYWm:[}/LiM\<֙o.O?.I%ͥjQBה&Y;ćѴeE2 Sk}*E ׋g$V"V^KQjz!WlqT5;\ 3!Z)%^S:ڴuCM3َ$A֤b[2b)=T*"nJl4h6١!b1^IzmX 摍uDv82ZT<éGdmWg`Ҿ>GkjT>RP}Nj7C! `~Yiճʦ׋;6swlgW&`čzM"ès-)xͫc 'hmSx ۷oW3߿/7޿vZVb;Oﯿ 4޺@yU)k4L[O k5X<N[b(UC,V||bQ|q sfj{\dxFPjI'6+r1x>O~ǟM75Z}ZI*@C$Xʹ}ʦFѨ 䆔 `JM8kKnj+ުoO l45‹:ScF)!EϐqB*kQ}OQA lv.j)zxKc< hlpqƶ6joE&h*֙4S?کb$"Gkڴ OtK;㷕i4-[s~w`q$OkQbkLCMMpO)?'/b͝%:Phk?^TCmMzMOw)ۊAMsM#ý"GLC{M)]mʦ4$7ǩ=w k ,S6.sV05(4du4ŗhҸFSpd:Me'(1m552ўjSncVCFoYKY*Q\W*&RhkG"u2{uzjPߒj䡍|EC!mM2".DſoT 5զY,aѴETM췚J/<ה V@ 8tS&GiWWjCM02R-;@lz18b1G55I=6>imʦԜ\W^}0Zm4簊7`-X]F*@Pr{?Dmὦ^B tkJM7p56fT}S& ?m^v8YS޷s(5}]/qȌo)_FS0ܒ(z7Na{:t^STk"tl$r<67m 3"1|`*1T#/{M;+ m`JMm]{4MZYMjqHd zEz_;-5p,&u84{,^O{0({Ml,ɚڛ_7Y(5}qXm45D No4ʋi%,y{M)? z/_A(fF[I6KzMV>RE:MSq68ee7|bp|R="}hj8ATףhk?RהkZM(ړ5*._AI>mijڍє:hjߩeQ[jqh ~I%(:Zau;X#6^ڽdMM`JM76ab)xm߳k44a#Oۃ#.^IXqH)_+^ӄjS{/CMmv4_ǔJжH%xeTiqx[bdM\RM4EnŴ}hZݶEw^S5ljo|T)GDZJMkdM*Nq(5=F=MS>l4m`Nk4nF?C.wWVT[FSI%谚J^9הk?*_kʫ 7Ywt;ņn4Fl4HRi'y^jsjj0jn#HMMU1;,R״]R4܄҄B锚:4_̏'Fb;JFh=y}##m5EG8=Xg4~f[MqJ;zwyJk^Smnkh&┚R4{4nOMb-3.}pݴVi<ƍG?ߌZxD9Q:MW{`!-O%^o 5[~i }Ix|'5=Rnaij,\g@h*(; =A'ez-6״8ő,ȯ>5mCIm3Pv7єXH%(V ,6E<񐿻;ИdMNƷT0mj)~єF-;w=!bpĭ/EzMMh4yĻ_E$TG[$"lQa}4wsMSj f4M;ooH[FS|p2T 1w5M{hZ|ewM^W*@Qd["8k+>5>Hf.;MSy bqCMC"j!_wiJmƻ^SVZkhM:SwvgJ%v7 )0ڐ^b YyjPUJMt4M-rnj|є Xx^1amFG5?GslO⍦FGR "b5Oao5q_JMWFMӴzJFV->h4^"Jc!%⽦4TNGiuՓ bMşGEB`|?7͢kW")ت>Qjku4Mp1?F+; P+5-AH/G"kJMuM8?FAҾFSS^۩Dwi1.?יIa\3iMIp@3߿z?~۷G?~xZ__y?/o]|W𾻚v<=j),k7Vvq?/_'WI%DF$Je򨢱[ HM{׻Mul6FNT 4wV4yFQ|M!$!C) Tl(Wrzxwް6H%RI>?̶&$ę7o޼y{|q@O5 zdr:6-%~r2.x_K=M<1"-¦.j:>hqK7>Fd4B:=]C Av=K #Ap& K+Hz{>:k 黏&} ϋMGc]W#W5uo͇r.a衿&Wޙ/I*1_-Nا_1t.n>I y+ bh@()d('  EbQa(2 tD=NFIĩ~[n1IDԂ:5\JFRq*P$ v Qh.#0tګt-0fi%M+ H!*J m @N3% >)ʈV-%2$ K!xLK`oA%P Td<ΙL"0Kv`OO}RP6Wk@9FWTPQ ː:'XrH2=B:_9*_ J"TT8X}p+&W& W vtblm~v4{ylDM_JG7MN+Smj{e&^XO<3":Jd鷮tM<*N꽴ɓqx&W$#!gOL$5I8F7IuXPl% E0WMI솮(d3AL ^) Wԁ&l"LPM Aɳ0Q-mbɃIew7h_MA=3닉ڎ>W\Kz!2RécMs=Sc=:`9}vWa.`ގ S56)$8z9<02c^VUjPw~}(Qch`NϢ~jas,z?/%Z3N[I$fACIؙBduL[3EK։)B/tY{ĭ7 ?FL $}UFfQCSa0"E%o`}uMnDA\݈ݶ"~ $oiDMי➬O49+8DF"nu%'}vB<\iTNk8C(<MNhZz760bQAP{SsD:)Ֆvw^/ۃׄ"rN#7!jj!KU2lKbsʑ6&.&ټR*Ȍ4 J,.` 2IuBݵs0EjP,0:!(:māiۛ۫ˌ5\T02C2ȯ]Ol ؂Q0(0j/':NX1 ?JSf+SA0S1PL,^ =6DBU @ lߣETU41|dl6dI̱36&` IDAT(21n^Mϑ3hx1su`Gd7p"Ǿʜa\J̛榱SZ{ՔM=;ۃR~JEd笉HWF?:k8? E0]͖3.*AM nˣO|b]VnNc8nwrw]G2dp$z<o{kn%L`5j^M_;*04`3AkBˤgJMj25m8߭/ظu4D/vhGۈT^[ڐ~,A 7[lTw]J5Wk騭$mwipXt!/5cå5.mCR% 䚏νhT~A:a;onkESx[H./UDTK] Q%_HYtt [@D:yqOC;kUj_ޤQkfjp6] kYv!t?hm.?\=r Uj^8O 9u@]џժ2p}S5 miOϝCe䞈%]ȂA][yR9]i=n/䋛>^vYˏ_aoWF*ZaBŒ5Ake5DOK<&ǃ&:1hZl% !ѹI 1 0=CRyK`[LqTӵz^Mْ!.KYvLqIECJ'DJJMqz:]@`R]5т#R$V.TnkqQ(["ˆН2ՙ)ȌUz}2Q$ `4U=>:Tz[/ ƈ RLi2i8M DDlc,ӳg- %K7h:-"!S͐DNgM Dva##JG~>lAz6py5{ʢdze{p>5pRBZRk`w#sZ}˩5͵͝r06B1$)jЄP|U߅*R{Y$nj M {,ᓅFJC"kδi"N톫M?V]h6Ly  O~hC92/STO0/Pui=)~ K+j\ZҡMV&Fd$DŽѭ.6iZN"K 1){xªC'Y@<19K ļZjp"讷k 0fU!%g2(ȗۺp5*;ݧnBKj r \S_&*63ǑRjnk! &9.-i=K20K ':7pB8nO13q(_ntwa~  ;x~ԧ:gM,siPDMwx<49lg倢j~w79ƲP OWەŘ`dؼC}g#NLL&d0>_Pk{_ %I1Ob&<ɦ_ׁ7kr/rjxvjȺ&)ԐjeGLhٶrzVvDݦ>kbFX7pNd7&g.l)j_}с ђ{FV/NVȶ>R1>_Kp(6ft& 5Xg%E]Zo@bF̝BEKk?SARr| xybG/'ǥ ]_rivPP4F*q2I5S8^ Z#Hv!bj:<#~I[eAhJtΤ痬pk>S >Vo>bcuq)ܵ70o Eyx/a _|Ͼ_mw/6R(*!NR*LbMmAPh6K "Wb pCrtM&@#zkrtM(x\ceW4 gǝG:H£. lչs٠)mkٓIMGDž#o+||u fEX͈OM ʮP)n]0^$̓;qTa c O߭ۅ HPۄC\EĤ+蒎e%!&*ZZI5U/i%ýk5?(H Gt=Q"dM &Q2; emx .}-AT[K@f7V=7(_]̛p $9tR2'ZelU&}oE&͟6)9Z9:31*<*j*cwd?:ja@Ga4\Q[ME& N E}>yL,@"0̷(UZDsHdw^KR',DAZ<ȩ#%ƞ[D吏 ϋ2WxN }l?p!<M eLh})$E}~sTcGf&I¶vŠ/(eªNFu WC*,z2 0Ϟ#K_&7L!$Con$f(YWe럠.- !kTH0!s9>xtϐDSdzU]{MOI`.\13aN_n] ;"Xḛ&d˞^ g(z T@%/y#毼m(vfxͿqYC~R𭧥7$t<MMS'q~BZ K9<&Lc2D,黺m5{hTe ~\Ȱ2ꚼ;sIU!O 2p Ьhzꁅё5tN*,8*=*Bi x9 'f'$0..v|Ee}N껪6l#$ BRvOcۂ6zMH"_[Lڟ,SkQ`'&+8E)K,]:e0FDcA)Q:oкXu4xԑaL}y'Pإꏝ3-Ƒ($c?\\n*2 Õ '_YյL(=?2Oi #hҮv yhs-eRe8UڊvMS!N!C&Q41Nxa>wCA`e2XhmlIH칦D!e^ mf#a|~ЀMXoIX2#gہΔΟ62AjDNE?Wx,}Gi~}fY|s"k耕kBNG2JI}1ndw#D7?i:r$hb:I,m[aHei`-u\E5;5Ϣ&w4|ْ] +HN&Ϥ"¨`a#`A$d*ҷA b6VktP##3tq,5Q!W[XQC鹌A.:s y9J-sCFxX) >`!@oJ}0|gbƙK'MF't`:oBBʠ I ߀ޮv5ǫxIGo5r O' aQӠNAdu%+r+dƴvËjB~!MܹA iu?#7lV!Ƹ/yGLyubpDds/CCㄩ q&}CK`F,JΤz,)Zt (¶+>_tM^D*%#4[gM H\ ŻU4Moyl^SѤM_,m ֎"23ܱ[F]S bgё[F n `IZQ^u0ĥq/tgӓ9z!Q-ѿi4J*z] tzCJzŹVE .x'+\Qg rx즩9I $_ʻPw&hG }El W2L:6P'*GI6 dqT:,* `VQф{0٤E=ʔLx|"(".5MŌ$՟;pԳHSf}4_DMʒ g -Κ4:H G#Yi@睋 &pyKac߈ƐbaFZ|jI(M$1=)֩ QBttF T]B!v]Q !NJ_Cf΋g62u0lk&-YpScC]n0/y+,mDŽ|X/ \&I!' _ySGP5*k{wpCqY$v¢(boo >r 8g?}nE|\ĔA#],rF5=9|20>JNK9/M1=9uj`=Ԭ\|}"vY^$kJ̛xz"ꈏ#U^;a%>ѧpkBXf~"h*!JbR(< !ef޵ ̲d#Fise'/!RX|qsG4Os-"!la:s\,W'*B$ݿTuLܜx`ӴT 'XoSZ<6i#gM^a9n-P1 :%0kr\ /7MWqj !w(hƌdp_,:Z %s5r&ḩˏ#~ S숉K9r0ljuǡ}[1#뎌v Gg Z$bd t[_;myDMw{~Ƅ d{booZ :Z:s}K_#^^hʈEȯ r&aN+9䃢|oHN_:Bϭ<4mMiN5+h$҈ďmEժړ-(,qӮU 5bzl&^3D0)8t40_bt_DMu8AҥqHrWRhbNK2Rt]1'",hfFo;+5d|X$ZN3efEaHWaJt\Q@\QlI Ĝ cP&Z}B ٢ Wt̒N),VU_ūxH0P+逌AX [=h1~n2!#{s%( - Ge٘Q`XK-Iz ĨM$ 1ۅZZ3'fs}P[3ҦP\$JZ岺% sҤDn~Bt 8>FVem#lD YQ 2YFO"&^N;Q@MʥO\<|Q?iq"/WЍ_Yl4-Nx".}/_h>:nnYbFe[YrH2;{#YqC[N4?MXYpqX*_Gjv/8HA)-ޫ`uopȇ ^)AdʹL\"eذ-nlFaV湓{Nj: !Ge5/9x笉UxPnIe]?o0o'fEMF7wd4h>|yήnދLꍱC\D93^|zdEZ Z2qh_rB1"}Th>>mGnqu-xBd1˻2/5<6uE|tdwS)htZv"U4кݢXEMvh( 5jFt1$6מW֛ bY2*c6:OqX\D2NKK_gP5qV@9Nl ^92Sɨ! : xl@73PcsRO\okli.[G:CsWCٖWA IDAT35:eRD, W4u6tFdb8V !s hS1A>gvDr2dXUVMΙ 8HDD߆l\!#`z U Ժ?ظu{2̒%sNZ~4R}LU+a(_0ZA*g(d 덾5c˦-iB֗u5 UzrUKE:7[ɬU 85lI@? "Q]sϏo=82w/P)qNd~l?beK Ȉ3<Ă`sgtŽ=Q_gMnUCy}hcD ; ~<Nv,qg洅_|K|{ޝ0~}8).O=}ͺmCGGo|kGdEu5ٺf"],8w}R1;Xu7μ=eZ12] 6_]N,v Ip~vDOp\md%|5An!0<&W8&8y%Wmn{hUEn+W[^S.PsP Zw/*M.MM*&ٽ}HsrWZk CpT~K0Vd=+O[f 8}ǮEWbSJ.=d q,r&vܷ6lSH N7%')>?'XMԩtx0C6ѯ&*J,Ç<]~y)\6rDMv+vB:~W q ?e[ᐛ1`) Qb!7/UʘYC#r!U]Ui 4zoRGQ EM㧒} SW5 LF Fz#l)i:bԈ\ 7#߱ IyHx\dQs. Wc%ES?T8Z-gGXX*!XQ[6̖P@GiM: rD" ]wa(Df͍x{lӑhIȉs؉sNJ,YlxV)Zu9m~bӐ_1wt$<#OpP)ʦ %+ӳc.6׭+gnJ$92ioSyչe2Ɓ05A= n r傉vxxⱊ꣼sG؜Bso_8!rYX,#4 m-ե?;OY};?g߶bֵJMxF+BD"qW/wpO>~̨Q\ zF.(o<'M8/5ui}^W!}/3.T(rYH$/i**޷-:2ne!k94|O9fԤHXJ?.jۿ޵wˆ_rsS_/ϟnHJL 6=*gq11>m$2.&=9lDw$^ZQ{-?mg9YAAtj;?^t^&{=GOX|h{Ȩа'HN$pә_A:>=~&#.zv߿楑d(yVut7~?+|z(95N-Ŭ t&mܶ>|o6@OXh1S&zG|uwݏ^s 9/xА'=5malLRPB,r3?k=0l)8n2$AhuZSDOk=_ZCƨ GN|_Ol+7eOSNJdE IÝYc5>>c"-Ϗ`G?%b) Q1s93O< glB0X9c'DA͚[?8s H֮{߯7s^DX} bmmgϟ} 6w|c@ Q1If-. :s3~ڼ|o^@xD]Pe7 'O7O%r 8x3,@HN*/_|2XA${_ er_Ld85|| sS !|舾j-<9tM;(~C~l=TP^1qF#Gw JXzt< pIO `'QaTR,1}ًPC%g!\PJJC$a^L']Ur_ָHy7M7'.@axώwvi5p\j1*nDd\]h}w'I jM5r:]Z5U M ڥͭL٫Y+o_ⶇyjFSܶ~f>2bU$b+E R5DB}w?n]L;ߐ]] _w$(0ddxقz1 N▌cntL?f* vF}y}-Hw##/&DG{ZZ>uˆrCS//Z͚Y ;Dy)6e#kOZtsoU:>(2aS5+Z5^Խ`[fz[ߚd(1c;t(2fԕ޽iMMo޺ZCįktu7F2y~5u-#9#XcbM\cyKYHZcz~55ԤY} r,0HbՓ~5yRZ2$DT4u^n#!)A:Rcע•UU_Snns V~:c]?WC&7q-n+٭YĻfw\ͻ98t*pGyx;$hlExD$K1QTfo;Xue{[uY.W?ʆ|6m8fp, UYBCZRr Wd&7697ml\"גּ w'eE@o:>h&KיI­5tm M37 +;x.myf>\ }W41 rNjEMAi)P_nz18w1gwC8.5'&[V 7"Y)[ڛa~`R,gЗOìy:]F][_RuMJSz,Uތ"}y}Oպ\R&3p(.[|ɋ=ˋ׳an(-YNG*;Z_$'vn~XS{՗5O~ܬ_jmo}wo7ukò3ŗ`]\BDzRƦƺ:BCRKwm4}Ԯ |`swԝE2.>qҥlD)c 1xy7qcݬ6׷5% J-,?cͽy~uuLƦ!VD Cx~.WFm4ܿWX(I.)9& ؄y "9I;_~"Jum3cU6>3 ƒ."m؁T84^j* ( z 279,EpB׍OF>/R*5ˠk|;:Ӎ>K7rL6_KȦ~T416)ezܓc2ubXh؁(*.oqHO=Ӑ-:BeY ϨLxDa*%<ZyUNK¥ %~p|V ȅC$KqA[7WΚܪ<O]E['n}+y~+nxq t?tw[sk[Sk[N9m'j|)/5cv'֮4gϿx+o_|) OOCx{z[ M{׬ɜ!=ͻv^kV1jsKҥV>61bϼUDS)z{jdʿPo7||d2X Y/ZՈ(h4eۂ|1zp/rreVi͢&Ash%nl9@ZoY7>q輛Wy̡&A;qM$MQpo>PǼo''ybtuv$9vP 1r%%d4_SIN;KwZZW|4kJȃ)*?x" kZ~ K1?^ӄdpx 7\1D=wğ$Z ъ_tMgX`n3&Wwgw18VrS}Ө^ sDMgk&v2ȱlZH.:$zIճ RSnʮOKx#Xok~NV#3+S& !kQFA?4S٤bYɘ'Pdhc/>ZeK4p!$$2IH 3"*8?^UZEOE~8HKtZ8蠃yvG 3wܜ б%;u ]1JX8 J"'B%_GokBDBXWtQ1m7K9t#Bcr6a0,5nb(nHܶ,}p԰?(fZNo/|aEx,vt~&<% nj.ٵؘ ! =˃ȹbeqxtnv^ \jsȉl)x>=K76I.upv_"Xna¨0H$ F%[6C͖6aLVp͌ёT"(o= Iu %@݋}<.i/_*%/*j#J]&d` ع;JI IDATx,r#xB h=to}pu~cOIc?fE_֭:iLf"ʢ|mzCH0*K?b6n^&ʆIdVKUՕkܳvYX\8u"R*A:cVenjg:f\G$RvNGo?[ϚEwmV-gꕂKC=5m?oa+>Zs7D|[:{9PRL÷j2l[gt9omH(o}kք9ӹ_O:={tGXp-XC՘;6=qҎ^dn8zkA_CFY۳5|]e:DZH*Ow*^:k4utjf{JrE3_?<*8֡#Twnk.M1Rs H,D@T+grkF";zg Iu[}ǬEe2vpR&&E{$>M$;([(5`Aw#L֯pW DhFtM:G)1SI]%Mlt x\f>n#  _Vp+sĭyqKp͇D!M82"?w߱]yo}Gc`[-q*.x`]d΍]mjouW߼4FÎDEE7Tw',ԭa8w1r_Sa]zo{ݵbXJϸ64X.aQ> R8*gR2L$3A[h$I٬POٻ)[4oyN9/o NiV;$+ꪆYGF-L0o=yۺaY2lĩ;Yk꙾dh< O%2[`j^R)#5W>/d/>u~ :kIHHb Շm}`͋9L`DŽQ6$U41_ۉYȌY!v? ‚c/i #_p^a۝pY+[g-;>ί,boomE{f8LoavL,S ޕ6WΚxdNNtmٵK6\I:VrtZ7^͋uh(*6ݵMhvm,תNhڅ((u U_=[tR`#&GrU͖ϋ3t$IQkJ74TpϺޜc;ٻ{`5-snNrdBѝ((ibV_4=yA%WeiM}C= A$@="ն64K kR/ R}Fj 5߫`)IԾ EM4];:esNBg4X *jB<@DO>} jW90"X6SBQK3Mv_7)'/}g;UCɱ~KIVDP'DB$&xtuB OQKD>3[Uw{n2>Nl¼ohqyUN_1czDGE0kvi{t.]y' --ocR\f@Xlru8Z^"fa|m ]F.D-LPxM}GKFz]?{Ek˥Nz Ф"XwUbEE ;BznswI.ygvvfvfvvn}"vI]ev N>.ֲDG×.;;*--5ƘmZIiN~o嶷i)ەt:Yɻ풷׮Q1u[O<?)&;pcn|=mwKhz? ROR,Sj6>wbSwۮ;ɊeMǫ_dMJﲲ0n?.\ rO o=zv߬]T^CcvֿG%G%DXѷvEǶl*rRuU7:a}Q ,XUȌoK-e<_^r]8WSAS30WtX\i},%)0aم<ٚԡWZ$&’`BTDh˫7<VrY{]y\lqv-(go3GM  l9\R 웑(k UAjh\&J+P滿V# 1eG.4$stQF^/+Zԡ;hۊyC[]cߵCt;4~g>UQ8()i߉\ϝW/Tdg8[[C::/m3uǤ$ UIfrt</#Z#.5kpS$盿k3 RDMM1ѹJᒫU dEY0Og[Uo4>=>k;<,kg>?|<jkk>uworUꤼV>>U}!UU]Cetcݍz;ߖ^D#s.i S;JR|26ml1ٿ7AymTWo|"|Zw _1zz}@Sn|ڐV-FZ6h5=;R(l^,y^S8QSpqY-+7o.ʂV쉳&.usesk"#Exa7MN%tU"5:kV4J^#_;yOUNaW4єbpRT&sN3K@.a&ټ0m۵MM͢R),V*3R⼩1dFNׅ(ZJ.USQa2yB]JWSrKLG^؜|}-Ȓ+SxҮqMכ2r̠.{tӿ;:WhFL4h;.|F7hӈA!yGULZS` EQrEQv0gEQ昡\}K7ɧ3ÛۊyClTB.2nx/35jխf~l݅e  Եz#jݮ?/PTMO WSyz巊ͯysiQؙ\Yǘ4cdEo=q]/iM]4%$d[:Q|xs~)I 8Mo5tPyu94oc4XC2:eN%Q.wiA13(Jv4X'j s`tM4:ߨ}u` -it(TԓEMFP5; 0'Rm/+@enI*.k#G#Kt$pHG`57xO& §.h|=''[S"o%ݺib=5dJ ~a<X'w?eoSfshWޥlW){f lN(5]Fjs)bFB铱i MF)vI|IuNi`EzUW[UŰ5D4VTWZ]NJ7驕%K)7ϊl{v&x:ҾSkohw߀%P>?_a֘=Ys I$9'PG.e{T1 7Y_rI3к[ p9ӹZPfNШ&1& N a93'JNp ]sM:ktMڹx~ GuF}B/>%.ӒMpp9rUzť, \{Ce U{MH̔*]MG'=cMLN71)c"?;] 6P}jj*WvOK5(\-5LYF J-P=hd(yRPnZzE#Hp鲉Ȉ"QN{{**%*%㻉-3 ^j7oՆ爭^OU? OWdhU!{=:."8>:4.:Kd{vPNv;_|.XG$Ą閞$&(ˈ+?gsqq5] }dFE;:S1-,Q5i$mT1B̑Ǝ# )HM&nk$3iOKxw>v6OWO휞гkZT3LF Q杕Gq긹!#)Au1q+Jʪ9qsX. ]P_m52^׷ Mq kߔUEM bX wZ0K5`OM=%UeYN5TԜ"Y,w55r\$܏ѽ)?JiIMj/mU}y)*S*I2qt&Q^[? d؜&O&Ҷ643$"1*V=fBoǗJ<ޚhʑ++8Ԣ+&y72a0Z.4fM0%1"b1G'*UpltDZ#:\{B8m3^dqv7]=(=#|&&H6@jPfAo.jLHЭW2I*-mkHJoB?ҒOMa]dlJY2UU-_TƤ=jșŭ5Wk 5> &&ӿ w:*`*(m.n\ y+U+ +rI2eEs=Aoà#u @w,r"Wçko+].{M'<Kڹ**&+D Ok/jK9ZMPPPUvb-JFrBT* .,]Ґvg_n4jYܩVKv,%UUk٦ISFOX~57ALTuܲcK3>"{}co'|mNIh~rjfDݹv?@ђE"J1fƮanzQ)q]n\;kjO!+6@˂WPd=kAteX7ɩnφ[뚈|*2^Ew6!'{koyihFV>_brq{ro\ IDATC]:c؄^WO-9nz]ySq ZzY%% k&'[~)_,, 9:֔iQkj}.~o;\q)?2֐25:26#tL$2/S} q F=R=46Tlɗ&4~D!7+[ ?Z}pM$3 ]y|r:;od/Izϖ!i>.hy/ |d5M ibdzif@[n6lMqI]fv gd ӳ[f=]ɉmg|F^?x)5WMO|gaݱC㵵֘Ҳs!n'nARXa29~@wqqy.iھ)Lo᧦|8,ZƆriW%8(cYw#cbG Ҳ"{86&ak\u74$2!>~؉C28r`ܨqttNȜ^j/C;h]V4]9k9m3CF_[Y<ֱ\&E|7 @PRtG@D=+-;hI#kTJ{m1.ݱ[H䁷_*;\#|G/Laklzl\&Z-,l+ۡSH'KgM6IĹlr;yWyծ=­]o9]g 2(f1?з11 mܗ(9Iqa(*.CljNҦ!=F\ >nF\-L7_MT.l_Ԡ27L$J +U\I]ӹcO{ʞvvؽ֬QWUi eGuc퉇_yev|KS|LCq~ **FG:ҏ9yˏ`2$Kڹb^=͐}w%C'o~Ɔސ@M&88gw|U:о{~{xמibtO矚Gְ^_|2ۼYӁmvSB&K$~oN}d p 7_QM7Ke9Gj_?+&.#P-uso8NLbQov}`PAalEѤ_u+(s@!M+62U|6iC-*$UJay]h}̻{i0WCH}7ގx]_n +묁D. `ںe]HJ0[ xdl\N>*MD>dn땷tK<箘6&hE];_z|HK$f&!M3o2="b 6?2 =:8:U[e+hi?;fq41/?3Vy<<2y: |MȪ]E∫G^ N^wa'М:x{OxjctNLh搝t7c꬧g8qjj.\v_oY MDDׇ3NG L&\nUz4et'ƷIINCאp\l7֟:sZOtwWU]%]ϵzwxV*UQtUɉQq]~!hW)pl;}>Q1qt_VVpc|~b6u7=ADxTǩ3G~xa*òzN;ZrA**"2"V۷lܶѣ2 ^{{YWOPwzzLԋi ku糎ŧ,X+ؤo+ i}ٻsS%EWfX$&+Ur}5ϴ+ж}SLU[>d:U]uٺ_w08}ZZQi_d p#heDv$wDΚ\VvE(Mɕktp@>qGj|q[QTtJ+&h 3N k_UH\5qk(@Qbr›SOMM>BL}Z ͕O`Jn]$yꦉF52EVC؞ET?j{bk .K_>U8X]ݺf+h.:ik84$_C>DҺ twpOzZ{xrm`OWتD;ߗݨAG>Oz7&t?/aUZ#]E/<۞EPtl߭wV@+&dlH*71Ү mF ߥSO?TSc'uշ쩆>ŀlESMM qy÷]RHOж]Ѵ{M̵ :p +śE. ;q-x9z1{LZۮ/8ZU:)WuIDL+X} 7pʺKF1Κٳʯx7}W6뙫6.p1WMⲼÛvȫþ F&WNل*7xs]UOJL Z\j4n&Xg 2D?dk :+b^MKp$f^^gMkiFfqQӿh^hvPnx7M͇6ck@B?_UV׸t,s)(\{\QY&|7Ⱥ{Z W~%DSo;"<^o=G[!Qv BYY=qjl)tUI m컿YUzЊ}o%\Vf>8K}2ڻއdlHUnzbz&M9HH`8yjcG lN9W)7=)6~;ZD@˺7 :ξpvM'MÇ_nNkվ{YGVSny(ѽ;׊ҾSSЫƣ:sQ1A H@j2V|^WN6pHL4*MMqg'pw9|8qe޸7}1W@lAi?rIC.WUaR]nvt~hI1( Z+\WKOWDv xiΚSte#ӣྡྷIkI8uָެv>ow#G9hZy*Be U򫳢'&mPp-8k%^ɓܶD jjDڽD&IBĬhH@wN&1%5qqܼ&_Lnx 榉_w6 zc1E Pjp_F']ҠY!O@t$JmyykW Ę6; 4%5< J#l#MHr$ "Mljju߬X~U\9Goa4jkk!MkC'YZE:krBH8GFAo55ɭI&1O$~5O i:kZMȐO}VnU='VvS f$I)ћ d'gMlKa9n&W]dQʣrY[ jjx+9-\nU:R&1 "H/{6D>NVCo5q?:kG:k$8kSG˷wdp=)M;I#7MuּI}S /Lm[h"rthpԑ\X1bLw3'Y~(Kuom NJ7VQ&m+{G+ Rzyj>=URPR6VP,L( ~c7=9Ak.Fm+W4Ȼ3KD6o`q1SgMNp!zTRS'KM Wq$m@^Sp,a'QD&h "h݈W%caW%,9!mB:k"^&se.WZ4a1( З`hmdβl:/+J_ lAjO.&=,zYzu29CJfHjoymɩw=KNR$! YiRѨ{${>oWHnݗ9{ j:Z|f-)[^hq)zX}U+ i焇!.Z%\Y"8gM勷hpm iFP55;+ b fb{M x{7k:kb\ \ɢ\ FD'S$o556 Hƣ}/ȕ&lJNKa Wd얣wYw˪Vsztߎc}RxN>"BG:S7ͤhR^6Nl!.sBdXwh5$=@}z**T6Vg7ŔǨz@vN֪"4:ٺ:DitR4EFYTAVESWeZXUЪrb]qU%gp!W5DJU0bRI#v2kd&5Mvzrep?՚4A 2vE@Lț؛CJtkN)Ё 2%ob\I<#qd-WV)vKϥ;]D0 IDATu g|c9~g[S9F$K4 t,'&"I&32I櫆9zid_= +e};Cj=*a ! l[k(>&Zy[1/b\6ydwEvWN%wDD˳ZUUۅM…IV ynAGvxS`u-y>wCDj:-Ӯco'[&L[;ˊM\m-73"[UgTIQQ+N'79gR^-!NJÃ:fkdfU­%w^ =B̋t;^9@KZt4!<p'G7Mn傈Ns4;fUwkD^cHu%*'זWBl*3 hC[d#H߉L@ظZ҆5 oF҃nsu$#c! y4IAxkDU/4:LˬNmU6BZi'dI WesԊ}&E*wMKsXf\i]2:-jRVIe$Xk T( ɣ.`Lҟr(Wtئhk Q[s,~'j:Sӟw) ܣ5H&r৸5"p#5 u$J {=ܶRoloJ^RZ9h(|.W$rrx]-qM-\o^,x̾n3,$ӽ/1w6+ E^DQ]@ k< c\kAtUpvJV D"rHn4q/H1~%. ɇv6d-2sL2#.|C/[q4ubf=aI`VP+7˖U:"iɯ`gU:'iSJfnwb7ei2EcVo.f}ne>2mܼV.NF r& %vy F1ρ4zT+N׊ɭ0o9"4yjG;|3oz>%%Ƿ{ѝyzgg}A], _?CPz&یǵIpdʈbv=E;tu^/uMMek+Km"4%UR7f\+d1LRk յ*$Thgr[urc7T_2^] P?* #ύOb*xEMyE.3'LFyBf]!>پjNMI٘_ztl3eMlN%0&T3Ї?g 'Yb4QΚw䩳&oN*Ǝ Ir% ib_ SJ+ vcߍ 1KϏNS&:0ʽLݾ@e$!ǤVNwdH1֒jUSE{띿e ^μ~?brWQ6TmgFeD(滧tŸctVDc8k"./Kup[+_UWp-cJyJ1Su*,f]C +"n켵 jQh6&3u-V.{[Ak1[@f o[ U ¶] +[ yhHD4 $`P|?Mdp-չ\2ܶEm\AO9fyEfKuWFEgv![p#lTV5ԐP%LCI0h5$1ƹEԬ [Mt>M} [_[‚ITZ%joL7_7iyX-bf9\\3ڟol{SeK 0ꯉKI]|7O7x䬉W߭uU^W5qkװF ׇz*J$hZt#m?'p!bpu ݞwiF5ݺi4Udz^=}j#3zg<{]>JNv I!@rWSL șė+f1P@ ёrrf5s2hɺ/ɇ3KO$Q)$,IR-ٽ2 f r1DĔc2Ca%>!#NjګxԙJȺE9&$=kUSIrdd wINC~אwlS(&?MN&m:H-ߓo6Z&Ed'v5[{D7&a])F$ެ M7\bW[[Uŕ(iq+nvχG{S`L8]gjNˣ7{`Q+dK0 7AuS<+ݲ.VadNjC)[G?HS:ƇU;851xꬉk 44ԖUEBuQSGZՊRE055z ˞1kIgá}|>OόԔdk\2+&|iB4A6٢adR+[HAf`E%xҧu'}'z|s o12Qfs"/0,LQtOYL޿QyT/% 'MB.W=Jnte dJ=ǐ$vy`ٕJ[l*1L̿lwȨ$*FQyU}/'lNom3IY>C]gB9A~_CG/OYqm}tU[_Ob&Ivgos%z‘† k5r]񎓯w#HeQl䟝c6iٗ򤬪!3f[0xE\2AR4xqO"gFxQ?Oҟ_U+/ I-gҁaQ'ou}'/Mp$͜=/7|bc=~ ]soZ#}bZ%齷k1CⵦkgtՎs "^W&DF= e'чnJH2wCB9ȕdMy!$WG:1r#~׆t2N!qs7EZwMo*MYa _7v1^x2&YMf?G]hZw2S\ ]ȍgS4U3fuV11v}o'&ymg<աch s!&=D^?׋ESq9w\bA1eҾ/ ' %#OkK^I^\-:Ok ]^=Wz{{FnzF T:5\N[w%j<7Mt}e2_ Ϙ)yaKR@71uϽL\D.`&t|Vr&G7S3vLyUj6mʍ @aӥ+R\]jޣ #@ l\Ye>]/N'eYrK׈R<kbpx.ޫ <  =d੩NdnMbg7ىk㟘sY: xk%`>Ʉ_-z9x7uJM)u(㲉2uSCO(2I>q!<Ց$Ne2_8ztF$ii3O2MB^cd/dS$dEx8g+8rmYyk/iN%yJC"#()AKҭxa,yw f&[D<΄"/Kb8}t #8Bg+51'7H{D%WmPP8iߏ ɒ'O4 [B}dAyEF> iLҜIDnGM&2Z( [v[Zen} Gwx{nwqj⌼w87nD,U1I(&@Qudŏ1uo5\>mѽ) UǶ3H{H@ I"^%Yh?x xu1PD;[ط M4$!)$ "SvW\NfOsb;y%kc;gqijWr߯䙕6A?ɢGч~݇9^|ѽdOݣ1F6Iu%Me#G4b GyBeD+%eϟT3ɏrjחEWO2jw2SO'Nq(}&2&r)czTC]Lz9wsb +-&%M!WӜEMnYGԏQwI6\6GB %^i*"l#dyG’$n!N*;Yd23ћm߻ytl})y3#f 9~n?S}&aj%2b?LfAM# F#FTiP*X]r񄔋7FZwC/O2#-&#b# %g(a$%  nz=Jsy\Lڌb\?i.R7E8;9'x杮닪n%3iCcm}@Cb{QmUu.|0˱b+mhzr`51SM^\g̛&? 9wlֹo[?E][moa;-{@!v;bt7//PCb;8I^oNa_Ex+AG br;M4Ioˋ%m~dYyWF䆹ptvYG=)pT&{ujxU{Xh:m6Ki} "B-1=H< ,N6M4F[L3ʗ,)"O0O4'7u*4yf\醜yySTO`aZB Lw-TQQeP\v.fA&?WDsWlw*4Dm|~y}&PBBىof=@w\rK5 *SߠQ1%#J8~ & K xy\OO;Y4> sjb jjpbloUREҋZSZP鶣a?Vm\6e]~G8| Nw?@dra2IIu< ،Цh2To?xxpd1Vh"g;(Hsx[iq뵬~7Ԡ[RH6|usIT{ON: d-hl؊&}lXpu-phOKsEYZFה1qe'ɜ~{V2 sO4P4ٻ1,iۋ t$_OWƎMjuDLBcwK\޹ jDI#rYD&O4+k୳7ys:컝MnIO|qA٦?Zd@0&8wݧ?us}IR)dn1ӯ[d[NQU$#ҿep%6I6mI&k5xb4 BR0zԆ6~>_`MԆ̄|{hzF:;rMI%\IVu)rg^~,0u 0^U>(0@nj~H -Iox 5Wl:khswdZ5l<⽓"n9Κ|rFDdO[>g!mSXw2k]~QRb 0&B%c .Z!tW$FDɉYOjĤgb iJ2!Ȩ^H(dry02n>;~o5R%}x`dɝݤ?(g Mvm'ÒQ;w,V;2|SBE:`I]O;ZhH12|*Me$L7Iy19j5NG6 h~ ۞'ɛ 0/)"Nɣl{ Cd ykbM4b7Un1&W7_Eу.,?c",wL2$ 9SpSHo-q*6Qdm IDAT0o۩dU1}^#54OEkyI%[Uhl jjIX-^0!mB][m1"1~ftk>$5sl]D?n荿pS$0|'pQ:[ qbRƺ,d$ml]kɞɩ8s4Al>~̋lSIRwHY#HPцHbK=΅Q򙙧39?WU-@WÉ%$&jJ"W8 7ݢ5Zxs^j89+$2 jzy$rDy4zCnuC1:'㥀37zٟn.&~4^SjxΚ[7M5a]EUIBVܭWKNJ. ݂jWb, lSz^3r,2Ef,p@HS. =$R + qrۨFuD%ء7c*]D2S7)Hխhғ}ɜP4Ub12*m #PZceddbKevANkԄ>dc𛛔P-\>&j\VwT9ߊKf+&'}lH2N; miOnwO0|hy9FaK">Rԉ# hQxpI@$&\mbl)-=7{#wInI)@dh~n^$@b#Ub |_2 f<:}" 1x;ZNؔ[ &%s0K%^c/~iCbިn`CS^Y2o^HɽJi%@D$9zɩdbv7t(4>ՊP(G$reLVܻL:'FQr\:K~~˥\D_?T6L>l1/LLKuI 4.rfջ̦ &7&Co$Ld!%-i62_7cK' Qm7rM~1edw)&G&::kyr+i/si^ԊFJKV8=؟s¹ *;F CUB8Eч({3D2Dx~Jh["yNJB]U&Ù7W̏.l>GEOSdddH=W3¤JƟOe )Lr{PlX-TQD1\C2=M qf`gzM ;DQl`CE (򬨈aEDD&^/^.w] |vf7ʰ8 7CoپׄBH>_h.l Կ Jqഛ+qttOM;E*Aw.G/zm0SP>GlRgrk uAO4ڰPrz M@o|E([2i؋ _{C(J6\FͱLF E4Ib#,ld:cшYǼӍn _vG\Cx0%zpEW=dhV n:o-1w_pŲkZ&j(& Q[T0Gv15+@ahJ_.io3엉d<_:gʣr.c~MPN* B7A3ME I + MMلlt6PMq_>O#^5r]kYYhPyk2 or_PlW*Pv4o{BKH2_EIQCzCꍖ_ԧ\>|ʞHQI:Dw6Q S5^>\(VV)+{ܖd908%a2iqE~exPC |`[;f⭺]LpnBI̖J-Z[a|_+C"l?gk_h8$HF>4U@!89Y2.oB *|?(i<Ơhv8.gB= /i뤦 L$ EpH2tjw-:\Ѡ)py>4 LJJ !!IBCwQj ZNFSb2edkRJR2ct3H ($ TXY6a&MhC',N!,ǣVrjΈ/-6tsȨ<Α׿Je5q9<Ń2$C'PI6RP^C;[؞&'Q>8\Qң_Oﯯ@CnE (,UHjLPJ_]!GXИGA'b>aTٟ ~Q18{c}+`2q88%$ðrs[5̰D?6Y13ʺ[;oKMJA.6G_v_aAϜ.2j0j PXޫLj#=&Io0V(K.zj=i'q2hBNoH݆[W@wF}Jh)AG9)E~El6aM :H59T$n;yOK)+PlWJl&S1*БѺW:RP'Rx8Dg)_Y}mPYtTPFzTuI—FBPbJ<;z}]HՍ,_#e!i񏡁2N庭_̐n;Pf{hqܚ2Ikb^IA jZAk0dVᴣƔ*WZ]zĝR&BpPٿ.ڹk#QSP G[kXXΜ4w-TSG? 4[vr+@nמ@|;y:c$;lk@Q;^p ߽,|ZhM΍?{?uiPjUEG\Y:Qpղn~z ftG,V^ᣮ)b'qURnshhӃiYhLWxם/(EL;P7Ez#- S}Se5QG"J[`Q1Ahež2Q\Azyqcb4˟1'ɀLk?3Vs<+h􇴺 8Uߙk8Sk6 ˈ0eeOךؐXn߲=X#t ǒ=]-Cq쓛e3Ofr!X~qN|ɓaL];=զt΍k2t@KSȩRL*hk~;Kn>_3 JݤC4/N5WEM\8hW#w@ԎpܰTIb쑾FeF[>0 md|ۦ_0*JG܄tPL*`QlhCMpd6`Jlc16 :fj@vZ '[kEZ(X"'dRVhwM2As9:K_S.韘0V*" s]zdηQ]QOėnuRqGg0 ko.ڼd@%@,2wJ 6DM@G\gC mdqb&識i aCi4x* IѱAJlO: |*¥C* 0PS,<@]B}79fn 3̖ _ }m^旄6t9ZK{ ^ܰ6p\Hu^I0^w>7Od0T{v)" _xj?}rK),Ԣ$⃢ Qݬ 7ޱUӒ!'%B,ƴ6נ2e5_fJE|7MUl97Fq&/ ħ4ύwq1d"W,}c8m1rg{e;)zkdjo?xm{ebSh FXZdzff$kL~Iq ?XGtk(GQoVtZAmt4dEcžb;{]pڻY$18_  CBipr¾]n%ŕ{=BQnTiIDr_J9as;m&+4| 5]O<6L̷]PX{5ULRд8C7q F(LyZ4=&sSk@":E .IoWąRşlBq$>-4X$v7ӼrږCsmZ,rkawS+.wTLlמiўnU2ȫȞ͇nڞD~Firތ&s66 #ŮCKpk5lJjYDvq,m~)ˏYy("Y9qaNĭUXe0RHFPKT@܊jZ̕^tYkVnRx&' (E͸@BWjNl\ҏ1@2sx#ayED$"HZ -{]х<ma}12S!ä8ov[1oetlg>#c=q@Eݳ_)+}5ԗ}Ge3vm&'Q\,ڗ[>5p@ש.mAQBP_?NfVWrX_dRN"# +7D-:/=`ReQ Q7~ a@(ðѕ7֝5k.fKyaqH^ ٗ;1EFG7rd=5[7&̗թ(GQKHtFVer3kJ-| EMa~îQjQ`S(# JuBI" BP4Z9ɟɉr)3A#v*ZKYWKɟf*#c9[TY\Qݒcһw  A021|{9+_"h8b|֖Vt~DMAKG;ePz@1ЀPP$StP؟  eYDFAzufM |A*_kÎj6kNM7_IYpj12;h]1`^lb6e^K+nIoB䑳F쪨+,,'DvuS.<~67ZuM5w5Fjְ.~ϻtP9x&R~+Oq|IJ>udݶy ~!XO/O@ԑ`b b+l\5" h_hö 2^;K+WZ˜,daEJwLˊ^b1M a/WRCneMw "u bII L&9p&߶ūaI3mP;Yz(vqldߔȐ*Σk5Ee]l*jhESb"CQS XU8~EE+ \xPCԼ{Ԑ;z5Z saf*jR_~swTdH'UHPpT;Өh…76/M%2ӄrzl&|QuN_׬T,ܯcL3Oldw ebaf[k4ɢM#2R%B+Ň &c| ðɣ>WQ*j˚7cLLBG8z.Am@Tŕr`L>za8jHڏM&X`bh@"";9 j$Sh^|W-+띇 YMxV:1Yуd,!7xghq<<} Ga"U'nӜVNx%=!]Ӫ<%ju# 7ˑq=} LuD#-3-,al&w'3hR8x3Qx݆ FKq'G>ڶ1:H_PmA< ~Y7H۠m|`ߙ_l "~/@UywY޹a :Eҳ/W՛m?e {4^iQ)q>[u4 \@}h#nXշU(VCljrLEmx^ZpDmGG^H$B$J5ppV6spڂɫT*້+kq=: jZNbg LLMz}rLbv']|-83y*bh>p[YN_LL 'v9/zkZ\dmgcAG=&U¬m@0^m198Ko ǻwߤ>`8O},"0$H:B8$i/pĔy0 o"1(MZ8҉C\`B!Gmfb!%aʜH2{x\7 - 5K lMD.2|PKn wӊ&,xB~L]+UNTi}t4/ o"]T'j)Loԥ+ H0QNhqC 3י]UYDM9=҃?$"zU3"!sc%C iv]mZ!v} ckM$rx֔a?٤.m؞'[ #-Q;^xCZc0GEMcF6h~~x eR@MvM@&ځ^ȳqm._RXgP7³oa*f2h.07;v`Xy+,^y:֤7 1@4lCF$Lɜ,_MEM&u i*MiV뿨ќUS$qF>yX$iE&v|0wn+#7Ɇt9Yh?q<7!^p\;m|J{?욓_v!痞+W|Ou^,53\h"S/ O{c}-̏p=ɤy!Ɏ8{4u!Al7C'u{(~ P J--‹PGΓQ~C{F43zfH"j}DMs3!Eޙ y(ͽ2EȮ׆RvU  rz$J2=K|.IXEGdt%粯? RM\tH"JO$f丨Pi-V_C,,lUa_xjD\VA&""GMILWY/B hWS?9bG zE]J`>|qC+yv0ɯ<{L1>yalEVܣ0E%VoK&cQIxGbzBaV HRs"W$m8e|9|9d;60vjYEMBhLSi\°,J&+Ƃ3{$Pz:o]oO=Ď Klzh~;=0?ً9l@܀-jn%\:>ޥmfbr>5X^?DhS8{^c 9_3P'v:yɉ@@C[lS6La& u9&N[=`R8nmf2S(9fٛ/=ڞ? xrS˥x'H__BJĎcibK멵֟1sr9}(: @Bn!=:w-o}ƏR1Z4E/e.E _ 5qR%cMc-}oT†ʏ٤snNA9[ vkRJc@\|.vcas櫵PQCsņ/s 9Srߥ(9Qo.7~sQWڗ<4uCSڳ Ie;7TO}Sg]ZU~\3bJ5502A/?>셼=G.f_4)%:B*id5@ս}uYZ"|_]n[×v麛:JUiCSYh>\س .r<dt55WK~: Ƈr ÕET֩z>!v 75Z.Z}PQ]?M!IV JJY8Kx.", ,{6Ntd19=.«䍒hLcЁC{]uMZc颗 Jަkj++w:o>^3l0@88dAT֔_|xs^|,`0j}2q} Fx]- /־61e7v&E3Y1ͫZhn5VT1!&%fqI5NriM%FBWc";syEM=DfsFgYZYVB97HnۿwWr+=y.w jb/&[t>~_,ЧZDMoW1l)NR|I.A{)z#RKyygLr*x~w#nQ1%g`rw,Y$.&qgxX8vK[tÖh> -yaw<L1uFb|"HKp+t쓖1d` j_𼢡/\Ń^-yaS()7#!#d55fBS.k-rӭh!cay |"K:&%Sd1IQb:CkbZc)>$]5\s &FX|*%j/UAK2%  rʾJKtOA˨ev0{Q(.c<`>"Cˁ+ f;V^]_|uEAzDo yPjS#h)Qʌ* Z P8>5 ,&L4 lˇ@ L4<,bN7 .%Mx?z5YgƧh5پckEb+Sz%6 2jc"B&?|`ύ8[Rs<~=o"gooHԞGHu=ƭi x_[)^~lҲR@tRT"DED32aC #sIPvNJWO+ȋ`006&ˡ<ͻc.a6?ڒk _<7lk^ԤiP0:MH2\ʹjy B&'yF{-yy'N5c&fbuE^Z \F} Fp5s>͏mݚf踳|T+CN,#aڌ `PV` k#Ia)#9wqbV)s,ח$ѯU:&ǔIabv/FV*(ϲ cP:.15rNdo8A+ J-ub$^I+Ϭ:M+D)Dv PoJ,d਻&Tftw27#.S[' nge ;)uMDʛdU۷o.>abfd%WTAmFDW%P1I_QH(p3,[hZ!JDsS]*< &D=[ykhnCy[yglS =W,R.[,!ͩӳgҘI̼{䕖h~ K <ĸ2PS[zjG/4{W32[򭙷6'j<|yQiNwͷh]lmm;L1z<neo.qyŅm5Өh">}V=vc{$*2*8(x%~Θ''Ͽkg۟)則yBž$O/xʝM`VDN,mlcm;с$m%U#. ro9i1'¬R !zuMVºzh},%kl #3C8]ز8!nu$c2t Rar7|Rk(5yPQ ~o7?mB#Z tA>=njv۶~ IDATk۝ ?2%ٷ*~lTǞUzvI GENWjq'QYQSpP0R˔dXgno'wMs*VLڗpKKMq܍G_VW? E#n m&9 Yܔ-^QDW?:nJRWQ<ѸR!޴\Y>By;Xp|-^:l!0|5(pu ]+ބ3LZڣ*5a/"0"cU K0^7g$huyM|O(WLgź-@oj^ڬ!]}GNdsc4hTԯ%+ߪ CE꾵lN;3[lӃuZ3tX 8r~1%>C.@rO[PP{P~EsCy ֗.T{ O/1}Ko&q&\$zF]WuĖ,"?cV3q&s%8RѶ#t Fw_Gp9)UFtdžXKeUMa`"Bx(1֩D7L4/2vc&hc07mut.)L!\e/{dB|q_jb_)p1V`ȘnHʰab#u;q6'j5_w$ArDSi ?yD%F#hc/w*7&Lf&eIYU|lۦu\C522I.I*RkbnCzi3 =*Tk6q:VGRsHKW^(%Wbn@6.fcm ~Tz,΅-m/)CJKkёrm!#c"J{p%zlTE/ } U'1.aѣϔꋊ 2^$Vڸ}ݾi99zHLk☉Nyт}Y>ϾD,!đ7wǞ{>򥆏}'ˤ2>ҫG1F?'N9ac_3W?JI)5o⺓]f->MƁ>1젟W<\e&'_|jBjԻg/9ݰF7yt,I3n'_xҩ>ҝ3 ?rs*1;7g6c)A>f׿EQx8ox'LL$A_QUFfwUθyg}fIYݸ`u o|,ʺj nt~^sў{}v[1ς_~[Oc_S_r)wXmc %ˋO$8>ʫg|r@DC;!G5NVNA f$pEn8C%4F4?c8ۃ1. 0r`Mwߊ_)lMёT휁oΫd."u{ιxBak蕶hq*YVp>C0q GD~E7nM4.ce͚$sksw'[/XcTeuWӼh_ڶK$wF"㧗 g*MC2^Roڔg,7Pm8//;-~cد˭VHyPA37Z"9 :bk^*;:ڭG 8sO1Q(pV2y\TSsT,y*VQ:y>؅ٓ j4{\X$ 3 e̙<{ S%yjt)Ny])5:pdPAqJRm\&'Ĥ12ohHwϢw^/8fjΪN]2uЫؼ}on]Ii}ÌӅBj㧏z4 Q1=b̼^|Tkă_WWV/Z~/_ x\n^_~fw^%HO,(/)/ #S/_~wt5X?69]Glgvmj:&98FAK+P|xE.˘gf؛z{| ײCغM2'P6Ѵ4.ܟ-7W}w_zuɫP 3bis٩Sy"0&ZbE)LfcWF rp3CǍwqe} Y 7aF]VQ&ȓ";]hI"䙓d %9E.Sg8p̢EkO/){]k2Y7sQO7L|rv"q1q y׻_:'~R7^:yj|l\ltY3ֻ]Drw`,w e?QSR9rfn_淂Pa*jo vSNhL%F<uKq!Ǒq(K-I=3ɍPo>K63!ټ6 %g{Ʈ33C?Xp>r*%HipN$7e%Dv}3 #It){_Z@З^[|P}֬+5k/bh\ )%%+!_ߢ+=dj"E83&\{0Y!ƒ&eIyIr$ ~%[8J2Hx|Yi`}9#y]pq~y,9Q L! q;.돔D)> 3 Tꩈ0b9L% R,ƓdX''Z,\ƙ_bȾ4{BaIyW*T1= VD~nXX؁;QCBdzr Jk4ѡ]b#&=$=A3#sXNG\)(xc jy\_E-9ЉKWkk<ȱ!:7slcԚ+Nv[B݈Y GQ5_\6BZwN0YjMx?7*j+;G>葧CC:%tZ',~1UIy YcVVU>tіk;,E&E5`0,{G#wίzۮmdW?nԙ8sQC'_pvabWÌ ;[{k~%8(`dS!UȖ$+s?zcehHhxh;:`w^,ذeIS8 gLr{c.\x'_vE*rs{OKrUu?~Y< O3_ z[\a\[,="<"*"*6*>Ag͏kY'YĤ <.w-3EM|Bn?~- 7^bRVG=fN?cЯ}15o>ź{g 5svKmz,]]NvH.qg,ʮصDҁ«EަeǼ8iVcqiiE7iK$zyqNLd@  HCC;Jڜ_~[Ԃ͓n&;ScqwYV7l`}_t]F_-JGh<$+J@n̓p呲[z"=,#Onޝ>%-ps kݣyڡo"5`b'iݓȍ~OnʹKRla=͋>e/ 9p6 g(ȭ;&余 փ@G&;8` )b@WS\ O=O M8nW`FY#+{n1Ƹ`xWm*.z|㴳6?|uYX\{+[/B[xf #p:6Gb- #ggm?{D˞]޴P FEPEqO(EDq C6"GB*tɥiir !֗޺{bU䆅IIyyf MFzmE'2c.@-BG~v_s&r<=w-~H"a~[w.b\z{;:;6͋zӅXlnnpMa%t\t?ϽO=󩥢á[Q]y:qyhdtbVu9/>9|?ξj U!C~)㦸_&1O&[wo:hcDpM:`Qkm=6fuUxƷ=q$e#A{4GJH޼Y7d]<Qbto14cHKcS=#z\ƾbat.j^k؁b o:3WV^tO_D^q,M2!!7%+Lْâ>{rl[HY2p2 {cP+/YA7 Ӥ]YW\pλx\ajzM -tcT42 >Ŗ|g<;)LvKz5pDNUsmX&B΁:4#"cSgNrﭖCEd\ԤԠ Q_@Tx鰡T.Q>ʪ3o4y |oq};}uS9P L-.kSP\Wgߍ1Dż«ԍ'Z|)Շ>Z[R4r<=L-YQ+/e`hhhl ,&ruǍkbϜ<ޝDJHY7pG[s:}щFc[v~ᒩͳ<65̵.,4 >~×^$FIg뼋|j:>6>",rĐLظc|{ۗVkanU2څ#,ޢ oL8- Eu@y^ǰ_-84x:^6]W)Z0 x}y~7;t- jFsoRt}<ы76Wm{YE 4-+]UGˑK?6}>as;$%$M7ݍ!gpDRd -k[dL}c셷Sf_' KR+^q-sZ^_|EG1V͍΍ZHP0PrTyybݾӬhnp׬HeQHXHXW(rGUm/'OY~ctTcA^rD]S5?r5P|%";ET(坭."H^5 &N#I)Mk_Ud+FXݷKL yo,y]*3#R(ߋ#,pS.y6(M :i>n)vGxo5A~.z7^v쬞f㬎9ݗM1#oWpPH;<:=~3lhB,vZ +޲὿\UYE9qHƐo>6ae-"u6C |ןʤD 48]|o{ݵ55+^RJN--y+eG3/&"]q-0i>bȄA~]'Wt(`@dr]h.K&M햤P+/YEld'ZxɏʖY.7HK]ZW_GVڗv BNjIR(ߋ ܴ8O`w~8%)vpp]ZMs̭M{hٿ4^_OMoZ? :뵺ps4ߜ4(2xuj5R)2L*467]+ʉ4]*j")do|cGE ΂&x(U465 t}ڄin!DLV'5=/^"p>x*W_SWOBlBA ?# 0_ꃕHG R<.kQįEãlPDy]hQ Zdp.{M+QWRB0w >YokHhr{)E}SbN9ACىlg.IQnzIkyNME9;㳞3|3{BSznr(A<]GȹSRRVL u&((BX=.~|=esoQu2@p\LE!.] SOix<"pIߍe'n$eMIQQ]b)kB}/OyİȾ L;f2suQQ@cJ܂k%F?퓬[Q#YMtd DZlf 'O5j(AǛr5[WGx\s?Q^-~\坭n^KDwropKr&v xW}Dzc2-"с@1nm;zf3SN]X=yu=< 'fz1vjNuP_\9s؄ #e =j?fll2x ٴA.w=~꣢Rٰ㻎YnO̚>׍e܂z_~f\\RV x^D=u ̚:)㦤TI*_~p|-ǾX162f]SJ:0 Eۦj͞1 i6u-5̲+8݊5fs ^$j̷_~\; {aM7{7r\0(jj坭nٓ_ORyqyٓU` S.j?!etVɝS0|j2.]+ul{ߞ{'#>%5]1F{|UC1`GW{xY`5:+vqz_lYX"- H.~凕kkov 1 rygy}RA jx{W}eRX\De^DheWK#Ymedydۏ8z! XW_Gr㧏}bI>zs&]QɀVݰzÑ~"?#>g\Y &!.7w=zߣ+` Fo!2cOMsfָX"\QUrJk>y &niS?ey|bV=x?20bȈ~j_wJ3kΒ^ q6DR7x3kb3N'.wgsew\-p߭"] ~=Y}^z%ҥrnygˍچ0YY,ۯUni_Bpʵ -l=ђ7^zE2 j}{%-?%llwCix.rfr䮌 p:zU` S$Jsb}o?n2呬 2RCrArYuMPLtwwua<5`n«vϵ;ٝ-ƇGG~>jm}-`by|w_4vRb|ZjL*{D5Zm`@h"J-9ygOC ?m657 zhwZZ*j9aQǠ ['3+'s3{l1}Ҵr.y#.o7cFO\<hCQFlCGe#R]:_Q]Dx☉T@QTDMK"=C]5bټ_qal6G""w*ZJw_>iFTDd>=K(P1>h_ЪXۺz*>6O}E2pCCx\"/F!~w;9UFGE1a%vlV8<+^8~.j5W߈?سa!adF6ʖP='ۿy_rdCtj_D_O?J&7?z--BP-#kYAmt|/pBz+v":g[uHOVxa>*LJT iLAhT5jmᭁjZ_-`'C<| R"aj_B0"*8F+rHWĆ`udYPR% ;RFGz ~=N嚽y .:4>.cyh\xx2[DЯf (ߵˈ /敕V7^qa#&GECgbI\T-c҄|Q(?n;#c1BNX!) !{ag'8hs׫o 'q.S V7?YC Y+BRPT=h-1:xF ]zɑc "Ξ6o}`O@IYe܂Y 'iYU(ɉY]# (?bRQ!;+=s좇NS(0T&-(wpBoޱy@ݵ565N>ЪV$ F'>6޲=ffy\!HQ\VbY(y Ÿa*|}n^k atu#fcVW~k/F"XL|};Z? ?NZ}w?3!=AY"?.wAo<$1窱 %.庫gf;+jr\uk8NΕǗQIvRu])t _hGCEfټys6D_t^VWUW= :orTsEM~EV'mF[q*O+jbqV-'aKƤϧa+lFB v)SՉJM~AznBHX.(5ٺlV-ּ;u`H9㜫!b񰨉Ůj]V6 ,!eYw^k_ZjCL?adە7. $ܧ& (#Ļ< W.7Ay'g#h~yzh~<99B{oE[6bh1GbNkk3TVsOb򊽦Wg}]>B  rjITUS?_ҧj-SnyGz%$y|&)IŒƆs}7R^{ᵄ@_P*$RIm]{x Z~ϻ_ \ۡ1Q޷W+*yQfsKDA"Ah*J"d_YGt T3%E2lF8PQ3r(%(f&GEDMhSu sţ9GqW\P'~Zyh gyiKN":⊈zR^ k~Z~ZZ&NWѹBpءH*ʎ:ʻKF'sfLdFkuǍKܷw c|<6>kȀddfNEN?eȯv>ed2- T&+\̀DF ˩ ;G 0:@:y̎ ڢWH~n"&7[[Q6,x'0J GdŊ}<ыIbr戛j\0w$SwpEءЅr+>+^6n:Fl& EES?6%!dtjdzD@0mtd5/3tBiâͺ Jq"Kyn\TT-k ":?[è`94O$-'+w-%qZj#F=_6ɇ™PZ ڨ,3ˮVo)7X؎&or ;N&6~PRtH>};d:Ho$<2#&o]>^zEݒ}f% SGX~mjl&LniM@lnXPXm1,g.,')̧y˴G~ZӉxp&5Y֎fB H5u<ᛐJn?w-'싚'gR(Q# 5rmEM67Ed?a/t"ժW1 GU,c3%eXql[F$T ˻3NtG;TJFSpLD!M^-ǘ\Șor>gEMM[NL%Qhr>QO{Ncwm\ʃ)sbP#̖+JQXΚ8t4KIt8TU}h[PO<$f w,{TDbXb]A/Aʈe0hORY|仦#QS/PVY޷`SL۵:o."x▰@O$WoȻp=Y3b@Q#Q!DMH*&5u{`FY5xiE4γ5@zJ Usy1s.fX[U,+@ZʐƓ!\S)jRIEsP IDAT]xG ƥ9^ٹKv>GQ4_'X<⫺PY~ZtWa܄(i64K8O\%g^xhh8J䔺^~PR#)i@g35pϺvL/h^D`@ǮDM8TYWկHL:案;=5JUEV\$YM@`#и$Q(KϪ+d8I̐A4j8m+zʈ Aّ0@ߙ;`Z1B,T‘jY%6ts dzbsNˁZIe0?Xܩ` Y,TC*Fў=jG?TzZ #ߪPhs(j:oHp@7}39U uMQzj\G˪ذy"(Bż:15G?EY +kJ\VHORt落JT(|,fi8vJ"$4O|b|$Ubty/c-hfblX$Ng)q #:=BGpF܇ڗn䀨 :zhO! p#=5Ѝu]=55kp 5 }<3K;p>;hg:ɒGr)Pi1tz`z_1ԧ+*dB;Cipn!U8oumfp!CIrגORRIX~Q!=ɰ/5BmQS}sե*Ŀ}HQFEj+DhU?*߰#k*gUGZ,L>b&Wv)^nMu]:Cׂy}>DwB4t\/k0J 2smukߡXuΎo(Zd2f'N iFݫU׍=wmN .ň \sސBl.~F@WEJO ֹZ YʉɃ,M&#/?~[[>R˶g\^M|vtؾGZPn:$SjVOZ ^JZ"'r=sޚPRǁ.WjfW"NwtE/tļ꣨:?o]3GZɟ\ĩF-c͒+ŕⲚ&!8RjNl:'&!QDۉp5zcYaSŇ}847!?3q J'PWOX-"\Hm!Œ7$Qed8<`4bdv&o9Nq6~>YWl8|[n̮iFkR7H'A$=7 l2/,uh#"튲Vjّi(D;"Ñ6^9|ƠfL3*a.>Q,Qiwq]J c~)X,\0 `;hJ͆>֓!dcvDM]X]N Èf$F|q|ݦ "H;f64J\[Rl!o؀677j8'DT5yA(*iN}iR!+_hU>W@6.8BWQUO LԘ0\u8DvÚ3mxؙZƎf^uW D.)= ᮙ0F;j6:vV=D5MO %}_etҍ݊毎uvFP7Y7]6vtN#;u9^zt| N刯'qI'&H-b^G$&)ڋԅuh:3\t-D F Y2O$(\z~8-&=wVR%ɰ y LЫʢ]:AJV{EI&K ??[x<=m:VbƏqDW>*xĽ&lUo)7[&,9TeÅEg|Fq]a4H=%kaЖ>[,3"/&r E0KjRiC}GgaWZ yEmRzR$hX@*1nX_!~xvѦgzy:6TFYEdRLw$ruCo-$s7߬HKI"Mdِ)zk iMë50(Pm)E?\ zrf"mцѢwQKwJ؇8oA;40Nj5DE{_LkiE '2ˮ-.'x>c3#maiSF?]$WiJ$Y%ESkp'Of[؁2Ϣ㫭o&3' ZF(jk5)9[CF3mK%ZZaEMj)UvM"?w 5]-;|6wT CxwMMfM.VK@^_LtW@x ͅ]2=t3 JS\^ŽШTVâSN ڜHm:;1UmF SJkqWɡ^CPSU aQ73%?ܙFC I#0R볔5.P?LE1b<&5 >*rm1YL0!.0QKaK.fU=rF~3X杈4ZekrhGr6EƯ)I6f&.xѸ~'5  zܬh2.0Z#M]!⨊T4 qt1gJfP9 nΥE\A.2?mZʍӢ~j}Jfp.id-莩 Xǻܝg n֑!yp}^\tNN,}f%Mv/܏DwtQbPZ S%z[m>'DWZ\ x@2RD0}@RPV;Noɻ^\9J将>Wp!Ħw#Wkr9uY3HeS4O[Fl i .h [C'C t,X FbjDjkp_M7Ȝ&Դ\[jz\!'gK]2ggq3C!i]KcGmٌ҅Fˉyr"e Ns*=QVo͘G y?.:<Ȫ@q)0XhEH/Mn6@x'! .⢓]kn|v\r|FzYWL晼-)hmRDs'a%rtoU$3GD!IWM݉G :]ԓll]ihj?Pc8y g4mMfVe7j}mhkXl5&f㵵g ^>;/^ BJɑʦĮ>s].T mHVQɯZD߈Z)h|ײKڤKCx<I c$WU_Vi/OߘȷljҌ{e+6fF@tѷ.̭=P.hhvO+*NHR~Fr\2XA>5<oz}ss/[AR'e\F _\ݴzd2dr+EK#kL,eJiDttn:)."Mde])N=u-5qX*EtNLnDM@' it4WLRK8.e:܍S;z2OLg~L*b|=*P( PɤF(M[7o6m>c',ϤxQHkpR|XmrƖͦ{jVg^uLbQ`s#F0ݿG֤Eg]?WU*ÉR/~4|]EmXdU?m(r#@p󊫛:Wޔӭ&vϱ힋NuUk1Q*& ʒg/'>VG1,#~ )5\TwԤP0 RB#! 2U-mFCQ5cJҢqMM$@3>Uh-Cr2F!bZ}%D[p!2e"M' 2v&j.İ%ɕ W &rcRcvCgN2H(X/iC,Vz{\On_}cʎH%Vjݕ7VSsh-cQfxLԤ7 NvuAV!%ˈf yO_K rFSKwk(0 eLߌ?!_P5f^9S䖼_-'S5JWK`}z$%Da=Ћo5 ds71 [ \3.,؅B}K2n`;f;&kڛm2ht\ڼ{;|$/b4AQ ώq.J7[pGF,JҿĈ& = bՆFW#zqF?Y/U4*Tf PbRĠ*5QTnBoa4Q8M1/BC%N[@2(- Q?/"%v0{قp)jm"W zkk$q[8[28Ҙ(`4" ~ n+WY!P:g3!yբy/S)# cM]L78.Zz4A|mipf<8(p7˚(X̟cJeڐUӐFWX!lmÜn-FEX̶1//w}}zF:$y+?2gt=LW =b˜#廞JoHp!Jq%MbͽbaD VcJ5<'&>um?zHVAᡁpWj?07x5ywh}{ %Ҧ:]/(<.p??Ţ 7o6Zh ym}RC$t<n0K@1 zDٽZݪ$&oK6Nh<ӜYgV@UFT;;ņLoP(qP7-(hźp mhj)Ct=٦H^*&Y0rY|9!&$usy;'|ŜcAZ:$5KqS(WX=lQD<Ө isY^΁_gc?a wMgqPDP&n(۷ϻ= \:t"ç1BLlCWR>Ey~N *D!hժx6OSa? LX]vMsŤ?L!uח?4r胬xӉc&ڏcΥ-^ȣ F DE ꑁ_?լDd8$ ^ڬ@ʚ瑏vr}%c@D2ȹZ(4Cm.)Cǧ0򧣘ԴC7rƷY,70o@G0Y(gƴ#Bz 9K$j&(RF5Pgh٭0 -h, <Ѱ81i=ܮT%JCI6{o")wqaQ\8taVל+?#z^hmm84NDMz crd^+dc&+,0f HlvZe+OVhD9 h;Q͘Hzn!sIU#1~^c~?Vup=Kf>:aʄ}O+pؽԨ/|6GO_>cm^Y%E`AvbvU}Z=/HE"?9ȝO`?LÄ/Dޚ0[Cd@rPnM)vyf|AL1YQikUW I+PŕO+U)JV|9@CQ xVn9<{k'#gZ9Bу?,q!sa.2?U]2lj JvQL+痟s+GÚUi(VR+[൚Ҹ&$#0 %|"X3IQ4^OmeM?au}VQAhr-<zNQw6Fj+ Z0FG4 =R٤n}w !#MZ|WXT#ikYTP m,D" yR4^/~-$5:gxiX6[ޱ׮;a3g޳{O\ LZ/;KdJRD  re;6PXZαCR\Eq=b(rL p%?w| DSn{R،w*8V)d,:Bd4)}p`zRcɼ/gdIcFD `aqILAgPr5\[yc ',xqԻQ JQ_UKB"AaĖ!xus{Vg=~U9M \h:֏d)Uʬ,+*)ȣ|<TKG#f ;($8:y>2W K'M5R^fa~3I  p]GcD2hp)s6\.Si.L݇8 d}z(RgE9qZl~ f bFS$L#|Tx5[4,:<ȶ#Ju b\ʘ pʹbF_ŧqzEp?qmMGO^ PdətHNQ/ۮ`L~Y|E zO(UtA#0Nk)j& wqFaL yQyK_gD_{ 1pka{MX0LTXRn wuuO`~H33"uVi3yE8E Z|\r1˵M^\T# Ad#9AΥSU.uA,68)Q,DkS Æ'thӥjAEv~/;O#seYW~HoքVm/mYesra>G"osbkuP3I="GO8UPR#?ڏ1跎yu{Xz`Lq"+]jtgur rV[/oQX\tXLq<% __K:ںf1X`}3n|rBTxH :Fsu3p=c9? Ʀ^[SK|-_8*  |.|fݲpU@H/7V)-aqDꙃ_?;7zM{d17-H~#?Oܽz^ '[D Ol^rٕ2u>F:G1;DQ~utI}6|G|_8b5)-y(d(C=jqKE ۑ-o7h23 ْk 2҃VK$$˜\n[ˏVv[x抅CwD,~ɮ XXXZc)LӺ0'z3稷kczLU5k?Ok#k%:d!hZEnhh`ѥٯP4iOrԖfͭҼrJ9Kcy=h -S|b2QgkW薤1')_X#9UiU_=3àueK+'$p[z' 6_g46RDnXFrԹZ}fMLzKVډWVgBrbAo3G.ZƟ44y>5 ]0:}e_reD}⻧=a:w)ڏ+f ;icұv6cg= 777Һ*: 5uɍ1)LR -F+Ҝ6;n\E{Z>'JŪiURrNe—pDBʹ=҆"FBk_ܒ}pSq~ROp=j =q|r`L6L1/"_-\^ ts{(Ȳe0YFHX%9n ޝ0aT&!&E M¿bt@q5R -\F"w“@w_[\Pi r#*s:zŌd?8'U_V zLBD;:B :\BNLӏDr4@E 1Ap>-g30Q6wZJ o˜IzFNPbЙ/Mkc'.@e3D,r ` yMɶj^M)vNU`?#;u#ϑ廫uhObiI߸^rVBb薂ʱ׎**˭ K:E1*TU6$r/hB\5ܙXqI}VwjW~~kWDb嫏k <FψeV*jG1Vo/6:wRa iIQA6:p?{E-ɥ$QB(ޤ R) *EQl( *4i $nr-!$;3{a )T9Zj:>Z2S@ZȢb2ǛEP@CUPU֎0/$:+vEy-ϲ}N1c+h0I [6{qX\gҿh['UnV- 7Y?&o1~DﱫX3i/3)7ӽCp.GXǎ[EL#/ng+tj%$03,Z)SHR9tp5ZT.z8ޣF ".k j$?%-yӍF8KGD4kk?ٞ.#g%jeįwFevu5PH\IۡiY+<3XRwkaFM>o/?ޮ^ &+q*9yֽF87V>A/]E!P}ʳZ_4z9=)7_6n?L?~Z@ݶWi~@V;[xQ}ͥds,2a=}p"XZrO^G{Κ s<0'WSs1yF`> e>KFl^Gίj^{kk+6Wg>3fJAV6>ZHs^=;Skc׃ ⋟{ݤa)gJhޣa`Jׂ)rقg[uJŸaY=uAucHw3vu(]Upu9&KǙQӷ5Ak2  uReii caD;Yp׺{ke !ll$Q#u- ޙRC8kDmr6tF3T1]ѣ[,p_DjQ̓b~g jd$b"U#%֒3e{oky1,a0U'qȐ3;qfbJ\ iXM Zwg ڵOƖJ˦6_QَnTOm.}U0ӣS ?Q͔YReXݬQX:Դbq+\rAvQwg` NK昊ZT)Ց$cQDaS=jY'."k> $)yTg̚0L1]Uge>ieᾷȇ.9 &okݬC*TzA]g<35:Y2?<2ۧkԿ1Rˍ qȢi,N|l5oo\oVlkd;thP܉og:#;p1Xf|kۥ*k./5˰^2s>OmAϴ֣CVͰչճ,\a᰹/hߢSV,Y7bY\5y4aCq3 ,T϶ϩ3WLv53.241Ws3+/_Ӻ WUVe5qv/ Qe:Y-j2jrլ|-lRGTjX{2؂"#cm:|H{rJ B3tCAqhnEV!FKRCkv5)_8Т *]%GSd(G 6_=h)ITȗ<4~Ý8u-b.-LbsE14jlkP$ M~̤5a#g }-$Y\G~Jg@T:Po9wB0 3VA9ɉLmv _o>5avVC͇C(>Em5|udQ* "Ƶs91Cpqaİ .EHJ Xgs(6Wg@mlG+m` &-Zds2|J)K#J)o@XLEE5>C.n Z8mks)9rYܧ`+ ]DxjQI_7vjOI Mj,5iq~Y.:{9󮟏?k:NxlJzhTX ?seb ?QDOXj7]A954\`uI7{T(;4`da J ^^")fחqC:kҒz=%Wo Dމ1uѡ-ΛG{r ϯ!FVW{\[i=:|p̕!ez #%gw'嶹g pbveZ~51|nj:-Xns'X& y1s}xї_WLhKkҮ*7_$vim5往,!ǁNߵ֮ OD@PDkǚ}qrs# -x望k{y(@@t¼љh`SlGFOWUmBe;b^&.TǑ=1IA 8}B4n:66ɱbѡ)*3tG#ٛCF"QKs1#;z]h%8*'jޑkY@Rg~.|xrK*Ԝ+>i'Z"X,P@9~7*#.QWon$_rӗng[ݩMn ? yP` m9ELڝ6hkqX +);gPs h-_\<෧F5Fݗ?}fa=F_1 Asx\2FEKf슏5}/G\O.x/kG~{M)#UTc-?wuƜ[7mӸ[Po={R\.[3减Nx/%ݍC+5Z~bkG6jzTڸsݧ?4vJą6g_Apjf%Rmc|ԑ37 p-ƼMB)[tWVu\Rt*M, J 1m{=ð v{6O k͢rBf UMZj>" éœtJ) IDATr *Wx _ 3kHEMr`iN0m~w$QOdPYCR4W]iHc9.)PӠMgR bТ@!j0޶KPnfw[{&+ ѭbg߸D*<~wM0]T@ncڽ޸|}l'5"%|oѩe GjaCVgz 0>&9qpdlǡS{su4. :7{NfJ9|b/>zz`+Jp$zp`As!I0O- jzzz]m7ֿL+?!gz* /W>űucv'\}xU^&j0P3I_T^l50 =FiG^|z}9ڥ0d/(Y-sjc@~r^۫YlZ J3m]M6eN&}fB*~꘷/{vW5Ꞿ]Y:5]ti\&/0 }b'3{uSQK֢i9,xeĸ|ϸR PWnG5}pѢ&2MH.& E҂(9\ttUQ CLY&Y(L'5:'~>yqb(=ZgiHhnGw7*(v|0E|/Ө״]OMfwçZєy7o+L2:2#±FӤmw5q͹{t4 :c?) +< El9%A^A~ ZEXL05I+yJQA]3y2AVաk̻)W߻ިuWnE :z7;?sʗo y]z0/VX\Rg@맳&,7 D_Y#sM+ε:7?)L}evC"8ljtўy~șV}.Q&Yq~aV*xeAHTkb/<֘@3{*1iZO`q.\ZV!)+W8bkst1"&(eT&c;VSKo5jAlijޢ]tzޠ3ʞVc %$Z_,kE*G]Aio0o^l\ 'GY_?j'  2iRf)rE4QܒFAHC?I+2b;RIAYm;o3ٖab—GP]Mo 7mz ^J ^:gG(;bA;Қ_Iv5y@{;x8 xQT/VXY˟Tui&Ӥxyzbj5h9ٱF-;VQ 'k.lݺm\7.挵~bڶ?63DB/s8(쵓`3̣ߟz x`PeGcqM#<cSskyeNZGmT yI [=;qV޹tܺ 5I:'8tjޣ;.ځUFKOk}H}A.#l=59hP[on0J+l^ԏ]5|j:F V3Goxy2E(,vhR]HO6Vo3dKdt6G[֤fAc.m7ʺ澱Rlq8`ozsB`Ԡg5M2mgUi6;+QK^y0Pϰk";@=DMJQ8|9V4TqPQQeH-&?i^i,bRDhQv9znjӬY&SիVӾ_u_j9/݉m]5|a8T.f.;'iQ)RyI) j߄/2ǽ߮G݋7-|E>_Rs4Sǂ!ӖLVڝ+?EǷ-YVx_R>{l3/k>vhʽ%G5] hx'=O|r办b%a E л@ h>:2k2ԺV'hL0yQ5MK 4,o:@/H9 QO4T'iRpFsL7PJbOO9 e)VjUJb)5چA:6Z*-y뫆{=s S9˼zF]T-Օ2+H s&,znƲ/m8`7X၆|hDžCIև4b|,ƣ0SVr}Z%-ڜ1`EE6dsL!Yqg6@ϯaT9*pgȷꨮ"]*;}7Pta?7jzQv'ZtׇUzήh;~ "n1HtLKOMu5̴UJj\QjXK }_yW*/x ^1j5 %%%MVmla'wE~2򒞪>8hckSըmHDZ*KMRJJj4:M3=~կ@ cN9C^Sp ^bc(75.ru > @^ RsJ͜'Shwʮ4AEB6C6ғh t.]"̒]lTd\BuZRRjWccXZViY^~w3R}VʭVkkO m ;y'pj@5C,G^Yi`Y:%FA+X(c1JdvG}e°`Ͼ竷wA:T~^ %kjlƗ[5|Rq5Bm%jE%TP?v(HZԶߞm& ⁢$Rz[/77Q+HOHo>xt'Ca1@I~4cEQ3߷wz8\ cwx|畞ӿABR|?.ϯQߖpXRY~AIx\/6v}ctOa8A0A0 K]Z@F34]xoY@EM2WF'OZ53JvQ Y 8oͫ wb9nI~֖/IrS>82֪ Gˢz if+lGP[2ߚߋlըnXfvCW)<HaJ}̬N3^w_?ܴұq'_pxmi[U׻YGviӍa ̢&ayѭhk`=W`(Qny$\wruRyA> ^sis5t.]p$.f t)Z<,t J2.IK/r޼((Y3[Ja2\V'J-S#kM^/6ID2D|nfj&iyXFl>fv^RT^(;Z8~(ڟJc7q0|v4:r8bcw?&jX/>wߓѫ+B ߔ܉g ?|a;/ލ7k9rauԑ37 ~kM;ϙ>fV۸|яӁS2r‚M_C޾ o̸m=:تJڲw \`0X7ЮOXp9e-(I3QQ4p@QNݡKt> 䅙c¢gvzNJ sEAmOH8hqeF'Ny3K [tyxPǡSL[jkQ C5ka}ߴC/'Zv2_;tI(?* oʤ8Sjl+k^@R ѷ|#|{i C9MB}3 )4Njw]E3:Ӌ:GP.(o㛣bw^׍f? 1S:;?_NwjT|r쏍rS#zNӎ_-'`AKp\!)NsLp|UV1&g {MXiZqBhw`Ew z.ЗY R1r>CGh2S2mƸju)UY sL{t%JVY*[J0?5@#^TrXWD*|}G;/:?ﻦpdRbC^|/3E8`=8<{#ưDi@.5bˏ/>FЏ<__C-7l c8Z<巖!~洮x^+~8!͡cp8Y;3&xm p jS>(h|28Gl\yO0튽K Yx`0,Dbqb ȕ|.]п|0˜rҽQaݴk~\$*m\7cj_gM0)4XL >\DOh24ϘlO^ujaÛ'&}1-̎_K :P]<;MW^COcsc;b1i-ei87CFSoПp=RA?ODN0||U[%~v_CZ#&fwE۠b%QyWB hh4t>-esb2`BL֠n9B / 9/Q).1YL뮶/΃'VzgNmrpA7$bY)AaaZڹfoդӀၮ`*8W57\Za]Idld9~y߭M\{Se0EYD/ޘ:d0uXPV^'߾S\>#ǰq8p|n%Ey!8zg;sS#wx~ᗿ|:J !Ӗ2@Eb80+ҨNrܑ -7m ,f=_=箏_uo%Z.tjѼ[RZZ/_:'@n=(B >-wyn$>1GhM4}o7Ag e -3p!7_ׁoi٨g`0CKCm{2?FI+jވ*B LC&ջz4ªHq 1[H_2}4P p0FuuSTk5 R,OcwQ,TEi IDATȔw^o@H+`k>S)5?Y[wұRI15$r7>.aKvG/:dߏi؜`HC!<񛏗xԥr/jIIO^va:r?}[$c9{or!C{r*MLwre˧>nԺ\&dnO2821 k%Ż׼%\ky\Rw;HUJul.'\VyaOgF7igfrxB/g&ujgïm?nM 扼 [_<]WD\)l4'Cb4uWCE ::\Ȁ-hjkr'E;hb3P >>9\kϡfhjG e~!բ㣞{Ong.; G55}ƿnԈJ!y`#~R\83!AWgӿ{Q0_qPRQ*IaT4184 5tK-eymAӡ8&?EyAWrT'6,v/VT=H'c"H/~LSPzilojTv^ɠG)._\؊Ք"ITp߫L1jSK4u 8keRɠU$zY^zBkiІy?"R>DCcV@NX{UcJЕ0(9 c[0}8 q?idY12٘RM\i%R #ਖl[y]7- |Tov/W,!p|9.䳯;h5sZM%T;znOZVrxH̵{U !ؔ]+=5?;k5j-᰹,&KU+ӗOB-EŌٷ!<.CHPR3S*E5o3qG$jJ*I ^9|5N-rnctH(q\R'?WoUwru2' *%o{XÛ'~[?xڒИ\$I4#;UBC;_6pͻ>YG|E4(dJY(ţ;g&9~u[%~*rO? gtzB!ʤEYBh\^O.K&~\Ҋy?zz)3hJ{xLTD7ђѹl(jXJ~S:دA$+0ӪJy^۱:ҡ=_xlA롖Xt\u3WE4PU C?}iyi3C&=uo,3`- qj֜WiըwkIߴs'"mR?3MԸ(PeݔߞFJe;͗ح|T/ q,^?} gw6L"cɰ}:F #QJ~BoK^#H.Bw伸WQ+=bDkG5wYNu.Ioܕ)KXx)9`<_ȎMZ(su^;%Jtꓺ-doALI$a%>ei&?f6حRmfH)憵vBֳ ۾ukIuِ«[{PHJ -. )U_nTkW={f!Fg?KѼ{V-([v>JXK ҚZ,LLLPWd*rM=<,B>Li|7B(Vws/B|Q7+*^˓^)1;ˆ~` Z&b;:zydeHT)^az92I^酥U1wj/ݮz`ӫT/BdH+ςzo~nx;gڴ3R-ooݫ*`]1EwƫV#u:"fI^Bd pI2C+iDMPK~>U5*EAZSJ9vqkMlZܿyIs?pkT[b-2cjQNzk'[V'v6WW^3o_97NFmSI: zsuj{iiH4iO|]Gj3js٨_/_!>^y_?*f'\}OԦ mֈz]Z;dKuͬ:Ii(nUSE:E>Ota|: Zdq᳡ɮK*9~L/ݭ oK^N3}OJ5T5,+j*vÓȐ&ʠ@/hƼYAP,K!9)Le4Kylji[(5Gw zw"S# e};B5iҤ#Y":`ì[+?{ ɸjh͖:iÝj~'L_llSg<}m\:,| fqZg2RiI>&DrU%8z!yKN+e ו'uy-M}^(/dh"eJ~x^˼I.Q4U絧4[zu6ՓTډoN!XQXLru6WwڥFoҷch ت3hrM!{<̀ LֽaF (jQDZHw)YbRvU5iRuDƮQY^{)~WY\ Z~&:d%84G8dآ$nx[2Ң@2Kb%& ) a`M!AyADmgg'uV;9a"V^V:A0yo뱤.Ki3*X!SDy}o8t$)Ѣh9 g3Lz@/EmF+j^vάh"DM"xKU)'tJ7PyIR-NSԜegNwI%KIkx̜ȡ+"ܪD.)(Mr"d-,¤VrKiUdEMpz,%fiNoͬ8Q ؜_gLhe18(5NR0o~ZiÖ7HDjs?RraSQ5pH=I镬e^OyMfƳuw5$նK] H{51tZT0eBFU+I$4‘w-NH e zhia4P$"T < diCZc4w搁~: 34* kб-pruy술fn,Oih"pzC]NbYs yƾIT=I,6>ևA%Y!h<ڂJN~͍rM rá ]<|PE_\rɊ3FG~qN4|6ơ#IUǬ kAD N\MnEK[B-T,=Z)^ HQ1+3jrK48˿2U:#+;JY3!'*yaDQ;L֣:%w:60yW3 eV]+_?X8M_1kOO j7z%/@=RiQU"Eu0讯9q$;(qTR2NBY|S`,kPJd4bzŘh."RM*.z(.H KPW2rfいG {ŷJ`7dl8rWV)S2_W[rPSR򊙹||\gP79{\] T_ eG<GIƭT^ok?z] a{AEZ+xsͼ_󢺸-4g 01a}Vdm?_Ii,kB޾^2yd*ۻ)1L!Qv} ACbafPJϽEjλtxE8UD}xT?f_z~4X _:UU:?\^'@@j*Ԅm3ti6f[_3^Wc"?D1qx.֢&&Ska eW$0vjAmZ-Jy+FqIoK\'m؁ڞьl$Yyd?G 4hUB,aޓWsD3x v0/,Mևv;g&΂:"M.E܀> L]\Mi*Z%K_Avor}جVsXdY;Bb{Eup=#x=򽦶UGԤɿhFt46g_d9^)7ְIUKʺ#&Dztk8[p{Ԅ?ᨻI>5 HK;q8M,nURg`oZS/՛PfHuމ[@+udLH,,'ʚVTbI)3<Ⱥyl.}pNW49^ kӨwx@?/'n9A|V#Jk^ gEƖ(suś:F.MYO  41fT(L /\_Pm?@ds*TDEw@}T E Z,O?SL't{ï0|ۘEM{L6O^K4T#{-+MT+j937 X4B֢1X^8wLP Ɍ73$ 9 `x`qT'l|/ iQGڄRꦂζ9e5rD"LPB}"Yn #9lu!; pRȇғQdogn( IDATe1/Tie2l5Vx)?றK|15T1ZqM)ɻmަ'IdG?k|ZJ^5rųsITe sb˯ g7RG@Gz" `5v]c/јnDwcLg5v({۽=r'2;;3vwvnwX~;]Й:)֗BBƳly^Z"Wh7q)v ː@5eLKvCsflyTb ]+@kZęO}x[ˇݱ"M= lo~)Kԛ #oJYd1Z>iZ*Ei,Ng63Ut;wZjjx.ef0kjQIL:bFZ~Xm8jՀ郧VlRt;xjQ"Q7G$XtGo 5PʌUJ߶bNZ(j)һ0]dXd5B*¥S ݠ0ZhExܝoQ q*ej]Lc!H)4TFA%Z;D\ Fa«\[IgA^)&DM7 lj P GA@7ߒpן_ڱUˇ-HBЩPvVz /p]iղC:#ɓ1Ea)oՒ|Zqc%~~OlܳN%J΁R ż/͘a4ID4HlpRYU+3<9(DJЌ+` o4ͼHıG7ص'|1R{mE|%*AY/!Zjs<(!&Kg*: sï.nv{IiVBTTR4'/GW>UكogUa^>`X݋HXXeBHYX 8L'YdӼ:N\~I(^4vc j ;;v^!OMӺ){|5)g3N,pchR 0w5568yTb G*c(*]gS#ΑJ ^rh+2s,94\iFta ^nR扴O(EߓUJU/IMU9~N]Нz"ӈm#k%^:)!"C?gC-:BAj>)ȣ#hTڌ-qCI6 OCK(|"(9(jG RTbs4SAFe|u-2SG(Jt,8p1&ctby!;RM|Ptv 4 t *$W5cdFO+;Rf&$ tZʴ*x_[ZjfM:=Lt@t7|RV'"Qaqov@7/Y i{V]18T`#S=> 肷vZLzGh[~A֮BcM*L/-ӢySf]U{5AM-ʔ'Qٟ!(; x}FqFJ8E5ӟ檔r(e/=ch!]FLw3Jغ83k8!Yl0t3so9E7뤐dRZpgqa= ów2 +×]=}gm`r+$7-qoIu2kW|))+IֻOY떬ܮӜO8O|">B(&^&N`efWEtEzyls]a[,?ɓe1n^#S=ѴXPsQ4z[S0*;ɓFUΏRc`[hZ%O Z_޲Ist5+Zw4S!gG*[P/`d%Dx!{D6DM5[x ?TqJ ZdOXg8b^=M;kBД 7(췅]n%UJ9WhZ7L)@1Rb\g@_  GG3k白mTաדdޒ2ӐV ܌1/#Դwn.RQ]]2 %QzȑcXH6f3ٖ}E"Z0C:Ѻ[)%O R%ji݃E>Ts*ÉUG cb1@co5bOu+c|-Qh@&+C)oi B`N~ V"KC+4BtMj7zQR@祫8V)J QmPc+V!(Z-W'wMtGqA=X7Kᯒ'Ls>1e|0/h9Aˍ&CTF7eAwhh<4-VJ/zjr]z:2subKS/d72%*oxeJXSi-tje4oH'Ay-Cy( ֵ|xB}5TR gp }S˖jkT*g1ZwdxSlu,(?l1S]t,vL=+yHlb%=!9XJ)2cI$e/35ZXh:o~%]-b0Mlpx<(6g}UX82h!h7$VnAw}C-MVri&QX*tOYg^ՖNȰB*JN4oF<]:c@Xc"8lIQd ܆ݴlDu{|]E*Be9ؒ? d|XƯ ;a&B\N;?jN6Ls|RѤjD9)82X28[cbcTjFj C \!J%%'ijW$W,k^YӊPymРƆX FSF7ҭPБuMJ4 ïw Juו*4jar0.-|*jWr9<|u3iXrėr&qsp3l&MbYc/NnB*ypO|Q)kVd3ym >a "C+Nc,}[F[qo<"FDN)FQFaDʒoiį5DŽ4[a7aֶʵcQOQ>JWj_LJcgj[z7zݡa-8M^+/&i}ٔwjzL>j/W]h;q.PFe@ԕھӑ"|h#M c79rC,W>v*Hs0?>[@mIsꐏYE(Dquٮxg&1vy4d81yt,殮53,φt҄'m ͇ b[Pt*{VAUǫFfhdx&,3ut[^HoG<-ZwW'C f`y7k=*)2 k:M*懱k3Z}#,EJFsO ܣØ7 #*Ne-%wQetT*'Ԣn=5 RS'j5cX¹=#pj{E ^ͺ'Wo6Oڮonf39īw/o40pļ<;wma|1YgO 8˷l`Rbïa_Wo4/Y?l?Q [ui' +ֳ's>ƭV]< غqbhYIgwgl;hyudسvHy߂D&c_>{ . n֕W V=lrM' [_)2^4Qф٤;h+VE=ϧ3zKޙ3p9'hM~OdbBa 1*k跺vhY)O8B7U7h".3!~<2n>JIPpWWλptd\.e 2t#KO$vdܿ"UT4!BX hWYZX#->,Nu1mʛM& e˦ͺv1óv}~{Q~K9Kϴ9OuCUыwuB 6۳]@4qadؤc5SS74AZ!.e%6rt!tze+ @2} a. 8 !D;bk4{5A3\jqdn]As7խ`|++ cf=NXdB_dW:%x~9rVfNځ{b!ݿ8xqٌ}/`?ڼ~ל@ҧyxGr\|qsro٤Mf-dE㶫l3ưl|qqthټqo6,]ַS?F5=C14,|Wm#:vjʒhFM$IC*,(%qK2yW2g8uJnPT[dx55j3yg<͢&U|5U†Ht'J+vNn4&d-5gVG2_ӭV]rU<.aAUE;m7mޕф4AWFŬ<~6諎Ȏv@~+;͠hQ7"~ QS7EAihY*pŢ57h/9 M<Aa%-(m/u5qA3ڡk)|1Hr%34t%(B/J}sl\xz-Om˟#pTwض2ED[ZM~jFOL*d7h_ƽ:GƯ: G,DMH4hWvݯ^K # ·ڽr$Y~CNGxNx@ BdDnC[̝d!b'e@סs',4W4a2X~O&;sdÏK'.frvN~O,ỚgS@wg$(Z-˷kspI^H,gIe0PޫU48}t%V1F4lZ ESyЊK2u~2W4à35}<O$sEeYk#{Q& ܰUwÍZFBqn;nɦT ڍHE!'a%lJj7(7b2!a s/[+ǰiS7lգ5+j؜t\K}Z!~+D%b띷 <øGC/\M@bQS(~6-BP45 0Xaw.fR4(а7ŐRԖv"NHj9R1*FHїmPtB1U!W6(药j ӏͳ3W4c\d0vqUVåe"pfd,maߛfY&CIu5\ѤS_ٻx\ޔ%w!WR5N.{&qNp@1G4LҩTZzD2jы\t|MCGiTJe W^n0BaBwhwQ*F6Z9נiv4*m\yjc:c*%<1y;5.[_-nfR:Y0e^1BW")AL&4U IDAT W\ٽtϮ^@0~##_̡M5 (CPx&mbw^~)Zsw@'. jopI\Mk,VDc0-6IEśXUxrڕ?Gv> ?5~AS֟:~vn3rS1M⋍g\^ػa{W~ޥrztP\:Qi4RҔ&r¾ nEMRD2@b&3[kP H6j"|P+{u zQnEg" hG}VE>%JJd5Bʗ1.r @:4E7rB}3UR>1:/䗕%Txc_N~ 16)HsQytst1{Wn=`:'lrx('\'ZM¼ߍ^=6r OK> J?yB4{ cH*ڣmId!.PI%~XGJpIqՖM5LƐ;*ء ¨wmj œkǷ#^%Yy\zo.>{Tͷz ozqߦS_DmЄ'wȰ[AHEL\tjQdhTOIRPS(#]P*h]+0E}΍E?B}Z\iu([KzVV =L7făEΥdCƠh,r\נƛr^@yߵ%>{C:a˶#S x>:MZ C %id`i#1 Iiwc:{ 5ը-\c>IUjՊ_y|kg2Ly*m5ϷFA3F^k?~pKw%X5K殓{KMWL:{05c.d~;%>0*z)[tu(ӪIG6f,cKatqk܌27ΑTf2M_ 3n̪ӯփa [P)d/c0 ]gLZTΆ'~QQTxz3nŸ9zbl.M椾JM6|<pps=Т)Ϣ.B+0zj2>HtzN(LЬ3 j;K7Eil;K/P~Jhb$:hH(SѰ0t+:)ԦrFJlrhHj&Zy;R%]K-~e껣sq诧C%潗ӱw-CɫqzuјlwI^ŔU`?L|?G3v>=?xag~oQn_CKLSF5 jJ|fUJ덫 x\'5Z%Z6_I|82i:Xܝ j y)8]#ɛ\aLJϥyIo:;n:"ĕV[ԉK<ܿO4#_MJ\i۳ưFy73WM)1\$*vYFQ+Q;4BY ֲk|۾cjI\DgxΘ5 V~!ξd{O'[[+9:B1XlV覃RJR:Uruag>\V)eOvh{ |bS3M ":V!"Z_lEc' E6T;#Bdn?'DMHGBDMzmsE[KRpcf6݈VtGv?[I覨hS %DMFy~heLsT4r$(1șk&6(lr5k5jsE2ӯZvͤPr{?gw;ysn;8~F<aV$J̰55-ǝW;gcXXԽԞ'(eURdY\F[B1Ě~>j;\|.8vq[~` ԫ^ jgG¶J߿3oͯ!['1L/~ǎoTLT p0ϸ@ߺ2gaFͱ?6 pomԜm{1X6Ow7_FM${49}Xed>G봖i*4Ud[SSma|)?-d@!M]36My/{€/7h G-u4hCph( uEKl%KR:HŠ( "if@nY" zM N~ 9˲._+!p.I,8мNo}aVD^Q|EWl(`r;D_;n @YeX&T5雂D&.z{3r|o/PdPKyj(8Yp(?z7Y|D  yg2B|6M}eGO7go2Cg}k~ *Z0,cX$31k}SJi鷣ڊF5=FGӵUǷm޻F'~uY\oN ?à`0Lk7T:#q"*ܦĨM)gِ6Gj9.Wͻ'+yCI9ov9Qsπ/Z4 (v=5Qm#jy:>&F5Mj}̒ÏѠ6:0 *ެ"jz bV QG ޽ Q:pͤ<4 1P-4wކ)/%̻vsU'ȲkA jGVL{$S-0vSUrT+OPK %f%Pզͦ.h41R*plN" MR-EMj{Qk[>+bnI G^ \]gj%5P:gۙoN|B3٣C",strJ!i 4\b)vpT)aȆLA$.ȩ˦oH5oVMu2wuqˇ~&u Z}Dc9z="fKEAWJC y&E/|){{dl:jY='HU$*A\[Uskk߻sD7iAԜt]mX؉ř` C)BmҠh뒞7k|+k[ |bёһi$"JKZqU$)(}yJ+k ޙL*[jܡOU{k[ˏRe{mz;s:PT$E*2\T)je=\s_3ڪ;OF;17jIsc4bZ)x.,6o^.ې,T+8`j^2Ika}Y[ؐkHRlB݊]طӿ~ڮ_oeP?Y_E[|ʫh^'C>1D75d*Ӌe-[RoN<&'>7H ѓr *|$U;믢 >nMS}ؼ^[;2YD 'TT()J!A_r*ܙNPanT\m钳^g=*03L[(N^,q$vNc2mB'BTR0LgSVdT=&-i7`<8{KgKDVoQopVݯ_W>]RaA3rPGW9OQF*Շi+q2Piiwz#%K$~Uer▽7R(&G}%5:t#D =C* q@"nƵ^^~v+MY+jO<P "e)HԚTn34Xs"(JR'TlΗJd+)Ңˏg0^Pr.]f;yXDN]BY|IB1Xt7OPueSƘ|ݵ|/ϕqjqɷlU.R}ZڑkDZDtfc;5H\2-B[|Qrs-)hgw:i6($Zq*2%HO^5;'p78.;R-?qy%S2r z)`#00T\j [LCcXsi(+$\1F*W %Mi{'Zӿ븏Z \1{;1^_"-ӂ"b|tXH!+1Y$sl&-"_'Îvlf&uڲg˧44򫏳Lam:bN#a଍4fΑN3f6ғv&Wo27;L64G ذ~!Tԕzbz"!"/{oFB)֩ޏзʈ揜 ^vMq[P| @mOЖM7wB\TΙRnB6wp^}&/3;IІ@/#M`wٖxӊmIn擊& hds ޮD"jƒ-&s.ʭ|q!%o;ʿ P>U#X\ t2Itw.iʚX"B̺ ØZ״dlcN9kE$kJևb Y,%_NE~syto$=DQc;W:^[bRD\B' JbfxiD=Bv\ n˧n˒ȊL ?0.rqդXcoO7Q^ؔqdˤǯRcђK8Fqd<Qu4:JT=5IbjBF{ð~lqGf1N\u1z"0s&,,1yC\FVMڍ? ժ7o-vur#|ZѠEڍ k~ l©0bSrx ;%b^%=|K{D˘mэKDIZl+:(XGTX *dHQX`9눙ħo~Fm{.Kb˴V<,3o ;Igט#a bKQ.ȎhdC4, 90HDg-''rᡍ=д%~ v)б7IѢu0jNeev \$T z"Z.1&h}jz^~/m`GSĶhL^TO֘Wdq$yi;tre { dPYW{`fZ!MI%?uie`$@0荙vI XJx-g%:WIƁ9`Sթ4HSG!P҅0_FիP6ߐ)YX:>suH~[wZ%o}ES`E)`STa }\2l5t*%\ԯ*2S xdH{r2̢eLe:=g)lnw'2㄁ L\=J<些.?k=5 h4&7][2w^N.\o63q:}\":}1qu>1>5/{0F?iiM;x"qaX#t,l_|c'{0'a$׬ 4էBңB7[ q-$eė()#~j5leyl#Z8@i_֩T†VK\?}bF.Դ="<Jnr]}6nI.FZ SO4ԥSqOiԪBs'LC`rKԵ)Њ78i;}?ZQ3rvFM˛#c |*:9 $ZNqp );?b^L*06*#ΆV^EPL㎃ޙnuc,RRg SkܖhFTJc]ѓ53~:cP3 E߉r!)ϻ^?c1]7?h@hH\&xm7~c5"yիuDRRD Orf7_ѤkNj*v+ŦȒU Q$뼳 /ĝө)ؽJm=M)^\1E n;?_nH\5b%#}/Lw %ó_*y!U֨ ŹRXCiUr(Q̑[΍ʻ8ZKi IDATȤ o"ҟC&/.hdEolD1G -;7,L_i M|߅;1s\avu3kY2\gigʀFLHWr>ɰą]Q8 .lB61G/OE-1ֽ#ЍAoMe[S;S|I @~_+quFQ hS磳ټ_磒Ӳ+Vx.P/񏓳WY{\z_(?[CTUpR3rfۉv/\`뾳o=ͷf<+㶖HoJxjqnv>%G _rUQnO6O`JD_Bӻ*'{O2 qDv|2Fur>+;g7 :cnmعS18θiڑ{CTosT&ZϯB ݕ7O=(ȠVb3 ,c\!vuw6lR?bҐϿaiGt`ޥ:y )PIT\sEƤGWs\}rW?FI 1 VK>(SdJyzkܶMCU_>V:Evx;uݱJIgq0QJ0 Et8}IE22PqgO[Cgr*V >zޅ#]㧬à)ͻ j5u$A9j4oOpoAMҙ'8|plaV~NBy~ ʐ~C7i`kBALIbW S܌떫CST jGZb~BN -펦DR¹t'[B^=FRb@1ю[Ť-Gik+OugԽ(C4% s41SQh@4ؑ7?*/mA/CV{ OZ}L:{x'?t"$/MQcŢVWKgZ.ށto~g17t/O]FLjFssWRti=~c~ovJyCMs= 3{4)K_g`hwaW=;FǤgz\fJ_>^yUFfp6QR&j5ػxKwYŲrIn%}'8h1FQ_wx1e6oWhܢ4ruѦ ?/>np@31ICGrFU7U[}?'{7± ͇?.5 Â̆8nWU"Mw4=i.۳}? C[fOΗF7+]PzvmW)ھOhڼFmz 4э2~y{e8^ցVR0aK;r~oٹAˏ8圶tnZZp`3Wef<{mOcrx,3'ػa67h6hΏV^zEdׄ [}QGgK?JU6ܽx4α^D f6տog7N޽pEϑx)YGQAJ)[|B imoܹ!ܸZXd ,zcЗMȣST>=v G>\co *E.H"T@f;J곧WOhyO4=*! 1{AD1 _z20F8pyFQYU['UY6W?"U/%,;DxS7t[k7l^ew/DO8~YOUħQΞdZMZыw 3yY;(3{w~?i^=sQ*&NUbVQ!mFSڕxx)ƹ=*hR$wv +BYlV.?E5E{ o42f^шS{} Ɇ$m)Qafym_`t#H,^G!]!3'Sq2}NyKt>-Vc+V laz+[cƱ^=RijI+_}ey==|kUuMdA+?N3}oaIK9q屽ۢI=,L:M )T4y:6 r+UIW"_xYԒ__.0xJY⨧ɍ= dX 2IEScx}?W|߉)Y:_kڰkU޿Y__m;&qkf^ҷ0k/cE Þ%<qkCX"Y;j@эor?_us3~؋ZiRl/51G0An=>#ܷ٬'KJKo3ӫOଶֆalMR^<ڷ23^;pc(\)\?ZM~ggCgo81lsyD|?;ֳv&Qr0'-yRuh޵Ǩe[~⏐.,ٽQ^[UJ9~U5UΆsL~=8{*) 3SML$ھ3َ>[3_8}}iJ7]ƠMJ *Jt7MףP+G #T"tkL[Wx{ny y+*Y;wM %JqEqw~k᳖39IG`;RA9L#ڼzzqi7f}K{=">YWP5)3yմOi5CwJPgӃ'n"- r#<05*P@nB{",vf%e=SPDMt|X| ŬFSЮ-[Y,)_'<4ﶥ;r0 g|@1ٛ:Nmr;)Ж oǼl Cb]%;$gBᓋZm͋f_/{q~^n;nXz'>=-Jlw+2EE{hqRrcLy?/ ?]N3m{ǴujLT&g&dmsw8,a,˛4cFvAOLħzV 5@sio3͛dkjIUYF­>|)g@BV*,J}{Ҥ6=93Q\)c]}+ [?P=_"CBw>[kGsX6rɥ6!+}<ރ"`S4 JQj(RB(Zk(QޏL@q:( HU ʙ-|K\Qֹ7B1\*֍eO!Fz}1K\QūRO, Ԯlxw@) [ 4Pʾ YIN|O}N\݆Zر^o?GL|tbrxuo^p W0{{=jC-NQ2W~jA :KUW(B:R18E|i5Y=:s=HENfCyWb+4O$|G c%GoҠ)r8? V)]z~ixDt爭)W(w C oNiNеcbsOѹ VˀBޜ)MI dAJs3.\09LcZܫLBS.tQ; 8{EFc2 C@uF梦*}`Dhֆ[։K+-DMwVJt6gR߰@h3:*gco&dvTf^D"%;-D֬I03v N*"Žv_OƔ#25 ic7]䡑˕N82ȽM@DMLNԍ퓙J4;&\EvN;9> O9ZIbiǷKJwٌXT3kָo#4뉥pZb@j忉&=ߝ՘&gY-rˎnUKcMe)LRkoscKݴpipFG\>~On\*ʾ {3x(0V]G Pʜ\kfETz@+w3LbbOҹXH:`IhF!\ic 8 jnj{!:QIgQhi{6!o7bpibƶچ5AFvɕbzѫbz&Ի4V4o b 9N@+s S#MjTx)t$IwLײUUz#6\!϶ENllGDViSW;(5Qk:wL峓v9ume)4+{nȌZݵ1gq贈Q V7S𸣷˔ww]s469yR`>5-T ٯc!uM34W4ӳ};ETSB{/g⪒rQoԁ.NRj|k:n5֖!W&]Atx$QJI"U!={_mlVk4 :ח?!,^JWbR1tИz/MZ(lK~}[)ޓ77L5+` 0MzM4V {)ΈWRx{R椛5i$IҲsEi2hK,75\3哧E#)2=&]|g* 8"4Skjk&RmfSf=Zak kQ"VWL,O'id~I$H̓aU+},hcًa'<k=]]&L(j7zHJ5L,aqS)U ouj@kx+;YlbjcR: kNJ3/M)e'է㽨l0ʶ:x{ !(UrKn]-4 ^g>T~ ͬ颓9TZ-8 nΆKL(.,-#Vvj:?R'Ҫw IDATўS2r-?14OXK]X-WѨhtTه"#2O ?S7o-qպ  ;c{+'$V'wdZ?hUs'bA,\Sq ޖ%ִ`sMe\I1*O!9"« ?&5oJ4⽕PyGԌ"/ vh6=ZS[ԤgBMoW +$ W]uQoF-Ɇ5 A N'WH1ӘU L~ůld˟]wod8"$)ޯT;rӹqlL̤V\g_HטZy-+U&b fqB=]id`l$~>Y.QY7ݔ~2k~f+5IÎWi;Rb}5%j7l<6nM,v.EABlёa+dگnȈBZd8)xTv ۑ5th+CL&y۠dij⍄ER12WDF#Jk%^N>.YE?4K N$eM!2t#,l^_9H7]ȱB,+-r:h'ˉޣ/M%@=#]!QCmn?aronضh% Z5{On#,fZH)ʨ4v Ol ZSo2D 6ZWX c;\DdVZA[)uq !pcBԤ,VreݎMmx4:z*Ϥ0b8E^&9H,>H\o7k7(՘"Y1*ysH\aDSNKN#۪nnc֣Sq]XyB[rS Nyښ#62ݙͤ^ (jb9%z ͉"9 ҾD"5AOCjq ܉=hMlޝMH(6ixl U֩bÎbE^ndPudU5Z{$0_, ̌S4JQ R*SJr>[Xj.j"-` IO/I~KiO+ !SGrX6#Jm4i?:9 D$)iTtڼh Z)0U]>&Bo=bt$h8FEޑ'Lei֌"oAHBM nχ>TDM E5#uChAyrzVG-9A0)Q# 8ShBw4K-#Ϲފk9(s=l7SRST&fpWYp"owf%: (b&aa-U%Q[,!OEBuŞh'b,4,hJ}Cdëyai-r^!(2wD;>W&SVMiZҞǥ]J+O$2}vh\'jJ,NʼU>/RtcDT=8{3IP ̂0RE XhiVto'GZ jVQ$[DQܩvVŕxjBkezR]J E]kj/͝lk,'U+SxG9#x H\D" 5Dj撂o#&XZiͨa"A<'2q1aڿfqL,ݔnO_iڹ[;\PMSk&jm'CP&P*Fx\GnpIQeW*#Jo 2iHJ\ZkaZK+ӁkxFǪ*J%qZzLQxjqb`XJz GTN |]:Ryj/2n 145elq&sM*W͟g ʹa-JzM`R85e bLbL@EȗGRmαaHRZ^_ZͶKLz7ޔHJzeQǁj)]yFh/~R\TlL+(]vqnA}<7bέGD dXZaƩ|_cXY"Uv:u`,)EEJEaR 67m*+f_˗'7 lB)Xb҇I(ᳬ gWH-ǟ]4W%3-yXc<5B tn\-i) j`(Zk{. *uCa)yRMɕIZT(S:uScKӼ"W(|gYT,:TmU䆄 UG).<$n{w?+E/ɎmZ&il)y 1)Ry"IN[z@0Փ#W<'Ic%tr#TOh}o~]杩xjr>BO]Շ^ :dM+M4'x{+Ssb+rg޼+cp3 RzܸhFW4;gg5Zi1*٠5%'RUZf<,PV|,2*Y՝$I,wQW/ʲ'qyHd;phHH{>OXj$ 4i22ҕg9ۡ+i)RbBuxg)Otwv)Բt_HtXZ9YJ6oJ/JW櫸un܅(<>1 Y$+;NFk4HuV."%jM匧Å8'*~flr줴ToVnQyv|<]ZX.I̯&.*3 +K*+HCTܐݞd՛c!E+,r T_ 0;gDqZ *@B#gnW=+s$Zz nf;6B?*ħ*e$J1*+<ը/Cgu1(Ŏt>GQd4G'0Uk gѰ0jG3~=m>~FWHA}،eUlq_1̓fPeʲV^Fi* 3g-$2*y7Vi%ɧkr]VdeSKJթ+Mn싯#yV*|r{BHCڸY }TnTPFNf!SHELU~IRTJF{S:d?*%c3[nJ?/a#,25~V3CQ'JNdb]s׹]gZ 3s -RK+7:{;OikN'E5u“LH7ұ[\JHb)Hw}#LxgMv|íHJ`@vQՎ77u~ß6"~GԌZn0iŴ8%KN>&9iʹk2XyCgJ7NFx LSPvA^h4w$S_D60!]/E8:Mm}EM;N Nl33L0C=cSJDG93H4=OI)T z[n|nsv2/~>ڻC#(]Z\QMd8wqd[&rpg ^ؐ^SEV4*TR'M^o l߃kdQ #Aq\|࿀#w<.|Gc[V]'0 U\uJP'}$C^A`ؿ$(x `a#hc#T s֛QC4xpm.ZoD"E|FG5UJiaMY5φUYUx#ȁ9Eհ reg$>)a!í 232/}<1|,Tg6* #OMꭂ41zMnϒ('4˵kiz-7^=>[ da76jaɑ@O4ʁ,ʢaIr9Cy@SݙH;{-‹N*ʛ>\;C%UuEMi?[`c N=%RlP6CWDM,&}^w)"*2/_H;o&U11GwVԻD-:~drz-ߧ7tHl E+2b%7REMv|΢C~}B,x ļ.m' nn.3Fuuvfa.[t;d%;~=pZ:r)"5qЮgulg9zӨ p4V֤Z8V!jϺ1x Iwl$Vk cc,V[([7Rø}| cYE,wX5VW1O[Qݦ+VhTAO~,,]> N /vu7eDMU J~EQBTTRD|U'WEe{8z¨) wN`M_|0F~0Duߞ70[6PXy)ua|m,nL؈yQ>WTjS%Zw)zт\hYOU,$1myj4Swyr?{=7 R# ŝmJ!3o)aRFaTa3-R3ƥ?O'gQj:,g\ǎ (|T:c>V݂ DZWWWUh &eev&:ۮ qEJ)GHPW`:lvr>OUIefذ T,p;1a]MU ڠhqqeL";8X9yr/of22&$&jFj£t\!0 QKb}c}367sI8nT Ik!Ȏ!Vq)R2&ylFLgQFc 9h|A{?{" aS/ $8{?v\ G?'7Æ'+oܰ(i_ bXxmzJz捞["W۽֕W?=zA$lq\hls WJρa18<`Z!9~qUeqkqvJ_\)(?[7|. 3`!/q&C FTyzœ +Ru/}=Mec(:?_0%S4΃N{=-K: 'ݽPYo̠=oi6c}}O3>r2Lq((g_?zXdp׷ڷR)y:^;lW7ݩ_z#%y{}Vc|,I\YL6kˊܵo^x`c}݄&j-^8+|œШtB&d8*µ1Q]LRWS3yҁzC8|ŵ $#;mpmQKJ[+s,xwM!*1_>kTQD'πnF;3u4^EыbwX6 jCKlq|g{Qit(Sv7Ը^!d-V7w9Nc`u_,J $Xes6Ehܺ[1>]FyA*Dg 'z+fsX<Pʉ9駯zZ{,;Q/RkԹOlb!=-y9TZGļ3eUܝ} ~/(wgh\37Xs"o`ЙQa.Afpzzϟ%y{Oi& v<ߵ|Ժ.=ܽ"kw|x3~ܜj18,8vгO V϶(5׷v50,bqrpկ}(D]ĦzE=u_{֨{bd:Ann[gKre"#/h?pr=-l jb-~5f4"pzEM=2g=[ԤwRaC6װ:eWo;G*[Wg?q;Y-3x,+ERyA>¾eEtCotw0h[: 젏wz|c0/iʼn.\u9;zKh@n~ynaYfJ_IiT1O爾^qvzn1)_c8_^X,:aAvY89#L^])*p>,O+{~No |oo~%{Ck ݜ}fM\ZT,U+f ف|gU cv݆7R1tszrc}/F5]R`SO]\}C?#*8HՇ{Q70+(bu1Z~-g]xMgˀ u^?yN1o< %co3պj]qsMDdA_X/T[ &3BdhF mM ȸA?|sΜ3y.W4q1\!*2_e3\}X]Y"63i\eČ-};ze6W4Yօbz >rV2Q`C '~պ\BZ:\-Tµsԇ:oF*R}[$I!o6,/<ӱǿ'Vh>obb  -H "sjb&T&y8q%bnQTZ^4>ҊB"31vuuk$.&,+DiY 6),ü鵜 )UJG{v=фozpX< rv~co}\dpL[*7ot e9Z&.*էR1_]^c ;+!N3W}BۇFuteqe< @ /3?MY1箤2j݋F/p1݋>I+РX6 NuZjK6Y(u1q^ 4`  ncކ1 +RINCqEIE>y7qU(p [PkwnfS ΍['='Щ\!E.$d_XԢ²̇)7>eaƘEKe ̂WRNdp!W*~ݻHSڽTVC!?^Vp=L!'Sg_Y˯e; & d۟j`ˊv֧Տr)FU|p-9# D Ŧ nQW(jz>H?c 'ZEt˝lk qks79W~NFUg!Pj ;hk5ފtKq;l~xZ6|"Bѹ(;̘4pW 5M3q(QQEQ /x_ʼ6quG?̘02+?º ΞidKVn?پ>~CzRN$Ŵ0  Ի $f'_DZ;{ܔU?c^$9=6 ,_x뱧?e\b_w?_=H?L(.nܜW.s鰊L ˗Y"yR)s2i5jc1\KMvg8m^`M_(F6rFcQ4#6c"nXָ*%^-L&lb~Uzx1݆"f.NwC@V۽_`lh T04 OX㪤lǗo ;oi1{~򉧷\ocѨ7Gq}$az>pvWl#etr ~4{oTX?}gƥߌۿ.3Dڭà.~h53ieX3/XX8-WҊe?q͇Wdr)c1֛q׷}K`|YjHr*Zj-^#{jvOc릎xM]s~/}11룩i9)D7x>}+si`4E3 ζ/wGg9O?jQ_כ Iʌ1?A;:ݞhUFQnWPX6TJ6`| nZVx'ws/%,-<'OGwZTQ7Ok@ˡD 2,. ESG88:[%*xPLӁ2hͷ%n}b<-PxvO2)J򖯛6y[qgy _ogGw ̺4f\G!ܒvK} f_jspm+7GԞaLax/,.;_t0Eop^Q+WZM݋{,#eKzٯmg}e:w/ʋEEb\kdcںtE 0cɹPtzHPӽ^x 0ƽ3 \E)p96=v׏n?~1^ܟIA*(^ΐ$q{Gz6tQYo4\ܷ;u'~48E0# Fqif~Bnۑ|.9evcNbZĕ F)OR\KߕWWEM|}VAQj Y=<9EWG/pY1yĻOV}65WU_Ҩҳl"!3V߱~a2!1nnv˨9.]yCu ϟ݇N:shڅ c?o'\7/eN0qm^D۹xޡR\a vSv",UTOYa'Ɔ n] ![<ݼ'pJ\^8n$ZPY/cqxϪ$7E]c3.Cv7CtA37'g{qԝ&U5ߝetx5/lyq"Z$UJ\~E='6WlF_aī_*,|n~x},"^:`CK]PQA=2 o+6hxo! 05ZdXܺ|<*qEMNIe>̤3zͬs8z 陡U)$%e4k/Du۷޻^oS`t߈(w Q dh5EtǤR^ .j:x zjƆ?w|߉8zqR*o)>r&xj5ҼfxKԸk؆XCo/{3FVC,jJUe:4VRjqMI*ֈ*,DՕ ݼg%%_ɫ;gw:Jpu|ݿ6(wT(ٵ?p&=uxW#B#^7cPߗBja;=3Op^dE5*m_UȪ_bEE.AUJ5*\̷i ˫ư7ؒv]riVmEt⭳Vة~Kv⟟1hxHZ' \?G vyX>Viw 7* z+ DM4nưH!*ބ{pJq\E-_s_Sw>7bHI p#~?t~KU_9sݎBwlۓ,N@VR-[<7<;c+)$0ͳQimdz粹FD5;`BTXGL,L=uu\޻X_3>5dJw|z$^59t9cqD]gvQjTF>Z\kYVc esOMV0b?iɏBf{R{IYA__kͬ_tlds+ټ. K/IlJ\(J(L-!L4*=<3L9X&W=D4 D q!]@ݟ%jzc#6_F㋻xED_ u/z>jmqyqa2~VfϪ4}{Uˋ=}I𓸫ц ΋9@V9xGn-~lDPN?Q>zwj`aQb=ٵuW nf<8QkY(Ȕq5˭ФSJ5fueoW/iyCp6x1J5jX i092IUCYt "W*&VSk酺ΤfzGY_ꛭo?'i_)իW׍lÛ@pS VZ0LcXl{aM~8lnOr='3,1&;3]FF}lVu,>ڹǃN^;z'OyAa`AQ7?h}ߑ*^JR[hbA)=oSZȆtG+mio#_2-(I \Ĵ{<Ɏ x{;>@]KOW裛YP:# |Fg:y8p#*/:t}k_wrQoY_N~mŖ=1k/9fVdBl=-Rcj&W| {]#r{&~'|5S[3W!&P;9,*.ǯ+NVp~T#?ޜUwTqب "4tb 7{llQ"¢f[iHdA@foNar|%#ƗF%樾~h4B  DMsJ7UJ+5YT.2ՋYkiyf#_ΦO1R.\`dznI7eS({/Y*ѿ 8Pq 8*"nPAee{G.?w뵴>xyy/^r7س,kSu:IgZo58/ۗ^E5$_w+(o IDATx;wZNU 5)?1}̮ q]0 1OTf B$=>am^:SPTO nxG@ċ_4 %RI_Q҄}j¹)_6Q[u3 !TGFbkW~M,I&g`Ic)u'b#=`Ѿjܮ&_SbֺS qo4^sϯئPCS_ξ;t9e#)6$mk ]?E' jSWɇ(O2}}eJWQ6OP@h,&N$ wڊIA]FTN t-EZohbkU5٧RT1ÏeTY &SӒFNJR4)4#)eb=?59cB~ UyȬ>ns >ѢVjL%0&)׶QHQߤb L5r) C68^컱5_GK-of7B`Z2;VwN4 j;An+, /T4ï 50xGc6ʢ&#Iϓ{tvU_u`Dp۪Ui#t!T7eh4:]fb2MVjY5 {sb'_~bB.Apxl=>r=?p8AC6٬fPEIeJvxg'Nr;p+*Dar6ۋ氡ǩ(/iz>|[Ǿ#pfɕ/Y7n1cV=^w;̤!yr]Jud_+E*RΘ`C2/zEnx,ztt0 ~5C)݋DZ[́E)؋|U^#*pHA[olc`:'01)G:>M u8Njܸ]oFZ%1~S'H>T #eءc44KۻI;Y//4!ozjrԍd.G!sbШg %xᠩd+zhYbm41C> >8oWJM($ۅ6 @]\/ NpTQ4yg1152NѢ&qywݸT@nɳAW4/[ZH[9:!u:Ci'E] |0߉KzĻNbT1V_OOg򿏖rSd׮]^ i53+i$~ f~MxZ~ 4 "5ۆrnVZQ2饻sxQztZD&O(jKrDtdH\ņ/{ϏKM\;!`ǔ#;/vwz&W;8A#M;Fu<`>ϠVqFmŕ;_W4]9Yc`j$TKJ+s@T)H9F{:p p1*楨K%9ïΰL?]7]V-ѧp6_Q5Urk\eVjFO6E^-Vb0 NДd^vsާ?V^mAf-߲3jWw9K'޽S eّt9qʒD[y,vyz:h45JI\1TT}oM0m@Hfq5I䌕<||hcԨ`:cr]R5n^d2 btRccEr0e 5wcDkI9來 $ըƴ¤\S~Ӕ9t98uT[bS^ GQt7<Q5d eU~A@~w n Omdt%)PU%iM4uox=niQ |OtǞ(\M:5mF@AV(.pUM}f:.dCVh f.#UC<&$X5y~MVnڹmsE:gq/fn(9V(|Sr>jӎ5\ iA0{^$x8뗒Pkjה)^S0Oڱ]5 bSy{/Vm9q=@ Ũ_k>U>CB QzI1qwzɂ ^*{m ؟5nR(ڡP E8x9 K7uk1.>4}_֥lzΠbd-;`Xel~4jD&oUx}~ v:c_VxpׇܲdZ`/Ci.Iwn C]%@Ku^CL׉.>UYyg(3]g?Q\Oޟ2f*D$kR?lrV)Wu4 SMzru2&xe⇒5/0gͭg$-Rx5Hy,[#a߽Ix/86rIdbtB.v8v2;/dG3o lv<߸\h[|PzzlUcs wSc%D oֳc8}6V^}!7eI wQI߿\@.>n.% D2MԾRJtn_iH{\& 6$.[s6՚~5RQwH?)J4+6klN) I! g4jo 5*5HN [N->l;G~,]O!KCѰQǃox^Piwt1MΦ2/Q2m]DodHRsm,b?w^,Fc{ijE:Þ$ٲW@ '0{l۔^ &הJms] Y)?J e/[S4c0_C#37uUðg Ƅ=5o0wN`>Wv<=q[C^6[^6mkiKn5:=7ۈjn5\,u۔}ũ+IwSC8Z&&yFV/y.Ys=ltpA3'(y gW#S:e׿3 1?1:Zi`cg=z}ܲp'u=[vud% Om󋳸@DDhUPćg"WEV݋$ 3 \ 5~_J&zr׹ۄ1݆ˇьGGc/Ao߃}a<[?mNp HCfQw=cZBze[{[~ w8v}paUǯ Rȕ'鮄xv=05'W,qh/o}JXֹK.)!7mz s)X꠺11c8{3C%NgO>)8pύ!A'F@hTѓFO]9q51Tq }8  ^n6<4mo]5EBv="v6]sK넾uzvV글&SJ3眣Mew3nVu7W˕:{wsbОmaKθm=/ui繝ꑅ.B v{bѷR+ux7|O`شk!E*GW'T,u _>Sڵ#5-}ͩ~1uޱψ9%.ןW:uND~}~mQc1*Qp%<@\AtiJ$z,f[luJF;ܒi֊_7jtk++k6Qz:%/x)x *~H#%Jee**~6y/]-O2V \yB-E!bw8nz0CYǯf!\8_]UvIMgڅ0!&+⇨F w^(Cic&1R#վwD $S-OPFA&cQ%*~UgJ6eglΏ[GԽW_U?6>(DS~CǖPbM(B2Y5G_qg!'L KӦLADM*)d 倴(QAs(XJk}B<:3 tR:Ko϶UR`Ou`Ziv67%yq`Mpi+D(v/m/}ΌW]@? [?F1˵׬ ZV 1L>,Nػ-Ry['7QAJoW5;^\osWgUTJ.r̓ c(س,]r L bK7%C^ӔG)}'D,R{o804 ts%NnS5.]>[..GA\"oݤR}ڏ(.cnW2`C}'t`)טDER9#o )Owxn'2?1(ѧ\'kez:HG#~H+vM46b#D3!!-,)>"1l3SIA+t%Irip,Ctj5C1¿`}>>`"ל{I$r'&Q6h=ڎ,(O.Ҭ?ryaXz_7,@ԘNyZlŀ`-ˢĜ8KBWu␡C1 2br~%:zؿFY$C](!t~hAWٳ(KiѬ;e15e/6^6T Bk-y"ET)ɘWJa!Ui (U(\R/,F3|3`7VRFCԸC<|;iʱ0#nGF-Ej^:rUHݥJ4D.WU2~rn>ˣ3Z=u>V+Fr92N|Brg*(@Upa#9!邞)Ex?yL2:W)R{mc-p-+qn0\-CږXnb-r7IXf{]+p Lh~f:U/Q4 Efoq}~7(]<.ejz|24;%| E%mf{/^%[b)(ZI~O\ΈXnU$`-fLzC..HmMfRY_.7$2D!SA UJ<L),-xr J IDAT9oKԽS/g%D[wvˢ) 5|<1s<{ 05 1̹l٦j*wזbߪk# wJ}{W+5Alx㵯9&: IZYC6/Uӓ% Ga(Oul{ҮφNZB%hvPV{_sZ؄&r\ǭY>]Fn#C#^f峓9=EWa&S\I:yƁ?~f2BճC1; NӾ>n%זxy//*nYal ?Hgs͝{TnĎmx޴c;w7COj5,iֽwO_:p8Γs򢜭½Mɜ^m-懩)aL mLyy]fOB=9w("C.Mj1&_zFKTN tZEQ@ m 䢵"BQ<\nb"߁8dF/S&* u>F}d_ ɕ IVʴ19)M;EkPɃs.3!{LǻqG͇ϐԥ}翼O]d=A;?Z[1.4h]\7Z#49%)H*hY%@:>lC}U]'6a=Kh:)PKZZG{RwKIl&JPV]^XHCq:(NYn~g nТϱO啕h2̵׵W4qF~%ؕe|kwNPf[?mD'rʝJ׸8ugB*w^ tTV98J$㣉ُ y>Ƌ{EgV{`3naI!AqQJ # /3{ź#GjU+G{|004>{g%Lgpkv>Xi7uF]-Ȼ~d7KaJ: d0};;%,k'Od1LuWNt)BpϋK *4%o|Jc(qzž7?zbQ6]5n moku 2r_~r]0r.- A=4e}Uo'X[7W-,ϿUX֓W02OHTX^єs%/xxh#۾gM7(U|gNbVl^iu?-+bb,]m؄=jݙsGZap ;~?x.<@l)3g7 M7nرfK꿈]Ц]7 Nngc%L{ow秌uP}%MSN8 =;X3Gl? 96^t2y߻kXU荎IX61 ֚S4گ6w-7O^_eakթuTG^є6;۳}ߺ~JJM K?w㹃&Ls:`6MQv5t|*瑏C[[S i{M4MݷuY?=떞{AdG\iL.~ؤ:_ /ߜO粃0|߱Z=@IME\M:ϐJĆ(˜hmmNXDžS T?`O)qywaSq5,4>KC)Ժ}p>utIû?B u&TAF=qJ "$#°0nŷv'0έF>!wm#g|[5_Qt^` /_y]f!_W1>ش|X߽KxD*\M/V W˄$]ĩ|#K?<8Lsj4w.EIbj߆2gR5p6&+SHP#hm8 ! iLN/O M,W3'2uĂ8`h}hY@$1*\%rKO:5]Se('ṣQS٦ML_z*cVpRAa!HLR-B?(ljdͪu~G^@MiXyȐVd"o<57~S$%iM)H9'xb't9viPA? K={e'k!r[fq7p [mrK~7Ǿ6m{)d*Vo]sW{Ltizk*nc{S_T"b49bѳ3rL_X\YyŦ5b;??e6O,VΠ+ז;{_Y^s{~H 0j6\:ggsȻi̢z/8B,uyR}(&a_U{B0Qɹz +<9kԑYc&fxܚ~yzOƴʕ8Bz^הg]<秕yi?:lx<.I`M>?oɣ6f4tz]iyYN~W:`wSb~A7Rɛ__OQaդ3hf%o۳v-l߻霍E{O&̸fӢǷΜVTxE-&QW)>prכq+|E|Dp+i1W2ocݲL=~f9OiPH@+TNV`ϱ-k_\k.u$hK>9b/w;b¡*J?$R,a8a6hYfIv :cgy;ⶓ WpפW>SPռafMK mb1ꊮ_=ϯG|Q3[֥G)R%SqQظb u՜rПc*M#UIl`T3 d4jcʳF)܄Un'uA0D1B][L+{*q*2_2%P fZULun@TuQې)]W S謼oxlЮ1啚8.VK߸nR""XPB n؊b .,oz=M.99ed`\W2s@E Abo"5]PQP{`BWfe'$qY)g?C$ b'1D4G_Q\ٛZń~I=qVmisPB-nךWzɚ& ((PY/\;?K">Ǣj?LUگDp9ZոK},Sne #8JTXxPEIf>f{8<ZK߮%]jV4RچfYRӟ+<&-N{ݻnҲϱKK>-@M\yqź٥AYN]8[5mZ.^dQ`/W ׼լ?@hrd9d"Z}U\𳁊뚾M)~Y@9/D7%Tf^0^&r:*םQƪ ]_6;p#&:F֓g֝[B};[P1R1,lXd[lZ,X=al:>dBJ0YRkr6,1/>V7_4 :AO.XVйTۉjBu:94QR.7q"\ cAK:i!ӭw%Ho%!a7-oI–Q|t-y .\9IJiŰRDR:OxX;.;lPGjЯ?KySBc(]Q41Wny6NpK8QYWVݡ=!r3So dj>}-Pn9i[1iخEj/υ514>^S]0}8UJ5kSfsHF) eb_:D }O(8WBZuؿ\ db맾Țq"pl{9-_3T:o4& OhV HEZ{̫VSeQP}HE&j"›&Dya1^HW|+XO"~ζSk;="EHyv}@JA#"JI #e=$RlnJPZ{Mv-^Հf3u&4r}ǟ PG 7[5Us)%F&VRe26kYRuv/(E*W19vog[I!#>m=54K fO=|fLpduxrhIIDZU{0u{Ws}|..Q8*7]H3sk+۹OrEib+bb .WIs}rsq?cR5*a(N j Pv'͚sJ~C%eTQjcSf|OtA 4n~d4&-&@,*uj*eM^XW"t݉ől۹ ymg?:xV|<[}D/:M D֫(E%Ikك>ړ"/HC <@P\Ll1ԭ1 %NQM7O +> םwNׇ$D>{-|Ǜ6L=WiN>%b(xG2Ri;[J眫JVNRYdβzB<*%&KCP dVVgĂb$bRE+])UDMͺ;ir@3s4u-o9IW^ndl`_RI\}P_\,~|X3Z~,$?P ?LrFOl0Bu7jdȴ_o#U8c.1>e@#SbH4J&^tW_kqpQL (1*$(Q]攱9h[><uYb$bQp+.Q7?bʒ/zp^h9;9|awS6 ؼ拹z߆pRhQb%lLe%`IV\G^[= M&Jm9]L6VP$AvFf(DݙjRvF LU3 Cvm9<ąG]׊>A؝= r1.V\{KLp_\6 #IÕ2A1׊垻U-V?ɇɒOIA JiLW<2F"ւ%2{%4iN~ݭTUkYZa\!H(-B{l$>PC;f燪g;ttx3HAe5oHcejǹ{iH@J$F7ؖݎ Q*Oi =igdʯQ_f4S il5>s04ht7R:Knɼ+E/KQ!nf3n/!ĕQ߸Uem^%|)}1m ”;KŷO sKf3_N(o?iEJi6J Re #t>dBE1H^5gJḵ81T=]dXt|Q> 1WO\b|KXcgG8HkWdļJ!ߡ?9O6fy1bG-b 9NR nbrmўUK NOI ^Xw8𢵔d^:dgkIDmO=}ōwgv&:%i3$x:y![B%2SyԊh+Gj/ǵ`KJ36V\Hk'8JY6S]arprPw%‘| pnp$&Gۈ?1sR4/Q/Z蓕GJR!A{b&şd=a^`l'=e{,kGz+Ίf=RJ87$Hg!J?A# IDATaJ V)59Nt2 r˫.U+_z bQp+Gv LF(L S.n'5i݄Gڽ!/7h!KT0 c]ǘ^kF)eM5Ur˵KdCMr$SO?̄:IemYlX'.)_aKE;j|>+9"h5IEk벆&|J* Eu/L V?l-b-Py)S;.`tj&ǪDe,aQipa[zslq`[q`o/N0%'<'1GL<Ic[}L."T,_Kmʹ9L D3R戱G8QfrHzb›@3V;`/57"DO R1-}҆,M&ȼ+ [~|39C^$!As-_iVP02mR )$;eJI1O4{Lh0ޝۺ#]%grsls&C'wy0q+Q7!jf+G+8a? ? .rOZ~=wU˄D-=9e$/gdB6Ej\d.01:SUjV,roWF XPGǚ%{w=Fܮ : ݋Y|\hbbj*>⧴3):Dw >hb8KPd~OGex ;썱`=WL9DCbWEh[ Nhm]sAr_#D㝕Xs 59H擋VKH )#ВA+4!7g\ /Lඉ(&@4d| cV֌=1|^aq|dA|q5i(cA5,XPWWASO5~xb0\sVŻ֑/HKf5|v~N;ȚH4REmn5ߞ {Z-˔jYxeFcd1Q6(1m}Ff[/.F#Z4dYkd 'Qژ"PT!#eY%;rTר0u!圿tԻz 񏺙沐f!P[`1ΐe)6_FPLKvwf;g)8NR[&CDݤiM(uJl*HqhwIpzuAXۮTWt =b$]$S5t l/irk=W'lyf&^ Ƈ+UĕB]6;˶N^&VŨ55 lݴSOfYK"mb| 4Rv {Fb<7eɤ"*D1 A+$zuqC,o.wƋFefZI"ar86T&þ=rJk;# V6`afXNPYzd((‰j(ŜN!/kR+*ACehg(ԧւҊlsȔ2*Tژ@"ʼnlŎ\$֫b4rǟR=ݱ#B uqSb*g$6T]E:>y㫁|%¿Ynbp=Fp{gQuU[֚oO4\p|&Z&DM jhZLxp 3NA5 /jjBlD[TKDR884N%Z* EM_ G`"5[5U#hJ>r␻m$I^G&g6P/;t~'1 =ksC]e%A]RMԏHO8A6K$3 j$M\X_Y (|bڃ%<$G+H~#aeJd,)+1[`(h%2҇ K*/yKp=`X!OT5@S"WaV15%0w¤9~?E98! f%Ēqȼp2 9GWQlTD: >}orbݝUV3j i+ ٗri4%.{gqq&t%Ab=eFNH>~‘v;xat&VĹ [s̔)FQ6%bb>|q7+;uz2C k%![\Y!*g OT<$(kRwD!F82]U%!*dH>`fՔj"]wlf&w׳S<55@SBS 1s0ꐘ^&qᬋ[4"/ =jGȔ!=F?6["W˧}>k,2sWɔ>1L_W60f)l` O ^4s/l87B۲bۚn]Æ:<:wpQm۽~ыŎ&5آc4c)&g&{o ;~{w{q wB~8;3;3;;y#1~5 9%$+ki+k7$j䮍Gg/-zbau'Oj[yI8{SӥEM8VG;QbԿy] 8umyQ}+Ztyۿɏѵbn,iUEϥ75*<XQߏs`q(lAh-!)HF<ܼ\TVk_8HGz|4m˟:f?N+:eėA5B*p>Z;zN5qK:$ B-|E5x֜oV(V!P,et"*T"ibܾcSZfjYC5jL$yv_w㠮C6tI<ЫԪIv_UX'~]vʹ"QAٜھ1'0Xyz碪0?(ιڬ I{] 8æPVcp l΅zQݷ&?NEO`gمKf7pG2ѱbA{!!REkΡݝUv%ENFZP埡LLKhP(̫ZKM{hcxͰH zVgexzϭЗi_kݧ&_$lXV'zsmJLufQ-UתT ܴ9ˈVtj]/H.q陝wmU3DR=(ˀ 9ߝhw߀6F椗e SFcWiT)3FqwLwu ;sMt ɠln7gzEэz.wPH@;.*VPʭT(fkox[>l"/Qg9[eJoxi-pujkGam"ڸvUˀNCLw1eH "~mԲebnaj>|y{nոJd1g>C[&"աE*,&K ¼Q`g%?S Ů"{o9y(M UЈ?FI:iSԨ}(SMZξ2,5ɟ~h-{l4qNw؜o k*C.j>Z#v$q<ݬ\* GFًKVAiICS!Ris.ȕDϛѧ= -h;Iqt;)owZ.fQӋ\_v!&DZb٨Pѓs^#}gEaQHV>mI4?ͧ&% &"BЖp?H`4ա6ےO4v#Gn.AN9}EM"W$U'Y@ #3kգū/oMViUfը}6&^>=яkE9\z;?) k@oT41q 7nQ곇n=kE83O˭zr&-j㼘4hՍ/r<{? 5qWZrXI+Zri>n}(_{L h*Jzn mzAP&;Rs#Y@$:@g0&xW:OUYy5Ҙ ױ.^3 T?f*TOIʈr1WM/woZѤio\y2&.9!M{%v3I{qyw%tAiUdk D$pjn*&6k=)ͭķ¹m_;[g yؔ;MptݞRVxr:vN]ߚJNulG?r6o1Jф-ʑ!4DN.9ZԴ:k|+`-Kya`Y;(VE:=znP98GPO$!6Uk#sorHx.튺YOaUEmz3?n9s3CN9k=SC6LESiz澵 s3{7/3drJG E̥Mhi.g$Y,evr@6Bo g?:;M䫩Rۅ4IW:{?dDC@U @y֬ޔ,Ǘвk.vKk7`ztMYmـNC7.qON)6Apž.ϧ߉;Z2ߺ6M{<e{C-z$3bzn毙y{JX";`Bc~};{Mvec 5ʭUsT ;QQjy[RfoB8wԲ?eFѶNLy>nq{͜CzEy{cOl4 kRB/w~:拉=G}͝)@E9Oh.NfXO46-f8 -j*1^%7Ʉ0UMvޚPxշV]G+ u[6k@l\bw|1޲q|ody8 Z4I'qq<pQzWP) 6\@ }im@2TB._ IDAT5@Ac~wLƏ92O`4bЉ'M$*]ޑ'_9kƷ7T<ĕ>s-l{_ѺͪЪny9i/o_}7 /w-m o5xAsݬڰͯuű:.)cMvWIrRS=#'I?=3LR޸C um4.<7'EwՎlEy:¾MJT u.Yz\a L`{ Bc c!\Eթ-ZքEX#7gzcQZgpXp{/էk_υn[.zE Es^>HȊgwJ캩MQ*-n<<-ݭc}z'ջ 0 P_Q9+SbygYڸlYZ 3\.[[߬AW$H4 @uDMP1:a rhˇI,'eٓ7Q|n3ȍbcГ$o,5I^VGzR(O_9o ͱwNsǫ[f2ji"Fzg^bY<.Or삔|2{>Oب^+n7l>5]go/xtoۇi:#LM3xޞ#]Č5s|oMV-h"br=u65KhD:؝o%rG9qp-촜DAq$JI9c@,MOwW~A\shԦor%m0\z88z wSfA"j%U0eK v#k?o>޶_CVQR=i5 qx'-!6#))Y&2"_OPlbn{oߘSYk &?g%js &=C>$YgyYLv";?sBRZԿ9ܳf9EMzMz ]mԡx%Eg g['꒵Jױ^ٺ+!޸i̍P*M2-Łh88L*-bKKSK\UX0ԧ)3}R( n,"rbB KkWlIJҍ` 7|piPGN5]b YڃTG2LC<ָL tr+m̉&p6n|}OeUbק;g_>_I;jIôKߣq/Ң?`S]e[OrT$bl}唑Q}/F޲{izNbiyޥa},ɓWFi1r{pq7utP0r3%#aeG9^BiNV;Z1dZ5Chw4{I鴨۟tέSn>N:y V (/+'5ݼ0ck_į\>1 viMY33g,Bcl._ kͪEV نFc‰S)*S>y7))AUr_dhk7%2>s5+O&۸C&5#,Ow޾2rvQܹw!r6DM3ADb0(TBokM[ f<2dѨ} &G;@WFc<}(CJ-Z|Đ@lp4]=$!l2|jm^@ToTc)%OGi!^~CiNڋ' 96\=bŞ2d%K4m,q ;8I n%ks'G+1uDczZʻoZ|+As9 GZuAJd u:51)ua {F̠z[+lW]WEQ~|f3Z!VY &+!+$?)%4@F.凊4J^02 brĄZػ4 K=[ %7(v;8?;^-ǸBBC<50=}m\ay0j"[CJepM6BZ!)ľ:0s^lgix5E8 9F¾#{=[F C yjq[_}￝W0_Bj@& EQAYiDVuiK-3,0%=Yk Bxzk[ ^utN(=aÆ5hTe˖t?/;eZ9*Yǵ|ʤf j-mO.g3q󾍎jfLtmz;VUk`gc|/ۈL:i&a~/9ܤw4UD5)ɍFtj>yj*_M?W>!z]~[ :jNb!Ӗ4J@l\.4tځ pP+'t~̓\S˞oƂ2`D^Sh^zwN fX(q|t1Qӄ(_I=/B)( 鉸8&4xLV*>k:MӶHn^kܡ߹}ߟΔOȎlgʟ[--pk*6?FxLe,N %ِ_3 TEM9/R!!k()4KCh{A!\QJGҴ5ȰXDIQJK~6x "CC_1y JK hOo+Y.L{>XwC+ ݥ+ԓ@ҟ:&tLμo+d+H9A}5ULFq$d5cb YIl]P /.nđ0@QT0oiԕ0X_QgВ.n=^ijEFK;|>9bɍVLI3%}ڲ;k70x<׬ߌ̄:g9<߹zՐ^5 31ѨN!ǬOS)eVAXjQlWW}-IO7|Еtj @kl#bĕ.ٰv9F1VMٖHzh5 )?08VRcC ࠶Rju?iI~%(Wa e6W&YNʷ3oӧ㐉##oq "_ηg1K?+) eSK*\* RN锾[#Xj{nBjX b=,Õ6婯)ձ*Tll q\u 8\+)ިPDxC#}eTrrԗme٣IGA YҘTJs8E.u0 X<e%#Z4vk0 ;rcevhU3O )b,LťVH t~eKA˅μͣ7,~Ev~;˵6 KK?HOzT|Ztw }`"6Efu#Zxk)`&[d5Q&|K, GE4l$-X-lRaQjqHhRBa*v ASC8i!Zk=uD{8](98 &;J<-w n"fwpى" fʎ{Da^ W$I}qQNEXYkAu&GR"A1[x48vjpl/gBfSP% OJ%K5yզQ7'ɕrJP*Z.(̳yS׎?YchˏqN]9v-Ru(vml,fK<|j%{ drrTPfv'ZD V+i!Qfa'w/y[Du\/ti뇶V&{Ƿ~޲P^2ȫF0i5)* Ԥ6N\aJ;mC,ceky]Qh{3@Z5WD}eR8EC^HC:>%-T_>Lx5ի)qȄKGgb8f4|q o,+#"ܸ~p]Q:+B}Ecwq)2*ccvl>9DtDo[LP`$dyRis=Uixq":|տrT?~^5M2$֨)'~́JeJB&Ѥ)Yee ?eJmM/CΔ/l55ttO`JdL ds/zL5`GJ<|1#u_tObO}JŪqml0ohESͳL mk5MA=xIԴ7r7~yS5DA\SE_13u=R N&=޲+p~8+>^jM.i^揇f'ZG~%U{^ F-Qo %^=$=R@f*SԤzTq7F:V&qr}pvD1H^z=lF0YфMJLTԌ;PBx Vi z4-=s<`ЃKӛGPܠ:^uV0 Oᒷ$ͦ,'oB{O'F~POw&e8IxVtP|ݬ=#*d@1DEį5W)뤆BfYn{EUZфKFG[?m/z$?&YQʶ}! H:M^<;B}1AlcM* V;oHm'ϥ+5$Do* TnDM\AnT:K%n-ݰP4ۤWBԯn Ŭ Hh_IrV4qF41'Fu&pW=XջLcU oVL}(ڒJ_5\Ruwo=:X}0t猪T2~dtDBn}C9wmh;Br-[bV4v+U2߻w#Wգ7 hߴw>ifpzY ٿ&2M Iw\C$'/v{yE5aD߱Zk9^&nWOXz;uGoA澑cAu43dֺg6w1bL{ar&n]2@98]IȖST2ݛr$>ii8^/gfY,5]M`);.V'ٶe?P4-5a,'>4ʹA͍|oCEΖ(?,gZ%m A^f2S59rшł̦NTt=uz}~_Pr4BN~C)(b/t|3QJER FtBLLsOʼn BWeBK>0/\!Qd8>Cǔ'hIuRQrsLq¨@nZ/: 9/%%o{/F/zL3# DY˴d^5ԏ0 UJaQPn \5æDh+C~`+\{SYصD|~rJ ƘVGP o> IDATe3Ph55,Җ021tvٟg!gi$(1=N~l2 -_yɓ靴<]9>/Uy>[ xĎR!~{b\H^ڋC =7IZt]Dh)/4dRƑ՗+݃hZ58^Z G]ϗ+G2M&)Ƽ]!DBS _27bgiD]j4h?х?F90-210sꈍ}:4F󹂝~]"3oMM/0|_sA"MѠΥt,))Nki EӣN?:<іaU*PkI֧03e9&Q c}}k1>)GUv o޹IjZ ezQn~ ZcSl!ue[_X!m9{@l9~y$R;/GR 5f|R"Ϛ>5-=Bvu:26/w"0 h5 1w̥[,BA蕯'vuaٚ筧&()%lBW>Qȏz3-20#2簺7 MvGM?(7H g!i!D XjfX̡-\pLHOiUѢ{%$EN,|x_|<-S$>1nG<G{'rfCk.>F:D[Y9y娎aG>ޘL=\-qڎ ӓX[t6r6;6ݹLn-fsLrkXTAQ~Fv\2#q7O#kGF HT>#W{ Of=b]e9FpZmAJEB;JBvq|Dz 26hҒl~@;VAq6ͺ-{x[/:L B! [2۷F64||⪃bGR; ,e>Z~ȥ ϫ\5+w ݴLj%{ocC_*tq{gfzs̕\;Ƿ4ideAhHC 2'LtU&B8a4j۶q>KS$y]!ĸmjgMQ*Jzw-2K7GX_ć⍋LFt _/K;qPIRT㿮8d"5Vx<+N \x'VV2LO9di|Пsͤ_'k]vT)r?6x^svk=lԠq/|=:hQ;V_ cGrty|rU+/0~z&'X4y>*E_#hӆ-& :q柧2rr3FqL. 5qvFԞO~.1}~oȤHa"N鋸*)GWO4:l[tKM'+9bΆ0'ZujFg< ?BzmbʒFՓX'ëgp΅^\*Nw#)k禿ipp6l\H^E?}n3i`Hh6I̠Wqx½+>̩QXiOРU޷wŒD*۳5c4v^nfC"uP ZX?>hx\yg3Z[l!KBׯ7ǦK_49$mM>?_G']}i=)@O/ >۠nJ6js\#Av+ϾDϲQo3ho7xE4-z,*ow9cuAhhi^HX1'Zg4=!VC}oJ!R$._ђ$i AM]S? 4w{]=Z%UEy]dMgeuZt¢ڎ_;YKkyqnox|1zQK졛`=WYamRGyE t)ZA78cIbFPCnDP^mץꔱ ?Q*J2 !*UT Ctg;5Qh뭇ķ]gHLzvj<j0{yK 37 I\~֊>NV|۰ċȐW|`wUPn\Y(zE|yAtN'O 4YHG-) b/Qoe-_4Y}P{c ĩǪubnbf%S%8x_y@?!36.Jvڢzs+QRSɵ,=NlG15NH^alD%?D_u٠j㎍n!p`vTP+gD)WXvYop~}n0|!h̆ ^~ 0 qWK #ywaC1OEj}e؇_Ҍ-^t][K5T ?Ky?S  `~) vr+hN{־,|5cGSl$'/gPnxgGLq8< F`BaiRZo3̭n5 MD!Jٴu.f /[zv'қ+~uT [, 4*H i-j"~4vf4as5fjf}|gTQb8ުFfl×6gl^9ӗ0,]<zQrĿ6/pH2M/ĝl9oi35E,n,z`ce Et:F1wއMظ%|h͆eVugT|gL+w]Mj<8CTX,|;AkqnCfa$ejβs]%xQ,W|#%_͚҃R@F (4Q wVhӈ|+drUߎRXw5^pGR*1ta͐arֲ?? Pbtʎ(v>pGoz\WFr_UMR#*Xj{<GuY#ڢ1WnoiT zŲWM%Jhӎn.>)Yְ{oioR%?l!a䕽V;_>sƯB$phe߿;uu_Rj|ؽ'mp+lUUŶ?v7 J+d)n[m\%Ea޶w-`@BHQSt份KEVGvL5. ]TjT&-̹rgίڢЪ6}ثi-{%yaVs3^^;7ʈw唌'v ri~ʳ~iru?e}a@X@$!B^Tݓ;4!EsNWL=rO{l34K'uzs_p}PwHQTPTƩ])#Ď2٘;guzsOMwoO)dNq2y0{3:ٽ뗵:\.Iss^C\\CCBd2KV{x5/b. r}n(VŖM$>o>UP'?n)8i^V]<|<rOIxpJQ'K,N#AQҌOnulh+LE" !ኻ>zBjKMۆ Myt N 17̩Ql1L-upUISaTjfXreSb=\&SJO[|wmFXl3NarB˗72=Zޚ!65A2QTJ QГxq+,6Pu%&0Ewj(I{7ω_vkykK|+U{ EcMk:BofGE~ uߎ[h;y޸s+K\6qUt1*z_Cw9$|$S)C#υ)Sh,bڃ#Jr5ƻ~OPf _?/yWg':$37U4ʶ+UjG]CrW4.׷(I܋|U(ʍWFRKލώ'ދKN#X:Иs?ges}WZhNydMZXE,z]יVX}𫶐wsc\'6[Zkf\;ӁY&,)nna~ D+FCC 0sHCz*:i-۳T \.Ѱ/BDtBن#~lDbƙ)&j;pv?e$ҳ,8ͷRLԤѪO$SczJ'/?hڹ;U{܊;"Ѩ֗yE/_|w&f"ؿiEȳV9n~pO '4dlԹ/xNӪMLS1TOȊ2=Ž H&+^t tOd{ S%Al9|+;JL"0RzOBEiӸ:fͫOҴ ]@)Z7.q _\+=D;TkDM2S:q+ۇOh٩HEHS] %;8Q-r٩,K3 ? AcI}rQ&sd ZyASJ;p FEp`7܆F?l$CKzjBWRr7gi2V|&{+`LO1eVs>Y,n0ֵ0V3" IDAT wf6(qIys'a.wۢ~#f?H-3z>yyYYDe ,&uuJVRIJͿnK UT\W?ZfBYti *&̔"ɱ " J3fɋr)DBYڲu͗7JuO(3 "=R3}Q=;q7Yv(} oBR(% Iޱ8qxmqlIdǶ$oIOs!}R4lAí% 3Kv&޳ +Uteuϴ.v-ЅTгߖT8 w,ob7E3vLN $XImϏYvr7Z25IA`y*xҍB,) G3 |\aHHa"?zNN`?,:&pH .DXe VvFSkjiRm/̑rꓝwBB~pէ5 sq$-yǝ}_civjڒĬ!tʗ*깕SIv<0R Ku;X)^Kq%o⋴ N ׻=qtntb-(Wxt9@;8i Kn1ԲH{,޿Rɐ_|?a:3 dˬkfHN<_3|!ѩEgng,'ciJ {YtrzFS%r"m"Ew &S/4j7Vz7c{J6JNzQeSi[:;pF,`X@f6PDRQKtM6,*TYpnsxFYj!7_DܣD'IcUrJ\EX=>.A3yN6\wniw٤R .d{(o%E/]ڎ_cGe&?Yr.kgb3G-qa~sljgL,T& .Jv/'H]o[CH %ys%wKXTTEX_wrt&$Qߝq9)!vOgzE(`V+_bvǭX,ҿ"-Y.Ad4 \FߵH[=OV!2SE6MYT)H[sH[[Gf)/挣t*w99I(#FE]Ռo_YytՖ&].4 /'9ŸW$ C3iB::KSWZ&|8KE.rdJ8;qOcUo(xG樞,\n-.ЍLzBMcǪe* "][9;&2G \ȉޛRꕹjA0 l-bf,nʯLއ-BM#4%-%^,fg6gr3lqa&$5d g|Q$Iu%Ceub&PYPtMT^ʪ9AE/=]\պ^G& Y?)21ÌK(:f㖃Z>L2V[k_iY,nYE(4̾-mm:6u{ݽC@*VM К"UM_f,RH$VklcN*!>3+SZr{-MhwKjrf,"G1;KYl彽۵BCM(=nls/lbyIKW[:PsR9JΑ<h@KЫ8>+5 t(s$><[h#Svba\XJml@hq>׀|r+uq6*K T5|vI(_V&| ;u7G` %IB+D3to\ FIdۏvSj9mɤR<&X>W9( ݶOK|B3 ssa-ƽA.K:OOѹ\mb&&9Ey)-u\Y֐ SKF4CER[\RZLGv(U1\;5JT4sҙ(۳bgm_'>+yA:iNnVJUr^ [3w78>jⓔxi3;CM!x)AAf)ʰ[C;!;E-EN5PK3EJj8xҞZ6&pTmԬt&'d"VE$, )LU)kG?‡50*$x{"tN0F=*| z70pQA YH e5! |P5A(A{8V6^PcG>+`4j%jA 9ڏHbL:v *B *B *B *b-\m6s5f%9,ȅYKp7.DYܝme-";לձ9V ȘZ-;&}oU)!VU!e:ν&UIhF! M)Rݜh]zDǒ8*i ̛VuL,Wx9$OOo>60،<2yGViU>_~mOpa^ƱQgK?/m{;bALK*e?\._vh򕜬6>qٗ,흽{i.]>ܲ9YnҨ9i gO/̦(_t;N_6ޡUb}ፎƒ (uRe?[%Ie5ο'&7'O*u-rXd:g!r|%H ⟇u΁8AmBMJz G`Kr*`qvy,&^V'YX],Jҟy{⩹i(x1xb0$:; i-&JV9 h7 &9St"'`MG}g[lkT 0hԪS}}d$yގ﹑NNS+V/Q0ss:fN [=9NիL/.γWk; $5 F>\n)bm )Ģ.~!/&튍maGL7,{0Lf-aܤ֔|#lQ9u $K4{Vƭx*4kuJ@K;&ZP+ou7ͺ8s2!Wg/t73D\ksDe&r^4QvRw|MgnpLXw45}D{bf0[;zߨ"-Ś)Igx健/ V著r.a|WIcpJ]r|e*916+9k [w̜ۮ[ $G]nv㞁.K4yneO7uW6D14?] g5<4JQ-Hnj[n3pMDڵ+, h}(4$ْ}}HdDX9$44$2wn&<r=tAl۱Q(窴G'8E}imك$cLaɌ! t Cw@>p,yJW1 Ibd&hwu|Q.CGkL t<5s039}tzۊ]Rjrtyf5-kYhݻV$Vv5 HK pcuSbg$u $AV4l353H/^KMO+v :">IhOT`/O+y1qwݬˌ)\(Ln#m9IߗlV8Oh؆cf3޾ށ Eh UգGfQ_ʌ';wIϖ^Ne,}S|zZG}cf:v3ދș'SVcqW,)_FX.l?/-hJNO3xҪ5Q,-ds׹iufz:{ɉ5H}!|۠?S_N[8'Ԙψѷy&Id wӽ+K 2 R9mq' f" b`*_C1=-%_dk |SA v?oR%GRKkCv $]A&ۀ)k[ysgM9q,_1$zJbW˻[2ٜh4*9-{ᶲ7k:.`-}$FzlyCS,•xoRjgoza9 YlvTo:+^1}E;/)'\V23IOl8[H_6}Ogf&*M(iu4 &?\͡8]jꛟM:ڻcsT~dNTy,tCS'Axxu8YkNf:j?Ԥׄa֟JQ78 0Iwr,4|%u2q]b%xr=Zr4kcL+6**;AH2Ia)ֈch 5y]Ο%_k۶m7a};F]RA#izOt]L´s;y2t@IGuElẑy}ƿKBq~J:}5?dq /w-&#xo:#jńāED=2eo%[7սĝY 4 Egߵgz>V͞?+?ƨw:}]+ʹSsSO׷>Z+,kLAPSE}gEfȲQݣ| v,_"`^b(ƱVo1BM/dBp:f%6LOMaZJCΐIeX< HWozJ7wM)5RAEXe 5)&I:0[Xd5ѫ6\+6Q-DqBB4d'=ղ2busy^؛O6=\KI2U23oubLz |kTw~Ek)Th(FEJf 7F~vj[Ňl׫c?S%O2ʉhg\OzC?ͯ"ۥx%̂w$S,&AOC/o1<221 Q5b5U0Xhĉ;9̯%n׷Y*y*e4f7޴;]zA힛.*1! 5SSۏTW751fB/z#(,Y8-%>pJN3iLZJZӨjFQ)SMӨ6?)PeÎ )BF:\8i-p@ `")J$߭`mI x^0 At"E9!II EA$ۻZ&{M) z'FIe>?tU Zd#LVjM8; GN|ق{"\:o`$rFQ%οJ{%J3!c{TS'u6;7$@SDtHv3)GLKj r}[y.8dV"B;"ĈTi-fs /bbb{c^N%-|.}3!7+9Ɨh Pjk͞VbOw}流ʟKg~5gcKma6xMK ,4Ԟ;{,[ <@ &- X!tk<,+ȡ&*C$2F *wK"Z&Ӈ&}b:Q IJjuE5-KIL"}WvZ|-ǭv)`L [zۻ}m+WNs̥UՃ"1i+?zpc=;˂:wث !'؎*qW lLY2G-!gر-VjVe粆"n. /Vסi:Ҩ  ֿJ c&JN)3KS9jyֳ!gqS5+~pKMޗ(EsøkO9).HE| #! IKuK=̶QqFs}U''ZNz4+5m,Ǐy`RG#x.ylap$MjVʽ$\:W(_#τ<'x=O 7}=G+Q$ۢSG,3f)v(N#:Nh3 wDHÜF ?VdyjkaU?P8l/1jk14y{V=mG*ny&SF/uby){|ܧ1VXrQhWE8%)OO>*;YXT{5B3E~U{Ym87xQVVJOomih|sNICP V~QeUggjI z8d3P`fz @>K"bGl FXM5͚JAz]'tҺHS ^R2/b]xS>)GuC!OS$5+&zm2>{k9vSl8h(!I]Y,VUgbj9f`}U0w7xu]OfLd[r1nrW`>V!yo&*an"bsTɯ N4rzϟl, 8-+\Gj{8*ҒylSo\g:!͂"j籷آ\EN[UmLHZC-ˏ44;/ #,1zJK͖Hx/!;z,a;k sK lZ+7W,+ ўrA>hS 0W*8ۿ-;m߿29et9JŐS&7+5;E_ojyŷ>}~"t[680hIRQKtM6,*TYpn)M*pg}Sru oFn% ^RzeJ)i\[Cf&ܻb ibaѵubghwdL ׇJaւS7>8T `H;7!.Iv 37EW(V[mLt~b?qWGļEN=e/vE\5 3-AQuYv{&|93Usr/_K Y{㣝3tɴf| obb߾ivAڬ,Zpzvz`Wr4õ&IMPϷrQ7wL~y Ie9ne*GlI+R( Tb-IYp!I#Ej8|hJZ0K\X m2(fHIʢsN$lIbc`Lyok5.%^??ސ273 *0h.)yfGpGSٿJK *q]ZE(Z pTI⬿ʐaƍ%BqA`_ڗh",r7,"Ify;6Wcc:ݽ!Y 3)RZ/oa2-Rzn]6Vz<2/%ײ;ۄv&gR+r*V+]9X)4qk.a6GШ^ȶh-ļtn{5s:jJ 5'aFRLNz5 45pw7>z UNV~놋^|N+_$Hs.{+Z:#K*HIs9R0q_47<ƨ{[W-}VC t3ozyM|jI+LWZUQcq$.M;yrxA9J|u4o$10Mw oW[[vZ 8t5|?bF$ x5la[b?6rm "p7 CT+fJjyFhږ0Hp8VSUzJGD j4E5`4ܜTþnK}:Vgd&*ȘQu콮#\q&NL*cbUsj)sD) E\u\2l+19̥J범T[&h+_ilbH_2RHE :,fkȅTu4ON== ^=Uh\Qbv(kdw7pf-i\i3Q;gއq%.l8\^guҒbRbfe'NkپdӓbOI/#_aCK%'FJN[Z^sTCSIĜy33>Dme][e̔E jJ~)kG?‡50*$x{"tN0F=Xc IDAT*| z70pQA YH ;:BM5&"4CMpNC7@ |{ MgBMpV!g&I.5=_T6֟Dj_?o5׳h(sBMpnoߙbVO5 5\XVO׻vyGp饞ong{'y m2Af$H"I2bw^x*9rnsOvxcq(H(Iv05\X\t9Jn7o>ZsNKAmwO%g/?;J=Tzp˹-I)9?z{:~IzmQvT|l;{cKG_9}yAԄVpVBMysOdf&&Dq<8/ &^u_kҴˌ}7֜{_]](PvM壙OǤޖ/a_]Fwl6G_WoӇ[_룲E'kz`T4ͨڤ,02gOַWp]jBFv;՘6$sYKp>;,I҆ގ]n{"sşSS2{mCLvEkYgᗻB}׼ٹPvn__x<:+3p  ՜ik{=V~vQGx듕Q"Y,ӝWm>tfMJ{sW^E*k y~sjJO'#i>*"ި8mvf0.kW]oQ_ě4$EdYg$_/e/\ 67kŷis7mC~p_FH4Ǥ}ƧJ=LihLoR^[ZvϽso7_i.C+J9]RrIf[3]|$L*/̮m`f-s|;6#Wd[Ν6%ZnL+9*Gް87{^E}9mG_i3+1;N%cͥ}$!&-ZdOKotaQg)UaupR@[wfvsZ]6(vJ[8)ҙ7hHLpi-Vo81{~:kNT5z[2%3>Q8:S\;l9&n:uy>5aqpFjC `֫.`^gT5(uGU#O ޻1}n\lJ,M|ah݇xjei)^(\.aGOZ)}ow|)G\tUll^gP49[֧f]yٍ m_mQ-<7yv"X?zv󀏢;!4z]D@)O;bA* RHB $}.{%\I(~fggvf3;g7//_K6T&+ڽwahk6e{+醰6-.BU*%k?ZdP@k/P+J˥yGٵwC[(G9xEǹ]e奧Ο^Ryެ=|F'|=j+Zʫ1Fդ$\\β߻"hƍK5" w>;`t:iC*3_VqcDud^46o0jRqN=;r꾘1$ KdO2}{ӢLR֓ $U巘tZ+m~SS cw8V$Y{{N}#[ky`U9Y5UM(&3Ԩ*\YOZK4,I8*3!<ɤ2Cvn2d#_Z5⿭^+LM-QBCEb{QӒ @G1lVwFӖW04Dc1Er4oaaQE Rh7l13r8]]Q D`N^-ؓzޚ2߮1!&69m\sq +/% zQ0D@&půt(yBv́<ӓX,S^RU%fN%pqq#ϖb%CU1#&Gw# "/O^=;Z'_'.Ek?oKL/>^Լ OP'_>0+57ŶDtzZJu:B.ԋ5whs9\l{ߓ JŰ -O>jQ􍶲-?[vۆWro.\jگ撇w~dY-=v^yᕷ_["YL[L瘡kWүaX7x[o-X7tu$Q= ߵu;?_7~ƑlP{X͢I]ؖE]"GE]oT+?w{oFW7.]p:+YO^=|37i[Iׂ&e>nu9f1&20FKe6sݰ3eo_s:tMA&? 1Y.i 30!&/iTP\${ 㳣oUJ5>w #L.Tp5TZsveJ$G";JT/]=?vtxQhZ,h\f#şYE(5w׃nGI(BMyp/NU&k"}mUPO:yPJ ‰g14ڶTyz|dV { u\e%Gũ]VsOw܀U j9msQm)yiS'c> b?)Zd=BJtѧ|8È c(WEѵKo4JR%kwVYh]Jt=kw`0=E;%O}ly.bx+ Z4y''<[]S+me yZ~heDDǎD<=|~ ð_7Qղ􏍚,'{ 3WM:NR"t!cb:|!gѨ,^O2b7}}ͯF߫~oLFn5$uU~dw!KN 6: vhh2;og\O;CEFtN8<"ZJ&tqܢ65پRY_?x*V>i]o:S0c73>.j|@o,sr탶Z+Oh8!4y*&eܠhw \;fp%.#diWQx.VW+K.i׍Ճ>&zJگVے%n::DYJ-֟5 {s=s F\u"%vYme~3ju4䗞c vkN#Ut6k[?NF*E:&]0k YTuYNNCPӉ݋㸴mT"e#.cxİP|2`1;0XBE*$Vd-"^ 3 'ɻ2mU,ZI%dqXk:yֱ,vXcP4 ^峑6#o\M|wY@Rq(BpdJy^9%)7kѡ6Db)qяEȳ"L,+U &OÂ}2NlqDM`72ܸVQY"DGy1ZvӖ7.^<éK 2)d2f BTU]Q[WE4J_~m&dq[wkfS2H,' )J")TEevQd2YZs9\>1׺UdweZP'5970-8~9J 7W403-DM gh"c:FR&<>}_KQڜRM]͢;|ɲ/̠ӻth_\de&OEeEiE_u;'SiL9JdK*..8TK,a0X. yxGF+^~^F<&QSUV[[I mHL _yR%Gj4bTԱS=H[fNiUivyfH-&ނZ֩C4{zNWgOs0Zq2يg55]Ӧf诉 vAS&łtw(evcL*!Ν0?e'bI!0Lu;ei?鲌ȀHz۵ϛե_PWulou4!(BFL#b,w2vD_1 W+$W$tױuj%4Е݇F0ƽ4[1ǐ)ĉ[״_ՒL*l&7&h ˪EޡFT~[edޝ &SUH.%(-M:۠udKũ+U5Ѥ#?a%˧S!fT$NUQT䷼x8A]|: dYB0˵6}P/I>a"jϕj'wm^A ZˉRfKY7 Uۣ7,F>ګ wsYt~_&Nʁ7g5ERtvB;5{_wR.3ul[ۻR޼lJi 8xCayemRjV<&RYiyi6#Ͻg\xT` m'='/%34jœ⊺zY(YP],u#`Þr<Ƕ"j*YR} fnduVÿ^2f{xv??I=RjF~c\.~*'/"RӮ͋/eݗT$a#=AF d.A!ᛑOD?Oܳ֠?9qK &R}Q[7.i&C9{Y^z'g۵Oe+}pzڪ[7/\:}A (ۤmŖrR;/ĜW'L}v࿇Q>3wo5laleqr*MҮ]^}k7.S3ΣN-O|/iy'L 6wΏ6o8oS&\dP&0YKo~('GtW%/y{Lybr>~FQZrnY$b_7ZGl),0IyLRdgN&E?Z+2X߾/]i)oXkK6(FU6O_VY{f~X^Yz멵[mb*Lپܻl}īeuR:==Io8(6٧X("r D5$ҘǓU]D'(fpnDz*,Lhzl‰=sՌٷEJ$O7sȺJ!>:1s'-ZStŗo2=nIvڒ Nm5¢l4+:F݋$}u a/aŀ9Z&66d.CBk)(q+k=܍k(,]`PQ.=o3}Ma#]+ۅc5QY\N;7 ,޽%N eǯ3OͣIoN$1]6gP~ ԹS8>^*tm19*!TΥww}&B.Ǘ+V]" IDATb8^HN`Po)/5icrRuӪy|Go4v[WfN4< gKpiE쓿]sv7DStxMR;wWIn=DSѣoq'M{.poyri]EV.yW{}fG vQ[/uš-dHAy4!QoBщKU?uo, bU.IwWd푥]A|~Wg#?eVR.Ϝk4lm';f 4@ !l#D.?N㊣>-*q1\<@hd4Z=5[1Qy:߽Әm[Joߒu E+Uڂs<ʻUb6)֭d?PBtWbű+y{9U3^Fkܠ fpReADBǕ k! g:?ECں ox<0ɦljYpNSW$P_vm)JCtջK3䣼1sBg3~Bl(P'ok;]6~ebIirh ^ =&؍r}rE^7 Q._>2.= yO[NcGG8bck]MϻbN}h.T|n;tp瑔>cMl|. 0ɥ7ZU]N]հ[|w/7t:\h !pW82(Vv{ne Bw"S*;ID*, $;##b a_6mO>d+1V.qy :tXFm4W(߅l'1Y8,\Ҷee_).VAҷ߯A'zx`̉/_J if}1Wp\khҷK[x{wcϩo]KF$F{7"'NjxSYx 8TqMvv>YXqK2DםE],hAi]OqMb$jBA8M&bD0iM[It4Fczk64zBiO"L>w KS?ob,ڥχ׍ 4Fd'qin:}2NOKC҅' Ci [R7(IދP | WAY`bv-bD^1RYgNѵN *fsRYMfUDc roa&ćMK8V^8!zQr&MU,΍/El]3>erMV,4.A"X,XPwƫS/´);7sg{2=Lli,Ԏ]ٽ&:4]S.>H?[vlYm Ѩw9&ʙ.:X 'h'K=|z]';sH;֧%E =pج`j"$hҷ.tHSO4b=kP4韑fOS/kYFTra|m콼)t>*ޭ´n"UcSs,"ݗWTcj Mn&z,S%~ںXB38tg'dgkL3\ޜ^wea_ˍv`ηS٪ I`x0yFOaz_O|Pqj^+.L~5I:Ef_&̀N^c~OT?;TtWuu~;YWcI֋@>Z[pDV#yM(1xFRk.*8mq"$mFJW+H˧w`G@6k}*KuS"Xt_7SPUVW׊ n"<,MA\G|>aeJ Ld*Ȉ~d F!6s mP 7 ߑ7r']FN PUS6'q=|!u嗖1߭Ru ΑmzvH޸TV^Rʄ>![V=xhUVn3}M.ZM;=cem^6Pu?u3>:f!u0uV Q aݹ;RYSdW=GyE`TX%쬨˱Iq5qZaUzF+rFޖ &XSf',.`g)5%*^1!JXaloť:ɶzABqn|@7| 73/ &&jri[.I[/Z(z6GYn6VށR !|ŐA[T1c2ulyԼ *x y ,J4x| p2c<[ ܻ8QYtF'/ 4U41%S6*5/R1ȃ6y -mMɗWOR}?]/7:{5,Lo0 ށ+\"|){[ UƩ:3O\X>EY$͗. $⪟:'X0.9:=X*?t#jXngtH#j+;_y;<;M@υ+n䕓F6OY//VsVt>Ny{>,Dy<9{DZ4[կG4 @t<\ 3tc^x ?5ų玦];5FVLݻIGnhp߹{C+P`H"q< ôZT&SRyw0̴Vjdˮr,USg,"RX+ R֨Kۤo[C¡U{E|N7f( ãŠ4E=8\SSڷ0?qN &[/aA%|D&{ɣGާ"5xu]]܊˳i%B#U _ 4^sQA&8Ty U1'l &ӕx' z)a `&T>wXVU?ǾǭCګWHDa9.Aީ! voNT c[u:\թ5ZJ-WreeؐL$?D&LJ/S%* ::s@ Ci4&pUok!N2ڴ< >tcT87?$5mVYU6jBl{\?]XY Vʄ¢v۟qؼں;nn<d'[at:3*VvñV6׭#+Nxtrk)mnL^dXOcV_4VIտnZ-TsO~@+6L`8Ѫ6"PycFoR43ɜ-3[yFnW2sÛ(D/Vh!\'V0ldV +j;fU#j?q޹ f&dm6Yg ߥlth5˳-lv2N3*U1zi*w:2R 7H@ٌv5QWmSqbiF)%CX՚ Hj(?{)*FQt.C] _0]6sϦo~"gPb|zYz‘b|#7?K2Ջj$&wLNQ3,CRHr NO0Piy/3ͳ^rڹ h5ai{clEFvνa8VUWPzQ 5| .B jf2\ymd@pB[2Oeb8Y𱹢N_~y@hxU \voPBhmbqnM5h{A=PiV6;VE'1z[{0̳`<7:R@QT3;ʊ\Jļ\g<L3Hy51ޔR>AmR-/,,>R%W*SK@+a3"{GK^cuCAZI!i.vh+,0zEa/PfyGS6ɔ,"Hg A&IU89SnH7Cݟcx&+E4we:wn ͷrbߎ:9QjUtaKmIXщDD⃘ƈԐzS$1ۅR4`rq9tJh 3O:kL?w5f?(JYaOq3 $(T*LX:{xim@,w3t&C9m!"tjw7O.?k+~=@Le:x]:|\jc(֯0s^Xd8zrr_n&^&ucߑ:h waJ?rC'?~Ӫm~.:qz_iYa/=:]٦}ϟdm7<|p\=u&z IDAT9l }F*|)%߂ bx^EņZڶs}maeam2t|.&i˾<~2mr ٿ7":wmZ⺯V.֗k nU=/CcodIk2ri=%XTkswL6MF?fqu5W4iumVZQB{ܵ\>?3:b"@iN s1EM'|I2]ny/׻!?[֋յ"Ám h !,u%E-t0WsOm?.uT՗4OS+8{*62SV\I]*/`& xnͳ[H<]D}@ ~뜬?lbS]i<,4UTIue\%kQg'}e8ȰٳLJ3ϲ-) Wsi&9(mJݾ}Ϝ6k^5ծ.n;Q[Nu:@֭,zIQ!n&IXG۞ GT]JJ>q'{4^__lqIժ2NaQ[BB~!A54_eDN^B!s-k|.= pYs3 KF{B&zy/Yto8PCx(Z^@+[;EEؙF>/ELkLz><,p!,nnxeP~h- #:vB&{{+xܙ3vݳƸеK7'7f,˴W+3.>cXQM+}wjvaVD/xKY>+c? {,n^WSj~ ؁dMjŻRt)su)3fsQ}fRmke F0u[A^Ru6RӴ4m`,d}@|T}IRLsk46OCϊdu&_4iR3ya. &g;hZ:z8&1!6쐄4zTX/BJkiWKMFR}N-.Uk}:>\h5-uT5k1:Ld~qMP3Hd[SzQSGkMyRQgwc+[ g4ʕ8oViu%G{^-\ڷ CZinaiP*. j?wVt9nUuo=PHw 8&oERQi'`Yͳc!K6I+ei_}Y,,ΉCfP,W֢6$P~s44]7 @hȠ ̨䑩O?jn\""Eиd -Q3uca'S$uMeQ4^n>4Nh#<"IyL/U\OZlW'jICJUwZ][$u\YܔL̳6cJs>%>Ed ~"PO: #TJD&:+BwjJtT|vtg"ܸ;]_STV'~ _"9ŵ]6at]/TZ);o^L8DŖ[/O>I wV4X4J;D+Fe{gFeMZcy~>znW*%RN'6VԈ:k2Ȉ~d`ɫeTPOrxNwtgk'e]ƿWbf#+Tm;:k:W'2s)sZ*@vn7nU([t,Stoޭ)s'v TJEy|@$1:=AӖf<5>qzB?ߓ6$|byS.V֨5jN'nfoGO#S^&ZW_L݄  ΖE .++-4q?u2|lǖe2,r̉e',Rk\&3{Z᳏f_`In]i }_w~iO~sզYu9<⼉OkrF迯\SUd 97,`ϺtM _6G- &"t^;g']"M,7 n55UFv{ma6)I^rksUCQhFl.W5O<^vEBL%bL@q(Ee!FKe( L (^"d/ԟ*8N?2mco2\|yAzEٚ'2\U,8$;[0-1y9F]RPrY:K/W4ʿ d0™,&;#?qGsz5\$!JL:^/Gz-wKSvk>ɟEMݐTjt dPJ[Z˩А#9*gXȊicϮNT}/9Xƥ{nL&M[̢̥eW6<я/D1B܅i9q'kvM;TM-C_*ș&+YzFj'۾\Rj浌~Ӯ _Tό~^a5[OYU1tz%g,w,Fm?^؞#ɧ s mk#{G5;|*t]&X=n¢0'P2r깯z_#{Ϻ+Is_\ܹSYa0jJ߹r_g/u.[J ¹fNHHKܺ Ϻbpܺ>yO>l2T*^OO"_~s3 us`Йi4jB_ms9VҪ3 Kڇ!MQ+䲢;mC[ jh/~+;V:~9sɧfsWR.9|g|ՒO}CXPdȾ!2mپR6mЉCac܂M68;4G_ wb,wQ}M-Vk"QmMuaH&5j~/ͺrfC?NI>¼B"\ݼP%0?s3/EtFG;N t׎!荞0 R9&$B`g`I]u؃[!?޸]yU$-R\""{G(ҹ'rba^5ygrKr[aVH굜RTXZ5!a34hŕ3 2iX~/YRQgM^N>V#}G^=k;!:TJd1Sgulx].~1؂gF?q6,غ>sdmGR]=`qvY!0zpe5cºD5G<-1-BX ='0P ,.j_q 4Oc1SCz,6c߁GeHF=yJ< RԼ~ګ<4koC>ev|\/6V-6?e+}s3.nA=gɇV~-6x'ްİtEV^|,d>g#zR0p-bNXb}Qgs1y58kgO [@䃓v0}G<\>'1 3h[R*!!ⴕ\TA75Mema~x:FҬays#[dfRE|s ,Sl_0_j[1Z~ބPfUi ]uC,no>EnTr`Z4FyZ'Uijn2\}Qv5ߕl}i%WUeutB[2: GWv@W2RQtA'^Ky/ۍN*9\^+V_+f㷤? 1"|ZpZmM =}Yv[ڕb݉[K%ёƆDN]edE5"LEqP(類m==k}?ӳo.(|~^.^AA^)kg3d KT0OwVwdJޞWIT_Iϻu^C}ݻņy3(US6ZLT3 b0 ?\Hk4`˪Ej=|yyίb[X°XhaTAj*!T%-i @S8x<";M:vOiY1%w7fZڳosnA~'>(JR<pK&:,_to)jKL)*z2L?K ‰= |0:Ƙ;ϝi6ž AvV-о$ cGU[MަlIdC`&yWiݳ6u%tNۯ{bdﶪ#<0iOY:G 5{(Φm!Ο1 =ҫǠF 3~ݻ`.hGVG=Q@jJ?Nq 1L+/^V7B"F"4KkT9VB?5`%3V|~#\]ܛ_*۲}?e450 -l{ m| >yM?3 m@Q/xDZIeb*f [~tqNnMYGq|qŕ$w-RJBiRꅶC])R (NHH=9ݞ.'!v?tvwfwvef~AAAAEM`C,|H@+6eb`, AAAAAAAAAAAN%PԄ  o            HX            '(jBAAAAAAAAAAAA]AQ             Ağ[߇>@*-0h99K权] >9rm>+\tf<0ٰ-ۅυ`#   r*a[KoԄX H|^ a4)A_v؁}LC۱7W> J o's!X   BY_~8d~8jr@NsEo۶Z Y9~(FLl VV0   tPԄ s^9rGlcdH.4Lz06 9aR1y>%'<aථ'ͣdÐ$|rZH )U'Ƨi$l0 .$%s!X   L 1;H{Tô2j;j LUYF0'G/ Dc[j6+iܬU:-fUEUN>+ȕ& NqԺжcy詥Htũ)-ͷTtrlƾb^b-ewfz&_555;m-VFeAAAA: (jB΁CX{@QSt:R!4@kt2Q4LP1e `q%Ӹ.[MNՁ:*,veb`|.KAAA9+0kqz;%mչK邝n mmdI$pa=Gy}W aߥZ IDAT6!wkrx0; 5 ()&+6`o6尨_Sk}uxUNrS4'uEIx^p/tMDYrU,`r)o`WհWhs^h5˞'ѳ vYQd(Ta7mUJHFy9]Pd %/f:%]EAAAS\?޻)W\fZywoAڛGF-Q3Hˇ;ЃT{!^G4Gwc5Ԗ()ޅom><s\ ܏u\Q~SբAA9&io^i6q5\/uW'fu =JMI{$2)2;Um^'$YvV4yUN]%"y~{LNUo8/2[﷉ZzJNAղ [kxF) P{JlN}fThQoItVA<{Gn_(2'Ąȟ{R456p<{ǯ{,[:*[>MNH,h)RZNuXk\ےLަWd󝔙_P$$i#   ޠ AN>& =}dWc#8vjPǒTѱ2dL6~_-'0wQml.{Ɨֳp<|B3:In)_iYY(LRVىasmNnT!=iF"B+1໭O|9J>!q_f~UYbOJ-֊Ħ)vED/F툭r3@+΄DMY3>NEEzBAAA䄂35$b r֥_E.h<rW>ǔiACeqQ;E=ZXeèSYi@3ȿx*(=ש]`;T#  ƖB@=A i~jRx!K@Q9T4IJtӪ6+83M !IT1o5izG®\^ 㳂ݲmre9ܴ!I_&֪- ySqc4g0/RXvx=~ZsHFj'$aiS[єwe;kӛdR'f7eGئhUWi E*$&ѭֲuaP4!K(cZ1KV{nt 4S'M4V[R5ARGTN,m&gQ輿!}/˥)³4hZ"   :(jBѥ߈艚oi]4}{v}ǎ M,eTE|kX@'hN[` N!U`6m_!w,(' $0|$fAl2,Ss4w2xOMV'RCڡ $Xθx/m`9M,确 ~{}͐m^7Upx[Xwd)T̲|=Ņ !-h"&+L3 OX_*hHnعhg”!?tnZve[X#|5hw7tH*+~匄gׂ pvʝٱP81 KAAAVje$xrZA˭8? oKs'&"jÊu&-F)I01I4ǎignwTPe 9^p(R1ݲ^ V3=Ez_Fq볼eض \M3%yJfM c"e}_Sf A$ 5ܿvxC" Ԭ<#$](eԌ,ɗK3EB&_|"bLq>!]@?:QP5Pb~dx3^%2:o mK^BxTl&QIa`OTѧrl;xZsz5^o*E4P}Yˬ)D;>w?ͰZ?3hM[RV3s]W9z}::?Z"   (jBȠK;yw~|X }u9k^йi$P?%w8 \}T%3`RJQ>/+ZtdK̀ߥ? \E/ڷʡf7UMυ}w$S$dv&psdF{pgwP$9.}j6_CGAA .A>.çC\*UO3n__5-^a%pɣDΡ6#KiJvm8y/8Ц [<޾EΌGaE nr8?70xakzd%0W[sqf>%!Glp&L nc"VÅA~Dh$lJ [湦?I>nVh:}&(`5$! _# ܦv0GAA9>zvb;3g7qdָzZL[MBe#t0 Dάz@h~{sYCȴjI/暉xWs hBzj4n~3oXYm6՗ZK5Yb6c! o6h$\"E+9׼ns8+Ȅzs(}`%[:puM#M[hG _q9C2Dg njL3jMVe-]ԏ#<A~E-HW77¯uZZ8b?6^-X![7VUշy2ۗE5]SЧI:S4C*{ƕbd\uq7~,Y/2uu[ h}>?JLhmm-AAA 5!Hdpm).yw=?vJwu/ f L|b=GdZ2!c,||ݏd=If9pk/!u6VtD+Fi +oC<c0qQJ,*%+렱p_z,XC"Ǧ:7[ ix׀zpHzMxr$CF/";*ԐzT1O XAG_, sKkgSOH S DrG7] [jW" zjpw^ ϯ,qW4qD"B#Ruwbw\Ry#%ubNFXD{VŴ*B.ηgC.yAAA xIiI_/:h.jLD)oӽ8AɌzָ$QVh4_B,VLTnXʬ!|ҿ爛f>qkNz藜PⲉA^_|Ib'H$MS)}[I.HuLemԠϕ!c t1^/iz.@ ]/e2bF¿-+ϿnǿWV2Y$3%>>MGQjĀPDKg/woD^LA"Hppx7jV$Lxnxq'8>x8I? {NNjH&5i;kO|V ALia!ߟsH8w<<̺/Rx֖G%5/ʩh*-e a\ .Tl@oVn%zsWaĶ{(O,Hx2Ekub-5#m -6M;>莯.X̱cQر,%ߜ׋[3J17ɞJ7.B*"%0a"r~1 DF%.HKeG~GFEf>({;lA<~ˤ5ڍApʗnɐ-3B.ηgC.yAAA ybb=zNtܞgMD/7(?K8\<+l1Lx,/ʐ`mQ?g?%uX. Bq<(%vCS%‰z cbU4d騅JjszګvfƺOɃP>*yjoiJV_ݴI5׻74!܏$Z13bk    H A"O~!2;{ ;g]f;5҃M6#'q- 䱠ˀQ/zY?#[@ƿ 1)¦ZXxNwQ4 ltY}v^G5*&2})^sj2X5p{B#x-ãSޏmE{S[?j: C|}b`>0rHHa•Қ3.;]v5F8^ܷ9Q#=z ~zh5- Ka}}iIJ]ﬧI&ylX^m(WwyM_B5]+M jJnIioQc̽hiؿP|V5Mf xMe+"!./ wBˬf,\Wؘ{w` ü`u'*Vfys|J>6Y7_i -Nkku3/X`[m $V4>|WW;Nj,}SUd M[νXŨbfE)]R%uej&jdAwL>-j y*XJ)Vx0=UԽ#]3Qٮrp+NVY~wzds>^q+ϐqtWtC͙Ry(Fi-SGmLޯ"AAAA`$Lν?CΚ)Pos/n\L÷̏u8,{Σhri|r\ cLPdIFXsv=c [vgb:ýl6N=g ĀD;/e1a[4Tx$JxiH86Rzķ6Sr|y6`DsUfUɰ៍aeyq,||GqesNPv+`(;.>yeQ g·xM[AN1Xh%cia!+3᧗!3m8Ի#xM+½`-> r0PI|sf>,jn}-FkCK~6Rڶ<[>&mKs{yqu#w>kfu< {>Ie.ηsC(AAA *ί>xs=Asq,99f&V/27-{+HA Qi,?ZArՐ|򄐤L|gt)S]D,&1xSfK}V87̒"f,dU!n;SsüfUg _ Ǔ+⪷o:þ6QO_P(&TXFdt5:aMu4)Fc5ާD6`.K)}i*;jn,.3οd`HZN)hn3q;0h(Pb &_cV4 +4^C'FS{UWw3%ᴇij2Y[DG:F ︈jVDAAA$Se~0rWT&c;Q]KX)7 ?q9w~ۡ_Mk?-z\)Ä́w k ،(Y ag 부R5(:{>U\5Ք9G  bMBE IDATwo͌E{SEZrpׁPq5qpf6w}p+>wocc#ImuW->B XO$~,{&]FQDpg_G.nv]=vuDF(lL CN}; 6zZ@dc=Ԝ7.3fA'z#"9toD>;<'o ZmP SKvX,~ d!-5bp\}\v{eLjzP$?-$r|;04BAA9~-X~U[bW-9tc|zk,L4nJ,E|g\$5 N6}W<0%iq !ɉB3Z~+jr.)<啵 oӪSqAf> c;V!V5}|qDo.2v~SbOm=XQڔ>I1]t G֔Փ/z:6o>|dk,mU!gnN%$k{maݨu4.#UGG* /?`Zm^oweZY2E[{Ȝ@-ʹZB0-Γ<#!id}e?yF<\\m_\l_n(USMjt}0%OI%=ݲBpD$65QӸ?nn:>DD1Y;#e{R%{78⾠Hxvz=O3HۗE+-Fl4: wh0"1yN?Nͩfz8#\jl%EKiO`]p+moo]S1RGN%U_{aq0 H&Be[jY:.5Vq` < ?O|S_%-C+$8l$V*A+I" v¦A K>)K l+s^8޵cZݥ2 aސmjOHjQ   '55!eI#U?g6!.4g9J:Iռ7nkŸ7F=ly/\[dߞNV"S㍮8blv+5y!4Ϙl*:wÊH1<>Oh*iB3{ vq%:If 5 Iyo03=e46T۪jʬB6$1KKlu /Tz_uL4LBM ﵮձYaoۄDޢWf+$}ɚ#J`(PbziRs*rӥLPo>`9j!yN&nSu*>z9>^o- $rk    qAQ5ϽkOƵ6GQTVA۞ڿiy4ٱ[$6eE]*I TUwPGI Ji"_.gk-oȀ!÷O:Lk{Wq񣙚 ݉1rBC o#H}Ƒ9+>?DSX[>.V a[p'D0JQ sTcyǏqpfD"/ƃ&ԱM( :?yI 7e[N ; 6|?R$è3P; k[oDrx0"k27O[c[KL1G^p5TK!9[<"TsgZn>N Kޭڿ.st8ѵk0; e6'$!X   %W kX{4NtnEIT6DabfK}ܓRWmt\^~YΧ'XuաM߻h9Frc-p7V|H;}V *l޲+glK[+WX*Q5qb+!euw.Pbn~d3+K{A-)|6}>UmiYsTڦIz DAAAuPԄ mnѯ}axC'_k$Ů+a6uކ?#ܮ{mֆhP|L81LJ?^X+l{Z̸E@dTd"h/ۂL%~-Z jL_=5=5o ۏ5_^݆@Fo1"Ɓ6N-y;NruLǪL45q,ҺAf.( <Cj(bS >x&me{rx?<^n"!7C.lfc5|pP#d;^y> EA½ξxzllP*'ZplWWjnӔ3*\ʻp'*!X   I[l&F3q5&Z+:J#>nre1xO58IqH+#pxY;sBq(.kpPH?r]9¯8:+cGNF-wڴ= "jDZͰö]l^&p_)7 %:.}N='N ; 6r's2+x`5;n䎯 ǶGۓ0sxpm3]!f)m{?M˞ )=޼&2t$ Gs9J$w*i.ğd?Q9 ٢AAA:<M 4WÁT(zzfDoKZgXšjwRR>2gf%;[-fhg!V_ H:(^u.7U:"kayr(ea3| =UT7ppuLO[QJa¹oy{ 9dϥM-;'͈#ӹuF.A1Ic'9@_k{yJMG 'oO2u!}sZKAAAA& û׾rٯ/=,gn}od>v~kaa]\'vg'k v ؍`30B!n>W.H!O`[ǹW q,ut5!kh"'ja_"ʋ?R%Ʈ KG,rei~'qc dq4G,{M>F\ͤad* VoO/:c"BY3TpHf`5up; 6&_k@l2;!v iᅏ/yǹWh%i2C hA(+H!ۓ@èS"P,%2Hυ}mNަj! q1uQ(jMF?!9 ߢAAA:6> j#H@=2[CDMt"h|&3q*8,˷[!qC$:)G vTM|#4=Z* rfg\## khYϞܻ۹jzVQ`_݉L S]i61CwKγԶF_22>VHz_֒*|zKg,`Q42%@v{K-$1;㺇)~U,-uES%0=e/|]li)5-+I`^[ġE3[O[gRXk*Hxff?~6X=OY+vSzS)Fu<{3Ÿ͆^Vs"k- &mi~%8s WM *wR`rybɻt|AG`ϖ:02,qMy3hg*^(?JtR@*Пr@.odQ   'N2pL6m4gZYl;_Rn&+(%%Dnzqjod8$Q@C^0ؕ/Gh!׿-aŢ[\֒7ߊvsW-n(+*ݳd\w܌@cTu& 41Y|&Ƚ۸.c:kM憍Oٱ8Y4mWl/5_,1>@&hqCˑ#[jkO{yl0昅}R44[3QJ %jG҃VwJ/%D?-~~ C]C?J<lm8<2"NEK{_~֍ֵj)٣]61n6K ·Um/29֘b\d]uJs;'ih =:'\lho&v|i.Ӹ#$\i)ib;lxa wzG{^>-S1Yb;w|a;viϯm}E8$UU \aMf),hq+kjDAAA$HPԄ `Ǟ }]?Ac,Y#p齟s.g<,đ0{;1 `e&Rdj9>1 :OvOM+>5> |σ~ȿ_005HP!z a.pUHh(Z[-%c䩢iRD!,y/F`ԅ_}&?e:B-hq黤rN}; 6o؋_2qi^?)nx%DZop+_H_B+y'= JV `A\1ISs:`ke}=F O࡟}]^n~_N~(2{AO_Y=Bb;wM|0BAA9٠,gmZamc㧤WɔȻVD+qN]eCqq=s4F;3bk^}!˟\~%E1YFC{x֢V,y&R19`€.GzdM\r[9R@³6n<ϰ޺IrEJm"<۝Dܰn~畜ǍpS]d*U/2칾~ L q]_ΓtK\Iz˳=\lyC;V eJ;Rq_J5n*.cxkKN flLNA 2Ǽm^ܵ&TO503-d#Q^m}tyY~B><&χ@kLŠclgǨwW;/4@>%*ɑC1z_ E#y_k5ܱP @o>9t2+yCؒ;àɰX0E,F1g,9.To_9o%;&φe\dqvxlF"T橕q5xBk* rkmk11з i1u=h3=Cjû`'.[5}X,ތ9.75uѩl019`- |2o@ G1L~C!8::$_!OA*9]XoAS\?/_sN8CX%deb`>uC IDATZ!)7K<8w'Z(n . w!+l10Q2<p+a=Hk /r0@1®M-#:(u,<;T J"L&F[[ Rha> ZDfk5Pƪz K"u~<-Iwk_~!_6HTkkVSĞC/%_`wDLP=/UӲ_IԪB2§+2!WdԲڌV^! pYݤ.b4*&D!X$ɻSԫ kKEF.GMwJt1ZV7ȔSW'YbWvoM |K %ql^κV d]u,,qWGCʬ!ǭ%RPQO2ʘo;2i\:3,k,wX46V&5KRNBk    H0PMs=I2ՉzXnz?N ~Lq7/OP%z|W\2]f-oOOpŞ=j 362 WIYd98 Z.%d:@E1ȋTJ-(5dszS EXGiԕMH]"k.\jƻl&z .UtԠG'hEN< 9Ys'ߡs%ᩳD8j翢TOs`G'B~ ]ӗPubZt"*YTW&6ubl[ æک.ic$G+OCw{Vü cحpIC(y>gC*j0ԓg$+4Ó*y~9RBImup=p-Y1|pO~Ck3!<  hWgW00uIɝv[I徑w*ͳv'wq;Mw! {Yt{xooX/`JC-o=V+VXϙgy~e-q\5BCjғS2u Dy1 i=e lE\Vq$Vtլ\RmۏҮ4a 62{czV"@uwF)K,OQh.w,s\Ȍ"ҚH`a"۞.KI Ke&k|)M3MCMf&tkºE^grqĺ-+]Tt1%ىC?7VWj6C$~2 &1 rB5"uZ]h)nřx%ɦ9ʬa\rm:V5cj h2)}qX$I`Z"  xӵӤV5.ptfT_. 9A! 1Czm>z<}w)-֡Ԅ hϦ]7FRgM< mQ(MC1 *+m*(v|ș ^yA28HH/=029> Fj>'CFxxdnk^ 3howdG0x 4,F1C2Zqlҳ}+N%b]}YYGvY .eW{+w`Bx)>gK2T›78)]H*/`bۗO,B]Wj-kiMdRnE\?SWaC^MzImVX ~z(J ߼$,yNՔXoC:*|VlWA"B߄w!Sع")SU! =)wsaqОK o#\4:t|.::B |"r3tKC$n[ O"|p+wiSa%   rB1Zذ(`! mE{u1xujK #Qf HtھD)=> ûȄ-zK1U^khrà,E`_bOg$mY"uAZɻF:Q3]B'BmH"SMaNk    H련 A"?Eʉ1EJ+{ߩz<= iCA R%QYXK?הwaRj]SL6qŚ$Ԛpg([F", gr(Ŀ]Ka0MiCS q۲ 8xrhrX]+?6U,7ojCiʋޢ  )-K$CQboûA$o41]9&x~'+)vjKCAg8"S<OiZny([3tf!7j`O닁S-y m|[٣#ݓ2 eoe eTA ( qBd#CdW =].M4-% ~_/7r޽w/>3E")efI}H/Ogs˔:R7C@F,aƼ+Nl5f ů#3"ȹ-%Vys3w03EdȏOեVW*@h3ӁIt+_abF,f6'S0LbMrӑ󻑿;y2d6y0Ճ'@:Zc9SȶuWyc^f8uN̡wM,yJQ=M[ee]YVY̲S[JbYP鬲9sTp_ݖ311 ˾ŁY~ o$fu)s`շzLI0 %Yo$A>gUYs(quD+ݐd@(EB k<|m!#ȽȌW`ռKPg-ZqM`&l%aY]n󶜵,^zٰĶne!-&~TyB&K[ms͢ lz8v沂Z6S*~UR˚E=a<T@!TC^n@!عGL'^U */I$ba}yv.lw% k/gX_|WO,˰?.=fMOt_Z3 /Oխ@Qi v]%yG<Z;A:@{+x-nXi(h\=V!EkykpflECҒ[ܔG(@qpoblm]Ʌ(]h]SGi7 ldKq LxKm@DH3?X5ݦW%ul1O r]+gc_ S؝_v i^_umÖcj3U1 C1nFJI6Iޮ$R2鄷^ƕ@Ձ& ^QMy% pնUNzEbѨ9@F<6* Dm.{ʝ$D}+e+IPuHАWYd^HYCPPt}JP^>gncR*T%d}[M"('ZjsL˰P"RG ȣƆ1QBU 4s<#"@5yr,KEP\M;mV˄"Fh+@OEnZsF$+$=8oK3~@_EzZg k2"_;d'A8ŧn=t<a$kx=wnQ;K7?qjζWpnӰZ#>sےFgwҐlGse^ ǟIPTԏ>7jA< H4 Z6;';19][~+pvUᡐ(]yuo?p %tU@ ;(^VUjՓĄN|~R|yB",V]fӼS.W=;9#F4?I|BQ}|29bmZ囍#?zZ+9gnSIMdde=ulgK&͔zW*@ozN}p߾xQ,k$p뉘FjO!NjUp0W{ZM'1LY*'Fԭ}_ Vy :C\#Ny1r:KwoLLD wŦjwPH/kĔL6p([2vzF6:~-/GxڬX7"_Ɂ]mӴNhO\4&c*cp:9#nS0 c|>/BC_iCfgdO* !StoAjԈq/SL}pH7BVݴcSY,:_V'u>޻v'T"QY >azD/ٷs>}?ڠ:l7,QTQFщCP3d)F:!$Cn6(= jWb|RC̫T&9RԏI)S]CMyC5J4{7wB\QQrU]tYJ.xGo͓s>sbA;܈3P*GIdV!0oiUJT6n@eY>-x%Yx@CVZ%L18%vwM^r\xH&S&-%p|` 9o:ta[PAc 3炠&8HIM8K%RXa޿y_:>}Pa5؈&rrssz=]IlQIN[4/%x{ЅPNPطe5i7čk7=L*w 6m´`ս8հL2ӖYf3w>d#\ =3M36VIݰ{ 3~>-dMD3*RҾu{465=57/h4*<W3L,GQl۞m|1]ck3],2_2O,!b9b TpmZaCQ^$_GWETMMwD^a{O nK^jk̿ߤKv&؍ؾ4T634l6~rS\2.4oXYpu*B>*>rsyFEqy_m3ƍRO2Udފ?~?eI2O!ߪA2i^KnO2 qCXwi l;K)fD7ͣieڥ>(#>QD7qpGlx(IKڼ)7M@&v+g]Љ}%GaM5̹gUOKկ8aDj æo)GỲT=0oYqhdžɆ1ǥ'+kT+Gʻr?N :p\B}1 sʶܥB͝w$ԫR5kŮ޼֩ot;Ҳ{}s\yTdq@)*2c`:fu IDATןGEW¶1G77՛Wb}c ̕ ̇5l1Y5Z _s4]w۹yZ֞x7Zb1bV,\-E7rYPiuŷMtVz趝\'Eՙ䖑=zܹK{[-uzi|VDw?SmWzmߧl~Zz=}cd/"skx36$ڨǜ#3M~=:X&9(D<.$ŷ*y{T LQ \y BEd;z .ږccBVj_swM/KLuJps.)Y/ 35e5 Y5U-sC̩|o4ᣆr-"u)N)~`Td]zZ]2s2c+ E"!BQ+n4>I5l- _ytF`꺟J< |P|G=&%升D#&LWYXe'yjVjI&F'd7Vs^9(u͕]挢 ANc!°/dͶIle$5y+ W4.&oKz/vQ =`)e dG[Am)yPZu j,hl2=nz75qډ)$"ÑU*>n>w/5$Uv)i߀)!!NBwKe.1zQHm`vb8}de1s}Q@ҭ;.>%qqBZy_8ڻs3:f\O#l3@@eӺv*|}|\;oև#ٻNӥCHLRdZF[/. o7~~Z^:;j[hEFҸ}G~5tTAA oH$y?D}^>{|㭨e2P TkYYm۳9e-sحcoߠ^7EQJ295ȩsεD$ƈNݸsG[iTT" hwF֎|0KafSRgtœf.NI/#+p;U0c)(^ϭ\T1O4*3{ #^Hfؓ@^.c;Bp aO5;#<[R2T߭d'Aj_gPd6cԴV׸ 53F 4cq~F_(lnOP{e=&U{,oeW5|NN_\[̵rk1&i9M@E B AبӒVăYEW|zpPZoXN JG|Zc~n#/ tf;cjmr[g.s YF)޴(uʖWdSHxv*_`LMy[8$C x1SO1 Ue22N塁;d3%K6磁0zlCFMv7 'KNKKvr[wJ2;'㳖.}'zɳAFM} 3>޸X/<)ɩ)lEzfϜ ?7n.֥C W4jܸAcvC/\?طQuwm'y'$b JG}4m\ˑ~0iOI==׭ݶ3>rsh@?NNѹ/ ٹٗ]ZaKgUU,dӽ^`KOVtٻiKEv*^5 z^e2.&߼n #G _ny[J/~5jԥC3'9>h܍};/->x۪Gr[uCZ6k.#),( z.9oXLzbB芤33b>X sk v巖8?8D pJ/xU48Px("a+֮,ki :vc:Xt"",b֤KcxQ]Rm/WKҙO~b"چL|ꞛA)RS!KA, p LIWP7{xIఊk<Y5Ifb&xPr%551c4inu<{C̝.0!fJ>ϴ̪m*s AsFz47@CJ эGҟ`#gR1]nqX#}Ch)A6+xB~`m|S2y)|Ee-\T %7z1F%}ӿn"n CkE4cʞU*6!kW@mQM˥ o60SʍF<,`}3R$S2enz=v/݋c_iػ"L2炠&hdqglr߄LFHvOk$#Zzz^~D,D~l Gwvыgf GOQ*IIjx}{ܚ|QFiulJMg";['&1!y *PXHH$BQqTyS9[)ld}h?{WzZU֫u:i>uz.6[Lۖmg-i&K亵 QsINMtREVt]3'ՎynQTAM^傶2n߿}q{ӭuPӾM{[7oͦSS320+[6dPO\ntM`}?~o<ԿvRK)u*9-ӆHBjo*<=pֶʖ^<_HMKÑ:ϱ)#=;>=1 jŠ{ޯm/׷eȨPX*؞@w&t6U2Xhd6FfgKE lό}'j5UhLP a\>Dаh2J}m ׫xiÞn-BιJxVVm3VqA>"xVϭxl"(M>I'h Un ,i~+MIFu%KSe!VAMFX!0C̵?u{լ2_.&0+ü7=74khj n04%+bˠINcua +>hؒgjZ$Bf"ҕzwpլ|^@C}Z9ɮ4m.-QwTQK{&s?<)0,qFH 6aXp`\פsS=讣^ /E(ܜ߷1w\t8hxR4-%-e̤O]8u]o/ozJ^~)9pZ5k_V_ڑ5jTr@y֥kg!ьŽ$ }wܥHErJlnAmҾ+-ڄ24%$3tŦUjUjzH(w>B 23'+t"<,\.GWo6 ';})(š48hT߼ WjEnHl_{ O7֋(G2_oK2VSx,ϘduG[bǦ(yuBx~L0>I[+#U„8t,4lE(%d1*ϊ8SΝ*Ex#;VioNg0ͳ_ -0MGiوP!:~ /1ǸtwN=qJԱ:XKBrϬ8n:ER9>)Hg\G`,cչsNs#xC䫴o*6cp"jɆ}^VYO|g]Jr\&Ghp/~0C뵒Rnܹݮ6#39˾=qT&ϿH*^?wSN!;ى?#DvsICZr|˽ݕRk_swƽ=GS cڂiڼ/滻ٽ{ΝZ㺬,+r(u?_vg_M9~wn8^90*QPoYz蚶̽4[ gw?[GA.R#[ >:Ox5ɤh4;}<[/>?ݏj0Gelݽ{twgc{ۜ~sFYV8#'CN;pV.$e@׊ȭvFe' ]CipڲI&щ>=_le:\C[v䗃2B6N=&y2ve85HAMD- g+o3)-\(fYEug2L3fK|q|Y噕!)^)6AMj؇I./4}gMV6$;9+~xbTgX5e~Qk3Eڂp;ݯO2ѐ!qmXhم1ު{"W` A,1 y3%\PoI0lK4tHUő5|ZMqP?PeH !V ^e6@]]FF7q% zo\Շ\mި6 `P8Qh`I3t|B{#{g,i1E,^hB6gtz~('s&>|6qm{Y/$)alqw<ӭS-9LPx0q;l.[Lbn Q["*Q[1=zxȘ=D4M6.w&E{QwlL.hb؉׎]f Uim^]aX.f3&#uɲ/YlNJs;^iF23w>씷>z{w/_<}˯[,R_sy⟦teqYݰ`r 6дmA0MO،`1f|<|`1*!h_np\Vb|BrqPS囍t7]۵;7-8QuA'鄗kɬ6EshˎrpY[^rCp[*DeԔ2Ub$]喈&7cn6& +1T_~qH<@Q&Xh .: 7}(Z|MFbWp@&PWikF;<w8* [骻c57UbI9O+٘6B;SCƷM,>\TWt;Hs/ë}u}$x-H)VC]]a 9]dp1gE/^o8B5g18 GT9km5q̄@@:qʅ߶fO~MZ9 N??l&L[3m尛[˲e֑Gx[~^ڪXC[7o&N;QznrZʣGQQ+N̗r32Plޯ7l['/`EVZægGp(߻udFQs۹Nm9rnG7X9n岶hUU5C,ˊ𡝺pJVI%R/_FH\Y7,ܼz-![voqnѺYN^O׻/b{*6xᔿ-n˹Q|L~~Y0xW;ŦfLdb,Z8+',:6Amm˜9< ,J=%憻OPj σa";I?B '%Bx7h{jQwOE嘤 aR}Y5Tk6r%ٮuUY㰕[QegS:u,|79?f&䡯^מ2I3iT;\}HMRiG%" ;ZrA$emRsA(T]99wY^/VK38U,V5}8ҭYe #5Qz ϡmw+WO7)",{o6|cYvDm6qqvu6ѦEXz$Ҵ:;dC<~Xx)kqV4i;ì;(tei8">%:ީ]^v8ClҪo~U:V\_K/:_AhO2Fp\֖=y9lB*pGvb\ڝ)7o_a,X 9BG]Y7W>N_gUx*a­ag/)>__v܎JG@JoCL:#sw/.}힊S1;UOCuU-sڡ IDAT2B1S)AkN*9OBQY-|Øg,U*I)EIl^Dϴ0JoWPS*ŷ*@̶*M.~ %B|ZZM"䋛үf : L( +Gqj! m! !侸0ş $FoߓtH}}Zƴ,j!,?v79\-tWs?cgr?ʬB򱥇*~Iq\a1!%#ǭ%;}eeV?ļczǥ9wl][ҝt9P!cp  W$Ex7_&._\v=[dX_HUDht}Q26%5_!QG7ރ{'̚ĵ*K.╋v/F Kqqq32⇯ض# =y֌3a(.]lo.:_Q}V{n~G2gMd瘯 %'Ӷv_c%Qt!SV$BE#_9ɮv~C"kgoxU[/{q>s[wo^y G-Y:ر->ۂiDϮ=_(omk@22qi?*@$J> j¼p\ZFm܃@0*TCJN!SN*an~`>.+kJRB~z, C,F$P!_`WcO:hy#o@n)E^n"g52#Nki]UQh 빪BJ; hqE[ԥ]O*1&fKzGp-,7&ǜ}6e} bZc%i:2!rrTDǛb ⡲R!Ǽ0<θuѳ5iEQ z濬ĭoiO6 {zo|ß\&w$W}b /_czW^ɤLH#j9תT%扉ZVX&˽Y9*4gY9Y7{Mҡ+tQ\rkN[nŚ֔꼥1sG&t$r~qe*ræ,y$ϖ(̬'/??eUE2J\|łgN[~CdF \WtP.h-n˹QQ$k^o(^:HGQw;btٓ?]FJLJ+<4<.ѐc&7] ڻvW-&uaKHĒBnhnE1<8/W7E#VW&.2>vtySuZV [:m+悇#^t?Zϐv\VbDZuY,$(MXNYeiCߎ Vx7m<ժph˜9Tx`LU_ͣudQ}y LjڹPjJ{FEԖY?.04uya6#bqOlcR~>PTAMU=Zǣ!7kxM,qD.;>Ȯ5fy)||0ssungڟ Q6 ϑu$x$Smw~uXK/12#wgR / џDȴt:vtv uFd1^;gpyV18N@g85 _({4?_L$)=r@h@'Ԓ9*B~wqJ~H+밈_~C#@R5 K82 q8XwQG@Ƶ" Wo~;dIZ_8րN})zn۸2;WnУa5՗ uT6ya/QM(r W"*Q4RQZ!h S5EcJ*vʦMD ePm3¨EkTzj9½ub܊;ldv\&;=CLYS0Э br&.%#2*hgcY!y^ 6>( o-q>RlU'v&< tJ x:NSTrڅ56nzWAMt3^&9VI;1HN|E9Ǹ>ͭ3\jd(ʼ& ԥԛfƧ8V3P(FUOyTpȈ?Oijvz 3AP$<{m߲綫",8r>Z"$&' =NxH'k`m-9vZ=wK$:qYg.( EѠrD&urZGyrӽ.Z<--#NDb7)",M\qC牖saL19'=a'ص?\zV|BHS4XMp`pu=d y!w^:}(rˏ+Y˹Q{q<|Pn}-O;tKu޹^6Pz|y(Zam//G{hjdj ĭtq.t]q3)U*n\RZ/~UpaJ,2,.fJ"dnCxVۊ7V1>QxyMQ$vhS6$ nܪIϺAjSH]Äa{=A=+N_!po7O6嬱&eHx5Qa OD؄ Ee_V?9c,7^gB%pI0߯'$WRB:u(R>.G֔zx`> M|E||!ICEH>xZO)VZ! b7 CMDQ't/a-&j 9( n 06Ï7VW bO]w^7i}3E4b(aNW\JE$/7 ECS7Qv揪T|\, ;?opձO;OmU*E7cOco=&<4_xhۿm[uMUj!^r1#ǔSYˌzfSzok7:.]qs׵}7| @Lm >\'W92 6pB,Ip9ԣ)i)|A5?d}^Szn "Z폿nmCCF1MILGLq}{nʂcnXfh--ѓ{N|;KqU2oܼ{Mthݾg.Viex94ʷuV61|vѥxg躦&}Ku[[#m[^<($#_o"*?"<:BG%kz˶Sa; UNk2?~_o,&C6cGD6K(=ސn˦٩ݹ-^7sar9pws뎷 X3'qՆ fCߞ}]{S@΢&q5hӼtHrʵZ<L ^[G?)FM ɹR2Zri5ukݷyߍs6G z3'>VdM6u~UĻoces01ڿ2ܾ30 Ǐ>F]G퇶uޔve_KvXYᡰ3r̪Ͼ@g/U֖KCi@;~Y#8N'&ne}S@{k[t`jA丷|SGDVmG }?ԫSo@S>25~cٞa߯,+<4.ForI*޳ම/F~[ E}iO_u֍;7/y٬lPr!9*y+/tn??p&@Q!:Ŭfu-",\ťO?ζ`LPgO |gM%Z4iqtѸsrnjRxk{m>Zrt9F".ϹpFV=v_L?׫[ݿzם_4y& ;KCb͑I,5ٓ,s ^bsծu?wlFR\\R.:ݵcϥh/O v4‚+8la[/8_@RaBS_E勒mFEF]]q@\qQFtaΜ4?x駏0,4/1Je!=]$zHLXEn^y^ʐsq7AȀ>誘D_!e>E+CB0 RxYbEXqf6#`~lh(t2e<=$>^חYR+!7ҥCTӳkOjnD]WF&w+S㧏O5f"吶h.2ăGҳ8m{5mt#$bINni|UdH\&_aaHp9/C7E|Ĺs0umZz.}KN.k՛v'%HMO懵fo=y6]^2]=MZ8`^] aG_7_޾ |Ƹ{C$eVJZ/g>_JBeo "*CDQ((8@%C޴B޽}$_rͥwmi-e%yKϓd5ղI$5U~]F%^I2oۄk35?mj|[jSNTz q.ÃlA*6*vtÍ4A$EQ\ul6͍qdgu$e-MWeq(f[kn R\&ϟ_ѩR҃Y ^3}^Aɢ۵n,fsaqafv?;Ygu`gФqb( Hi5Eyg/껯o޾ɟ9.6ߎV{z)JѠjrrsvYAOArUҼIQ㙣[_-.Lxv>_U%3٥gdXRJz0+'?V۴d~\Y{a!arEk? U7G.yg ؿSM3{anCBS9r'֌ÉwlRSg/ohjwoU6jΫszw41ǡdbZIYk6;+SaiZv/r IDATy{]a65u.QTU{OPr}06a育 A.ea+ŭ?k1\vnBDy#]q-.,ɿ4zw1n+8rR[-_Ev+?8=1ܽU`_歁ܕಸޒpJݏߞp]ob39= [bS]p#KҮf(OUoRAM5 & ͛ğK:&nc! *j p{ A á yP {qc|ù\ zj{u8%q_Mu5Pi4,F >sɴ bۅ͟8(͍,5B9B xܘ,SͼCUW͟uE__]3"T2"dhZ=R6]lvXlڑEbd|E<N%:L6I+5X AGi,#(CdĠ4d B$|u^DXƣndA&?cZhHJ䓿"t`Ú 3?)`scBwoo skh. VJ "ܳlvI[ lA8#^1(1|fAX2\Xq\}'W2?+ "WCKS5I]e H¤9xXEjLbAiP9ԶC&p"92lP}\ RP$NW' h)I'ʹX&q].>,.BxϠ[K*tFɳe>Mf*G0N pyސz(JQ49i'-h TBJE")wzE2G/mdX\yELv"\̎7֜~Cbط3H5;'9)ufꠈլ+YWWU>K-pEM-wIRsN)1YЧ$&,'x9(K (*m k# mI=*8lLg,*Ma(z7ǣe57A*D%?ln*dcZX b`Y2ZsviXS1_PBHhQ K 5Z,kPB5cZ{7Z({5v.F4uƦu)$HMW;{OoW2>!ћԥ4RJS*\$Bj&oP!=#B=Po\Lqʹ7 &"p9*AE-FG"aQb$!CR--ÚVsLfs[慧6ۇ0je-f3*`ZjrƘ 漵U.21^ELΦ,W+7> FOc;.|V/7's,š&%A₅l#O(P}G_$8m/uT-;VSHJIJ=np랉ܗKvkYVvIAM$2QxX: ݀>Ab0 ڢ{7E=g1טALF]q^(3|p%HtMnl~*Ғc05b6GJPnԡ;xG)|qXWW~էw x9j/&ᡆHI(I\;Q9$Yd<*uO\ u$C)3e&b#Sb-͍,nmG4qEX)V{u6MTx@jwNj6 @oR.AMW%IxGkǤ," ֱ,?شG? T(2ЅS0Sts9/UW2ul&ܧK/H)Tj?p Gsz4S圴Ϣ"u()>9+cv2YsnG\ow)tsQIpw5*m _i]jD\:J];q\pm۟,3fʘV]~ovl@}{`c0o}=ZQD/U2th"rP_URR(q{.4MwVcz多h2tF fe{WOjZ?&Ѵ1s*<"bNzgǷ`Q/|cޘZIlY TmD;Xna o7`₿W/:e5 }nWQq (wⱿ}  -vF6@(6XNC3 /bHRsqED@=>(ۚ5\ /龉ǾEɨPŸhT31?yye߅oةk (۱sR^ErS {yu|5ֈȽE[.CǢbJDA#Lw߱9DQ;ܱ+!\Z/y:^l67CTqb*i-d2ȏΙs-I@_kAMxIX;R'U렦Fel|W2bd(ZFEwlj7M;W}9\O 86e|;_l\쀧F>Y>uK:I@l]$P`({ZÜlO/ j aIQeq\%dV*0Y YOf&Ad2ЯA}~T$AM_WJ_LW#tSŒ0'sA{6뙇./}fu/v0am+, F)d>)xۙW/] SSA:[0P.- j|J{i(p^Iq' 7"wenEVsov TB0Dl=weSi=SI 3B~`r1"w;S0|h5VdW'If~D㚞~yD╪ c?(Rc{q+EPs}ssXO0ʟ=|:ռ~?١Aŋ3^E`TǩR3Ͼrœtn ֯=~褭\r3xڸMc՞>D+uwϟn?OI$'Ul!~)ɥk^u:uhxϬcv#|ܐ_Z0.3ߺ!s&.Ҧgh@T"i*;/kӎ Wء3XLrn :ȥr!.2 ZF_ e0ktW7n[Cz oX)WIRLfS^aέ;ɿl[?vZZLZxm# ]suxes~& &oHtm\*Whb4l-=CNm?t sv(5^~l"%x]^0aecT^|\BVNzF7NxnZ.B#2 9YnY˗_ȌzxXy|散'7[ ߠ^cB)IL&CAaޝ);weO=b#8s<뫎_2CB0oܼ-[eT., m%Ѩf\=g׸$qk{7.:u^ǜzro6yrn_=_"b*/sm$Y >ɘqry==m)`{PY{LH{E!'~6xk>*5`-;^%ӵٸ F^"m󨔲爠& sߺLB3W5*.cŋ{ ]cUh.L\{W& epD+29@Sh"G@=l"~.\#ov䳈/5Qp+fAA51ies.AyLIyٚ.Qؾ̶ށZ{+UJƐT3Ñܨ1~S:&NΜk1HzmxZ#{ZKk/ƍ0s*+#y u^sAP1Ͽ!ȹ1^!+,/)Kd"LiǽxpS|V[%TyY7oC!:*方~4cnf6iy/#ntէf*6tl"@Ƌ"I|lo~={k߼q!a}=3'N<fl| n)&?_,_7Q)!xu>o;)ʙw@1md\uhgaX||~6kg3]׵%ㇾlj P\3bչuXi7~x'H86/.pRϹwӠ+VxxU^{:{c0ڃJOIZo 8cCAMM;oԶ'K]~PUSmraB:Zn|6=}41y+^78xM׻c i7@" ¢.[6%Ή~$-ij m1F uHΒ ܱ7⸙/M-: +ʙOP`X֝G WS.sS}_{mH׸_/k ̠feggDԋ7zjXC`Vk ݽy ٮV}> O. IDATǍgoER׏XyJ}5|+Ȧ)dۈc: 8ռ+i!HACGEFOMդ-=?qށ?*%(tȀG e JvmgUtAғn%eZFz"2@吵LZRhtfJFyQ ZJtܯM-J+)Vډ+y\TO'4ȰlIBnM'(VSGf-XU$ڢҨPZ,q%νI[w+EEu:H&"Xq|W@1T{G .k>XX{Rۙ-kǛe:U5Ƶ}d 늎ߩ+&Istd-3iϦ8%q_Ń?kwƗHOD 6Ix) 9nߖWo{/G4q"ɔQfCو&'!iי 2Gw{ӿgcEM}]ԕ |B멉lB" Dkiӊ\VMNONp@Y!JwKJ2OG4񉄢g>vO<&uXX^qO\glKs{J=>~8bBz6!;UH:{WIo贏τ"ƽxb5m母'GQ_.WbPB# dž#q NwhK ۸k7^OˢiEѶzzA{DD5%;'cͺO˜'",f҄وW/=Fe-)ӧ.gHdzo-;~?\DN괺}?z&~PjT*=Bƌ| |kdBb {C~DS k{İ+#-K{y}Oy9^v۰U_x}՞E..qg˥[B.ݪ()}0>/X%PDlg&!D9f2ydaPꥭY'sZR&wYnl_TGОeF l`ү.<4M!jvHOo9ly/Tw+V;p' h5ѳ~IV:ŘX{[٭Xlkf*$1\zҨƑHv*ZPeo%XmƣOܨ1.it3S f=[ (/d3$E?Az=5Pe}G4{7=}^q9uةb}xsZH˹˧=}J^u$dbs[:tF=Wd>&0<=Jfnӊ2R/\;֥v0/|ɬg,0nLqgA6{Gjf=ޞ> P 6y7[5`(Z 䇙"jNƌ )dLڭ'hK(K?l[-Pîmzyx\\ORDƲ s8=suO胢;篜IW)<F2'7VuE6i-eט2kc^rXbr#Nz'UWz[UZ-d &qjۯ_6^^kǶn#6j۫07#Q+Ecٰ'| s2L+xWo4/&% ("VV~Ab[|ufVڱ\/8yӶL ~U9;"q}T\ǰSʛSb$^:3镡\Sށ߽K{iLgv׆n7@`.**زu tV;h8Ϗymi8nCӴF[\T\`0<}EEE4d&v "h so]{#pc۴-b2r3-@e$8PTgV%] lefA1q~8d TIG=0 >@dtFJQAMš騤cBq##ESϯ+oeSWW:[:Bs;O0V& &s0&B0\bL% oElYTW핿mFpQmlDUc|}6A\cQG &HgHvjHEYs $JyX ToS}7jSj"+{'ܱ/Kp|I[eg-[,bbQMěq"?sߍ/Ǔ|X=?ܲ_wwpxbmaڅMH`|[JZF\Bz|ӧç[ u|o:C7Wm ykk_݋_/<<橔3%tXk`' }MO(6Q3f_Jv^*/8v։T"~5_>!3.~kӬ;0)W/!x(vyaTҧ'G4;SȌ4 ;m5c?ctjڭz}`I+7>7h/jhك;n~!BҵB?zfGM;cVtۿ̙NEyܘݻA+Bj5< o*餅 p]QMnsGJ:ĴF4E~p.86L}F4,ڷq EE־˂}{%?O~5nr<3Ӱ:<= Cm;xT)WPz}m댋۟?#W.8rwh(o1#_b#z}>Ӓ^Zl6122%VF7m(^E϶Kǵ:xD~xnB8|ާţ2XNM_['^~!PJ޶;z-NHbIdܻn|L&n"ʨ%`xd o R}w bVFX̻|3zƸh~䜹\壦C0}ˌf1J6rY$aixkH1*Q"5f#q%-C9vmVQPAác#PǽB=B#Zdvinp fnԘJj¤@_FЋzQ}&-Em"?{fؠdz7'7BT؃B^,D"=9"\=m܍JAN' 5`*MSnA:-\*a!>fѮSdX$6<ܢ[jX4&aƸefO&n)߱5c/!xzu:iב˾qT,.Ӊ#\3ƽc)淯ڣg~rvJ:Т?2j J1 ۾gzYz%}|{܌2' m#N9Tϼ?~i=D51X` >$ W0?]pPMq\=_H8w{ܻy+}Q%uO].$y[čׯo7g-P]nYԗڱ:hsN)ͬmʂ6:}M׼?m\ݵS_x-/{M">xLkVPhԾ[O;ğUe4 W?To.*7s|}sǟ/9MX[ڰAc';phg5ȩuswǵ++&m(^TV&q$BD4j)DHu+c{\e^?{Wd5Vedްc+Yu Ư `x|\ksPp19QI!=~R oH>BD鲊cxHm3U1.Ancz~ZxZ m(r,_KYJa}g4ӚPebn04MWՅhaT 5P5 4a6y97x/oޑtB$/k/ sm^O\uy;H$寅=̕}lEl`J/T>rZbfym G~ȿkԤ~1^cC{OC?:ߊ?Mz/޷uW[^zLVV\]?HڶٶqB0$8^k7|>{Ϋعw1q[9E4f[UfpiߥrSB7ejn\Zb37+:/dZ{[KjҜcV&½ߝ8۵n̺;ߩʋ-_9?52굳`:U3~l3o`|S6zP XU*8'EZ-Iہ[$Nlpv!~6ʈt{m)Qi5vh˸YK&txhe 7r'xEF4`'Nݦ]f[pv,?t0 _ZP$<"yvoQ׹)QV,nhs3ORkfGMo2Lz~fAw[̶߶B؊q޹fs=~.Cv El?=ĕm=Ѭ}vIE>-ޒI/j4I;d]{q-DOs'_:jK5>UtMmi)ʨaA08l2Esz:33QnLXx}Rɘ:O{_Zptgi6לGw}1uc6U`u%sDoK2RT#5G4 tn"|pt+gw52:G[*^0~-oiR*d]0b<{|▙iǾ=1FԓZ/&4L,"Mj͍k!b*itg2l8m8b>p>֯VhhQ5ٽ`Ee{㶭 3B^.ȵTf=Jc1; =5P5l,Y+h6Xa*8"0Pz-R҇g"BbF9șK׼Z+ߏ#5jtL&Ӓ%K\ǿ-(nv %լ$\ZT2\ޠ9EB^Z0_%w1רϘ-~uj_d$_4Rѣf-Uꀰm[67C~{Â*/ Ew۴l~ِdrrOOynա_d}G/]=w+fcxjlLZQ96NZ6o6qz<=jS +dzvBd6}fVWƯWޮMRH,G)pC;;[NNABCۿݥkoD&.\UCiݭKChzL 6HRJI ۟㞅fcJc78l)eeޟ49!fed`c68]f[Ymx!b/;Z$Vleu׳7~Pw'觃& Au`D;Vsj@`C/$Jwl%hkyNIXuo帷~3Wk>r:x[`<ڛCݥ+3DpQ>!("_[5cL]J&x7K7bz1K:-Y\C$]?5}Sy;:v v0vҢ%lw^T\* (t )OƐm! IDAT7dbmg}~3a|͂VQgwmjy.cL.xSHqX:U>₍ d KLF'\G@驉 "SM޽:?-6V6΍׃?iRSf1ܤSXa1ic iӍϩPOn;?q,?pMiwz'뾥ҡohkWAag_/~]3u۰v"xz<6*SUE"kHhHN*]sӁET{ЊDPӹ!%-oN}Z+,qD\~㔨jW3S DzX\!Ni- *BpV14W# n4IvF[sY-o=*]lLDA󔍟Ao-ed@իz[FE_H6Ih96 [dqoq4bP156 ۚamzȬ;)[FIΥG8%:pY3 QTjvi 6f9*H 1*#A5kN }REQdO7.[`܃?d ;Aj2۠AOO_{7>Yg&)^*f*ݱ ESŚ"z`0̄l1'v2g愹;qq'=#um'`8R;M HLgÊ{W}ǶWu-{&~NaH rtFQ%] ˠL٢3'~uz`-VK +^[X,w?[',UNdO;[lqΛ~#\eՈ`Xsi@\]NzKĵ"*DG%š>}ý󐡃J5Rwե<ާ?kc Bee=}1Ua[񭤖Y8Gm4\3%撗)D[ꉘoOd誳e\X-EN M$-ÄZJ%nP@d/۞ŏS2Rȼxc [pSUb5;MXѹYnA^scܷ?|/snHD6f:Tvdʔb#Ȭ\:qB뱉œjw 4Gsg2345ޢ>nԡJf<2sf@D mc}l<*mk Ds$IX,0 d4zN䦤^w;i( +^a:pD"eu %Y7w`mUnlѤ[rY[Qz)iS(FŤީr _2Hdޒ9u&U0*L?HH\C&C*!Ji9yC"Dix}V*4wĠ_*e2_wXӨN$PR7c-cx=*B0\֊T> S6\@*b>Um1DOO=Jl.3&@XA=41kckn$݈Wo`v7.1$sǫe-Qpy]o1wZ}APUc6깴T|Fj=OEl.m4@HnX 8SS[Tf5C{p)??pO!QLBg,Y/|ӼQǩV]uWZM{{k|?f>t?)ZtfjT,̺z !&,MsyԦ~^n?1 O A|ŤuiƌJ/_k"=BSUxT&H˧&JuRUK=5]DJDb}ljaT>c}&:ټ/Vw,ce {ilv|7o&M>l^>Mb[&]9S:,..d}zYҪfvy}qM/FG?ҏ^5hTU؞\7Jh{B\*==Ԯ |<`x$ GKO@:: li7a@h\x8_E Q۸^{n0520b㷻[z@ H RD@ tTf M[&$ޓ].IB~g~;3{~_N\-9R17++^g1967a-MM{}:AwN׺0f!S,SBfWM\l Ei?:%& fٱu3nγJ-.3 6/u}gbA{ Ӌ!&-/JL2) R/B*wfJk 8{UMu #2IVIz\o (ZyiXJH=*WQ1=-sVKa~qhᗴoV=1ZGUO&$47\Y5)"4H )SUS[+9hc\cGv֤;jT?KMl$3h,?JOR,gY,xyz4U;V6qȨo&S]U/)POVfd[E&w0 }G^ t2ƺr*ܢIǸ7-SM)"iKa6M.S>>iaNŸ9{}7OO2t6?z~Ǧ}x1"fYް8yNvtzcưu .n<.){:mL:ov~&UkLϩ#fR,[l|-c6Ng̙m{yǔHZ (8=VM; c܍8ד¡8s_m!yAIotTR l2ݛcG(!U+X )y=V8TYV[HdK7+ܾ{tR5Cl[?ѺU0u&!qT7j:o9*iG^=g)^bVv7դr苩jycݱц zzw<欙+ly%Nt"j֑_w:|mg5ka}GQQ&6Τ~:W[ws!vLaWo%g㏒{ozkeGf/_1yh)Ю1jW/ۺsQߛ_O_I \2ofLРo)ƻ!1dU;_=8x O-+;N,Rm0|~/KLUtC˙sg-JZi(ڣkQ?.^ lp罏Vbˍh ?|^y,AL~a(W%9xڲ.b?.L^ypw;=l魋EĪ2CJb{Q v%̍[(CA@ЪNG7oGW`XW8qKO2hoǿ7wok0ZL,Z=Xc]Ӹ _ 22,mI`й\6.lf^ʬe-L?\=H<Z<_w]2c)BQiPxly ]oGuTOt:yHUׯBkUh"jE~ALqnoEubXO\ZBg[SoZ_O[hd7ޗ*:{s{Ź6̱K n^uЉ99\U<ØlRJQ/-xoM\\wpsˤ[}o }󵻖\zb[ǷYFE+'t=cm{ƙMDɴWy!JRHEܞ_*{_m|ZuD M"yFz3v`-{ վԙڳjc o*FY&z*P5e6*bvG*[5Ro:1ly**kYU*uR.U]"TgRmEUj ̟?YCMP#m?kRQI+VtQwPJ!)+^N={(G{F΃IGkXdԀ蚭Z]5{YWF8GJ(SWv7ڱ[Ti@i>o$Qfq J+d9o[e)O{xr96WQ%E'c:'mtm/QqLR)RQuA"AsMci.O4iܿR;4ry!N=:uT!|K5YT] XBGX.OTU U#0,.gX.U`§RPyGU2<.&ixQ+ř 90$^q_{pc95f, }(@D,@+ jRi j* &l68.ѓ{;Uj%K h pv8J_Dp4ZZ,lMH:\dGswn؇O&>.(+UR%"g^c8 Zϼ^e: 0Ń_HO'P &H]YZPHe۩:\j\dD&`^j*՘EM(^fT) 5.[:aL?[W9 IDATn5,)Pk,5 8װTbm?r"`_pY^$Lv v{z`2XQ\)kWas7[Ua)W* \Zq'K}:MsLiMHc0ӉAc2?wcXvB6G.f8Y(Ba!!nIazG*d# 5mU}۹Skqp(/%ϙ$D iႎּwdJtŢ<^brx.US"$pc18/aou>GTo-Gc1rZP@m^eR1f8`YɴlCWL=:Gj]DXSDMԪu(v)rљ7Q1"EHA,^/L;z}ksLj-^^;V5P*-bâR`lJ"KJ=z)!:̧^F/ ^en/18M~ 1Zv\_`BL~PMը(YjKI.) rz͹٫Ҕ{ErɅI)EZb–"DB}vɎUgn̈́mw<{}ܑ4]mi-韜Wkr]UgMz>9 OEZ;ͅ~WuX70_ԓa҉Ujh47xiM R܂MWGm⦄G h{(u4%ihS7/PSCFn15W[#h}S DMh"_Sh5<h"`Gd4 IUU+jR>M`]g[ /z^OTF֡Hȸ󛽉xĮ(MsM[~W=Fynm!{ICqAJ]Drc,7)N)Qf^U˓+ԑ>9xs5چՖGT[˩DsLӾLƺp~;ԍ>uBĤ9l}85JU=8K'In0 fF}Zc\ǧQ(n.dhr˜lnʽ-W<2GD2\Z}P}aq7y)h?B`! N+T#j*nI;-YN!.ɻiv5UipYxUQJbDJ9ȍ.gjkLo3Od3D0ֶYuQ~ѠmkȹzU+tADM(j9ĤRݙ:"(4E9ØSFb+6 d+CT=\YR&qZ'@M.ӝ/lٚT'h=\~tya"t";-E֌p&Q]OEeY8" >qLr:{*U'NR 8vzz,G&G:"g/7W{*Z! %HnN:mI%wf ]xsuxj ŒD*sl&߈館I+zRL!-x}݊>9h ԮE K,QZVu^[Ioa=ɔo.UaI ez(?j)#K+3Jרkid7^ _'BǐMY֣[ :nuZz ' uC&2П5C{!WNt}=JȤZB 6 J}k7GVWIm-猍ޥTI:}z{xKe&%ϿOjYqMiHE YoB"Puh&6RsP-# /]FgS|}xR882䄭ez!R_Vd`5چǏ X&9=pZRs0Ru JRG9&?Gn]bԥUR4U;եYVCF|KKE9.ErS*^}JTX:c1TףBR"R)I/d- 2TGsTUΛ4%)5xc2vخ; Aiâ86h܋ 3;ׁ[7[4[0 #ҽQ|7Mwp"p-ibh+<5-7Ĩhb3靣?ޭC}Im{O]ˉt{ϮvD({sV*RqON,Rij!URLeJUaaњR@8m c @5ѝl0bC:8ʳb;;9  sz\fRPKźN妗u"-PiOFљ~Zե{EDux۲(Rl>;#SߛrV/O`2lc<,g̋#b|N߼:^uV8N^ 0a11hM^mjOgߍsWuGgx8+U RTzi8[mj3M2Ԥ죜ƀ`Hw щŊSua¡ WKuզ+k+1 5JC]ڲM)O9&QiR*6>nPWi2I?t 6Ԟ"4xHw4I7q,= Q'% ^BbyC0Jzg^|D)r]a v>QXbLLitja j?}D[^ I!Ђ`ǧԄXLؐC7Gg,HЌ٪ih|ޞ&?u]"{ҽW:nlc';q4"cSsDSNAKsuOIMz|Zy 4 Fl{]Aα꼳rh7eDPuo5چՒ06J~&rՅC7.TFQS&ROHp39#j㕈1|rgz*hH|‰83[$ +ZlV1("Lnܐn(k-*eFS/G8|N);} %ۈG-f=lvω,we,?>ov6t9ccWHn˩9Ab 7_IF[ųB狼(U"O^ HQ3ҵ} BY_ёvAZMz5E 'gZ;4-ܲ?ӊa#z>JՕm[)E9*1읾=i9O5Yyu*@f`IaaltӞ=U(˹xR\x5)ճS䌼?^pu&EJzkDUMmzDM@ pa0,ma7޵sX]*rRT% DVy`:ؐ3t HɒJʋrƑ?}ƒGu&I%RJT \mz^:kdĨ*xݱ<Wze% a[if192}\~]I Zp ^5$ԡȋRċqI2;45yzѤTᘅ & ubo9R8D4AmhV P5ME^gdS%6.:K)Go|i3nRSQSQ#T3iJ64iFLMŘNWi S,FEiI=Dв"(@I _^t+uRWفYTS!eVppkhz5+dAwLӓjw;we˩$9mb4^N32c(-.Rw8:d=e+ѽ 6WP=ʼp>Nk: :+nRf̜BJChcT4r_G,WVZpj:1|_Ru_}1 ;vVDA}㪝Zk[ ^b JDZ^XϱoW-ke ?! Ɋ,K'q3nPtgմM} 1tiHuѿq}Q+yB™NcNl^Bln^Ư z~?UJ**otK3~MwLE.-- _:oײ^Z08[0V$e2QQb ]<ޛw#΄+'"kJ c\{e>m#*{_7;Ǩhu" Bgw&K\C]oXk+d5s"]ӇgdUzhkqܞ'lG+Ez +T'QNiX]ϩ&C29>>\,*Li^]/,-I]Ptj:J6V ՂpTJ.Q&6Ch{h:'ࢇc 7 !!FRm.TsI +╙tt\SDӗls6Ȳk^p )ZKltfNa0O20~jUX,_Ћo9:8 )o[0a6xח%Pj?SmN&@hâ:?ۊbK b vKqkVfzNbV!];5T$neի|Dtt^$2r9YfyUZ4 ծɫIN5n6o-5|4|qC^ǯ[qH41} 7X4,13b:'w__u@•FdqՎG7άh}/rM_tEvl32fB\?Y1.uazl/_] v8w8.o@Q7uhgiT*^Aap8gaVU0MC])>Tp-)qtO%ڠ//C=0m)`6drYd5˕])i9Wo%?ё6JtPa LN4Z%f6c1,!6Zvl=VBQ&Et"*)Z#pb)G4HmFf?s;ևto/t~Θeiis4hb}MJadOnE?0c#*ߒH7M2 9H"KtXޗ_)?uq`O-Xq+u}3X&:GAT^!ڱJ |  )~:y]$* ::b@g C^ @Na—7M*2!śG$_+^߱K]\ g9W .8iyqi~O5Vr/c;G ~x6mwfwU(_i+HiCF|K"nԠe0^ħRTGjF^cTLs7ɈGޅ v6M]LD&.Z׏~|t^jŖQݼ=.r&._K|1vA^~6%;l[uiCf߶վ?۸V=|yTRqFcRM_ŋ5jLC?힃GUjUIyQZfC[^<Ѐ%7NlT( IDATF>K**10z5:MfqU>}A%6ܞ_Eq U c't,{nzbBN2 rn'|l(t6ԇiy @l7UeȀ!?.^9q-#b̘߳KO_.K\ kU˾٧#l[T?Is🭻m^%(1MsL |zWW pryOIWhJb7?YϬNRi!/@E/&Q- ryWh~`).iu?0RqN7I9bzp“7SA\lB7O`\?hs$qFBA,c"bDiG]ݤչߙ{sY:>lf 휱%RyUSJ=ۙod*\3汩j)D5.|k/ OM**FjeM[x9[;}G) p/iVYL{lLjvon_8n΂̢rTy8 a݊.>6g &%|鵓zw/ˈmmZ; jb8^QL5⾣ЉV#om3Gݼ'D$NS&}S7VZy:{7dGY9{?--E9;5/Ȱv ut*8ҧ‚Z.j_)$-#vЇhԊr-%;G9E:;zl=.6y9yXF8!wɆ*Ōί^^Yx䔱S'IKJN=1{g\hY.^\W&|~d YME4 3ox{x͟1biz fy+-͘237?J4cgO0 lHl]:t3Ï= A՗if75?ILޖ.VLD:JE :*z'vx5[2=P Rq3- b"EĐrsNdEg'$f+h=kᖩ7i Mz7uVr>m b4q27)5w('l5\Q-3<֧Kܛӵ&6CiMHi#Q~gr &BH7:]XPb䌇fw6ZPTFfk($FD'V#il,8P8(l"d0.ܺwR4U!۽Օ /Y*,a2>K&8hɺ.鍑omT}&4WdǾp<ȏiojE؞HRn pxB$$v`͆Ev|c5 &yN}xŘ霃 ndxP 0Ag~ۡ99(mp2PB>oC^MOI DPgkigsb{9{^JT_(7}m$=?U:U7G'r%1`]B<+- D yuv (j~2KZzֵ79RWX EpPNnU,uo1f{:=ïAl[d~ںEg9;K?J]Ɗ|Y0QN> }<8q Xʚ?99t^'>!"]=ue5L\$,),)HH}PPo-ɋ}ݒw.~PO-'|Nq鷍7yK*+/[qT@dM3||- `6 o޽ٹe=׿S1cڧhS:w:t6W F቙-XƫIo 0kp:^הJ䷤T t_h.fϷnNؙH٥zz1 K hr t 1xё_%g*,zqlï凨͸T3: rz[6Zh^#)˦v"EWVfMBau2u*IQ(M[\V:߸{y m(6H~mim"|1aU֯!ib7Zj{E.OudB"kq}MmT䓜䊾ec :YpjE̐f}'tf"RR?8ӷ, hp6dfA{y4ٲH$(,-qI_zދt;^OK7~:IIme-;i.WH**bB6x2 c?4a:JnӠXAzV5ن}9,IִMW)CKv*1t:^\RAml.;XfJEbiZZ2@@dKV- iBe{~뗭{鬑[w?[T\i _`܍/WmJm[n2ˣ>vѲE&^%>L|XqUF=Rg4w?E\%$Gd;ǘ <Y@=x,,E0Ġ4,输4AKƠ:S5f%r, 7Tg|JkHOQMV1(FCn.TQkSi%'EWgplڲ_WdOHnL'2ks9/-Iv*3Hw&u*s"VZ.͔%TfGB]Q=PZ(oZMWZdJʎ%Biڽ RK/[EMt y/\ؕ-35gjp껪[OWST]/lEB9djVi U|Ne.*!O9{Qc5؅y߶l1=șqV_:ug.[;n^MB٫Vk K)aO&G޲$6sɉbX 9<)(O}b=+>NY䢟vS("ێSjMO# #{ƽIٶ@A #!~1Xe HJOtz|'8ƜBьIzEMOe96PQ_:~e,eAh-|aMxG&/jjNAۿu\s#{KoW5UN;5U)$6w7,]'xdͽbt֫/Ztl7gwcrZdl8Uxغ6P% pۡ?\%>qorvQq^InFI s$w/[OH[ԨMfj ŷt:d/lC1 J-JIveU8ԪUd^ OwOrTGM}ԥkF6h$̅NLߧអx[W(.0{5ióՐoahC'+l 6EedIIdػ[_Lݰgi( )H5wU"ٔgoa`~tV0"L0IgU*K 0?6ivb& =TW~1?DP5ÞP!Y>T"Fb&px>Rr0F>u)G1e ]nhwu~⸱ʴL0ki0f_ΧHiBwei7$i,4Wۗ-А601܂}-~Qƛ^"\^uDýiSscZ9'srE*r(j`ґ9ٗZjˤ^ؘXnRfU[/ b_3V۫7W6L3>Jd`GV>a6떊&#lc11~j[~)8ŒsڥlspO՜=j(;=: 7h4P`y`)nI *Tb7"hM!tPW-zD#AmXmqwO dOD{fņEsjRJZIaMՖޔZߛg،,dz.jiɅƝwAW'[LGf0eKSU7pD;0Z1VE3'궧ij`H(LJSES9%)޶Q,<|Q}a`4?hUgs~R4:nEα-}ݯNN?{5=/S~_gtlBz` VjI34JxIEO#Zߗ]F{ڝ7NAYv#][<6kc:v >Ӑ@P槝׽B]V1ׯZm3DZ̍Xk%lj |'QB"9l>8O&6pC+uϗO}59m+ve6.?6*64_Kvg:si)Js816Ͳ~k|Ʃ+'XBXʤKʋO\:\\^dwJ*"8顦9;=Ѥ̕K>8qϑ?|$6щl?d}FpxH2wۨr< O-Iu8k?oۢ,$٤fg!pY!p.{jyq%TVpgUi?2{YC0% gזs8[t[`[#?WlL=^s ]҉]Txsɳ\^wx~؜.I]'7>X\P2dmn"LXW.E}H_Ϸ+,R3\%E 顾_L LMdDdzŞhXmBn3vNMTu]|O!~h2GՐw(b1X QVƋdA~/xzai,8IRɼdح2{>Aj@a\ nsIC1]7PXnj,A\'ƿ .tt6qUi ]Amb^dM"sa]~mrQ]fMfa(H2} K.y ر; HE 1";"|ʇg5c;J<D5˥flX8J±X Ó&M;FyE 2KM񒜻y'B}6+dG 8}֝rbB}⢂tߖمl(7qZr%So*,SjFD vIJna;|o/ɋOpjn]衁[@Ӂ< &ʍ׽Bxw C#3'g6;~ lܸq ZѸlٲgտ_϶Jؤ})̷ۤż7<#Le_1fܵYv;?chTh|mkqw0=T[ma;kmxmp9<&֪K.Cz IDATfB(&<%9lGO/bԤ,rϤs؆_߾ͩ 8a'7 3>}ts/OM{zxIdRT*˼BnxV] ̼g GjԤF ϝsͅ PTE\,JcC/B0v Bw?qM!HR(n1$%CMW0? Xcθvơ:anZ`Z8 Ky)[B9QMlAYSb =L1aa[@!:<lMh&xmUgP9U[l3I$1a- qCgN4;}W/?zZ:e[}z<<`JÃjǡ )_ aё!pfε)% /mg뢾?w@[HaayWF Q:K ?D+{q\Nng :׹NYŷf3b)I}>/1OMZBvչd4v3tԐ_cXl Y'ӉO<\)}nAWKGpzjc i7K.%%bIM@U8wpxxᙩO8cb1M:a\,Fh@AM\ZIg/Z,dO3V0iBQTT'l[P wnhYԤɶȐMZQ55Jn,K>J'B鏾6x/=؈m~e0^fޱ ]NZ֪AgzAo"L&Iq N' 9'6YSFN;pb+ZZr=zg$rYfg|Cx|ݼߴ2;۫W|OAMeFɽFAWu*rIR߇"xqAV{n4ݡ#֚X_,^lZvӴ n _ꨡB"vkg,rZ2jrS2t5hi>&3Ҕ1?ҳmA B* hZy._?5q^Ï^d%q2 L-YKw}t{`>ߤF j2:59wT7ѹYiEȹե?G?֧KD>O0'[`PSE^Qe\eɨ'֎>9,[BQR VGfRuخޞ2W 7q<(%r?[Sq%"Dhls Ffnܪo[VQnK\|0ftj2 ^i5uRQZVzVY#B'k}K) @ AM\Ju16Boo$3EgP1 UX+a(,L6UH <w2d'?08yԋS/?C[VK:qM;W6B*JC2Ę+8ɜ;Gb1{@ۨ$ѬHhyYSݬ1iY>&bQ6zޏO f/}5I@dm7}FOK¸vΰD־{tcɏNFI{ǖ޵AMχ&=%ss݃!At"1!dT %>tָ+L]Iވ]}r oӉ}66CΩ6m=^K.ϽeBhƸgbLm֮#:o'9:b~)>vkY,™o9e`h`3((کNv;t꫶DӾۜݷXSBY*s:3>ɮF,tΔ~Fi'fNw0izf;xTvC~Z1ᒼL:&b*(C3_*C(%~/Pt䬷B#p":=z.qu1ei}}swbK:aJO@h۵m&z[as>uxd׶R%t8fV^zlJ6ok?vnӮ[p8ܙs$L0lOee{X;Ie;6n:~ss_ߪ:tJ!.. Dj(e['eɢk~y5(K l'|4B\(ERAwvߖ 7d݇O0>X)+J A(ei h%֬7? Er/KR2 zmݵk\K@WbыJJkm ˣWRv m/,o0a6e^tBB$b^mGNi\MOKN"&w׿oee*RMTo6v 'hHѤKsn۾4b1ڊZ{ʶܑ îe2+ymX!5i sk69ge[R34+PR.+W9-o֘?>{r1/<ӿ7%ϟX"ֶLfNե'j LBݠQ'.k6|h~Uw-),os7 >Ib_$W6-k#@QLDZUŖO^nf_~ٳ歔xs|$|%^[ dPt{._AQ䥝gBgո}:'K}:Tv0o`Pu{3YlnR˗:-ssTEYĖ_H҅/&X@o/o&*`4s׌f8O,({~O8,$C ʺ[e>DBЁC]m=<ɳ\w౿+g~9}1y]UA;|EsdZ"Hpá22B#t_6,-rL&B~ӯW'Uo:i0ݕ92Bh?gK/M8ڨ<+MX )pŃ@+AMt:[Ԛ\A.h3hk&(*2œIU!=@#_):K 2SJ=]VrkРyf 7 F333:RnZwݴ گW~dX{^EJҴBka(wtEhqje$-[&jnM3.E5wV$bI6,;@L. ᆼ!j7َBREX`K#j$Ew:>i5AGjoP҈*(B =A9Oc>3Pfhr-K9Pǹ|\ hьz[D;i3zKEB6٧W)D{\SkC(; XGCwݥYhDfamvU>O|ﷂ"(=%I dKxrLBkA=\PW[|P{(;GI~NQԿApv0 EH:kRYex(p{VcmdU4{(T 号-5ܫWj%*Ƙ&ɒvȣL/<VS9] r>P9}qaŬ戡S C&Z0  ;rH{1"w ^*tx Uߗ͍ݽAVba>ICz%uҾ+Co:}!c[VZ9=A[Ne%z᰾{w= ̚CH7`_2,%Gq#JQ A8 Alh(|N-k||tJ;npv#qyv]\+ [!#3.zRSb=@*M7+6vr Z(TZZ296XLOpO$~|SirrۺwuPATZτUr/Z{&<ؗs@e.%NryB^-BP-cAMt-Œ<*w`PU!=ҤYmQۇ05dN(c:&" vAIMW*SWDУAm>e*9kݴ)ѸP~﫫n_g: >W8ܟ̗ky y;3IhJ ' a0_6rHM6b\IwZ 9LIU0~,/K8m̊+uʢE١,q"ۻ/8 5:ѷ dChX6)H]CXK<ҁ^1j-s#5Z̔{PLUh-aR9_^ޠ؞S ⡍]}g:|N@QG-"ǚuQ] 1|``pgwAMGbR_1%`* jV2a…8O̒daSJa=Rr|'Җ6]Dvhk}JvRwN-YqU;{ibV %3TЫ~4\b8v:o4:k8*lTabv7~гRYLz>7﵁Ԕ/b~(?Cq[01iЛI8v.ܶY, fJ;oGlD IDATEc}<2隋K(U$3b66W|ϐ:~ j(UޡG qL cDIpcݚJsm!kAiۄ0Qn ۯ&7pGX8?zu2GPSv3O+v90YGcLf࣓7Zlvn\/4^~Q ZSSQM_^8Y+> -hqmZyprIe.?l PTA&T@אYD(,?> YOKAM(x%9fy?jK#4킈TBGjw4C"u_Sd+R 6 ƕq۩=e\e˂}\wi!Q``PXD Obѓ k쐥$f,tָpn#) 뼾V$4A-M*W{9Ar/ZS(jhaYKRx5q*,٪3fXP'p?v 'hu[{\c-`Ct"yL+E͵~9>z;&Zfs3^lى" L9.&d‹fMt}EJL)я/}J7^5zt޻,:V֒ʵ&:~ 8Yji5v%[wdF'F45wTmax ;ѯ/6-F:6Wv2_ogm~գ5׾H͠n,ݦۆS%ն&Ks+[D2;[Jo;G49>8h(n@e ^½̦n%kdʴ8ܻږEKj+ {(Kx"QQRKA_:*%L5E3n]fjHI,e$3+NƏWPfw̪&G ʾ$qý[08$lVf+YA _|).O7[gAUSREq}QɏX_ՊC;nnvvtg˂ enuU~QWSlMBϏ~C"|X*#L]Ao>>?*4G筎I%y EYBș}g#lu߮[ږ\֊]˽bYt+B5pF/}}Pަ@̈hT&:U=2ָRfQ}U.+'~Kf5vY$s'Y!Kոd/Cd%wdfqj]5*K>- n[-X8/e>}Xy =s C}^꾃ڊ$%B|k_O"^ mP_gfm& zI{`lj]@b2)QCb Lq3A8)@1^k5Rj?$U.cUg$^oA\gHLMԄcsja5o7+\vCG:{+[d]rGYb5dl(On`6|`*pyM{ lnX,ƿͪ7ڃRGu ԻH2VpcQN7tڦD|p:]Hj[  :APWXջP.T@m"zFz~1fq#xi?2y\Lw2W8[V9$B{oK~6j:8a,1;9gjn$9Bw,RYPdi.Hݽx{9$Y}?++y}xZX\Pj޴D, qqYx,]AQ42=PlQc8ͫP0FXbї1iCC!Rw"JKE:3(sD>vki>}dH._H z27ʁͫ3;cҀ&3jU/śؐ:_l5(*m_Uo"/J|Nש&op$O(A ǿ"8:Qp2EI_8{c+<7@8`:f\~_~FQMȰH w(]W b7ѳقT"~6PNa-U=iڰQ}.7?R|ZJ)yi ")t),}5$K!GPi&+:}iɌ̼SoֽĨVZy=s)4fbw 1i~$hGh`I(Icl~}&Muͤe ?+KW(^L4WWX/hNճ)e ڞGL+,dwko(< >8&y0p:CuAi_-`֐~6uVaFgF$ݘc6K+jː-D1І^-%m:8ckd~v.Tg+VOǎp?l}QvWG}Z<?7펵݅(KH95v95%^jkSCP jk=B$"$.5i@AMo_x6 yCX`LNC{׺2Pi0p]Ѓ'0s/92>0iވlQFQ`X\ׯOɺtq I B1ھGO ei&mXF肍炢=ä@!7`äuZ۹Z|{B݋ EyI2_h z`F<w(߅Byx efSسT8 J&35պ;`diGT{UE US(pqقJL@yۂ뷊(mblC޹ T/Q~UAز iif(CIca JlJ^:**C@y& j[ 6Th(jqؑ'Dž~6gMw!v;t:^h a9˓+'t%ɓn rc &CK[R\\GRΗ{C|łZyլz,QM"19*3p߀&hcO5ˏ`NJ>?VAv:O *YumF(g,wE9IQ>-jE]QUVй\+7)q˙vr_ɀZaʮ_-,)*߳:>m-b!%tH~>d]˼|į_sZťŶD#Դz٧#~ڰ7^ ~tJPlS!%(ť;[TCOM,W `Ĝ]ovW4AJnFT GQ6,-sPMN:cT5^E`+`rUb^b_ejB"ŇCش;'zG5`9OSxUpl*KOg!N'j[K-.꧹)={{uyo+4nrpYpbl=cZ"{5RJҶL6m (Bo[՛7*D5Y#?zd|{0pd/VXuݶ|.߼#Egwr#ެ%7}9O+ޗ Rqu4YŠ1͌+׸?S9km+ѩ!JEXܳpf"!5@kye]refF:ե'zu\TǰD_??:7@{ǖFG/J? rJE ż1K!(n*ʀTۻ,`yW*\hTL2VA;k$X#ve8N堅%N ǒb-*LD9.赾s]|IH}+(T[Jo߄{gP%QsԘQx\#Rv^5}ZT 38hPNjQT~G8Ulj %YTx)\$HM $K~MzM GNc{-}Oh}i06u ?2罇~2.];̊oׯhf*GEiyaΧEnlF%"}m[NكS4uM-! l۞5iue?dhf_jzZApnV"+h]̪, EN5NNhvv<Xt0j/^,Tj}hs'(BdZ!bݗ?}PiJ˕bi@,\v0ƭ(N\UtJTtzbfV^if[[Z.%zոzJy_>^DVj5D0iSEڌw|G!MEY,F{$N;kP6d_~lxmfٚUֽw l26<OU|ǻd}Fr@0nJSo^dʨO/RXOOyuڒ)C<EYˊ޴[~ Ly~>^\^.c7.]a7wև8?}g׶1g=.*4^$[?^w?njǞA!BzNuc\[~;PZu{kgcF %|.BY&c$3;ǝ9{3}ݿ5"٨qQ89fo}~QC|a:oHtSd?JsCSvZڑ- mn%r%ڲ!L!&6qC+'2F#j@ʘ'Ǿّy\"F&eQv~c>47y/zK0gL\;Y>UX`3Ӄܯkbj=z}[Kf-:z: wC1cYUϑޚڌ"/q$:U ӱ-}Kro*[(J4¾؄\fM\uItM55u=<@:Q&[WMMϳ:TÄutwؔ2?kMt޺;F>iCsCFDE Ba*.-Lå{ ZË]k]oy/-$rsӞe33 澕mүg?)AAб}}pl۶p Z6Y/͟=v隤>=<5_v3 |<.sNk?|k/lX]De‚C_~n^~aKҽ)!`FҟrFsmϚr%#pݳ Ro0Y!l@ֈ&<=9..ǶW~cH(؎UEUՒ'q燐 W, &4鄡Xb#Ǯ;*/[q8P\4lN淙ϒ]e:\?^}xbkyp7l+,smT٢ToA0f+,!WAlyŤ#Jot,& lH=GTW wcUզ6X֟sф@ CdMӾ,[bPb.RGtCf=;g*G,uן1W,0cW 6,ZVډ/QvD&Dlce7mAME0 E(O+ێqçء枚/ϲE#l׷[OZRYu!ˢ/]9?e!\p/tmy\7Y(>2x+_whbp9g&PfhIE>cP9 z{@T[V it<],{c[~ [><)M/Uw #."8.!*y.av|-pJ8md{K$?GJs4{|Fٵޝx.Mv(6j￴%#xgM8lc#̝e139u[>װMkQG?ĞCm珸/ٖk!Wj_޶t|ʀ&-ɠ%B,lIZ%Y^8BZU$6zGzl*v՚{2?fڍ^^,lP o\G3NO4-6X$q\"DEL?cS]rw[]?d" \z%JQIEQ7\DsiEk/!srm'Ngh{wr/ ٓ?֣g1 9wp2Ӵe/՟9jgVj{y1;&7WӽB e !8P\(,Aq.@Aޣ@[IN/4MӴi /)o}߻wɽ{e3=}H- !,%T;EM-0ovi;MLT;ʔ󑖝#B52W6NUXzz!TKhM PZ?W^N}"0@GrS Χ>|f`G/woK0&];)nܖC<ѩ}Պs8'- 0a삲EVIvaSs A@ zr۞MnXȪvh?1 'u>Wn m(>&7+ډLX$~4{My!q#jn,.$&0}ǁfnz|GﱟY/-ԕKY!i<'\^z3;]H䨧WuA=V/_XXZ oMdӠ6}˝{OxLTn Ŧbk}n^7&*Zo#^\}F"GZkЛ=54JyMP[C=Lt`8= :t LTtWC$[>jHjJ|=yO?:,$ W+ etFzN u]y˙9Y>ԇoܺAg!H6g7knn&MܰuK?[fZ:w O@9'=-wn9ס1 [T.OLu:pYÚ{f$\Vsګ"^;qd^!>Q(ݫ 3r asG&95ے)o3~ %z,:/$; i0j%+ΌڃPy)8oH:=J~@e)&NV'+,9ӿ}w@x_3-D[bZk5j`#sq BfEz*>N\ڪ bK&1P&S=&|j8XJsߩ4bDޫ:t} ]U߿=xJye.eo-|滯gkX"HsEjDuG9xbT/fwc5Z;xr)Fћ);t5S"k1R{lG{0 uf=Z,#_-R|Apv]%<凳extTe}>?/Dq iBCv(ו"}IGf 5\)0ɛ*2ts6(Raȋ]s "@y>R,ܛƒ7 犍: h7vOf/ EXb"G֑#AOi PO 0 $:üùsc:tj>*n֯OH=7 a.mqx Bq^Cq^T}(/,xQ='ۈvWҁi)EP4&?JuE${"I7:pkp#ц%üqy=-tq6lpߥ߾3mu}o>'q< CMzjhHkINlڰ{~=|:כy{C1&V203][JKX*v2n9cnlQjH}igEUsLNZY1#b 'v:R8]17 jk[~;4/#3P5u aƴ>tV-۷;v36B[>Niٿn:Nꃏ1 8Nfot<3,۫Ǯ͈#SVB$Y S^i0t׾]-|U38ӥEm>[wBC1"zh ( xpD!,֐0lAHwzs< [#yu=+.KoM+>J[#Ş.xEip魵:L]k}!8w}>'j7zβI%n=^5&.@PLGo6H:@w#aXX?sL8-'u+qr?0XeͭnqՊSOSQר"KBze&iԇ7FovzvN=Y?ŷ^7z,Zg 7 k'V41lܲq>S6 5"I0q|OjHȡ8lNS(G>m4ۮ^{?_Uk7ߩs6fp ށ뽬( j{oxvb5&A[жx.qtji|xhm$*"舮F̤1saf3ryqqYW(F&pئתMN?I%npiE+c s؜?uB1m0Fc#:;7db\t7HlIo]W:m}tz>1çӹh._(Kǽƅ#ZWYEM3IGw<ݽ>uWu Z`FD'"M-:j05ʘQ7mOf T?}f ^':ȶ{ %j|x:+ԙVk=N K y+X$ Z^g)3oSӹs1q&?in/=7YS)Gec F&C%' j{Π[G+k1@k4&MСm+W帯0X`[\ƝGjtk pmC1Ho;2{V|KXV:Ci ⶡ rx6{^Z4z%Zxʼ>đ?MTÕdaS?H2dʬ/$(mXPT61mf@Fruj''4NG\st8W>1=FLaүf軹~G=' $E:q\^LK";xzxz{zdnn2{ NDM7gc s}G"5 ƍ |~cGw߿ޟ'm_{b!p&G8(TH~q%։)=jNv@j H{h@lٟ{}Ƃ ui,SORXTQ0ݱ{<2mn$Jy=+6,rosN={GuUTIV a4bl6'z7/uUoW{ףzmZȩѧuN<,ɲ[xp" (}k'?2m]Gjup>R_(AAm1=ؼ:9&*4[Y̭KECH xjš$3p'зE؁kpEqE񅒮C'۵\ SI62TɂE8$j۳C;~;j%N7YX]փӔ9jOЛH zo{cG 09Eѭ׫ֽo!dk@NIo0 ݭhۆv|Rz<طo]3ҹs.ugOM/MZܻp1/u|ńB]ُom7tبS[DԤ5Y;Mj*uso>+e Ρ_>_N_8\PTKU %2Plek|ɍFɽYe n䥅4`䌥'̠(*M˦ )NV맔&'Pd<{, =}W{#qyw4HY IDATL jnčFItj0t:V֨U*eB^V^v3͡,2'!mS*I!|uJn\5}B44"جyŶ풜d`ʨr s_:d3 +k?&!9B}XQa0nĿٶ{oir4ߕy zU"hF0Gm do\uOqP3SKb7Sԏe ɾv&$sđKL<59]xn5;.O` !EēU~2~~_[tF !kpWO ww.WQi7DvգW:~T$'oz‹X/Eqv滈a |$.lɯ;=<&a;_GPWՏ~aAl#I=D2O`͠\ؿ-3HsJ:~rׅ{zE1)+pNAʒ-f= }xH8xq= C)(*0v_HTk)ñɵxՒA}~|>'Ebl9p:ū^u+@  M'+_|%~y# tbaqRRrR玝7k_ٹܔ1wfC:~;~xC1w؀a\2ww Dj'kVt&KHFv\Q&ukצ6=le׷~+W<\(uF@3piU찘>@@s5ע1,Bt4t$g"BϢ1R*sᔈ&&piL߱ͭ_}`[,.к3_8DQzfkV[x}g KO-qVY!/9sWpU\MIQ :u~}7蹝/Σy3}TPߩPYθ_?Ӯ0O7z痛? t8AĆ/*(Wmu鋠@&['7l^KǞET@o~uURݾWZ Iꪊ58I's1<0Z%/Ϳvf?>AQ]NCϡ|˯S6AG&'pˊl/n "md9vجgT>yRA}a)7x7HR&@|eISۇ?]7򐎞a#)Pf% ۊvؠv\?^{9p(ZI+JpE^o*RcZA^BMpoA #r:%AYhns)j!IVЁfWhELe6B4@ rE )#Bj읨=H4ݱX sFit@Ԧ~봒I#S &OA:bZRe])Ɩ.sCIH1V닚8[gyDMt︖'r #ab:=S$+9p4 OS#CCm s|uYܥP`ܕw = lLW"o 5iY0aLz6y$ [ǒt,6,?ZNW?%M#g{2 66T{s+60~Lⓝ/5pLȄ,QgM{cXĝp9\=h%'/]*^m7㬧8"4 L(C]\OYm,{:C, :ZYQU~:u\6Nz=aR_ EQNSQ{xg='h/λ~e MAmc")= kTܛl^cMPM˧7/ C4*JRTd_;pZtNaL&(qp*EyUEIf?> /^l{ߗ $ TjUYE￙r"S^YޱO݌-W|@@@h0U򢒢\aM#7^^C1AT*^K^ڤ٭·DExxxt:E"FvEz{OE/53>i/FEDEb.KJ(tҺh~2wXi*eiY鞃}Ǯʮo!04e&w1&`da3m>6r"\։aےes@C;d5l؂UEC;@ռ6lrn8r#&[kΉ:L9ޠo?YBof|RF2'NGsI?s̨)-,o$៽[SlRɓ_ʭ_ӵvKp1-YN*=Ū/2YkDC Adi&PpԺF;Ck[SBM6VzfEӔPvV*#eD4M#izF!@ƷH0ܝ_"{;tt@)*("(bq[geUvMXzVM9RG~tV X<55ԶL >Lm̊8 :<##YĖLpIw) G.k牑 F)E G|AdF|KI}d@ۖR4p1hPôlwԴo_G8-ONhV 7߰2McY0A'E_3NuD<}cNvG(nMv|1Ѳ¹{ùg:Ĝ5aApZ/ZUL|{ݑ;#gXfeq֞ҜZtLa"t:ǖ=fs;^`p W4 Ps:z׍`RLKe9:^X%dSFeꬩAXJJA~nfi$ +wTFbWlf,dEQ;9RF|s0(glQϓMˎ` -pSE0nCbŦډgE88|h2R_Ԝ$]/ Cj\o}c"0ks|-N]03(M=)1mN Κwo~G~֑-s ̜FmЋh\(-C80MJP{aըlۙfp0tӢ&BM/C016h(2.cv^T &DiXa  {hfﻦ[{pmݟaIʤX 4!HvtTݎBS Xf0bg9eqy;ߍ]&~c= X@ s9]ԃnӆۂ 'X5 > jXHX5+ "^5 + b}JaZ"!Ejzߝ,wmY\%n( sFJ uzwF @ص/F*HV.6r8x As*u{b$Ƕ"~\y=]}}Dv0Vj~b;3I^[Q:WbbQTͰb'ڈ+)nR<߹+XjgΧ( M*\Z#Nov>nV3 RxYNwʃ.Kq](R,n9*Tc8A%irPRTG )Cc]C6 墐 C"8ގd晖JwBaH4?ʗYy1gSYfGqaYuaԞ;jsO rHwj-bc$(V{7('>ki9ϩ$Sˉ)<r9<=F!=\Mw˹|c"el$PC8J!YNՍ%֧ۜuS[}]rL3SpNu/& 1 G֊Aؘܶ>솴eJf1ڏ.juSVm.4E8h{*{'SePj*DF:?a%9BQj1hWQ5YXU,2Kg=~`FRP~7kF3k7#ۆс2y>_ K2 I^@Ri87e1̈́܄*hNPJKn./)V2+ԦĝN2򂪋)a_.8co9(Ju}.{n},\ /cinzb>wifATlři17ʊ8K^omHQ]nIjPQqu)I9~T9inW?@֍9t{l?`4^*M,IW+n5"~C:S+3,|7QS*UGRf#Şbs=eCEZe'kM\G5C\$0xQ?]%wE DJaY}e?~sedq`cGb9sNhճ$XխTXpD5#NVX7l;Wɦ`]WmÍz:Z\_p즞Yo$OUHu[6հ1KeN-a fc|͐4ueHBJ]*&T8x~PN\Sˈ\5,%`G_~VJZ*KK˻_؝oe@SXچ5(j ȶLnS ))nelSRuʈ  wC9v4up,Wr K/XNM[ru}1."f:^vzK0zВ osןUNhu5Kg Ò^pr 9{7DY3Jb~΍J'8WhR_bira h`]0;A˂gVC, fZJBˆ9OuRS,6Kҋ2.#FAPNlvX٧KO[&ZS+ؕP)hlp4I.|^SA&{U?gʒh/UKeИhHk(f\_MA${e$YY(0Rn"VqJG䄪e['4')9Sn!۶ '=Amشy،<釔{ du޺祿ߥ`I(|7xq}$EVE}%+X's&WQ(d(π2o%?^wF-^'NNOFeSk%mv[sAA~xBjrB[|mKIZm"dX1Xz_^;PhٯFX;/ ˣ6s(MaeJbSnw!@Y]C86:JbseOje^iM] og4zκwg'U 1{ϢũOOiM\%_1|vI_fe m9ߥt~ԡ5%FkE$ nce kc8p`iI:=gSFDZl/jZiɚȭ$^o Enw XvN fX(`T5,-zꊚ7TR*Mǃ ,.(RG~px=3Ϝr ={T6w j+jmQM1ng(뇡>T=`z1iΨtՙ7INS ϒNL.kK6T,x ^C{ܼ4AQ>Sg"*d=%;YZ/wTuyrjM(Fe sVt­D)YyK!*sMK,Q =w(0X>ZC)Y'q&r  EHRtn31YU^/eBX,vnI ih dHE r{ݸ%:%ez*;1uᲆ*,Ӫ֋zUgp@OI\O|dpWU%R01_~%-(կkMh?ttٻ vgR`j NBٚߍ?v5{qSLJenx!˄)+okSln/6SD343jg{RzL~5ݶ_տKFk}ItMIR֬*הz]ZQ[qYD{jw1!P`NkjSڙNzqdI0VqPAfڋ0[bOTdM>2AEvW{ˠIՙT}ouNLyFTʥ]>V4N ;ٙҼRi؜(S^fKp1s=w z$<3HGXպs+X<ѢxیnlE$4"NDxщ=Rð;#ݲh|D;-%2wPtA\^,F#=K D:#ݩ|;oVH(螗aXȊƇofYu97EӚ%IֺzwK sbi lsx]v>Vu+p"v;WZ\+g}-gP$2bUS=ӽDuok~8x,-O2"xuRCt\np{QM.htDY$|tq3ncQ4E 7 $hJ+52%í{;VWOZuUDRN|6ښ?\׻Q;^JURɬ5 |[}LRLPH7!'&X5 . j"(#Z Ư9qvLWt 0*U sI%L# ]2{R_;?"\:1xIOdf(&5,%q U\ˆ(vi벿7v[(OD_BYCG 6H ~D<#z`#ܫON0;UEV?6y!ktVf0U|en؋Ֆ 8-4̉!^bQ$ƄZoTC2:Aɮ)JNԔ'?-K?G-wäJdC.ùLg`m}ػ&v9ĜhoY/1%]o* 2$%ГB#8R.Yy1չ¹ąp{,{f}ⁱjDM5z3)6Gw׏iQoIS4^8}Ej_`WvWW6>ٍ|o-DZMY]^126kh;n_p`n y=ort܉ e T&p*<"CUkWd)hql=Jp<:[DŽʑ9 sP4첉 n ]ö8k*SdL8[ @Z,4'hHT !ĝfX L{:<*? #BM&X%,zb^z+ `Ѵ%l Hrij PĻOS}.kˡիU:7Oُ .c鰧x|BQ-^^"vxlQ4ZY.ih2e)ˤfKEijڻWZĵ5iUeBQ4مX.jRwO0:7.д4݇Izfk?OJC묯b׬ H۵}hQM٦ɑ^놽kV6(K#"fs75n 5,V[l֊gg?{E}{OH!$;H&P;(CW *6@T,!$f7u$Mz3zuf!M^T/L,/aW;pSܠ)Yim:e턡@7D%#rۢk:wWζjڹvp7:'jYhS|d7*޸ISߗ"EHoncQ4zROӮ:(LAP%M}pP v0Uӻɋ҉ sJCJ %‰l:^_y]Z FWdA2dbB7]l+eQ _#t%LMGfe\>dr"qiY:]8AD:a@_L}9Eyh9Z f)Fo 3? s$Yf1uF2_0 D&Ƣhà7Ɗ"}Q|ݢ[M4 X8-Ŧ':~0 5du|{3O`eN;QTYQG7&V' D4мB""ӷXLl(k5҈9CiIu|ܤg} aC:Ku7}zIuXW"YkBe3fx~T-N5Ex;&]J\;}pOAoiz!Q?b:4ܶIػ#0H.Dh=LEx&V]ǵX[_ԫfב'[X/GpO5S#. GzTe an!!/wl./agYsD/+tݙK I;6${r qG4bp\KEeE%t<%}>b-40N$o'Bݱ c%ʨ:'0`]7V|/.,mn.}Yu #h~aF0Vhu;89͊ܺ|-v`lrpxt8G[(^䍻\`*c\̋҉ S&gÐzq;Қ( +7U^V1 .~|TWmOѶJQS.+pX$|x`vqK6'z:dme;9n:a >b8x7I-1k<*@g MX4##\.DX2 ǗrfbSΆhos4$V՛=q SYQxխ֙@_;`{Flb1-%jJJ\H`kj"0s%`C1˝vVKTwH_&z({L;Qh^3X'87>#ĪLsp"/A?[z 2_pCEqW &!KZrSZJGi^@ĩkM{ra0 q-\Q iOED[K_suRECu IDAT& bph;BVExPtf<)U4&JoߜC: t0zN7G=d{Q1x_W#^>xS Cc-҅)#cMX"'rWQ[7LH]lW*͌NC\^RqWz2MI,Ej u@Tih-ۑS([]vH},uu<& (u ,T#JPaҮ{YPygmUc`tR(?ۊWl&:g-^}G]N>Eu}yp>P:?)+o"7Vm~" ݘ,6]Jռ}Y[?|nkX% 1%b@I59T*pյ-m]#|\9#:u6a` j)<)TihL%1n .ޕ$Y"H!9rӾ9lwaNc#j2(͎\RO E@橊0ϨDbj4[N6Va=*9` GbJ^-hrU:>߽5iSր}mۄ/&mXJaaizhO"LZbg+ć$< sΖcWNO蛱9Ǧ7|y*A+V0o=J5 }St|ԦY'iHɺJAMlZlGCE~Y!um®;>5[ȬDJZU{GD;rBSqJYve %F=Q ANc[qEt`oI[]2!F5Ymte~Rd~5}A# &4lw}@pѮNtXHTɛW~SɘV{sMgL$P!h7jTGؿ(O(ˋASU~iFE3;_,u{^zZ-{TaAD5\0^q,Z^DR ZJlpY`M@jH7L߸ȺMSH5q ۇNյq'[\OXG=L 쉸0q>Ⱦz֤ToFIC5'0wk^O8:G]qTw%IJDnDoW=jM& b 5di}mƫe)$\ʭ'"7 !^Ƚ#7E-s>FƇ,"ؼ{WU$ʎbw0a^ʼnfF5:Tvd ]Giwj^\o}I#z> Etn&/5J{5]VNVY O "F\}$H߯8]?YL7OGCl6Y_O)|GҽQX4+i_{fgt`6a1JAEsզpȏy/NP܁ 8`-q(3{QGخٍp5Z=SkX-bŰEm;BY%"L{x#2a,Dp_11L#]Pm)5So0z\pݘya2qyR-92:u}35f_b-?O8 0 4Mss9H, Bk냤"BX&zq[ݓ6o"gdKؕSxElUZ20妶yQoqѿ;a}IA&ڄG׵XE%28 <,!g }V!jyiMp+HYUoMkK ״jz~ʨhz1?$ṰK \Nن QB޳uj׹Cĩ".5~)tiA6؃ݮYkxzYd(C9,MJ[l{kY| W${^k'jnti)*f׾Ӆ%̺d <=$k~_ rv4V2++CJ;6\>^ b轼tQ-0vbwo?B(A1ĉkbX ݊+zFE8ԫ phi,e_FF1A]s`rK$W#ZOL@',:ȮbU dT![v*!jzp{csng[WK76UԤn?sf.V۬w l~1Z2DXTc4pz\lH$tPEEGx8]6J='OL G7*6O~Aݐ6A'go5kUl$>]cTېE9犟bV$Enbzw}h1\!۴L%N/F)>.?gWS|vSW.p>Thc ]Ѧx!<o>zSzS'`޸h/v ;$oS:^4պq~>*gC(EMAnSQ4cY7SG"aGA޹vY,5``V}:ܳwz^=0AfV4Gxpw)_+,o=ZKͲ+H,*Yh54OAv՘g:z~q=Lߞ΁>qD`˜\]tY{d(<.+2V 2 \IOE6?v[(ۉqJ%t霍[3jU I|!ރnֹ%i2ךpkz.אnN\ XNNpqt)\Ͷ%.(l*]ixX%TioSB0coh0j514h9lTE3M kVY.\ϋC3riSmSIK̯J1 ޮEZRo#jjS"dpE%Bhn*W> DcXЖ?>lne5}h MO.6a`&n_' 򏐈< ~aY={uMB>! p>yB!0,0 (.F%vJ?yֆꊂ%O^9s-Vmz!Ȭ嫍&"[+KjCeN?l]4h!<}]s}Kv7|Q G!Q96f;:ms"!}Gg0ּi@ /LydmS&dCHޣGpY%'؄&phhޗ4{*1r; d-5o䗻)zMXsү[%S:e5WO7N1)b( C=B-뺺wu)ש>|Ria^!c\Vm(to]is7mC>wn窷c %SRKNaU wZGqAB\5 W*NY> Z\ m:VDo:?}z!FӉ*ROIo!V FN1?EimTuRB|g$|];.cG-%ܪvc填og%Ӈ\"% 6[(֨מUVًW4$Aکt`s/h/k==qRow -^&In?`T4J>9ˍ& 596feBHtY$jJy+Ǚ(\p_J |q̖ꮝ,u=/NTK'*@0?;ۢz8hIܳJAo=Bܔ!e׎lu-'JgoO`!R8ʶ}#2)M\o_zİ+ vNA3(0GxXy@\l3'Ԗc jcuY٬{Ŝj*~`UȴU֖5'i)nV .|=xk@SL%卦\ԐyJ#,GYM`pߢeVd o\u- ݦGk^K.|MC&߲OX:h̬K;;eP̬CӘ, "n]ظ΍I޷Y[./}Ӻc|_۟e[~;>f}n]aTJy ,h >0ҖktX}<+ޠ @C{878b/:Ux DOهia x-rx]%#3,g䗟;[ d_{h TTBņdzJ϶*,EK+C`jh'/@y%tl8a}c*w}`wg|q{tyw3$NHH\*~'%jLnEc|oM鱓>aW("Km7z'PYVG}g͇Oz6D'O?,_ʂIE{J ))bģؘPko +*uW%[m18k|?Gsbs3x( IDATa7~,ӯ9UHEER3`L#\pQZD7* U<3CBB ;4J-%u$OOїȭe8!D<&epo;[ҬȨn!=E0󇏮 tY\bgLJ:DGo&g}0(1.C"pz!Ւ(oDOT荑2dh0# QNGI=c:EZ^YG )h,oHpsUʟdg/ h=R uIRԥ%EMie-.ɞ~[ϩZ6>&hJ}Ճ90⃬"5<[SJnDBb!Anˮ2W!"Yb\L xlkREkPLlTh5QAkkÖ5TYSbɧ)RF|\KOi#9詿n Z]%Tr;q.ꇔ_QߚƧh޾|%J3^abX]=z6?/ODl C~0$LE^ܯ ?X ً x&? y-U/V'<=ﮄűLxyɶIl+1d>3U>[c1Fs(Y4RC` )"粝earY+Ӿܓ)ߧl4<5ԶdF:QJA#!Hy8L1v#ljY#Ƨ$W18|gKSJ&Ӊ`^ ;݀ u;qᶿ)3m"IU^R'xG%bC戛5EL{Y~d(.;֔iժwטdcWOuohܨ6JvӚe->&f=aȄ1x{N0(T'v}gT41_[rF y߬-,;?E?t4-t#GP-j!w(JD :=-,r=oNq E0N{U1N0i5jɺ OG ଉmSN. ^l"pr":Mn(!YqD&ד"U7=RTuξPQ>1C#_/r!tE*&[U.klʛK O۬VKWR6a}X 3v}Ȥ(]A„%SxM(sx? #L2tqX'\p0y Nqv{xBmlzr_1sQd Z4ʿjɿ؅{ xz4-#MWrr~ T xqBh=O8V:j^ik;#c0ZƊ>]OPk?b"0H /)~հti$XIX59:Aj| itu1~͡etu`TBb}bܑ :8v襑gu4!M ?8L!abxܷUEԊO/2' ϷQԳ9ńe0yլ%]o3GO!)?7Bӹ˿˼3Sʖ'/{Fl$4Կ]|c4Gk~mz*2eor2Yy)ah̋{ Z;5Z59VK*̈դ~CQvVQUZeO-vc"[ިRSGKaMxW;#k=i2dz[ihaghS-O:3sʻ~yƋV^'*,K,\I1צ>ݸkZEZDkRk13dB\2;{Y-ųS}s#*joIWJW‡>6Kk뙥ańtZ:sMl~2w[)]i7]@Ӓڍg4҈+u%d=%}œ~^N>5Sƙċ؎)*?A8\,O(c^j b2:P|-z#Ixg!KW206’,ZH*݅yq2FGT%j;E9Y9LiOJ r]ZH޺3Ռi ևH2OjW[)]zDJ0)Ӟ2;L<]F;G:~05}ǃI" Jjhl(.-޶s[viEGbkjk~}Zoo[\rieML=!,$L(2YM]햭߼k aX_]Ÿy]8Q:c 7Yjqή]g*hmt>2j\A`:#; ڔèAXh9\Oj!H@zbځQ, z #Ki Lh2+GtjTL%DSchv3f4s#\ CC:xrxOFn(XT}YQUxӽ$i!~11ao cƸLVKr_6SBR 0nEd‡5ؽdB3;EccEӫtGJt%rj*=x{\#;cVUiin/zsLJx@PYP<2oP~w#mN;`o^ҲNx]_#5ɽu;4uH#rr9NCp\&h*!4RGeE,6f}T/с }Q285MgyJ=7Ƭ>|M,f{ݑ4`zJṳֳW\Ȕ޶" 4VV4?E92 "?ii6~]Yi~r|X{ԿWksPm }_(>޲ φ&ĆCy:x `&}y*b: Pnwr)7WF'5 M IE.;'#c<}|L)Rсq>ױUb2d'Պn)xfOmj9žmf%` <.q!5CQQ‚<צ/鎖T,#epzN2j*vԕ4=αa< bo(wMf,17ܢ\Mmo0<ژ{ di&bz!$yT+Lya&wΫ-#~ɉ|5ЫE:̛&ǻ 8LtkUkr*`#ca0wMvsvXjiG.;q÷J##)s:TE>?%a`&e^yˣ|l]Ozr>H!dWOh-/*-qXni{i#W޳u˟7?շ9Xk?(!U=ڃ9iv/\Xu]jOa'%v(Z_W[KrGFLԴO8W0D]Ufzź@vw D0|C&NX`e 0_fJϼh{=wesD9a P^̥Fgo:uSE>l I1([::ܸrnBU,I=&-<C&Q@b[̧Y_OŮiC'2+ hˀ96~WN6įpd×Ϙs'?9!LFx_+ymݫFBRxg^~Vi}1@&%,$lwrO=pe;_B+bRSzR&DBIH=R8Y,-_y13-X 1CL3yL4f/`3V\*[\}; ۃ䘡bQΎg _9 Cf0Kg&?cD\X:8}n=5 ^[gu 9RBh̏D= fW==FUWT'i}EA`;v 5A,յcd[)ٖV|D &y. ]Ν1K}/ឫړ5 5#3.¼%bx7 QNMNI#JCG  "ERvc AV8vLWL]jP Żf$1e fVD@>HlW+3gzaӎW+Sh:I$y  {ʘI> -9mMH"Da᝜&Z,+?4/nPw@048r`E2D@͍MettDxx,U:g0So1<ޗ!5}/B!|8_!ПTYC.H1"ʛME,2l&ḁrhwgT \եxRH)8,Xb9B]謝N$̹&68*ԗX1<E3"jqx0_-{&šzagɒ6AGa8T \syiQ8VJ T(1( ךbCƋiNWuh N@(er!x${a2;U` reJO$#¹t<0Ef$,:Yb^$:] r[z-@@h?~v(ARz}m{͞_OauEKGr6=Q83t'Jߑ0z > 3iؓw>'V7M^( E^8AQľ{d=BB洨O*ӹF`w\ڔmm-@،sdsZ!*i|Ӹ~%عv0oS,25J>!^CR&q!x ,o8|fmQ?n1 =fFqͧ?"XZXWp/{sNha껗-Ke0l1l߿-+#˸^][]Pϣi?_???3|̆޽{s^YT*-*)bҿgcz8Ѧh+*-ꇯ~̓Ii_C(E[KkC$a(:gi7H,bx0fqiGzGDvte ōԗX?nMؿD%MI 2SP$y~RdQ`[!+A֖g guX/W]zP(,E* ,#4w覧~s4}qWOҠUt(b0\GiyJ p)on4PhN?o gtp^OK籱2cIcr ϕw<7>"֨9ens9b\DZew;?yg{(jjm} >vH_%jI+.22|GN/5ϚU4rR*G%0 mz'?|OM~q MٟJG|*Z6r]2Ʋ Iz5Z>D)].Ĥ{n\0dbL:*"10_Պֿ _^(",¸XU]a6Tcߞ+KmO&" @GRP{yR'3x'S1ғz'4UZS-DԗY~^ $1wΒ$? Q:a窥&o IDATt~hOρ@q6jԿom^?sl4͓dY[woQ LN^P登`R{ι?ݘK93o[# UjX(ˆ?߿< wwF%: XgTm-ɿ@X5< ݍ~c=JYQQƕ,ʚ/{ pmM ~fO=|CA߾Ͽܸ=~IŖ/&^y.2,bΟu3WԔs%Gn H_s>A\~\on `s⸑c=6ͣ?T) B-7n1fKmSՖ] n .5XTk pxߖ5f2X e4#[/װ98_,6,K샄.@d  rl~׷6gV;wʙO4)W4Y}l5(YQb :qE׽Uo<_huaOa?/}x_Q{+j_衳vޚ_,.]"3A3i4n褿zm[[RRUhU^&qMӟQ66Z1x vrnÔ-~q]Q,z|ko'&W5T݅#4w7?@,mhÛ3:ƸtxjI ,Ji5m'jJeWNuE9ǍKœʺ71c +@%Wڢa_zef;>n8LIc'e/Wg c)Uʥ+qM ۰О϶|kU%ݳg~iU)wLoLʘ9x'Μ`/ƛb QnVӽ Ei[O?` Y$15C0cc=[&_ f Wq>#ׅ$F c;O\j7 .f@'}墪0^qǪ!'VQۯ;฀ YsmY'I;>9WxVxp$'_Se\ rϸ||ފ~Zfnkk,6Ȅy-G>a HI7xϘuZ/?i)BP/NҬZ`pL:r݋\ᒵ_fL2䍦)R^x^tn޷ fw ~(8 wܩ~mo?_8gAfZn[_aXDh})?~Wzjqe}x.{5:T)"ogӼ-*ԅrf3]4g%ēp'~mm;u< x fkԒK"H:Gh^QZz _s̓ӵ:փ"| 4QݥKm&d1|c46j׭ZA@w F n|[7ΩZk[mEMFwGRz7z?|4{G${q:z!`Zl9`O a\o~;Jۛ'w{*-qėر$4yg_z<>*wН7=6BI%2z]c 9ϲ؞N$bAP#zrObs&<(!cbyh?}wsdOtʗrO״Y 1IH=ۓ qy6ȉ߷z7([j&Mވlq5mM6鑷-ҿs7=>yAe>K䵻)*ueݺu4Ě*Q}Kٙiʰa\s'}31A̲{vݵv:JR!Aƕ:ԕwwM"34:̅F, |u u\E5gO͙$>=uE5 ute|$Ȋ!&^ ݫss45,%i3ʹj . ^귿zcVrC&b?zs '&&vF~zܽ )LȚ7sR&۟Ya),s_?gW,x~Xk\&OƗX`mgguyGQvzKr'H= v bEAQTPAWz].y'Nٙ}fgv>o:kunoۿ|s7nLxwϋ3aVR2o_&,"nqts?~uMZ`6t=1'?T]_]~cf\)_$E|d+m J&=:_=ghZl4th;Q6 N{6 9p,kR_:*(iwyy^>^^^2o0k`O.z|"Bí1gϟ2q=۟B8vw ^O3kyNUۨlkxH<|nA ʢ6> fx)iDMphRRA4V1GQ7G2D8[~o+[*U)G]T=*j*g$"_6{%6ar$/O{ูS1@"2IBż8gCLJڰQľ÷%Ud fsxU)~ȶN]d[n6;ƫhpt8Sۻp" *':瞃DZyv.u]=Sd2k~m9T"u\yOL5W^1=|d޲9$&9>3KKRkWu`MuE:/ ( ǽ|}de[I,@+ :/Z [\& (fWU0l6{H"ZMV4-uTnC릎־-1uI,tܤO2ePjt&d1)U Gj$~y1'Y1=O>uXgjG06[* tH [e߆6&L@d$ [i}ߵ n0Eؾ 9p7o'(*/@ƶۣՂR u_SH zWJE;z_شeӢ7kf4c Tub25n%<5@^d4kaB}\dfWc{et.hK/z1W9dȼz5q9!'=Okq'Oz*#EޡQL˹-8^U[Һ4 f5;MjɹYfƋJrܨ]]"j[aF ٓ鄢&EYQJyBCɨ"BcI k*ߘgf \=}kbZ\' pN6$|d5OQmoHߨk"XͿqqH3eM{69JYo L0eŪ(6{I ]45>Waۺw^9ssi(=?ofvN?#ǬaKsQhU3&I[[Ohk]=zqG"*uH*9ԏ T9(^Zz26$5 wNґa$FsA꼢z *~Bw@t,6 `rި3_L6vYkp'^@H&EBQiygRdFZW;@8h@z G3^RZxI3~~\p9ƞ}X9y9[=gyh??q˩V@ΥCX JYȃGZ 2~ߪO똘QLC/5umo0iC"~n񴗿’"{ָeϢYPy uT*VzeU&j\־Y!_SݡES3gs_~&n?{2NN4Wѷ +󅮹Pb-9ldPho>e?G;wznH{~9oU7ۤMvOFERI<֟d^;{bR~㺵'tyovFբJ'*,MԌ;j!Ih6EͻZkQt^UM/|C} IPo`.:I6?a+SۿΟ1)f੿<)p9؈Qa6Tmy̮'X;{;~8΁~|Ƴصܗw~s;26f:z]6s\&)TM䟟fO>KDMޙrrnK/_9@w^>⏸G=fq ^<\SOxBmBN~|ڰv?vi!ȽO7MkЩ l;ĹTѥcJS^|g߉%..5k梕^~A JAeUm6"{>xxjnשּׂJ8. bF<t_vm]:Eݮ>o/-7ϛ1>u gkUkdHE_>wC~ب-Q+W \.7̼o&L|vp-50oAD|ys<Տ߻nkJܰ<qhQ[r̤K-okp5~ӂ2::j9-ufKMtt*8J;Sמu-wxibɒI)B+b?-עP=#_]vzS"B~{B *DV#rbUiꖼu1jLVE^ݫQm`Qǥ63IoƆMqUT]ܔ0NGmXV.ث}Rˉj=JPCs5YyZD =:)>ћ6{4إ1} &NK$I*X+Zröw{&-Gv"0pSV!31ށ•c+[a]չ2u!$͟;]NVJFShbtwlo~ :Q=ٜ؞EDS~NMk^zd UіӷʫK?lHqO{Q 8~VЉ*y5fKe\К'g8=Ӟ_h r]˂"b)=viu4N(y~Վڲye__qYpyMPJBb ) {x`M>|em50ѥɃH*yb/Awfxrvmamg>qbTgu0R5 "/6VT]o}|Б_|}}l8?|ʹԻ|E NJu\Ys̎o6.oП>^ J^7ʓ_tb>r?}Ka Gr} GDOkQ@aY_Q)/]Gz@%hnk7|}APojkU^ͫ-g.Z) $Je72 ؞<0VS_Sv^jӇ&6j8|/(=f:}EMngsx#p@K]Ev}OUU P{ՅIDXuC6 /}o#3g|<%r.Bq_!r_$^K^|*E2J!+a1Ͼ^T?jcMf=%e%.ϑ:/욟TW5J9 ˺(,?$K qzX7XmXE^e)zqU8 FIB|bۖQ Bt(W \=x._蕧s_fvc^HLՒCŵkdz6UWJsCe(Q1bEo&iOAcg sW(VJZ3Y^,= M e?>H",EZ|j91ݼE5YNRϹcMYd~;:/ViG{@vysK(:s ȿ[I὏RyK:[kQxĒw_Vf^ѳ֦YoI@aY]L/w| [15>@9- :^U[UWiW2Z^VŬoũSO2$J(?8g4Re{jg.Ɲ?/*#ɠ֪媺,_eQqC/0vT(CQdɫJOū-3U' :u}_Uю-:[EG_K$I4ʒkm̥ s{6P[ x~kS.SN[Γ#x2(_(ƨ+DkRz~vnpyNS>Q֩Nͥ?xE8x`]?^QzP@h2UՕ{7,޽mKKcbe>DoЫ5ښwXeoXƱ.JNJ\d2+99jCrΕe֊d2T)/_?gab]ye[RpءCG|dAңΒ{ϢRk+p* !*("n-L] :Ö> CmKBE٦FQC2+ &iOAm(>e9C=3X}*&zsuچ2Hy3ӚrU%g)< B?hrf BcGzL v9`,ϴJpM1>$0כzߠPrTqf/c%pc.cYK%B$'-I|OǑ=Zt$;õSc&rxH.d-UJܟcEsEZUaiGQSb!EMF A^.amg*mܳXf٪h=?X(#mLwz\yХJV#c/ө5srK+jUnI#9H2LfKRbN+z pNq17F)p#d E8{4UKEؙ윂JJ GB(tPv'r \^R)EȢBz%Et=jJ3{ڍ[C轤wdg畬"[4ͼV-|3{gyM'4N}B‡ d[34StnDpcW]k~:G 0DZ0ec8dcNes.i97XlYKCp;y뭷@g\T"1'X,ZUf5>a56` 825aRLE3;7)$9> (y̝lڂF/ ߞ<{Z˔ֿղU--?UXg}jtʀcgfRX0iӝk6v/qʳ4/~T?L*x8lyRwC~yuy>7k|+?m?ӺM5"*a4đ=<}kp:#O)Q3& q9RĊF_TG'D# RvQENUZ]/#diCzOurUfN* J'DŽxR2~{Fjå珏)ɸey+^e6Y.TT)jQ)1)ݣjzͅQ,~Ȩ2^%-]d_ĆcyЗ^.Vc*?MFv^@ CFk8Dž,yOfIZ#y%ZB&B;8hqLrmI3)u-nXR9OOτUԡtRfx |7-%Y&3冽r A( SYK`na}zDL\SR^ rp5 0_z$KSIۜ]8Lo, 絩.Bk6+HtQCAbqG liT4┅u.qa幂dtUP`^)UHm'D"NhC!{Δ}xY몷źڡN۰[B^- Z5{zG-S^Ts5h_+`ӓ!IMM&4,GK47ٽ5`.{}IFфIg`l]&D.o4uvIbI7+u9_lҾh&+2zY_BrKQɒ@o5%jb͸h VE"vb*.ܢ&}+jq=z=GL. F=uZ-Y%D./ȚB$WYэ#g1.[Cq y?2H1<熭GRM~ >})?3":D6}BΧ(ET-^-OfY5-O FK_g;3O@Eg.^y{5])L>~J$'FE&)$@p~>RDHK+~iʤfz&Sc#iNy.V(>?mN̥n0_}lRP,(ĥBʶݣy`$UcE|򛈚H5x/S^y7_Ԛ/9&A$eЇn>êUي+Xe> {'<7~, e 2ilS?t3quچu<}G]ܫ1B 9 ',-IJ':_?﵆3xv6+k8>x'"܌xmcv%5#*na kmWWP`{Fq įЇXK|as>4b"؏.wǔf=ӺM2]0@͂Ufz27MD'7Y ckͤQ+ʹ>,cQnNVG}J $@9[h 6PYj^ bB#>g}HR@ӅBhjESMpDDV/5M8l k,p]jt` PL,,1 C$"A^ yEUTKF-z%`=錦jʸ\lke %74MãY`4V4y }`Dݹ箖>3d@9JB$O>8߷IOۿW0sEMb_DHk&u3 uFsaIUnQMS=hViyaR&QnA3]w>At$3?.:ҩYЕQGXEM\4VZaKY A%KjH=IHԙ:(XcoZa#NWINp4 n5pdEMK0!@P} 2Am Ɉ`ADh2I\KOC\`;8qE4ڬ6/6oXgv4$iE~ }nq8EUWO>LOU2aoqM9_d7$@;9ztob7]3g6ltT cU4s n,ya\Ԙ\H&kr%Mc7x„ޒ^;˼ǡRJ%ycy kuݨhp ImwݰXl{뚜$  boeaF;0J0{S"}bF̺gb~y1v]m5Q[9(E"nRH:+Klczm%܂ԃ@BLCP+s"Qč|-Lg4SD61lȾ$$Ɔ9+(NjhNjT4wkU)=&qR"yxT+k*{Emz@ԈZM5IcAdy)ݣ3IB4Q O-5 # F=W)v4@" >H3eay2dsQ28#TEӤdCԨA 'En!M d1L~HJc4–:~ş kmYkScH}ޮesC;i֡OI*}Қ}^fv# JPe'-' kXg=:rv7,n7g9:$ёO?`tKWާȯ|IOcC fsϯV|thL\%:}$ߤ:~,p:vyRjс" wyRziuEܠ))195(@f l9k}}LX7zEDB IJUV<41R%EZ~N4m~"l e<"h2Јb0Ў !%^fټ-Yl`Ykn '^bn.3jRJBGMBg,,-Ӛn]cZ^k@Ǜg1eܦz媥a(ϧwe:抦]y^zlPPx ' OyPiwMU8$hI_bx@GӅQ4E)B똰䈡+ 5,BAMޥClLf5}Mo$q^ƕWLfr f6qj]gWᬒx;aKЉYy>H( k|ɼ0C TWӊA>nI꓊n(kCc>a8A8}Rt#;gPvk=5V)n&Kv-JZ-iZg8QF-%VZ<_ .'T㗎_IJ 7|=-wl|n<=OCZMpZ%l5L\Gƍ1lݺLEVybW˜am.ߓBX+/+R.Smɺ\f.<C0Xpf4";rv7,i5^*dy{:o:g̿Ž缾HJOCKjbFUhN=S_SǶypڥCWsKk̼ j:랡bl7WN2[36-7Z؍1F74|mɖ5Af5ܭPO_);3\ <[nRf ! 7L}O< wHͻO,/ͺާC6s#36ƏKmmmLjIrkUZ* }I<,`rV+,.Ӧ`jn[P~vR{.P*Dq9G˻0s2 jN "/J,3KmȶW46蠜N.Jľ^OtNH{<.RYcgM 7唈V,_.(:uYfW>q)(|Dъf>i m4,ViQc!nJ/PHkFʢX0E#QĦ57[D!#kN4;5IRU]0OTOi' 6;JoTӾg}16;:Wx1X&zu$O'҅Ne Yeo.+ c" לw5YTD9TdB2aTvY]j_ol/Fdys~=pp@?AQ{?!:Eo>x-sEN=e{_-y~<G&K0M($8=:%l%^GP? 7[~/T,7X$˜OqG%Wi,?EBy̦$Er7Z%%"4d첱 --R[# `vכ e4-2¦ ͚wda,i`&Qꂃ0jQAeo1xA o5Qe JOLrK695WEKnD5C9fʚڬT7ICo8*A$NU,ZciR#/ZOF"yn~{aeVӥe-)"BI0iuy9`4}l 1)=*,הV-tzFm% +%5!PG8UHz'E{Xčw< tԣZe-MIz\v1=;O?uh#Hkascko4~ڟʺ¡GG5ۅ78'clct"]t&Qp8"($($˶o}eq|f0w2ae5 tj CCi/I `;\#}Vl~|Z\,@iJ9:b 8 7Y~Srʌ_cvd5k[ߝ W4]`5e?Zl,Nȗxnw86|nV2u.-'?uM, aS"&0hC"KTkD3'" T2w\X_tdG Z'XV*Ci*ĻHì^XuυH#j166ZV4%fжi,,rH:?6 [nNש-&h4_$wÆ"/},gsyZe]}M?>s}6H 3! mԪbjxAARE?^7ynWu5kĐFQM(XP8\aI)-w*Ϻ2Tj4_wrxE=ZRO)Liv4K. e zv`K 1(K,b)8l|H?0@QR{~f9gYj]poT#p^GҜȕOIa[%\Y8&iUe,/f{],(hϰoqy ^QyAm +~#[qFwA2dKB8(¤3^3.o& \1Fr{Gm ֞q`Ga[k5lkj RJ#2+&Ǵ<4c:r*4yG؈B~vѨ32!jYR^bI"8ӓ%rKXxT>"vqb~WLf#91x_Lg&OYT4^mK*9Zz[*Xp'r +\$Y%הWֆٹprY=c)UCiTLs] Q3 UF6r >iDBϓ3jS-TԮ^1A*jZ9yD/GIni>m#d2;$U{KֲQA^UʟIf9kذW3U֪`206QKGFR1r'Ij^iH-)w>~Κ_NʲI[TT)w%qV2:KE\-q9m ^a#BaR1|aK^teE]8qDBiTXbZ.EMnOKb't`hƾDT@Cy{Qvw/89{Ѥǖ9MRY`A"{lnH ʚm?# Az ާ6fgCb:n0Ӟ_! db΢ٯ~oo/jxfb y`lIzw̞ ./ٴI*>Z11Ƕ,ܡ\s'"> =n">G}zE!<'3qE\B 2U!{E;7/%pmÅ+v[ChqU8^AI\p˗9<=-*|KLߝ+:cTGL,1]v*d7=d0Cbk8k!8N7V6T(DR]Te#K_;>̿ʤ`laNS^!sOFxHܙk <]fZ/Jwtܰ3DL7ϥ 8zh\{Vr@g4}' +,}Yc<롱z\\VtqRd :POC>!6Lf_Jh.ȡ3Dxhr &::nۑ,CN6t@O*(dp.G"t(#[$S:Ae^-:Uz6< dɲbΔpK*^ȷ?ڨXl.=!~Zכy?3z$Dʤg*wLB})z=&9=#QL*:uEp.Z\TW doxzM+,?:DVP._fJ 뼽 qplΤ֯۽ЕQ@%9.^cc7#yɉG;&C lxeM߻N#*H/˛%O:=bïXU+jEVJqx|oଗ?ovjɽ=ǨfA QTYwφ,*Wk(He\g<׷]iҬ?ihXҠ}EŘƂRn6_43HP4}Z~_Ncb$blN$D֮G"Ea֎QnԄddb@"")JFSFW"NhN۰N=HqwO^%yG$zD>TuZk:[oa6#Eĵ^wY9ILefoƤYʹ@O&pǻvtW"ﺜ\fҰY#x h{LKb¥G&+ME7$q֌D囎TF2FDrR¹TR#_:,ji AjOP ŢkƊ噎Y tDB{Fr]*n]ﻸ1V8^fN:cҐI'_;]|$0~1I(L45ޓaJW9^cFGP3xw7;m|ZP:GTXe:[? %.6*eng^-6a'ϣ̫6^"{ٻ6"Yd\Fgd`c{ZM>þذZIUdwwC5rP/}OdU)n?ptzo64oCRQEmaNtTa<4vrM}.y,;Ka{#!Bpy^+{˂4zڇ9iy$ `y110ko=CYH8ְ}uLlIQLuE<-"Z˖-ki '_Er\{72B[>`i?rDpbN)2Qol8/ \*0`׾ qy͹Sn>n*p5O)wMjVao/R@xy_]&$5??DA >|CjH`0)*+n7%iW~0aڀ5"xJ1ٗ Gl8px.#wYm-XN,ni! ˳J9Li[ W-^Ղ$Nusz܂xpa0Dߜ^7ߞs`cwUZE55/a/_I|,!.,EõrC9s%XA" N"\ոPvXqWgC9>,j} \Sk2ii(sbYyJMKL-95 Czaâ9-Tpuȴ$"H lt7ߞV2,e2N Fdbȩ4'qnd ŦcE%Qa$h bBQ~[`[@WXSN)ɹ,OӷgLX p-N-R/ϫ_rL\N};rf) $+;)>@Sm}x{·Ș< @ePQ*;Kl W\Jﮋv.D 6sPzd[[ %GqGD1fPڄ1u c[>+S4 6Wu{]}v_Q"Ok+?о\s48تSCѱ9G+KĂ:TrDn[[R5hKWDMxgIæ>{`zrx&.j(Z,,J~G/e5~zL^nnްWġZ61ye] f88n-}lۿxYt'e IuTMlmTDf?邗QCF}oL4{.{]zsVs5?|YvPGOlܔ糘,VSPX}o߻F{&xAL2cwYL֤au]fn{KĶ{O\8&=yH̯p;~kW֏/5W VtDo>qїlt[l3켌]_Pogܷ(QO=GlǚVi5=h6çQXskhEOP0,J,~hmn#?Wm&%6O-:wksvVwY`Pk&6/wvmwX叵lIæ{(BAhlq,bW]ZwYNz( e֏2'&[^uI$Jzr1ĝta4"jbEvEҮx"q EM:hҖ|dAEMPMPloئț|a IDAT[qGӐ)4׽^4~DQeAݚ_vb͌lܴO>Ҁ5u9*• K UرqGNݙL;~]ɢECAb wbop5jy[u6ןؚG6٥?H2L"Ǧ-o٥ODFGGEs{#Ǝ};_7}>~o)lټņ/;[7lk$ʶNp@{ffgޛ3\؂t)s>.@ߟ$$T*uD1OVd`Nq5Ĭh"$\$ϱa:̓d4ATλꅔjefnx*FcK='*򋲯9SU(bޡ"/6b>|=C{P}`Kzuyi/iƐyrj[XZ;ES~x1vO>k&c!hN' VW~^Qӏo],_71 3Ɲ*j?tD.m^6'\]Za۰yb0z.^>~A}^ݿϝWƶRe_oU[6س0b3H|2sb1ălKs_{x|tx6'O>ᗕKk֦mV{Z?H<E%XDw;`º?Ѯ/Jo'fe x"LjaۄyxE6jݽQnZtarxif=|`cBJ^dԕ|7FQp݈+z%R8-jpz19ѠImz4zQ- mҊZu7:C~QA8". p۶Jry=Z%8{L5L148Ԝ.fegy<|}?( /7h7%=2'999b/NWրH[ܶe[r47lϬ.tXLf=ǁ?%QaVN֌o]qѕ^/"G&\8׎_mPAXHKFҳGēOޠE;\2 t>Em _<"o wbNfuggԔKpC R &4aL 3(2ҝ=*̚%&:KfMqF=͌ffO~/[\^ʝ *le[tAQ͋\Oy"oJ1!g~xUA,sZM2m?'kP5-;ֶոߌmFE<4\<[)ɣޠ;?˚?u,[+nLnaΐ9f8nѐNb.]4 +r[ӬSjE%/<V6R 9-{t9J&8k1u9 waxLkҰQˮfEF);y'ou9&M&u6[A]ZM/KW5<*j[7 9jˋdԸ k; `{ݏߋؤ^1=ZL3(=GaٸEle[EϗpRhԐ׭Egq%J&ʜ_=N$̇q/9Y l·̊&W/57=7z̉[w㬊&ަΟɚO&͞Da! ^kbs¡o&&?SpN`"2".[h)If.20,2luyh|ӊؖ~]&-QܿnΦ]_S~zRz|uL '(Nu5GA\4u?6 Aj ɳ :T[8C1 eWpsղֱ=̊ZsX ͵g`/Õm߻ݚ f/Pޱ-1 ,\gmK>v/^K$Y'͙|;/^a}n~=Eo{1fN\um}S3>znX</oz%`O*Mݏc1qm3 ,z>qՋ?n mQ8cV^ƜoӏzPWj4])ڌ@rΥ='ve}n8[螜<5G(,&XhUgs-=m'vT5̟1/ Qx#o6f1}}F 5`|3ϵS4Uʊ>ln\Rk<.Oe4,ܼ\L&ް;˴Sdމ&Р={_^ze,*Je% t1oy7mU4Uugӛ#?iqVŘ?z{O}+Qu ~U O'Q i ޣkW<#_hM^s:V^4F?5!_6ᖃ? ztDKHܐ^m?n\%6Ey  s:yLhxa+ڲ"q['5f$ևlM2 vuek=R>`UTo\;5b}&N\UrlO `4X*"552L.W|7snD}NxpSʮo9hDHPKܼP(-sK<Tj;ċ4u]?pGQVѭm䷎9O\@/^ϫ#U.+G"@8BX\uԨQM4ynt:ի+,~'ǎ*. :򹛬ETy{[f'c2XִBXiMs9! +ɠ3rZ6q ;ۨH2~>o=;r++&3noX\7׸yG6F*JL," 8m Jg>l?fɍAui;2鵚ʇ<=c>lo(1 ~Y<_}F]퓉rppwp o@H|̉[wo5iS)z"P$y+vm_N L?}9*;|ިQ ҙ gfeg8<8 Ȝ7zđyKPpO@/j,Tkc51Co5hmeIO=|~ⷾj0u '9O{m0ټ&.$9k4&֣K`1JReꜪVn~3*jbsb/V?_𲼷-VES~ATJ-yo4:? 3+fNz#y*'0a1ab0dĥd_oQg8;i$)K1\VdcwN,/*̚6JL$Q*RiNNso0ocǂ:UTل}7H^^oB 14؁s6Z|R}mԦѕ ,CS)TrbSkZF zQ/U!61_}:Q6qW_nw4b"^x^Xl1ڹ.=ẹ($.0\؋pdIQk! Z#!Dq>%!NnZuoh#ߢ WwWz3@%hIpLv{ues9gE*9s튯Fasʌbaƭ?`C 'CDm5f6t~^*K*ۀ x. iO,[{k}h=/'aCQ/:%^M!E#69!-kĉ/ ZFGjrlL[/S?~2wS6VDM:QmФJ^0(1G/T/n!.3,ASPTDQr"&gWiL mqӿy7'yYN6Ox;>jWX8JQ?@m6vhsKO mD$6^Ƌ/S6d=bqw Fh$zVUi*JTɤ׍9bO꼷|anDMGEM QiT>_FlֱӳKOttunDօ C61e> 7`#l[AQ^xPsS$g[a܀)iY.:c,i]iPfrs>4 mlNvW&{*\ypa9o ըy.i2r ]i[aAgn\17۟>S(?7fעօU=t)N Dh}rBDMy xDʟ8)3U+l!ua"r(zL^@N1 51iAsFfMKs}d.ZeS2OUF5a*R/(N3U;1dۚb_/~["ݳ0EMOӓ4AΪiPo~bw޺7ώ0$AyMxns%ߍآaicYQuZEM|\}Ûx0Ƒ-v[bDB)ͷ+J}ڤMKS 4Jݳ{l^ڣ;7|؇Ԫ)w.pS(*Rs&B~ rx" /}z[kVvnc42- 8 IMfNo=en@$#YΦO2hڐY[kB |P)Ǧw^ݖEBc[gQ=GDI+Ğ+"0FO7 \GuY(&@NڮU;ֲm\ZMtl]f Y<9-S=. Ҩ^4R}`˟_ڳ?jI\V=^ljɍODD]'9SW0OS2|7)rxG>݉1梣JNZums T?}{6O`зlu;t=4FhƧ{h+/QUPrR-z +_oD͹yr/n"ԋj GnggHsA^, nwmQ7?6qMK,oZV6h[vIK6 r :cIZaj>,Md ٤dŌ'wWv\w0oQ}EWFZ }еCSNvlf-7C O pZ':\'fLQper"c1//2߹w mp)7g Gy"@cL2i+RM!#"UG[K,6& &")J{:JmF0jDbz#[׉=-SfHg$?%52T:xr ~۟+,޹^:;gЈ&5GңM},53"!Wפ]I!Íurufفy;";4!P*`ꩿ艝 wʿu TkM{ Fe|/s_o~7==,tfxpS:J=g0t|52E[>O۬EfZ9N)Xkhz9s73f'߿Zk>&1^]1Ud<"i(.g=֓g^|pd˞#P=uK7Xi9Yg_Ǡם{;Ô;239< 24cxe|˜ IDATS‡7δym,J2W]:4IV/_6O8iAOgq Ai4Y~ <O+xF-Й&m{M|osREƾstͳqc}xn>?qh-c}vF䲹}ɜVZeuWVm%W/]f/]@)jƏ/~_mU*hSOyV,>xK_`yoLljuF$&]* .I&Ԯ#R`:>kl%>ѹ Lַ?Vިg)A,&kM;%3'ZCQHSQc7`Ybn7(b I:f(?uɿih1iMʃ5If=50!׍; mj F.mJOV )nk?W}#UhSnԏ+ё1 _ܶaΎL/ϭ&[^PWT6.Uo֡D:0wFɈɟ4F=پMx\ډQVsvIyo* q6 S&ljWx 1܄kFnt*&j EӋ d6{ṣݶGf+z1" N{̾PFʒ$\Ƽ܇ 2]>me= PwQ/mRid;ok9fw.E>Y%o BrZ¶_RIJ~TnQ{d:h2i[Z!(,|}`GwnѴH7 3Ჹ ~ٳ)fiM۔?Pfn?{)։Gwh~u /Ei4؇y} ]zJ:.Ywz?g3U1Q<~:, x--\F>wGTE"GQSh{QJڇ_Z۫qkʤ(nyh2_q}iν!^w_OE_hm\Py:z3lFȓ0@}مYM&ymF`ytOFWe)FcMG4Sוba}Zӧa)W&զ,.{j]0SԏkA1Nȝb5K#_d)Ri<%9Ho_LQ."QUȤ7ڋ&$_ttad? * 4Gن׵fOy38m6 ZX2R`oN^qRyrgUr{z \-LWX:S0}!&}w_^׼QzZϾPչMGVp voG19{B-&gjYRFBQe5hܻ*ueQ Oc>T&O-"JO0)0Y4 0n7)1 ósIZۋ'=VWf +*IKN0_;eN~*x:@Nqw5%-q:SV/cׅmϔ=,i(nε!ac*~4˲ 3_5ݶbKQyBvY=s;?Et2JED^fWɋv^m5/?5a,\'a0ٶ$e?: dsժҼWOuP{ިU7[vYP۬@R^K&|Ǫ4:3`:EgžAݼn ՄbTԴu׶]zy|o/~ٕj5m;a|}[iuZUAQs`+SBlRvξZ4k1q.[$-Ӻ- ^/b #'L?ZǴ4sTʵN#yq!Zz8{\{Xoz)< /[2u )NZH& fxDjLy&32OtO4b=w&]Vw\;ʡr(0q&Ry&.W-ɐ7"!ORLDM8&U}MŔ*ڤN;bL}*\E_n"= s'q3V xjZ]SFPu쀉?f,P?vh;5 S7m*oEXG !#7UPKT-@;mImh*kϼ "NFeQt8:|W8\KU$yR@T,*\2ifEHkL_rI(~Q,=칭1l2&]WsfM 7ݜa8EL*c7^-L_hب2ijvܨaTzট xoY<#nP QPrUX3h۝-Y|K-֤GbToF&(w ;?Nmҕ҃-tۃ$uU&d iJ jK}5jvS2=ǚ9}Ζ?ݿuAn"ʶ9uAmp߿\seEMD,&Ag :FqoQ9;zwasΠ'R2O^:qv̝0yNU+qWl^,N;u5M1O$ADU%\;UrvN'zx2XlBt'\޻p碼Ӗ֏bs8ǷO0AVnIlUTX?w}>~_93}C8qz<.`(UJBVPTxέ ,`n5c֮}~6GKCZZqBmLx,YĹSN2Y٧`_d^ l.|XdD) FX^@Q5gol'.eɺ7nˣglnjdrG7.0<[bsu_0+c*J'6W>w A^{g>U:/tݎ[zCyb˼{uTQ'ď6Fuqt>QL#=q]ٵ&)j7`Pp1߀)0 B|ZU+q:"*0q=,(ML56x= LLBCxP8d&>#m:esq E(X\d*Bg#/(o{t)33pImBS3QnXUeҚL)j+›Mշb&p];F 0/Tg2F]|p2[ KuB/H1[?2} ȢrO7Nx7QdiS6[z-RIJ]t:= w|JYDJXAmQi“0<ר5ZtmX6LǷē[+,Ǣj@1r`= x/dipZ!>c9pC*gsTFmpfn3`H)]c.kfnۢWa7;R|bZL@{?tX~R"oJ6q;2YWaňx"TaVn>);[N J_GN1efZ 7!tj> ̠ISI bmU03&ML K)3-m=dUŪ)CXCǓ+ xuɠcIlQpi>%?Qe+j"#*z!+&;$Z"6$M94\SU Y? z0.K}oSYԤӁ &pSpe<¶ 5TM$M5I[f`AQt[1h-B<p# r9)g W涢&nG(]PөbX~bQIQ٦b2 qef3lCdNA/gn۰a0OnPhL oxJUFa&VBvXa('ʼn脆PX9Ӫh".贲Ȑ-aÅ&rti, bxXP^`$/n4GpʎXc59*_a`3[i:a|mxfioUJP HE5(BiWLp^I?*(oŰUin{ά?TQ7GLK2Llz`DiI4ihKn[MFx*atX)2aUd<突O J[&Ԓ]LӘpL/JO|-{f_TDKRywo fQܫ*pXXfgP]tK)m+|aToJmE4R'nYҒ:>^M@ _,biկѡ >޲Z7R)"eN̲&LiI9f/0?rG Q\"}N}ψarZV+&&/5` ˿YI6V.#)m(&%98Z;OWitQ7mX51ȶS"]:U8BN? [Ҟd>;{s:5ܼtSHgLHav&'fK! uGN:3ʟZ CIȚ{ O2IHƈĔpᬺx>BKGHɂ}RQ~hsy{6쿎W}SR2*+;Z E> &FUq8Q$KXέ1JH5ܼ[*_jiztT z}L_Pv!{!Q ߟKQi*CŢ&NuKUosKMft<;"8 9~]!p%7Q۸LLY=@nya+IXyadZ-– `qBi|/HԐS%e&!3JؚbSGu;`{NO:V{+ \kMd񥽝ɭREM6ۈ%zrON>*m ̥xTät;+&W|Tc,\<<>cz7ptƬh5Ms$Ry aQB˗"'N-Ȗao!#[Pf\cO¢ۯħtf*7 Ք+.g> sO\Z~7I;2b&<ْ3u&/cV2C_a]ECl?Nygϥ&FAُAC؄O@ ma4eQ=X(zԃF)re~̼LZ/T*b"7!QRgυP)TI'7PbhU p xI3 )K/zy |1)ma@_D"%{ӧ Bi}mRuU9 ^fe{NU~Y.;AzKFm a.ʜ< Mڷҟh"{W2N [fQ4qj,+9N嗓Vbayr7DW \9fL5?Quo'3DhdFu c U=:9׋it]^"3} P8ZΟFfPnrDJ%ԚG)ݹHMl+uz F^%@ ÷+WETĂ{l؍ػ&&1c$b/^QQ {qwJ{2);3{5Fa1MOYy`XBʨkN[$5:JySO2/6×ctJUխxFKߙ9c/#[bnE9o 萵+?x#ISgWr=<i">cYb=@mC}#.'1f%gV͛zYWVJ܈!EP_L[ETk/dtڂ'Qz&K1LV~,w";u qqA#T"` ʉ8Sne*fBZa7fQSR98[SLUT%AK1ʤBW_VGatZ=Ys*rBٹ2.NaE{R,JSڙSWJ[ϡfM_JY^Bq[uhB'[J0JDe[8\G<8CIK3Sl>'C˽}9M3r`ң%EʜINܺgqE9d9e  Ӕ"-ܴڇPca0i #UW~,2PmFw۰zjG8nUR*RHá2H5}?1|`q T}[=QP8KT2}*W: a}ݕkv6Mug mkg3ӑB=F%\Ty?ߝ"8IjVp\gM9NܙTS},0r{=CV eīRH|02#(s rLݓ .Lu,Osl?:OaI=yFbB@'gԹ2Y^RiX>{ n1nk%T)2pi`A8?Q :=`jdxvLem4B8ו ]SkP$9 rz9v,!q_ߕ)XN|up[:B߰w^Bn-xyW|.~3Jw/ 5UKK5R#f6fj\!aC$im`]ĝ 6mX7& r:TG Y c\uZIEa~tV0N&,\DHef1kyaN[cbMlMJlb1IT>Gn̚c7-2MatQ,fH{x5}޿@ݚM 𑲸#4D'*ãQdAݕP7fQ{&:yӵ.54\\nϕǗOX{E|yV漨u:ηoZԄ 8x.~2O gFV6!ke8mYP)hE"w&ל=XO>^^͹us7Tώ3t yBX D!d\@&>#%AҞ HȮYW[&KA|:[umZ`''3 uڿ`/FTf@^Mhՙpk}T4CwMa:V4 q :Q 桗 PiۂAJYH!DHϭQ#hQk`؊\|4nrbEZ!GAAQUO'ȷ||Fܣfa=+y,,P"bt]&;3'O׊I@^Sa 4zOlhYd;sүK$" YM6&̱N̰6*"M,/Q]JWZKuS2Mfq`G ,?f_>4XsQIi hפ je ?Ltxgz;ܸlR-)$9ڞ*/n@I}:r4ZU$?nnbŇ8o?jkwi*$I©N8cd!]HeTH S[U^Ñ7֏1FɗIi8ikݱ"2›w(e*Lոy!g}B"nt&K!)(<׏n~ X<ݽvJ32Qagu] ԣ(ۂ^d`"Z{F)WY*΄V0%~OU01违I53㘭6ZRghr`(}B;/Nd)Wݾj!/]!Ur(x:㦮 deѝ과(;MuvjXGu%i(#a0\>QtA9e"h./Fz?w @^lr|_ⳑc|_ϋAQAn:qO=^gP>w( 4yе]^|{$!4d  B7=ׯUɵٗjF@I]2SG4/*ͩ|7!/Gt&pZNyыot/jOq~)x>D鬌$]q<螑|F"qi:ڷ  r0X|PAy!Ea03ePP(3Uź=iPˀxUOZ׭`kՐD9oWnti*,i7)}Ix^SEoO#BCL~F%*L6r"W~Ytne8|F F&{4lXJ}j+ӪyBeԷNCDe8q)̛s6_ݍ@Fv2pwB7ըEs~kqo[*0>B~3Ӎ ~ 4Q: {=`<1ng0tSU< ].hlaw!^S{U0 -ʟIphbbjb؊(xbfn mJkWcbUkLY,,9b/Ū͸ȓ-ɮֳ,!=0Fŭ:΋VS97'10`2Ph" \اӘAc6|:*n֊&kC|8Y =2{mtFՊF)z 7- 9n_|bmTȊrS4ecجUU^WNH 3ܿԇZENj|ڰyFEAZImbtF.[ rSY=` x'R_>6"u6@-VpkĨX.;{v)Y*Y{i{.N&K j?ٍpj8Bf])'n #Iw|nfo>~zQEa/9ے*ӻ)g`{_(ٷnQbjhЮ9І]KyҢ~~i߯1>bU}j,ό Q_D5~%D-l!#$I)DU-G:e&, <jsBy 'բχu`r=bUQ>3G[zv2X>p^jҜc~fQB+G! 0Xp1E;Uw(\cd3v:66ǶM~?B%7 1'7o[sqvwUf|RuSIM8.灠"EM|WVn4է,~5ƼwM4bGĀ3PˀO3G?Ϧ6L5QXɕd.?r~9Kw4?nUi?E 3oӟ$[趕[{S Xk jl$:qu;1k|=C*_\z-g:K2%j;yB<'J#Tm,uL<Ag4*R$c=y䧘.^\6G)|ƾ i 9,u4ZMAq^rj_7s'6.~rw`hFn.=s'8<$Fg?RY|CZ c}Ûk!moQsԞ3okCLg(Ȑp"Aj1hH(9JR*޹wgǾ o4oݽu܏ƍ`QFeѢ ;pI8'/w|o.b{cupX,6nӰMC6qݵר!k ,J*(,x߇lٽUEq%ZOy.'5XQfrh/ǝRHDgǻj! j> b7^df|2jȲpILqv6c\4E"ϷmLysЧnZܱyg}dGG4M 4Ny;5Wh'cebV8oAMr7||8;,1RjB7+!vr*iwcΛͧLCqxplդmlNӖNղqe3W{C,"bZ4n5gZ4y~15t*Uf9x`)xbc FRZT>KhVFϽ+'ғT )_:Gub{U[68'ac8zt]QS=Ly 2=_d4T]kCiI&Ԡe7m`rZۦXSjT;k_jGiޤX^; xteJ IDATkl+q2mnjT?:)32ztJ~:^zvpqns*Yy`r rb1'ۼuv5o=Ny,%XTwz1Z$EéL+~ϛLA܃5֤7fֳK{וO)r*Ez[KšHh݈'Wkm&:eF/v oQ;atFSRX)ãb m+o{#I9ytwK%ڷjk= 떭ޜJ3`̀3ϸ| SwQqֿ-v͠jgXf]wRoㆍ+Rɯ~ճKRffsbTliv"b>q(;6L7n;DM+1ܯzu45@J:1jDM3o. o5[i黱W/aXv}vئPqLƗ ;";C*avaĄvMM6.˷ϩJ>WasT69F# |gySsn{%s-b!^w2 kMFx\=HEOn\eu o{UzXʸzViTgZ][0S2fC|/ 5{y?=XDjb$0G&뿚E}7KI_u9:D!-[6cˮΥgZ7io-j*BhXϱI{3ĕ#5H 2SF1J#4jsȠOq?YvV5+uZуa'抡hܺY|kCUmHU}C#lbs\;rғvUچo`]'v.O)ɦ^`ع(:0+LSlK=Q<=54+(Bߖ6w*y*(2 v9t+cٰz@4NQC1? ֛Yy}ٽ^vI/x*vu/ɡY!ɪ@r]Ƿ84fͯk׽svzC sl^P=sD7ҳpx_Ubp&5QeKy搞F 4Ȣp_8߸6 >[6|K[Idśnm5yy[GvC.F? t?K[L(E( 3b\wpGe?ܘכt#A%i~\## L6z'R"ښcLh2m8rƦ]!%FF` v£8璱g^Pm?Rv ֐m2T7n;=I)I/ɥk&CW :1&#Q^,;zEjY'Uڵ F_?v޹wg?3t0m#{`Ę>1ٷ14]\.IBdI%NJpC 7X61Ų\XEmjh8º]q^ͯ$"嗟aQh_Y>IN"R%[qcmd{F0>{Š~((Or9t&{Մ˕P 2 s/>W\nf?8US(D5BgگhH KA y+>9쎕LNj퇕JU0u`لL\49kc"[1eux1][ظ[QNӻ?}밡3~bbOæ^g1UjXPT0~S c4j'VzGh5=aÌQ/:gQP9;Ψ,._7`MKF}:Wɍ;7nܾ6-oռʧپwǁc^:8\AK{knW:I3&eeg{i?P[$TxuaLn3ʱoRԕDh*fuX/4=C1dS7U>K-9 JTZͽo;.AsOhr:z6E5<-QL~)XtVQOz=F3XQ׉\Ң?ċ=M+uU֐QOӔKfC@d *X73I}NQ6/cqAsY*.55Rtj۩iϋ/?x3Emp8./(7i{4nX7L&b'#> ڇD}Zi =Rƺb;¡?Y@i0'RRM^^yA^BÁ,U,`ZHrˋRl9:ؓM}ږ.f$H-O-_)%z_?SZjW*ҺarEr~QS _Q,-}kO)K:3]hP% j. 7ŧә=]͛2E_\\tWұUBLnU)+$6!JlF?ͳX+II,Z5xn ۭg+S^۱5,/r6MY2T=2L!RTkT*JjZN]rbgctmd, ;uՄ5mV9ֽbtPaת1:7Sm+\3FG+,;iT&,} TF셒q =JMܶ_]06WФˠk|kFuP5UC'ΞwI&IIKYJfk1Zj"J+xivm0"/N!t9ڄ&QeT41™l}NsNEEN#]m 벵/W4~ӴZEdE^apT! &}(4䷗Ma1&Րwdę|#Mφo<M!(]ITđ$M)m_UUk6"4$ow^ڂ =FGRhZ!R2DҞ]Z\1Q\uJU%l hbޔ-?*XYP^g5BZEk)mBw ,t;KE'QG~Y2u}V(^%&hˎb,(oȧ9Pz,ݞvM;9lck(F% X\MV\{ڼjeU=&|~$.8X4ũ9m(3^=}'yQWso54 X̗liBX"wlX*SNj$MyЌј%+h0!<$'4Ԭ2+ w 7OiV?f∌'Ʈ1% %Ts+X/T!{NBD\fV{V1L>mǺ,ӢRh wF]ܱimfYQ K#k?Y*C+rWG{3ʛhʫ/]NR;btixU_nZge4]?o5@E"5dQ~abi[ŗo$Ir?٣;th:ع ˧Lx\FIM#bR9:峓2/ s|E+ozx ɶ+*Ey!~u~'׈ШxzMzvց2nVwmճn`Cs9jq;܄y#YNnyٷf^#/hۚ)z&pv3x2S\4:yo%'+E6/kE6QbXniJEd6=̑T}dqjw0yf7YC`EZ_X3+%8gߴ&wquqn} u})/V@+~Ď))h:R5#da1W?P%,x݅-RUbvI 0sڸ\eԫڼ1Iwnĕl+V!x+V p BdKpy*.apb4NɢRf]wir2ݛa &8P=$  ". >([PxRhy`fEa~c ΜUjHZ01sTוɶ6Ģ#crNpڳ}=jyG)ݫVO5ida3JUD0MG5}iʋ:4<:r^▁LlSTQ~ Ckmj2ORu S%!HW& mNg9e%2<Bь["V>&huh[z&lgdB..C8@uB#gE!7q6hm();rNA s޵&+7v觍2uKfž%4<GOd0{Mz_ XbOصc򢦶1m˿Zg5 ӞzP j52 (0:]lYQfZ@C+kdoP⊃/0"6R(D^aqYc׷4KEĥ"MP̻3d ;ߚcq#/m`CG kdd!R9}F?ٹ;G,:}ek<&Kӏ %XcҪd}ٍiX<_vkxj9ѹqW~F0Eř㒁mVs K2IX@ţ:c185bchro-"VϜ4朾h*щwCI8Sz+Rs2Lt7a~ʏQ׶ ?8NS<9˧).Qm@N"aSM)-E\'v W,dmGW)ϑq {O-ՠjz(rW j]o, 95 gWIQ̕Q"CY8faSv ˽thHWT{3|^0];?kg1z8[LG)%(=UvJss+3ePP)QyyxTݴ\:xo@ icr-vreߠN2+bL)yKt=t郔}ϋV') f(OTv":ÓLyuنQڏdN^-fڦǸ&X_\x\S:jzvTs:M>üͱ;2f˺:Ng̛eM;y7%77 JI~cmZ'vEBpD i_+CsqcӀg IDATtQ_h4┛X>Dj{9fX꩐YWǁ6,MG}|U~۾Pcq+ݿNhVF[_{KeUSs?wZ΃)7>skê?;T}u0wzi[>9CQ\H5b*]r%o߽]֠K1ƦYMp#؉q F;Z_!%f(%}&Í,V4Zm[PwdKůMfpNuzg~}b0V-O4~b!0eΗ^4w!ꁡ(|d'Ҹé=Pb_{jB><,ڼ N ޱ؊-L]VFT9֊&KrÜ靷y>/emTʄK%1orX:K~>ur&V|(!7d0?5W;m{w5냅C2'{jGмI{V[]?eӒO=z#}"Y ~7\|whVsjj)e$N1?lw%WNVI k_'qƙ.{g %نMYw3l] .hֺ^^SJjfaam8;qD}Kȉ*rGzܲ![svi2LҒ$ :4X)$IZP$}=~2s\o,u] ld7IB9Wo:y9fdcbՖ9u#b";dy5\c+̚"EÂ'Zjys_lkV=! m8}d߮Σ34/_Pfn߾|#}mFbX)-t؃|07ZJ4 Iu[aD􏴤$5/_LI!uŸ|; 䆝9b$=mqZWLU>˫R '/hqK|)ƟPRJ"p<.ՙojdAtz4ESIWpºP̂y3ܼpw{ 4Hw#;c (%kbE-VYC;(_F%NdŇ\:]@N}.z5Sc+ ڂĀA,zay)#24^N1} 75eћN)76w δiۯs6d2])7[&*ԒJ͝POQF&1yݛpT+C|#-H% /7 Rz<}VXͽ[hdRp PwAQLSZ1,4dS,VC2i427C7՟ pyЉ*RCfdl+QUʢX"! 3J ɴB ,^x.51êYVٍ='VxpdxF)r RJž*i5|]%.Y5~ux>di,sL#;{`wgxByiY(+!3m~]a|l1;w~6n-.|ScpwAQ_)&ioo[{WPx>cur1\!-^~-K?:cŃX˓}k=w^C%hgwHCbZ!/˸Q}CšRQvpcgc\j+)jr f}d3Xg;liN7/vSotص|G,ŕJ6j_63GxHB`w:rqǦN: w7ĪԪzɳ>oMYPD9s,7>VU8p\#Suڊr6 ]:7m'j uRTGHB S0,'FݑˎbB:"ԏUʣrRJ9tYZ@W%9bzb`ø \q_ qckQL(3?s&-NcU_7zGCi1ezrQIf_jKvxA_:B=wCKlyfvh[FfAqV.yx6@WzUbW Luqᖋ8E|i(DMP)}=rL<(J_#'{JGߛQMa߯5k~ _Tr@ϗd?K6UaqkVne7ˮck_U[fWUl:𿫷/O5A'3a2FRdf\yveJ*ݦݥu@. rd$xtΕvΣ[Aeq\F)d҂?_;vFwڼP؃䊢V,I{pԁ >nk/Qʊ3X$*s~+|OHR.I{|䶕vy|^i݇/}Ƿ&ƝդA<뱹|!JJ.K SǝkuQƓm#pvc0rI079_.KعCՎz}=KҷGm;R~20oZ_^:;uG L\!$E7o\/Iϒ̉{dzr*e~aOzBjбf҃- $0D, TjL.;zV 6AjM )iNS<|Prhdsq誺`2&ǸM]HeiެJfyUC^ͦ}aVRjL8ݨ4 m߳zf-j2( !1KtVi{.0IF"~ӓ?rtOu׹}Å'o˓nEs?s*YEFHA/}j3[ {֓pUK;YGa|/K2eJnJF.s?3>uaWprSjwAiftԚ5W6YBܗ":%F{)ie'i`C9T>B^А3rWnr%Qsu~j9쎕=KvZ֯ƽ 6U9Іw # M}V|Lf P|9ʇ?И |0:/]["zNWӷD,dcdT4,|^ZىelJdyUQܥ/>~nHpqqLRY,vU,h{)EÅnG`]8rh K/N#p=mA Ĺ z(&Ż8b0]h ~W!'WWŕOI]("tK2:kVQP.$v.xg2bQ:Kv_I dE=H#mh$m5} w!(R"mRvrÆ4bTҠ\)x.{u=l)PZFe)%"2Ł DTT% wK/iJI[k>rwݻK_~_V&LoWeUQş f-&y&㐛:*Kw)fM{+4Hcjz$ @o&XJ)%;#H>`[ބW'w]VJo*6U%|p @ Ef X5Ej!dNZW.:JK9v˴y7||ay*jtPԧWi(5t؆9X:SF9TELY["qAP"mp3QWaЮ uUtPvѓDyF jr.pJExK iJ8C5q ?c9zwx?rDA,g뗩%E 5:j5j<R ;*L;I$>q %e%auٞ{r8;.^ =P}]V#V`%iM;*^Ad_Ppo*6)M7fc+TƉVB:L WfI+ZWh_vkMӫj'>ȭOi&z ؝9Y{>xG xY^cZHĒ)/D=l.Wo@w4& S՟,-)~ s2^#yx&e}&n;SQ Ǩ3ڨUl2?%Rۡ̓8lD1oPJU*ѤSۆ7M=H񫿔+O3 W/LCLmòt6OBKLhJvv(_p(`\cP*.<o͟u% AX/%]lin+zZGҪ2l7΃Jx~1^k7Fؾ/֯f7LG!6{eJMm5$l3<.R\jzaHiiII&Nswr 23v4ȹt@VTM'n3±/U94)L`Uaݪ0e~q8W$5-D.(_rЦIcƲ]V24Y|9:\uFIG4}hDS ?uGtOksn!_NJTY楩Ts,F:mX:raHG&v0 pVFb]4}o Yq'QpYw.dj<&)7S;~{;IjeeE%E%_uRbḐ%Qw ?0ǁ![ <$q&G]B2ʙU# ^ԧ|(>]Vxo.TAH>KL6BQ]cCIC䭅O\KUovTd`3)W2ާ .SZ RvRs}Mn {:ó (|"$"Da=X)~4W5j9ZQTm B𼣪+`I^QO;ջst6>(^:)IƮ+S'^:;XgԞb(aq&Eu\r= |^-"tѰ EaOk9ڿS9Fr\Tvc)2]+90JCʪuL;D%of+05-f ! h Xmٹz@?u 郚t,]gUXDv.G b#n'xJM(szٝ"ld!)g;P5]9enO/mJ6XշS{`".\{.=XSޏK-XYg/CtDgv:ucd0%R7w bbSV$M["~3M2aO sS֖ T@Hqg),,y#$9NiTY&-x,kVrb-oteMI)ITfVϗs2S,7;Gw8q[`I%WQR/Z&fXWst5tOLPbSn9{W%D>(nqVJsW IDATrُo*3_%| {ȉcPD j*L镰(+G7 w\:Ie8׷;YU5,)KCO6SjS8 @3AM8.AXZRy^ΣopWkTΫԾ TJ*"^g%:IE2IP!V*vhF OSΗ){pJwɚ/fv,U!Cw l[ƝW.H#\3C|4,O'gtv=$ub. %w6͐m歶<~`bw-LmP.q+_{X87`QW6R#IR+4YQ︧"8ɢZxTd+fUJ E4tlr}L(0G)(Kix+%Mcr5Z[c\>P C˷ Kh'O=(xI5- Y7gwu1uf\I?ޚO ?Չ`B.@3AM8. I #*p15 `B `E7 bE$I*JRuTał֢$|tZg]=Q4:~]l?alS&mM/:~mp|!>re|mhUrRftJ>.+!z.F˳ *6>SB9aOD5Qҧq#4E qG(^ ]E~eUs\ [|1M,_bkrJTՉ_NJ66jB8AZq?\/f}0]-F>8 pr+ 0H!k+F>ȟVYc}R# srG|󪍗U'3؈?&шyDLω?F,~I kT"2'}d-^f? PzkrC4e|(z/ C[Щ#Er7v  *''F-jϵh &r$6Y'"T:ʥ/:e's*](16ۗo#dqʣҚYA(-ΗpJu^c(!HD!U EJaD\,)2p H*_&;3 A K{%SjnJv*"RCzXⰹ;κreș6 <G8^~m6ISPߩD!.m,\D4Nux%i3/EH᱑ yәLq-]Є0nG&FI.O!aPre#s: 8fGsFNkNjҗvFqpQȝ^UhH&ʘp}1ŬR_ÎuFGo,ǖcaU?ڜ/6kSjU74"5: [ґ*;b jjNu k}s!6YAH^h@O)t,fQp7ES^|1f ]fX<`Qz"a.5Sb1EQOYWae+67dThѢ2)-]@C[>U0at5 OSNOxZK-fCn%6SeYv sN6lf ,[_=a*eZS~UR˷}[Kѓ8P_V:<v"98YQJ+H1;#P283er *S:kMy%/ܯ(Y5y&~ڐ\gw*"GmSxKU9%):P"lovnm}h$q*:.FPqkk~@1]e2,^W%CE`w;gX]PXۚ .F;xb \۷޶AٚY[D _8 d9Ŷl]tI?C6̡Wj*Bh v*oaY*'R+ w.F߇=e#7rk/$ܪ7T+c>I^(( D:z;s<5@s׻o.ze楁\`X_(oa~yzA/ls% k֗3/U05k}Yhl=bX<݂gM^FOx#th|޶ɕ/RQ$fu_yI<Jn\' OW,[%D,06uF F` "h-c(C0zXL+V6bRhw?;:`Mhw Nw { (/Q0lA`Q*lͳ= /4:2 RҦ ;b;l6ǹuБN0-}9_{sQg':AM^~Ify\AeUh*"'72b1Ϗ7ٖ#psv 1QaMvPe- 9oQi^^Qvԯ7ͩef<%YQUwq\BV'n5{amT9C-bQF,իug#hRfɆmVI]Mύc 14Ig6-y^ m؝oG;jR7/f yGvH66hpeE%*y빟{.Dnܖ#WkVPEED'5]) ί8uYxK+#Et/3 $״3.U06' {YW m0YYn^/]؆msZw/++jT5NB/SQdu}}8xtDA"C\eqƤ';׏jl ),. !{Rcl嫇B6i>A=Le  ΟYV="Ѥ(f_wsrosH{NlmrRcr捋?/},U]<bilm 4'IxlͬATMQ}';yGtKvP) O  .NK<C5b0;^ATAQY^G&|&4:݇ AMOaeϢ ~%NT=%晾㕫1` ]`;ZDt[>g{@4]؅.$%u}2y=C^C:ʂ|UU^\o B}Zڬ2ٰh2=%؎EQ4v5*]pvik%z}jNG4Qo`J9{͐H rsy;n&oUn1}.`l x'Q;WSW1O\9Xc\VzTqr3n8.g9~x}}(u wOTNxL;i ޞNR'_P**eדؽm;ċE-;9-z1ϏYS'2y$:gE}z  Ip-_|%)/LЫ@ OnxIH3M,Nw7͊oO5ߠP"HqiqAa}MC4vr$ W%%Y;yGa8|- &O9]>?y4V[.N.ck+E;m3׶-8U69W~<FWe_]}羲Fz}ez2ށoM_ǁ&[DXڏi8ln|=ͻydG@Ў7/XWqĔQG0RKŮ#\#B]6A^s >|H ^n]uOg:o})|zxzEww哇L( e ԶU墳-܄;,tGEeu.tCӋU+Nn"K~UgY/.)~xp~VAMmn٩/U(zpPׇ0Vg3=cgO}&n3wMP)}š)gD,~>~6hB>>ݚ/5Mk~i2|@31![߫ݍ|շ "4ZMaqQZm{[_.{|/o@(WKJO=rw=Ij 9ua̝N:Yoύ;&#޾#uLlysHس_{7rLP=I={t)o7l!rlT!᳾b }1o'OMmo߉h+iw%ͼe+ʩ)%bJn{|DPsrPe=|pf%.UkyyV*euok~]0{ANG$K !mcێ.L?5"XaT"  4vҙ[`ZOwO^&)0?浔)E$I(ڳKϗXfȬO!2,rx!jPln(JֱK. $K#qzxRA&gSԏ~+7ݜ=B)TA[{SHThs)rb8-_P-̻;aΈ}?psvG˟X^YmO/blO /)lD)UclB[?~[v,uN,.G ݇7ח rscޓ}RI0’"jxI%tk>gμ"u C~̼V(j zR^QT"4:N~iB!#zEOl׃'_f^֙]ktrWԦo-"I:HڨըV+dճol޶n5trT>k̖*y᭫`lFdX䠾#I;Kfݥg/x{Ok7/Z2#_yn}-;.l1Kaύ;1 3շrlј1ui?oT aԦ<ىlZt4/?مHCP=ٶw;^!}GtT N˱Y:nh5k6Y#fֲy^ ^{0vVq®s~nktiL*_/w]]|sg2U;@=B5=ulm^gn4h,lqH`~9lG5  0B^AX ۲o>@s#X,!t9&i)AMN/˩­),(1AMRezA(jſ\Sji?[!/D]:hBOSZv~J]̂t:-ܩÃZWcK!FT/g-']+e|Q2Ot~_ؿ|iZLICk&ʻ_ֻs3]4j*1eS*+o=wHӯ"@ k%eƐ~ s)?l\>8|-k>#:|.wVL5PMUeE%_„|C[[?#v%p1np¢Q k5}," \qOQjyVk\BوӇlo4bxm$IrH3(y+k+=6S#/ߘ&",znߞ}̻̅3[vns:îwDwiN>d m|XlWMyqm<&`:dTeXZYO+ HQ:aLp8A-z_7`8lJU+Tt:UwLb#pbP,T9LFX%@Jf<1ji֑$*w%,*,%Oj֦j) |%2~]PA&Vhdi?-r <a)Mg;8֋36<{`5rP#RyN%k˯Op(88jws M*jy(};51Nm]:-tXMu2AoNȤPVoZu\=ߟjԯJ S 舎6{|ERS.*-{@wkgowùߞȦVDxDu?~KLu/U"z Jꑚ:噣 7JTі^J֨"Pakgjp®}G?7J5ہzxG,|iuu"8l>4%ĉr[uZI%QBv9OtgLyxD`ir4SP>H#"Qhi1%VHHF)[VNV4X,8h^Xν ; s{T)'E%|EX8I^fhN|y"-LldN{ڸv_Sq ҨVY˔LcVmOr?ZzΡA3fL˖-Z|rm9֘{ %-Bc_G6%^?bR=\ c{Q?^z[hv%XuÃ"$cyIœV[ەIyAqsv?]m[rXq'_9_ERW/ڻv!@i^ĂR!,8+۾7?:FV5j;Q84*y h_z4EzVZҙuE?gg']Fh $.%_t{|wܥcaߵl̬k"%w{þmy{хɗ[FtsupswqrqvrvrrvuvWE Ѻoq;{uKx ?AՌ93s@O{y{po7Fi6I O8s2 aRLJwWR拫RO00ALXdfd| Po[!B uE-ulUɼIh(W vJ-yTUe뿋j~뤲XKz9Ep?.Baub܄gIc|m7ZΦ29OLD=&PH C} 7rtp(8>j6Htbpmw,|ae$>d,*0!:~e,:>탍&=',?J(3ݜ_/i}\; *04wa}:=;i:JD p|gԔO/v}}6=Oʑ{5Uq 06LZG^YW{]w`kFm!:>Mm5D@܀pSEP}g}g @9knY6O٥9B&ާCL-.)?i?Ziڒ JڗJ,!ØLM:nnw&jeϮ<==]]Oy:W˨njɯ;(uD?:N31",bAM>+yD Vh“%wAMh!!|뵩n* ⢁R,4]x"H=2g{;hŇ_>V(ho<{uC$?aSDvj/ּ6uG8?ٮJ(iΫ47լ>5?Oys9mN%͵" J܎lG3eę2u[Q^x*TTe9ǿ+K٦WRV)ДTtq$;O?k2ڐ*/2ٷnTA(۟ebgQ%%<Qccwi_ ٥YXMxU\aх]{}`Y\{:bToV%ws,$I}8oNŤiSԕmT9c~<ɔV^T2&Ka~IVܧʜWc:C>-f)n{Z acHHѭ%ՆlH%<.嘀 doٶ d,Υk\_7JY/Su 7ڼbQMvXa8^*N.yPV+;IL~g p$dj3ʔdߓՋ:BjmtO5R~/і߲t5j5ruKmjsbpԲ+95c,GAM8.8%FXb6EԎyZYe>Fڷ-h!rR^n1# >Q))_!/Z?1Q]P0AfAMIL͊ˊƾ34-;^*+^z?{tEONq;jeP-fiJj"_|#~  EylzNd}[>j1W(5Tr8%`tl΢w#B@D̅zl<~$-x\<ɦVIY)]|Et:}FRJ\!U߾{bULZS|uG9q:f96RPS}W/l1s΄ Ԕ>=80sk;F< ĵNDW0:]:RII%k}. QBC D%'`UpPgĠoo'TUUɳ2^i]mS$lOm IX5\'[q*1iWLwie/Aj5(~VuEdW&.'TXd9 qz$xU&SRsVS|RitNlU,H$p"C:ӟ.&gdEV W@t(7.Z"p]k꒺:025݇9RVGqy{lfCW=<(vz:n.yo~{-⽓N1c|?m.5=Fgl]J5⥑'}۷i)R *o!@/anf_, x_q*![s\0D(ih.JWV\ gLlr;SDSКK5WԶms#P~?q7u|Ǝ-#lO;XOH9R*fq9Vr`r4=Mهe_q5K ▃5 qBL:N L` 0+)Kpy1e7Ć|~55m:1rUAbCQaKLMh ? kReeNF12 {e9& (j16lEi'bS(^G$k˲֒l6I%*oivؤmާTĊ Cܚ*MLӥW9#xIfoTUD4Q$|tiG猧 &)RQ[r{HG5QDD"x`2oOR' z6a2NCm˕LqMq*UU.;zBʡ Y8̢/$0܂̺lQڴkW} PSm|W>as^ S0xجイEnZn|bv=_>.j5 &P$;ua1Jw<>VUyŬ[W[v ,"^ Zʪc;ټ{肫w\W/D\tbA a nG}XQR vvJ=CZf\'ށ̒n¸wbZYı«_p8u7îPy1?awGX Cm+~]QT 겼TIGN~:w7=oǿ,޷|/׏*D~PP= -'X,0$e\t4$םԑ=pDŽ6үh)W<:UUIqw:0ւB9aə->}ߙ))~&t)n'L Y.2by,`iu+et$YJ}n4Rln?tk/w~^>ΗɊ.N/QA]j Aٮ5ys#:F9|f&,~i#&Tl9m!ǎZ:w{re6*mxLBy*]nQ}P.;UJ%f Z8 حByf@돚 dkv0؝Aqa5͉_3Lѩmڎ' 'Rٻ]9xTjF|.xrYtp#Ipb=;;NÓW5l\SqoslW_\Q XhAM;/ j@<X!zMon(;R}xA[cS?S7; }VD?l>-^Cy f*4^D4*,dnξ(?`MN@ĉ @w; 7+-WIDND=Gs9޻/ѽ]R"1sчvDe|CJx?7zfc T" xnsi¨ ixܡFH ZlO?]mZq)NXP_vǴ2 m#Rg'ݿA&S[rlcT+ Ck=8Zrƹ[֕q}y}p|!".ڮ{a+˱<".4a E\q]7 Դӿq;_~Z-=P5?@{`~W@^tjtXp&8 .mR(Q:Zic-孿m ~f`q3"/z/pF_U~:q-g-yES[cP)],+/gX_.}a֨'g֫P}9zʨYW8I2IglotDlAVtB%H,?$9q{=_$ዽ$~!\NthˣUٺggv_:U(;+y'ya^{H趙œL  w͜N}P*Hb zD(3>۾gRtժr̉iujIo0H<=H*#2m53]*G_nSϫ՟Q@^4Q2e[qm*^6t=Bk]6U*I;I=A)}.,'b}-\ͯZi: ͯ;ڮgFRGhUTO2a9O1ӥRHi3WۏWҁ:UVAQ)@fcƟޗɯΪkf͉94_6 ge5gw&;SҠRV aWfVe 5Ҁc'Y:ǗZ~a}`.Wf<:!iVw1Eۏng _:Tn~̒EsŏQ:?x(EI,>qaWշb׉miy`ioNrܒ}vڦ%> iƻ}ɜnnz{â5uˏڇ')e;V+ߺ.Qb\>a(7V!s?K'׊n-~kٕzx70 eOЛ_Š&'Lv|י,͕z|oo_ɲSj±j']v+]̛:˛.6b\cuB)qǙk7ܮP ٥\F]v]s mլyГXfv9㹤C[W~a[-].@w=xpWs;Pz b߉"bxH\\t[nYsV|Δ9t|}ɚ3l߬Q3Ӫ%W[ӵ|_ZZB⅟4fvɳ?r1q[x%!֡HZ{JbZZ~2*z T*:i%T^Ed\ZIa7HS)S|AMGUŏU7iFYˬ9,<{ik@x({iY-,,Unh@cj]udmVUz(ܚFT!Թִ>weK&G]5=,f Ww /uu! GzrcMBI!"Z.zfXKtfsm0oR%PM.&~&#?|Gf gMP7INv"z }VXybnkwh:K9nc)Z<6&d֗驒-@OϴU')7f=ݯ6?y _Uyf8lQf=y\F.(9qaˬρO]fvzڹ#կ'zr\kUjeFN+'V[:A };3CѵR:o͖e[m8|*]S$ZF%Z ZŪI=ؼ["@썢N*N}fVPwS37Eo\3 .lፓ;~)q\(BvM+&MTA&6_/s1àqu9<F!*Z!UH Rn]Vmw (8GTk2mf[l-M%"PC*&}PS+Je|xZLݖ >ҏT1>#Z} R8ʇR#?z[ lfr\riGONT$)uE9˟ #m涜g\k*S#qIƕ8X3PN\%͋져Szu}6w+c39L;OJ\c }yQ$[q$%H=SR5IK9YꋣGX*G%8=&7r~7P*raG!Llf"̉9XXhԛ[dWa2]ZfeB skeqyT44 a2kAA7uA9H5Te\۱-20RyqF.=!#</\_]+y=hߪ=i|)ԗb[}P_3CoiU뚄 C76iM~Az._VxGD*ۈ8*WT›ij7ZECyZ Rnj=kXyQIK^DɉYZŠ:@Ald7;X͊ o"?ky,RW)0.ct%SߪV#w$EuA!x6x[n 7 ~,9aƄ2gZ-xErEG2iGNI}^# aFAM!O b[󵷍K(Vu z_, G=eL* * =FIjBOʠDsX bd#ԸqG.bxO9%ۜ+7i]36{f[M>GtGE2ttOZ_˲%ͤ \ *W{!ɔ#Xmx@uKtX^asY^/~-o(ZgNmAM@BrUD-5J\Slz8^M22۳ӌ-tqFq5_b؟UJ_jAM ~%>:P1%BT!"cr ]~Y՗֐'ZwECºG|*m9B ?rzornd|r"k5 T!Kaval;3l"IW͹|;xxtEy˼I2(S.?f"ki1iˠ,Ͼ " zԠf[{Ý jZP3`" =>dgp=i6c¬x()|hU.|B{MCICz QV*N돣/ h#Jru]$9C!3rt݀mZkx4 5/q/:ܽfS܎NuRLCg8Bs~Org9E?m*aIB0BRva"4#PĠr'KE/x5cCj/}ī; "yINBu[ׂ h 4 M] pRӤ,Te[½g-\/r %c<7xe (CI2}+=1lO748 Z}WOm$+r鸛?g )i\*_|TP4[-uB):=TNw! t IDATOIPSAy'MFUq[~=4^Pk * #ِ7^q7`w[FHۚeIƠ~Ax@<_IrG$肮ʷ|} -`!k3CXNJ0y7=̲FIzZ_CG漻YOk^Dgt9rOTglP,DZPE]L ƱpEN/l11չ.N^pHԑ]z ^s) !s5w-ﰫ;j>%>V?:8p|xs\m\o/+|@RS f#K::&90:dGz{AN7x;Idi@+Fۄ1PARW=C 75ԕ~AU qđ{<ⱊj Fk{Ee9vڳ5.&^h=4  *+sAMAMP^&pOdXjr&)Is[&2 'ʬHL1* h).5Q>dvWJxe {Ow@qnMǢ(V͔w574[' }ݥk8 EPwۏn۰WQhүHm&('c6bs4RA~U@oP4p^8ApoU u9BP\g0p_ㇰ}k*4\^K]0-")@6kǬqH "uB+1B!]CM}uؒMG3뇫3wdB$X5 ýq0cmpYRCcy[Jb#RѦ!x?Ӆ;+ )DLUSb !!T s ;C|zێ,zr3;ubJlxxb')dj0 -=b39ݶa)×0] sCS"_a{c0CXW,zrv2ˋh2CP=U5{Ԙ[k=c9="x`/to@@U Fjz^G.h(ߙ:ݨCP 'A)܆ wZ?r6 P@POi۴'ŕ) 7  &}W.O|v,zq90$΋vV}{F|NVHO:y僋+yHay;;Z[8Zvpe|MQL}b W )ygJV :OP/zD򽂆L&Vg܌tixlsb}qƒ[w.3iŬ]aAQ$I$AK Ft,-:$xF~Q7P.; I?;*v6Ϙ9ʈIb_׹#Rtm#: _$)吱4$EVe.'˙/F֫w;8yϚC,z66.]ӎ PC iR45T\Vǘk ;a/??o~ilYjZO_ۏl.W}2Wur^ʝy|kGc֪/䋸l.IZ6(Qꃿf Ň.txkx&>_!7'0YV\>xZ+˵U|kUwuZ9IJ i^Ynj۷N,=9Wl{xs}(o+E\=^սc`/.fs6ٙմҮA\8EQJ2#+ؙWfydĐϟIۭψ!#c 6GQ'?Nw []7~ws͛2sΡ<.~KٴuUK_T #uH0Ѭ~ 9'Wõ4'R2SCJj5y>xw^<'#O8lczmFNG#{zqI?~eC' x[R;mhtLU^M{Soscϼ ?Y#<쏏K/SΈh 1rDIl$*"N"۰l7?'>ӓkvhyZ5ldڊЀpkͥ'o-6{'5o1{(@q.N^Tf3/pG߷c;AjǙ*Ya*fNL8dZ)xxExk^c j eatMe55hۻ^.t"?qAM桚k&PV` ߪ]YwhVWyc)$ޱ^uwq1q0&*C$ "0C~1* Bk{֯[c>x}k6vjӑfMpjN|*e?:rBuMɴXLۻ*Bz mZ0c{؇~sM6^ɟNZz]zqeBB|85#+!NxnT:ʜ  k  H0ݪB{ tj2jyhVpHApfqƽ5iM#hq9.eۿ=jⰹJnFm,l~P)d X(q8i rK/FE+̺j&=*x_T:d-;Zz`?YLeVnZzNJ_w0y. jj۸W|#+"h.Zo'۝2og\fv9m?zQƢ(ZP[^^on%uOsޕS Gԥ% Ck#"2cwFeNpKJlsmo:aq{lLS{K JG$I=YcGO2~Fy#?ئ】{j۲ SUxDQUuwul{V]n[b`Ce﮽[7o=9m/]+zㅊE*zE\.φ?}HT(fצ]ş L*y|&>鹵@>P/('cFẍ́(*)'~0J-\\`~/b Ĕ0˙"GD6[~p&߈ftn܎3ޭ||v%=Bse݈/TkkuZHM80K%/*q~Ts}x?wiÙXEעzM٦WRvxr86+][Ҏm?0ae*N3i8nㅢ(\VPTR)$^~>~E;Vԟ6}Ic& ̝e5 7l>V9jΐ%At-x;T2^/P]7uV_Oӭwrjdz?z>|wrPJ|F1*^͉*.d mi&AMjjEVr S5wt+j4l)uQ;XE'uaP@mڑÅJXx9-SjjzS(~{)wiXiYRD>dt5;k_ԍgIUnݢ; \9s&aڽ&z%aufɜ3UĻ]¬5WW<ܵjJr9ՇB7IMG]/^a ۺfN, Ғx+)Swxfg>N75*nWFַ?~k?0\}F,i}n{Y)F ,|[|GkI1r ߘ` }W?u/Vq_t>>G)+e6 [sj5axAn_8x1T54=NVU͏R8݇|k#c[i(z2ҽ/w Ubhz&9)/? .Y~4qGLfM6?:+>.WVX0Dl|xt]v+˳]+7szvrOkol ʍ 4'*Nٙ?=qGOsHirnئCqs?;ea'S M Irz9{LhP9Ma;kuy-QJյu/gSw6O:c_H|?MOԪg3j*Z"ޣ61>@*ߞzז%u@R,ip$Vc@Tg.8V5G u{K0uG-tUC1w. 07' G'%%XӾfNkvOVpdW.[ṈG{4v&֍m{;,sjG|kZcԔM=v}}Gݸ}k8(Hl?+׵CwJ8e??׸Qɮ?_5F6w Vk/\bJe6d{0L_b"L[ax>ְKpI2b:(2P r?Ʃk{4E(S8QŅ/}NRBY50Z0^黏o_GrEw\d7P+G 9?7:d<}:["v՚܁3xM~ 7&0K̵n7Nx, H*nTdp-k kwP%K ~e j u‘-竏=pVM&#{0|y ׎c_;4\0Xn3w^," YU3Cq":Ǥ[GrƑ҇?[۴ ĴmEjUJ_h`T9:b&[߼߾?Cuw g7!O !OOdtA('N_u@SZ&NQ:lV(4U*)BԦa0Uo8Hw1PEhf-pFo)dAݾ(O>Ҥ_0]Q~cOS^n{")(BӨD IDATMTgonm;t|if<˼0nDX~ilKB#פ^"U藸gkAL/b_itJ"OZ*n0Rf\7E*!Od`Bn jTQk.2 EQ,朐fX/0yh3.HT"\ G9*n,?@(ih2cbdjʖ2ulR?I$72 . j*n1PPI{`+d& Ѧxl68jr!]ISb !xP\CK*"?inZ"LNugH1|y *%.|j{φ sl*ws:}mG49g;_6 VPcO,(;; /+j4*q|҃sUlHh[s8=bO@7x<+]4CiPOힵc'6':}Ĵ8<@/\:̼-!^i}G7&8wӕgQF$ATk%t\i; uHMN/F4g>>iŭߜ(({xұ5ϟ_dN=u8v4)={G|xu$(_Ϟpp }gPn՘;2:(g1{="d2q,P(!Bɉ*9yOU(g ɳF"}y57ާRcqOI[t_[j_ֳC{#YcSK-tie߃NU..-RQVo9TIJ>v/ӸFx]:%<kPɯNgɶs輤|E^QB:J^׏'!\7op6Bۍ. oH10٨Ȥ38%<}v`w+uek ?HT=[f5D59c _7Uw)c;{ak- 5c,K{GIO}؜w-McrFGKJ;2 zs\@{LqGW<) VwY݂Vܘ@f7nx14:_W\Hn{+5j'-0l_ <^zOmV]hQ*?s -|UqY_^u-OaW=g>zUIl\)d-˚â+b:0`ވofnlo^ba}ֺܸ̭ZN F6q"hc-Pj/1&+N▥+rVoڡ傦Ө?s}ۢP4-s@ѯ3v>Ljil@E揨Fuw3'.\PZ""/%^*L.\=%.i縩22b zo{zspUGpm+H_Jw9'm(g/Kb.̤ e'?ڬ'W"JVe!FO-W 23s+^%f)SX-%U$%i і!J2KlDL)"6BoNFQ"L$Hy0[LdnSAkBP#N˱?"]_V-&?bk˩R}XyIZτ]("/=ֵ0WԲjʠW4*[_F-)}V+ªp+ R/T\-wBoc_g9Kϒmc=WI8pЀZ1<(9]ܪo9z^T; THaVAư:1& ",#g9_<"l n~ݬ81{wRV2˭MV]-dǧi5XHMGq&K;ׇGq٩__=~ +1b*ͱ{lGz `Fjr$xE?fK{*L>jF_X Ysz7^]z5m(OpX2|ZQ=»[L%[M!',W2IH3*ĎISr}u̬c2ℿl wbY$X}U[C(G hLqus#PNWAyXK6"l'"mJ$`PyQG_Wl}F=qϜ zuYIqR}и[&0%,?geF( c}G,<#5oF1a-_͕m ϊE4 LI-RФ%NFF`{uj5  @q TՃa%.Jyaء y)tpXQ1hL)n)t/SV,݄jwdN.@(+Y_ K'cTdnsTC"ӵB6cE]IlDBޕr )P_z `R+yE3chmfc#E7OLڜ~%ZEA7H\aM{! zτ5?ztzlQ*-9 N\*WIQijFtTVXOO'fvkߜa7F9w|w}GZٺ( E?Y>޴mˈ(a1M,]]́+l.߲wJE PT/!.U>4'ЯxÛV^ޟŷ?<6:?jQ%7C`d{s8}е ^'zs3&׳_pqMZds.uGql%> I}MeƳ wu*1ePoDPFq!d-7uEiyޥ_rʳW'-@ 幗-6АJ AqIs]q\SPkmvRe iMf\ϖnNI3~u(yk5)>F:.h捿WxP Ť{*|odOak(0%AMZ&A*&S7+qiwX{6?xwxhuƟd2ѵ>쵺cx=W%NN8]VGOCtNvݪcݿ(`ky אW(-RSOʫ)|c1~o!RsޤtqxԤZOAHBpoG띨"F0=?{5u=(̲7Pd(TDYʖ).7t@)-{/7zw:^򒗗qG#^8A]p0[5q. 7Y!LAPS}4)U$fEg*ʚuk3&_/4ĐmLL0].j9&+YͲi}C#grWK:>Pv? -qv Rۣm@miJ,;&p_T5ZSKLƈ& ½ǩV"(w lJ9-Oz,q5j:U'/} a#S:OK54y5C& 1UV?n_sZDӻhAj^fCn(>ϭHۍ3̑`ڔUx$= 7x)'44f"Iۆ2+n@Wz$gZ_*#8xH7:iaG!!" I@ȕ"GI )9*ii0'ӂ8(h3"NqS@ 8x#]LXS1 ,RWb˪rpۡ-Oic ,v]`]9jZY5+V"tyG.?X3Lq]yѧv>ǩOeg&{Յ k%߹rcHЊӸwwCޓt ;NA֣0e2y *^JJ>,jfv 51֫ËˈSlyPwl I@UaI%7P(ڂh6\XAMX3zmku!󡀍nt\Q/M,V{ƙ6RҊѧcT}^c]I0s S[v']/ X **ui鏛;bqh{|GT#Ù(5ZTUTkQ|JK`> jAfN9fhcZG#'e 9-Ι[.JiP݈9@=IЎtݿDFo?y{*=5=Nt,(:s{9<6+d[X8{;ضԜç_ؼX6z;W{e3{tƘƴbPMS97mmHH{}kd<ϯ6cC;d/Y mFe&D% Rm"^< Z (V/+9ΘpIjE%wVdS_nǸqCl'^ .W߸s.䷸nvnv!ﴬ'"ZGT2Ԩ*RCMԖ_5<+(hكmSߒ ҸbUeޘ.WLRD(գH],7;Wr C 'KZ0S9LpY3|եoESf]ҷBNmRHZ4>6"(ػm%sVŤ.b &DyVͶbρzXNRsԦ(1Yu'FldJu):ϕf̰i6Qi:X}'>1=yjut]| S_mf8Uݹ -ҁ(?K@f)nԆUynތ]1q;KSCPS#UL ݝiٍ38Ǻ0UBnUE={OBܓ_p&ZcSw #i}qgCjucL_9q*wn.t^6]o؏dgy^+ا󀹓lܻqۊ} ھR6%ʃtY}r: 4O) dgW:S9pe6>ktE#.>c~~!97zq%OC&ǜk5eN R+ MwU[DHju3r(D;cyV:]=ڰ0' mƶr@+} Ns`uuoWCO|/8 +_}UAv:zc'UfW'0isL}a͸<=:ͱ:V}u4k;_ъ1eR.[]OG^ކD2.^K 4^0AS=V.+! NJbBS^S VOݹ>ݘCq](5j*I`3:bθ*\ lan|4^4}Ϊܒ98퐎 YO[h-ܼi"|>_6Ev{ֻVjH(]":E9)Xu*v@gIy Muڲ;8r{4Ftdh[ VFd1뱦I)Un Ql]:jX"flf]5ٽ:z`wDx;Pҷ/c|Tb3 18krl4Xt^jN$ TMiW솱Pf_yPihVe/4"}ԟ:IoT'<卛e, # ÷աAQxG6t~g~*=k?1qNx&kqVX6a4d=P&W9+ZF IDATzgJHniVC%;t93scF:KE9iTMwˎ#g-C !@I7إoAF̯{nj˕Ke$\7 odwŸ/ ;zd2jVUhsOH6[7- \z2֪uׁ3:,4;É 3l/ tTv+1l}yfߎxP#%V`<뉻Du^LGZhuK7z+o1q+/_r:չծu~ZF:d}t_76]P! Ll@Wk"U_@zjkLܒ#>X XN$X.}N{9@sg˶(UZAja'-##^W+M7(^yO7|lϟwezCVZe;aC[濹AئKf&FwOrdQ(u:]BN8{,Ӎɔǜ\mcAP4ώ=J=<~Xlsm/qNn6/yU<[,lʂv۟29<zT;>o{FOj嵅Sy~<.OToo7ؼnZ*yxO$k*g::~Ħ_yɚ5me>^>K{{ڻZό>` 4=yR Kde%r#ᨽwy EѤͳA sZ`1xl-*Snf Y~1)k.kţN /&-+u LM^8fڣU&$߻2W37&;hd1lV//LI[uy~inSm]+gp:ꅠ(&Y}~M^A=F 75eⲃͷW3~w‚ɶOKmY^x5AQoze()eRaQ΃qPq ԗUKA,ճ3.kZ5գ 9`F}LGw|).H y`׀<ЁCm*VAM8wXE~B +UNL|wbΘBc(&IsO?EG}COre2s[vn9u&UCWWj)xڥyK>WӝzLD"3!;BPuA!,VʤNbzVRmR.]?- # QDyS.xVkPWmǧIj1L/|0Th_!IU$SU Z*)9s-PF?n@sAM4UK/\74M{8sc5gNȣVcۚy7>ǔiqO䖧Naޮ?w}޴mZD8XL6U\!-̹zd6W2oǃz 䰹ҨR9iz*'Ν*$pq*R"K^^eQn)JzeEQR^Zt쟛?d>6b/b%œljn]tˎ5Ltl{YK|C#\Ir(ѽ3 QC l7t]gvN l0jKRTTrRvh+_=?k}Nn %K n~}h.^8gNҒ8.IQIYݸ[~qclz9 "ZG898Yl,9%yO_9_RN=A R]vV4K\r©7'7\/4$0щ+ TRT\tk6wXo` 4! KyINӸ+Fgu0^R /BhS:R稤D ZM"̅"uJf.2:51|Bx(E˯Q`(ɷސ$y)~_x# ;tJޙajIM}fV*+WWzͶb P]5u=v@gK$!m jRܳ j"T2؄i#{t65QeWlR*2S <>> PP?]"vm,S29aԔg+WZD!5/Q4bOo+>:Whb|})/~M\*%)ŕ*e2a".jRRJ#5rk9:o*IL{Gڰ8Shge\MPTIrHcNhՒSX~F= ~g 5ԩק}jsn7RC= ^<=54v2n715Mx{цQ??xj&ދ]zõPI&)a͹ T5iib 4竒K5ubP:¨Y^B\J-Br-WsINh$ a#0vԃ5[1}[N7<Vݙ[ǻ9a#XMDlL;>bz\m ^n&I}N|wq;8̞#g`(c;jٸ) n7If[ϜmД eZI"ڣOG؈#&h<+tw3WnXS9q֣bBB>3-&Q(_sMjY$S* CXWd}"m.7>ޔ떌)%Z/v7toƻԔlc"Yf;,MѰo0:Qh< G9HW/z'qB> qU3C@&\EcD  *nhOU8C5զ 1j^Ÿ#T(!E{RYQڇ>8(k`zhI7μ)$vP<\MU-D׳!9y MmxAd.{7mvߺ;9zw ʘCE|r32I[jkuD 鰄Cۅy//yy; Ͷbb(5WvM]ȸ-FW!43gH|@M*s׾I*K^ JCiUzEX]){tF2!?*U?JªqG@Z{ٞ]\)#PZ}$L;F1dR>bP&қMrBcg*A ǩ$)fo10֏*&$4>_SiԑN][B?<VHÓғgXviM,)2qQX(L62*_6|)\&Ѵjȟo*2J%R"Tw讂j\5=8q/_cB]vn-Ϗ+17n$!IiBq:2ǗgeĶG \}4+/r'8,ØH8/Fa~tfp!4RAO!;K =n˵KЙ-w>ʓXrǙ.S~Iƅy扣6y1v/_ZwZǥs?j!B7[7GQۼ"e\?]0cѳ܍mŞ/'|qM>K?|D QhyPO?P2ݷ,]wmiR`Y$K|E|plȚ_/uhXw쉶Ѝ[8 a3mu sUyGozn∆{3=CmEP#+0,"DVAӑ]X+Z^[]8E7U~ܗ[&e"*DD D"͟pVW-=Uk--Dhs]p`S>3rd^hNY o1Pf #ZGfUBΞU9)s=CdƼ_5ASoSuXr]w$4™g4e)D:ڊHIpz;ޫfh8y\&NL :Ѫƛ(Rxx Kq[:̗Nfq. ;mY"$V5,^6"] D*GdOt$yZA$)ҭ}?m< AhbY%L9%BxJ%A}B}یb]Ŧ~{A 9kut %"~%7TrT| [mh2]uU%U.>݄˄(T-?QZJ\- NKFU c>.%~3 L_&Awjh.fTj)1akTقۓ~:X!'9Nh lMh~Lı@Q4REq])=UDç8O.o)e:ࣣ[0VUQc PWZ*gbd((7/wAB=L/ȧ}__NJ臑:DcD4uЬ $IB+S۳s6(:zADi'3s)[900ǘ9\zq"6Wh^\"ijXJ \Ucbٲe5*7Њ+KW?sZa Ì hNm>Pʼ RW"yLQi̬V =2o@W{Bk] 6lnorUђ:R$WUrht\E8Tu-6ST}PJɉ0?ѻ%pbcM0ҚՐE )]צ*啤qNPTakNH#:opͥ(tEݣͶb!#+UՔ$ؖ響 dm*%=ԏ~ ;VG\<jғxN $}0v4E*o 殄H[6 류GXߞ܆0N\e- {ogXk<[뱕1fZatD זnӷC魿n]j *AMx>q\[A;|8:A;AM:y9 \ \!JJ^צ{{uq'C6+6bwը (;%쁍\0rA?p ĥcܶ2?k8ӝ#b&I4dwOvxnƯGF?v9X0a|oغ-P ʄE%8(L M wAP@VSdՍurp3峼Œ+T9K='=agMӯ[.#ÜݿmCLp(v7-jL҂,p qKܿuFQ}1jERV(QLcGiz>qmbZ衞-q08(k#B')-D쎳8&"`k^;yu6erq2$,W1Ϟʠg{c5Sb:1S~|wGzߩCȁfn^=4v$z/ɫ+/nX&Z8~ȁa{ AM%.H?k]q歹 sjURc[n;􍏩wH*GD X1'~}w؁+jܘptjF Gz6͓#(řIۗLY/spt}n(cDSVQmXLZ-,]{\P* W/h(9Dxvޟ13>ώ7maHgGWΒY~{"C͌j=PQ}kZcWò7m􌷗O/ͥr>O~l$’'}gMS5`a3`(=D~-+OlF573눦Œ ~eWf PU%¢_MO\}Q+FRQ1Kxe6ͥ)WT"s#?Fy*5WWP]Zu$?+>c@ݳGb/4Iׯ Φ#%}|YITX9P7fLWL۞ms j(H}L?ͫ I Ϧ#6|P*JJKd<9=k)FO{ӌ-|s+p_oZT5HDdZ]V}O+j.Q).K5IjGDP%mNG$.+NRCZ Ef 9"4G./)yV l#u[qتâRrzC}8!>GrKW "o'34_:pP;VEh j[kۜ1lcZ hT{RDяLoo-_X{qYyEe|NT`u?ʤu}tSOt۟+#޴b :}hc< gQޛ!z}LF[,; a⭜oO[x'jHZ\<|_Z+_g͏%mOBQ#ɘlACmWwe!!v_Q_#&*?0ByHO Fp6%^C&9-kZ<AM_&:ރ zEB tnKYL'|4cuDg3}d1sxef{`12) [T6 I]:1+M҆Ϳ_:XLG~w\)]뢚#(U(eVsfN?՜뱧j:ol"*|!聯}lh3#}0~YMB=M9Ym#z 5ݽ\yϠV_B=Sחm9e@VR4e،KL6lbk:k#z 3oy{M\=kd?}nԪ'qmJij&I%E,Юqs8fb]>}k-CZy| |AӮ1r7 E¸ĸ뷯?H~P(?"F~ѧ o 6#~׾MT~N^Lecih$M~F,htŽ8rFc*CY]))1 uB ?rt&Iqzyj9Tٺrnڂ(Z~9|PJP!烸[1;u`│XC 聇 i{g1>{sc؟_'<_{o~]=[01"|~xӝތ(qFW4ځ1)>7OK]"m$i~Ѿp?h4.@hek(WguD Jgbh So'u񣫓yV}p-*arFQp\1a'az) ^9+ >TP\vU?.:ٻ(b~M҆Ϳ_+!i.GՂ=OnNCFhhQ},ot'k]7;u3׎4{ 4f4|_1 XXD-.̾]ZOt%¢GN簠pc!4q.?zZB% xéYPk5?ܼ۪˝&6s}:fr8e\}k)\2qVemaHػWܨUL=5q(DX>UkøsGeFg^뮃y;11J̾oI:{QT\/mL_rAh0rZw冯Qr,4S'a|pbt8Q(bytD언{_gPnʍ C! ?:?҆z烸SQ~ n 9h`" I+Bmv(b`WYkpnJFak.ZK1*-Y&#<8DT_ 7v9_gi~yw{7f}5\[Z0^qG*ǐgco]qzS%RĔbƒ=Gou!j}L"TAǒRvHMՂI*s>&QNd+QYkwN_Kru޿k[>gcHn]$%r:AdreI~3&7;j֙:vӞo۵c7.n0lXQO)6r9u:nYiLlva~iԄޝ?-cx=B)qlڰװ[vK^s~Q唸UHNCQt ~aڽ3׎(5*gmMO;M Hd4|FyUp߯Ŵj~҈*[gWo[a]ppKQ9iӗLJ?G?:d1{+w<%m\XZ0j)PXsD6޴Ősd|UNsNH}F$ֹ.31hAM fJnP Fjwndœ J=ZR1_!y[`T{EŖ 5ۯƜn\e˯<<_#d2نn6uC'G-_o~lLP -țosy*m֠擐دg5-pΘ~]qYy|T5`w\}o?lyexRUHԐ^ 0/a+¨ۃs"R̝kԍT(sefP 41CIAuNWsA14!zwn82havӎy~" ɽIIBaf^#ϽX87c:23WG0Ezo9MdiiZ%mٯTG^XmmO2\q0PI(/r ӣeS )M:LBFuISL?rcH0ۗ)5pSiI> " u[&qd?}OJըڋ́Lj͈iOl`me_TCalh1jp]Їäei2˜c US=#qt~җ< t]jz_fR;IYwVb3mlPv(Sj6=lRUK[]OON/Xk 9R}7Ol#˹sDHxK E^u϶a%#;Κ84LsZ zbv>"bL*ps 1inh18^˷;Kk>QkSe eT nd^'*WU$m[Ckj-(9Jd(~8rI#}4G5 xv&5wVS'I HIP_ɕAM{Bg8rXCtQ|JsѢo>dLeyAh5lVmyoǿ<aImS% ߞ,e۰tޙgdE63{t-Ιy_xE<ٺwjJ 1̺SWOX-jus3uM[l m1F4Q>zvn{ѡuPPRfN xenJ6q˃q⼒*G1 #iTֽl4?OxFԨU>1W EӅYw:d/nC2o [tS[8-iVmesmJj8RnLLY\ώ/yWY7g{eoW*7Ĝ;d3_Ϯ\*'ȜIa=8Vᝈnu1Y_c9s?rUD$7d]QCwp:o͜st7NӚ5gjbZ綕*qI؍{,ZDP{Q6Ґcd#Ԑ6N^3c IB%|^q~YfZ}~7|y_/ zMw:5ZﮢB*}0kMFe˜:{{L;zC6n 8sϛ ݺ>#WVS]0z$MrBD M]TjR/a~t܏Ye2w@1nO$Ӕeʓg>(FhI(--{dA %'C lYGewkKRҤ>Vz%/tmDVOIPc= NS+A"dvE܊,U+Y0F۞ʚ0RQηsÉOmb4ڿ67kL^dHL Oԛ)yq;fNm.J+,SS];Ƅ8҂|N\+-3m RnrU]"c"=9ϔ<{#-=1Y%s&<4O\JqNf\o,f5~Nd@'(ZUԾrOdpazkK4zuS8LA.cE%z % ͣ[<<Φاj2ݹ7vw_۸)=&|u\#ЋNq64gP, 0 H6A5"oی?4m#qx ;p3|lS?%?0eaB,+|{WNje׏Z#1Mog4׎kGgQF&{_eG-~A&n\t~ :$/؎:ý|,K%9l\?S Ḽ&dr"WLcKkߩs_MnܹٮU;2ѩ]'۠KIosc˘mϬ||kg=QC 53[7Zf"p_G B[YwCLe6aF0p5=QO0 F8JjY܏N` XWϛkhTSk1|edy Y}|gi + e+6%0er&@-h*؂_&}Uh/7才nNdVQyՔ阠Qu\]IIu*Lج+;#cEDY^AL&7j7eS CxWDm!"W'ƧZRp/gA.A_VmKp1:}5o2=u2XODiF zBwX}nL2+㝌&NzCrL\Em N/7Ly1@y(→r:KK3D!6!&%Lx#Bj EAe)d:*X2{bMg>=6)|>6˥5WƖv1'һͣ尛7 ߉(ďZRGmOGBJj5NC'jߘ,ásn5 r}m \>]%n;p>2]v{ۻS8aHw?^FOPi9( b茥y/iǯRbޔzƭD1 MQ?'?2àQ)7mZ<P1}&/8e!'z)pu \^y-6&G-{y%b KAvyg-;rKlHPȋ^1dğGuwzjp_:S(йX/CQ@qyV&L*OJ8(o-2ZG}P(1+Td7T ULYS`ď[ e@N "I%0>@oNa1ׁe/QCb5hylܼ-^=tV؏g&s?*hPۘ!u_}i>&jb,8S^aMTV8mʊy,Ft.Lj= ;Ryba8wo2/Vǡr#p+CY5Ih.Bb=C嶠&2,;*Ǐz}ev Fsg:<]b}YSznAӉ7Ho}V=8{P:{?cs]l#H{Vuݧ5Ĩx#aN-|U|~mY *O]#m C}κ.2_`xJ,Xc #Ws۾̜0 ;|Z\cAa h Ȭ*fd0&ҷl' |Nkl6[ o^?~aNwo.3 ;߼F&>v\):j9*܁3AMYEmXmLIF0pʈmZU'c[UWx&vdGTˎG$MfTam[͝k/ l'%6V2+ӴC莀rϝn, \̇^c;5l`:u.]_o[S~YJ6˙psI$sowhOTb}S#F鲵Yؼ6lj,z/M"_#6|ruxsP&"KܚNzޝ'Q)MgL޽ `0PW&S>q|Fv/DQo縇?x+}X>DADjCrʓ~ >%6ۆ,:}NsTǔ1wx2js)UR:m~il0WP^^^PPҹeA =XdU*mj.Ҷ(9KM ^q#kO&Mfw.طyt+fu,NoOfڪ˘L.+eGQk֨uzΠʪȏD 40|x묧m;w6 qľޠ`/4c;Ⱦ{6'U׳2Yֻ'H$W8eX`V5< qSp;s5 m&I]B5Yg44B_0ldm.\oQyEzhazv2c*aF&iH U+ llrc~*5ނe;HW;k2U%زa('j6Ʃ7:)*&N/6I%i E\LmZuNiu_4| ~Dnm&+RwA.X&WH}Ng ΃4@yMP.M:<5l㔴:m3DݸQkLamr8rHҹeA Vvn= /Y4*w7f*r2T&o9}`r5\7XLvVCI5vT3ڹYqY7dwbemֽm/@.7i4 j*yU+xB1gTVM.;=||zXcs({=FDĭt<QFբɢ9Q"9[gQd",&zߨpux`YH7 i4ZT) y5JVp_{vIӽGW>kbhgx4Qh U^SeOxxiˎbZBuM!n dT.c|2 di:dpo3]PxU{lLI͹x ZG9u'_~9:z wM2zwe*J.Qk7ۨuXrP_=PEz~ ׼.7 0wtLe0 f;r!wrߦ{b[ڶG6wWh_ YF%D)*zaots fzHYn`N ?vOFz7f[rӀݲx!ɠv*ɧ> t1wUfzAh:Aթ:JSXji{UtYl/]cohvRd w0x<jsItZU9~A~FؔZ.+5‡#M-_bE^n% %މϳǵW5JDQ|&\FS& FuY0K }ľ[JI`iYygm;0#7KKejDXvGM~k},D7i g"_0 6,5hy뛹" M~TQW*޻w'// H8\RzQ&9c\M=59]wsc}<*Jczj**<-;}w4En5oۦe76Ȫy~ <¶/MXFR?ZPͨ jwux\!=eI%ҭFC-pNE%~MϾ_UKNmr,(:sܫsW͠FE/xml+'2x&b.|l\޽v`=7r9mĠz|*e n?MzMm`0\6rY\6+O\,ΓN|[cd2LFA빛[6 .AMIdm;F_:PMx{ڎ]6{H`if*] ['=7I厹HfѮ!2&6fQwq9w7?IC'COF׼(sid»s~xg_EblzʒweՎ?4ffܿfo+2-~W>b^mӢuoqaANz3g҂?}bi嶞vHi~ط^WAM8\{6̡m߰zJA6tI~9pu+eNgڍrtO QX>âs{o|| tO/.7wTQY^bNE4k%YzTaӇXf! GtVuzzNaYavHt AG#v{QVޓ7i`뫡'7`=59sTg:Bm%7~Ք S|}vG&ѹpg|q=7n_o_.)!ꃚJL =4(T[j6JtOrmECy5&*B..Y<6˦,$RjKO:ĽY2 jŅ%G9>󃢐ӨYGtWB|QrulKʼn MWomį8)UZ0єWJcr$f *WY]tS1_j|om*-p nAIjrD׎2G`"z1{VIUD'$s\T+`){/k;TݰqyE+w~{zɬ|< U*iq0P,}Ϝ өK?Cu'Zuiiub8͢ZŴh*(~o˶nvk6~z`H@uhqa}6snJ?xyg6;q:בsII岹fps|y[tC_,&)n>狿.;PX>az.__=u7|kM≯ [9Qzjv}&-BWs^=k]rR'UpLa/-G+B^8޻7?>#w9RuIgse:+-Χ6.vuw츱l )ee=QNl7jO-XUs\}g|GdsL||dSؾAevz٪/>\jXTdٙˏҋK,jٚXRVr!2!۶w)Ywkn[F NasV- Q?>: -+/Kj!/!ZT<3lK&U:Ǽqċ ЧRWťRdr]g @F/[93P_ %|!A== IDATB}]8g\AuyT))'DZtYLZc$6n2mkI#˺~S!Ŏ+տ롌Z!ԱEc [NH8֐QyEe"BEqPB5+@qO9\^7 \"JB1˦Tj-i9njdU5$κ}?}7DAz3*?r zjs TKwcCFÙ{]ë~mO« ;?/_*ۧM;x"F}ᨰvZ%C6^,*kAq}; A(4dy{;%vpg9bo8sL٦E{f׎H\b(u4e!B EVԇN?cS{6]RZ* ^%.)?<'[WqNA aqV7Um#>}w<' |e/j#ڏ]ᗏ_s8Ϝ:cY|F/>0KQ]9;5d]iW4d2bj޾X$dyY5}< ͉s_o0HoIhpH(Rտ̅3˵𵅽ry{x Fk.\:6rH&9ߠ? WymP@PHPȧ}E%EG.< !e;Uj[(k'K˼D^MH,5{rNG\v=d@to`2a| b44Oq\.uALPWHp%((LxY"$PkLSȴ.o.;'^-'Q;ʪrK);"cҔٞ.|&rMf*c[LJK)j*&Ph3&F^1,,*pc@]q1(70V9\̥sGņ)DsxZz\w//m"*^ x7 Vرfnמ gŖ/8q9jE)+|W8կJެ9I)+ݷqc=v~֮9K KsTt%ҋo3ve E~urb}y&fݚ%{m |%8A:AQ+T>8I:(.Q%G1mBQe"AA8 ttBɵeP]p ME-r?_"]h5:=vWs-Cw1Uw6Eyʫ<1UZ}MX3ZRP'j}|+iSkN(:uIjvkЬs[DP]FAq |_,O2|zjNRSˉ~yo5?=vCiu~ط+KiJTYg_n&m>I奥»jfY4zł5< Fb'Riₒ_KpMYآIXasYLVU9gZuJE5weP0Z̕zwX}9YEOiJy|ҙw7ݰנUl;۸9LIb_EuUiA݋rq ywjANCx8\lh[(5jB^T\t_nvXB eܾ|AVJMRj:@p|[&ZabGbmE#G.,@*PGjNsX^=k>Y~/ ){/;N rW^^&siR|cXÅ5{W\.70>54{=~틚iw칈#l$[K|6QjMe*r\xVWlj㳑r.?pV"tn'.ŹbCYLL7~xߎQj9/ݧ&h=v^nѺqh..򠰌j=vNB(U,Yܗ6{̵ K_x5|ϴjj*AgqlK.nmp\n[1W;Ю î±9Yzc[Ow*΁N;q ֠.  ^Yj&FE\J{Mm5( A18]+S>ױ&qkPS0*K59%L<H\nŒW649׵yۄ! eF!O+NH+\X}eY:QaTd0pㇱ[]G.odn(Ll$lPSKFjxEbWڅ2K Xz6aK4u?_4z;/5iH!DBEXB0ዻ`qy?_"U6>U\ta3NPktIݸUPn4yJZE0j;޾qvjFnI\(yc[C5DQxj2 еAMAMAMBP5W&z,ՠ *r>󦮎l(_X*4 7nU%D(A 4+*AM\/$[AlW(  hԐ㆏p7opTp+A0')=R4 5$5i_ExY]ɺh(jf^+݊ВS;Gw_d{FB_Hy6:C{^͝ee ['ˁ.qF1u:78uBק\>Vs>Mz'8̨fKcӻ)]^۱/+,;AWa~^"rRn5oGUrNCOD p[)-)/=+= ?dD$$hAM/Y<*FUfh%`Lk^m|A#ꃚE7_B x3|_0~МE6AMK_z` R1k[)m'G!M}o{Qrgqx7iZqze=iЙqs!u޴շ˧8BuHbNG]iL.8jC]jpT+߉:L3~̼D>rcIlڶOO Avn9 6hSKJcƘMYvB2@t !.e z4,J,WTV$:ztDP!{X?hQp_c 8]hE,ߩe&#h*D.Q,oN}snyH`W vq+ 9ZB[єU\,a/ y>+$"'i]ϵbU+K ][[0bTkn銮;e լ_(ǛRG*M?>Ssc]{Dcp}11g5^|N3ƾ;ɭ:ajlԳomlg9U)J 0ԈgL_vR֝ ZQ+BdsD>[5)ѵLp4SyY^C#bX]{eK2O#-wUsu8dsDd"8nv,G@qmMսP 7j6ȧ>`PHѮxs:=+_,yHc\(jWLYQ=ܻrbde1o #7+]Xԛ'!vzEcnsD4Q P}ȴoxlbx3\/7D)jsD9Ղc(kOM%&2d"s2GMYx(UVdSOݥYp R6d]|xε˺XVɦ'N0.Oe5KDSqRTXqVUMћ``˜YR7TWopth B" #A\6(a0~S ,hv `k6cP!(7q8ФW (2QP;VQ"l1P!v(' NXfoㆵzqg?έB }'@ ]b^=7>?D|$+H?Eyqi~ֺVH.Otpt/Ȩg!7OIh .j޾ X2+ rnmEt,]&RqoVH }g-zm!o$⺓„ap:F 1^||a1o(#6C\#&I  jsLxA 2TP0 a:{+)oF41>/v0.\'.z"D&`nnۚ+-7VoQg!Q1G.ڄƌe&ZXbYq!5Q-W|ʈ/_Kyks9<0q  IDAT>c=lH)M~;կ|E5*,3/vmuL8KU+T޹Ϗ˵;&v q:N?HmS8Ϙf-|5&N[R^ϭo菫ƠUWHK !˨l.Lc;\Y(/ 76b8_u6^;}\lLgS_bZ1y}Cy\y((*ܱ5>v;)5k! 9lZ.)-IHol=P7Q{ 7rJ?Ҳ#84Us=7< 9cmbx(y?}OgP8W@I,Nb6n~ok&-m ;y~~QχZ/a=[>ƑBtLn[M=UGMV; @#%{%Vs''sn=%\Y6<(asɏĿ]| 1rSG15e8'EΪ{^9 {< -*Yc ͉;/~V^>|w~r_KPWhߤ:qO"6e!܌5jrWksu(+ -TMdޚ;nƮçYP[7Caq!E5FGDܼmb[>|ާC?6+^yk"b-եܷ&ݾ^rxFGy|^WX\p.\q6 7~ռqkxiC濺 7?.Ni7YbZd9Oxhx_4$?Hq\/3(ef.jtw =:Hua^}S-%,]&2yjaw|S~k.CzQv_-۩+'A1=H&qD}/LmyS ѷa@JeҩdyXbE SuC^./(%k/[0+/=XImYb^(v)J^"ͿqA|:4{!"WAA{P:E,Hg)ϕ]О&aIk,Hp!wҤ/2Lמ3TqrZqEp Քmt m~^uoGoOh\22ߞn'0v<:Ig>{ i_cDCĽ|CcEն`X!ɉSTl1֖w*ΕG 9<AM\jD SeNE#&q{ .I-HAV(Нsߨe?iҪ"";Y0DAɮo|^̺OWת\ŪPaWoը]BuMyJ⿾A˗`*З&&MxfxQ][iO'A ZtZ4cMş 2E3F4,ά s+&CM$/oQ lοb*ՖN/P)r sTj%qޣ=駏/?gmDSqY{cysZU& CDsDS~Q;g 4ya<[ޫ.AGk2idd=?t >jQ/F4/ wjm㶥󗶊o%z_\j\\ʢvݿc?DQ_)L9z5n:戦ri7_>|?YV4&%yөnFUILj>;d*2,ڇjS)8F4iS5ڋ<<,c(7Fg4(߭ɮxI GxED }8%yQ(2e.'ͤ1+h17f+x ,y]Iz=ט9N*ouL_G4=ʹ?l#߫e^]/sDD+ S^6]i*MUruYvbG᭓SIo^ξnT5[.a2|㒇ߩnRB٬w q>UF)jx(Uܴ9Z,4zӎ'jLV;$b#fsR)gbYCVtIU~BT;y<UD*~_]eUi65kWhr٩k]DPuoXTK>ZJ,>PWKOqv2eCY*/Q?o(BQM|4xj>\s W_o?>r?`4\u>5z31*1?vͣ:p("Iosk~qJJʩYrY7N4{ġa R_t^ o5n]h6/_f JUt6ȍbR/KYmٍٰgw r8`H)u/ےW $?]Po;g_ ?ZR^t?Rʗa(vqZ^=xzk얃ڠ'wm6]4 ȸnחW}8k)|2۠+IM/:&eErй:/7kܫKwxuݡW 5؎/` 9Az 9m߶EE!Rt3;씒F(liw94ԎZV2}ތV[w%Mo 3-[|g %~C F꤭RR3ROg>&g{(md)@,-.=?F@eSҽ{{咼.+XW8>\'yI5O~yDvEp呏vJ\jU.,>+!O۱Olq17J,暵'fn]5wA׬$ɋ/͜;%<%nT3*ǽڞAM\<Ɖk[ҭ$^fҬ0\}[J{A.ki_*՜Iu+/j ahx"qf܂0*\pPud)}YD;~IŒ<{ͱ*M5:fnǖ|Kbу؀#{lQJ1忚Z2ѝA:xqbEeq&&5?cuDAʱ.)'; 9*ſwW`VP!7 ݳL~pp?2*\5ȏGyFu]sqGI_sE7rdsrxQc*[Сrj:9M3G}["-eAb^P-(F@יBs(x8A*8M;tgӢ26[Y=*:)[\=Eٻ[dئLsU]Z23ԅrPaO\Pn'_ UћۺED~Ï~x~>}"CܿzJq+,Q;׬S~:M>,aYMə>VV-?h2ȥ XzKx,^2&rk?]1?|[ .(quݎ!d+f'^)`;X{>غE: ۷g.;1#39ԙ/Ν8wlN!w×9aȰN]%sJIEeo9W,un6!_Okj6|N ҩS;oTEڞօkyDRyt5 ܟj68i0j2u~-]"댉HhɾӍ:޾xh `};Ir;=M[HGw?}yשVS[+{aVfb Lhk_]00 ph.O>n[91xν;G. 9&R|ȤHhR  Dg%}h_ƺ`BG& ň.*0 'Q|7&W`ש:Fd4M)}/mՙszfgT"oϴ ip %F>L+<"c|gK(uIQ[#'M ]hٔxLJdt?L]#:=[CV[x%xW8+. C! p8l1U]]$Y)6Rl^Sl}0UNUJg;Q^E4吸6ju0J[ H۵`Lר]m;=+IU0뇥IYY@oH4\xv (WDh s4)#1t%$BXsHxBs!Cx]5W.m$. 8t\곺T'"A;(]c'"pu)9<*J|C#0\vx^6?&n9nAMvo y/`f1,bV_ jRݟcEVw5M/nb 7V9^yPd:)7׿}Spv'.e7>C:sȻNdF(y.pBv2V?FޛZg~[P.rQOvKz+g& IE?Xv6ﶎd;LԔ6v 숉a&Α5BLqF4q{zv*uJ7~+ⵈMv)ʺox~T\ 1IδZeUeΠȰhmu=-ߚQSٷo^eUD$_ C1[Ptuϝ47؅IiW̚JP%HE-pL$hSPf)u8=dflЋe\OBZiK~Ʀw}=uCUTVRUحoݞsKKeeȤ>=lܶёc׺\{=sMz~҈`ht5F6J)tRd6lI;6jL/@aqtۺɨsp W%GR?~]n w,o HK􁋋h1'}}6nm7}Cᩣ{_oPe[D09%{؄\ܿN[1I0F8U0V3-,uY_X+MP1Ii+8.&~#{Q0~!֊^gW*Pz\w8W +J܃iE5 pN?=/ hj>>m.ڲK,o|v'fO^2kժ"_DٲsvM,RLWT?1‚i!E\*g xA5A ~Ոeut)-Cwݴ{ݜ%3M'e b>ÅbDkָBc)+גtwb# +m˷Gu4'}Ct[)p#5y]#g.qF4U^gg3r=`89aM. p[ fŻ(] 'q?4ҤxWw]X?p_4`J9tК >t(n3SV'57o N1ֺ^m-u;E IDATTU2Xm9׻nƏq $Kui5m[+JcrׅPZGiU( bXPcn""ML ]ve tUԨqQFG60& jdmYFҙxsSWĄ OmqU?'sF$)e!(+]ZHG4& mvMM;)CCV"t-CMj~.B*K\ISEM%)2R1yYK|߰`f[(yۂ:e:g [q6:5W;C׵DZĞ Wv__ƔtҢcp!!-6}E9\]E֮`v|wE CxnY= #9CCC'?W";3xó>˔N_<5Ôß 珯zY~Bb컛4XݳN-&Oy?3»: OzXza{@ѷ/0'(ʃ#FN{c9ÍGyTpc }h58uL"ھg 5~YSJJF 0>&>%&*qO>0[O;(QwMF̗F"Y pjֻnFAMD `L*p%K *}\O21.uZF[rR,~&)YnZ LʿQ? uWxK}ߺYG#u/2{4uVW/ (;knS$ ͆$/ZL=zOA%g,jZ*.u? r6Ns"mgh^+^ W+F?51? 5Z0nz kcv꘬bAP/{A{m֩X^0 ů}*n0$}·Ny݌Iq`ҸU=J YN=|RTuu /j$6->*9)6M*;l]8W,>)/Y~\,0=&Dr.u Fj娒2a66rIu=V"n Ikf)==tvrY ?z|<<{á"%Pk_AŨ@94/R$tF49;-GLTI?EŦ*YHJSt0J8&~@IQtTn1Zia2O_77YQKk_.a#zoGP68/iTƵCxE/NL2T}LRLuؿ苳3/'AMXE}|LOwu*4[Ph%xsm>1[@G߮sxb%krQB0u6saҖ )]Zo*:Ai~qfAGR<WKd6/ Uo&-C 8' 7FߋON5"CoZgZRI׭Vnl۪kzD,쩇^{LPĘ __mz!-+>?jӵ,ÖSUke0Lfdբ6m7`EO<簈Ht'&?q'=}'ep,ńbm^Y!nD;q A_w>EF ʏ=WY}Ll%*: zкc9ÍgfnzŎ{5._vNGjo^/ždhnxî~/=ɜJ?4>H3Fj1 B"qS]-?P[.SfTɆ+P,$ PW(j]Rի[&jy;`JIZ}=躽1}s6uF j E%ta_=,^"Q-Hp4ijL\|%X $!Bq!ӆI[ӝ[k)q ;5W%Qb5UM?{kyi\ Z0nlD8k2(*t4|.VYWve_$v$+sZxE E}E=x#5a^5 7t5qঃ&?Nlv(S:8jtQlr ݶ͢y.éc_1e1\:lBR9W~i_Ka~|d5M65YUm嘙^b5ڪyKgOtܽOaPSm%Q'UrY(r]gnb{BUY ZߑC({^6Wt4q&f^:+.G&bq-vAM$!{ܳqFC[Ҟ{\Eo/xgُ28xb(4B2nbq+W$uDԻ9Ԕ3n#I@% nS'Fȫ@-&֨\K*pL YRmSH?K$p;Y>8Oy\bC6k>kr]Z?Ӥ&]Ј,GqWD2e1*c=,5WDʹeMOֻcH+)zid|纃_epD/DMnzz;iݲpeUiMx zߴ>u 5ti\IZ0' `?) K] ;j2\+!-(ly8O v]غuwq&&Z[Lz8,*,9`ErUiʡ@uL߿)wsɝGb_g=dЩη,{LKϽv摒E'Dō6c%M,յq^̔9 DaǶ'^)١=,5s}pa-[ӤjR<|xcԛg)0,E??k5<RDyT^QqQ%/x?+(07xZUDyD\{Xg͢)׌ISL#5y]Շ92E Ew]ƪ*G#ogPSHPH\tyh/1 ٹ{Nmփ.qmR~nxڬDŽR-s9c:{Zb(2aˆΎ&6Iݔ)Ӂkf(>S/i{ 7p`Ȩz*\Z*%Mj4bGZTj+RUxl1o+mSƋ˫~r|7ilpRʸ\QkUrҎPRۃ 3m_hҌyuĘUɌLVsxؑ6햦g ؕjYљyAŶ~4\cmsxь*)&=:i۷+]U~".L&]zJ;puMj1C_ߘrH5zh r]j6iQɳ|E=#;1 ؋n]oSFOwسC'γ^8mnU+Qa>ȧ<8QsS:IwmVG$XEa[K-@˨Sڱ+pҞM4'w͢ǃІQ`?Wbz~zQj.U Ohp%6뙏7ܢTV7~aו}r%^(qG#Uj53WnF ЬXO{rƔM|6`E lIVZU4s&nnK;nQ:,L<z[ +b}aly1,߱zv4i=p@4g:Ե+UeiOo& *z狢Fozɔ,d)>ďOK"[pi4Ձ) R⎀KbK,4};eVmDM,2zLCo+{X1r#=bTDrQ F)Uw nM TJ@:NG", =g4*\/[~wLd~p]1pFǼ73?3Ѷ.J>oIoY@D!Xh/=v{g]>I[t +sjR!x7MM*ص\ܵɊ/IUk@j!UѝfC>#1ƐW3 MyYҳMiAM4IRBbN]}rALu/P{ZZm5߷~YTݨ֢5q DQN|1T"W IF?fGֆ8s<`ڸYη_-_3Ouk ̙k E%:ߪuRu] l{l]\On׃' K毹)O؂sB";ǟ5A* s<3UX =;yrXDc]Q:]?/갶QFQg\a%XJٺj OMiJv8\g^2p),}7넋 ܷaoǶ dVʺ9}=ѧ[oϭ"3qk>Yq둵97*"Eʜ<} bxPe)2'm)", )*AمkTzjEa-7CHCT{l*e47|/OSuO URi\=?IoT$} cyW.`qD?X}/[&TVh%H2oc7!%;wy\E2w}LvIhH~[0'N#y% JSTDhn"k2e .f8}'4K656a*q#3ɡK' `l1I,)!n?Kp㩂dGs :JSd@g֯U+e-o3\+aj:,ýkAM*M>0gޜb>zٚYP]O]aKW-k`EuIS"ӷ_X9z0 kXYUKg;.Y;wv|#6nYdٰ~8X Ol=B1J*Mc[Vf/wgl458A(#b}Kv]>3_.ae&82^"WrQI_I 7-/ReP`XO6TY-ĖB1׷QSVק̸{UT.*[?{'l&&etyO{95ßnJʔ8{Dg1l,{kMi)pJV5b럶}bCY cc,0u*@gu+f; IDAT.tgew)UffhF +A6NȖ*+^894XϠ&g,*mpVQz^XTяĶ*ꊁia=O-Õ|yL{pyu^FYt8~ibew2ega3(&xO(݈64'gĪ-Zx|_PI=GƜ/Mȟ%HεaweWqgv=bҡaCs)CZ' 0G^%K38W FV5[ c뒣6> :d>C0\/Mn_}aj L{c=C7}[#( ʼn^V-̙mGxO?^ާ+?HN7\PVSrjEOI Lm0<@RN /\>VQ f_zM^U έxg-QDE~x^S z]OD'g$2 Mz쁭8{.<6U~װgm Km{wKZLobPu[ (}X G]a[nc XEG9lܶ)80^g;eרwƑG=\JTBcN+*)ڲc ;M}er*-/`QHR_}{UR+C_-jIlBCBٗ>w9bTy|HX L*vckݨ+pyەvxi 6<=F*"[͌Lߝ}58_cچ|`}:ӅiEjTp˔, Ůk0Lj|SQ"i;'ĵ6#is)m:8l#fg7VH{j*\zҞf*Ǵ'c">I\7c~JwҝBe,NAcI.F+,^PutmIF\YEN.pŲ-<%hH|PkBjW& nh9Tn-61)rS8/U,&C쫡Yl/Ɉ"e ap8\B^Pӥf&I%*X%.H m-XQ! ! K n̒LAOӔJV°(.%+gRv CQbG!ȏha[]ZF`,q?2ZE8o:=OqN/6 hv&ykTZnv8=p3'“VƘ_]#QP;!މd7`pkis5N? ]ok1,]n V 6{fD. > ~W{sgsDiYn \͊5=@P@P54jYOz@PBP5\1S0eiPY5cm%[ WAMι5:TNMnuU2oF-%ﰯg //~+th6TAn$jPTСP HM 75n(jbh1[nUTRتGlq|vbd!yKMb7Ҥ0nc0 R5ѿZ~<^+IR٠S/_( ,$}/y@Xp}(0^2p+2T  L5$C~PPk-%f5l{F `{43 MCq%nK' -sS4{ƷRYD k͗,'kx)-R$qO̘i-n[Xl#B?WʓÈ)-@ F qsssSj\iPE0}wVS {p6'jbS۶fOTUP!pB[C=ۃ_iVj9a x"W48*Q>0$hz.dL _A&EҮ&n7f+ajހE5˗-&_VN>`aedṘP$jhƭ!l@|DĞiJ_g)8:f}25S AP$U8*p\CDi1uK~$?Θ5`̠q3BٌzMMkZTs%6%r\ 3qgӹ;Mdu`6hbI˔7CYH̘>ITB^],WG6x/=9hh bkieiHM,[$&.H ݋,>g1GLK$PѼ\RO# &8O{"qB5%q]26]1JL!0huowi]1iYG(T4Å{ݬf8틽!8:(n0jpBL[I TxݏoyXʾBZu vZzw5||Bhʔ=oOm!II vGQ[jρflbٗ?nѦ+ߋ:0fw!)b_^U~?>N;6_z;1e/h' x  JܰNgLDbl.HH+X" ZFMcFac6Fˋ,>$59iJ`\dp-̐R!utJDFXE2%*sAVdsq,#b=tE)I*jD*R&b i- n&jFgF%tQ㸼nΕk{gZJYP0/뜢$T3Ff'!Q 쿄P4lܼ3 آ}{mZVյl.H ҷ쁭kkH `-b;q2[a#hT .R}E{%y7Ea)TpRy6\dA=i#pYpdѫuޡIUSH (qW4*Y£}4b083. ac.9jUe Gq"S.Ċ&~}}lw}b2B Bf)6Rkvid[snIBYYO#K> j(p]XϠ&ӑgFqeN 7f(w5vG4 Ũ+ӛg0K^of~9fQ9Uq-UÖq_haYY/hh6c MtOJ{ z̮ fBhz Nt(%5*um%ii~ᓵŗt.ǽYPxD{+C&IlO&5O%VLZ>,ʼ_?\sf{'H3>ы:܈l ]բ}~ŦCynNOnc!Kxv\1arΌ R(&l[[-)t mJ:"wv]4y|Wkn3GL%gbjݕnkWYJqΥ4-{A1>m-DMd Ws\:n65n<ʰ}=%;oRnDfGuYtrJW=Bvta`pP*UɖZ)^H=K.7{ͨ& ,H`;K i$+b5Ϙ(o8:ýQhwN({RY"Zoat6_7͜GwUb,I\&~G$c]64R^57K ,L Ċ|U@=@#\͆QJܣ<_fo m51~}+p{%6TD\_ˢ: ڨV^}OMܢwGʩ6B8{[/hkyGf۳-,BĞTTC`^R yˢhGkuŅz_Z#g֕RSĝhy x DtG|J-MSu¥cZa]<6 m ólc)v>{\e@sb2Qͱ~6G[8vͿ#XU羙Qj#, ~Ok<"pK#x"ލ´"/pNt%E\n3m.I1B[̦e#ֽ^d +?>mN`OSo#(*S [f.71M}pʰM/y+\e5UaOG 1S&1Xym%okO@a˒ p۲T7LδDەԋ`ߞCw` 푱"յJwKM!RzZiUM7phR>tq5 j`ԣs\Ro쵅35zV38u#H/j,1rdRF45w}RrENd̂SZnOȷf 1bA#'Fh\~s1v_vakU GҚ9;E49)d>c7|[Q1ȞJV]w++0R^jeq3%rO[SqvUes{XgٷҧOxmPPk ? rf}R6I_Էg8}лN7㣒9G  Mx੧L(> v/K._q+mzet?{r M7ʛIdBh4u 7fۯW֌e%Ңk/ek]}Z'+dJHB3bVUM{uGYئլOש hQ=*ĥ{̚J>6KXlrsR- S7вuiݹI޹z1b= ض?r{Sj޻:T%뫋uj+-s639}G>}tk<Ͼ O+ ;C"Q)-شx}O[%KŤӔݷv Fĥ?41 !;v=BReu Ƿw%lzms؃^h7^sJmϜkF Ӷ;Kڣ ](܋,^%є4 s1\}݉^7}4pnbk.I;2IqyEQjF)Gp N}t^hOGAQya-_ڸ[aep`uzzW!HU/h9+DFt_<~[0pFw '<'N-:SJU2JMjqƂV`oCfeָzR+hJ4/Pdm—e?SJ]w++7_e)A=OT]-cO~vb̤xƄ\7*ג9_J?޺rc?o3@. bg=f7׳Oya[!s~ٽ[kH\{d/=5_$g?{5^ݓR(-!{Ȗ!CDd(Ɓd Q{C)ҽ5Zj{}o{IdOywi`[;ѳS]YzoX ]$s}EGami!T/ǣHHBܼ[7nױy&jհ|1B~yz6h٢ag^ײȥߡ$䊸Eoߦ3 {##ǺG0evF* [3[gڎOyPȖ:I.r螊kNm jɜ&Xe^+*2A[t iĤj+av%L(UsncmӜ[zmq>Ԩfwv} x81sxQSZMF^-w_ض{FG-ݷ;//Wp6uB{+bcmJD!Ka;Y1#eX  x &sn%6 IuME &ɤ{Mz978NH!׼c}-mfl$wMT)\4(bu^;`؝F_1d[YT}K) ϥ2?Ac~1݅j>9fNd4rtzumv~k JbɕV+/0s}wA5b39yn!y)N?Џ{2ɐ3*,W??gP.(^~(4Qk8cFx17iT㢗rq>ۼϫb5&h!^ &5Ř;*AkqIz[ 擲 ޶u_탭;;,֙oɆ#fH2-u`n޼D?6Zml2*ؓmF"|_4́@B J+Kvn6C3gZ͛D(0_!bPWM\jS^y3o6h9I[F ,cnS dd.޶'T. n5MOˎTkk +  ]*Z)$rIKH*ۡc}xOF_D^f|~^nO_ǶύmMy'ȻL-z7ym r1K?}ۑ鉱l6//=ZpoƤOg]Чf.%gBlIר;U4l2LGE2o׹~20es~(%ā*j}p-K~gjZhqQ<׆ETe9=s?呖'a|D5d{d5\>L9&eZؕMFcez$lX!m06pyo*yh>(:6 JJmܦMO⻴]) edm[. G3MO_ |R?>YphF4/z;-D,+"bTkN&ny =t_fz,)A+^=xՌSW~˝[Nt?W:xK/}ɳ>}Ɖ{rO-t{;̲o8Hҙ֟Q ̎hu 8{zKwӯ^]y=%xDlCx#W'.@Mo-S]%f±F4́@&R1f}enk9)Tp̅gAwȲAƙ(vjX %%uzuS٭(1-ڽ3젯gUsv0,R7KGLQse/932_|[ xsm˿YoMw \9nTDE_?ÚТ+d`Ӓn?T0F]mL]iꖖr?woEޣչe_W\:tӗO-;^Go;lRT!ZN-u6?2-?}VxN'w5{Ev^ˎڊهIZƒ˷̟$t|mڟ߻y{: x׵iO^7Vd۵<78u_vP)[ z}ԵX3y?w|ӿ_>$ն﫶Rfn^M}h^\gvshLZ_D^Δc&Gϑv69݉n&ʨ:j> q2]C""muѫ#T6ܜY]PKДJi%f[Z=w:z3%E 4{q\%2bA(x랲2<tm4$-Qn} `͟8$sldbKBJkzxv`:˘Hw g[/}[ETMڥ#{(u6e$oCr^qNYjS+}5k _'0G P4Lf$޴9cNKD@F*u" c-6m0XV~ڛZ64#m+uryD}G_:^_#fQKk.LJj]@aMQ#\$+ny/w%2n^ ZicL0>|J o4́@EĭF`nf&wz ?Yg-xZECt# ֭߾o~(W~R6KDNHWm' ̌Ye{ p·p=/lݸ i]2Ɵz̶bbzvbS3Ryߘ3NJX&ލK׏=]<M2aqf|v74ANjUBxz6[}{㚯W2fngJU[WVަ[xDT=nާzŒ0p?D;ݓӓa  $,=صWSV=v";?(rf&)Pnd&;Jĝ67oD5/jp^/:܇_,xM^5/f*7V?5jَ4]WFv6o=󀋇v3<}!Olk$3ٳFOl:vn4]8g8sfH -t7Ecwn,,C^]˻;_ÕsL*H>3:)$**})CA}GJi%*FJT)>l(djbʽچwxs=cL\qWrQ w3rG.zGK]|7KQ y`zZm_Tms+BΖ)[IUsS'L1dxjM]B\$EޖХl_\ zy.~uuyurhh:~W6UgrFv'؇֙R0~yeIVC_ e@f^W91.yTƋi\ی@gkjBi&oȕ3|/rs,uV_Bo..'%jWD6&ǔ5) oLU&O9p%5;``5A J0t(&l9EZܾus|W(yQ7삚~?p{ܸw1i&L_" jz:3h4L^")|uƚ{dsIoyx4YjFo-S>m6^p,_Ώ:(Ԭ-cP nr5ϥO?ׅ W-׭M/IGN)Z-Own5sBWOLr_~ DzjV]3ϴi6)3BiDq~.+5svk߫m_e&9IjT寮1}G`1tZC[) 0,.\6Pyq=_pب;zS>Ojm3O\ r_Ko={!khʻgrW/N!Lm{#8YvbC66-8` +_zбͿ ->sBpKoiߕj98t_F:SC-Ty{[Q.l7~UXY&z=&wfK{#9"S9!O]M6zRs1ؗ!#?)}5?4I/֚ȝGo+.ȼl%3 &<]S(=Rm.ԯw4ӎW.t+(c[ Tj(zlYUٹWuY$W8%P^BJ+-0V;͟hWW|ZL6g/9k| K]N"w,8uz{V&Y8M Y?gMͤ2S$5h^TXH!PUә+MxD0+19ēЀvAM-"d$Üed Gٜ59U;8S س'_ Qx)p7eOok5&$mX0?1.ӰƕKLn.by!B^5Qr@1N# ·<'tCm8t8:5Mkv*:jh$ۭda:C+5xHhݮ(mV6U a5[NKd^{~2s*iВs)c;bwQ/ ,pctbVϥ+37l[v&aDBI]}i>֭-J#93N2;9'T,(.E qV]D6akK#:h3Hgc6l߇/V޽[ M ~u.N~`LF:yʂ=S}8h6)-]o;6h=H=fk&uRŽZbsֆӿ|y6 9̛hUY(ImlP%+go ”c6Ggp,*Iߙԗil2)3YUDڼ3Waz6%4ZJ͉, uΟَ'ȷW>Jڶ_T4]ݞ< >J4g2mê-{ u4%Bacȕ OP\Iхg-+uu̽6}b~[!!B•:fHW8&cl޻V9J- Ą7vZgRոI)0K]irT9ɁsYީLqfT`g}5ݹZ|)^ N/ 7#́}Vʸv̞;F|?25Ju!ѝĤ7z[s zH]FoHu pM;}y6̱@&ag0TYZlJVaů+3z-U(SIO౷ZnR/(nK?rz68СCׯi4UVbįVprcVB{|EN k:eX\vc&/v'-#p)רȗ_ަ׷ؼjY$?xezDuČ>[ΐr`!IW`)+L3[ &FQWh;Ir@ӪUгVx#IQݺ&ҘUWys#ۋx|y6Glz y֜ңћ|*3j|Kq[TIb]%Og0GR!18I8b1pnckAMgl(j_P N:+R]MH6i̬>y}N2kN/_Ĵ'oInOVxo!0~~Q}*7 PHabzyxiL Mš$rv̱1u#l?5Ҥo64,G磷FU-ǧi Il"djb#<XHz`]l'߱Z 4fۼH=ѶCr?)#W~-4a "ÄJ#d;ސf")J5GCp8H=%*p!Sc~-{14o9=$PݺI?X0 ԅ6Ľۓ&5d2+9{73(5s]UGվOBCT6ʣte\zTҞ=}scpc {>p3f3۩ʨ&QR= %5i55 /*$QE[mCwDUg|]}6^NhZD@lsȍW5D$gpdZ9E= 8F.5%5ɝG +wSůBVs5cL6س\O6׊p-.r,=ר7ZN˨b_pt~{^o~,_>!q*]>S">g*Gcv:Ca FbkR.)Wm݌rl֗C~3ޢ 7e *m[ Tj(6'/mEKgŷh4x9KԲTRF[gff&%%l:]ѡ&MB;bF,w4 U{:MY)Xh2fd(RTkT*Jjڬ"pa*s5nA6ܽo3q}(won8˕zMkW$I˱]9Ljr7Fed2YprxW8]pS۵|Ydށ!Bq!~nUiG6(jʜLunJM.tVK#vEҫWK`js:~ k`yN^R%g*\Wܹ5U:N()ʐs(c"B"*cЋl9Hת6w |t; ÞpN!N]N|<|WfG&_KM! &9ө\dzUx 9")ȩ]؂F=LZa*ْmÞ\gK٤9)+8]xYrGP&C]&w2ǣWH IDATTY"_] 33emZ5ds(SN$ mL֘42F8p%E'xrTFNB^s^e;B."L=j>ĻsU j+S^yc j2=+~"Jc\fW6ͩ:n}(ȄE}79~e[D$AL=nh%f'fHcοnMH!ޫs)W[3s7+/Dh}zvQiMT4,1EuSl-=?WJ sl-P} t z2P\̜&SYc*b14ꃺMwE8k$W׭fVyuȑ#GTcɴODRZx/]#-|^KEcd4x krԌa3_sRlCv|c"xaPSF*W,sv=R(6g u;VYZ)_\7_3gm-j5c,IxhX,1k6x^'o-6W܌mÞ'|WJ:А#3}~\!7E`+ǔbY2KɼIG*9[uaP*rqH^v-\;94O6pe<@VKy:ܧ,%0/0Mu"6Oڸ9ӝG?s%$Gaq_;)wGp)bDm'}],{y 1%4JUL Qt;Ű?V"& &NYTv;*?xGS(SbEBڦ)u?ǚokSuTu(t.NiD/c?;.BeÔݜ#݈b;Èb4nϔ]\EqIսuﺁNۺo;itdPJzyAزX-<}K7&wd0E3Z"%&IsIeRiCqDݥpIT)-|.okM?>Z}ٞT(catk&0e3ؙ[|\')J̘>Uׯ\8PAMo~2euX\vR밁'IYߝ5S>ѫxPX]S2pV\RͿ6ԋ[ۓn-s ޽b Z~Q]l|c sx-PM t2S4`N>}f0M.*˺7FlȪչalN@egv̥[‚ EoyAOoTaO3o~X.>ǬHjHnuѪ it}$׆N{@s/E~rv'CD^-jع]췫?sm٦qWƔ8OlPL,oգvCso<{oM nU 2Fj7ɽ+Mx Rf]?͋w)zeV\;i7h[c~[#uV'5gs4_ u'Jk.RꔇC_J-RlA떷;G, YJvwvh,x-]KiZDlviZ@P@<-ULDϱ[]Bg=ZLVK-O.ugYbBto<ݬ”]ghә1>1ceĴ*Ɖvo7)>҅5 o׮i/H6{a3#-ֱ3NQK0p?ˮ2 ?us6'ym&a͗mqOdjuhe9w~ n]c-QkʮK&.h;̲]}9r370+;(s|nţ@dPDK~Zda%y1Nf%ݼܼykʸ2IQc WTd1z5#ֲ{ȶ:p4L(~UD !碮\V ɫ#}OVuʺ;WtRs$h$o)ٹ&}]+uA"W Wḁ$Y Zѯ"ӻCf:wkffK7r+C9Yd&qk?3-BЯgmhյaP "F|˺FMQ}zZ\_zR@*Źru\?MYCM>5).>sBܥ^NSـ [>c;ixկ9Ї~2q{@TE׉߷~NvR 3Y]^yaEqzζ b\*y->yʩMEu!޼bQ~U1{>SmO7 Yd,.It'd~|{RD >*y !K+ sl-PP:zM?lw;×~w̧d@u׆f Soi3[;Ye$815˒UcŦ}ϻ3 H - ͗$D[]$ZǜD">߻e 2 I2VhSoV5\^vU$"I٠&ڗ2{"2Q<.IX+O~w+.ٱod~$E9A+w <.|xu޿u hYM1~\Zx5_]#Ϳ=k5ށuBO~2ȊGv;y@,eAm'ܾIMkBCn~L\%^'J\ER'Zy+ݕ>xVG$EX;dj+6{a(/ۤ}Px f?>>42!CQݻwHӎ̠Fۗ^J"rN:s D_s)OM&Se_g Skf*L͈uzwXﻍiBWsnDa&,0.! ;m-?秣Dߪܺ􄌬w6ORMpco޿֨dj!T_qdf3OtlԬUڎ9w[UArj#Z*Yb⢗o^_U/m2L+o-6j%],[N)ovD(3]^K}rUg%e@T?^#&I~eϪH7oQWQd:Gw޴vb_ƽIRE_痯ݠ]FmҡFp&%N)*TOpdv\LD4%>yl{(ޖ7D8Wޥ?٧ r阨9y%2+<(30c3Ml'FPx1Lc7,ni*E|roܾZQYqƨ(I]]ҵb⢏>پEVij9tT,h93WNNoٝ$IZ{_>^P|f3ӏ/)s2O/Ռh{bDNY۵4A2rɶ+'ProLxGBȉ#k>ܜ]CCz{B03+2TjxNUdה􄄔']]~Npq Jun"+)鞃[!M:t}U9}^[e8WxB}tL̵HV-Z "o[!l7XUvbT;߁>2ln^â_#|Ҵw'h@JH|͕&*O/vo s IDATnoMjDn5qB.ūO_WsKq6yi5vd;PɆIALԊFNrlA3X|A ZY1QǞgVr2c44׈yʽNKs;ܽU+Itye$uc}{"&h4d&hJzuڪk/ڶPr{ޚ%@LM"[?gܭTcxi$:y2SRʤ.5ّW1 wT-W`Ør_.m}] ;^,8HNcG鉱D?rLٷv"Ed$o[:s,EͻTtD4gwX>B C q=LD-ɝ/Rf{@L6GDr+^Py9wia3m. sOb?޹S?UzzQ:f1ݼ]~LIL̟C"d2&%;kwVUVVǼ1]w ]KHlh2~mZgbɝZcCk]'h74?T*qV?g|[t{-$<._Q*s}VZ2X(91w㗿52^0D.h5i)Ov_p8oڎJc WVS" ]xpMv ;>ЗW[ըDZx_hz:?s/0 ȹ|+UI;ևrWOxEZ\nfHDm<ߺu6eNr'ɤP*Ok*n㆏g`HpҜܜ۾۶@ߕ/w3fza2L(ԪGpW~C׭\?aı'%c^ڥ}HL&$ڿs5UB(_j$cM6rP ;5N֘y.N.kâ$3vG]bsHVnjvaCfzh5wĥ02]lv?7GNLGZ[XɖNGtctz-/IrwVcA#2;p­Gmǧ<}RdK$rF^rtO5[5dvF,etܡ]E55h׻~t!-񳃚TMɱ>N vmͻ;{s4u_y7҇ή߶Pאָ7}նE[g'g^ȍzˡ?vkɗP^7ɤ2v wqv ֩M7y"+FFL0-n\$KFe•wԙ#ҫk$^/7?{ꬹy|оu#ǽ1^>Kg/ M6dͧ3'ԏ'is*PHt@%hj%B9 A_7qmDKNyͼH&$uo1Ib<Be jըݾ x\en|rR\.OU҇տi }hJH<ϊXWBR S0JUnNSekIfNZ}w7";Sb/:.yQfK#5vM!Xp[mD->?ϘF/?aq'F4ٯ5 j n5MO|3xתGk4+j/NnL9y mVd "n2l#ʫX^9` mz y ?@kٴ_~nnE wWMZ\r׮eoa" wb [r}\tpׯC  t;NA5&|_/"+rܭ>kր^~̿Bi^?vg3BYn^;s{U*62yD{5<]u:BU S"npȒ%{Y RZ^IIz^$/yyyywOaڸvC:۳r2AŅ3ھ{w Y~diKr͝s͡PJ%i)w1yhU2MJMOi;jʨmcdzwǾ^ ^ѫMGLsd΃:>h4^Ry|A坞/s.}m@@_B6op8T=d!aZ7w3q6BK2&Pڵotؘ,aNQ֝[;Y)(w^<`~y)VQCa/cow RXfQz>uz8KĠ Cg[_jtg+B*Ugf ,m>u,>Zb_V?o?_x#k3Q'VqptJf̏''y=|g{]?[״F$r{(%|Knn]e{1gEҕ̓Jlmvw(Elمy"Ih j*pM`aCsTSp/jHk&x+9=kU%Xq?( DAaaFsq~7!C7b뾋v{Vp½GZRg3#{z8m5_[-|km;+nN%M;YeX:͞bN\.,+7{漙GWI$5T 85Met7qoPE(ITAMR̋H0#rufKh0 ex!}ʢD/cn矾[beg=ȸk4]s>#/ahP:;jc8{褣c>~vhӕ[عޙӜ/D{Gv-yG>&ؚJ'xeq^у ?v`m6%مY).a8+DZ'/u0t#9O0u%AuH^IvKNE( IE-pLF1w"KNdCAc(RQ NL+mon<~I)Iֈ&?_Ի{^mgSަ[n+*.NNMnݲX$ݽ;dNv=7? D{q십Z*wb+ߎiOrAMc;7;wk/F]gf;טּfC@AP fMկ WٮU>{YLB&3 vDc4wE _~VhQ8Y83)dNͻp9r=GO`d:DOs^~#\ZյXwz UBuAM4 A_V\i{0Rb̿e14h?|xxCˠWXQ颚T}= %|* w߭EIܵWp`Hab:?>1~QŅZͧ4gkoۺd.Om| 4hԚk,>c{4$ o Kؚ幹ݾh{!k/([!oObD[CRU鄳Չ5ٝ~őo퓺`ŧGm2t9ZAOFjuZ`0Jŕnĕ$srn 9Ӟyґ+.4xb^9nɠ9PBw/7g$eGKUs _hy^_~"-)#5U!jQ^eCc \]AaSA̶wP;zЕ6&Y[:МxֺLLyh$gŒFw1C6 i6>jWz .3l/3c׎]_c8; Ƌ7h%"Wm֗ = Y}A U_#5{65R: LC#JKe:ۿvok۲;#NqHP4z mpIJLދVͧWݩyP 2FTתDN.|nAg Dw.9WEM:`OMQԭ lR j`-P*4gJ;(N4i6n3tw|Xo#_ =캓!3lC'U\)[w~\nXhXGs`aq?\okVڱ+aL>LU/r =kg}9zhT'5x(i^-xx[ht&a֗rE.-|=m/Ug(j?~ڲ&;?ڝ槣x3ۣ6RXڤ9Ѵ GzIԬz2g/ɾw<9m4ݾʺhqWtrЬA쀩x(g& A02٩NY).^oNܻzSgν*.5r7nI2^AK寧j]ƪ9ҖE§mp8YhYm86KA\ݚ6yyju%%rsb1+W֜y7Tԩ)giNVq`-갤G9xt2;ot<ǃOh *$ ldNܺ{cRq鲅Kb檼}=?19ќ%O8w T7,1[:cʌys9JlNDG4Ȼe@C&$80[ncWn\[ӭ֮c^.%VPe MtiC* y엌!!G^t =9-srß\<"ɬؽhDSKseYs<)/]w.罼1u#6h-tĺs IDAT'D%oUXvPW06lBsufvP$8Mgn$&-EJYEipw@!S؋Xl)/=bFZF`/:jE94kXѰNuXAؘ7W`xс-KǶW_πfMv7Oݞж@c1o}h~C U6֑m\2%=Nx{z_tz7/iMy?܎FmkySaӬ>XQd}9c][ɟNbL ^>FteSn/vrgӶnFvSbg2c^E[mqXiP)Ὣ'sڤ%'m^cgZ.\\.0'Ew?ڂ?.}9v/ݻ [(Whѹ_U6Dhvբ\_r+Ⱥ8.+^O}1pK,&a{oǎkѹǷ~w™j6̉ncxV,[_nٹa8u6F(pG* Du}pdżgv:aJ"ztuI;@(2QVʾ?rH ou" 2MwPsa('5 +xoG _$|CًRFolvIo33u u%d#_]c(a22̫PlimO9]{:?S1 OV.FN,fy[, v ,XWN'/.˼ӨW_UFrr̉`pFI*sj;:w z7`bs5M5f&]7^}D0w7wg^Ck~_S\y?YIVz>t6>b;}w>)Ibgsfn27:hSK*@F&<>7hR:õSF.qR][OorE*QctqVH/a~!Q[uvr(Z>VWB;Y`"ĹuHY9eM_40Rtԅ3l67xT²8;)I$(Zevi־aB|ߏە__;(xғyZQUM+n^<ViI5 ={75_(p\V3^>f7=C:U{ 6H rrvnS:ѠAM-_p\P]nlB, //s" مzғuVO݊UݵH/Oa-u-05>nyt쵷^}iH3**#+詣JN󡳔\n,u\edշ3&|dόuï΢NOv^<;ySNjN1EA-8C 9!̰<{IDuثY9v/뛷`Lu;4{ 9ּYحf=(uwXשf/bQ͉ŋWwT1L}ygJp}}>oˮeDVaN[ЪY/qyz^Uge^~Ǎ_iJ3}i}DB1}:WJfұ ~cY:|k|:T&9 :ZY(snO6[] yI\1 34EqWN]AL9r?O (neiF\ccī7t'Q߿q|H,3Zg\߉sZDn!VU*)J{Ėř 9ZacN|ՃJ EyI.n޳6@5.]jJXO Ƌwצ3 )ؙc!gM\"x!~oO?jhEA2W'VUN^zec8q1S>c6>92'^Z,v/e;{Ű~mn]d|k_"yJfmT΢/ W۷v_+2JjYj*-)$\ _ҷ~_W5VšWv ^Gߺf}řv_a}gQ W8K+~{b]<k"POjr0j&AMD Zj,5APzAM֥52F ϒT9-$ .U4JGQῈrËio5γ9`?B1J1!$J"F8xzfM.q+b(bP((TId,)T'_&2oM\,;TWT \2BӋ$,t9OU1CL1}:"Qg٪P+{Ļ3@F{os!DF43fR /K p'oqh'/6(X\"v}3Q[lۇ&#>~[T4%QĥSCO j{"rs1R|,G(v9OrC9,Aj1̌&D$r~՚J#Fpm:᪱0=P(n&)dIyܲ΀dZngz% ^"u N#< !5bJKYGa(A = òP>Ik3lupwJo&UE>A%Mp8BASBKYJq˾s!myXT,&g"M5&;JVJQ1 kW):&GڙӘ.-I8EH=A)c2ҭJ|M~MRsVm@_a#L2!KQDR(IQ<.Kۗ';feK;ɡ>KĮĢiYBɮLQr:rVOP ѫ&HJucq@(壨fz`R ۈ')>9OГ >B(LDCTAƻi ׳1"(#Yd3Xi+̦Eo0O`8bbA hrZ oJv*)$8vmBfQ7' Cbhd߲POpfNy5Ɔr]t=ϗH;E zTd+brdARY5'~3~3./9!uP INOUr*c?8i);XjYĶΧzt LT;׽lhV.z八xs*+#9*TK?@jG t=MuNjB<ȇ+i+ z= Nk4\L>qO$D("U\@ CQPG=I205į̆8UqQ$6Bi5ͻO~2β.D')2I[M* IJY*~Ϡ&Fe0&LI˴U$)D܇o#X%52f%}9eT֔j$E]PnO/*GŘjmI2֙ǧ9(+^Oi6b|ru\! 4{KßHĘETbWcmsDDbG1J`N1&Vx4)͖eT?E%1OB/\g^(hr>á;ZF@ J^Q I%!˥yODXac%_<+R$"!H8"+?X,mEQKUHF)25ݲsn[^ LM˜eޥK@$r>1'y V & Ȅ[s _Y{?z:40BHaYQTɤ +Mޱ%T͵eQ\ޠymġZ2ؿr ;4 $G ļb& KĐޖ\9K"qr 莢NGL&S{l-g88Q~=H4%jӢNֽ`-*hz6z?1)oS;b? ݎG\D$<\mKT_+:nBSª'wN잋}鬲N/ m?(k___z~׷*?*1()Zz|Ak,:,rIuprf}o_kQ|aΏqSjJ KvRˍ;8G=]LOg'F 4'e|.Ξ1t1W?h0݊OIHN-.|dۄi,psϘd0dB,"=Ȳ)HOZPNun&$m !RMTw넒P/ e9/1!&ԝԔVb5l,xKP *FAo[#1$= `;=sWiϋqv"RJTnrse/ܭg-4A sA7g.1졏P_zwӁ+j0hn~ڠn-F u/BRdJ|/wgkߨ&O?N脓G.L&Պ3d#:[RqS`B}Dg ɯ9M.[:ajf5Dv_3t뜻ɹt} q N#ERz@̕2p]~ui9Gr,7oxDׁ%&%DNa*Thԭz:yhFJM-3xuaǩ0sPZҮ~E%%=zYu{d݂^ٻ ~e([aWFyx41 -@ 錖MCtk_rVm@Jl4:Kg %LT4!D6-NVw:#}lj5kUlR| "S{Q^57YR)Uy~tBP[ƴM$sD@ghTAMx:Mig@$pP(rnO1T&ym&" FC$5=詣?_UiǮӿG\ƿ#e2's^ C1"ݛw9)7d*nPOuf#ӫrܼ-).Z.y.<~%+$q:4 Vit1qnús9;?u;UgNʡ+qO? 0gM;oƨDmT햧d"T1#n#jɧ.NOAim{ 紎͂02r#?+nT` , "a(DsFi]x@U2$+ϜD,LȄ]+"N6 I4.ѺT{)X,{~&rM!|<]) IN=OoDT|PUyRLZ1V0 ܕgH<|lMH]NwFy;q8u Җ{/Zh)Uӱnc-%ѩ2EM!'#X=&/|l#/Z}ƹ,` OE3AN_8G%$ALdT FS]?IQ8iT[ beoHőZ8:E%LD]G ojoC:I/HQY jBCFQRrKs9?e)LK=|Jc}gZ;(JOdۖO~^D﮶ThEUUk2u*FƧXتÏs2~R[>ɶx,! .YyΣ^AoGYQ[$H|2~2֫iPR7l2s_שЧUA/r1-Ey1蔪I)I3u -~[wJr*SIP;i`aľ޾];uo#}7YX0g5q>5lJhoypxI>=GEnVU͞LL IDAT5{u:,u8SzLUɺiFfxfk|̈nuZo{[fh*VhJY*T04K2]d(*䤜bu\ąuD &iY'tʟ XA[kk RKKݿe5cZ5)ֺlxaFECj{TAMkT脡};UsCƐEq9x!Eڡֽ]lǑ舰JfS僤5u2)m|2JMabKؑ\LxmC2*D41M„BXr#C5{ˎhFz88w*zRċPtLOyyPdCX]jNu)3uS6hLm%Uk)}R*z-(O7EYhfRD=؛XRu+y 7fPE+o8塏9w  fU*J [5Yro>XO}d;(BPxKtzsξ8Z N#7o}ܚ1z/D 53gLWG"urR,y}kn4\zMx3rTw`Pl4贪['NzL4eoQ(Je9?YS?m@([>,ڽ3~.2o2@Qj5JA,÷n5W:Mz['W.)gk@c >9ITeѽ:I1q͆ioN.n߻ݷg_ZT)Cp@ j@E:qCO{zIxy¾rٞ:-_u)4IB(AF݊;J4Ԥ{G/F,}! ޘۺEc)"`A%(߇Ћ{\5f{S#$qcG4wz߱6h[IH쉝FLEaGΓ=;SΕ7~VL70=ԀHAu*-+qr9J7)יxCT̅_n &T{l{,.T["1i/P,$&۴4K;,^d;$-kmє;b N^fhEr!yDnEݎǶ܏^`ʂ='c=?/9}UUO?rbNC RWCxZJYhЉG%ZIuű +򃿯wע 4aJu}ik:糙Z1xc$bSORX\h]3=fg,Z~SUo~//8߲yKy%ڜoSsDM ZH(q0/⸫ܠ=Z/Y#8ܯw pqc?>ܷD&HKPSBr QmoH$wo|>21y&p<)BHSQQeP_lh2ɖ[ Ĝ;IKd GaaPN{!8v+CN}XPSc-X-H%ȈKRFW(.|T[qGB߼>|mhxtwi霞W_p|O:ӓ6W]ʜ8 +@]ƈ!| yN$ȝ,*D~"b?+Tz#Awn/Wo czEX( s2C UL$vx9g]#R8d)ZbmQ4Z#sik2(Ү,]rP9ayg_(D3MnH'/[A\dd,B>o1LcR@P6Plд}4=߸<4٥%^*D)Dk4gN{fQZS=.vӘjzWUtʡ ٱڅ`G:ҧ=ۏh\&-FH/9<7iխҌ!{y9jEYo3_SIݼ&{0'3vԸkib :y6O%OV׮GDDIANAVA|\s=ܬgmtdvŎh-ZYs?kDC(hi{TwR߫5)>KgUT9 ZGrܹ7.R߲ \'[LLuLqqQAٙul5M&TK?lG&0)=ȴb3؅JJ5| ,RApB-13#up!U,K<+bm| H+RO/Rbl={^+_CHKqnBM =dLAlAA8w?d'}f[N|Z17ӌa 4ւJMSr >}`hknRO T Jcxʘ-(q|kԆ 0^vճϿ7)[I}환d(kMިɄggѹÎ]@!Y;S4дt%q;d,R_؎k[e:eG+qpm4چm!aPTykx;E Ϡyt@ݡ^wUى!١Pd0[Yc@Q2č.RRʥtruf& %H_ 1}r9/ZzE[Pb*?tJvN%E41RԮE}cCdXpEa&@90R<+ޚC+P5{ZeXHg"듗9 @I:dN܍s:ݼ)uJ<~rżC狘 Eip:Ѧ߄)J†ZzhRĞh-&L˒īz̟|؜Nh{]vqϯt=K 9ƾzT1pZ98vhɗKnjfg%@gV:t"3)E,4T$mzMKt%1g 1:urfZ25v]0(%?~ZN&LE1㏿1O\3p4?ߑP=rm *ޒ??Pub~CQ_(Z/$82`2âjE-k 艮RK 81SQRkD,h Ȥ;]ȹH u:<㵔XDuiZYƎBȰJa>t"f@5F/ 7__ZN^OOm 68q>lm*RhMnB߉iXY ({˘S3[C &-섕qo-~t=s>ДIWe-Xx{7׫\ L߰ /HҜx ;7)Q1Z谻uqcAn0XI/2tIDJ 7 2%k:Acp_̠7?AOMCשEdHys-`V-CUyru_Ʉ:b행u[},KLQ=+1;pIƱ蛬NG!t+g}G\KޚF>ζM ,Mb rҍJZHCÔ$4rk?,7Ï(;x9SW~t}7h`b'tJ}+۔'Т߭ יFzK*Ufxk`5~ P§Xi^w~1'UlEV};m\)G6~夯zs9jv?Ӿ `%QxyS;^|ڲˀ.yx"O}lMD~8Z: +G#hiq?~Ibv'K **&ҬlG4QH!0fTI j]_QVVaI Dżj-P˶}*hRu_;hҠ& , rahJ}^n PpC#a5;~Twb(fD)JCQIEQJ*!)94'(3pg۞`F@ySSNL0WkFhSicK3+?,Dky ` $&:g3K<~1Z Cz9qK$ ynG>-z:h9Q. Cqmpc>DL2OH %lcPSD`rzw`SW&[TibH6-꾗/{]vČB7hK5ȁD_(\*nhY>ᯑxRzǪnvZLVQK酙nƿdp+w1E]l!uqq_l2%7lKF-#|Cnn ^\p@55'd.m.!3bta6l0"T# 3(7RH)(F2VoNW+^}Mc~5\mB &\m뽣_$R;[etJտ y/6wsʌk:x5]~ּI]fiHM<>oOY0}vv!2,i ڄ xZϚeHFw留y%rP\ҧ&Bȼ6ieCU`/a#'soqG?-;$3p?r髻[aEAMz{%Vc|!CBy":uxw zלr?g߲K[qis9OJ9 5_:,Kփ;wQmr.HB ) D!"(`C@"^/~.wK!\ }ٙݙٙٽ[QZ eԈp>bJEQqѣG_ppzCikQA?ܹxEngiD7Q4OeOxKH`!ԌءqKdLJ|(&vb'.I?j59,,?(N[d0\%( pqFp"g&*n7mgPW h1/כa8RRJK?z[4yF3. @GZgx7W#ˊKO?5W^8IBqQU)cZD,-Miȼd0ӳ2:du($ ~} wEsN:][_ڇEZ;{^vnNio; yRw|'M7&݅}qb-89K\!š1X4:/XcҬGMKؼGA"\Ab9{A=@ڿˋ1~zAx B15D*LsH1?aPXUBm)iҌO4̿ IDAT'r+Qi"QImEGyVc XuT(MUbPDE[Ƒc Ị*j"‚fo Fc $napN=bpfjJvqC[1I g~JQBdyiZ gw9YTw1P1Њ<kllYeɔ>2tqM1d݃G:3G9ޮ ''(J:{2myu Q0*iWw0@Q1GTB2ݑ#7Qs7zx51iDNxl"Ї谛UE|Q܊u/DS-s8ɯODžlh"\uM&1c^LaC'B>NvfW?"&fjxB2TkVZ)785ne&f[&k$݇~=_3ܯ^9ttD,y8• 'NWU%2IoΪKtV~kF]|}Y^j4Ύ΍ 9U@,[U0*P&1FS@Bd܅ANG&Y!HIKٲcϿb|N ѵm{=ݫ*ee2Pp4a-EM!!s_kP4er@f%i<싄e 4+8ڵAӑ%+ovgX7- :E2itЎ&ozNgypsZYt_)?Hql)3Kt0^&}rc֪1 Tr] wEA9Պ3 ۺ*;Z 0 oYB2z+LyF%dgΊe??2Tv?557Z@Xy[凍{dUH|cʌIv9ل cqK<{#噁L=1)=@x\؈?3G$N[_:' 3r4x*Q$DT?0H:|VnV%qϐ;y!!ƣ`31Dܾw`źW_Ioc8ЪCBg7i LUw<,HXwY,bH/CW Ss_7'%"GE zRiɎ~aRifq ti m?mg n.G_wNҕF 5e2حf>xe)(-.][Y2KESQiAR}8įPU6JԷ KEī90Կr=VawmD'SL+|QNڹ=j:Qeʬ2siHަ$]YM2Zj1Vb~yK۶2q|Su}PZ֙Y)|UʤG-FaIQ H~eF1乓;28xFtR>..-q9:RO(/W2SY,7}EGT[v8;'*gX N5}ݤ !ܘMF(qn<H(ؾcXHoϖ4=_pأ9 `!jZe5)Bҙ5m|[WE,\oUF#%A9F9TZ"DӾ9KSsJ2 ܝ,S)Gt_kԬh3|vE6׼.j-0W_Sjjh41;n-QНE* eoBAEF#a) L:ɨP=}s ,S\U쀅я ZD5T0>ښGn\R Jŏ{i8>~x! i? 3^KέY.H6p ֛sS#ͷ^gX'qEh~r`2~BUr1π) ڃ.h k KTkt)ޮJy$b6P_|\jR(ӑF~ƍ3i[yBu@S/gb#'+NiRqL} vnhUsWGXӗs'T: 2|Z~lM0 a2p "8z*HN9es]M]#zw%٬fIu&F.$emP^]:`E:DA5FN{봛)#GS|hSr B@0q~8fiտo`q|h3l)᲍2/n ^=]_oG'?hgyx+snaI_9|naS Y*ö6g?>IPedXFZ2m5&?ܲY}U[1gS*KV_jq`&wjVʌf 9&]gyuM!-m=Q(ar7~=&߾6]u6k٠s-}d>5(PEY'SϾh{ИDMt!mI62 ?"rS_QQvICęF9. 7C"M- L_sk_"/+{mlSC'Q۶[ O,\znsSoDp؜yYkg'B∪̫_Np}Y,"ŏbhE譍HEY֩;tRcVW)~"v䜱FEvhWqRf$+HvEk~*V +/m*4}B>0_"з9fz:ڷR=(R҉ Ԝh^C޾f).b22lG:5~DzaZlJ#"&P$;}H觋 Y=RZV #"vT=!uitJ Cg ]z[E{?FI/d/f2J˰٫YopL:bC-M'>.?^u2֜);m&εE+_7СL,EM7 ˒#׭N+7z0+?ʼn VlJ29$ 6>TRi׹mI$)'<ׯ4?w*LO(+O nҚ $L#Ad1 ]YGxžz"BOŤRSZ_,b_w _W﫚wNG^v5a DzjΠۆHk3a2ƺRkE|fP_w`fˤ(RB-IE.:f殮S'}AmOwǠ0o"<.p /'\Tش}ӄcc\]^x*ɨRlxd }zٗ6RYBqOZ* 2aS7,??{7=_Mܥs[fǾ?j׮)t_/?y_OA{h~~RBeV-d6S]H =yH 3啿tXtumKLTZ"|ۖt %Qgtss?.^ۉ7' }f<g!?Ss[]vp ]JɲZ>Ђ155E`S]VZfb\!t|?,#;x |.q^}m :M,`K'/qX ]&WIe wS$fLԹWQ<9(>ǷZ.#%zQAS1M2 <ɹ 6Kh 1M&bhZz{l:{ KŊCX?;Hv6vvA<ש*xXp /$I25r~ SYTXw3DZm_r}]}_IBoncIQ]5kX1wt"\6"&nXclL-."i B@o^7>H?6MHs@ #~xkP`.lڇT;A. U>q}E2l:|SeſJ.Gr7nd Hn2a.]FAzi}j9ȬYBshU˅kƼԬN2&|˘) :]pn@QtrIf=)ָJ؊1mglSfQj*}=E \/N.㸡 a5d" 8ՐXN&7F?E!_pSXj7~tu2@`j{nă}fN۹'(_Ġ)t`q<]W%-0t4JtZ}O;bF~c)ۮ6YlZ $/Dh5VadRe8v('趯^<:a8M:eU/edtRH ˰Xz,5ڟw7܌q1qVgp(Fm-&Ln9_[mwsQgRdҵˣyP^M䫩]⹆U^Jύ|VoՙfM{CK>[Z}-&¦?We5Q=&EEY 5Y`6~$ba-8bx!9GN&c(ڳK󅄤Ĕ"I r>0׽( :'AmNg0h߱9~ޮMI 㷆ݙOqMr'~YyO~" tԆ0ܕjfP[Ly s(VLYdİ]-hX8Nz^nQ=JyV7|PZN"22n%vX]7SۣØ:a/-{ ;V֗SR6k:1ء<6 IDATme;dֿ7 (gM?x;LO5,_<GkaaI t EBH,rt3Hb(EU^w/[ǟ{9kNNNAʃZhK?g\쒯&({=|qPI/6jo-41 #|VjY;f0޺>HC K#tDtDh4՛fd>]oL[ӈ`XZ&z1azԒ|(Lnõr81Z 4ȕRs[ŭDZ:ki=`\ٲU]47 H!)-Ξ4TSFޅ5gIb#ҵˍ:UNqG.ja>#j5vqHr@lӳBp+.BH`8 ?>_ykA?_{&$逪c!k A maSItV{qS[G9l]|,_oխRQ !Z*\55$ıA- z~^H,> K׳ ҳK!82R,+m}4kG蔚r&,2ymYKY&trY]ZZk4'ҶkA "yTXu֘)VGU]LE\&6RT)JZVkeҒZpAjߞ\T8m33?tٶvm8&XҪ8b5 |u2u3 Wlڜ*u ɪJ{,"7Jf`l(C*aG}U6,57wܥ}u9fG5\k0Uony j]*y=qwzwԐQMc;~r_j JU4=Z -C-c ͗T[1L5aD.#&e=xLV]D9:B8ӂP|y#aat<7 itEHTd~ְWWB)1b.zu@Ӳ?"eF$MYHq 8~`O#Ʊ?Q],uJ* `1/#(2>bZqKu5rVRSꋏ{ɝ[Zl@e!j4T?RٸYaI9#fܷ Gm[>߹L1 Jɱ^RRP,+흳mYi:hp41C_ؗIu֏gX]RVnu$YP v6Dwc,Lv ^.=u偧P?FKMR6 VTRlp$Lh5ZV/UJL!e%eEIIVqMr {u+]5sLch.Jyffg';;W/,.KZ*?]Lm7ѻ{oʧ_|?h>@վ>@yNtO?(ZIMǷkƭS}O^!/5Ãw6/s;uJLv!/*w W^G8={CU֐L"+;×j="42)s9ܞ]{Zy6 Ԍ-;%_-fK[L5#BCxv86/9.#2;4+u:ihhVHnHo)OkP- !ܒbv(}.< E@R*7%"w 7X&! Iߡ/ K͕`GGpfBy}JkŲyxӴf;Z/ݴ?Ln)pzךv$9p/-0d)؞cHM/C>u/s ~.ߤM\4ԣQU R7Û8h=ӈa G{3~nTv9zN׌vlY~='\}l:Ȥ%TИ>u]ZRE~q61k6=?ROw \|Mz?Йl}]9Qn@$m_,Y46q l}gKT];[qV,\fjృC$eS,C~jcf!sGӟnut}a!acG7{n=h.^POH_&L3,78=2pSbRYL򅟸Y_D/|*IQPL?'άpРR(M%IXF(۶ϭ4tWwm3 x,nZ3>ws}ܔe߽V&+hX6oW]ca׮fœ%_k]qQ=M'a=~1s`k5iN\ǭr{dTg02}a|F],kϾa=~gX Qv?K *`A7 i*W`Koa87o߭5-_|e cqaR^s .ϽU[b|W/ccԔҲ@$S~h5@] g`DMЦZN*AG\M<0iBJR>­+'ʤ%%iS9y/j{e^];x!F)ʋK/\?JMEƲ'w;X( pw -='3Uk&!f:_?31noDlVhf|nqnqvk_>|J06OȮ^&I7|[7vݟɝ 2iɎs핼_W6aąd[ʓd?. L6q #U;{tM gu:4eFDQ e}4:3H:EgŮ^GsӒl\.K/ hXƊ~~3E fk\(0\ LhѢO95vо~B|ߚNxPbtJ+dYgꗕrUz݇r\4j*VjVʑUQbޘ< 8|ZY.+/O8BiFAb0R^x߽W^ߊ>ٓbKonif%&'xFWe$]?Z5I 7n]6~no(é"PTU޽|l7%Y;0"~T^up;8"iq~ʭ |Zaҥ捗psq|#9efH@0c0 iyYQI}rZ]b}q1qn6GVJKssܻfW-:"9;Jt:*[6;v޻oq~℗7EcS7[IHP9ydߞ}b[gfgxe1f[Zv . WY_jj̢pPnhSLEki @dg@&DM@ }+bx?nH7r\;06;/ֈQSN^Nx(F&SfA&I(-$*)@t="=@(© RW0lOE)JBАC8 j2E@TA"((gXή&Pwn֥h.ԴPE>J =X @Cm5FR5ēd_z)Laؓm%@Z œEotn@n7vcc)Unl YHnm-oO3Ug{(pH2 K״Vz8&ABDN#b%ˉx]sXuJ@νQQRRJ᝖eLVfJFzn$S1mEC`;Yhb1ӛ BEd[}(jLIOn%Xp@x IDAT`\Tҽp"PѱZhSh/Ǣi  g\N\J햩'sϖpc}~9VAKCũ IEL$XJZXPb7 o>t#Rs#RHbj@&99!#(oH犯W.h8tcJ]8rԐmhb-})Q0xO:TZ1fjO& jƨZ[ٚf5(#bӇtDXFB^B%,$ʓMF8f[l"?}Ygu5;7o$H7"Goߚ GL$]R3\C x`:OΚaG'K8*|wvO|9.7jz+aNJHM([ L1sth@ZdbiNvlU edX4Ą uGFQ0cgLOsF9{=7uLb⺚ul"]Qu_h"~¡#ncdr*!0Rb]$#sjfbԥvZlwQ?\+1XJkP7'S] 6͢UOR T~*I?'{-coJ35+YkT^hKI ]4.ͣksB;Ӱ[&~Cyª do!utN;I:ZP~OFN*ՉGړ\6WOFgBXjQ9ӠhԎw:jQ+ [Sc*1rIt]SjZ!8Ȼb/W.g-Ipq!(4}軸c(e!R{XD0FEilcVDޔrp~*?" ;U 7/k鐽jǥ9=2jR_Q3(5%^\IR|>͏ ]v~l(1Rve^?HaT5i Kvlhb-0DW[].*{I\.O8R_/c:25}=r[gVp)Q_I'!PC5@O73ԩJtv]Rrgl2&,sm/62dd aY 7jq6mtu"*ڃ=x92 W+/:xAѴ}$1Yԧ3_|]7ֹ!4MBz*=*4r+:O/"g"8?yJ@/s ۙM( (9.ґۥtW:qR]Z& Ε+8R(X]Μϥ2*Ibk1iVH{">#ZK:; ,%@mrO-.e kt"GT3ZQ<1TƛכS4|>2އhψ);$D?[yOĽaM u*vRґӈF%L|Gil^lUth,g!/V<'M %;V3$HOorXj :2/ ~C$cXȍjIY&E.H)[ζ5!WEz4fYd ¤L$u4h$/iRif^#?[/wd,wՓ48.`{ݒrņ3g'p >ȴZ,%7tp,66di2pBJXe}(Bn@*#qlzEJ5cr΂ Th9΃+ob^ZZ&aB#NdY(}2ٻ &>๋{R"-V(Em8 g pncca3d谡] )B%mwׄ46mSv /ݻw5w{F䅘MTōW)S 9EdیPSG\J UFD<O"(;$1.ADEK8>|}!Ai&[6CCMM&][-AM/;?]~~PΞuNnT:L"M2<[^Px,o^((*?!6EIv \?1ho}_c{JF.\y) )3Gjm @s7&!H1N6fofOm3c/rڕj>ߊCyPj^PܸA{W`@Wn`z 8FGYk|8d)2j /M Z8%MItT*w=Ī`,+03YGTs,SJ +MEFtKEMqlFe84uxdJ@ :.c<]}9lF*]wzN,9{3ljnVGxЙlVK_>C|8rH3҂Ƕ~%tG]LZ= +*+rd+ו{OEDj2Rj\x<^9=dj0+FWHyWmwz6@I^n3DRpQ9*Y'w(Jn:=cڭgwo>|KI׮QX?KRt-.x"i~6uːcQMyqXTARni d>bgn/GulCGMs]dR庈\'I)xs6TC%)b-&s4(jD%:U JLRJ8Gs,O"/)ʘZtdF%D\0߼:N7|5ϓ-3HLDc{cPSXE`sLKs-4?n_#Y׮|'$ PϧFVWn=v!q W]CAMX8qGI?z^ fE^=䲙ܡ4z7}$<5ZW~"IOP[`3$KX;{b)ʈD C[ţ]o!uZ5',~on[u1bRun |W`ʮra!sv; %Yߎ5Z /XB0M1Y?\%ye;tWj¼(.\2~ݖg5)r d҃X`$iu$Ґ*PNJZ ȧijlP2"/Il?OwEqYOMVjPS*Gu2jHjj&FC}yRck'ICF'$iMEqJ)bs5T `DFTcDUzT'ijYYuoY/:$ֻFF_g勐TM[r!宱ߊk-0GZOٮ V4:|5V`r䧵.mL1oRl""k.?J}@%WˈV.+j4jG&]tVe,y֘5M5~)Hj1dަF)/I#SΞW k¤wӞ.9c`=3E sB|.*sv6w۷n mMV(iEaxZQ^ϣMHjV嗯{d4dyxn4 eͻN||?~Pm۠4{w'v۱lPl $ ٴhi}J#kD~̔X/Vf^Zx[&Skd"m#'Ick00hoH뎠$$־Q*$'t6c= {,SCѶ }$̹EJ), j F R)5Vh{ 6)h^x@6'F6Ňr\##7{՘?O9Kv=W8HQ)T3SUkw7.uAH+== BGQ2Of [vrw h\!?ԹДG'iؕ55{b؟]|*c>n{vfhhU OՅva+;`/T;nCLg\YaK<]'809>)5ptO~[ 梠#-nK 70hySW|Vj$S9 H}ٟ];šT8zmBQ&Sg5q*YH5#+JFgO#lbfuxs8|h,)dmWQevζcsj'FLޓB*| vW?lVq{sZzPt >8峫 m)zd3xbA׈] PAP؀.C%|zvVI o&dJ1/=΅}`}4M܇MFɝZ~AM@|fOOVC_={ci֒TGr_:ؤS_S>""+;pEVX>Vt#D^_/Gzwǝ3LM8Qj G,s \mL6/̯r,};h(BV=ΫN.)mYTr|;[Yb׵" tsEf5}.) gqL6x,Mvu($+.ChLy $I#7G=bn'Tntk'Uv.M;nr dPSEjKK@pRDD?ƪ`oD9g2jOF sy[٣E|G޹5eWh@bƟtd/Ľ!L5aHcAc_v9u,[oRNYsTe_~KFed9-Ï`a17?=*KXOe *i궲R=O$qu05xBwн@z@f&}z˩kٹ&}ײQ/w6MT\){w#x3~nٸ'2E}'w|-oA l4bFV/KL{r7Wcc8cŽ[VLd \mO3'Ὄ_Urh[㳸Uvɵ"6ڱc[d yppbj!OfJd&d0*T&}ުYKi 'x:2L:Ag z,!9,6 ) v٢ ^q#>=eTKf-lb/=wܸ}5dth\.AgԪC'u/h-Pz2bEy*25"/r2]~zn>tk1 J%&*eJt 1ֈA;;/_lJ}N# :ֈ!5ydW#+{uhӺ0J~܎Uzʔ4,X.ҭ B\Rژ⶿(R55KU4Epե|&)DȓB)!qb^TAg7^df5׉_|㾀kZ>xnѻԏ䰈q$rQv!?YUxB/`U_lfg b켌gU/a5Z!|2JҺ')~n`cSDSv^rDxFed] ٮighzAvu25hF>=j6OuDPr=GP$y6_ZT;""+Z35K6^R5G u.\!$~Oا|g8EM<2NL=k 10uIsESKPM)m0֤zޣ>/}6q^(tR\ѣdf; ؏^;߉ c.eW|;sN7ߛslk̤ULnUVc\qDx. C)-TyiLP1vɆ1| "im/M#J zj*D=v]Mo]\-}(gko)OGۆ lqacT2)%٫Y #f?g >xXZx-&uhd[-u%u' YD5+wOto7ؾk6.!ڶI Rs)9;hTEZVwʔϦ^nڏ:yug e`rsD<;w'14&/rHs CBikw`=WQYy<n`=@&jnہ_vYuSBCw Ҵ=E\\JydK).BZ?e!:0Ի'j5'wRJ⯔_b|BſIP; REW(˿~3:M =kmPeBBzvI*z,!%4CWmp#Vl߲r*\?hy]:rQh\Ø)ILӝ8ʞ|e0?22]V 3?](/P\О%?,!No?D&}}g@~ߑE1䤢]w^I_.-EM\)ҤvW{-ȇsHWא‚0O{=)"&umfdQ ':9_GzHzAL+yL'#=OCD#w]Af:"a W dC'=)R Ah7Ĭ=2a$DРߔϽφ ] UcTpK5&#jvUN[ЫTl7٤޼|vvB9TGks? %).21{ח&#@NT0Vq s4VG~hDwq {XC

־h"}+@ *2g~Q7uךw 5e?9xnB4w*$uN߸^gSβc 2?i߬魨0o/jbٿyX˭[^zMZ},ԟ?:S _Px'#{bRyt.Aip\bT:uЩO)_%cŋgmPu]{>gFo+7<~h)xE63Z"̥3gnSPkXTy$6SێygvRr,kăі&7y$xZT J̇O SLֲ !30c;4Bzk%M} L5eT'v,O '" b#=7zKzyezbO!.\-c+gpXLLp2 g.^(1D(u4$:9N6AM&M*JǻK. .QcqrFW2gi2I:5Hwf7vKp`@>H|c ]ctA)ɪg`Dڶ.>\!C~ڌ>l$z'%:MNq٭fOA'}kDW4gEY6rX9 ,Ϣ_CٟVL"m;5RICfR#rul}k AMTgOlN]=NaqIOb$̈pF.T}tP̋R+F#B}7&Y :]z1 ];v=qĵ׬n޾ T#'V60UBo "7[1z5yh ]#}[g跾uiF#I@hfe8~STԴөsK[ZYƉ!)a|s{* ZM^? 䨨tY{+gll;2 -9͈֣B..V":P @Jj/fCU":lM;B^;\K%Ez }XKRkEY 9{l*\55VC"+l9z]3a ]_ڭ$>IP'3-*[ Z@I@^{&SV^埙q)O{2yX;K1IV`=/ytﳻm2]>8%l?=Ը~S:=;qO+iELFqDF!"kNu=OsE.LiIl"ϒӫDXhx]_P7;f3̕KU;Ļn=6Gm jpuN- /W8Wq&ya^~f2\Т0!ؿǩ.m=_=9np?uZ_kyр)4r?$_E-"c2u:zr*V+R#huRmf( 6YzsoY=nTlsCuۥC)PQ)DyƩ֨l3o޽C'_ 8eH݈]eyˤEDSg1H]"]"(% &}lW㤑= տIfn"|ͯ@Gxco$JaIXj1š ( Gッ1E'*u]YaAQct!YO~`@iǻC\jM6H^UScU+˾n,:)&Ƒlj;'ˊPPۛbMi"+^{_Jto"zҢ%k?Oui KڈaF.7ȤTXdZ#2mLdʼ kg)_#J_YLnX*x]Lܝ'*XBoIzt,s:YT w\ LY8g~tk. ~uaGv⫵_:N;> 4jߛ`19~S?wY#-3s1xNlg\x~ɻ𯅈uުy+Nt*u5]Yw::DlTZaҘg.Y2J 3j]|+}tR$県>QKl#b kK CF4Ӝ{Hئ,{yOLtRP#Ujz>XV|[9ēQ%^_^W\~,FYbES!Luώ29]fߑcJfSeN=|N=)+;_~޾oOφַWߓgON_8 OS}'WMGAܤ4ŌI'?K[^yPW}&ٚ:-+zs)_1:C@Pդ *clԤ7jy4XO oى$ DvXU8LJ{f,UXb&p~A>XL%?1-v¢ YJe:.G.7 DHavA}#[|4\62Uyxۖ8ZypvO`Ö!NZM1d JgZ⩐||NR_ŧiJ%KG@r?J{@׸ 6O䤾\HQv39osh(t{Am,SSEl֨ٛƊEtvYkJ|D&[zjR:l|70iĞ]z6 m\Bpu=d+vMp?%Mw*9A[q]HhX`:2#krR(%^| f1 .2E%-srrs:,ߺqʎ~>kҲjV'WHlr* Mi&]bUdČK֏|to7ĉi[/x Kֽ6erJeBJRQ*Nk%Rpο ;[^.ǗɔQp̝'7S m%S(<(VMp\}Lr~ػQꅛWLssҨl0hN!v詉\0r(G"u%xjے>aM{mTŀ>LA箜ui\6? INKFJa5ZBgJWΖ[dϛ>o>^D-[l^iA>4-=bs.1r=BH ]@CD' %Dzޒ0i(8ځyu$P3TjqY)i}U*VS[<ߒ>{l|V#k&[j uÈ/2ӗQz!GVuIb.zzO{cҋES2"oOm^6m[zQܫp5i;M*LT7>iqX{Lhl|:2 >c00F%gqtfV3,7\ص27ؔeU='|a>qjlƲx86@ɒ#+Gne'x :m꽗|Lc}hOSF[3P)o?ԱMG<'}$T}j4E?80m0Tp\Y%( WOKR-Ks>W%gvHɖ$VKשSOs\_O^IhYtȫnstzKJΎ^EI}9>=t;׌='V6 |k&O⣭'klZVʵ3S24moJ B&UuukݫWz`eyq ĒKs@]-o3eWvu.%EAMw \YE_4oђIO.˪:#kO\#[xPSRzbDC󱀢SsXϠٟ-`И{NXu%ڽKe BQs>_NL/D%xy㉰a]PTD@-Z]\)nӲkw.|q1%rs+`wJ2%]7QxNxI:Yӟ>3;tnKzvؤw,[Zuks\sW63~#φ71:uT\GN>v]#3oL߉z[|6Bj7KhV"WQM;+9yDն;w3Ca-ޖƑC|\ɏJs=»ŦNrKΨukբϸы~5,RDuzwtAоS\`vתKX^w܁7ͯvp֦!(e m8GIeDin f,:7'6'yn  .,K#9HgJD4(u'f[P?%>/c=ݽG1xWKs-ig&>Ք0㕻@>5D147e ~e^}R3?[ٮi8{ǖL:sgy jjՁ0d,i6cۮ\{؏6-j狇eȅL:̶Se2%Zvֳ z2ul-p29|Zy܁ٺw'?تH%;^DgYHUBlYaޕ?#hڸ{ntjz\!7H8l>x<Ay\޲yK~0'?Mݻ$(1+KZ~y"jƊOJ; a7-s^_/+987Mh:s~:\j` y37Y]+O~Wq 63۲$ vί?gnAQ&|N^<ڳҏ=d W/|0+~[!ؿVog{T7fQ.?`q. :㲦Ӫͯ`PS9p(TzSi *)Ks?#lgBOtn%'1pĒً]]F yؿ3}$usuѥXZU7% kg8Ϥ2骍xVtr?"}"Ig_6|E=]pzYxBѪe Y4V7-f7~Q" 9EjeANZ ~yUQNn>4S)g$۲,=Ks诅y>_d0 S%i/߳n,݃o=/}Ku~IM`qx QUr\R, /+u]_ԷNn\'W*!K r>濏+G@?|շ'uNΥNsi?j|HPC T&/,x?6'GSҒ:Au\]T*%kLٓ-۷\v.vn+G|v3'Z$8q9\Z%Dy/HƦJݓ(c1Le2=#jקQӽޠ0 EQG1Aa8#B#T IkVkd4DB#p64'}i0I7'@Y SBѲY )xTYi4FG"?NT[K͉Ky\ K(Tg@:!TˎƅWߕq NH*RfG֝4pFGE=g@P[@N86^[vL9f\׏ND=5jGYlrxe:|-N'$anF gq܏zXexfbO2br!A!M#ztCT*^T1Z%JcA*NFH nӛ`y/>)@~Df*I'8ԋuٗJ=+ NچEw`7'5|qU%ab{YBu9a5 "eRP;nRa1p\pNHRr{Mi{BLYx<>kN{7:|~Z×$Ά]y4zl '$pva*KѹYeY b:ukԠ|(uBupp;:InjwG%nJsm Ph1c|nJ"7;k0֠ShD>6zlaW]5 r_X4ȦcQq0įYxNlƷi5HsŻLiu1i(Bxv-1 !1-trFa0lv`ԩ54< &dV ե û)%x,*u}mM"\a֡E0|4M}Q3*/Funn2*z1ȷ]M|&:T:`Y55e.D j,u=c \rTe̗wH9yTZ3Bea];vsuvtBqa^~޹>~j 3dzLi/ A~duzO_8ѻN 8e=Ęm?2SҨF]&&!$2 f~ioiT6-XQ4 JwƔf Y8BBg|c{hWm|2I/'zd]}vg>2 ĵ]Dml-tXLzYf3m"*`ϕ-9 F>00贖 _ .H&A^nN"ŨRUrZvo\ٹ@/axO7'8Ii95sn\wi T%.W2E _hÒ.T$)!ѵg?jBI]Ryᄤl sO!e[ڽg'$+3;ĠQ !WcSo=Nf3zgŋԼwkJMN7ӷ Xjh*Z@APf=BNßZn F]9 e ]K_8P|3%))x0D,5e`<f[S6Qs٨:gyCySLi|~ILȠYGqtnܒ\.!X)^8(-mR*hq$wK^.4.oGv{#*f*J"yC"gFxMEq|^\ E&Qj׉Z 0gh gTJ_VE!W0)$MGmɽŊѤJ Eo3+(8/:x!AkNW)=Bȝ[s王Yp8lR\&gͱU3FKʊ}&Wh^ DCo?M/d}M͌+}Z.ܪ[gm9 I(dYvص`/h ,HL{v*< Y`uK9)_O&0B~GòP F¤Q>v& k۱ 1Y.>=Mko Ul=pvBvv6\_]X̧ ti7{Ⱦ$SFf?[#y*<ɋyg { 85[>H&Z]މiylituiLm_Hy▛  m쵫/Cz2ܶ(CmHDϛw`"'X!AlRŒ;­`t];]uN4V3U8xtL;WT7_VUF$@0~t;2j?a,ۏ!jȊ/˙ZbE)װ&1[i`#{⁛ Д[ c|PRLQӆ5T_\ZVpwh IDAT6þ.vjH[XԱ,qryU{4;KW#N0B'.)n|?iiJ +37ӒH ƥ_)ݎ 7PX+L4QW,Q)]S2V$dmG,,0}-{ {ےm M/2Տ+{/TQiwZZ-D'0CSTrT0@%-hmOWue,$̐d*ž49[C)6d U{ v~2;_քZEhi\ 4ve Ʃ V賓Qm"RuK,ݥ-Y= XoA]nvXR%BA)dZIyA|1½mm $0RV.˲,Ž˅RBAB7bu!1hsvs4u/gax] 0>&vk# _ #ҙeƥ18bHDT*LHa4\AԩgC ŚMYlx!AZ6w\1-t*KJF}Uw_a'XB=i $J͸ .Kl6 ];J^r',+=+$N2;#L<-ߤ͔l- ýSs\O @$lz.Y,Q8q lШbUԦlJ-ɨDI xXsПN&JTpXxEZ:=Φ!;\m(Sȯg[ޫ!*YxG5[5wӸ+w_e4=IR؁]XLU ,Hy5i+kiVkFXm[ NM,z uRӎӒ L.Xu?&<>9'_!IȸA]P1z/kF'(KXg;O;+g `Yڹ-TJ"zRe-H+9*snn鎲"#E jpeOۅ]LoOL4TTǀRlL "hԞyds{)Qin_QOm0~8inTjYn|SL}7 sQxd"mh1ҝe/ֶ^Y>W"ț׊JQ_,K?5W]&!d~ B&?uكEaP0!kRx3ʪdFk 7_BT=78BB*S׶Q*U(ileiDjxSq;K?vJʤ"/(yRsZWbkK: B]<ލJgʤbQEY櫧x| cF+P/Nxs]GY( J_%\veM"+_DDX.˥+7%~h2-F-#maEPeEWZ]b2(Vo"DPTɩ~ ,5jMWNPl=>&),b~^8.;Yd 7gD`t[>WJsE THJFQd> B\!JD)DD@PLo$%˯="4{-Yd DiI#sѫEbaK3c"eQ=]զۮmikthe},2di7zTV[dr5߂q!#PJeBو26VEv?|&ϒ:7,Ho l@$"l+̢̤\A7wAb"Xq |A ֆ~V2yJJMl]fo^Hf^9,6JaɜclٽÐ5?ͼxQ~qoRP*__DD Q r)$R9">{ LbWH <)4xv>]lYyuv$&k=g?VLH}|ոJʤb&zWGԝ4}>m_%;ۻ77a cF 5F,,'_Ny򨪌P)܂M%Vd`P("XM{:5!hll"¦ BRT,]ĢZ%4&y#T&RQdz["-NZ{5dVZ(W,uƫ_']5UVFl25wQpDy=CnSSÿ|\Өu~,?MGTx#`1cF  ?K&Xp,b05E\ƙ>Ϻ[O+um`A/HZJ:K3rq\ޠnc|M~ s el;|uL͈ 85yAhp6lѴ?V&(0aڷ@_ZlxfOSMI:Zsh0\+2PKPMYN C|Z)/`TfkW;*/)IŸ72WzCNYAΛ5M~SMJךBc`,'N JoEcpg䪗DFE>.BbBFF gd%nfLntK2ۯQ⥗Ygα2k-=SIbMsc]g嗟}ZCp8aa8Dw -&^UL(A=J+%3ƉO2ݽ?֯MƆk+.3X>f˃x0zW| iifWiL-j~$Ne?Lya|(e*DPu H(1ʙ|D\٬SRZ`(Rߜ#)=j4Ҥk=QcL$DI(:WGqOH/+# Lb~5?bk|309tf0Z$y7$&a)5$#ɾF4HrY*&A :+AxzcLhi׻k 8>5/:z] 7ntr />PTY:F&Cb"^p9Žt\~5,^eYஃl{B*CI{wY;.xDM>}6O.\>Nt,ra:JlKX!kWM^ٸxc_+}1Iewmn ɘUZ[1+ -/#!^eUT8L`PHrqR>/vڢ;JdRImR( P˄qJ f7C%c08$s',+,+n V^-^!$s'.KsKҴEJηq'”nR\S5XdB\7-S$ԙ?U2Aّ֡./U{ .k[% qR$TM_ˣR£re6y+j6G_Hyi2́W5ǟWޓWg4nO+(n.)*oGJ)JSsK Jk%R> X.vN&k^cρը&t>#razǽ̭an ¤3--,usFb 0 Ze_>W# R:LRt2qQT朼.!~Zn>|9،3JwbIA%]c6_FH:r чuBYC;s7Ee?# Lyɟg*<(<]ۤXeYi8I5ԥgt{!ahJ7+ 邗ũWWR}UfJT'u,x b{bLv+v"u>7g3³,6J"N`RsE3şPtN%SH5f@9??HNH~K؇AK3 5 T? k$ Rԇ`aaB1£YE&&faBv>f&>Ss'q)Xͻ^9revl0;۳ƨC"ά<&ZEZo Y= /?P*];Ugze|j70wG ``)dT8H vGb݈!3m'x9 1`[ 1M(dҠUF׿?y'.zcyD6Յzn&_Ru~Z(L}LABczY 45MyoW.X̠3D^Av_=9qF֗Jٹٗn\^:u mݩkO.:n/&9}Z5ƀ^'fiTH,**.JIKġ](7d [FCDK񃑤 Tb5 3 %BAP;rG Nc D$~H4e{;g-8t3 Ј_~vQ)ul3 n^JYd7禹\( *@lR]]'2֤N- '$6+*M)GY՗к#2]VdM]GObaIX+m n~4PHĥnJ OE\gD3iԐ@ډ j9? ) E/e:b0*զC=ݝ]']؞5)dҔaQ?([>ydΉ[]c{Ѩb͹z1_t FBq_E꽸ѽúvoB![%+w☕MXrgic T|:G{Sן_yXTZAqٹ[q"ir+ryzV'ދj5g?Î 9n L]gJ2!旸aAbRe BT&V#E|ktzFvl .=)dۖzDZY Q"e_bW>r&6PC8mxm/W>EJC}]|< d Z7FM2M"dg}UZa-m\sA72hAw779vếYl,99ѧv}{ Өж~ްegmyGN f/p҇K>Z\CP,-|u賏>{[?\:ދݽKwLHLᓐZ`EsQնLKΎQ&4g/_8憌ahC " et{BH\T`U׬ >!d *Z=l6Bٽ,]՝H"~#9."(}#M gJT+Oeog 0/e A4w :>8 $ilhÇwtB*(-twG^X }\G'Kk;5a'9O_ ;rώ*KzEQU{ѡ";gɹXq1+) BQtտsp' ܒ__+;ٖ$ӘO DOT/.ˡ,Hr$& B$vpu$q9E|9*67ՅA;xM Drx]CrB_d1=/x|A"Jz>Z.Q_!z5(?:xnMf,^dا*j5٩F DBtFBX(3g3LܿVq2} M,K37bvD*yuP'(dWIr 2Smlx2Aԥ]a3L=F$Os0'{k:PȔ1'on& ^WL٩x1nJkW2|/"YWb!.kJaنش1|xppׁu;d/טEߓ)F'07%, rr B&iTZVZTRTTr*UJwZ*efLk>].]Y{4)LXdgcgQ(|tú5C jW'95yƂ+,tQ)TLwn߷vC.2҄"!Nǖ5u1l1 Lcz5&˿YYŠ˿ZD UL6H[,5q߆/[O-*Y.~L#rY O:@~i;̶]z?@eE1?N!AqYS$G)'lܟj̏X IDAT9n2,;g *ʟٝHs [BHZ!ADv@S("MQDmBNvTk%8+3:C8[P|Y(7 (ciMZӻ)a@E3HH#MRr$R]EH\&b5M#]N*)i(rHW#*=ST1}t삇/RR3 Jvam>=LBHh^dvhf !38%BFA twX@c^.H^ X o߂ĔVNMz"$s EeDaRyfaA!r[J}2?RJ!%{ ,%ݧ7y+_ܵ4rws0Ae|xshGY>=D!8 Q][b, Ss_8W,yD"*? Tٽ6N2a+3دc=]A>:*QuQ,ڃ㵫a' hI_zYdM@U W~ mSJmqJNt;c☛k'c׫8FSx??xHe{/?[ӿWO}l7y [LCw@%zF#2LB7!$s߄ıfj!N,`i@Tu!fR2KJAG,kϞGH AXz&XvAX˟6gk,-8b xgJn5,H̔HƔ\-H>LsuagܻQӑF@/@02|tof*w}ݎuw<EѾQ#kLNjGeusV8a¿F08yӨ0M4’Ͼ͓WEb!5a0{>P/{6h~LΜ ҒOSɯ(9z~]Kt3ӲNMs@Mt{Ym>4DDt΅umثS_H$ݹ/͇LD$jimU1s6pTGq/(;纗wsVg>;hr̙2۷k0;/iܳn͂‚+}yϤ̧zflC@Tn2=;}qi]ϾJ;>Z{h˜~V,"tjUשD+sXfcMs/LJc\)e9E٩u+#$bmΈY?W}T:V3{ ʤ\@kYkC#,]HTn!c1Ĕs3H4?#1΅ 3)ifWUc h1<5 {virj 6i(ɾGpYFիϳP? xva}2LoOo&uW}M'OTFt+ҏ̃h!" sj**84yktKl+Ö0"-\!)Țf2.'=a7E>;1C=W[z #'dW'~>V0pjVPZ_}"NM{OAQ虾-FB[Y\|iT_;#BFre@e%-iᔍEyy_A&En6jb1׬jnߨ{)/7jG\yi@ףI vC9`жtؙc 중[MR{x rcf* :'/Y\83'7{HNxq|y>nZ1H[ECp ZFj kwj+d.ڠ%w̧6Xɞ9GyקFeȥ\G.mR8s'gM_޳LP`Jhm\˥08{1ߛ0xJh@Yq?YJS!qo5d&v=SL!$J3_F8Vc#.UCrDXCȐHMZy"1M~SјjW?-s=zoB;G] )Djj21]bچWZXTx/&SXH$E>J Ax9y@_`i}{mؖR߸#{{xݷOkt˴2nO"Q9J–,hҼܿL&3\ɦ6_6sX_o9wA]r^aɫ;T)Wn fL#>etu/VTVퟢa$5`z|, BD$2T.-Թ ͛?^l|%M4­30qaLBI$W?\*FI,+Gw^ٔw/r u3;PL٩e߯R)5jǾ3 MCa|_{~[unsĝs9xמ@#6`(~r>vm۱llW?ԔFe$2;y'&վ7nP$\Ri}u~+tyKh>?Z)>tSHՖ,Nӥv_QϞ=bJ6/а(`3Gdq綖ͧ/wUblԖskA~$qj 0|Aup’Q HHhB7/ίEu34ƸLЩ$DT`S,"DeosJ r T/R8)z '#&!k7}|k,Lu'wkO898±Gwyɥ7Jӵ+Xj?vLYx_gbqQ5"ʯVbж;Ec91Q1&uM_Ct)+bIr|'[f)?FkD˷=[BVD$"u$ԯ[ts\4V]?JѸKTS[Ea{jildx8yᲥ9/3iC cMInfZvJvQ5ҩիc?oW_\e߬-d֖(7^2=b/6'G~\&TԮ)R失-E٩[da w/^yiesڥ/)XTyF5S&0G*;J:7D@sҪ*,*<\{vjtw„|PaݱHMy{hXR~xB-mozM~0ɕ'tpRZR3{c ϗf2WeêHMRSSB۶;W"p!!F&3.CgF&2Ԭeh@ K_e=Lͽs #<8.-3L(Rg>u䏞'=Kݥ ) iݯye7zV4j_?b07ȰHM moܝWKrW6gx\VQx:8bYEYyvKx_ \>i"]#vq*)oTv?~M?]'[* f} kqټ>bind}x ԵV]c[u3tzWmM"Nza=\e2=]!KL\ :ǎsAR8ɴtnjvݳ!>ʯtαu=Nx 9oI);`h:&cШNOư$?PQm5wez*{ׯgێx|C{/WGHW_ G6%ujF&Za:$Hvwޝڭe,Ú`yx=x`93&`İovzy\ˍoxz9)5ČceɵZؖ* s9nnǯ0.2NrFMkhȮ tL"T:zt}?m@!tZALJ/+s[kz|*cWH"Yfc}}ͯӠDKx_R0=n׺G/ıPI~W{y${@*XPQZ"u`kGh=\?dOmƳmӥ^L*@&޵Idjp@2FҹNi/Om] v 0&5W!=]\gêM5~˶jK7.aYkRvͽNaQTdfgt+/-.-8j?DQ"'/Ջ-*_9=C!rvtIJM5<_f1ÎKz޼:o&a k^(15o{߻w *Zp.tg.eT7!cV :;JVu}α熥K ݢ\0);R ~XwCf8U >d;b4%oA/Rю1($e)lA宷3E[O b5k±+qUN^4Qr~KE лQ#V&~ggnY#~ս52~=Ł?yjKXP,;!TDk%Uy{u)x 9 UjL>h.v4_xd/^W8YL6`JQVUz?Rjli13£%asA-4#_lw,=VYN<_UZiͅԤ޾ȿ|~҈ 8_spADVVg\-3EL[`2Iin7d\9X4u>!1lq6ud]B/L^{>XawkNʉ_6.=gG@8Cuڂ[)".nѵ ykQpt;,y]*/-lw vRؿcƎK7}۶z#rw?K /G^Xg筢D R,)+9{욍ͺ\{o=׃w|_NаJQmf 1V]r͠:zǖhñt(?? z4'SC#͡o~LVͧy 7X*K Wɭw !=Wvn S:c;7.mak߉ m~@nG3ٴ)DzU;rGƷLZFڼ'5T|^KjEyK:u\p1\pGSi3)j_B~J[S(*߻LbU.jE *q/bu2p z˹/2}Qe9<*?0"/sO!$y!>q-D:fq;mSknzy^JA('rɢR_і+)P@om4eWt4*7(t_?CX3*æssiҨk=ϔekRjd4P^U{rFV~ETttɲVb `:}Tb2q LHCym9?h#'@=uYDN^0eY^mziJ[[5aZ*"iSԢrrӓs8[;3TN[11m2hUk[+/˫e^5~V"\tһzlź Jz[ULZ[cEcI JkATSWq 9f ԧހhuEGi?rx_m`2镍DH]!:?!=Զ gVn4gj+7So|;$!:;UI}@oiRJ=ŏK!|J{T@,{tӽ,z#;<훗ɚ-@|?B>V?JV5gU_O@D2Û*@Ji<:ڥQM]R"0kBX|:Z[rG0"E7ԚV/vXZ{Nj0_QUP9t%Zqi*j7r)/5wDn~? oTP.&Ws 7mܕm2 EiIDey8 ղHSW1ٯg$0* T2a!f18Q RI1+r R`B׋Ül8^`Ä,5:=^֟)7 sx3,jk(YUTnjahP75QZөɏ2AEk  $z͗d52{\{ON|ץׁp]Ybhɯr1OYsvka"H3'L[pO>qqMXp~'4j(:|kr9rai=wmQZ.퐜S5MULa'b7 &@ MlqkqC-:1I]xb(oP_" (G//Ӗk/hk "Vk-tCmEoEU{aOǑH#㗳-v? GU=s̲#k|xr]>n5#.?Bf\"0:p2Kv9ԗ?|]Urƀq6<;FnQhuLVÜs,Ͼig%.bþbݳz( (%ы4]J@wMXB#a*G\fP >o1n2Wc8N R~WXV=Բ?;!'MMHe?'>*bԊeDOpp b2 LxE`:2>ͱ۵RUAqE@os/M^.œZLw\4hX,ʎ(b1h ~!r:50.9kn.bgSn:bAȔRBZ~@HƚO> !k LH+UՒR_+5*#jjO)0!i=,*qϥ5`zñwNHzC!cew6,҃6i7"NÅiT5ht"RԄURQZ3Q0DJԇ['zL11 #ښTnhFA?5a-UR^;n+u08.ŀc?Kw 6hz)Ģb : UEfbT7|)?Q"eQArjK1q=&.Rf}k/М ?Nb.\ k~ Z2hp:2/B3jawV_fj6¡#c ~UvƄ}-z6VCKIgj%ezD.Wg*HK8I2Y/"6ԛNokT4̐͝A=xG/DŽlçNbZ~XQV-#bB\#s RnȔZ0*%! 5~d"1mjtS4֠3wW*-ޮ zتi.ڲL?V$ [&]6vXu68V(serBu[:޻<^iV":7j"qr_+q(ɤP,&b[38%/~@`5/lzبئ_TK_&AQ+F7_/CE#ףGzEs9GJ5iEzH0(g47]((Bw>e9 Ԗ"(opJk9ϭ8.Wv^kEt-g`*s&2\;6ɴk#\a+="n vlܤ[+${pTJ8,?C/, J;PdI%US*hw&M.BsF7 }],9s%kH1ȝ7epDls?8W8|#&$wO_5_7ٿ/mfFR`BJF2FHgD! ,25y#ĽQ<8"?Bq}پ9 G5 R@Y|uj!@EŤDd=+hb j3PT'AWi ,Жh1c e<fSXTvxLR .6_n!'ߑmsz= j06nI=E(RT Ft}K!-,6bS 17z-!a ,1~zb%Z[|t4[s*.x}|@YX'wsd3\Ma߀s~QS[LǓv dJf&1q Bn)v)P_3VWp7By>2Onlυ8cvԩAFʴP"4͞o$v0A&T}st{ |5B!to5-Bor^ jhvKo:|h~)X P|W߆h*Mnww)6F#׋4etj=b[# bVBZVb7a赂5MfG"1 )xNZPe驓 CžJl}6yZ)k(V6LXUB!*9,;0t=G|bTlK$Hk/K#J)e" vx6 s -f?J#S,a.w.b0Yy%Q֛.6]˫رG%]$TIPMSd,nN\q6'\ŜxęLH`B6X /TLUoU92*GǺT IDATLoOQ/&5/E1];;&$&G/ۑa;$+MCCmm4[hLʓL1]WG)HQAsJ'j|o^Z)wr9{WƶdĠ\fǾ"jBَ=.DMЛ28-'V llfm;V)sJ/^ciۆRutŚJZ?7\'=Jƴ(oB)i2:`h;+d0BoDØhK{klb 4Qej9R'9ACKi>jl4"5*ϰOBprOlQhwZB$bQLeou89p +%:JֈSlǗ^;c3}]g K\Aẁ9%5b?".hHB&$t\#v7 &)H5Ѣ ~l鰓&$ntT"zRVMp.J‹ۅc*q,8J$ YY./ (IR 4g9#Ro^C4 rR4'n-jjLw@61E_Q0JA0s&o'ך75xt /̭%PգbRm`Z 䠢VYoGBwXOW'k9xer+Ͼ#f-Σ9.5򵵅?ؘxȈ:F,W5 $~dntjuLIy5&4+;BEZz:k.U)7K*-j3d1h:X5hj2.F EUaw;ΫwXݢh> yb0{#Yź1VT^B դo6hܥFݚdڊ+V~]sՖE%͢Ƶ*p&z'\g@8эd4: z93x{T^][g\ C`I,&rò AY& Ep>,@ciκZMVnv6.BRΊ氶V99cm!Vf1hM]h*2$?6.D{%['|U޳>8Uj(Uhed`?z3E{G08MګEqQ{ͫIۅUJYܜI]PAD >2oIN'$-\uw"!L/ J+\"!J-rOưw:hwrN=Ƽ$^ LHZOZ)v^;T(}` t;!QcˇeURGWCwe*$y١%s$YVЭoء_7aջ-Np}s{ӔYԉt?>=ҨSe=Ƽ`.JmKԕXoª3 h'1`ӯ}2ڤG1bC=ds1Z%+Nwϖ-g7/q^DF(37oUt{YlJiz_ǗN"Js"(e*Lոz.h6!{Oc4SQW#G~ms˜ /k,ǯq_=ydC#$ g>JtS xNM]9sx+ww57Q:Sz5ϸԛkc/0dqU')7Pp&Z]rLn^N>T4amGZ gg gt8^Iښ0Ҡ2`8Egi t Ut]WU*ʼnF hxlC[s6j ڝ&_gZbLw+G/d8Dk7vI!2F3)C:+62 {Q{I RfO)sT*nby'MPG>[,S;qDt&c:E [RNp*Rߤ[ybC*)֬C3xJF{\+]7fH_%[<]\se{Ɂ\~+Rjd1Q.h0Di7R`BZ`䕖I} ;¢SkrLr}aH[~Oz J^'ʼNаpLWZɰDCc_Ūw44Oz>)lP-j3(9ȍ4U9zq'#i4ɢ4(YV]Z*f 5QUHEA]h>|u߃7e^_#% $N!ja'k:2a9&;XדFΫ'\,z{G臐 X.aG3{h.=F78Gtl%1+pEX=T6zwż+^AmhøqfFd˂[o@A,՘^8KLrypmꫪE8^Px\J 95:M?|4`=.NJqpJ|>HE|=;xijXPY2_,lsN(o^qUz9vWhB/\?t%g/9a(L14^QcPR/|υxQ eDXS;J-K2g7{k~ ٷ4P(X0F]x'UsQbXu\1#DTct9Tq8Ak7GD5J-A0=i|K_ P8%G2snRZVq0?>-Sfd}͑8 OwFjV,RРϑɕ|1иZY#qJm [TYo}|\G$:?y>o U.Wn=ˠwA*Z-fF kC|=܅ıXmj]eJmE {B?4h?|wyY4LOQhN'٤pX7K I+#>YkdkB1ZP,$ح]^6!*Di8O{es){2sNwBoVo fv3q+VSH%K#6&2e16(eVURlk+O4[+=5X:cOǮ`2fQPxR푦z|TϬ=\݀ >%?F&O4ZUs' _}6;3r^Z0K-@X[|C]"]-(f0FxCDMP>슢p>"A?o\Iux!!u'R6뙠-s\VKl \7؎aqj 7tcU)Y aSY1k![EJ0fZ tp^qN'} ]M:E])Oq %Aa]]:J}k d(ـl1*e\83="җq65 H-(Ƈm,t6\_Xhd?/[cY~w6ɒŃ{bY7"=ZRX.HslD@߈m~b<>c]'~bvORg;4ĥ2J+6?0b޴᭬RfnIrߧ%ӇB~LJ5pFRk*D=/$ EMD|ܒSogH`_ׁ}C:F=zzvl*(lGz?5g$:Le=ШP!1>v.tGI82.0! Ips}xyeiUu\f'^h!~navW)0!+jW{ل]pfQ"kPmU~zjꊰ\44r9ڹ_\s 7}-!xDinSfX:tgqP6Q'+|ceN|"K+9*~W7Γan&x6z&`4Mc'A}cjLo܃Mަz1`1.Μ&UsqMӴP݂VX~}KCJ:;+԰7nΦ%yiYycM#O}9lWtzvs2=E| >:ri9t,֌VH?"QɏFn&fCM3~|Uo?2@#5=Z]o[?{rD՟.Ko\v/׶_",wamho,侯9Ʈ+lP.^;> 6@sfyك&v~=NpOSwy|EQcY5Yٍ6-2d:qjb7:Ȧ`޴_[Ph.M|ӷ'Űy`eJJXjΖ-T|ueS.9:= '&m.Sg敟(3T /&o{xY+jեW4ܠΤ<V !pԥ4zY:+FAT79Ow V.z 2<1݂;[?$+:"o&?p'4kP]+=}1=#\z #]#N˃\ڼcgRZk˧Mt×xlǯuX g^I+,TN|OWCd__/_,/S$ .f1(LHDot^Tr: 28s?M j3+ 'kp\Ep\B1*͏Jw nI dg>U_#3I*"L+Z0٢ zuYVePB8qL1!j?C Li>c kn(h[ s$l֥GXhZnb4;3@ZbƼߞ{0nI߳֠x@Wx?JڎGaqN.,KTo?Y&y2>rݾ7cܼW~pO2kK5pqCUM IDAT#6װa[^q/G߶V4W~'.m}F_a^>\6Nc4*R]?mִ?7ٓbk1L!]߿<8}(>Wf AHDyE9Ϲ,'DVjL33pf`D ;YBpoF=Hdq k5&o\Sӭ>ƔgJ:t˽'kW=/T|Pdz\{R 2?x >^>ήl6di_Lã*[Hw{siiw[h勞 8+Nh3}hg?_Yhl7V7r!n]w~M>0ayã|>RU55}E:σFP7ZAgE !v(׉:B,T@,wP7~C]y\Ksvlƀ+\&mfġ&jr%"h.AyBN9wo;PԘb;:%Ä`B&ڄąΎg[LwBG]g X&=_IAOhqb ?j:̣QtẐYL֢Oj0MYU _uZ IJiלϸZq<#N^mwS?ԼO̫s^rm׫Og2Xq?_+?ۣ]{=GͲ8QZG 6|j:_mTN=G^|wGl.;{ 7bq/&אؑ3b-!fء6ܴ7Vh4+iDYĮF&'o/-.&Hc@)7kzv3u$W\c|* SDMluB~8]vEM}GLD> )+l^TIN{Ӷ6֚RԐċ`8FmܹnbSjT+:]FtxԄ16ίw>dU^yUNtwuwtp$n+ׯla[y^kϿY 5f<~n/=ⴉVUrvkF asJE̅3}]RtHz{_:o_mcw3*mtJ+Ws0GG,~>~#\`=w-`;v #BTs9,eID  (ZJzxh? j !jȫObT , LkE"FE28ffr6JUSfQVLyďػ鐷qUKu|Kѧ-_ÞDP\Ť$WT4qdTsXoi])fOX1yưZd19~h#}Of?>GsT^2Vor󱛅Ag<c*zty(}tɗ?XԊkJ2L"0~[nzy4DnS8lݻcw<ʨh%e%*S( ]EG}4-mL?Wtz7/`Đ-nw5%/`1O\nWvu\BpWÜM!9.F1u),8l+&IPRMf)t\tܛ9yQOHO84_KU[D9_oTFLIFW& Rn^HIK3( X:.Vko1piN4(9w'ภ(EieUÂGWtAAQݛ?F/mzd"o6Xg0l9\Pt#9ŤUg~5k|cҙ+~+sJkkU=kJqݝ3_0޽L]qc+cnbS y]9&ѳWPԌKE $*C+= 5@q+'Ft_odge])'02,j\lfd0͂5bZca|Y/ddͧ/3Lu[5ɘEPڌ%Rd͑^7M2cB]eomYV5nf_V-x3æXN:vh/2=Gv\L;g륅k\܌ab}oQDG+yfXQ֢&Ll xG.RxxՃO>c@]uyMYMzHFmGl7p!qĴcEm'!HL]hLӡmHT&>!16[s2.{rE%yiɦ]5a/Q@~Q7W<{zgJjLn6gNpCCBlYl2w7w"mV4ؿXY̴[iQH~ƵDJsmSU!c9[ð?y۬h2jׯd1D֑5bx2qtc~eS&@;`˔4h'@@WEn\b gޜгg<ۉ_)FnX9@r}vD<=1{~ { u2ZcVʇ7n]q#宀SG/c N}MtcĀ)>ߦO^-.r1\Rt\A>^6 ^h;Ъe$ sz)+ zO5OeQ&XTSV 04\|`O96)]"z^g >G"1U-T׻b؝{lP-R[wLf2}wމӆū 72S/ܦG\]nZ>Ƹ|^7ۡZ)3 %Qu$n-,/'\(2?!׹|IC6ކI:lLIMV4]q黹?:юmmMo~#"m\܏ac>{Ưh9DVe)IX`7:ngkٴ|dk~?񗏒Ã⍫n^OzLΎ,Ldyz[Ͽ;3ߏhZWiIEMA'/4EUiM.WG7sNE!߁( %8 3tE7Pb3R9l&FO\Ji xfcSŹI⥯fw]=FZUFS+lu2tM2m8MĮͷ.C'"(Mo?^6FQSz? bÆ [p ^ʓȩTb Y.Wrʅ+ebx7ITԮ .^9&ǟ:7?`U@݃Z-?{E5)[6P H(X (R] ***"JJHB{lӾfw !eC8wn{m9gFR+[PlzA6`;w.*j}7Lߧu5HTكy Bjm{6'-yܽKE#R'Mq8o6ȼ3=](ao[כQ(9f7%@ K%ؾ,yiK?6@e7?m[|~c"" ,f]b3Ccucb0Eh .Mr.2GX-=*';&4|!Zħq= K}>?}D}onX5j=@i>y :α,^jO[..DhP=p}+~~T1cDhwoSDYHuM՜nDG.{YעOC&%:i\c4j")"6,i":}cu2ؙ aIޝ /eS&ٍ*jqڞ{= ~q oeRXXu'>KΤg{y㫗|)ܱ1ak<Ć [R]q.E8˿ 7?shVsJoV;#xjr*Zz`HY5Ԗd߾#˦_sʸ<ޥ.\^&j->R i2"RR7:#b17BVuYO_~N\ѻMW3s)2n P;޾}g15b4ia.>ͼ8ިv5MmmEODZ] bFM2-,I²v&;n\>o`y}L[8K#p٧{mjkK24CZQ&kl#l6ҦթۼG<Kfq}dќCwv܍ pOD,8O e5gٓb9 WbG_gB ㄮah]?j}m_6񦰸~"|ۏmU7zX^$I_>3H$v%HSMw8 /C*3fGED}1<p9B̒cCn w1!(icȳxu($a,Okk+֖U654qXFM໸)Ym&35w1'u ر ٵ@B_Ond\]˨s/je2d4FMg>xx ??2224Ιtqjʻ#ͨiR.f yXP~ 7V|=}{5ïGEo\FM| +Gjf#E!Cw3)$:WwS2ևΐsh>*Q88Qtt !G\5+ 较]}sgB8lj4ML?茴 /1m:}=e$H$IpfX,Fh2 zVӠj8_xwh6K_'Lf2laFQPH=/'KCphꁨ&>v8:2h`x0jEs-VT1Yta]-nx߭?t\;# áA]UJ<&7}'~gֵ/^fG[SpwR'F;$ T& )tp׬եNq,*7{8/@,ticg_rǮ6|B^nc|E|Zg;)HO72R?=*K'DytS<^,zQ@hLT,%gNx$8!iV!OM]nCnwpҠjp,.6j \ 5t'O ƍwxGg큯[o?f~~EQ5_m#o=TTDdP`аA2Ne@Ҡʯ8Ѩ/ÕE܀V?_ZN ihǫd?#eC#שT1 4а=!ycqJcB {X`I&МYaoF/^h:]w \?X,I.%Ugqs۴GgOO sl3?s=7O=硌 z.,8*.r7tsɖFMg V4jK)cO̵Fi^HM1E(Jvy mH:Ҕh"G5B'.R Z}ʔ(I˕ȕbF@ ْWڅR%LQi;2}LWߐh}* %) GEC34tظG;a{qI 3ntZ nRȂ~&<}}s4 f~X3ɟלZJE3[aopF)R IDAT {81*tfcH^]G(׏_|[yЇ/~ Jd}w& 'm;{E=UN9_:)/{Osj ^i UM nN`4xz> RGK#iD'_;d5 n;q+/h]ШQqcSǹ'Vn u<]hCu]}" x?pSlR$qb 'iX:28,3r(,$C s&[o  Ws@^3zB뿲t^E[6}Ujn"aڬ'םF}/7%jZFٹY!}Np5rf?UP+5#X fLE9[49alC֝h%w'Nne`8GDɃq4+P((L`s ^\n>hɞސlɵB$j)_S kQ.]3ۢ87Ƀ (o~Jr" cEHIo)8rAa3~0볗8WǶWkNr|{7G9a{yǞ_ZC/ktvzw̘ԺF[V>r;S~^i3;)`#&߽b3^ۿ&59!l?(X %wx;ƇWbM&@gwyE¼{͏=a7 sp4]֥Jsij"{ߖ~,ۘ; 6Tuj6C^j0ϳ;,rg΢>Y7o mx෯R 5~QIw>{5|!-68]a}|1obP*~iӠrQ2h{ndy>-g /{P f#n"wVٯ6h~heÖO=7Uj?97J?oo|eO:h]7{j_PƊ#Bݓq g&fzY{iSaXj_;꘴ N'? 1ţT,5id|g-Xfw ٺwj4&ݔ6f G͸kЄY:S D{ 3./o[6agШm ˸PUy7et_hbf +qk~6 P"I ܓJΜ{|SF޾䓟} H7" "@ lӉ2u zb/cf݋a8G^W76/>u?jKXr݄ Ie~<~ٹoלYs|̩3?,J<^dx{?6v=oߣԪwV+/fyHpP0{G<0}VkCR7eCF%EO'O`C¿vKbY1!7Ryd3|L:T<\8 uڨA˙8OsRTˆ. NbO)RvV6gOydspio.4jb=6y/!}=P1q\7~#<뵐a0k*Ԟ|kfUia%X%6&?$$6a~x2p7B{\~j_ߞPx YlQGKmΰ0y{X1dzښ'NZ"}xkYkM?XKő.HӸ"KU$mM/=RҔD(\vOMgACEQx /1ډGḀNjr%}Ƽ_IqA,ـPo7 $!LO2.e/HRiذdOujEu&8SnUh@LnH.`>, ׽Iv 6$ܺicT=L$yWXQ*:kyEh}}`/ן6,y`Bv VYҨo}%s{wsI9unqd7^;GlAIe_TX[aw}L~[IMj ߀SE_Pؠ&g\6 {4jB<0mM|/E֔߶ ]m7YӆL!!3H5[wĨ׭4t{ZvOzNdg<I }r9blRW{bbƌÆ~ZkCCٝDHԪ'>e AAឡp+{_ݳyݳNupb^1.DoR}]i+\3"9{Ws5bh][3aS .x$_ '3 86Рڑ6IioVʰԿ9+hF3v!_,6J%X0M,Ooi4Cί撦Cjo}ar BNusc"loi%99Ψ[qvIKCqmWnҸ0CugM:/U^;(4 Sr,6fGS, Oo{Eg;܃9lfk]jiy{cF %l6NSSW?#/{U5o}V$%(2b oC `8 NsL~moT ~α1#p. E°];&m9)JA#<2T٪`lGlaHKb>+X]:1t$4[,̫z=Rem3m ]L.-1d݉ ݇R!gv@ >~g,mUE(F+Cv\x\y85C_oFwIW?Onk^r02*ZzK%)2;7w$G= ;+Ӣx_Fy G[y~xXJ[:%55ջ^%"c-M-bïF0x3[X4uB[zmc\ 8;-u O{~YI. S %'׺gVn[49z` &'\I<`ˆٕ%&tp\X#:'bCfsrg}lAz=j꙰#JDaku7.w]|aNI%; |V0 Dwx~Ef^%9o<6$M&+"̋FMWJM-axQ~8{Kɟ PBuKS_SPq@ӯf;o5 Gy12 nCmq)a)P%ԩ_>o3OI9ƪϤMe ϕ~PfN9~rkش=PpOGOWsE3{߿@;96GO]0w>`dfeȺ :=%تwWG[Ͻؗl!1Øx7 _A5KF!C^@QـY_ڼw(=j!)c\Z!+ǽ/ Wʂ^i*w4!mIRbf tkx@ݙμ39gé] X( ~@"m3XJQz(o0m"ۛҝ ĸ.iy3TtWe튴!\^1J'2 EL`lGx+=b蓩7w.zQȼ8^  F`>ɳ = γڵ߿V*k(E-AqFh%2;M,43yFNSy?8RPU#z1R] :T08 5' bRWcqh^͊!tyG8kڴэDqxpfd[_dz=S>x>f`4<$Du7Wd]y{wJ0禀*si>> Q'&A.j !1|d{rOty->DP.dl㟬0Xi%u!!V % f#*OZY/5|W6$9v3gAeV[oΆ X,X4YUyR-CoJ\4uDR_F*A5i y+>!_z Bp YV/`_6na<:z|VN:>μVEXT8q\avBxpgvh~bgQ}̦*quo):A$I.qHdRF< l7qc ul:&v'ps)K" A;[d*㓄yxx/Ex' n3idol1[+9CENm`qmUz+&Ψb(2,/,-( \ 1'0r!zpqYpKIj8mP {+mCuqdx+&wm*WOj 㬕jԐ2iNR }>4x{O2\o9fe1ӂD▊]J8a )'jƻvY3Gs1K!IJR-;'8uRg9kcɱc1GbdTb{㩱Ú n>-ZxVW6[ KO@¸ِ\R|:HDeקJ\?셀EAa{[ WG`!3[v^4J,Ԓ}ean1lL.žF{['#uHNtzIb-,Ac-6* + ^865$9.:q@љJrUUNU l1tMP|-wPkV< knd$P<6*3J 0T pj(Wߥ[9a"! wcbmq!n7 zki"pb=^j3y0k ~r5Ņ@[)2a#h,r G _%tW 41B}]͒jR,6448%J^Q 殆!Z(uӢbwm`}#HBl5ae8=.SB(Hz߬jr1 kܒ&^8Z#ģdP7OQKMv1 Bݙ)K[ù04GA mbgX394:mXvq['h:Od}!-DѣZt!29rzqtmH `ԯ/?i#,2?i=K6l'}&1_e+Ն5"673BcйquidV_Q4+̶c<>ig9VC;zˮ^Է] Ҷ#36 n6$,{Y4al`~uÄȹ*Ofh IDAT %thb_y#oq)NNO0VMuUJ82ٓ:Žm܌3kFMMӝ Q\ypA(OS4ǹ>pBR-.cWnVbp$}TzyD\؀^%H [t1):dk1[IB1sHK]|x$\fR' ##CќhTZ/m Eo?(\  \V&ӤKM czR0.H(eq 3Wdi3׸穆njܷpObĐL|e3ZF-:BksteRd%+pǥPZy dZlQ7$2e" sLάޙ4&䖆s]X@N䪺<`wK'd -;S=%ezS($~wt`ᴌ7'n!Ndv/Bެ &בӇ?y>6$E{8ۢy 'WHܵհTE\T\4[~cmEJPlBTEpzπ~BneoU&XF{)&)!FM>`$s. S-uvj \k5ZCU+䒈6Tc1B-W`]"ko 0PJn8:UKFCʁ~ i gTkֳI)uSrVK;R6,-,e(S1C#\-ZR/ Vܑ?}V0""(~5oUchD GF'Mlqȳ7K(7uPIÒ3Y &ti\.GQ,VWDg8̙߯n#/O,k*2l,%`Pכ_ H6&d'ZMSeΨ ̫Lz!cO)a!fCm1{L %~@U&X&u|0j= dP -`=tJff"n9 *<&tD \9.hSSh/.z R">1!0wew2eǎm7MYu#$$LfyYmM`3ld._;![*>+%T((%45U|ՙm-[g yiM+Zna5}fGj~.U7C{wBgd'ˈ lǚJ3O =jmB6獉J`j&Fܦmw)BtV;$ٍp|0jwb>:c(\T?:=zZ.r ƒ]d #K _E)Hcm)=Qa45R'EtS$4aH\S HHO9s,' %ϒ$L2V=pj0'3ݧWxhK1DMLmj-vmSQL[}jQ3,8n5$+BgX>CAH#v|I63wM`H5jj uj `6X]ڐ1Z{?p*1!b/?9#f?9NUWn#z߬ Viqݚ Q!}4+` ͸)fJ{7j7$A7GV\UJ2Z>9T($C&\V ϰVIfhEk /8R d.y~8_)xO e|_AP\9Gۼ_*c52FHqQrsIp5FMpUܴޅuhƕS.qry*N5r-_H grLȀ?>NR'6nJaPvKJi̵zwNKWjǧd\DmEj.>VޢK;u5-R`HZW,~398 qZ=Xe#.Ti}#t^0p m;3>1ZG-v=ޑ-ٚN4 ԪP㒂sK>eb_WU_y +[K֨M:L6ǵM )!橣ɘ(rQ |i mÌJgaJR)@G .F.-c<ި)Xč[#O{N]{b}ぺFMЦ0`=&\htFik7 E;~ʕaf 8ȥ|o*)O9bddݴEQz-hoO^JMn>O <.o,i ש.b[gjDOGkYwv7L:U֢Ӎ&eL]@z$̶*.rt Coye4.\k_ZsYфE@̐Vn~-qhE.{=W,< LHKL HLyQ > zΝɽ:ƺ!aSp~;!$M,Qs&hYEZV = Ud"?sB^ٖ~(5 0SQ_7i)[v9:sPO{px| g0yv Eu5M"ӗE1iB߅B9ꬺ΋K V=a&<o&kŚ_{NRNbkF |><_\Q:< a0k{CCŭIcyP4x&Eٵ *OvyhgPH})(KIԙDf gzހ@Jj6*=eޝ쏊`v)RG ;iљ]6xFai~慭tھ/qD^$~(ߡ)NJ]^4̐Շ&4!1Y,љߐ*NX# ( AF>eF6lKf<Ep͔lӲ&Kd_f aM[s|k$j3~Ù_>b*:UЋtbWmWb 3VK; k?h@aLN{Egs[hC$|a.F&A+Bhd*]ޜ\Q1\&0)}?";泂hCtLpz(g55ԴD4 mXәHn oLknኢl,InֶPn]"7A^',0n}odY2n_6ix_Hp=Pczx,m+1&?@.890:opp|ĸ{oXrJSL&ě'#ެX׈)CdRDǶ8syT>NyC :Ȥ\+0KPΓEń  `h\v el?{Rg /2ԛDNQT1kDrN\ yRӒx~TmoB0Zx @hdZ'6pRڕMut/qm$H#9h2m lܢˌ'\ޡf#OԬ;L se}+Iʎ*0[*uk;N^ۂqyP>lOFE0Cj[Zv -f#=k ZTwu,w~C_yhtB~o{Ęm'&G7j<-n*,VN0_ĎeJt\3fy]..4-:,*ꗝzcJ"TĄYzԄy}&g{d:K/lH.WfX md]3 i~"ˆ>W܉#JgI-[g51}KPU6Vm*?722Nh̦߫@ܿE#%A8"?,2DHBEӧu?֗%4Üʵn.I)!4TduU}C3m~*` _R.-`xUɄ3Πz _`x\h:N2_O+,v4Hjy<"{gce. BȝLX4cx-m~Co[T'qtC4T|60T)CoƚuY$Ij" =[ E _m:PsUų|00D'/E̪2Cvly y7`/,2??%Yq]6RDi*p|HɹţoNZ^$Qo4IhBw1yF9H̰m+uCڼUQ E(4aV<ߤ #=ߔJfL} pq=ݎP {N1a^i4Q c&GEjk?a>ÃN v.Ó/ݱ bE5PҮM[5[C1 ֗=Nj 2=5جX}fU҄A/͆FUo"g>wS~S%tǨpzɋQpHoe'x`\u1$0R,8Ä۞sýABYmE~~t`D[Шk,5Y^ޗ=E$_=6>Y紐\EeANVlғ6kpdH߅6F{T0O 4jU}~yo¾Hn^ Fޠl1qrׄl4FEH%^ocғF1+H&1$^J1d>>+X@Q'r6*/u'9rs#l$:+cs.C"E~VK(㖶V)S؎$ve|Zڶn~SAwL{z ôӦ.GW_+<<1Z E&&2 +FS~am뎧F?Wd;PMXE ثn~("L,-Y0&PJHԵ C$4qk~g82qթe=ܘpHw&^/' }0IwlOi__є+ 5VUh(U?>,nphC¢5~WU`"l:$H1XE-J%Uޟ<x^u^:=#($yܳ9cagǩ= e[GM8c_,^G6MZqi6+j-=r Q>G~6HL̛7Df)sG91­(v("[-z}BEwll&s1e.b \IbqY0t ? &6FdV-=kVD4^"*G).冤ZUwƳʩӆ?aУ%[roIJX<g lH=LVOki˰86:˟7s&0~ ReŨ[^Plb O㤊1q.>y 5¼$U+46"x$ؑqB<I}rHӏm[ g֯$<8Gh]ob3 E*uOmgSI.,t؇\y6@1\R`IUFzyϒ eFp&N+⸐(4u0!J x8~Ps7R Tmov H^qďHÑI hhĵЙC;ڰϳhj’rĊo(a^:l75hK GF ~9)j7*ۺE;XNp _ wX60w8] V>#5\4u݋ A#M^#v a]5J!3Dq~kf5Mhzoǭ,PR~]=  h-'Ry}Ŀq'^~@CY5h[Ca0jɋy뎇F,}HBv{2SAfv V+17 :wWO-,BSXݧKo7* |@am,Pny7!n-&-=uޔϸnM>ØƠ;䢼̿Om9˶#jq=<\T:@+8='齇?WzuNPUjX*Ny׉G/xlo\h`΢]P^V^;v?=w/z CYIZe6,!R<]ZQ(TPl4;g% rvrRd2HΊYTZj5u┫%$[acBB,X"ڶk 6u&I!S yEeEvnv٢_z>|L2Ey~޾iQSH`-4 \\f}T**v'3@\(yl1/jM*l:N$Fmnb= %Ɲ ]Φ,V􎓃sw!IGzƎ1 z3`Ѹx<'g۪9řo.sN\,kO|*LC_^ޭ2ÖƃslSf;'rS̳MJaSy솹QD&n>1z MGmW6y:9=ĕVvrk,,*J^>ҲҢ"ݓH 8>YJҺ+)+7k]~o\.&'tẼ1 sk#Q(W轇FMm:g6phv^+޷~2s3Ǿ5?Nz`wܳSCxoZv-CN&h!Z>BZ͏n6Րy|6$:s:+fאIڰ%EO2{wJ0y}' 7^+d J[Nnz^e:)$^wʆmuwR/qת  5gI]Ks FMUs3&4 y_pM+o_{ 1s f/vcnHws1fζC?JX+,9h(E#ӷ}sT/`ju:{) Sʓ|QoaS ƵW`lqsXl a]oK65U0G)3&if&^Ȯh7 tDxidpx5/@}Gmk!epqp9NFs_=`ׁK!Y+)wZ !sϘ ѸMF=OrㄞWp2[D\d0iTZk ZFM'nz4 :E&|Q8!KpF*CDQL"+Za+e{~&s7M\-(K[}{e=d̂Fj5V[t2Drc3N}d"yߩ]`GC}~{ svXyGLs&oIC9)o,1 nٛ'vˋ+r"VP+o2X%yֆz[l1V) 1u B 3̝ي&!ڙg/xEYg> 2oOU=mln@CJBfd,xūsO?8qѸˡWV3liO-ҝXREW7&pě?obcuV?SEq]B&V!QW8oۆxjaK(/Tq*hK"Q1fGĖHn=G/0^.^ߔR)(qJ܍ *ȅL)n{KR)eh"шV}deMqV, |aNZ5c% vf[Z^b2jrs0Ϻt|B8DY73>w~ؕEϫ(!) .L# /r  h5F7WVGD)!:-O QZ,1Fx,֊fdNNc AtJBggiXX3=+ 4Vͦ1J2 zEǺtܽWjBycU ᓇ SD߿?O=o_}pEl5@׉]/. <8KX{MFذoW]ѳ$QA>O+J_5֫ 0hjx9(\˽:7չd ZJq/.#רM/4>4P&L}P*ׯ7~ ֫#襳6rL)zoj?'8y<$kYr2b88t#7y%IDEʻ~ls\|2<=yMNY㝟L{2p닾Y7Rȫ8bX~Y'%j~|< &7jY_h*<c4m ]#dxxe ~[6ߔ%M]L18nc߱f<76:9< ٹﯨv%E6x{=QCG8sbv*=@>t&R~7]x栨Oe;;a@0 *2A5@+NBT-4Z[ȄIw#vf UP鴧F2LY)VJyw`gz.TM^2_צхIg9.L[ۺf3{8$ ZO ԩ+R7fZ)oӔ Kw&G@xj2)$;ߴ1:cD#y?yTkS5asR/^.S<{L"5VĆtCtSzjFI+O0{ڻCf2 gʄ)_Cn=䕦>gh6= [DݝlL!>zS.+ܙr(@$8B)4.*LQN]h6*yD02ӘH&ʅEw+< ݁atg kHW'扞s\\NW>iulA%l+)$+ˈ>Q,ɠ\q>Q/RӅCPPiݸ~Co ;!ln[~ݺ/hSK,5xޕbخ?o&/R!J#P&>KA/!`oΎ:bmM"0ux!J1dN_픔*ʠ9ȃ|cL"I~gΜiN6-x[>GcJj5;,oQ]P(}}'^3ktXw?4~MfFMN^HZܬϛ&^)._"4PN D*6yt;4j+ӁLcXdr4B;xrLY.^2>cg!|fXXWB*3^b0,c=' /xGnsi`ή;g]bڏ~ۏ_nM5FMr/V=є~%h6֛+=yW8w4 ӆO;_b@ᵉ(ȦrF^DjVѿןv lV]ՖiJ-![9; HRƅ{̙ `F#U"_0Z4M%r"%r1_HTmIQ`LnaɀQ/Bq)PJgw皯Zd [P *{?ܝ}L>潫X=6,!HW%gܰy?-<tﻻ9{Ol >p IDATng;9p:&g$4tz~qn 批E=bz6J5݇j?,sg3'@v21MYbB-`pRX(e::#E9feکk>^^sߞ?dV[+ݺcE1o}t'׉>ޣ{r CVT2k&R,XA"pb<_h\u;)\ѶjAY`b7J U4i*:{/A,e7XM@J@B"I_b: py?ث=˿Wِx=e^X2ZD|C/OpxxFL$f(sWƞ0Of}CeʵVl\Ia{ їku:Hq`0jCGdᓇS\h De&m"}uPH"sxu4vSn$ ?Cx؉C昧5ܨɚP .{{4ޯh덚 7> 8n愹{ǔ($e)=7N5L[ΪIs7nyb>oikj5aLFMLUFdA}tvܠT:-R,?ŵ@K.>x>4D8nus9DTE1XVD/s?MCaE)ÁKc:Ge?gL|L%n=8- sg-B3~BRt+4>FMoO~N5K^h$*䇰MUEFdgZXwKŻ?M;`9-?\v۱R\o?ߍJ_4Ô-C`w3?l4ZD.$(T ݶ~tjFiI<O r;R-Y(_ӝF4SɖߞeesV]x793\ yp|<]:X"g{$#8IV1O_*`.fdwDӴ7I&YSAqYBA%y:Gr9l[Agf^bbquL2E_U+$@2Egɇ~1 .s2JKq5s&Cݝ-@b R/{ rś,P(D2%tTRv+UhO+#ĵww&Zmr J7:;~X"0q:DZ pַIJ%.z+Z/Ի6voiU^]R)pN1A/UT-ؚw))KEbBT衿"KݦIvS2aX4 }4N\(C])g?v粇t,wZ0jW-'L`Gv3]9g?asgxu7bG4w=_߭[/tpk$:3?i܍ z7~3cl;30} sc#a J} t7M6fg|]{(TGj?H6j"g}nї}NL%AwEIp%躆+-5D*K,=k ,GwV;h8yxETQrrur4ݦAo28q*Ϩ[Q*0NuPߨLY]JA)Rd4ȭSi <#Xܨk.ekohGLg?]vAF8}piIJpse=ϝ1/:"zIy*4<zT-ZEIsEO? %q0jEr^U˱8<.%l^>kCٰ" ᣯIwӌ~iҩSj%K0lSu'',E&mǚ@8gӝLq =9)[oԤ <4$1Ah,’'WҊAcUm=ʲtɃՈl') Fog80xDZTQ D"ĚD}}LA_]-@IIEβZil#KsG~Ch兤0FbMR40Iy{rLc~0l; $ 4<ӻP)Z?cR}|WX:u=+Ѭwg4H@~Lq'/qWL(tozpurZoI^eQjgo2"ǚ|3OML]9g!SJCͷ1gM7ãΝlD$?uɐ>#2TKYS; u,Wo)k9DEi S[鹩27<o,@ om/9iJyMs^fxi KǢgUsko`ؑs>-+ݻhcWӓM&,/asݨ oWk*"}@YiM ~祘g^5Lۿ\Z0o!1djx|q`wD97><4K?hw/ǩ C H\^WRn-)iZ/M:GK6 紎ccggDZ|m)CzVn7LYl@?_Oj=!!KN!}!u%+zVaLs 5Ow ҩW~֠@cxœb%r@sG;RaNȁESA2`P!ǹ Ɣ8 ~RLhTzmEٻSS֤_iQ h[ze[+K "RrI^ '[RVilF8/p;۩.r&c`669p[cZlhjZ+ @ $-#]54JܩS[rEb|oi3 :bZX8M5X4Ųq^lF:D7eX\Fn z/OG/0&+&_ u)FM`rPS\3o0h{^JA&R^>gv-kסYE5MqL 6 竓Xj:g:0nA -wBSCGP~{ N3blDz>ٖlVgtѡHu*J?߿yBݺ2nX.OdJ7*yMz_EvrtpXdyaFt &Q*KqifĻi nf%Lk .WAXnd;+zը,MeU ݳ.YsuÔk;mެ.}rJAgsоb~ ^vHIqe-?Pp9//%?x3y[ܷW_}-GG'G'oOoP(:WZu9{衣Dgě#kˏ#C"z%,P}c~&ٹcK\l]@;'qgOh^"pג &be|D2ːL"fie"V`T EX F6X,#|NyI'?&YbvsC(;hjL˪mO[ O6&r|Jiqeէ)RkZ)z ~FM "\愘?\x6+lR?^. ~AsUO mô'^zD{;9B̌+3=t[/pؾ]uHN_"_xTL A ڛһƄuG9Nߍ eЩ,'A;s[{@_)cJF93W aR)_Fc nk?&*׃~[N태YW!qZ Q.?`<اO\䞣qYiфu1ǝ, CxuU}O"ۥwPciAu@(+[WV i;o#݁6鴹5KjV@8-|l\LH/C@Y'g4?pfm$Yq%bD^Aa7aTu-co%gEb4% l&PΓLUc'8Z 5voi\`Ӳ!n+{EG/=&ݕce!/8™?בbt:/}ikw4*\8n.7sб s>/bqi剫O*è \5*`vV!T Wdx/a+xX/9zaCj.ER[jbh[H>Bu'6 At+0cCl+Y dgܸR\n8y]_0Ir)Kǘ'_ 7)|fv|+v{aq8*M=&ilxa=F=`H[hZmd9Ts$~+v-stH2RWY_s ?PhL6׭}aj|+0{"GI"q凭$7u&Mm{nc~1 W[*РкIہb2..ukzk1cNB)}[9󅁱ot٧'Ϟ4Ot?9e&:q`Q+ Vox_l*p<(yZ_,> Os$:zʪk9_Np1z :F!PFz>qmX,+83ư.ęmES#3Zac$'6tv~4.S^=qjŬ4ji?D#)cyqZ>=4$jħgK̇XL瘪"G';n:dvZPDE$xZ`ܟR]!6*I{G* !2_gFg2j*+X5~&(HD aA5:wxgHѩBSS"ӫ8 :ux T [Ɲt?N)~] W.Gk 8Gx;[WS irZ7=9+Q$?k;&o[\'PtN֪mlHףۓΈ8xH̫z"]k3){V{p8\{?SFiΤ3BDR9N1L|5bڄV d4uD7MaX~IFK*4PjBZ+@҆(:L\j}ikwtqsT:Ic_3a b+‹F.9R5%~;pa0xgyk44k'>?kq[o,8]ӑB$Z)K ݿ 2ohʘ-xs}=hT:VS Suک-Yj)*,TbRTq#)(%?Z zqe8pq8J!,OuO^PwWsKҺ EPebAa[ܶa_9+7^AQT: ADܞud=hxA r۾/ԛqεv xt$rP"yzMt믮 ciIGŏtt&(a,+o[}m 7.^8oSg3 $JDba937n/_رoNdf=+֭8s75-S⯻~Ϟu;}|•~G&)W1w_n҆r[0Vȉ6!G.jX,g) IDAT9X,gTrB|ܱU:z %bd`#1r"S0eo1ێ5a{b/(P]rOeOiP&? g`:SWW|BtmR ]<ɱں,/{AX?D c2MUxja1ɩ<CGr90'nqZn>]5h>]9,Q-5֔b0K)t2vaff`ٴV^rg݉b@2UBRF&ŷB$7{LpHצeBf{u <;T`ѻ#TjM/*E}& puvŔ*揘AHƤLPN&&bLMU3߭gySs-U{goޞɠMr[jvkH խG/Q?tdvI?m0zN_vǍW먱 I K>^cQ‚Ԣ-:G#taug1.z,Üמ/^Sk0^> hxo؊\EA2*MO('yt|$sV׸&:RA3j 3 1#ZA`6!W~":ϣ q^iè;}5yZjao.^_G<&_.gڧi*z]>Nؐ[TRn.BգZWXngI.WrKQ _&,-֚Ճa_"4w!Y.#6pZڥYy%RHulq6{Gt+9g83vFi)gn>m-&˝kuUFG6s^?ǻr뉻#I4}X& $@ҒE"8`y[yf˛itņLy5M}ŅuԦqG~ _orAٖö! 9GzWz!>dG GOO^qM2#iں2kZ).?pzK̂2Uڒ8Z?/ikwk&Z[ߐ^{hG'3 k08F0"j~^,)%/nŤyԧ0#;6MhMvQ`X4 Óg2jYϫ: U8WvU~$9yE/ܭԿާS>B|w m+$@b҅g!U>a}ڿi(JTqݓ<] viIȔȏeys'a@%^/@B%a?C[~QDfO }cIAD{kXy4=>Hc7vy-(. G6ӥɭŲuQo.j 2F5lBD^q%i޻o\5*`m,ݬALfTŘ*u?g`FlZy;VaT2Wkt Z(*$р)0ү'׈MqAʚZ9W*-R ӲS겍D 6ZZ@LBFʒrTM"tiyU$U \GF rd]bE&p8=TbH xܓ\VhUjRUҨ[,̍#a<\rJ єGh9iD/j+R5!mu~U\I*"7yd/or+$@≮t+=!H祔:SN.y b㣾v Aޭc&)x.ݗba|9ą d Uj'҆QӠYs>a ^muRUsbef{3j6ZZy;R)*bqLg q)N8\s{vACwո7e~ m\'j̈i5n(($ YM~٣4 [@ I.z|+mN3Y48:jb~twKdXG8~'Mj}iF&BWp C-l Z-azBT4ܧ0A@h\XU:ЄZ5DQue4zJ¿ӯQnj}T8Ns%B t$&`ɬ?wtx9|qԏ!vZNgu{)FU՚iYT甍Hze:_+XaI<?U40_r; a G04ě:7?aY@̠1n _E{uqq0iN|'Fe8LHt9zW¨bL*r,6՜I5^k QkbSw>iZ0y{HnV'U*&NU2[(5 ⅅ0=gB>aܒO/)*%q4i]^Y RrWxj&;$*̚KXπԌ{S~S!lcg롃}]ۥvN].,4ys J?|Y{I|ְ7(,}tQ1C{:s]'fP=\J:tagg{z*5>"G.h͍~@b0{+z"$d74(XU\$ĽM=}6 ItAvgzek bQ;1~pYj׽g:X`2>SXz1ExeB~s;ʅ(! ?y*Xƿ{9)Uf7X&}`X`0UȝBK{b8`X37Zf6voMc~L*W[/[c1MMHM](J&}?cFy K:rK}_Ϭ5n526\Ώ~x/O[_<3sEKFrJd|CCm&G'EL" W "qdunTթO_ rd) WoݣRbN"=Y4o߿gDNX/0 [ Kts&jA7V:mN.c_qcOu޻ZAw;O+5I. HEv Uϰ)`q_BSK'a\g@3ZGnGd8|mTj$#nBĈ kL[oKfy e\8%5zRzGO?löKlwNsI3MĽ(4S3>Wt,Hb8,R:v:1&$N5]Rs9(-QI)ph>1`hB]:{\=)=}&xa6n@1ؠޘ];@<Kgn#Y]P]`wߣ])X.^pIa|wG1|7:CoWI kXL^fG$u\8nXFu2氽_EM ,|TG NQEẐ(vz}p-IW 6i0fiC9yhѣA-rX!\z?&^Rn7j MJ ??=b݂|{8廝'L(v98wԽqz>xfͻ ,!%q&|`qU=ICs=xf륕uԭDd ͒˓F>tfymuu{V^IZF &J\ 1 Znjt RD,xaި [)Gh9ԌzG?<ݝzyHpNg {^4NHz~yB$s}GNP7rrʈ.:GKm7o]PQl׬}b4_w=#yPke}΢ ۯg(/<]h;50<5cq>v+إm5@^^!R#E#o8%ks\Z̨yQ-իWv*埅&J\a0NG^p/vZ/oIkftI,-vIEfDMEܼ$6 lۢgSڝO727[yR?@7zBkW v>Sgo~vǁT? x4pP^F< IDATAյ ƚ+eљ7N`K[~Y&]E}yM+a$p}MUڛQUl^/={pC_4o:# v^b8O[-AɕW kr5vO qAX:k*ZűjM*4>Cni -~hW 2IOvj.+4a͢ jGF6zbIndᖿgAנ-c;JMtcȤf.(5*@1yiq.yg :Pj"o@ xر37zG̛jO9Q .h@ Rt,S/0lP7hO_t(>glW O`4贪+}{}d\jEͬ~"bpk"qyBa?]:S(ͽk^j&'+weĈݘlF^]v6 t1x`1'폏{=B \м²Ԍ< x|0 E1Kk nFF:bv4 ٬ۘO8*e:#u2AgQ csDͬ K{KpW,M*k l./m@P%8R68^^$c;a%r ^$ CL{t٫KE}ݻ4hy78yzZ|8wMP5#0i#ws?]PWCz<8~WFY᛿Ke;cG3_'xvx?߬mǢ^>8>z/p՚ܳ;@ڛ'dd ÌF?MȺeϙ5s*j 5ӟ8VUS-n~n 뗰+4ք]cjWϦR]|,|Ɖcs އWtuO 4;bϘKWl^(s wۺж[zyx<ѠT+of翎Mk8tjxHH qx yuܭ{~wlZwD^EWX f M~zE5"tt z!KF O\VpZ_CNZjQȹphg~ѻ"yd([eyWR' x's&_}UrY7| EE~11(ܽWo~/XLdD,q\QU9yt;kM0>^8k'X4,[=4e?':-;~y?bܜGFGD".ikjk ~߽sr&$NXIF}YUssV8Kl +_?8eܟ" fC7*Y<}y7IX<+氹OQbq|=t۾z뗰e'1ߣÂ"WYפnRnQ?!LȋTJS<ܻXu9cqhu͓8Wi E vN>=WlΪ9Xm򘙄#--*5{)+RTSGjrLZPTGnD}V|| eC_ΡJVasGH`֍[zM&D[|ٌO|fH("}lsTx~y̋-cύ9.?H*Yun~y{x^*I}M_CevzJŧ_dض/ _) fjn\):>+pi)#XDasN!bh"]c&7\9[rxjU!O€&TUYUF&L&kܐwE]Nn}s_[ϝwP[o~6w:'O J-UK N3ưP_z y4vtdⰇyӦࣗj4Qa؏N~|%T&ƈcf_إ>4iXL #x1Ԕq%.wDZHNcqb Ҫl 3Pp3zOYlsQr2  ,!RHc'īy7pgXLj%x&ErШLUV%$8I ჆oxk<.Ƣ{ߤF_Z^֘Q,&s&='Y4KN=}-V k_[4@ :WOF: xۄ^9_K9SPܛf ]Łݼ]# YA{fyٽ]a/K7Y=Э=^|éͶ)'58]~ (aHc%gF}mzX:SV_Ok醸owW^QޔySd5q}1)36(XR>T.=|̀1IÓvh4y~sxNӛpIpO@0i9IDQ?u o, cLʄ xVKnRΪ%#X|~>)#v,aEm+)=[Neu/S{,/4B-vޣ\jn/=oS12 g"Ozjɂ/jcE}6l޲{jcgz-5~cHwaiU3r>ZaO7n[5 j+'jEi1( I2ht:o_ء6JM &A5Vupxk59,o^fU0kU{_w~h9X_GFw^;y$6 ~ߑ}PV}I4{YE6O]SY;yl<{Y_ѾwWVY~z,ղV/s:JMK% /&=up`qr*&y-||éG74E{ Ξxz5UhFԐ}%30E yĸ0/ @&CNiFařy@ǵ+vJM{OGv+qIcTj X0 E%$Mw6N Yx8lQKx1mS7~wcPL&#wэcܶB 7j:i:E/+'ԝoҢD{3¹LJMre %3߲­ѫ]1vꮗהj0dH{'Vy-4 Y0i #j/!Q3}CcBs9V745>k9XL_H}Ԡo~k4l:-y٬{Z=.w', B<dw‘Cj4lޱ8)'8a4 R^p&㨚:BAwߝJ Wl.uF@1%貣 CRiw9{SuV}+Pĵ^ch_+t0/KzWPjÄեO.:vO@ʴfh#E;0~ 8n._K*׶MbuoԘɪ78kDdAׯ8L6ױ/FE:b?h4Zm@KMm60pk\قY@DӟGo-6s :,.^~='SUDGD )ǚ*wcS-~aq:wz̩_~bie?w̙6bW/__|.MyXtܗh jRJDof6=A_̠uy&j37A3{jyc.~vFL?@~6Me19mLBZCNp#M z 2W{_L0j!/Q)ߏ 8m8& w_l}Ҷ+5Yivc]\z]z+6v}^8l.poY,2iVyk2N" 鮮:}xkR9nutI2LEYLM_Op|D"$ãfO:{lۧA8܃Gy1.e œ|+QkUBM=~a)#} ͫ-ZFڱ_8f`-08v < TS^tp˧Nyz]tߑ)q Vה>mXv=ZXvu|ҒzyPDTj\=Qӷ|O|;qԙ3CS6X=cڻkIS„,(5}H~͓ڲFlFYbo|~ ڌ@Hdx1%|.߹~cPipoSS_Ʊk֞GV[Jg5'< V]ѫ1Zzարc"MURYaJ>wA>\i[Vhfɲs?Ҩza2&]Me֭k1vA(aׇ4.UU?>ӗf)PYpY{uێ}vIBAFZu+TL5FEʤ/O4erEh3MS4?_ phRRNa{VJM&O-Fְh+R'Gc*֦\4Nfg|2E@f&2I<YT +jf89&F d+ 7j5~aI wā{(AU`2YޜU_I\Ԋ_?SzZW-!Q?M߬Ub\d}Ad8v* Qy3gCA~~~a[po,59@lPrՒ<˽hy7c '_\R'iBFOn7?a C?I3۟Ex[V 9*Cnj=.ΠMhQj} 'w) R6?^"0 d*늳޾}6-*#9p &]*y@>=1UL}0-1[JK6[SRt៸zHa->iak yTבn\^YY7m/cyA I1Iwd1J]o*oZeq[;*x#M?jpLWġitzV5 ^QKR _oş`Ι}WNvJ?~3L.d1B7`.OH^nKP@DUYV7΍ǔLsw2h4vp˧/wyvE=?|flwZxMFGdzX|p֝G HOR+j! V>r艾>gd IDATO@n֘V:(@<Ili1I ;U`H+3q>ϧ'z2{TZΈvIwǜe1Y/Qh$e}n-JƙCxr)S1%VR NQAz/of <SH.m 9QlipXb̯C/PF&B3{9-ٰ*6i"C 4]('$ڝxw:߿ FL!5^!݋F]DHG$ixFՃ?$^0jķ(5pJ>iK+ SX!}q71=\̯}XQ_s]2wnn#]wA pēĬͩx:cqc }ٞ"-w Y:^:^pZuRSeԔB:ו!pVeJe蠷=(5@ׅ7hB3GFeVHu<]C+7δo*ZnMg"^A|LE^UvC;d(*9{0+YޣIt4<>jtߪ&"JM?gߗkv3vJt$W (7{xb{8zMyYFMJ~/Wӥ3ߙH&/۟ 3~uye^ysx]`crTLf7umzG%1xxLtw9ay^]KMFq&԰t~էN6b AiȊj͟-09#7P`ıiq~/HZsN7׫ɪ0>ka1RcK֚OhT+ҴJ?JMeqBI^G\-QPFiW sXzp;FV4H3 ʺj_27~#q F՚h%zTex5l}Xog l<3Z~Bkr[ïy\B-֐x¶RY5zq>S%'4:[r^~2އGkIԿfhf r2Xiq}cW9n7z!pߥiw5ڒޣ\CM/mΟJ-NrsY'7) 8@SΩԪv>GB$QS_q0\{u  gt1"d 퓨Nb0wDaZmﵶHjQU]_u^)Q V ϑFnTW4UFm)B>lߞb]/nԢTZ7k ?VaP@v^SWe i;FZzZSjdp_W#\Z%I N#⚲6_.3kP3A-fr%1 ˯)y:9L;AdXK^s59d5=N3GW)1F.kkw5u/wLfu;V/Ļ?csY}yA4}5Z]GܣRt]U[W n^nA_:gwTqOqo8=}S;e\ewϘAoWɰmnkLh• jc^8L,*+#J<5y;6,0Ư'f|v{MRCgpr&$LzU#Eh5E"g̥9ZYgxJy5OhAJFgR P`bH,59,Ci6+7K(YAÛ+58y*YbN0[5D_RgOvMb-Օh4yޔ.z&&|@ǑWej4dP֬SW a:uJ,?EU7O*JoZAr fkq6eJ 3ϳwZ*{#.3b4 vZ1 :}Q4:Lwit'RU 5wǧXu`ǯ2cT8_. өC]`jo* r0.W]|eKK)h2yFML+_[rnqdՆS\:Ҧ\zg6[[hDq&V渦Do\O~+6=7V\ZVK7R,1(5 /jg'5g[qA7X̯sjTM746ŏ'a*-E&B81dN |qK7M&Bc Vk[HLϧjoe(Ӻy5:tio9kt <7'Ek.obcoDH^dZ<`Q5HW=r u~{嘦ԣU %6kR􅮩;{6=&̊xLka-FjtT)ϖ\N Aq Hs`m$$#-fLo9Rҫ)_WwD=~WbKQщ[|۪t Hz|INN?)| Z c1Nterl[EqbHf50YtZ,8Ѓ4y'T bg_3iy;k*&50՜׸' b&y)DL.ý3?M8F#4"Г2VPhmN(ۥ2Ǥeڋڒ>-۹$ 65+ᙻ*dfz(S$FId|Sfnj *P# f?*Y4%3Ѫ+uAv^ů~ӮWڿjʯ2cc^,Rr^Yה i$Ͽ@jX\[ehz0x,ԈiԖ?J•0&^M,x؉ֹ6 ?s|;^\'rES3>m?t:UTh_TzBIw&̕Zj^GHd,)l> ,3o/"CB/xmٽ魯_%Syt7ca<;EgP/io3N|SgMZliD3[|"D|]>#C(8GФaLJ. ٓpTix4(hiUhc/YR攥fOZYx][{H sU}F%T \"'誂dOƼU?2,/s/ մ>2uvK#$濫cr@`~ΥQV#h4 y@I;{,J`_PRV\<,.!11СzŦ#BfE1E}W(@u:I. FÅOK,>"D+(1qܺ.1Fn_9b9=PfI#zRvӅc 1QLkO'>I?ϫKZF9T׽[|л Vޔ}u3!"7[zZp=~W-pbğKT½,d:3hfF. fg!LMo^MM>qLj>FmF&?h ƺpit8qq5Fhɫ*`mepgi221&Sc[s2#y#6\`:ZwZNM,1i,]<ߏԸ`ʈWpJdSY6ŘkG&ԏ. '{wNKq<Ѷk_Jz%ZqA.4T=jCPo0 ^:Ma0;nOl|5֫Mˌ ZnI|vUZ"a]4 QƬB3%̩jnhj45<(1YG?s pTrDj{xnM OZ4xY5wi*RU~tOȍ}kES?+|lbcK) &B ۽2eSjMʊ.6֋Py٣ !Oq')'A\ >"3z$nߋ4rխ_r|U>]ܸKJ:Τ#R^!q GBxJR`ۼLIVQԶS݈cN~▊\?~y/\<]DŽZMM&j`Чg.'O^^ÞrǨy]n[d^Y =y*]ٚƛ1dRsWZ{p3O$ \|HP /W4(x'l2:rj rW`x@L?jP E!t4+mY;U'j~[,j2;Vw k9BbѶ iwֿ[X\D8<=O=k`SO!&ԴiOdfY3$h/n{vAEͯ)+SP//e^qԨ5)P *~s`v 1 t)I^v 2[BhU 4WB FY 4bSo|+xDx$\|зIS*,齌\K}(İ ɬ5ruQv^eo  5f7y*P1w zyFM3uY[3Z 5-|xj-75 )pz3LkC.q|VKz>Fg*a0 ƀؠ 0Ktw#q1/F  |]v7YރK&`2{,6 7Ij\kbwP$NKt^?Q4PDY=)?SfҼ;XM\s)Āvj6{("ADwcċSCx'y\k9/(0k>_N lRb)P!K2k |X"RܮZS[n_g7߰V+l3ȍlQ;o&u'/юh8_o-x1UTȴTDJ.$!ngPM{1?Al!&b0 S @tJ_!zվX1{=v QP?a:oOf=#ZEeۓPpuRU6E4oim2x Hr^ɝכn+A1eM-.GPkvlf0Ff?G1;ZbߨGswjxOBD #9ƬfSPk.fM^}lAAlyQ!,zbiđ87p9 ve$&+#lˀՁ1|㝈H=ؼ=GyM%f{=_~'VBdmk2?aVzFDZT֐oiF1y$Uq2{!JN<;^j43k49ErCt"b Te|Y3^T5+pCj{FYYQUa ]¦4,iI"qؙm .Ť^=+Y?pؼw^" 5?[e=WsmFyޫS}q{P %{fQY.bvV%OLYso^A$Yg)ʾ+5{-j2ϲE$ wg񻿇61~.aե gZKd5Ҡt=`0atjƥ *橊U+E(y]/|OҫfN_ճ^VV“*kc0"*`ڊ/xk0u׷REb@2hУ/<ǼMֹ#-q+dF͗|v3ϭofw ߻mM=`=cQ{ke546*lvBB鏸8iaB/}y{xQ~\CH`yH &u&-@rXy9164z*~7/#,rpXoݳo݇t]@ [l#PV ķZ/,;ʭ[/!]axq,wL@C◰DI\ڈ6LVޣ5Angb.[0kmRm|:-adr,7Y'`9~u, 5>&U)%ݚ?M*j (nZ, n r^{CcZm+?ۋTEʉL7M+,^nuQ&S_s&3J;|Mx&vo0]UǷ*JbL(FMl&}8.2*[ !,2ˢZP7Ĝ'-D5`-Ȋ`a HRn:J<}Z0Kب/D΄k.D(mG8(`C(A~+Jc[_F sRnw!耐'cIA&2S_M;3uU JȬ$hkG~+v&_*u/.ɽ~իnJ\g6&! t{zZ^Kt8&l@BlShAxc>(jJ=]A\!~H;nIB<]ng-)B{PDh֯$O>H'&> ෻kz/$@BERt* ~JkH\^.\.!._~Svfuj,3xm5cYgW,&+C|ͦ&׏2.ơ; (-˜L/[q'=I>@P4~!c(F]CeFN feE}q[z9d{`w%r[ :q Sy'=Wa@ `¨2 \(ls.tJ,np*VȌvR)q`zš@>~ _=i 7M q[^An*(w4[,&'js'{JO5pװBpS2L=?z3Exèl SΛDGaARo\L3Puxm8SsŦP/Ű;BW;?+ !q Ք_cZl7JR[-չuJj> %&fH ;EjIkzAlPb{ۼt4T UfCHQQ;V<'p8,2b7ƬuyP`U9rkuGx011OAs?3n߱mqI]aL$㟓;/'sUu箝+PMJ<[He y\ޠkR>ߣ#Nq:sx,F,!rZvʚmKKULM[:-&1U/8?}Dz=5dwX*#7/5ݵq[ %2ɾws;7EAݼVdW旟]MIjEj27+uGObNIs\>fc˫>Avb#s"_cRuN5C7t4eqx~Ԃ{{OQ8 h;0]>񟇛{VN=_Fj˷w݌{mΫm"b**'/詣T.{9$(O>TX"P5)0ۗZ|Wݩ : KL:jկ~jTۼ<.ًg~z@R@Q"xLAuOw[ǫ$Y#XU^/z}G{KrDFnYp1o3j G9u@QL=Xб]wRVT6%tJRkx֥-utYgpM:i22ϱe饘B/Ao(AmLقvXA.FO9!5ߞãpJײZe:w|w]IN2+2wn]AU4zH2vgm76=;—ьuҾUUw~"tjއRHT56l+N(5N3J3d(RIY2dU-Xz acblIM4ü8.>oy4ЂęAٰ:B$y!q_3nLL^+M? Ajʲ 2jMPWKdwİΫĎ&~֣W"VRW^u-  <u._1t]/Hӣ:JTX]Af]ekb\2(IQx^Em(NPPA.4߳`vFoE/,ЀV|7Ri.rϧ=}mC :ZY(nΕM{}MzՎG Zϑϊ=|n^(t+'|E-ely0^3}\F)MM 9IW]ij):("'$Qɳ8Sn\?j'/t{w U;._Q=DҪ*yiƝm/˽C?oj }E,6W%/Q%\ ejk}o͵YW?ՙ8LmUnX~ĐAA<.OVHcવ׻L۶#,^ӺUk7wPi*Xzl @C2txgCxK ht-|Jnڅt+YRfvvͧ:Rr\W{n{_'g0%NsQe Ͷag$VGHFYu8ZFbx=#ƢmHSwK}Bѕin(͚3YS0WuPxDkDl%YtT I6x7c;g;d8)Oю1qh;>NcΕ6TtלĢb0=rhkʦDtBe=OkwؿIX!=Kj^mT>/ض3gH&pc*/@eDp$י[ٸ-wƻaڲkW s#ytBBVGI QSYj l|ymho̓Jxp0R*&Nыv뎗{-ϛݙ&~ `Jq^ F_Wc0 e|M5mA.)& Im} d^KH: QiFQ` S;"naVݓ_Ө7=mpe2i=Yq\z=aR(Q>p)8d*S MVqJs<\~.Y"dZ\_fѾ Xp@ "hӹPdӪᰑmÐR-Qd8C?CT_L6 M+G2?B-Seg|y*aphXKzZøZ"6=nfrSeBSw}"PqV̈́.j-Mzp{5;c?9*ꌈ+ hh  vش~w)ҢXFΔnUERc߀=/leh]V^ 2|C&~"#qd^.|9<ѡog<2"XSrÂ:3*Rv]Q^,KpZkA`&1m00n-ȼ% Fmcekn3Ꜳ-qQT/ŋԎk:_N=|3K-< Vo[q:mmxwkvl֖9+#\n/~e{51'}Pq溈C:u:_dƖUS$fk4eOΏi}wEF|wYo_}ogcʎE_cPAhtq\w=muwX}܃}CCV\I:X,jL c׷D6x춚ϭuNmVQW'gfmKl֒`{c‰]4i %X RjYʾ_@蔖<̳Q^{lro.+2Vka h%ڦͺfRJ26ڲ9ːZRAs\V(wPc0Yٖi1x4ÑۺG{ՎfV9;(xzJǾkG"xRgO'<^y`j#Oݡ^Mbi͙{"ӥTêSRգjt@fdx% ?ش&JgtNڝ噬6BTq _/% ,1}~QcT4PF9kʗJr[hV^*-v+BijKqn8*#g<;tH~EK5DEP+הg ߻wSa͡7LVi!3"jEUq\G*+H".5N}}va07h1DbapOO_N2*O31#,BPAC1iaAFD\tYW= ?߷tԤg##f_r55L=DKi9܅B0]2wEQh @>/lAzϒj~R c5׾ͩ \+p $* ,{B-7ZEqX=3q-4EiCh(6U#E97dJ.oϒ^1)7Kޒ*E"e'I8^,.j\r=ၠNYv;b?#|LSb̿tYy@)R n|~Kec!:i#Ĉ_#nl*zB?g6Rsa%c Z"xZJY+g ClkqkA([#2yu!_[0).so1^+ؕ{RW-7mEhi F|!tHmPHxTc8ٞ(UW^u' /8V(V팋هı=vz4歭7XL6mi kyR]ucI't1sk$n_ƕ)e#@gWn`Wf L"m%\n5m0A0ۯǧu}LDtk'~(C-'#L[j1%*j/YZҍX`V2KLW2qxH'ٳ["ޣwtVTIci_$jlvԠ]W9o*QbzsO<ܪ~^X>(4|zzοŰdvi EWS=,d߳`k0jr-`FM 54`d=Y0jjFM5aMX'{pX\L{ `HTn0~KvFMf7P-Syvu}>aT(Nl&k*U6E$,j-/kLSEUT@ O*>?Vfn թ_X4 l_(($I,/XC$aQ%H+-kC E\zS<ǯ+fLZ5xi!5$c@'9lBU ӿU;Gۈ h0inLX5 R%L zqMNY+2e+s*3}y\x&ND 5iwějt*Z^X'_, q%N0`ҫ}N FNO8)k/e hET+JjcA p~b,y|7+zWs^j&;0+7!Cg{3D&;W \NbrUjR a!DBZ l6 (!LAk eYzMѤ"g|m$L6 9 zNe0r ֎JH#/rd)t %|B|c曛t saK3_b<˧6ۘAXwvg5:mعk+#C۱塚]];#Y6h }s2NCN=8fj9. }C 22lE=}1Ţi+>o}f?n}uGfݽp|TdZ B한ZƆi4A|wv> phIQ2@ˡO\q\6ΙvܰkĘcF9c]L]3f3FT挙:Coû~eYQA]l&}~g.o%)we&e%tjKlri%pU&yŘç7qX}gqiԶazkh >-ފ.3 aOM-%> ra$ύ}‰N92EqaqHj?|_ǾO Ѵ}[1θs /ާ‘=GL[OO(2h\ˎRkv.M: 4mPתs7f z^jǬ='8 r]JЮC#f E?_m+}ªѽQ9`œKX x p8F)@B 4|76B QZE|C~ψ?5 z{9?~D PfٵU%+.-'-V+|WK8u{eã^zeE߭iuj+EQ=5t-(ܼdMSЫ +.Ǖ#[,(YV%7K;`VxIzvT@퀧&h<^t愹Nnf,ԶU9ٟs[3k'FGf4t& y'3ϙnFAi|A= q$I9_>h*66!k_B$pk# l $:ʲΗg5y k]q&>1o)QBFKb9,xw~AdkZFy?#u5zOzw FR98~ؤv;bGIFoЗȤiY)߅3i{[1UxM\ zϯ z*Ժ[nk-أzjPTv='9OԾX rؗI$_?=|,xuIJVJ 4> _0s~thXBZ+;u׹"/D BLTߘhaaE¢¿ݎ&ޔqS>~/̙ʌ|*f ԤY(-ڽwl^cgLٱ]Hp:mIiIzfv$ÈL30&t~A 2-.;s\QWƌe⾛؝ڶ`Kfܵ0!l."W;<̟պ;MoIUy,vdKۻ:Di&d¯;@ְduTˬFM<8kazbGHȰh7_%|zU 6{ViFj S v:c7\ &B= \9ۡQScz eldqP'CEiP>ï5>7&lNYY2khy}ge"f;0WxaWf.E_RJJ>U[kM&W.2ejs?-^+㟃 P[玝 ƪ72sxHÌkr81ŢsWqXpwʸQ"1a$<4|_e3}|8~m跃VĄ; B$Qwk%WUMT|n~Zc^ c& mvˋ-M2fKܽj-K._(yS)P4`LnnBE0XOM޾pyH]/wok 7߉tP>Gp1~qNTx La, c'/,x3n^Q2EٞÿZFC_P,7Id[$k3oӬk\=0rVqq35ι؃IϾmuD[iDXlY];;?ew{>nc8xv\5LQIaLJ_O?kOYNOuD&q]6,bEuLRC{DQI|iX(iEȌrV_g˝&'cC nm /4f~ſSeVW+Lz÷'nh}0$-ލbmA_ᩉYM_U&9i7a]?<ݧfF1YCҾpYq~95hb>;on{~L6 7MTe_<3YKw 5BQiW+ w#WficԊC?0)Jns:fI Iz2y䱘4FQS:zM9 a>>3-oV`LnnŊgJ(e\6S2ToOЫ F׬u5`x@Q4wR쌚]7HIP~JuըI,;% pPslh&_o YX[6GWiu$46&T|}mKj/In>StzN7[3<5'.^"Zo4u 6E+?y/W.K9c%LrbDև ^y_At& %ϏYK(L IDAT=]W?G|yq~I^$Vs$^gƇ< /~x(t-Xf>6 E]tartC2|dPD]jQvJWpP}h1oyJ_v|ı|AdD'ެx}1FqAtD:I{te tuZ|K 3`;+~WO{}}|r4YxV9Ʒmhj.r8ڪ\RVa<9|G|ŏ_B]kT֓_hz>SS٦[Jt1~LG.H d:_\аQe9Ȅ. _su|0ÃHnUΩ1W/LdI z* JÄWyy{C+U%~^*JWۏ|1I&Wn^v|[l]g,ᜂ[5ZA>m__5TVKtm߿@Zc ѦUG*xrj jm exÅK/W~;zjaqը;t![\67?=imf=Ӭ,TeҒ +tB̈́;?x?9B,pS k2 ڨWHIG0&۰oLVjhPGyB6?u>)?7$5W 2Ҩ׶ÆYike h4zwm rzj^a~zfzǨ``߁Otn 4 ȹ}$g[^T@Ż¡mOiN_%Frxl@֓#saLޟ[>:LD:`A>,HG~AsEF%$ e?z0PįgsĬ0. 5@_j SUިɢn}&|ܙf.e+9..+XƩ::Ϧ7Y گ~T c.m%?'b]%޽r&EkXZZtٺvYL։HDnBj0FA3q OLc#B"m;w`ն7O?9O u9cic\>O(;N633 #If { \9Q&ʈm0RiD&]g8<{j ysբ$?O\9k;bLߌ5_o7D]8 bÆ n$bƕ g %b -N>Ĭq:C|W&=Lhp@@M^`,ݼ7!WUu6&ۀskW:ӓX]Sۂ9ydY%i"<_GvXho=9f\1ӅB9j M^5nۻ;`i2DJeHz6ъ$As~pXbJˁC̊oJ2ז vqRu FhQL~¨ IY j#EmÀAu[&E6• LY^]0[ E8CE\@P $t}^I@hIBhgn`+r,ŬM\2dik*!'\G۸}BXYzY&$Z%*tw4i9톾ܶ~,K S|76K9-ϥU; |,1քcyX6 sLw%03'G]$0SJUtZQKMW xvaƄ<:X$ ievAs<_w&r&On3y0u^ٛm z -y#ǘV(uThɄcl鮌)j B#h?fPvu!O)3;y}íBSIF; :(գiFM4 m?_{ҦVUԄ"_lh酩҇Ŕ)Sڷo6͛7;LW†cԍ2Ky8bn/4rp mrƘL}׵0g #s mϦjeKyyZ0"4l?f|bg.5(<ݼ=~_6(=3ox%pb'_tKsEžYY{bgkm'U h[> }:zyKR͋_xbPIæ/5c)/ ~ͻ6xFCf%EW;I\0Bm /]~UN%T{?sHUfC1MjyN 4c4I\]'56֜dM@~xY[j"W~9*J\S\1X! RשoFK9NfRq0#5.9 qYXf!ٰnVq%jbh;C4(DRD?g _$)٪+e77lXvI%-wT%ӵ|sK;G;X$ iGWu"tox;қ35"X4@m ۯiLe~JO^{kBnq5CTNkR?)Gd 31F 5@QNNaW=2P.V#zfeSf~PkSA7E/n l 2M"?B[EyBk|ZvҼ5j YX]g@V5‚Ҽi5j1zbkϛ3S1}Gww=p\*6fAZRQz"n2aL&ÛKZQ q r^۠-dUڧu8dUgzW7~23{5W`~UPuѓN!'5c ubr.. Ѣ9xj7y5N%eH۬Xiܻ5bSE~բ83'KVxq@ly{qԶ`OܱHh$="Iʼn ot=f0Jמ]Ãs(#~( M)7?1^xm ;Ťhuu֓jFݎNajre٦WOu|][%cx3S󀏢{4@=tiRDAw"R  "RPB &^ 씝ٷsSW35tG4\UO@w%:چhY W4B{˃ae4p1]5h[VqJ?K5R]| K< VWz&0'rΓa)D]`/;#/7d'(R|a3bmuRG"biĨep;UerMJ'aB'$R%&-:+Wo9焐R޷$qƬup0 xu2.,Hhִ0ٲPo6\}ޏO=hT&vOU=X04c .RYUC)u]:{bi .--7fuA kñK ^Ȇ4u97Th0L$V<)o/m-[wy`MۦR^S6ee2D;E2TDl2LrZҨD%pƿf+_0~]9Xa}箟jSUːI.u2KҨ- F#^AFQ׳ 3^*8^ SU} 5Sz߭w+h~zmF}@x㹣ml,tV*QSn}z1<8Ǣ5W_=p\.I=ىm çҢl~R438u8PPo{jVupҠ EOG#K}uϮ8Z6,s6S,Kc] : 9-W] TJ^2N dmon;yT]h`L&"-:۠cZ(BTG;_GkSU'rrTrTx%L5*R }AL9XWX?)7e7 SNf?WKǟjs?GlF&$ 1!᱐١O*G{Ƕ琏SZ>,&)v&sд 0/>^9 S&=.BcҴ%ZҨ)Au@Ky?#e!٥* gO8{ʵ)al'! [C'YՒFMt))Uz=W>׆#jG$Ed Ț-%{|9}ϳvs&=Jj 3jYcӷ dVӢIO2i?/8p۵kEAQcĨII1j:Mz1"ɹYaICf:Ji^>t>NVh@ۖ\TrS'/4!=5)U!7vXNaչK窚,\oT4j_!KWgT("wKx ).Ŭ0ڊ\^n6T4SbSFkE1l 0ld H4USǥ _%t:ZC_!flɞ4Z{Xvٟ+߹ 4D\/չRnʹO台eb)@YZ -eߜye>^bϺ|I{Zd;pyCm2Ruc s`MѪ$ZtELFH5E譬:AYgkU9Vo#iiyGGM3>% hiaAz ܮ uJtGMUfThju/$;`­{8LH7܇^}&1Ult^w6ΘP{+a3x|>ynW1IF_ppU7CYG?'SL5|(3щ-{{'%¼#ҕtv w>L#DaB&$]YHGfMJWrH˫C&j>lKGe1I?c\TqTkbA.yh.V+<'`)FMD-xKj:X/GV(<ǶB@@bn5zAwۘE.0nqĸ%VRb)+{[A=wz^uIý9VUxeY O#cͫ4J Ȱ}[ cM֮YdZ32lkeaU˩E΃fYhg5iEϳE㗼`ȅ_ӟ.H1w#Sf dz"5x||-'.-k['ϪC 5YN bqR񊮞<5X9WX1Y7PXThVMp/_OTVVj 8ekj+ ejbs'.6;/f]XbZ>#w~JJ ͒ҟg<(%$r-Cl^Ɠ{GT['}B*Nw,IW;CV5aY!W`1m=9-5z یO}WM]t-SbnP>{jt􂃏{b&7JhE+70>w\eo_O>>7뼗ཱ`ެ3-fCCKQ tՋ~bl]D:p IDATw}@BUUQm,0ĠFugbwH2zx^n'7צG+\%_L}b+0:hD͘o KZyKP0)rmCՅK/,KURg?ݐa8Z|J.^Y6TY*S I\Ix[U3 ~X5vasL YZnB }%kWHĔ$5q$3+%pb Ć+,KDmXs()b_`9tk4)OLH`B4:l#*i&Ot r-X;PM/!0e(b:nv]G@ۃKdd8E+gj$l"pqܿv՜% 0-VqC;cKԦg;X~]N1}oeYL}gfxHo>x8WfnTg]ufFEئB. SOP:c>nxx9$rrߊ5RL\j禛%>sFܤEYޣ= G}R] ==L*_jAi嬋wWhWɤC&`C@Bjuzyj ÒL7AE^6jԷ|^-_SD6ډ?{?֐yEEr`ԅ+z-A;8l'V=O>o>fUxVXh7'+Ǣ>dZ`%EM,5A)Ү &Eަ;DM(V*Șc9c9ب,/;Pt LH`BƙTN/7B,U\;"ݭ }=0ikHԸ+Wkn``ĢHFMxMR닝! 8z+t 'GZYP]^9:~|wj㏞o`4aqVNx ]Hif0ٰ?g]`3y 1T*{89d*84*9w`ͬOlƥ{:1!<.+)I\3茠Ǯܽc,`V)լ`Y),.Rk!V 1g뤅ox] Q~fr#6ݻ 765).jW s9|_)ЪU 6E1;/N/+̡3Q0~lIrƕp؜y+7SR]=,'+7L=);/L}izç80jCBYĚSS}6}qG`ClPU>hd ުbUZMdݸ,F{5whdXX4 x.>AUf);ukD3ZqxFM?cDDAgXpz9?,ͰCQ 8`LnD.ub\N=_CF AЮ?:,-+u(;oI eSop6s(zQ)[2zKŹm[j_x5,.SRoY?稙݆MimlUeFSVvyCQs0%bmeep: q"zK *ʖhaסo@al{ڄi<AQSoݚ0cBRvo߼nǷ]x)3D"FM3FgOR-~YgxnZ쩳RՏe[0erwuk؜NN¸qmBϩƣ_T?FckmC](?n`'mC m;\brjJiYЪo ;[*. k^gf Ua1Ma4Z2V-ٍCܱ<cS=~ułqаf Fƫ,`Svת1]OH)2b?ڧxP~khSVven@P64%垂QzWY 0H/rJ7Կ%J5ڢK5y,zV1˨V6 P787<öeQ<5UGRr0USŧNՠHnM}[TUVa]! &$0!i I]aM|0OUEo qtG.i1(Un҈h 4Zs+Ug&;x%ҒԿyp}LThBʣ) 5+V;ofKjݔ}][vͬ F[ pŻ{Je dwswlQW6RcUn::E0-z"6آl!t1NC1"#vA\(IߪO +Xzm. d9R>li4H]x(DW$<)wƆYcbG4e)sAP1فkB0;.jALEGt0`$ўh]D}r%LQX*Q >6eW6Ej|?ʨ+n&$0! LH^kq-^\h\k4fvq?P#]sQzĂ `))fw#ZFMP+oXAl̼x,b{o7Z0ɿdKt~> 鷰qwؕ|:b[g?pdsCdLRVo[Kjv5&]tG7ɖS?Alͣo@乓RE՛WZjceas,J.-+y^rb'e._K{;{RysI+lmm EGKvnߧX#ђYKgO05%g2D$.+*)sΎMNK=@lϩc+ fm# X".(,8y϶~=>Ň\UIUp&-0bR\j)C֨n*/Q#Ŋv\.ɩB^J$E;/'+zFC/`B'$5CPKOr>V as~Bz$2ib Eh Haڷ04c 4n!@5yhՠ{?#*r Ք}p+ՠ_[}7/sǑ}<.&>}^u'y}{;3h ߧ~S )/Pqi ^|ΪaTAp6 uߔȓ/0\q|7.ўI]Ӄ<Kn)r!p\)9t\s&6m kЅުlcXՁ*{zx~iI'tUywtuJ)(s)F,砊e:u0ee<Yzd4EA\7NK-xW7*#=+¾qNPH~8ױRݓqD(5lK1֘5I8XqΖ+ЌlK;dVK0() ~0Hu\B`O@jmQf:'?((?RxC"nt5AA&$0! LH`B" 4[oDrm+= st0 9kqE W<Ys)FJ5~&KK-N =j)*0jhl(6&E3Q+@KΪA+>:`#ѷM)wFx4I/6trޗpy#a $$QV$ʽ@+l b|U}LrIwj6ن5dX]5YZyҘ2v/ aU窢42IRp̬Ӕ1l$jNQBɇt.q2$(4(q9`xG!kU|9aKG+ Ʌ溿דURPa3bX-`_!W/5VkQ*#ϫřJTb3c1zU bLU|pLr)QP)&I'դ1D!|^"CBq2M1KEZx0<&$0!i "V_=]Q R%玂طg ak sIi_Y}9LģIWnF9z_)'e{+ a2FMD tnv稯>UXX~1zLy JGTJg k{DRfܝn_:ڸ;Xv  NFq~CJLI@VpCkDo=iKd֜`z`lY5UQۅO҇,Ua\3!\(^(gϱV_P<_{CׁAwg-6W)ɤ:'ܯ,`V4MTԤ.{'e7?Q,Q ,[H3ҷ|Ti~KTg*_[5e I2-mm UilHq壭 gy[7o~$Ks$˼m.Ld` "cYk*ˑQ3{> jL P aZu֢6qJ"L\7:*#&$0! LH`BR3:Vh┅j|cӣfbFԁ&ü,uxEڌVݱ 1Agi ~hU~֓NRwbzXrOĴayFMt܊Z3kNevHgPv)kCL'P}. !y:|.]U!Kp־$AڌS2vc*W%l;vl}"c` IDATnm̒t怈qIGtiRuI.4A u d5e:jG-@Ib\Ze U0Z剠HCFO 25nQ !z򾍑S=*w -f*p/pl *qW6h$ɜV-E-SUj{I/q:6؊0{ʻbݛ檺 BNu Q1@}~E'ņ8ㅭYӈ^=ok^0sz;2jZBtZS,:0iw%ff#+r ʕx"ba:Ss8^Ϛ*ѕ[MWlB{ w0! LH`BTcҵWgDՃBm om۠}NVuc sP-Yr5%^Ci*57*OP:c%F B[ٞe:qBCS3%hZjN#UP10cko8Y٪r#1dYjudlIduUy^PŊ6XjՋnИ] -+^Vh9. v_PZj3ȗdq2 xȪa^U*,Ia_ v_1>+2RO-[o}RΛhutUB&X"^^-Y{վd/06"?8U$X$(dWOCQJR=8)V<agCJAgƜ&۰VZȺ.zS3[\2qFuW?5(a1ڲҟC#ʗ_JҴzM|^ tnr+Fц8'bŧgGe:6FB4қQ55ZvZqP;gΉޓ| =7e24L$Y$ingi3exc </ӢQZF:6O*qQNfXϽ5(b@į(ohS%"5ނz P!Jzr˴ɪ4.L!4_>!@{[:3}0! mw` /0"mDZ􋥺O D:{&G߅͟ɤ#wkxvFM_<4z,}.E}XA.LpoFWͦi:_%"U?I#'[#>a}9<,5oHp$ = yFMu 5Q@}FM5' FMMܨyhuz?h#}(,pmY 6 &>&kaBj=20kQ x &2FM@0 WQWطrKDd J?2Zk T?3`4tdդсUk 5 3UZk D9vs'nlZ[moەoe Q #6Aq\7+k˭3} ?\a2yj*Sk 5uOnQjXLHR w ?4bc3 5 5m\v K&?mIw붮𠞓F.vqr2D,-+ض7W_&.vs%NmNۻ8k7h l^{ {[F(FF 6ߥ݀nԎn;w}j$YWh@ǘojVDRzh&~mP3>l.Yٻ8y^^c n2"zSoxaՄ{붑j'5#Hy_AFjSř_6ᯱ-0s?91"nho׭oP aT- 0jx%Y7燰.K5+qk_הw cM_YL' ̨;{e+e ΰs"ҙAW<{t3QLՂmojE\/-+!J1XlӘ%|26nsU~BJA[2 Z¯ M^nhB B[G&K\}Is_@B40xi c؍BR\Y[{&{+wϤZ4 3|WG/2rXIGWq:yw7NJpˈ~cmqta=Z4\?a'o_1w hsqV=:l |t꧷փx v=27iWK" kݵgи;|C#Qm:1jzF{v^ ho=q?9+ Ah|.˩M(1^BZf^IqL(T"rAnuZsQ݄x;5)57'j'F{?9 d@eR0VWaR s?1opL 0uoCytet:_g&1piQ^=%Ź`Z!Hy:>C/bYHZݫ&b_8h\ѻR k{8>^nӄ)@q&WKpp͘FT~W'%At7U?5bI.ȢYuXDlyeZK~72 eJT+%fqmGǚHqϲˤ IO`8kqO K^Ed*x_h\r&h8]Z&g'qru+Ullɝ1LR.R:ۑsWy_Euqs9*J,'>_<8@Oaq(Uʢ҂{e5J 5^RVd`t @Rwa=$8wUJ!VMu3YB|US;`sᔸ"PhkQEY"ߊؠo@s坡|WM8sBv^z{{{r9\WоϷm2CF,:vnޑy{;-\EhPV=|Ɋ&;~vb3_2blC]W,O~q"ow7{ \#W~Z}d 9"DhRjoN{QiMC@Yr7Cu>E}DG$̉ŗiëaB9` ġf]f'T+MXbceRܷۖ;_ٿhm{zs2Ic:Qݷ;45uɔU,&;,(e.4经hM9g܂A鷰P?î[@- ɰFNJKMKKI&k"FL4 RTȷmҶO95rk]*5#6';EEqYNmkOo2cȎm:zAպVO8q:fz4Ρuޡǧ4{EIVt2QI"m:bP 2Rʎ'iwtܙjѨ#6Kȁc-56(c"P|&R3fB!g72GQdGס2Tm~}'OjPpϻEvwqrrR+6ʥf9]\V-\f۴۱۹cOPjO73elQ YL>ooސw_|EVaaawG{+־?v[ٯfX8}ً &en#6Ow7vҬkY6-1i9K|d2mmBCzvlNJ};ړbEk/NND^\Z{/v11u%}[4q}Rz}-R<٨ե1b)-p;Ia>sNr\#@< J/DwxxNmn{1׺~GR<},)3,dJJ=w'r0 kfɚڗq Ev[Ԝjn*̟ g<=z=m2#iP /e#VP &(-NvddTDj?vut'wJ Sm| 0[~ZWd5}S-[72ܟwJqӈ8 3 <*]w?͜kG,ieBJ4J÷SOh[U)PK+5ߦLdĠxgqxDB*: h0R7e%4Y[[Yk.8 ogBvn..-ͱqvt&nnѶW;jſ.5wW#dߟWVns?1 p3>ҳҍ7*'/g޺c{"LT.q*o /?9D%p|Vm#f{'M:_~_1j9Sg.SQIU>en&MgCZYz".OwO22/?/;/ō(E\u߿.j&P-%: eraD:,RkDqn[l&/ b|!#_*W[r\^WѲ] ^-Yfs6 *q`XQÊoaа8(7/XLH:x|['lV..vϹ,/T٥f﬩)OHyFM@o;Lj5~՞oFܺ{C$a=tfT1: uVL;v'a s=}нOaI/矾vܰ{eB 8F{}[`|ց񒋣[ɬH91O[о#ߘF/n"I_gfRjdZިoԲCz V8˘J6ZK-د@22yŘz-/|Vцxg#>-Wnjb 8o,۽~q/HE'~bfS Iݖ_xIy>(B?L_5iTbRf4A+wRlTc~YmՑ;y$6C\RvǍ7OCAZd0 DeE%E(8:X,65s XyԂW~bhJ|ͦFk3&X8c׎ϷX2Z*;/AÞ]  V-<~8eDKoG?`ի?]sU"L4O]?K|.Zi/RzwQ{O7y>8}I0,4L?˖[JZ4|wS&$I4?!f@[7~ckckok7ͷ~9U3^o4| x%P=AYEMM=\1s]R\2Л6!ɤY[!R4"@%BB. 0jvvQф#2w'=+8 ANLFM pCH^bg MJZ LS`u%tvlUyIgvDnNo?[+5Mae&[;] Rjge&f:ĥc6EoURr5e.4Nw,H^¸\< 0ҨI$3<Ǧ/dX?r!N&F\]VSos9V2{u '-/~[nuŠV6PԺb8Bc,9vːL3~V{+\;}rsrj63jzJ}y=Áq*2w\+fԿ__LخT)l،cmmn[VާVw`1ު!p5G~LYXRMgfDjLhT' CusVF)MB`7V#~dԷ(xü2I$*5wmEScx!f &>soPkTE`feF\~ n.7Ƙ:9n SR 57Jt)՘pQfdHr[k$6NY4L)QUxk)d)g1-CX1 xD;@i0ϲ/1 nxZU*r 2ޫ4sC?~dr|gy? dLhUՔә,Ro&ӫs۳y3,SD`:||O&jTwT8FEi V[AQ6TC$tñ=bb'2чJĥb]?T*odaQi .Ul:um=|pA N;%lONN.B˹3(9!#h~N(yJwt"eovx0=ȭu$ MY96< ⇃gҲOp},FmzF^Vasi6$G/v22泟NԤ&V:e=UYg|tHZٵ[BߜPGSZ^chb1h^2*NAvq͕|.9w갦.&s4rޜ"%U+,1嶣ɐwE 9 QҏoϘ RΫt6<;罯 KHu,V7 Zuۊ=Cb{Ƨ쫡C9\XY4YB b`p%UZ\yJ~]7xM_/ nʯֽvA7S )v.m!tK DR* /6-5|c΁ux|7^a'rΛ8|^xX'PgM04miС^l(w/&(J[lg>UTT9l~t :M?u5=Un֫j+LhM.wSNߙFI,j\.93Q9 + b۞3ϩ#{yb8Nm (5#`4rءLv8rZף6LN^2zM!oj 7Ltdr嗻d[fxLpX7ŶhyTlb%|\ DKf )r_fKrks|]FSb=S|(SDWuHP{tU =އ2.i10~c7*ޞY?"piWvIjDJ T KZL!I;uCҡUU˞ c]jlgIN^sh.,d ֪DcK6Y4P_.c YU:Q)m0y*=U~*R,&T֟?n9HjV+oo꿐 ά:5}Q3,]ح}ev^[>1qQ#MaVYl)FgrFyedqy_VLy=\\BgWS5A}nVo;_n|zi?~[=p*2~~[[,@0|kvU[Vh| 3hƳkQ:03e'/k0ѪUZO5>4*e<-i =O}̰+ ['ߚM&'G7.m#xtų3)NNK t88;9;99 ] |<+5}+ _|Yy|}6O.B'+,8EeӍ"^΅M8HSv cLz uȺmڈRc^ j%Ve%KjK˟09nP/0)0-d2Gx!+0dc_}\NcXS+~c?cz7Qu$ 11ül?Sw[-)%ô/՝F`-EDͣ,fEL`_scnQ;;Q2:̆BCXoW0rrq8f#BK8lRz=X&b'JvRޠERќB}YqN\66D*͂TH#:Q<::bF0?1?B[qY\=u̐FF8t:yTi䭭/ Å`ڐPCqdKd-׏zW$S1 ƒ ag/8|%=@o=h[wJ:1]Z4kɢsvPwsK,Tjޣz&7'γrC8p&98FֈA(#L O1Q8.\M ]zR%LW;$~5k UM,*VlH":66Bޮ:QkUY_%A}CҞ=Cb$y3 :$Ӱ`+O_w>A;z[5?I~0di"Ҩp<${QS~˾*]ԜxPv__U>!K0 4DX|u[50mWi21q]\gs#iwZȅC"8ljnѝe&UW`g/ZC[71),-}OY4\݅=p0OoȨ9KpXt:F3ً~-QYeҪZCz}ZeJȞy##P;gֲz{_8r韷0ټ)V5| swP[tOMGYE!`i$#қ3j Ul{SM0-&*?'?6u4jS \}BV7ur^J!]cC̍ZrjT<~ *ڰtM!ֻDP Ko[Ws~,t13Tfݶbm@@Tu5ќaCGzT<6vMQEe6| >oR _]2\BgrҠ@LB|uUie @S.g +F>BG9w}q ]~{Ib۳3@%_+i{L&(zˤ>G_K,MX iɩw{By~ RXZľiL!~6-Z/Nny`}) ;$C)mH򫾺W4 ȫ穀/q̘ |=}SgQ6.vZٛ;g ]"hR?J5>"f^yݶbtHp|;I'0GjA C<i.W;jifSXo EG?NR$0w29%≚,oIvS@&bSؠOq-S*3E"QEE}jdʂv(7SX;CM~Z4}}Lwp_Sd켷'\⤯^oأ3ouh)^ZF܋D*%P)TjRh5F,yW%7!ū' ft`E=s闺[834@h NBi\iH|lAdEwjz̉5o(ߕ}=zM9ŕ"ȂbBnN>MxyɭZ/N9AG  ZWn.~^:_n/F >2:S7v3g+vѻd||H=C2ȟN b]8Q%9kp.퐴t #eؑ2 otJgKu qAbxog Sx`4 "nZנY,(C~Jd5':+ jh?8ldowYχ] m W_G5/lcעIOhX^ُ;ƗwD CtbԤ0jt2y꺪.jGu_$71jXC$y|nhTWrOqLjF$#Ye.?.PC@TUfg-{oeA_:grGJ.nGAC!տwˆj,lv@VqYsr_C{5Gf~)2t&E*Xf~ꔾ'S5i9k*.y:uw p] e]QZjfښ,<Σ**<)m#0$ jd@BqnZbVm#73G.~jbe*9Ǘ61&Vx IDATzUQcu*3c"`+#Lkijm~6a4gYE=:r>;VJ?D8{9rjDa  4G JIS&tg}%nz)EbsAB6ˆz~fN{9;mxbij? h -ez Wib}%)ԅfLJ$?PIqM<&fQ ~:oOTx̒ndM' G|n[1w|A퐄yzCToۮ(Ou+tH]xüpib;5fWD0[Sn<"V,r.ݼB%c)Tz<쉋MN[2}hݼ4;vC1g_xӫ?x~}5%OJW[_${{z?6=lS 5uU!F;.Wg) BȂҼFB'!iMYM 2;i䪇gz %co9PC6b N&j5e|wMi럲aL:cg*6I/sπ{  ɿ}jZ>2ښa&9:SSem͑d?m+,2w ~ٵϽW=T[1 +ع{ծv>-_܍vj5M\]\}@eu\)*it@ʸ(H`ClS4Y-|%4tv5܁p |A*+>Ջ`uhjzUjv>vN:j6 <B?ٚk>Tag+'yh aYS%QT^UuRE "ܬGZ]N? En-0\4ZZQ(528C!*o"z&iYЦIVo֌$ÑigW':$֬.àEM{8DćD.!Ve ~KuԥtBAX\ԩQ,r^\ k9!,0vQ*1`NVecø_5 L@k&O,3ݸ3˕VD}ŴnSd}CS;rr o8fD/JJ?{9Ul:.R/l$w܂i/XܵiWhs%^nwUZO~9}cQS^q~{A_zgM{CV.y`9;¹pӍr!7}1с+)H~zRCXبTi2j׻|NK 3%O`d@&UЕEV/ZJ8A;Wuz7+G Vp¸{y{2T8|gʼ 2 0l1 _7jYjWL/ >P[\mЀA@Vٿ_aq!04~h5մDh\.[\ZHF,%ёіF2Վ޼usd@6r'[DoJM7R#?u)p " FM$r۱GtLeg(4ɟbb'wP̜Bd ^=:QzޚTu 8tgdUE,|aaD;D%uEq3Y.UқQ]ǺѩtjɠQb[Zݛ('* Lc*PgeM׮|qT'U"u~q!NR\Ls%=#_~f.;ínz;w[ZLqVB_ ndZ E׿ڳ]%QZ@v6}FM^u{tN8ݻe vH26m CɬkNO03O]ֈ H?LU; {ζ"8QEp`АCCV }{wqܻqaɏrIWz<-oW|Gsf[ ;7!zo=^oNR{ScmM~'z+8W:r'{jxʿL~EC#FL{v 7ץ_;h ~^;Аkt_կ13h*C;Le޹Ai0jk# dJ)yԔ WpeS B]O3T=+1 mWnK]Qcܜ|fnVժ:s~hOф([T:1((J;ʋ5Fv簦CC,m- ư󌪤z)cc0;X){sJ^m"VW8Ҕx;}PY!ƯjF9Fv1ɢ%a+,лX:wTl_5:#szx1'g7)>&fOt5S8N\H)LNc1h\͢sL!l&݁nZuB~VaHbD%y -/c5=[/:Z[îIKGz1Ǥϥ)~Un-ѻ ;$SnK'MXt`!P*: GgǠ0S`[}?I++?@+@mI*MaWgaM!} ŗ֊̓`oiTQ]޶/ڣN_xÞMfBUI9qxe@7s;H НSafRjTC/ר3jgK4WA%qu^?x+k\[•[ܼerI <~lsE ~BbY횳om! ~gU ~|Pd5]O(f^?kӏ6[! v}Ob6\ꕷ 2qM]Ϳ%N·~,y5߀5{N|V]JAΦ7XJJy8@`s?7m-'}h!$HNQ'Kil$Ӿ\ ~t[&C?tϷI5Fu pyu4ݿtgKL<JAQjy'q'|&(D8vP 5R#o!%{hh(NT%ن U.JAyفL> aMr]ap[- ar1 h.Ʊn -tt`Ѵ)%M&*^<}=sF ᳐gtHçeIG=LMaY,M'.X A_{UozmX<م=z!F 98MN{ jm-*?ܼՊ[TH}%Ys:$Zmru`匃].Y ?w} MD+W됴:N]IX:o)tnJW"1.Ol&(.}*˲wffteu!*kJ/$mg%w6E hl×L?|ihHF es1lc:wPӂJ+ߦ.i'Ď;!EO}JF=,{ 974{GVl"Hiʑk^l@8*5?}5l G⫣%y:+(Ϡזm͌'~lsi+ݛ]G ,ozqu!0uç`:-W : =<'Jqܔs][exґ$Ŋ4NZWըNBn翴r=m@wM黟 FC]IŅ6oJq˻F־]]W,&ǵW˪sh8fbhpFq/rCotBd U*ofR>?׹\IԻWoW9ioԾ\'_J v٢e\.N{ؘ#G]a8q>nRl^0±ps\U_I9.>M)mge*;0r";ܐQA5,Fpap&;wF^3^ ST3*v^zU胂ݤyHj][#}8ZW@o|[:`fczUZbIJ}.ˣQh(uTrk[iU^o<=R4w )C@!\4k΃3 fM8(]y^IuZyz*FwR}5# vS^b=ora ykWTJ~4$Wŧ0O@\W-4=%O?8>|R-;xRx)Ou{0x:$KuncMޫZu!i+%ÙFw30 U'HiW Ji\Ҩ{:SpB3 D PgZWp㏓[o9u(J*jٞpXR ȝ[S ݿs74ɝqrVTz_׫25vAvk'+A4;_ybk_ɿ$|u^kefo?l&Qꇿ^|rÖ|Ygr#\T~8'?/7MYSW|9Fn3gM^:W'&6hFf6J\qsBgBd|w>V(Y96<oQ< S*Upеk;~ڱɥN'Ep9aW"ۗ}5$ox塽z |AerYqi']}@MԦWRN?2,ЅϮ*K\`\~ ^dS)4 _L] pB[}W9!ktri(,g}5n,$î7Z堶w#c Luq$cS_yR>ɟ焊]L8Π5: Ҩ:⋛jr-K],P1$juvN+r =K3(aY?7:^3hY`XTֈq;Y"Wn IDAT^I.9&SWSa F}T)Lqkoz8^4@B5r|[2N#m+NI}s| KC||9yC:z}uߣa~.|NJ8 !Tr C|W*0Z a On?Nn bO=9)4{H`2%<@Ti֐mo|,SXCuوP{O̫܇EvX%RX.ijnA1X?0Ur"&PZȴq4CH7 't|/C8%F^vB(QySA7 :"**TYQQCd}NMD~qUҭ|B/p䬒*_'Μ8myE^q7_{5 -{-J~3.e(vYc?ww5"ya[x E3m,Im?@,mZ6ԗg"P$fPtH:.2)EMZlja?}WԞa$(bai)(X4Qlh k(B |2E@!x|,-NʠV 9,# pc}2_[h Tj'BwBZ¦.;Mrt ͤ@\_7pfz.Gs +VljLed.ڏ}|BV&|X(gdD ?6;yHq0<-)CZFCb=Q|1~^k͙$j!a*M"2鷡_˘'7E5;1gF43stoLӷA+RE-=Dq)=CB?[Q.j=F tBKex f/{Ħ^`ShCHWMbL vQ[qh2)6X47*oHq a#bLy-3eJf:)򧩸YLiZ(fAMj;-*2m7n4vyNVrjƝ2R'oIQ0CpsJJ&%#ffAQ@o?/ဨ?o.rzibی°h:a9Ua:ަ'oH#MwY%h{l  &FM`QS琓R][68 Zt`FM.""jq)#dYp!Qi/AA(5].wWFTj%b(COMFrZ (()@FMFf?5"3&:0jS⩏‚)U'y+ ?$dqb&N&25vFM)9p*@@Wur 9@-F*0h3`{N-+*+O+>bi/.q,:I7`&ը2qN:r &>OQ.-L9T ?EbP:C shw%ƫRBӨ}Y\A'2.x3ߐh;kDUq=mi_:5<tՔi9%b*0}ܜB=c"]]ܼSP\QW+V:FGFtz+Ro)<2&Bzc^Cx䁣FC랔'dP`EnFzBiWiJAA:H "  t) H/ % .m.B. 5쾝ݙyo}X m`y=; B4ڧ<WorB xoGKVfg[y:nG߰~Y}t2xUTʈD[vyշUG7÷2UuL40sK֣a?:y4u7 C58ň>,tvߎ=fV7@+D((-aL L.FM ݢ)>{ 0+)RS\!":R2&>_0m45jz0ĦC^`o#E.(΅4kG7/',niC{|Pʀl.H~pmAh ר _ }N_okQԄkэp 6~jx!&Ji@ ~_ѰGwxER jŨ -Q4Ђ|#aXq2IRy{ E{ 1 RN~z~[ M0i 5x&GF[j/($A,fざo[gOFBF="?z%P#fok9WG<,ʛN$>Y!VMLR'mAkhaSw xPis $B3YSP>BE ;Hfu6.Mz6gewP KM|grRt׭ZY0LLWbN <}@_FM->c/qi3WO,(@cN]#+F: K >qX:4,!Sz' 8Ɉ3tX✂c<sqE]5k]dH3šQ B*fp#/g?FF[;,F-Ig%>?׏63ϫg1X0ԪrQiFNڡ+ƥ}\>Up%,V;WԈ  TK 7}4WSKV重8|G#{d>K.g9qWNd_ooXpO|@3c'xLSTWgfg}āZVఞQ=SS^>[ }N|0f~4mҜŘEtfц6ɐelkmb &~ %'`ظD5a2YZ6=;eF7}ǟNlވ bj95Re,n3/;t mųȅ QA74USҕ#^E d*h7(`n ++#@x35@ Tͤ FT]N v/ZQA DYSk.|\}~o5fb3LXd0' Ҩ J?R`Tܷ[ۖ;ٛ9/\<}lKPs:g ت҆`d*_>]_o6/**?r![̪ٿ 6-CFQMq-8f*SH^7{G-1hV>V>AVQ|8ڶ!FrXtvҡ[ψ>09rut70,t eO k_kOB6BPYTZ詺:svדZzS ~x/wxtOc/K21W`[7.[5x&Ư9h:  dب wT +CqL]~wcoa-}u,2T)z,yiV栛k.ݧLYw5\f4CO+zR)tێld¨2BDERgK,vXǰFwuJ2箺,R-!UWa $$ ɆpyVh $6}3w |y:TQI2YLNq5JUAV$$=i6m:@ldQ(s odOHd C`C,KV+XFPyA4v6f#G:R I#jǸ~CBHB9̢C-U򅺫2]`SZP:iJei \iK{^(ծQuni3h-4:aë9ژ MR.U [:OJ7(j ~S;i/O5io\5Ӥ+jj=JDol[ٰ2v2 4|'Z??Ve< 9]"&DT)͖+Ճb/|9vX}?{}y|tswuյEb־܊gL֘RB/n3'D34 pn]3 IDAT=Su?J&WZ,8lJ>xH&AUjUB&jІbr u& |h@RY 'ȬI3wդDvDN}JذeNo%ʂ'i$lݴlȅģPNRZN@dʹדn$b)u[,ҷvxǢaBh#K<uݧI-!"]^9 0d q̓9e;egƟkCIgsjpaYegg(,zp ݵsJRV#'(-I *,!0MhDkqTy&Lon5D2qL*ZSF?rioG0__H NwT4v4G;frI(6sn5\]u;z~4`w#W#cZdMR eB3:}XeոֵwUJ].Ց'zt`] K*qڰh)ڰcӝ,f|؏ɨ{!4)NG?xkI,Fs3Pz٣/*0T{J^mk=IK-*<ztXԕxnX2^&df D2wB&ŕ,s[r/1j*(.uR]1^dY{g1$D?W{d|n BU{;$D3T {8ӧyy!vE vӅlD7hunc2,I/HFD\`X\0;!B^ ӷ[361C@i!?Pkrպd ϋ4(kt[0vLQω`9YvH窿=[,[ڋšlm͆C[]ӗkx E*DZĐ[jREwW龵~ 񧺆0$xN6D jAٳƒ00#v £n*RC?Q g{)/as8ѨI*ۂݛD?ُۧpt 4r R~/ʎ>,ܢ)0OGOxdfma?ijr5&M%eR[;"{F%Y9{-Ѣ\T<+Œo/VVlb;rѢѳ|}Zut{+h ӷCwfa;k8ebVe8}oǞ$vRJ爫J7KDՇ Ҵݰ2||XI} *ט9 :2 _2[@b2O}Y>o<:ܿ hP_:(SlTX1HoJ*?7b38{ V[x1F&8wu&ƝI+ 1^a{M&ݻ}ypӗCc0jN)zv !Q\}1n37x߻Мxl 6!$l©Mɬ1׈noq&L:E.tsq۱qKsd&nSV^?o2Z:og33NUwjG^*s'wKJZt#Wy\ ɀ\vn2+_kh {bQYlb㥟1 &!ʬE=lcmW%7ll.=Jq珍RV{[ /}45(S"x"x "Y$#>Do$Dd27`"+/?6 )/9hde$RZ^2qѢɯ͊0ݨˡs~n<|^EVJ"fJ#79_?n{TQy)~Xq)>"$Ci/Fb+m. VKYt#ӦJX9b?7DSi{(8Gv:xM߰=z N#̑DwV3aW A>D52myzy*F&uPaT&i4궶_M%pzq\ICv*Nkdj{y *;aFayt,Æ#-) l-_.hR)L(JƞF^ӇC/L |&W݁QmݍS]:*/x$h3 WgB:'vB |ݸ:L.Mm]luw~;zLr1汉QTغH2Q!>`SQ{s^f~]t2Щ=0giygf /FǏedg5oBK>džaxцCJ[aBN]{|iN`PeBRRv =39hh Q> 0i#Ջ4ћCQ&ꗚ~}I-Noh]P 2Hip(-QkB=oIC"* RX;>YeAx~!i:T:H4&|h}m՗Kuh!R rD۞)#)*&4V_ Ml"Xu$T5=/$J:YP 7rFknf`Œ2>I$ad03'>9FM@󑔖hbt.~e>KLyA~IdBQj+$̲;: E \$7SBFW|+β2 eA%l6vYv$}mzz^b]R`7E,8TBN_m %ݰmCP@_[k7^jcW*՟מ"RW"4m71 qQ4*DW"SFLy[m\^1/|3˰ٸfm,+}V|_4g1nXV/i~?^t RdӉbP rĶY?GdY_&XʰOC 4Zxy tUfdTU:;Bif\ <VGa:VפC۾EJiyT(me-~lU7>Yg:P8Qq :+cLLҥ"6!u.ςƟ~/E0FLI4aJG_ QfԎsW!:Wp8L%,ȅgt8X z<"ݰJ:u* Ԉ8]vO9'k1**=b!Y $*T!ӪUDukD!lZa=8DMTt Qb4 FcR>΁duE[54Oˬ&IpYFto;!KdיΒ3vP ~9T~֓bTrW{J: 5 v;إ;YQ Z jp' ouvRlB&ѨɤTBt yUY;6VdR>zi:O Pr)]bEw%{@/ 2L !v-(_;Tjn>9ܯ4)JJfl@.+H(VjG,>Telv4d9ݱ]tH"8p_gcɂ25=zIa|uRK ira[PmfMrPf^կx,G;&I+=2E+4'NJKqETQSV<+,G A^v#,"b0$#lhE8ܪz|FZD0Qvf5͇VWcҨ4)x8%OMHl*t!:081 7 w:v/o=%lj.9eO]mA MDTə l4M]=u}? TsBFM( *e'uǖx3%t2u:SM,8v.6{"Qm%߻ҫS?<̤3]Q.>/јVe$dL Lɳ0cĔ6xV?3Ѫ_RKXGt&i?Vd,F2K_iBOM_o4> J.crm53⯿nowcO3Fhӄ/ٌm1dž-p8Ǹcۇ4#Cxj5k6ڑ*unAȿ]468RL"_L!a?LP N4jr*aBȆ98{CpاSı߼{s#efO_8)dWj/xYu9!6H,^&' cq#JsT!4*=2:k b1C[Z`ܩ朏Kϵcl|& +tҥBѾ$SXv^Љ!$뢩0,1t!e׵U{5eHo+Ǹ1v4&i*wMdB!u6)9Uۂ2/zx.džg6GkHY)] 5*D!9qGs U^$fudQF1.mW*Oyೌ@l yxy>Oe3Wk_; ;AnIeiμnƨ&bYTbu G`hgG9eOR4jPt|?{[KN22wA 7i Aied hBqӷ$[Cҹ"Qʆ$t[jnwv°q}g՜jMCjoitϮ=~rVG!v ղ _O@Mwͺ7F P>FHF3-fMs.aԴr(+W`6S FM1ؙiyoj jYAqkk˪ixҸKG'z uTsnl&8|$%wb#\?~r7͇7Zy~ ESӱe2ji}'~0\ߵv6rךd [O\T\\exA>A')w)!'0K+J>vm @&g6xjR޸(9 2yOMjmw;c9y9 +C3%\~eH`HTD (ZO5U^Հ v?!8Spu4ʷ7ISku$1_@3HAh K Vڎvϱjҫ0! KʹRRyKmp\lz$' tvFEBB 0_O\f/ ,UB8;u1}%rVS aGіC'/'tN!4& ,hʶlMc᎛PD0'Z9 (-!A矸bI*5Pv?jkDKj?']DоA S!Ktqd[qf :$M*Χ /"dhujċM ] 5R7#-)tC|#&#^)Kv"Q5ԟa//Iep]3`eSxSeR4 hj?_(ʡZ+:j ָј1c^)JZ~}az$|+IzǴG]]Oq@T;6VEkw|jx qy*LR\Vֆ0vyQ^ IDATJ:tș'Ŏ(*afOG wm}ߪ`I?qdrV}?YP#NPCh`cpҨM*'zBTo5 <; J{|/E%MZ9~rh @x *,*>dkmL0eag/36i׸k+k[kKB tvԯdovbx^ܽ79)=gV7yݿk!hܨ)2,sHH%lX/%s9h{B9yG0_ynlkMXd 1 8.ZTR!|A|ɡX ò7*o`EMV% !SӘBII0}yf-{v>ǚL"?ˉ=>2w8+ aen2NhJI)kP6v;dOb4?r*oҹK[B~-RP3Q*P}u+~J%DC ^%Yt=˯sU!֢[o[hD۞Fn;>jhe}uT~vGbq+FM@ QqjRl|W!S&Nj}#&u i+[ 6Ec^ll~R ڈm꽘(IaQE%tzoⰩa"x㤬݂IKјownm5$Aça߰g J2ZJ1XW8ZI%U%ԕ9 ST]7 O]@qNO]s#}obޣ2] 7|kt5i栾BCx\Ux>^> "&+ }M*ILgRZl0j6d$=}t'3CCѢpN:`(ߋOLٰ24_=o\|7)%%s t 6gUrum)2=yiӀサ@#Y hK 1:*IY.Hy5w1SDk\Bc'NaДn{u,p`VhU*\ʕ%J 2jB'<90T,UiTʄApH 8X o kǂiE'a3_uѦk D +xX8>P{O7,_ ji!!\䁁LT}[ u.V!itC.QˮTf+Ad;s5=Ȍ"[{QF֬4' hVp]|* 5 l܍$eFW"h4 ٰk *ˮcEWoi 6RpܝG4_8lGrT5ˠr%JRP(jZUWufųY8QmxN`;&jmʡmQC'CT*V\[gO&~^.ܒL?L_Ml`U S&^A1xꡎP.. Z}w_&ס{gxgFSZ@}عWt8K/6p斧SX_6lCh5{G &2_9YzԡzxTK-ۄQ ֖^n^ob# \CяL&iuĂJ ;VbG+Š"#}!`7k y2T?eVUY8 :NJ)scU9]/OEad:5J usCj0Ih/P]VW)4ŤSɑtt+fkEZ] }8FRbD7ٵ d~(f϶%6X4(vb1|*Y]k,dkb*Jlr%4Џfm:ȮƇFs5Ji[rٖ[îXj\mŋ&dî$l$ vtP-T eͼ]!](hp bԤ"5&ܬLX:vдܔ\BRrbtsÉ{,&{ɭШIRH< tuLJF2٘3.\sᐝ7)_9A[fQ6_S$j7_!x=u4/ {F)d[n'3'ӧ{ j <jZ-fV+JB.ˤRRTV^<9/Fh4_/ߔA֌|Kno?o>quǯW+iڰ24SB'{-XYXytOn2aX{QF%] ,',j7 u BWyk6*d2Ųt8Załn]ԣƢPdN$ ^t*ך?Sux4a8J7J%7URae46tPHB\^8USDugSOKFfPy2_8eP2CY0R3 1Gvm6|ՁN6t?n`OhoEL㰪ʤS9L:Il&eY #d &o'dE/hD PZ_ :$82ĘG=4f@q?/w3Ktskۂ CR'L]7G :$C<^ўPF7& jn F*xo"YTDȖm d Y)M'3d:LH u5JI!T8L[G[C\)vq5Š'̿Wc.DILCzL9`zBJ"?"F(Έ+IУ?9ڹ9غ[̼ra~h&Ȼ}bZ<1s cj,2]ա;XZ>~xXzt44Z˵eF{|։L&yV%wRӚ.T^ͳ& X=x$߿jxQ)geZ(.JVCN3CqQ=AD\9RT[ntEŻ294ⓑ 7Xۄ=LxǗ:y=JA6lP\:q|j|^ ջ{`˺I9eh{~5jRՋ׭ʺsx/߸ :%N.*5BBdLzht>J<(YR_e=B>$)1ڼvܝեťqQƟ[=dh7 +RkUXlyZh&Ao*d"R+VcͨRoud-bI*6QYK" b'0X4 bqYZ{1ѤQ ,y #{5_7}v,,|,w峆qL7ODU_zLu)Q^Cl=L] Jssw^K4ۏL:uH^j^itHo4SxCQ!4QgKY%"dzB]~0M?w,׿{rKƠ.Ji;.ݬ$8q^k%mAW:S?ƻ wrxn}b[JtIY} 7322}QwGSվ3c\38P>79foաSWے+M *6O`=F>bFj`acRQɡ%XInŋ}BZp<V.ZcJ$q!):˽]x (rͳVĔus܂ot @aʄ)l9uAɤl<9s/_KKr}<Cx1ޫkM]DTH\ڸ4*Xy@B[gN4J#։Teӯ?][0LT[Cj/xeÍFFG&#L~+4%](蹾hc~p*3]LB`e'L.6`J dҌR!r*K-z*a}C :fȡѳ'|{_ꚤ$%Ul**q@ubw}q.G#J*ֵl}1i~x8hcJNKQ 8YN-hPz“kVCr:AL2z綀!4K}$Q( V)Ո뎖5a̫ uď9RY=Wկ>r*n&,<#;F*FD*73ԮmTdPѕzӕҲփh zE~J!cfÛmIX9 qygב/+j=1L /9opҿ렩#fa+͏~尹9bv\鼢C}M0:u͟=#~O$^37J[>萡GǾ.JEWyMu]IK@q\:T6yErkgkE<9d=WX; @<4`*܉SInZ2Q9pts "4tꤕ9(@:P/NOPXGO)欭}€na5V;cztVC`E̼, ׅAlAHZ8Bsj=N)VUrZ`jtkIW4SI#WI$pzr"ki^G^[!\i37+ĘfV !^N6b|2nTRT!M*TM$lRoDZUl쏆u HLyv)Sc,djehZC'p|/>d <&&iI 9hNE a]{N(%I/!5ņj[`o oSC#lj+Zδ+ԥ)H^IcfrVj :H9sJHRmȄox;%\ _{`TSG)ҳfMT9LABl|0 0jZ/"vNU}ge7 !MzyB:ٗF5(wN{-Fc,[Hx٧i5X k/+@pgk߄6qbo9w Odڪq'rD(0N9 :ps|cΔ" )Nr!cfC0nm&Gc9 6wxO(U&@b/7bڥ^={SES;Wv2L|?~yJB2oWsw$ڻ/wOf:"' _Z,Nyr؉\?[O{:w}SG1Ot:6}6{w7bBܩ+alq . {:{kw/>|ӚMvE%Ex*K-Rܱ1ho]Q&8Tg 9t9^r'όbk'hX6Lϝ67}%Olzz>_[\?}Ā5^0bXârRU $<).Uzj4kGPT2=h* weO>9;ֳ =CkD:QeriU\ r(rheʬ܋Z+7yBmB碛s%08r nQ >L"=7*2$]x"6C}rE\L~4:,.?Sefo~#_P&{N56wZ]s}'vPUTmV} 8c:SRrJO\D^\. AܷK 8}Qaqc% :sOrT~owܤ_HSJz Z'}jz/HBۑ+j Qf}ؗ/' ~惄]P#H.Ԡ/Z3^CaOf;Kg[ ξk)= %%YbJR&K!谂O%{9:-GOW$)niV+!q<r`RD(=0[9U@y0oy"\[.s+t'jGf՝r},?O(Ub|h.I |>Sϼ${Jx1wMT}swٳMӽ---@QY ( N%|*"PQ%PF)@){y]iIr˻ɯɽ 5gd+5G6V IDAT>hNT(ZRq&A2E՞#4G^;P\,0m[[K!='iןN_}02{%m"b"_uL~oEM1E(^`LOnLQOT*-,0* wo̓Pp`ۢkN4*S:w/]-~:'}^B1I{cT*_Jgߤܺwv w=t(MVU.taq.Ky}xlJ2j_0jU^זLx^|s5eya/e޿Y&k .r`k*5^ ƊCuZ /88 ]Y\Z)yjzy[etfv/(ը̛_RQwBϢ"OLm8 C=u*՚u󫏾Z`yIY y; &õLe/?\O: W7ޮZ 9G$2PD:v[Lr\,4ݭ[o?ioʴQ7|a᪅*øIGF::{MDTob?q}ɽ^fݼd2)9b:Ö'l $'m:i\6"^fUb,W%S~oW]D.. ^0B2 M6fB@Q~ __+W[UfWQ;?S&ÿDiCvɍc3"K?cm4חP>e~!V+Etpm?ߟY?%B͖B6$\\eF{R]lyt{ODmAȈ5הi\O :ʗ ,%}\)TPqTdYҫJ-e}]Ʌה?R~Зcy˕UyƒػY V.Wȏ++gp 2sKn˔ש׭似_'0 VCSD帇iye'.Ŀ6ni~J>wA^NB| : `2Ma!/۹w& .1UgEq}nugLʙ8snbNĒ{ANr /ZPRu?hu PO<&:3x#WK>j/fՃ$eYńlwogn׭bAzչJ|ȑ|-w<-_nL,j{3\.mηf4H\㼩r&kJt<</UU^舍 f3"~1h<-Z c_c:S;sRV:whҽ"'&_5NQ=J!G: eѐQYG"mNRZ79APR1MTjyJV6%Ϋ_\~@ތ|qˤ礮^rF^GI"%˙[|BѬGq V2?ݾ^>dCv´̴Y ~޾Vˋ%[x [TkTR2+FM>[)[2|pV-^I)4* q۫b#ޯ^0>DoWG>IёmM^DMnej3:8zwSz~bV*YV"h#BHPGDJe6fG|'0 Bmtev54 Nv;d۷N*ky9k?AGBUΞ4 XE_qu(2D4u\.WIp!ӟVX ^m KBDОtrm>Y庽FN^aOix9b#_Qݖ෯[m]؎j\ZP u"NJ>z)3wImt{NW: w :}դ cvTGQgOtb5P9 _3Ź)J]&:YB~uȑ37 }v?[ؾq  *0^'*S;}+^sV*AOo[ gfV'tVbp&ʶk lhujaD%9浺}@/oW5q99dd䁿k u0ʡ<# ibK/]./׈L i6]诖jwg=7Y݋66o.̏+e.|EQmhRt".,%cT䗗T\%5{GS *ucѐ}8Nj*G{3j7h@sBRg sL-zR=λK8&j/cia4ay֞W30mSZ͙X-f)isyVP&V\CY./z%3n|?p|'݃{{t,]Ʊk%nڵp92,:Ҩ Y~q[ٵN1_`Pa~l*nXk.C&\j('ڙ.yr>w9ŋd%yiw޾&7}c˽"X>!,V<}z7u W{5ϘfeYCRJZUyƙ+Rw(ޣstљҪ2qyqzb k֬16ЈV y\BH%%%/ݓbJqeaqo% {o∰G#ɢ3j"9%y{qms˾gNptT&K*oݹߦeY-l`Fa(R$I?m?sKɉIgbJ~x=ˏۗ kt3ZAVC=?g?X?0a ai”Đ0R6dr06%7dEW:g~Ji څlu/kiyj~'ۮbX]VJeu1W%,/lx -=lDtaQP\4;Ɛʕy4eW>@n(ӟ }iyU:]B')E}Zq"N&{s`o[GK _祁\0%}Ijr+'jo{4מiu E*,olzoꙖk0M~jEऄ g ɾ 5X~bLNM6a,L.:k~Qh"dYj&dR1eU:&Wg%ܽSN 0|_tA(?v@w(vB]OW\ .!vwr?.W(}0=qH>">AMoJ٫u-bAQ=!G1| J(@qF_azOۗ}?~.zp\<ݹlAmQf?((?{4*EQc* ; PRTJtw*pW&$@;yP;Q+'(Jq4>Me\LWOPzge_4y5"^- Kq^eT=.9єB}4i!;#?YrKNCBn[N1AvyNm34Z[\VuNZQtXWC;@?4szuz`@ɓń!qZFs"g8R!MIf B.H/CN؞\K˫3EBSWeB^3hjg2J=ֵ]*6ēW~]O0Lg޿+R.'/#Tgʐl.9md +p 6s1 9+~KtU7N7A.5ɦF]}g⊿w|xa"Wm A1jo%.4Uܶ/ %ʼ;*t;wfEDJ$##˕,Kr}nO:D}M]r?$R{pCGcaWXC M&qh._ʪ/#S[Ah6nneˈ&F;ZFS,&Ǿ8*?A+Iux 4Qӳ#vxL2'N]pE'KAPLwX'TU~2W< pB"hN혞(QJ;3u &1iv*R!Y˫НMQŗ?mvuS%*d܊x''k^H/.XӝF3,v9M}P!՟⌍kXavR~.E;Ґ YV470>i>Ki|[[ W:Fըu*901ɍc!CH}wЩ̉,"ZF$ y"ᙛi "Iô7.:x"NjQp>ȹ^Rۼ hIj}M?d;VTvVnݧLA>H7M-(OCFܨH! Tk"4Ԝ/}x G%<̋lҠ.,&ͤSW#wtXbqT!sUX"WO5Ǎ}R@L,x@5̗U IDAT^8ӿA lsOךts=F,9eިV{+'.weeoMsiUOukau Tós4Üs2e@SrÄȽ)o8ԤiDzߎk'~3D4J_=q,M5C̿+9Q%rY(IܯNUw A14z<8]7s<i=4"R7LiGm\pBr{/Y'-~gߨ&9X#TJL~.L}0u>jx*UT9pEm57DLeU.>,؉ӵ^m*N^Ehgi)ZEmOTnoVI4O%a#eF7 FBtLv@wjJ h')(.h_;\P.t/Y b2<܋e?~!DZa2\\_Zlh:kK:OV/ޟQk3S2 r*J+e~d hoĤ]43{}ʾwV.b2w~Qÿ|I Z5|ޙ޴3sV}v>$R bҙ8(g]2mfT@FծM&]QojjafbXէ@o7MxqY{EK`asbU_kͽGMY~YqaYCzr qv~ځۏ]m|iL^F+T \|?܂uko>b *3L`:3%%_4EyL9W7#븻Wh3_G6:Gz)ƕ!_hN~=$OBC(/{SXX'KKNZE/ q'ˋ%]zX=RfjzD#X꤮!(OV c{5Ez\q R Tϝ"+NԨ{UyEEУ@$Ϻ; n*;&A(D>xYp;b!AA gABF% JdcxW}Ta EzGz8V\)li|2c+E**^>:%4"HRe m<91YW3/J4zulɛsK}]bF5'?Y kIs& XpXVMO%.Or񫯍o9NUit!>AM4i/z{B&9mLF_|GqvS{O%*lHߵu4=Y67V2N_/|Ѯvm[.ΔE%yN@?֟fb&ۓ> 5"(-ޛ+Tӽ1~ #eLwkܝk(W'{2`YVuo|,Nʰȇ%:"k˿cUsN3ʢ"W;?hZ Va5+(0!WNx{,)dbw41TQX<&-!fdr=u55"yY~ペ C55WrԮ۠kwwࢿ/M7J4Es7=:L7|x~0W۶opCytiJZ6z@tO׬ՍbW/.tL!K9x\`tqo/}mڇEX.VqJ -C2_JoO2<5u Fi%$RMy ˧oLI%xbs)t; ܰ“<8Ib,}ʊ;:Y)Ҽ]u[mZGY`uY%?Y6MIQ<0MB\>1Lx;.*Nů+Nŧb[AH5w F}si@\-UfI?CpebXSb?vʓh+ϖGN܎1ۿA4*U8iIi)nI+1vvt9*P&QCDT𽩚R 偺3ֶ ~4nà?3t^3NAI:MSd1lqOK5Dok*L_24djP{/ڐPwMցYj T% \o(1%?XVaz]1I]qTh@YW*ε Oj빼S'55"I;_,k>Fg?4HA 's9FbzBjp*Y^dvڷӵČ Y~cR x7eJ ߜJ*SluR=:d'瑛wߩ/PC89LĖ=gɧzs7p_R5Kfts8^M̔*ԡ~.' MAM(Fp ok)ڭ9{NFfAɋc0%pΛ<@8/bScTݹ~/;MJtyطحA=#AM4vc4{yDaqj/ɠ3gO|="H(&bLlV W,F51,˜[Txz:ʥbiρPǴHPas18"3hJIٿq 2#LrtOKфaԏDgz4/ͷKPS?[d8NR,0lXū$tSKʋ7cL3 }( 9| 3~PiqA#z 3L?unlNTV~8C hRn`z+c of0ܟ}^D1,ņnМBCSYU%W|?"rᑫ_= $7cOR+erș{U^zYR1᣾~S@TI#H>|ٱq^ʌ^1=9%V6F12e: bVblG<8S'$6EH)՟ӏbay^"nTauyrt]d 𚀫 n YX ]#jOL$V[̟1mJAY1Lqbpp\iZO9n# :KOV#( J똺,1=E~pk$eE;Z2x> AKxoWNeQ$! |yw8zTNrnBmQR&~J&Usre=ٮ+HTSΥk,8請#ԁX23XDEPh,0{%trҲ$@1ڟZ$x5Y(i(F1D&x r-Y\ i ch p 9IK%De'p þr]Q&_UxWpnSg#J h<:VxJԇ(X#|֧ITR0C`@$- y2չCpjZGPTr:C8g``>};e OVJt6{B c^td+ Nݺ٣s)pqIXLFh0Hz:O}xy`G7ajP}QB4#gDMcLu"ϕ AM[h-3dzvxYp?#W}\EB}QT:9ѡGAPƽ7cudx*rt6iprp2!' ΕC=;1-56:g0aҤ {0 c}A !^R\~R=ͯ_waasj[g0@Ev~oGuvVϨ~[t\~46Do!) v0m=\|\Gz.35VMeE%dmqur3O2rs>h*,y/S hWP jۿD4D 7co[v ʟk/)OZb۰kB:/lܪUƑt볢rI壗lN4mi7 =´.0*ú,- ̨|Uħl@sX&SQw'nԔѦQ<\?^#23,2ɁMID'l,z\,0Jx(ן~=i5r#فԦkM*Ш J2@^k目TcBݙiف4&U{y"rUľ80eӤ̢(pūD}Ku׋~D|mc,bFdCi,a!r'ńlCTj/$ y ѻJE[I4"U E;}_A iԇV(^aD޿ղJإDrMIC} ;%nreR#wxYs=D{2_I}ޟ~݇.͛6 ðoUHT ;,h&-m,gt-7|k}n^, S{d͜GÈ; ?ۖ0g͖1R{w6[ZQtÂW^sQs<Resl\< _2{shPϿzv>:s 99*F~~ߕ&VuM?j6>c<?1<]`㤑*ŅgXcpp̼'$! ݜ=foMKZV\^\c舘m2meɐ),~΅ ILs;;v IφvB_D>5TQM.LH@xWL> Ҫc[I4?n%ěqHE3ޝ1g/#R"oۏG/l[:/1)յ+>3D4dMoZK譅޾m}m/ rO𴤴dEGO[_b/rfm/CM' Odz4o2MKkuq~]}./RhZm-$MO}ٸΒtqK_7|lnYL:ӊVMH2M==b ݭ+)*L41N^FCHeR;@kJV^JsDyl']7sݵ;Z?QG/Fwyk5Va;bm yӽw~ T sT 4t8j Sw@,F@"tE/@C+i~񓦞m:6 s˥^a&Ypjac\ 4HZARʩՇh LT詋gʔ n)_K|aPWш$ m +I[ FX}#"n;%Q>זgK.^w2 Вh6S]#SDKs"&AnU!؁)Oﲓ=EmfGn>GWX\^YSm|ۣo(ñoVˌ5Njs P٧߄[, uܻ$!k(ۥMҖe]vuh,s }gp߱6fP1S} 9t BSB+y@*\Yeqm{4GS|Pgpڎ9z(-WgJhOPfs|tTw9ERʮB#أ>8Gf}ӳ>e9*BCPdiGglL=ȕt7?XoU^ԍc,kܮ$ i 4QNG_"1= hD>4:}Ѩ ,˪bU Y:<\Gkd IDATDQZV"rht NvOm -PbS߁]CN_K>p,ǹ{0 @K&VWvAIOg|˺DZ&3TtE#\>pW?z IUI**9"P(egcvXu|qkCAQlDɶ5}埵߯&紪&iEIY~&:0!vo~os=`'԰Nk|uȅ5j;>TK\`T:մ-hhthKq?w4Ѝ p\4Ӽ\3==6}nW4:~*F= :mmĪhoO_ M2l?d{ox#eD{8zBtTc_-\{f~&-CПrWcUbY9X,w'9*"<rIy;4Mes c$G~`th"C; `1n.YqA'}C>ou=doy|VTe i$]_iĵc{n_7DVWtsk5Vc>RSMMeY|s >Ũ߭OV_ٲf͚g:yиG!;Gv&_{X#SȚ.?oi؀Btç2A/\νhr[t753!cX[ 4pCi=+1{驀5tD\.+Wkg,t0ݽݨ urR~D^Q;*mmA03}I 0fo" ~Q^:My4AZ(eN*Bj/0eazDإ*t.sUcpn[&Ңwj9a"BZ,t*@'kYiJ-d|j׎hTjD{v?l9*4HhGL~nv[/4HA z55ɂXʫ#22>)j-Iqpݚ^ 5Nt @P-CVn'/S)1w+jE?0GGchƏaU*ڵkSے4n[6┏Q{߮iwj܋ﳻ.cʨ^J0I>jBa'޲(tA9lnop=K檩_-كFD_xmp?~_⌻|8In>'?N I>Zx!PZƕxa /޹cDC@KЩ1\Z3\7bG6t|}F:z6F!/[*盾:s&n% s:9;Anbݾ9lи-RlbهFETbrSv|igLh5b+]6rnôi>{ ^7<*j59pb:*4HW$ig~A$M daBs/k$VLX3;kw}wxAIͤl( _:hY@%OS-Kp\&3=*LӕEEEO]F=I)UrSPͷym cNN2D4lݳ|9Wݝk' |զi䄀ƸCzMpy2r}m_,Gmz2Úe|%U|ȕrrJZVkU:p?C-dp"7| N}iƙ'%^nmu=^Q|KZr,.rz$4}C;W,c]?{5<޻н 2d/E@p K߁"p"KȦl t]+~_i{YӪ탚([0{Dzuvxdž ݙ^ m&#s* V)ԁ"=ȬrCdLVR0Q鮜Pzch@?r9mvic#:T ,SuǣAQ+al{grL*)37F4x 'JpZu k%C*$Z!i.X&*"2 +Z5ɽjz@`ֵYn=z޿BI)E l㔴:b PqbS mI9l=bm ZjejSb|i}ȐR%5H<{ Q,%?ؼv Q0 5`z5iml;Meܬ|ꋏfrbUU,?gOnp9AMՅwjO(a_]Ӫ'=$πpӀ9Su=e7'24J'm'PiJxׯ. ;8p7vu͡T+׼cR ay3bf0>w6{+LiW=i4ZT) yuv퇤 an_>Mr-<\c)a~&BGv+wPKlBD0Vu);4\.-,~cf)>>N7m*=H7\|@KfY_k>{ Ԏj52Hz,H3+ ^zr*S] 1p&ZCudkCLSk ´ſ#G8" ma#n|?x#B36#de2+9ʴ "桸M)nd> 1A9u> DWa.!J$]mOG% x@ 5MlF"Ӓ}A*$Z!i8~gzF~h|8VR) m8C~QyG34SWE_gmQsJJNK$yo,)b R 8.gcb:$DUj{iJ>#2Fx[KePbgM>ʭm[f9c܋M%v8X1Op5㔣A:#kk;w7ݵᢏgG^,s$qIW3lgx|ԋGmGe4*vyMYװ^:GAu'֒y¹]n^n;^1xK_EtAh8"`ߍLeqx(kfNr4`M?wٓK˹^p)&Ŀ oJ%uQSeo#u]} :_ZKDIy|n7i“5+ pݿ&)(7 |{wBh1ReUCc0DGjχ͓ O7(.9Oْ7$vZ|܂}]fU(@t+;^L2ODG4P,.hC6zF}5L {L.vZr՝eb.>jcP~ɔD~^ ؏PV2~ .c "[#/B`}2]IJ_q[u6Os6@(1=\L7ks1>q0f='ɺ;瘃%GrP`@gG֥iHebFMk0v;0;b[ seUjeJG0=al|؝od*C[NW7l1-]r06R/x;ƥYAbz2n),nڂIA p>{yt}Mz CYbAPcKNc X:{˲KB;/Wk ˌTa8شזg$  /i3ε!j'K]? ҟn8*THW5Mإej9:s%'T[To߸]aa!wsy\s yޟzmMi%AJ_WӳG$ea#VgiB" ͖.;J"koetrt4W:eU!eiǞylP`n"<8󑳷BFhyO?jI>:%T[>Ͳ]h4^?snxxL!Ov P,4 5?.!Sש#^1à.z?'*),q/Y6)NWlۻ7Ã{o~PSvAVrX0lޔϿ=25'lٜW9,ܺ2>F4=O0|sW?nmx*-6؎ݥ癆 cY%I˾sxX4rC\-Ծ[iƻev/^ZjE͕c[M zGj'&uQ eXSY*ϐزk;2gc0~ͻ^iq3}/(*hcqgqqcVKWΝzt)5 頦Vlh; 3ay,(oO(N-ou/ʕf;Zػ9%ћ Xq1نXOpwߑ`wF$MWRkh{0s^+\ =K!!Q8dΫRU>dޭg4__ԼꝋSѣ/5\k~$Q B=WtniYL\'6<2$%PV_XZu- A͌= -w qsղoUi<|zBL(jNV{jB}kx3Eq݅GkvQզ=?}HÈ#*V8,SȔi7r̟6[ql?laWů1wa w4s];/ye%3waq?2EQ9H(?CBF?&  {kװ>#W{6îB^&1Oeޔ]c|_dҿEӗmӟ[~; yH럷Zu.`۟1/ې`˜5 i%y.7 \1e@UIݤvaq)|7?e S=LKn~r.Uܼܼ~wI6uT0RZi {eRG=w) fp3 4[{DVŠ(6n/g75$v5^N 渾t :jio!/_UK(˵ɡ5KWGGDť-3tsq=lԆ2s3f߫ꥫ^{ws'ۥ۲e0~-Ѓ C*EDQadDJkU@w2'(F]o?vm9IM=/na߶`A;9&52yٹ[Q#F53% IDATclp2s嵥nbԋ<qUsE/-15ؙWdN ov=||e"KrY%1p ,Pa]KqC!rha#IMCq/+d+)R%Lait> 쪯^+|S5SoHTӋF~Ŕb1r_#IJ-I8*THBZqn/*$P!i \Ă'|wR+vS&i7rڣ}UE%O>yj;rvjɣzۅ'9D" ݸ7&5j-9=Mcl9[?Bɉ%;϶W+ t8g/\(ݤb;mO-Ұp?6˩3l^谤2˰4Tko4yP\w,#n.^u0a1v(T/|뺚q{&ߧا>TY]"v_ı?"^2yU_v=J|DO.7Ѫ$9t?uᢷ2O /+C 3_R% r6-ꥹ|96l^[tQQ]N uɽ.ƍӇ3QO_wo,>1u#oKsDyag@8'H] yp0GCPtod_?M Nw9wRʽzD[UOuLp7 (f8|Kd|U[~zUݴ4~L6o{59Q{ҏF2ݻ>Z&+1Mؒғv~}"+77漽ًfZ|‚CcNuZ9jlӳ[ʍ־4\Wױ//wi`ؾe{mb'Tzƕ/”?1}tX{?m?XWSӟrwW/Ͽ|!XS{DjKFc(rA)>I1'ivF=dJmE8s_dgzީ[b^kpD uв0!]e OmB$\wk /+bR21NC'6L\$}^&(C> E9~L$_C RE1c)S"P\1w,.lϰKzpwpOƛwAÖ10_'ȝ14y]n$z> W>c_gv ]^Crp$D`f[˴`{G9W5)qn. ײr*joWC).U< /b9c#v?pi@;&: YEQ ߚ7=0^eP\;+Zg>agʿOjma\DOdaElgPl[8q#6>Jด!eE͉h2OYoðT˖?ﺞ}t7_X\,AM/ܴt ~pEEe}]H0&X {x<(YU`\XK\  4{O=fN{~H]h^|qeye9}pb@(pD|QQ:Zp+$(v?MOy c>[aX ]OJnSdJK-cYЖXA&&4?MO uJ@]p9*$P!+$Nr7pѫp!WVV;=-jd̺1 \y%WHDv{(I<|iRV룥-ynڲEn^,66{ O]sLOK 3۲ٖ^P\QsWq"$R߾txݓ髾ͧ€xGrxCT FQSsܑ ȷs c0YO?QTʪʲi ۾bڵ!-.*nUaa@֨ yyEGk1̛9/"/`X B&xnlb6‚z%x[mm8?ٽtR?~ @f~W:cwܦ8Z?-$v%eElgn MlKr$؎ 46Ƚ0oXȶ-,in|ç'Hb=VKe0O%ll\.UxOh*A!t@#B!_ E!7>hS K$lbyaxz{g}Ñv0Y4|ayejç'yL#m`n8nƴWZ5AP@k&h dAM:&jp&TeA.G ZhShShShS (Go)L)9%|[b(pj5g H5,?K+}agIz.u8**]e&4 )jЪọH6\j@>ōgB4IR(P0c CQqcXA/%=`U?O[&JAܧ-@4^KDO}ҫ X8jҗ_W|%  5I;(C&o0l.+E;a8-:/6L8LTj0(uwvimb?O\{]fX?o7d YJL<"ػOH_nfqP3e1 C1|=/mT\VE$rs5\/A􀇛-3xp$riv,K`hG.fTDD/FP kWSPIWu%=GPԊ0 + II/-d7t Sl0P_e  5WҟR|?^k0:&N*j$Rxm! Dd:.^ض)8~1c}P$IK0$RZwYEEB09F+hԪ{wplzNIOGp6g|TNd`8h0m}St̩p]2x0PqBƫp&s2*$]MBlxSAYzfQ. (&mՆAM;%5_Pb5B1&11Pݤ%)Е|`>z c"O3 ARz'+9ppB1 a{`|#PH c cmҝc=kyi Ɠ3RWj hlncV_.X Sha.i|N;9ϕj02zgsUOh@G8G$3Ib*'k79l N+#I_sDfRE67P>偙sm{X| ѭ]Na੓GeCo,3" nS۾}ZZU^iѯP*@M5卹@"WD~u]TBvK-۩+"pj:v/48i*ѨLx}05a#ԩޣ >EKmA_vN*=:3E޿sR-K#bSG^KeM"I&# ͧ-2\2  C a37.^Z$&M& a$~y@JqlQ~ HjU&WclL'^ۙiIQȕ\C#IX;+N:j)*ClstQZj@h6᠉_zxvl8+knu 2rX<3ZD*47snɏh=}3BB"UTg[k&Y#/=P-FHZB!PH  `Q>sٺ54Vi23NrtƪԁI"*İqE _zﴺ@z0\NvZ- 0pL.}/^ugS[4G ^m}QSc+@JrEY-7A?))Rme} RCj3Enj}0maѿ p^-ό ƌ/%/^> SksWíԗ7u<4U)Jq'bzjeO7L4`ē/Veʚj:y='oOOl|8}(0yu^1ywfq/KO3SS^|se8&$Ut\=Yɺu_x w>=l4=mk< @#5 ŕafPp6BKS0"+|/C6hCLlØ#';9PRHW6;a{vjsBf4 |ZޚӋ޶Y#ln3b]UH%U\z `&U IDAT2P4>wKv '+C1+ʓV]V!HS˘tCc9`ߪcƜ= `YhLSMnwb&iI䆒VN,cՏk2T~mhE@;E|3*Fֵ^֥U IsЇ;?T\i֜PhGw:]MoXI<ϱK_E"82ԗ˵ùBzFzsgܦmZ<8i+rbcU_ 0y˚s[xLw?[e8lJ mAzy zbŦи g?E}v"1=f{aܗ0@?-m0PA"Dusi7+b6=/ERi% ze GJ$?hB+6M,2\&ҶMu;l]ioʮi%MӀpJ> u͡eCi{6IE)l6 9A aN9@0nmFAn;miއGNE0֏ꯍ˦J'5BI"DzAM*6jY%qtF7"BnmLAHW<3ϸb|.˺oaH5~`\c21Y?(]^ۯSCOumPB, o%BnIDu"+2K.eۋ711yO E0!2G/܂_!dqbK 2F/vW#d@?KPEX{WC&l(1BQ!8ټCJf g$E//#Җ\S^3 @&ӗ?o}fl;xDW;ő+e}@/${Kk?ffkgo,h渹OYReSk' rxEU}dži9ue ?r?7.^G|oz5^iSDq^|'jQf.Ż;gJI ZVwrn>sFVG)|xN+sҿ}8nȰ!_esI괕򬼌}_q[ûZVL5f'<YI[++?I(} ) =YiK;W"WO@QuB+7hchݣP>psq`0df?prl60ΊXg~: l`lt5pzSS;G_a\TA{yaՋ-6zY1B5ʪپ{{Ke)'Z0Tkop ˣ/e;vާW!tMmƏ]wtQ)s~ lyUZۆ%FK6mL?YLD?M7lD?ھw:je3bqjDv݀`EgKh8B46*˚Ig, [gya+=}EU;`yXSkksV14ַn_$„)-U8pG7echS9 7P#[}bZ`dAcı ˚5C-M3GQC@m{jPmJoz_- w3BBRQS`|Rxh I} /z l'1Gn^MLxyТ簃gxRD:ifVKDSFWG,?cC|kEaiW?SMfl1[cLZZ?ۑ|.xyw@̷K3{7[p\=.GB4\ךgߙ5~.^C1cpE>ߞ}cV;n;nxi@%n$zz oͬ^jb:v@ )Mabq|unض!65C-x:)뀥a3tQwkq5 *5g 6Af}ZeH8zHAE.!pu6U%b훠 UϤ'>cz, e_{KnAA}$$٨ );ڈB*-%Ӑj|B5R(#UdV!|2)1Μ M̴:sYL'oX9ż L <{=i]8ly U@]2BTyۯdqUr1>P!*$L!^$,0h?ˈ Z,RGv4X0. ?|GIB\p$pGNկ]ڜ0O<ЭIghf8Wߞ rk S䍲zS;%@@uNݞ;zg1 .Jӈ4RqX)ClڻS+4+oㅏ`=(FyBI7QX3/Ndv/AFڐ0$2E8O҆i8wh_P1=_B'\W`8L{~/(SDM,pOpeb8lRmN3ﮏy_P)dP A%dpNpq/Hlԛ[Z4M|VX2sCꅜUTʺԻ#v|Qc3`(6ndV6 +z;Lb=oߵԄggOaޕ0|X3}[v}H\Sb|;I x 4?jmPSN70AԛdgbIY_L<_o.M̼ky 3vO7m7=9 +KOzIbbb^5:{pnPh[$K:{=OҞtoػm3BBДl3\] *$Q7smH =)՚!5(0i^5sE2=e (F]_̤ ,TZ5ПAhV=/(35w0[_y|Z43(*d֓_;2[8~s_Ik>n8/{Ϙ2I؞[FG:whK@[8wԹj+08& 7xl?i?:2[ThWǙ8(YRR9)|bل_w(jʪw[Fj;=8{Kv޿:&>1˫)⯍͜741cƒb-#O_:|'lO5)T5~ yk"*K+f}̀!zMۈJi[."׈(SWبvAMRF4]yH$GF6?Yh >0뀈.-uӚ{jbouaq_A+,r /֣ ) { VGwcj;>}6sڥl.e$a{(*.K㯻-Ҏ=p1xO3gn^i |*$c@Gr uTDkz`^ET'!qJ&nwLWlj Y9ĨA1k?vaIU 4Yxne'Mӽ[ZJ[R(-{/* AP Q(* *nDED%"" 2dTB {mI4 _{w//w׻'+ygz{ F%x\~ka/a;hZp⬷u%NyM΅k&M\cx)}yv79":t=mtFU/2eSj#֯8(K:3/}֒)9)L֍14wcoصβkE3J9 nM~gϽ3bګSU7paӠ@JYB|yAn;2x14%z[Z9ŜJ7sTu}Ct&μLՊ߯} żwѥ6$%jO/͓ODu=S &Q.3>Oȯ5窥"Jg~M_«/*q j/66Wĥk-BY/zˤ\ucP߁%%,{uas-LJ E|JE9AX2@cy/F'}GZ>Hc0~6}QKA!u>ˠ9&SMUϡ-\ߘuFjOdyu&֜pvUt:tG{nx=WAԍɘ3+H(&(eU2܉9K/\kknb4HA ' Y~cJutjFn iAu7uLS"Hؠu'k6f^,4]u,;zsNi et蒚Yv 3OYP_~rg(Q1Tp+d6KoNraM2sN푚{Nne@P-fu'X*&qzkA35J%I,iE(jBZ I M IDATy/dGpMMAh%lw&>4/6q7=%ʊf;ww7|NZ:?mcXls'ms< W&+kk3} j,ie[md* 9Йm,-(t8IXLxi5O(./ͧWBz/f> Es5)Lb:w-MK~Ƽ*7han$4WyKg켜c>+ֺ!3fxg杛-ӿZ^ b8lఽ&IVs:o%SU.]-\Bl8Yߢ#Nl}Mߞ1̫Vk$ȠD[ͬޢ !]|r^Ӑ,zG;z3kJݕaC*2.[}_L-qj/q~n.8~yCnh@L&S-k R ϙ.Օ H)v,W-ٵ|(KFeӕc[%8lG-HlH'5.VRgu9wjm`ҺۼbqWd%-xôC*ȪuoYҢ.kٞiR`jODd;RsU;4{Kc k]/(ɛLS:%1/ z:a6>Bݱ<͓&+Փnc;*헰4y1d\iχSV'8uUYOX+D45ӟC-i4l~.bIW+~cܢBKۣ%c6A&\6?:s2MiSM YIY~fUY!Uswl?{`$ZGD7\=^ٳ-mU3l.yO5Qj<%9QkjQcpڛFxDӔFi~aPdѡ7}tG<7=- h5Y/ruqr1 h#3uۖ-ZSE.tIy'Ӓ&pg]D!_a8ư qfaqC'E[İp5).)b!#/ 澓|ᘶ=w5e`O9﵄ LWB/ۨsG7Fug^=_+jesjhJNE`0_JD7 QsAh.;: Emc(-zvAA]yHvvy]|F.=v̒E|Ƭi]^P\U E BvIx-ꊮp}k\Ai(oEZ[xKהul(+{c^MeAM32[CgU/' _<״7ƵEzCZ:9J?zu;\#DDZz{{t{\'fn4HAҮ>tUd;_&"KA$_Ӝ7ĸcS;x*︗=|.WQ`Z ޟo3׷䒦vӋ)y6ۡv6!S2m9Պ'!d?L [kY1E6h{%0q k:#qm&\rMy}ft&Z/[TUf݈n๬!9808@4~Tȭʱ:OGn5kĉYVj*5:_(a%Nt3ga0-$>l҇ccg jj6ג+ %X$ Â$?zq}pIܧ>>6?lSuqƍzS&/XށCR ^;hVӬD1 MQoQCϐ#f^=ɦ^|ȴJƿFs_PH5wlMΆz{] ryJ\$..ߴ]~3;z쾃/y9>}-h@cߩ)nbkN /+v4t: @ ,o˪ZBeo/w)mXPjޥ4KZ_^6p5_-1KTwV}LQ4B`a4ʤuunAY^]yc-Np*~4ue} e6amS2J\z$gRT9oJg?YΪr}߫nw?cP1130v_:}]*= <,}1]P;)C^oyKh@ Rt^p!|"7\4HxD@ b-=+M`*LU%E;nڪf(Ge is4 -m rIXR5m!J+5xԪi3 bq \*QUd:cQGjMŇU_1Eؽhwf)ӆ֦[B<=ܘ'k1?r؞߮ VY_+FZj! hrͨSI^ln`M2PcgX&SpGZeZWƏ>4}T;7@Fjj$ ԤjZ]o N>M*5?pw_R,םևo3zvQ]ST"6qZxhɣZ~ U0P%*YNU MEИ']J43镳1R܋ q_RQԽ sA;R#&5Sq?RYJN),-aي$ˢQOp/! t*}mM[>P{M?[pYXkffUg~ڏ 0v)"(Md~6MvPR^,3hFS(5zY/W I2B;gq8R}A]aתN"!RyVfrqOt<<$2蚛s;$:>OB^'RsЊ I;:#_I~(N +˒n~lY&f+  v3Zz #*őrUJGK4J,!w:frC[zTƻ{ _Stl20=>-][3A1m ru$}t!fAdMN޵]L/Kv6o"^7i8h"/jvu(SwY~X,M'*e1DgjՕ1"Wkr?aϓ MXw mqO䅚̃?1_Ħǔ[Mݸd/sq  KmX YKQ{\i}JEEEQQ=ׯ_H%l bXENxkx}r7B"[Qꧭ= ESUJy4*V֨uzΠ9\ÑTʌׇmaGg9'ڗ~d}Xe7ˠ`ÿ'dN/|{iVPd9p}PM<Ա)kcxuj;Ӻ+xwE݆M8wvha gUTʹ ɝAЬ޴y xE9׏#Ǻ_= m+h%jxurX4-:BM> U'>Vu 6b?r~x&SUFx= +N(i&}=b>\|~[s?O Gxpn_<с.U#PR&"R(KoJW|ؒ^#~ns1 {jmsfεe;7"~~`=F>$E2_l֣b I{=|^J-|4H~$cXVBc h~;PT?3c7&qrv#H^qVكr͛yd}xMgVc2=i{#oVWXklgf&S opIhK8Es(вC l!XecZ cIH#%ַq(H]SAiVrF7mS10Q+AP;QzWq*9L!^ܠ;;jʢx~M`<%M)Tv:#I5gj͢[:Y8wha@e 6SaA,oe[m J݂C#~+T0{%U~ tԤ j4~UJfI/?K?[~`WLy\ԇg&YZI8|jf"ofɿ23 ̔()hdF=4}WβOqξCʉ:K=M*[Inwq .}%H6&5M.3+1 \N(*JPeen׿*Zk326Ey:c=@?DB\tH%B~lJIVשmlht=]"pS":nV8aT7ZM],XZuO*w;ȀHVu j8?QffblROo2+%ez-zJ[=,H.=n\>BeFGvz_RϬLzLqxc: M"^Z59&Cꡛz>=aȀ)مY2C;u Fqxk wV, 4HoiGo,(;'dha>vPT&+ hJIcº^Ol3r .zZ^V~FnMiiеGw|f5mcw${G *1dk_AvĠU]4HY3}YAØܸ)Ynd%WaI7?~c7뱧_;~=>6ⵋRmF랛nŌ*Mwa]6> . 4HI_dkr鬒S M 4 jQ<9g?(7ʕJKxޔ9lk܅F63&|:[ҲK8\TV0qxMm&>4O,>8M׿fj'#7=1|,(:g727't7lޖY2='FO7݆AQlS g/bM~9}w|j;OOcJj5UWW%I7kDhcmd%_>a C9<݊RY)CExUh׎ldrRRs^Ɖ< YMz ꇕuXYV$Eϐ;вݶ;p˱#گjmfF2YY׃ܡ2%z%Sg ܺ!CƖdNl1Q1w jvo@#"!T›l IDAT LjgF^{0p@*b mxyݙ}|j(і\zgl"G6@z>"lZo}PGr],dE  lg]*!pUJ5gz&t g^p  $H#G'6[4HZA4̫5۴h m.{b/Ec՝X֖ MJirX4Ų _$U2]w\\ޒk>&t[5kWó,=j]q͟RҝVNd$,bRnu/aT)=3/}6[fnJ|çX` ;jcϛR7ֹI<|ᜉs`LXWӬIZǡ0ũ ,oׯi`З^gmu喵5yۺC|-o+Re5`nF>Ȕ(/¡4e!:xܐIkn̫'sܼGL5b2 N9u3*+OpDPLY=oM9 K.KX]uNԡ8U=ع]aTG"{,i_8n ֥ӳ)5!%vKt?{Р׃a틠R%b3ӟ1Mj:S@Ԙ`@=' 4Q\>^^qUڽMRBдpȸ[9䏩z&1!1 A=Vuu(HU&J%:d@#5vUTYCM$R]),Tw[ez-9Z-}Iij@71*:'<3iދ2sbJ K_5QJmZk1珦~;y﹊3?=G9,F>6NVxvqnZοg]g.Iuse]?YYZR#||0,-\5"aЁ o;ܴ^k~&Phޥٷlg!XBc>uw'8CgsUEYxey,c]8pl8zؘ3noިNQ`0mX`+9EsXۃ FQp?2Yف#>>U"o~?Z_r~EoL.wg8#26w06 *d[/d8ghqJݔw B=EX B4Bg}]42vY,<,,.fbJ[ؽ5-oZ]޺jҶkLݜ%bżln2lՔXlY}ʽL%L<3n\N9hE\BƳwE"k+snm߿C Yɻ/mwBنJkBv <c$"W.&ZVV睼tWJ?hXA|9Եz+3/?, {8mU,EZFWWoGLKwEuUyQέG|zLktWO6W[皶xA1}F^JQs-:&L]wfmǹNx;8bR̛l\wI>u ϻz\=6WQYVU^~̯_7@=VXaJF-~yqxh;AiuZ\~Hґz>29t  l6[TT+d嗮^uiYiMRWB/??>** 8'|z`dWJ%"HQҒ?BOWW4m>a)V3%Z,voeِm.ۅrے2n%-MuVm V{wh{nzݻ'Ű`fJJj|TZzpxz澸EVExrW’- V,#loOnb`5Ϭ;=3loU`6·̭{IJi,麳֊5x0@PS&jh, @&\5˷UhIhQ@C,&) @U@SXA8dU?N]P'  GM P[NNUż*I 4H < GGҖ4@Bx u;Kȫ2ʋx 6=s0 4H-h 5M`ЇPzaIu~((Ezڕk hAh Ƕ t!6w J*-}4pMN+%cSPKhAϺ*6QA@s&NGT(RYcO"} Ic[A Pji?!Q*'/=A A V;`JqqP0, .p"]w,Н"N_^v̳ a(GՕk*;ۣ;?t +(@2r`E"itS=C?\^hZqw$瘦ʲ^v{Ƽ%5rMY]JuyFF֕FTyI 8k U9"_fa"߁p%wɢ\"7Xٞ܀RnͿhNiJ(pC;$/ҁ%;sWiiLEXkUrr)ChG +!PG~Z]JM)(&QBzaIzEon'o´4BCد:qaϖIJ a"4@xҴY22rJ G*@_\)F$%iQnAlیz?gH`,%Iw4g[cߞV1 '*5}%W.#Q Ļ9A h@$ӔZz%x^ 4+Ḧ#5m f~j}&Cw*אRE1CH/+-w˒Qb8^Jfh-E4$<\N쾶Z 5hznZ(\Zńu+trbØ4M+ˉwh-oOɨS2mWnko(of,IWw߷%RU[u%}#(^OFC L:ۭ Ro1顽 brk*T{qݿd,3.0nE>Cy{b^; c3y6guo^0:E  Fjt3r?ꯇ%r3j9i͵;p\7FH`u4EUٲ35nz1n ]؇;Mw*\$ƏXzYw",拻]ʶ-Rże&|]k}ܚw6QV3Ip-YrcM/|i@]0#.10f)]hN# zl.@j>ePPIjpmG02E4Y]B>o@QA h@$ 2Um͙8_}Z=gM{\m:o%[(Z_\C->*;-*I֒S0.dbl1{?D# v[- ⴕ$bw.eRv6IfWZR^PPvӂT=x\u{|^1}C8<^Q+*s\?7dҰ/3Uɗ0JpڛzQEKoM^LU*A -PNԡoҫƈ\=p6GYYVQ?o;ۮ[;Lj'6G  c?T\bzk@r c_4 U1Mї!Δ\4TT&b!miCĎ3~č<%և8'o>R'#0j =F#,\LˊцrgǸd,.*9kdPˬԼ+%UۙD\#قEӊk@na{{>96=i{rq?`'v|v.3gNK{?ةw˦g^=Ѵ4uf4M5f6b^tѷCTsWu7d?yY0_yھl)e.kIl 6kXo:}2Y2\z.X2YRqE|Ǧe _Y")--Ne|nl}6BdЀȠ޵J4QWp8WKWQl$J6Ç_+dr%Տ5gRd3}hCWIZފ>xGѲ*@~@mFI vI1> >W9i\m:z[| \Ylժ;ZY9u'tqk lV7=sBIm3[hݸƏ!Ӻ˵4f޶1Q=(ŠҲۊwA_ ౭gq!xfA1 E൦KX{\۞ig٬!8FtbgꠦcO8%aq'14 =C`LI^ϧSΡG'a6[0Pt!خ}F@lRṆp:3};(Wֺ^ -iUEy:_"2I:;DѬ/o`fv6'jHqUz*kic?ξwQc3oTZ=0}uv f<ǛٓX=G/T!FS Rܲ]ҁnAKDI;W3xҚL07b.<8Kh@$  4H1+/'kD hIϠbucg8sJLtw9nLX$WfO23o^xޫu 2yBq܀G1}o|WK75n9jE%3;9+׎\HO%|CHD|˘DIնiDz ożM4A+hCU%mH8Ke]5v&.*bK81z]iz~:=Io<ڙcrgvބ+r\<ڝqs+ocGYBC#W{bn Q>DXu4MgUYe&%f)z{CX2uO!תE*v]Qm:Fu걍&[-Dh@$ A4ҋgZZsI?51>XZ@LkCdm UJu~_f3Xe{ƒcQmv ,(?OR)k9Q0'ZU?t򯇷A ۾W{|]0雩]6\73ԲP{dM.}}e?[VU׏NlHv3"&0o(=rYeO\;렱Cw!~Y o!qit4>'з8׶Sכҥٷ]-yB/;Nڶ{;# m i _qZd>Ħ蚾Zbw1'`.ez=QkCkRQ2b۾n%y^L}Ø)1!}_U-؃3;[c Gk-iLРgQjk`MN&8ʠ%,JF4pW=D_]g "'\fJKqJuxxÇEr[+WwS1nqq̼L#p\ c#o/>z%{`Eü&A(? ("לT_[W9q$  4HA #^}II6JL׭"Qؽ_jt,]w4])u6'!jbly5nޣx/'s\*(˟?ޟ%ˈ^sF 87nȤU>v˧3SZ> qhw ˧ihǟ[>AkU`/a-/O_3xd{g| o M[̈~;D *;??ޟikfs_8[U+T;);ۑv~掠=x<^'Wog&oӞc;y<"H qxMiuڲԭ~6~`wRWw쩳##?O:d7cOz*:H$rj,#+׽6lNE1lH!|I**)޲_Jf]6hv}M]T5}_ C6="~!^mn׿Lט;[P~R}@g>bX jL5m[(kHRm/k&vSxA _?şX&d*OoG|Ч \A}2<Ձԍ0cx5w%@ ;R@qwJ]K)R( w-.=!۹esW~ǛٝۛA/MB=ddd.$+@3{cc{owpxE^1$1j"j*' 3Rouv*ߧnkK{MDOԍ:ַ\:wlK7ڇH?oZiܜHzo4i)cYߏ]7h]c}y]6ipPc'G>/SRS޼:bhMg9fs95jm\i܌vnUpwI'Eg5ϑ(8|՚<7hf*5,qZoeno jh4\ޖv&l k 5 Qy,Kg:CTY]dnU%%/Gҹ{%SsCbTaw YJ]MX]}ʲeXr%I bSlvr_"Mev6A|E2,}bW*&lBuNU'f|Kd5> :$:$萠CRƚVd5v()5"1BU@. ՚1!gp޷^r .-]m/PfP֟:t6 h[q/pܑ}~8xn/F'#B nmrjF| 9EjrxblVwqЏC%J4L&Cn2d54c."2-K_*vc] yǒR^?lXg0?N?$.CtBC[W6; j |悏LEѻ)Kgپ!,v!) wl?݆ۨTO3Z7j7*;ödkWpxDV3>ՠ9ۄ6dڷnR \Q$dﭔdmxB~ֆ4Dg0:n55H¨׏ql42!2M劕'}?!6!R"X,*WxD/ZzmTnͺ;kbtl֨وI#oݿe>ч{DA=#gg[ZX ٵ}WÐ"s~ s7h)T}xq)4Ť5I"QQ,⣕9raqKS jwIVG%W*d+fbGc0Eq 5+xt&`L3:H|l Lз=߫i֋dfY4fqoLTϺ" ez NxDtTMQLw'V *rdtdp~vq<';7 O 4U֜mT]H՘C :$萠CIYɐjVܖBQӍcHMBDsWjjc<M0il&0cЉG@`9H7R߉ZtW30s+V@]v@n}{CѤR>-͛=b_zfxmEVm}ٸw%UÈذ%%eO#2R__[վO=\kXk޾72o=Ooiʵp3iV_9c̈%!㘌ӋL͠ksjګ&d#g:"^+2&n˱cF$Viߟͬ&Uܺ^77lž L'V$?Me3m_{E4rqDq/n,#'"Fg;斕~i/^dS5EB[NM4qt1kP@U?o]p å3VL˘6)64WZܺ'R",tǯjjVkL3ʷ0?H,,fZ5lg8t맖X{T̟1`Dl?!d=jWĥR3rհVݥe~0-L%rqɝFp&=)69&E1z_R6[# 9`%*F3#&T"p܆*y;2Q~‡/\&F?EN" 2#IsNDBkմUpPMo~*;dWg,lr*㎍VDgԬazD9'LRp&Ι4vX*85,zknWwJFUxjgvC]eb\Iv/wɃȴq+φůـ7DY9מOMH*FKΈd.~Q :Cȳ _&ay&[/ҍJ+* @Uf|i֨%w]i o5إ 4JiY/v"",`=F1_g܍C-$Ujͨ35;2 i- v)aͤUqs18,m7D3y)EKQ=d_!U6o^>q36ZD4VRfnݎ<%(5,T/ѪT5> :$萠CW!)Oe/G7̛\i"U_N6E0"RFϺZ.x0-rUI_?2J]IVӫe)whlϭXeg̨y(M;2*YXJvV,l6`"<n??2^6G~ Qeߗ^;tzۺ?f< 䉎\5n5%ƥC*yƕRk/BY=/׊TZV< _åNL}l8+5vs6\t΅fOⲹ'clG䜻ut+g 9{Uyt/5m}*}9++[35dsy{̉Zэ*iŹ-g YUOZkj5r+0Ǯ>c˟+>6ʌ\C7 3GFuoBOf[T_ioJ$Wˑf'7׵k}L!9`ZNkSkkNWt8Uf5C|+Mb_CM79qg,:+QfVS*X5J~ h 6}TH*3.jq~:-҆ ShkFw6iLsgF!Q&E,]MTעPiO6ߧ}GKh__5973\~־&n8L`?r4Ղ.՜T=ҝWayfŧ[3i)*GrBkͧ+պ'~PUZRM^NmXWl:I4GnȮjE' IDAT(/{o mTZz?~_6&hKo{"v&IUÍ3gX0ҳo&?: ͹x-C :$萠Cɧ}X"0G)58ǐkf_vbys"Z]K4]&(ݖrDތPuf24 ^J /;vU2!jHTx!6f:ً<뭒xlbejܠY<+^ ybϧWHLҍ/aSOQkT%X_13W =geaRx:R+79DbY 'YEe1uWbMdnASn>ZRvVYLօn[[ B APʰGtm36Iv!;}Lظ 1 ڄLѸ E]G/zy<#7UV$:ݢ;}r;(#tG !jwQ))gl܆'"c#]cTkІ`Z:޺lx3qr|luPmyz#=; >lN@b3eX*_GQ7ףgk?tƕҫaɜpLϜKڒsQ EEA7!^= =} zd_j\r㨕*oTLW:!<~Uv?xjw,! 6+,] 5-M>>}gCE}f[i343 >U%Cgz=w3E\k,YD;eٶ>l[R:ϯ@|nF(~:]LdY8Pϕ$-p.bC̹(((ӖYE@{)ο-crxs͖?Gʴg;AC*Zffθ)MS-{'㻺Wqe?Mk*0|Z{[匘bƠLFרm߶nnmXnR'wOw/u>Wq"kGƝ֖{ ӶO;Õ_fE梒zUra5(l&m\{ŋMDSYHoj9* S!wT?O!& x f]+ΖTb`3YDNeʞ2ǖ͑ 汘4&obHi|[MAYebE 5.$J3 +i4jG[Oj6SJuJ,ھPqr *o}4ZܵG=g meaKz0fy߅Bb8jӧ,C3#3=SALD&JeRRP)3R …ۧvw`0C:p'Mo]ÎLhJ!c0v^\8߻:rom}Sr ܗ\jԤ<§$Q6t"57xr܀&=+sZs|W{l :\ybMҶnzmZq! ^`0JekPt:lb2QG/ۯZƳS|k:QPս L(K4GX |Z,|sCL/ٛW7&^Fwx~lPek NQ~~ lJ|CgEy΍zZ^mϙʑ$bF@tH!AkME{.Ѽͫp Gp2RɁIL} ~1hA^lb*| -=k}v}nŊ(?d)_! GQJgϞ-Jm_aI'Gd%-h^3b+~cm@37_"AMr&A>-C;YRjb ~xUd&^;vw:L߿` jJ~#f-9|r)xv̅KEnd"-1h7|aõZ'qRH3qZ EdǶĨ0GDͧƝ/({_۷FSbgT<6lJhg@28TRT*]0B!RX"LMOKJNz4^ְܙ~ L\谵p[`q:C5pB84(5*-\;' TDXMˈG}%\+l;|h˫+ D|ofeFa<ɉ*܆ʏs$͉csx_`"Թg dj:.1~뮭F}Ǯ.6׾>H&~ݳ}}ҊP2f5&b6`X]ȴZؿc n\,Mi cs.d وx;Iڍbplà^ߌ sG2 ܈_ڽۍ2̉~] c!Sf_P?zܸcWTM:0a"԰,%RCoAPJH6tK danG&Z͕;\_/ێ[qкg?ʑZ$f˻uGuЭ>`FCMz-'ӓtobwߟ͙{iɫ-lӓb U}lJL:nyҊІJʯ^ˡ?9xRwjk{  ;6Lt"_?#nue9;%ĥ6M4AppkwT2$&pHXZX}xPR3mZkn{]J5,JM`2"Z;bi? 빔QU~x2뫲i YWQh߹aQOʹ{W hsbdK&btz WKv5SQOL&˯q3 xMyj_' IHUQLBm?K{?87i& FHI`0"k6W?țW__-=B$LbtcfH O\V7|u'ߩrGO6W7IJV`Wt1r^~"fGwRZݞSlL-'ŒCg7FR DKk7B!;w yM<~0zF|wPuV.\9qDrUk0 ai"J^lmuN;aK4nb bɉgJ_5)*Z ˒JLZb2XDB_a6'G֓Kjw]JH/Ÿf~ ƛXes+(v5*5Ǯmea=elcx;{$SYwIfفa73pZ7{Wx8Wج+/'HDtOƥĔkP2J5it:Oh&^?ڻ?_;Um[niLLd8#I%U/,"6g$H9qG?+4<عb5O@g0d⬴ΑqP-cZrĢ~A|s [كqR*d/fPSpL'i' E>}qF|pl;Wُ0 W/^2Xa܉^^3 6wBlٹea" [2WoQh3Y`}DW#\pƈujXX[itR<9Wzi֍z74u{LA*{T2j%Ks+.fJD*Iʚ]K%rq ֪a[gO>O@|JQ*":S)H`cN&X|sB)LHzmmGd $%ËnMz!ޕI2ӢÞԿI=G:zV dIҳS"߽o]jyuEhj49ʎf'g$?uh؀O?>/MK7I=+Z[Z ͥ2ifVfbR Y'b]\iQ~_O̊+ F~L)?q'-<">_/X:vX4P_%"0J=-SgaE3 0|854':dtEF+|Ly|TԄ&Ҁ&Ҁ&" AMPD-5CPPL! L! L! L! L1Pm< ZST&0.E# IDATk6ZXs":AL4]Ռk$@@V,\ М`g:jl}8% AMP#yYh (/ '*hӘ!c4op^:lB]PU[2q_)E\srIm9=3ƅŐ8#ǘ:WJLɅ=-z?ٓ((Hy2qox Ԓ2XlݼV?ue""ߺ-L$S۹T DeEhCKI+\*[1qzrZb̕C[%ٵaD"*&𥞾|ڴQ,qVfV\i.4Gc5|z۴^G&%<]+(0ic.>ZQ94Kd2LG+d5 Zft[SMl{ڏlrasdڡZo+uNHģ%[IB&YǔVS]۹;{v Z1CYu㐯(ɩdⳢo\AL1JMu? &՚\FsO7.>^nT vwvu"9ٻ;غV 7W 56nѝZKb߽HVRL6YnU ,UioQ+ qr]dơԵ%^e$E}^_*dbr&V IF4i4LB2Vw4ʼKXJaXhsFOm)5{`ˢEn'/Z<[&]j"Su]yBQ@_Md_H$^޻q\;"QYjK0gOݼđs=_ CڕaEhփg9x IF- nBm}Dg0m_ձ_<>O`! "daO:P0R޲j5h=n}QJkZbVQfjR.Ggv-JKJvL[2j['  ,'35aAdKmQڕ''"X:1MP6tҿIF4޿6k4+]v86 CQ_+8J uO8#5OuKmFV:.dJ֖\67Sa+O^UMa5Z*BJz][^2iUH#ĥ9ӆmR;j5 N]j@ӯB!chkXW_'KU*V5xF+i;c۹~U,d2`jm' oBg$RWm h҉(bZkKɄ@do*b޽x~h $r\;]v<*B:U ثsV 0t萘X"g9-pwuWR|bރ,],َ;TJUsss.+ISE;t=&22'r9A]ɕ!S,roܷYꙛ+sl"F>]cB4mpi+Ԡb XTtslENM{90ScZMdQ5/ɓrxdgmTFYk&QKud~LƁN_;1jr|SP~@Ʉ$#5 ij[ ¤ @dUUs^2}PhQYO$n[`PS~Zȏ5C5XMEkÌ}PnNmmi(Q6K'5 r&߳H7zdΛ_5m0љjTѢq3GF/pķώ5RRe޾{{6RjXO/ jqJj՛Wg4[,H:;8͙4ԮQkMfs~븻M=9&.(NiҨSNar|17nLsP6h2Xԡ]$ F޺ԡs #(6wdWM m+f9E̓ብ5>N eIb%w2ͻK~ Mvjbєw-x ]h?z-ȴ\!e ~@kIQD׫uլY{^{zĒ?m=L886w7//]tsӍ;׍J7^m]GHT޼گa&4Է߆6rbQ}-p WV>=>q&oմծ 崡*zV$)i)^>h4ĬۦeK "ú=)Y#ru}߾=ÿq~#C%\\ ԃ՝B?\Yuh!8UGJ7fQ e&(uf*^~jjv-ԴǺ9LѮqѣFM?rwݿ}f1 *LP'0 ʯCzf9߸cT&]:Ѭa|ؕJ &ٜI\GyS:~[ n}3L(cj(;*p661z̢g753+SY͹f jKrۏụNc~ M<ёR9E;pzE^{O\ӯQRj\8i8g5"֨_EyVjj_VtTR.tzfc 'QAMvΆ.߹Ь^+2esOX17N/61+g 9{Uyt/5m}*}9++[35dsy{̉ZэcR*h؁Gj*NNצj1W`;J}k]}lᡗ?K{Wӗ}lHN X,_E^H%BƮ#©&ʬf7nЄUiPǹ8w #H;ӵw`@{=_8nܹǡFʿ_;+byyہ m AMrKkZfBgnDd֨Jnܿbg54YMEÀݝɴJyx'Vf0ӆZRvVTd]/$* X ~dKז=|kSDowbM׎\=t+PMȔ;|Pu.3rl]!^jLR-'>8W2ҨեZaZNĒVc#JiNq08m}j*)965o>vIm cˆ7'G?ĉLHe*8=:vp5]P@ B>/Op9DŽEGUvvp-p5*~b&oFA=H$/٠+7 #z#=;Ѝb CL#qy8TAMCI̽o&z89 U^zkr%K \Tq_*.KؾijkoR ƅ 3mUⰹT:SYR֖6TZz{|lM6c3bnȊRl,mwuv;l[rzyGQ|ܥF j TׂbG_۫bA{/ @RI/m7TBrϗ33lnwO~/.<3i?*ڐsmr'AmY?`:8f/"qQVڊ wKn\{i~ˣ b 2}(7W}ܭ?sェ%r6$p%iiZNJQZ^+#k(8 O&LiuOGF֘Ξ~sTx;&<4yΚ6k ^|:'p9Q\!ڱj7zU\!=NKRz"sINOԖFF˔`Tu nhw5ז{fք7.uT P6Ʌϲ1o}rwkԭ;6k CI!& b>]¯}w9l]ZBQ>ao03w\injl̄ 5@`B뉱ڍ!i CQtͧ/sn'EԍLMY? B!w NTW%Sj8G5?.KMIiP8tպ#[N,]}GAQ;QKUj1C7J⋎DFUu;-_{g^a-_^U_79tݷs*",<@0N@/. jN]̞6hzƼ>/˖鍯xu(-| OJ劏=wl,j IDATB8OQJ" /erYi6hЉ\WPQvZL6PYD\/в vdmHd`.n1mrfhwŰzzPS bUg`(O֮;'wKESƾ78h lOzC2 cG]7>q{yjNtd;plFgo @Дy* 'p6& 3Fnj2:OoQ#6Zۺje;l0W64ըPٮna'KfIwV_r?z֤AC}ȓk [_=_k& =Ð1]QMC蔅Ϻ9m%Cs¡ ]'~9A3wzq.$jyC}ĩ8yFK];;:i-\[ rtt f=F&ݛ:mEk 0`{Oq Eu_aZLou|t{NAgMIWye4KIIO=7LM4Sl!F۾jۨS%7ղn_L͏OLzIDM}3zE#:0 nX&*`z?\@a.WR\v5H$ϽI37=ٕIdK~c~c|sݹO,xfV~Lw_cO;k=_K|~Rx3f_<{xE@-Ѕ?ΪzG=.c4X$ HʹzKXN v0N?" c̾7]ھ O0Ts}SB߯Y}Ϟo{^p/}[p3RZyo^"mu3 S3X[Z,irR?y=+u;(%ښC6$Jlեx'%{F(W-dcuI>tK9Bmyߟ>bƭ|0_ȕR*,.Qx_9u_la== 2w`KFH.Zi#eJn=mE^Rmy`+S0m>î7]NBeM.' xGbc{Y,f6^&)hPgO{7L ק_ee`)tڀؑ'H+e/?~}^TD"jY6Z^YΗ&"I_fZ׾Üw_~C?x|u:I*V>|kP C yuFCKa?0FS~vbd<@ëk{7L4BRRx.HKb(b0(7<)PcOe~"mʛ)';tk($pnѭi)<׶Ndn7} &`Ww|0,3L4H/USTI]vQn-coL{w]ZF}KJMkis|tyOZ&qaC'fa>qlY %yo~rymjӟ___AcP )56)>[V~gMw(nxQç|U_?RPd1֮Vo FtOX|?Lb*9{x[{E2k?$A*WiB;rwR.AB$8~H,IdڐHs{XQO??x7?Td|Ξ;m5+~0vR  2qJvwBoF_s}"JM|%oepׁzDgdhѯ~fl^<.z gòʈ0qY߽ d$.4-z?D?ąDLPϊ0īKVMs/[mY1yȸbYoMuUw y*D?D ipmw~Xf.bՙ?֡yňՎ /79/D J~=5b JW jN›/[;?vIGTA%iٛ=w>EqWݿ0vysMg}U~{$ТOJdbrXmʒ{>]$s De A.'$oہM_{yŦ**nj5WHosc&ߡ+0 sڭřGv6xs)E2|/_yԇ%ػ}մ_HH) X͆s'/E7AR.c6|hʼ%x4_xhmʕ8Cf`6=c'׮6ՅifapH,5jy+7]}fӚ|g|]H$1tLSgO;ngϾԒxVk FÙ3mlg?ݲ֝E$&Bq$ߛCN&s}ٶ=.}<+Rfh2kSS?煉^7WO^K.;:c]ye>c>U~0[n%n3*Zjӎ|LhY2-ӻjP8:c&+8Dr_^bmQ`UZrj*xr=Hڋ͎faؗg; tp2rYLYЌW;(0`rQ{6O 49BQMӞ@4dwdם}&yGx ^ΓI2O]gi*-^yQ݁^z[+x>vRg)hבqwz܁&';;n<&n^ ϗ؞CMN"Zl\͍,|m4{gZDoOνhZI&/SVB?ihy%;ZM,|RuՔ0xT=< $F, ȳ9r.P RI&i C3㤍*# ! IL G|7E;Qc;"pG/!mhe5v4-@Ka"C!ĥě-?SF;Ů:40E) u<~Dڝn-JTIj" ${sEA.qYM Һt`< #])5[?11LtqS+r']ɠXĎMSVAKňZɄ2=l Qv~r#hQ)BRL\=|;G+b>X y&|SwE{w`rtc¯oc/= ⫁diJI@x[ k&0h:Cm`} pCM7/h :W$kҊ2.f l  aT*<ﲝ+ӌi]]E!K>BIudSIѕ`Q#q1UL'etdj?wZr#\8#&5zX:)lC_$&ЃU3Mc[xcf=z [FjЦu v}rg/_ftQ}E`w8hۅ&oyǃt#;/٫]sk ]u5 <"ui4k3W2({0`9dmtE[ţhqg4h~3c-MMrʝcvwUaʻTwjm2SJXzf&AI߰ [>| Խz?F:0^2ܚx"WWR@SZ_DP"YS_j5[ҙ.6xu)~-ˆ;&a#KqLjNY>$}(`@oCK8E >FLg(M ⳩.)TcTOOcuuݪ #ت]2&<ޔNcфόJ͎$oU;_6yWSΧil6hUj}G?7t^+t_C*ItqnSvEC//4X/W\UReP~=cCzEh},fX7uE.}9ci&"Vb|AQT"+KŹ`4ЅGEe}xo(vyuwo:<>yw~sK>_\^[]g Ƅ H ꢪ)ٺ q 5&'cG϶3JV[ryEb<"]Ne=?7wq*3uzDӯq@܆9ɏ(JM*`aI3݆{@Q?n#$ZUe7Dӯ~^SE;5W/"pdddΞCsDCވH;ed#UhI"1L8z@lŶ`F͟Q!hokxPjsO25ղ%Ֆ5;wԷ7w4Lݙ WNs<1%}Z7L &Yo>(>5#"ʮ)8x:nHe|RgrӰhLn{RsS̙>Z" ;zf͎4<3$OgW,1ڡdnsNq @꾫J?iO͑aܮCF VjNc$ 0msJeBVH:~xWS&ʼuuYR&/1.!!+(ř͋RP*o'qڥ]Ԅfx5q4;̄S7G4*:2htWu^w7_x kGBә?9`uO;K;#ގ$ ֓JWl9qÈ3&xg45J$8@#v*6?PR GlTPcyq]I>&ӛNg/Kg|gK%*8jIc۟/n$AdxwExoӮNk0Qrp^5PKpvtNɳT*dw›{$v,Ĝͯ`Ҙ8x܍2Hh?$ Cc,dw:~#䂝 aI9@r ьތ=Eמ@/cyյhzD&xU54t^Ǚ^nQ`j2b_|͇J:>bXGeҝ5!↧ "Vl\iň> EsGYVVchnZQTٌ|ϢF j.U5ҟZ7jGhffIKK9B4 B"}4V3C4P}=Sċ#R ӯ5i TZC|g'㯁AA[cφ. nZ/M5h9 ja2HM>a7)T=d9,9t!>4Q4 9b;abI'RZ^V4IĨ՞Y{ЩLr˔^ܹbvWWn1G27Ri0Pvަ-yz'뾯Jb~*K3+{򷑱yJl͂i1%.{jRP}+E%DۍX<]NfAWG" 7"]Q)[Ȣv&PIbm6"(&Cye[7&4ȟa⊝Gٳ8=$)އ"6_-Ԫk(ٺ8e`WڄahZc#'s",DEax@+-vkʤK0>b6_o𐱼-"XǗ%qz=/}_'μFn[K]9¦ݩM Iѧr9Ev ʝS 1oL*՛ggU}OHI58E) ~ڏ*u? IDATKK_@Sj i%^V+QZ[ =:V5:Z^E|#_Q -s: olp8lZetFӼwSc{GSW=[j"NjC +Mb4N#LA:!;aBƊ&Uҳ|:.تR  hv>8LξQBX =|қBMo['S׍TalE'ΐO?4-1&10l\sj(r9RˋFǶ}Hżؾ-&n =*/?j1.խs%yQS7Ua^A l:Tsv_҈Ne {JOl?=2wh֓^M=~SGs̽p =LuoڭSGuo$X,Wܨ"Du=d6Ұ<S4 EÔjo^*rvXDɓKm+:EWFfKuk ͈8\B(gú+Rbll=a\ǿ׶xåw0ƕ%!;ao? mU]3-"hΑ"4߉fBvC+,Hؼ jJNS,oOnQ%&np3j܈~1MFkɪ(D3iVfJ EMFk~^yRY||)NP*>ÞrsFPGޞ{1o^W6f uˆ\h$đ|cڡn[K%&2xuJu0zJ@;b=Ć-4J{xqOFM-! )vSO(Ŝ{'>y`]5ũ\VTΥKpU 'EL="eaBE7D^$s4R~e.T/u{ʢ;]ruO3S<&o2"EM%AicO6,0R6<{T~׉h'wVJg@fWsM&rS\Sҕ) Y[nxyp &󈚨JSR9OnxT9ُg%ɝvW&'c }h7i8 e -MM0񉨩>|QC&}"= =ci0_;f0.:p:Ǹ5D+UT*Np:ۙ\DZ&~ }s&Q4mk3,:?E-@.2;q~2s9"d>7peV܅x*nLzGІc8N&fn&{hO/2,tۂuJgg”M=oہίTHRQln$a<{#L;UP\ѯwokRY~XRCr^=YQ^Bhꗟ*a~#Yv~r:phlq ۲=״j| vRi{ ):VZߎ c# &qsR=^1mHUb:#f7SW 5o&ݿEaQSHPR`Ufs&eCÜ F67? l쏍j`)}q9^-à6Esux/nߺ~J8_o?fۗ7GJ?I[6n6^^Otc@ݔJOV4D1ߔaÔqC"5rڣY>8Q4hit/NB4z{,WZlBZ3RQ\&9 voq)u/v V[.aЮYIVG^D+(z[F/[:R(j4N,5)fq %PGӢ /4SИ~ѳg9S(u ~FY_(#Ychup u BDw :2Ht)w=bZVSb.{<~@o5rItg MqN&.԰wlR!b*>fh#26-tl82i 2u;#5v8ZU<̟ӟgwsERλ\r&)-<?v驪 d*[,<ij5>(!\y. ZN!Ȉ[V¡vUC ~K+j_S. b_ 'PQY]W]kpT rf* C3 )ˉ7L3Fܑ c+>g kn q\E%$}X$A1 wضKBPaT:!|eh\`\n8j+BZ @Id1PZ 4 2z0uZ.yhQab(;m2!e&J~ebN`W5ܦcN͌ TK=tw!$ػ JM]DUS|s?H9'܊& Nhxj|O.f·{-w== \n[I JV*Wc8n(1 hW)Ƕs|l\T۽j]y;J li(֗9yoT~SVZLhXRF&_a{~TJNb:I"p •7iOy_/w& TQwaUQscm+L[툋~CQ,xFb\@PKhdˢڵдYTҞrvcM@(G1 Fָy;cXk "T5v{_,QșaZ R/ƶczѽZ͎[`"Cw=K2ѻ&}Zα[cF\΅niSu9\FdDuKa ܸy#U=Md5#(rP@$Nb'P):YePt` AWߕݶ`#>:T*&N2l~Xp# WLO.d4[w<}yk37ڝ.Juz \,!dOﰇ\q#?")34adLE!xRWxio1Zc TWSV}{DŽ.ya5]!I-aJIOe_c0֩>Ў:ǪZl..$'ǎ̦1 _ }a{Ɏ]${![/`ۿ7r9t8t2uZq (a#_>Qt:uqW]GRX{ǙEeIJyQ*}%'D>=$v{eQZ G ! j|OyM!S$bѬkW F\T DMkJ[K栨ʹY rZT4gN(r9.u&euAxDWd#.b(zdƛN(ƕ>Y5#JDMJ/&`?#mɴX 9n `Fe^Sboe/bS 0Ez@ /`?qb{,j**ElDvȕ^ [Og9sst* ;sR!iFF2q$e*;CSۼ("a?5kWy=0"_QI)ZTP6F,̍m 1"blrYeO3dpg>1Jy~[&[cg}[QYɏO;(#{:}$yOI)׈^Ndٳ)2J8q0Id>?jMxQ;ĥgzDM Q^R325zD"ȴ|w?4ܶU[7Լ<18f iC [m?46+޶%7]Bdb;}] {FfOF,`u0(Tt߉%8pxXlD .Grp䴄~fy}9VkPB%Uk#z`h1 ju3',SD"f3՜:lݧ9E'ogyUeeUE>iq4Sm.\&8W1>2}~@XDp96B|{l{Nsü'ta1lF(+mW;O#&JeJ\T5~Y{×(=gdH#0ORbPSWUgW79.jO2TD37gz_~-/[Q4Qlq~_վƭʼn\ܪVL,"&i׳i@lQ W&w(Ee⩽ûyJDp$D/lA(Bž[\_Q*FxR 5l n'\9ٚ-Ek;1tҕ۩wc{[E>L޶CV+-EwOEȕa+NNB鐻w5ӁʽZ]ψzPnaD]] a$n=e:{MyDX\TH';?ۯX@oa:s\IfsTe:]NFGv9iuz2pSǪth % ^v TcGsγI ܌Ẕ%Zl bzq?z u*ӫji‚jh؞#\L5o܉mݏ&=Ѩ|s U 3=^)TBW]V=f7pFPG0!p:u5P`2z쏍\wKAJ  Jm0>L%qOTs/foXRc']nESTSpCZDMW%!}߿ ~V[Th¨)߬x-wťLyl.R1F Т^~`kX*g7M@h|K[xx_Fkз/-(HBvẒNC3m?n~9 Nsw:;ms2 h}]mU 6l1A2OGMP TNH"QLB4}뷻2k= jxS>cHnpB $Sw3.ZUvQUQ`JAL֍fRS>xH,x*Jt:cx~B׺.Hʸ!\FrSyz`RbT5]luEV^ _ogb"bNkEMtb&uӝ\8~衉>x DWHi$&.+ Ɉl֑q+ 5Wx/vDiR~l[?@}Iv9Y n;/MUhߞ\EDT␁=ӫff[-T]dCÜ7ɒFg3i\k'2Qy{IO{^RIM4tR=k9rQt}ZuV )C^5>5Ee-bZ 'B4@]6x%:S>Uݺ1m961+UDJGxVFP6UGK>l VX*ī]a" Sﱄs յ7X);gdUȻ K dD5m;|$6H8S^U-id)LR^Lm apK`& EJ8+/q #4gctb۔d,D.we+ PA?YgҍI/{Sg(oKf" he RZ"~Xez%m<-[RKEH.ܥdԼ,h5+_FEF vwF5b$R)=ej w~=Γ'A]$DF%N!̎ VsZODMW{;ϮjL:lz _Ke[QSRB[S -gʽ6sؤy!SE%r>_4 y}*SjBbzMyM߼âڵ݄1K~?Tk|9.0K.6tϋ=ـը_ի|ņqI#03~\e [$HpQU%MvH6^ IDAT[v|S%[zhvHAҵt~*H}'PP/. G2 +ejaWф!T.ldIW^7&\UFwؙj'G~1NplE$F(Ro+T˾iM#q_/m f_ 썞L# J f:]T^Qy\TȚ'qu>P̖WsGtpC635ɫO?VVl8lQ;X:F4xȤ} 2,Զqɶ]l,TW=c v$5: ojSGtS'? (~([Vuق xM)iܴcBq8ݗ&Wّ*~NtLa!RbS#zBLkZ iڮ4 ގtg 0(uQq3CiN\H`-喉w@ F&O [/|8g;~l_]t@o/5$~D[c(⥏NqVXzxopX|byF ~x{i;Ԯg>&tuviЮ8 .}-qdnEKuQ7/e3s_CA1g7@&O;ULt5zH N╥ +ZS*΂r}eU]2~\fFRhq9V+8ڑ5܂&[ % }@G8l 1]9z?術hYE}|oG!K"-z;UrN"pؘ]># ,_S4^*V ;HTF6]O#f ì 'CbiI-@K Ed/(*Ke dAjƽtPJ-zs=y\`&gaSG9+'EOm"ԄUF Ѩ8x}G5;:f )FewV>32kGsf6._y|Ua;DGTm+vH"Q('unޖ5&]B1S[q7y_ւwvs |y͸wmƾ_mh% B=W3s޿{o³z4Ӫ , r3 SëZߊWWtWքͮ[5/TCK5l>޵G,UqƀݍVZrm(;W,I9B5;o hŕ*;[;H,Z;G6k.vBp>/MPr2wo9ukV}*YK6乓G rF:nm|bPA_~tGL6ٓgu)/}<"a/PYsƭ^$<3½׌S;2AM &yE y6{6r"%r `ZjNH>mvc|)kg`­RYu3;{_ ӧCOM|l -5aT|R7$UJI% W{i].,Q[+Bw Q$!eC2R&G/5D/W(*gMgw; 7%yDU(ZqsNYCd] +6P9MPSbу, O]!AlnlݾjE"NB&1'nf:I<kֵ{sMQ3~t֓°a}ruAl*E6kqr37Qp_iQ*]lٿ7(U~='O]6}f1 CVauDSSvMIVepI"9y E$sѥm/'օo%۴ dzS=E_mDW(. yVٓJjY*#gMl.}DN#{5#!|Bka8~:J|ùd?ᐩ!ƛv-c7$;݌<?LY*ˈLz䭇T_0B,"*ea$.jDjOR_E篐܇)ޣEu fPH_3;³[/gdD/ F֨Ӓލfk]t㾍Ԗ}Jx뾘)#6t r 2f6yv{ڂUY:m,>OҨ5lP0Ξ ZSװŇtDR]_OY9m|sF[TQ|uw2D'8mܱZڡwW?7š3B1;99j.㤅cK$j.^_N2-=~~9tw(EP!5+n):@Ӡ+b@u>;JyN, <$v暫Fe )hûcvǔ8G@Nl"39AMơjm0/&Ԏ|.6GҲ떿AV3Ԇjo c3 f-5]0Ṱ[v-v0a`]jGii|_/ߟ41=cT%/ 13g6Sz#. rqr Er<+;ȉ#3>)Wʫ5\\fM)5ir>|g-xyL0%9A8ߟTX,gT7870x5ڟvov3% ,HTtng?qyzuE.y>f$*#H=̵$ wa-kiWLfb˖:7 z8x%g|a}+g.h*^;?5 s/g" QkIsduIGmar1Ӏ+o[:'TuwG[[o ;)1]/C3.yR =K0Joلp1Yq920+OyUHq Un B09lŪ@fՓIϱžYK`a\Wi$S:TBhz.) okSGySuS!Sv} o[E\&cl +}~0LɋF\$$*lza}'y7Nފ]rYU P]}eZZ,qɜy_sm )zP;6t$GUiSIG8)`{Da!+ޡ orc9Η}D)Y"?|\ER}; T|9 WDz/rzFg-eGK=E]tkZWE݌珖.m^/6>ՈYIFak4pPe ,V*݄!HY6==mo[w7˧߯צ.>$Ly}^ӮuɑCG YYrAjgHw`-.e3]N E;s<Iҁ1YT]ɎB p y{.Ơ&~=]Ϯ5j爀_xۻJ,PF'Yt]RWm9\䠮ͪx<5l׃/>z")SdBZŪ"iNZFT[cޱ TԤi?췋:-ܫ"swi:mmubUdoá)Tгk _/"fBZmZ_&Z5zkIUT)9Wkz-UGusS#2?$QZ3_?V5fpIoAխZ:?Mu~7 j3Cx^3yA:okݼ %Tjj甾mNݜ(g2!&M2t)&ԉS qM2rA%S3o'VL؟J0UBԌePSh 1rrPQ97Tb OGT8r2)!R!mK͹&}]׶Z\K_X꟪bvH521iMTS-g7,U}>؟eʊruAH^Edr͑pg\ }, rJ|11[$LAn MUQ-zzQJY*4tOfd"$"Ɏ ԇ=vx+G7au"y^ lQۦumSF^" 7=&x6h5 lr۽4Mwu_\Xh: 3KHƜ􋽱k|퍳~M}9qRQmaݯ3o7nn^rqO,ɫJC־}`깖+viݜK;j 7Ӿu1uTk}k.i9ZZVj@)_ӟ[yﵫǸOl#,l 7Ӝ۬CWǓ$yFaOHR֘JVi(jt~_ 2<ͩ;7^vYt衙 gVdEsDS)F39# DQæj,DT3#rW}xokc[ϣE+7mB$9k۷igMH]Mm^>&F;j؆eo/ES7EŚJ˄ P@ׯ+hgOoq 5%9<^(>%j*a$΂KLhqb󸌗+PUz'V:z nN)lg+ѱ3V2]夶:x]Wj@r&J*'s)#C"}܂QUh緗ItkU75Mܲ/A^O`ATV&h ۽BxVN]uJ:Mr7RFi]Ao4\Qpn\ݪ]567J\>W=:u̧+', sښ_>o߿:1wN;s鵬 2ܺImSwFeSY^2Gc=u1">yuӶAM9Tb=Ș]?iZڔޝû;W62\}88F2 |wP"JÜ7ot#54]bU ?]8|CեmVK,MK}'7=K'M*T}^0\*UM}etT:귝Y.0O̟?sEKӸ?h֍?T|Ζoߺ}ytj4dJ5Y+7Tf)Ȱs;yQ˥ } jHhz1ZP-SX5z\uHcc! eC6Nf&;u@^aۣ_sGo(,"UcꭤE>El]JO$ 93I]_/*]#/[۠I[̃qeqEtz2>mkҚhZJHDDqB,>J$+ngAMώV}47Wzݱ^cfvKكHs3ޭT9eWѴ]pێvNN9v)OSgW4#TIDېQźIOJ;qHEru4NY[HD4"VjkOi`Hf#; |e螣;?Z^!YNl;k&Q=yP"7gQٻBt5Yl_16ao2t5ZaQ/#VSLU|aT62sDS1˯?AR49zc3^Νׁ5&*U PGϯ09^Ʌ+[`-"79{z_a4.yHsv[{\Rn"-ZzΣw,Jt$]xS]|1Wg˟C$}.ܺINnnՈՍDl&O7ע _!R3տEv`5Ujن/_t[yv(p>zzݼ;hР'AV/\Erzj`??(YP9]QօOG;7vLLU}|ΗWp-9-I{\I_"K>X<~X7EIwyüW 2e@,9{v:iתY>X(Js ˻=v s3hԪ'E> Uu2m;eE/_c@qWEǀ2;<(8EQ& qI|#;8yzej>Џu2{LH!>aڊ jloybVډK7}UPD45Ԓ \RUdPVbIJ0B9HV^_=]|s݄kcgvΕW2?qQ?߀R!)Yæ辣YAވl΂5Z8wO~cش,ݻjiFXem"xyfc丏ԪÌHMWkfVl"rс B+S_RK=G},Յ#5)/RE_}tL&3&~~s%Vmy_|NFսS&AM$l~~݆tTV5 RXt6QpX7;)jckEx!2xC5]rE9m)[9{gtNNNjjh^ 5Q6R{sZne~W#؄aF6TﵞW2(ŧXT>up/yF@P@f.4+Z(kj+4gV 5mմGq Ubsm`Nd$%Mϕ/_vk4jEQtvê%Imd9hREed~{V~̯ӆϷE5oo DzAMI _$)Pd3"óH{`""GDN*V؏0̥cm?Rq QU 2G㊧v-swل_/tx^o9H]]h*ԔeL^e7j *JTܜ̌wn֝&+?N~]vlNǨiu ɣhb O3UjXmQ<ͳ9yh;ַH+q!of^ڧ2G{HFG{KOT{mX,Jbڭڲc=g=ލٵN0qt;ֲ#=23>^QƴZ&*.zy545Z-_ ̖vQn|=J8|+I3C.(]8Hl g\dnL\?sIJ mYzgK,o*"jhJ)EoKEVڅr+go\fΛ6mÔ5הX_twuoָٹBP@y}L}i^kXjܸ\kNM5WwRoeK%qU.\:tlk쐙E e?wotAMU4G)X2Tc~Ujx7X(j̠ ,m^;:rؿƱJ/uqr}\:ejri;W4᛬ > Y9E݃4 k!7oT 8tw/EM4w]}<+ gN4M<_Qܸ}x8Op= k7l18ڲG~M7} f~b觯~! Oٱj&2!IҜ#WtEN{ֵۧ^*'/]jQ_8xY 1'++OZt`K_mWpxHwTq'k~fe".`Wͳ~izpA%V =ݼ{9_WN 6p1HrZJ{;&]}(nmhyYi4#9"c[9w~u$EwY2gODj8JGR=zE$qp_Ijg/?0RV?^oX9|D r \wcwwPxb-f<0&zV#6v2'ĺP8oꞶ yvֈ)f߬Fa34*??;E^/q |⹋\^ "'GiߔzJZ11u4ZC"T7=yď2X0s} {5f]4ݬےxoFzf:){v/2l}V.X 4xĪEhNݜlkuv2S wۅz/̳vڅ E@ AP@k94M m۷ܵIӜ|yvjfܟWvwܼlݽR79|vzkܶa=m$"k.+Wg夝oMU;C?Pq՟?00M1Qb֜0b\ݘ_b;\˲pjObW7/*|sqg?a^-/[Y<.DWO](qYZ%.T奖]NM:X[\qVo30r +@u(g۳&/@EJW)}IDr/'JW1GϬ/YFk/gX^-o^Zꉹ1r tE% .M]jjuMO{EX#ܥƒWkYD'A くu355Dd[9G.;'[]PcCɳIqg:2 tɁl{^gcu%WzŢ@ 7I&Aveqh G #zzK[l#0*vwPum<1WbG1?Լhd'o;VWqƘ"> f _aQ{#qNHMϓ q*ך kh<>q6LovUl>gH҂ yht2er5f;bZNıB ~3Y7Xo9҉)6so}H9}sm=,9Oy#T~P2\ͬh觻 m $Ƞhr12 uu8%4H׶fD62D9G^GZb77ЇÃۄzѴ>rs/ IDATk1THLgz)C:r.q|=AcVPT0ُ)3 5/k]̳yĝjb~=MMD61ކuړ/_9Ol(<ۮl}W7$K ~ ] ! 9/GwZpIjfy͍(j=}kA<[GW^mHEԴ*&)|s %F#f:˷Sr {Xԅ ˬL$vC\@]&~v^F,nT_Z1pCHeiv"Iaڈ8ѓۉ]V}}2=6B$UI7RCT P2)!xL@ k!c#$DWȈcɈrwy%1Ѕ [W> u}xix++Ɗ xk/%3.y7>OWր"sjvE=lޞFĥk$JZɔ gX9Te A1U|B i u hC,JM6`duNg]@x'PBsyyyx'c8J"FaՊ|"_[vpϠ+цb;[:px|ynfNzVݽ%PAk:{.9"Y'o~jI6  vaφ쬙'f([FIqɰTϠ>5uj3wt2W,ĐxM\yZB$HYވ7CѦoy@N.#[z܊M9fe(SPW&g%jb AO0 KSUq?҉w2%nȱ o]P$oNA|X>cHktGB<}H@kˈ&K_MhjٗL҄؉iġi0qՅkہgl:0F]KKjYc 2W mƨ촔!2Mj4tW^ϫ'e$ݾwlsEl8?xrmcGƈ&^WR4ۢ>c^/~u]v;uZrZ:tہ7/خEN$2S>s oJw9Y) _MfZa<ɿ6Ӯel'?;mƜ^=e .jֱ+oecR7>xɗP6t1n6F42_nʂ\Ck8o |tn)!}.xkم {eVwۜ4UeL8]{k"COL9F괎z})iv(8A( k֠Ly[C[NS@HM;æ7fsVLggN?Dfc/ y +6}?̫tIQ/Y]qK~kvtJV23}a<=2SYj_gFfhNߌx>Z9o/d4EwϙUܝH(1'ߞHNcFh$(y{_G7Voix֮Nb)E[/[kKCZ;ٹBMaZ%Ru_̔B:JD܂2ٽ̈́aCrsN=4jo.~g4,)I쭅;e~ k MіnHJhr.9-W?|uw]R,O,qڸcICv~4neX(f'';VM#ۅupl"D-˜# A"8eV3_z_/o4.#b7բ<{Ev-8KyEqn^n>.jsJV}w){3_2뉫4g{?5 1F4'r45#MLlaKJD4#~⇌VXF4Yqy~w%G?Xˈ.m5N-:fL<tg_NT#f}'3uJƄP,-Ho Ju\ڮnݽߤ/kSsyoě7Q8;yZK(M"aa$\_~%B;=j(?V+VR/Ow o^w_zZ~]7єsgN\qUTj̙6 *AG|бFԭ|Of3WNt}Y{["N{G}ccoӬ(8O˓8;&'MaoDADqឭۿV[muoemk[;lm:j^(7wrw !@}3瞐s_AhBͮ-UḯPL&CHJ }Tfç %c[Mo9lG/ms/L4.6сȐ> EK*nGn8q4ʼ~Yϝkr$B)#9F ^SVQCXo#ry/{/HX-zG$X97U}\jIJ$jʚ >W'*7żv79B$ SN{Hkfe񙿾kS $ժc÷Y{h^:&SAV4$1ݯvo ٚ3mQܠq6f'(j+#2X"ՠUڸ l1\ͺZP\b2vuʪJCΊrR>׺ Nr|ßMU4b/"ROe Q'@@~&ytT{q_LdZ'.|`sT໯7u’g_ p_Aq8@^bkspOTs$Z/8} 3ܨaٝ/Bݜ7Y9:Uy#}׼}<{ޠy,fk|S}8KxŜtO NcĄ'dkTCer޶=lQ'm/?Yg]5"Q_\pbdG`Js/};{ҽ%:rr&6OڅRl7w-S7c*uHsD=gLξp$nuI_:CT~. ;YʽO0zDž-^Q 6cl#'.py[ VTWffg>7sȾ8yNqylV5?iZV>}C׬{ׯ([JFCd;ےg,[X" ts@F^i]_fg\I8F`DZ+w\lIR(- m-4MbhW?{AgT]Boxuő aE>sg'-56Es+:ѧLڀQ΢&ZSܸg0}f(j1Vl48byf%˗U{N:Y1h6mq]8Ft҅:;(C17/ *-˾p_)/FYN{Qɰ*s;%L8`[ 2zA&*3e^ DЛ0*$FQ˯;~teKG:QۮSk c<fҙ<ْ9Ko1JѤULPL E}?~ڑ9aq Ůf6jp %#T:չdjLqud67Tw&O~8r}p3Ljq)OڵnŃnSэG϶ }i.rm̅3?]by@bEgmu;k^jr9;2WChcܶpB-3v7?Qt ^JSl4M$t`N]>G ̚7V/dҸ$4:eVŸs'/1/n۷9/'ǧG쒡Gm>Qɶߠg/\#GW:Euum/;FY93zB>OȤghK)?#L|^r\Ƈ\OR\N %{V=},/zu"&-|ȩt&z⽛U23q]Z3iKR&nrަ2PSLnP,kE=5=]P4Uf?a°I*,\GhdYb Dk i 6&!υk3RSԡy{:<ֵͅ q9V?i4ıtcZn?k0 ~ymPRGK skF~bR>WםBْYغ]cRF8&*ʤYp{}멵QqA`hrڪ6W+B@(2zXChͥ/,5‚Cm1-xh攙{yghöUAx"& jHr8-q pX|GЙJ}MЯy Cfqُ.x q_ Hk&)U,xiw-$ qDgL|fWlJ2r#Dwʨ=NI,bAT@Jǧ=yQߩ-5ؒfOMF7vt)K^grxD9'm%Žk D2]?dࣩC@&]"l?q|R]۟CK=6y y\d"@ltN[怨 /ZŧR CjV(z~y#ݫ Zةb~Jx IDATG`vj]!j[ލ؛{^4bڼ>8D|f7}6AJ],&BD1Y\bz9qUY[Rx\};Y`qWGj8C]7;'ܻͤQ"z3ټ{kt>8ĢZ]'.^u.Յ|aq^~w :E2ەk8 Q3) @QtguJFu/ 9Z#ݫ Z9]1l@KMrj: e Nɔښ~ynoW<0 2zAN&gIGmtnV+촼7jk~8~xȁcl,&{I^~˨װyBd2H&[HJ0wF~{7%ͷOqՎpqc "]?xΚD 4mާ>8@FjltlXRU`0h:NѨJEm]mn~^Go_67rX@OEQb2PNoxMگseUeJl1,D䫏L ];$26`sgKe3Ăp>NCW_.4x%t']YU<=>dFM1Qt_j#l0j9L@HGΠ PUÈ!fNx>>v_#uƘD'r 3Z8yW5/hQʤ|gږ/JBOyfUH&:Ԯ$g;g?W8GW i "ݖ&6[oY8/-7%czuy{%_Ly"Fg T{ss$yS+jX\uÚ@PtKZnf-UQ14Hߠ3X=6; Emy?Ӧ6uj)/K+1^~K-q<#$(X+NILq vI"}Nwtz-+ {7!<:WtpzN#Fk0W4:)q1)v!IqJ&q|V.*%C.mZ0;^j S0 ГPV|c]>3`@6&Gd֔0aLkJGX>9+:o0Ǣp9Kn8G>ṇgZ; x$^/[9* kqͳĢiJ7Vq|2j1pqZxUaBHvt鶪`՘I:Vް)\a_k\<p-uɺ % @<5@E:¾ [@Wr$$ߍ a(V(Jltfd<յe|u}S?5ƈGSv|FcAS!-7QuIOLѬ8 }gn$''kq?% 6L6 )i4"q$2 yǎXذg$"*ar~qնCDs 'OO"r:}ϓH"cScCFąEU?#TʘK-7FEZlVɲ{z0&Z:ʿbeo"gC7n<EV叶(kt7(_G-V/pb?b]κ4'8$ {UU ÷ut J!YI,>l /Uw=+1(j9yyOuGq.c=OZPVU7+5U&^=ffCg[q#ʯWD!Iu(lϯqx wd ?WVeǕ?;4iĀ'"RL=!j"q|d}e)-o nF*e+xKlʁI'AAwO1E%"T*_Ogmyn]9HǷ}?JّUDa,.:Ui_?Iv# z1 "δ(*-ҡmC<@ Ŋ4V-5 hmW˥V>}'pnӺ'<d0?YGjd~:(~ÊV|{숱Ͼ^SWCY~b?_A \R/>H&y,@ ^Q|E9, @}6 4*,Og.?uQ; IbS'@+$[X7!A~,&"e6P}4SڴęEHD.ĄzH#1nB骆uR ؊}0 xWCXVAc&pV2 M-ub(f裌?x2$a֓ㅲZE_%+b~}:L&@ǤG;S$oQnw&JF>\J1OQtkPBwm4n_~p .GZ++Țs;*Ur ]p{Ho8~i'q<6wŨAS}~\FijBzo6i0wG?7h}o I' x(*u2eK{nyRFV_{Sw/򼅻o^\m+yW(S:G.xqloQDQSΑ_o,<(rژY,&REflc?JAPX\阔^۸-_۾KLUɷ||G5$U|_:,OҪd;ּk"E'0X EMsm:<) =p4i^?dGLuI5 D=51>xJc$N3i ( 98wO87l(UgzDhKJOn6Qcr%@nu P'{򕧾x0"83/G{^mׅ #<" 7ęO lQ(5yZ+@ nqZR Fh">F6׸[/.dknU֪qQS5vqڭҚ%EF.˿;n_/*tA qktaZ犇޼5j͐x }cn<{]*׮z~aw`06Q i&nSɔf8:C\xvT-^hdݳE'EkϤ^lق*U&a3^srTvRvNaLၾ"c";wLB+]1ܠ6b6TՆ E,G~Tn= iu,{1HVEz/޸.8PSuMu*SqIX~/y&Q<|ܒ@+ $-Qb~Nr:X Cg%Lk[:T(0TKEWK͗-Z,W `69IBM ؎lS{.ԛs3eF4&CDI)} hWSX,U) :cH 4&l(\(1g֢:܈b: ,JGXj*@G3ѮI!ORTyKԝ_v||eqQ?%75ֿHZߎ*_yVrG < _۫d4lW~Ψu)E&=}`8Ăh6 -_x/ogS@ዸldPk2Uoykb|F}jФ{"PLPL4῿y6e|DuIЩ }$!t&KVg݊ ¬;~Ly~,qFY{/[tʁq <}?mhYy, dsBtFQʊn\<+y-{3G߳GљeJ&-<.Kزq9gGqikϾ+h4MFZq#_;|K׺QSG$CSYlɤP)UZ,н~}]w/~ytDHZ𾏿l GRN_ʾUl0%"^䄨 9/gI"\2UvykcWfv/;Nf/>twEM>[%3 *4zc0ILĘ(ӶZyCo,Q+S}ь:2()^{h&/H:}{;>LZjnŖ' 3m =R]\Y#SI|3,@4$9WdPkQ˺YS[\m4Y<`ڰĥRG̍j{щG$!M_YuӉ?~id)UXYZ-Uh#EQqL~PÙUdJ=hޕ+7 K,&-_t\lBiyOgL^QŮ#W+eDK+ֹd:2aT瘊꺵ח5y3o 3}|W7jR;k7N8>&47NJ5K3o|lF}gMhSzncnӮ9N:Uʉ'1biܘ.@M2=̞$WΤtLƐ]$;U4Q]۰.Lf!jBՇI$}՘-LaN IVMךj. cE6+,ju*1Ĭ(G;\SHݭqYk{J-S%K/RB_l-͵]g?Mve?h<ݍMFLX]H9Yޟ2G5PuC>wIZ NaJM:Y_i(`z@_4YfmFXULYH1Sm[{Y/rBu}b hyRՕ&BLQZߺ"geE{8jE$jªne:mXrBiܠ>tԈa(^yC%U3T-(g(TfVtf␞ՊS \9ڼu2WyЋy.g 3QQ}i7ʪ6ʶݻ.#{qՓ52ۘOu "d!zlagr_]2<׮020 Vk޹b{JΆKMG9vFn:WaY1sH]~LWjlHa itZod軧tD/)&8hvu죓 5FG r]5ڌA&myx)k07os!N Ja|c 35S @& @ IDATHN]F/8U _ݝG䖯Í7IݼkgޒۤfN;70(ҳ޳dwzVkdfg{xg0 q>5g|I.9Y &3զNs8,ES'Ɲd9b,#1cv2!NdV4ĸQ(AZ呋2WdJ* TD)}#%>Ds K\!Fũ7;]T^:Y4r@#l~ZVh %5'gM82]UYD" Iberœe8aШ6ܲqZVAqD^`l 1o޾Bl-9$WV4]a݋QZ@_W]+;q%?g~ z <656*qBuב"U WmґR Mt'ضR\ˑ+J>/NnјWvZaQ9Ed K蔘99| $^wF_~Bcu?627sʉ6ouj1.H%.fm ͝< ΚEbg KzJMu$e(& a-Ao8¨xxwZ|@G*am+n;)aϱ{QI!JÖ#Ĵs561ۜ|\MCFIشCs /]#cUd54Z7Wқ:|#洈PSԱ)7+Q=}PkօŮh"MYN 朦#^&ӄƒ9fy)]J14fM&i.+,%;oYZVPdPkQz/m]YQ BNUڬVӪXE'j:9+nH&]^cDt 2ʥ ZЇ&*(\Ro#NeLըFhE&9m,zӗR~5Zc-j>F/d ,ZL'oܨ03_*'ںfiYe1{]-nXu/ֹ\JyGfx8b6JL!q}"X8*TUNl':2 VY{IWuR: hy~Uj0Z~jH4!W T aD ooX+-ꪳ}#8^T}ƚJ!8D~kkN4Pu6F5]3Ai~  W(e<=9]АKcRV b||w(zĪPXXl*sڞm2-,@4F,sN(A b;orTP'2Lz#6.@gCJd֠a/gocKxx)k0/Ս<C ֢k3FZ#Zrކ!nxf]zfq͖Dp؍{P kəLz|0[XohKvnh},Teќ-у{K]׉RƊBEJyzG:Od:=Jpl\BkQa-7+ns]6Jiܘ>4afWn3|PC# H[z?=ө{՟&3z6=Y09LZd!MĀO|&Hg4VHrp̓/a;s\FnESǥqzQV"jp5W1iycNKRbz̢^6Wa_'"MqU(ldXd4i>%+q{R`1qƒ⚶C&%72MB،:w Oy鬂j[kݰ1Ohc""pP} K,([P,jdh׈rĿ~n]SOιMŠc3*IEna^G4]u#%khgߢuxK|UL&f&obtq?6n65!gE{R-ba[qnٮgNi|1#^af9MQ͊R1B xT+( za 3 ]9N2&-ZoVs6d4-HPT; ?ǥTXU56Q!r=Td0$ pMHC,UZS"8XiHݼI;N>n?qh*̹{H 8)f`HBG4ZXSgc^oVXPh1.IlcXW8]dh0 'n="㲼V12^60XwhwP]B1EY`svd190]olMGD̅o"1*Y ?Vh!.af&4}(MlV5q!g7UB:WYoE>%EǐY:j/Kmt5X^}MzMIb]Y˥"~YD{?O-=d]oDL:IKRp,$8+(Bn`҃A<+NAF#ygEu$C8U'5ڪ&^j58j>b.'T} tr']}78ɥb"7|O"/[n`e6u1i,IY6 Uc詡Gbl~I7,XtJap\dbjr޽LM:8^La4DީL9]k'qSótjKmםQ4nE'dIr4ʏI/07Qͨ7+Ёgx>pޥt` 7d{a^osr*nЅ ;w$]ͭ~YD騍nZh_>@fr5Փ;88@ܖ"EjfߙnN5t@@`Ze5z+ÛHDNjy̩FuwZ=BY,&]"JյMu9t&w+n0Vqƺgc^R*'jݔ_&Utt1MM-J/_2,X2o؅%;mup(Ļa^+1U~lt`Hb;V?Ci>1 ˱01JJeBX1Mwd-ic<(ќֆ-1Ty}HLlMj߷#܀?22c-ZqT]y\- MZUx*ˬy5O36 ʊt#Sɡ)BۏB%Q(nTQܘG݂™g}xT[cRV_iiauX/jȝݗvL c+iT4:"aQUnjC_V &#I4}XfYQ/ |هӦ^!dҥ" KFZm£ٔI7>>C|(IatG|#F}R݄g106PH1MKɝtP&IANj(?Ot6 e6'W "âZC0 Fn]+˟ځX{kB`bmh [QUđM5V}"oqC샂Ť?y/~Gc~b.q$N?2,L;n+k ߎr1dsesJ;z&|g[j|Rc aաA~jw;*ԟNiuEoGyMFPZW"VS^6j}mR"z`hP;9[^0G{ ߱xi $mn|:cR,JO.E><⒟M̯"Ƽ0'+֗|p[L)i:E<.>b=QΜsMbDj*3t>*jb "2}qs,Y.1ǿ̘QԄ|z=yhLdA0+P#U 5Vinj2JwO|KiqWZk\|:Xg Ԛx~ xgn޳jh2=Vu=S.VFZ.lcr{pZ)&)03nZX' fir_e#u>,A!)ʮZT6G[ Ŝmn!UۼظƮtzN7hJ8=)l;r q434Ɉ:iTԸ̄kQ`z!P2%NjW1C~h"=7S+.1#8?d^"#ܗ!t0%Q_&&Xxj^0vo=5'3jJH<.Sv]ĭZ|M6CsaOU|r%&7(H)VXP=Qsj\da.EJM})sQ3[Pb4tzFg7Rsx`fcHq7 *jTF,1A(^ ^Yfx^YqďH!6(EM\V{ "7g]Ӆ#c`rL)6۫8jR"bTO^-yq)0/Mڈpps>ڈ&)Ȯlp z$ƨX sDMA2,F@ھȱE4:ai%FMIE5frO/LzY0:w8o}ZS(SE \X+W}6Cj+ 44V.M!~`p@BTltHs/azIv4m/lEEp/0?_AgK'f9"9WD f)i.>Ol=}޺iCMeѝ]xy2]}z  ŗϮSjwUOtj`t-7&".oXgCZ%L]@}Aw~!c'w,̵$K%"Q&Tjoq6QMΦ੩KTMv9ocAh$!qLu1jY+BVatɠ=Ҋ,6Z*Y\C=2o-cy쨰:+yWW\I$͘8x .hT Feҩ&ŤqX 6&eY K lQC ðͻNW+l& IԱxir!wYccAsa辟SRPi[LUiޫE.>b=^A/xrW`sִe ~ow`7.=*W1{{̠~t|jyS 0Xk`#m#Ǯ QftK)0 IDATxfq̈!>ٻts5ex#mdfй5J(TJ˦X(\ Ó"17VwL_:dkaꆧK tGnYuOneg  $FZb<$FBӞw*V]c-aQz;MH??ėMl:A%өdBB(DأD>;=٦P:Nܒ3gߛ5bžwѰ!p<6U )nSbYLS.**>?úo:6rVAO58L_icO7T"pHG:FN[ ?a_O.rX8{L74[Gv*zz 62Fy)Nt&Z4zwC̒b'&ٻ63oXޫHIzo,[^c;4;%ϥ\'N9_r].%/.FnmYU)RI bwA HQ &h}ggfwy1ONסS_g =ဥNn©]A=1V5USl!H3/kWLy꾡҆;nxҚ!?~,mpHwƎ~"XnlfsDnZj$޸u=7K:CӸylIpl$1.ֳj:C0XU_Xjza3CiM;;0sSl!)Ml.:~z]s{?>\rv2Ո,̥ݱ9WJr}$e5M5Y̷h gyK({;ԾCvq\ ~|L8t\ϼ/=]-OK!$R[{x.ugԡL dkϘ_D5Ef'B ԫ 6J[>uR]؆HˠbP[z잊S^OMH7:6j2LT[h7dLGKȷdwSWə_otXW_[Է3{{hJ-/yc䒄"ș*mu*C05Z,x.3mU ..)[hj'Pe|[&!xmLn[Y널blMOm%>_!P+Ɇ&_{>c\ O͞SA}j֨Įav__/? 9|л5d9i{ì9Pc@=r%\ί7K~^=Whty*C02!qb꫻xDX;VvI0?y6>8ZT^ܬd6_Dǁ2\EMS at^mJR-z;}r8iuQ PwdE JPRrq^PpIR}Q~ѥL><plQF\im硳5˖d{vdߌa`Y)1 5e&ū:4&J~s>l:{|VO }hPmzf`>?WZ7hd ricCZal^}=?.TnJ+ˑR!ݵpw}|苪Go[vŢ90yg; -DIMmzHBo)6Zme}K+Fy颌k5]B&N y+l$7L1 3J)lIkϖ7,a3G*e$X8<|_lS>Y_aJ*}Xݺ?{V]bl@gm`Q4[[/3Ylla/l{FvD"wCpܲfq}KOUO˪raRQ^gݺco83<5v6.d䇖U`wlj.OQMÙedܥ~`9~;KRWf$U W Qgl L}@B;ξj{os_HH0249Hz`HQlÎwGR=Ɗdq&ڦqL9]7#rD/T<>f${J1w;QiRIC6}0(FI^uY uN#! +)G}U 44}|(U,r}P\ S{3aj|OhJ(PzF]{mβgq+exD74&7ٴWdD{j&m1QO-檗* %Ў%1BUwRkI[91y͑Ǯ Z9K37D~52id^-ݴH(qNݹ۝t|H?ʝ8kx]]#]?\M7D_4i]ũ*^{@{i *58% ߮,NيO<+|~d2qJ$oD41 ypf6uS$bb{.ܷO 'mwgeF#z*C0qJ˱ ef2EˊYL}F=dr/<V )5Қa}9Yq wb3;>)F1&A(-Ͻs ikvojn˞/>8Xo[dRJtFۉrX(X!rn\߼{d R!ћl'dËXEL֪s5_ڵz˺%lR=Q+7tѡ[wM+ %163)G8D鰫%'SIeڱ(׃ĦՅGJ~IIIDZ~//,aS[.$FgpG-kpϐ5w{7ْv ]n{ӋcY&ꏙY)-vjҜ3[xpy򜤋R OdE, -fm&M%o?_/-xܽ>YþTq'کTHoMI*)> sl OP|n\ 0k;6Xߙ/=ϵDB*\M]AL^SO+q@(K;tY2Mi+,B.v`BB˖닳$G(Ώ?Ɋ {!):- ,HcuA56B(';#iZRl`l1IP7x\b+I%GݱWk\m0ycDD$ bCjwbO2,F5b1O';&lR%sn`Ъ&/} `jm鲀Gb`(P}O+MX:RtW")D%XxRkr;c;5zd IS^pĕy|bcɍjgT4c BMLpW~3ĕ oDb>*"M04hw)a4O!JZ iU,B@L|(`,]Px[|gճ0>VqZ.rx=~^LQRL2{AfHM(S#sR$ima+%TȽ}5qQZK;r-wS97]RD7n"LvR>wh TNHΔ_mikq2H4Çt1|CGWH>j,2̆7M䄛-D=zMuB A*Ly`H" gn+ѵ|I&kJYSBY=HD 41>K>RF:FHd+sB>q|m0>8ѱ%kWؼ/ר1>.A"hI bE<UWpfTKgcy$ϴfN6:?k#۬J3ba2Luahj̒sS"KTB ̨x~AcV»$> [  Ǐ #;_gUwHߞx_wx@Pqp3 AM0'=X5۠&!p ?1L{ek?zĭC'ޅG+y/*J)v8iG5.P9C[9zdPYU ZXHU;[mÖ%s:3#Wv#߮pmZhz*Zƕqfa~APw*AM=kH!M(bs03%i0 #ܳryh"Aľ59V6K93;L/wȑUؘˬez }c#MGhZR"yh&pHF^ٿiEJ)c?2 c9Z{_bQJEV#큥H!j$\a VͼA>:a\spH+k*m<|97mUΈG^<_uX{~žϳBɽki0׼舯ݵOjM.?J&E(H+Fw$v`PHHشjQJR,R_FJm[*R\b;_<1P6Ŏ4CҔ<μj"] NU4v{\|qX$p:]C%Zt?98kefNNdwSE*Jj4&F LjʫY' 5(o{ @yFt*ϰlWuFY!XWI|.%".ZTTky[i6m*lJ)q*%j5y`4vb+yܥ |vA8ת饓O/٘~tԴurciVadOkQ`3GyReQd܊8Ko$U'AȌSq17N!VOb173|xauLmۊnؼa;^yN#O=qVg|aqIyg(joh} W=ZîWgjp]*k[^zmFϾN7/ʸ0M)):4Aߠ-L<_wbH~Ns*OC9Q!>gג~IErlq-%uv8UH9sq)wbx]oWk]ZeZox+k!#dN<*UKc7$ 9l)7 P:oyWouz:vo>},wf i[![.nKNqv垵ܿ_,tq.2ּ~8pGd,cUo?~pЙ{CO>qP9 橦]&N8&a1wdrQřP7]iklEEuEq_>ܫ34[g'Ejom]ޥ>>\hJQlYpvOhxO'pH,.LIO0[uvDtM$C۱k>pknl:%[Vd߿{3wN| F-k=??ܐ3qMZ׿WJ2+(f\S?/9s2"7~$8ۥq7MDCy<*pFI<1c̿qס~jE|QoOY 7pE{_M ~Pxcss;6Df؝ٱF^]=TRڝM{[?I.O̫vlAhWh ^eYӯ{#zәj;roG"j:0!إ6z?|w0_l Ӧ^rt9x8=D(e{h'Ͻ}x _4Ez ܇P[V۟_?h0=E^f"c #w_fEGTlþvY}R[Fd=7o./툉T*R;WJnf;~sIneyt(s禥Z >ydR8cRcZPf:i3PWZkwT$Aȉ88sVsR&g'Ԥjr_L꒨8TP0P֔~g٦ {"_﫯FʊSveXs1Mh*g_uR7W\uQ&@'۞.x 0b"vխmfR}7x-RhE|zLaI"K6gAFW&OZMB105S {FrlebXmݘ&#>-V[d{WߒES\nAfbJz~mUb|= yvXhXʮy8_kjفGn[ byoPVi jk4YdC;fw89hQI~x+c6) HH7+ID,ڵ}gjZ!fb뀫kozFR?=dޚ,عH$9yogzgcky>3ݩ]n V:B>}=O:C1_ء8VߞwF_nEk.w<5iPkftп̛#B# ~Y&MK:X>t}i%#_=\)%%뿫W]T36M,zms056$kLqi!S=\߻\"AYPyAKOe?N{ U6vy[,٪To\n ٽeKqQaW.Q$|Ϳ"[lOb;67k.$3->&J.Gv统ܤ©ZĺHd.`T5Zң9*^tX*a:"3܍Vlĺ'!+bEN[ԤCvq#?lL.lwE/b`]!-<"0x?z%h:\LZ)E )ٗ"ivT,$zZ5>Ջ4sUq^sG;eu"B"S}^~EB\dd.wg4B;c,G{$LߦE{a"RhC/5tDJNZ<kl:ܑ0Z>{n_;'hZ$',Mafe{ˀwM,!#XEnx)m 3ڐ9SEDjݾV 4Xʠ%0\nG$B8N.-Gu_;fïnm2)WǏU䢠bBHd "F,MQ{qHd܇WK$Ѣ1#e?r*p8Uܽc 趭/MuwRfZg\r8G:84HebHB6_k<`CWZvy~rrB7nKLCk` UMH~=83^&_ap[ua9PDQI /8/ũ'a-{͚}䦴6aLKѷHv̘όe]w9"N9&Y'91[6;&n}d:2fߒh;CUT H7}ɹïO& Mut}Z%%SK%Aܙ88P* cE֗{=k5eIa)AMW1۞)],¼W\.w(@0^łCDq7yo8qg. $ߦh?[}huxG.L2K WԤ> {~m~VjP(Xu/w3l,T* w|zz2Y.O{W6 a4ZwSFR4;|HEE]Gvj\ZRLT=%-~u8&_Ec? 9qOTpC8"o5}PW1)h6ɎM5ñ>[J$:Ǿnq>L5O4 ySiꅐk ~I$]js~3ېz 5vX]ߪQ6}<Z*%qmʯ}Zq^r1 IeH34nynjb/n墦sP#⿵dB4&.f%rqr'49t TN܍0Bc\+0qhthԛ_a=M6.mkʥ*fshJۧeߚz*فhA͙JX$+/;;͸]=My4Dw&oŽMY$Wgf$4Jvsݪ{ov7\w#&Fua$"D6WH[V- Ԙ=;iL`D(Dn%nvwҡ҄sV = ~8eY7܌$+j Z? IDAT>٣vU0-6Z0No#u Eʼn>#yrM5df>a8 5SKrN\lnSHS[O}{oܽe,HBTHS'ʛ߱ AF): F˄SfwO)QiM?od3;&;ZZ]ǾaMiEGi9UѲyuab|1wldPM1/5]yIN/G^\zl[ oi,/ZUI !8<.;N؎%ư-'j1mݧC?d<(אS;%Ưw1)5DyaF+em%Jʽٟ$G] Qg-[>-.hWǦҊZs&҉ 0kn{xnzHypk՟>j.jS f滄촌B <$bmEka?;,v>$A1 f=`U4MZg.*.k_nk~P G*ͼ9O}ϑ.Z&d&Mw?=ԟ?j\ˢv,5)f8I˭C/lvg0;U\fTz/*s?gTC󿿰-<[@ m|i'#/)kW+^pN8ϻ};yӷ^7cMFWn/|d9i{6~b_(v7;~F oBy 9''|RGɄĉAj:8pʍpj rzJ<ՙ{&\pp{KxYO1*wlx*7cNU^J, RѶl"jM~Fr)0 #cDQk{'0X:jԘ&]څ;Ϛe}mAo)tf&n]\8{3a5"̛^9/IM}No gA¼n|Z=].o}' `{귯ik|\ӽ Ֆ5rف>-{g_k;yϯ}pH٭;V{Od+F_tx> )/+ylvoK}UnrbviL:wP3`6(HMHW &Zo>s7>t&ECpf9W| FGkqcwYvNyUJZ׳(3A#.֍Ot멎Agޞv'WS.Қ*5֟ MW|\wΟޥ;>j)3/6^d"bMIRcf]rnW]K voOw3! $2yc |dwo(Nɓ&Vgjp}VcWj#N S[_ 5wl(/.3ߧg5'NbxGZmu}K+R2vc^{BUӕrmr6W^h1Pj=FՖ1֤uS*aǒrف.齍xdgSRxw 6GEH4fRy\wۙJ~p@,KP}1Zҡ<K"ܵ8$N^ooq:p\bM/EGC0Oonùa$™m0_xlZG57YvD*{姑xܽ>YþƩxhR!Y3ڞ{6-e]=pdurQJyCwC\m?UjIbI7rR#W/sw>;?h?U?u6Y2I' "vN&VZUqKVoY$r{vٝg^('qi~D,]ӕ(s&D?riΙ-݃OT%Pݻ!?*=]`8+?=?LRA޴Hi} o]-z zw8RR[8K,P2$f&Eǿ))&G)%lR(,N1 bFK u]ܸjlc=[pεm}|xzQzM+UJ>vyʣѱ&rm0j]T>=Mk KTD~cM[]~ո:1C"Uwe/٧멱,;|zaT![](؏f` ?iV ޙ$[*4_JI"M7{W+2Mmϕ/SWS$FOݧ;Z!=N* +?pkx#Ú㢾{ݛnYhS pM@P?o~6z,]~';p}Ov}kHw?H^!67Ã|Dٞg֕LZGv'pk7U1)4M1r|vo5 D;v4tj =J..(*4޹tix ^}Cq̑%u}zbl˛n|MAR}pO&ݱqEQE4N;st`)hjbټfqFJܹƺV͠!N]S4Isg<(~w{.;!$$zޥtib " H^B)@B 齷e/{Kr o gnvfyzq!Azv<)*҆/9am*lfc2e@P7oePnM}c2猺p#a̐`.h-lךMՋC\&T~l؇M`xC&"ggM͢R>oq ݯk^IX=:A<-I *emOD60U~i^ϓE-I|ĊEY*qvcI<ei_쀮%"teoMvC<φVf_-h[ xrujl:HL ]Ъ6'à#+9s^^qK9ֿ q?r+pØŕA=6MҗG]f|ݤ:V:%kds_I Mg|/Ǟ h|Z[Z%`B"Rޖ>t[yJ9"1maP_DVl7wifBDm;!N' 2W) v PglBX[QDh*Ԥyt,;Bmم2UIv\zSV}okfA>PIÞfq3~,8N5{˔=4xn?c\;S&"Z=7c U IanӨ =9 s U@.X'gS aOv`REQ a [=M-zp3†x3"Q-\W7l@M6nZS>':|= 3>[gWs&/L < ~M_ vAy9ɘWߙm ?pi½bӷyӺw|'gh/}㤖^Gܮ',٭}Zhųkѩ+|r4Xggn zp-a ^_ n[yqh#O4Ҷf-RL0oe J5, MWO(m[0XV0x`1U R˷[m%Ӳ zWT MJ*Knݻ:YcTj+N6fF+}\l/ڬ1styRZn\RVnQeuѦcKx]\U+. g^}Bq~JxX.yR ɥGz{2|F[+UHQ.oEAue #״zklWF6Rt_|zp.֗I ܇Yʘ*5U_z? 嚨,yl:GV*5<S@ q7RkbsQZBqaQ]8^^ J/U++MJcϠz0[8"v;SWbTČd#)+Dh7%ꛙ\!R"TJ>ӊۓͅ񌮒ۻ$ IRsKkjŮWCeLhIzL&g :%*1z)MX~$DT?we2"xa<{>JEtQNtyADhx]x6\l[_gdkE҅cwnI( th~I(ο]"-RX<_'OcLQ Qu<_!Q\謮g>,!=v&X} ҪT֊rrs%<؏ɰOC09 Щ>7O74`̪J% 9b=<4ܶbs54X0XhA^ E榏 s#kTV+tNقʧ5kNRДcΜ!M[ l׬/e>{K /6e(ƾAFJ4ߖmg_IJ7Zq=q9uq|L$TW埾}̣ӖJ)qL`'>n@2yT6/IUSO7TjHv6V#bsPgqqm™$g{G  epP\]vN53KiAJE,| $  ^b 5wv|}k{RT &:slUjeiEU*RVRݬ3Od$o Ew3ʬߊ" P-n9? x>\|reIS@r,  t6h &Zu)Y%be<)={ 0jf)Ր=tN%Lhc^ 53ŕW[s\I{T%%y. Nt>R=n^Ӫߡc'j_>? x法"ABRY݊m=[qٞDZg]좸|Р(|0R۾9iOsΖ7_ՊDk/nKƪPTլp䥯݂߫?!vKGaG.ԴݑaJ2 bNNXPmwS Kj9y8Z8Ã0 %lެPC~0j3?z1ӻu l6EQB^U]_8% ޞ=cm߹c·سXlHXTN7}wm{ pup :C&%uYitҝu%+;|x;>ϊ`I.9nvN%Sfmk'cgƦ a}nG^]8l.V"q]fvӿt‚u"aɏ0RB f|dzs*k~ɯzՄ} /3ph=^x&0xth4y{7ֽK?={bB^ZZl:k;-vߖ]۶>w7Wmvqr'].}{{.k=³^| At'{ kIS7=~7WϦ9;DX{]KHwD9d~&פ3X@tF.6 ?@ Lfsf.h-9 ŦD4%b[M]D;_¢Ri̐ɤmoQ>g?6.!J&7uq×.OG`h>CMC.u`0 ~`œ3#Zǘt۶{gf<|q+eƺa0geF)qa -{!k_=5[m s?~tﴱFVK4ꂢLj-lvn9lp+Jʓ0ۏ\\O & ;?8rH4o5Ƿ.!ç8xmBfw.M%8:h}NV넥`XxoWYI9L`m7յj2^^G\lofgB!Eߍ}'FMUfTZ9w;~y}u'v˫፹{,фdd&÷@ ,AeN\C,S2p4Y QߍQU]ۨT_O1.qkoHH\`DeO}Y[@4-=l9V"'XpEzbqG񷳲ӔJa/I\?{; ~K0,KK0/]fdYpJL&p 4^7,,1ݾ(eLBJ4BS'5\=DSuM%XXj^Ncė=Xf͒ոReu75`@߈.RՐ37_IL ^^DSiYiBbBn~.͇>qx'\F u `9JD;苛r܌UǩR% 1x<7?%_`VM-V\V5y[2)*"̾=MڨoD h BMR7 _4#VBGrfޘ׼DN\/]=Cj0j!k,}eɨ_lʹ/T*7>2xk/g&Xժw,|}®139,9Q{QuR/r?WNm%#Glj1qכҝ0WU]4f!WM~q~%Xmپs=~}zeUGwb]~ H8{12;C漂셯O^jW}(a6w6 K1v+ŢK^tYg$}59yimjCLR u۾S]G oLg$TX,6nIBR)%mOya-_N+_@dȦm+ mcm3R7,F̕Iھ+eb%{񷖬K`NQl66dcq󼗖~VXݲdQ<~" {[{̐?i7_"ֵKukUy9oo~꭫]>4dVF`~}r޸A&X碂GO/?)lۗO|J1q۽d^, gㄹSpۀJńɟx#*// l6^ ѵ_mzP=xϱG~|k"yB45aTN.sd5S~8&6 - ɾJU ieU ;Ee@sIs4 eYsj(F<\ZTPʕb*0n,uŲ8TDYS psOsfˊI3uQMMq܂Z\)5.d(P)E ҩrR@g=,vdnG_4b?xy/-~W?llZ10ЭQRV~"ܜ|bl'EK ~fuū$ؐXJ 6f޺Mg$oؼ$OLyayťšc'&?-(!]]X7̋aЁv,nըZTj_8Ӡ(* UWVJKyRI_|axuo1溺Z\0(7?78{X1ޑݛMgfwqyC 9wYRjȮ2&]U})/G=`I%u +9)&?BޚMfjۗgS^RwɅ7af܍T7̃,7*D\,?ίLcjߺ{ZK>-W_aw6-?LŲr%J}l3B&e?[q(tS^(e@xP:S|iSQe{5EO (2aaK(ja ͗BY0"fB*Trܑ {h/fPTZ K'9[CJ,x&0#33$~KR}}k9e9C;6,MijTGu#n\k vvwMI{R\=O4vo7II!BU*U;˼0?Xx|:Ba~4N0nõk> ndvEE_YgKy˼R&<-#gmma8:2CT55GoD4)ݺۼNg^@NP\/;=aa'7ܜeK}k`W5ur?`B cƜU(0jȧ1Q XVj̰i鈠QMr_jM=(j/Ť+ aW.DD{>Ry 5ިeuEYZB#@k$5Ct~+/e3gZ?a"^BME݃ۨ"֯Pv* ͳeMa}OKw=&5yBnz穇yp<4DZ9Rb2%rb oGcA+$5DEyM5vRض, ,jsxkmHi:ȱ{~ٺrl[wbz9HYlD"tXa:hLHP~T;F!K!$4b:e^X66w֒K&iaj fib,cW[J/=<\9OYfTbXas,JCT&6@X' vL*rnlk\8{`"w+ w-|o+Y,B˯d)ؤa#t]>_hj2N30W{d<}맕E ͏?HAⶪzq(/x-ta (4$gl+BM:"}!tk.N+?} { i^;#,uY3#g*+'Qޣq36yK(Z<0Ύu"I}CȕjBefWXqÃ_&tL(rl{$0"qA:u >ڙ &z,FBa/ _@+cfO_҇oXsz};Wonc(T*0LeL'2,$۫ Ju9'O2HElt1oD ScVS:).3,ejzrR[$(T^ mcz_7X*n=*JyPjK^igLD'/'Ѳi^o*ԆN?l5DlN6o֙cm\aSL` d[o#ey&sTbk+G|3xiu)E5(}dEIR?Ez 2rEPJcPh ӊcbg];s4ӒiIi:-ZVpʲV(Ya4ZQECZMԯZRҚYxx&>= e3JJmoiYE磒û`CDŽ<<`Gto1Az)&yIbS&EY:ESZ@A܌]K6 :E7LG/M_xRW$im27^l۠E'W:Q-CDRuه9%?fzI"[&˼䍕h4PXH,J%2T&( u5<靿;R)|K&2%@Z #dڻQՋ1THed>h o{(9W&lpeB2.X/ʢsJ0xS3uaSp,rL(vDžGzrk~꿬*4]@+ 6$\R)&|U.C=*sj2[ ud&{SxitIeW$wsw*k ֐34HJjj̳1BB7.uZCiERE lᔧt@= *fD!nv ҖF*EDŽb2au:_. f(}5 v6V\_.TYQx8ڠ#9z.3A-=PsL6 +uW'[rܙQ@f+z#>d*-64VPRm^.=jZLT{~ioj6RFm,0IY*xnϯS9]q}%4j$E^Ks5h9X08=[^Hs*kh,tL; kMyUUs~}MUWQG?JWVvۼ 3&"L05b:ITGz*'٪8 *AxYn *_, 1lJoG9Ɏw[5;WV8tKIOv0}ʂ¢{W;ujg M_2>bxey[U{4 9.n!ESو.0lܞvuj4Z.hmƬJ$Gb&Z?UaNw7J1:"e^Q(JC?PN&(7GK gU(rm"ؑ]yjCiNɖY{2k*C}\ IDAT6^T]ueFQaSiipU,Ǯlh<[)H;xNj_%(+wUEt4N(Rr_ʋ(Qi*Hє.,ʚ"a6#Wa,+?l;˹]?s&1S\]3yi bml:5yqY9E"˩s]-Waؔ- L;I-VnU(IVRPVGC=aY8waDD iՔ ,(Ί}WQ|Y@S@ L_!/\Td1rB̀k08Fy[O=V.}bm4xw9lNyEɞVn^VhH_-2Rr݉e5Uy7.'P*Ln4okc(aN9.Se^XwbM2e ׽Z^T0/7LY)R&/Z]C2[G{W''WiyE)a$NnGNx$~k-ws$%>cF ڿ [x<9%ѱO]#DSQAOx6` g[_xzm{ͨb&ckV]Ydd(6L^qwJW2]Lwzkrmɿz3rO6]SZ6b$w%g {FŚIF`oߑʛ"9Vh+f0Aۨh(^ :B~% u#Bquei *b^ai}Ϳ\Qv6ôFeИk#J3v@b0f.4bu CNGQI%ͤE$%fTy㰦FO=|z&>Z4*8ܤWyiVswA^bu)*ITx9M1YȞHR(?J:$ɶʽUBiN~n L"W(cEmKʪp.nvA>NXf^IȕcztLc ƭ{G>IN(,I雷idՍ oX]_ɰl9mD!SŘ9"#<UuIiy姯=j H4=Ǚ[1I2~wS^;u]Oy\vCjW&Ff`eq#>K`@^A'.bI~QkMa#wef*$mf<UkʩP領ulxnmLn1ɊO?} ;k܅fLw1:]ADgyhvv208H|G7W~Fy.u5m &΀>dYݜm-xʗsVdB2$,$2{OO ?G) Ȁeপ<19nӶ5'z䨟?z֊%|,0S'd޵5|a<#cn6\nČtk9bk~dD\v 3r R0+e2VV[ty[pGZLa ݸuQ&~o\%D {Jo7gM}%=}铦k?ժG1ʎ#z;՟o[cQ9yS_|9 Mfyq/'fOj_Fzeцz76b*8#~"_/ h 6إQ+|u٨W=1\?0'd5>C#Z7LD%昜E v@UMh+,ǮRŪT+s1ޟ䨄%v]=S>x{~!?%أ=WJ$)KqeU:GQJKl t++JutVi$9IꟄn w,f]P}[S0?jSuP '-MNځJ}%NZXEm19P4RyuM*D?{^)Ċ6/-Vh`KӓMQ&ʯVh9)tu!UqDk/E)R/Ep^Ѩ«:>DRzB̛JPFz2!aPϦ*N9AXF_1 +cD7dW5jtn.#nD=̹t7m@@Wg6v7bpn^aݽxlVJfatRJslMx/1aFݬt!)5u:<b /Bm7^@hkhd7#McbFtKC}=]mu73mibuFӭ3Z]#H$UrIDӬ1$ 6-/Ćxy#l+.kyb鎼O_?oNs/H1~`'/} K&8ٹ8I򶻏k3G`!VsQqY::1+6mK_hh Rժ?NЍUP|wOFH4hݘHLs&]qFĉAx@x uqz!qʀ,a`B4b܅fLw1 5rfUV|ͣ454,8{w~e=VVUXv !0r1k7I}HٜkN4O&y.{yu uz^yyG&;Ê{3nōAc2_i𴼲XV!WccYwWo/.X|]qg#.gHbk6'y$ #JNaů-`03jdD_,'aF (ČR4VN= X]03a_[[}X|}}VwڰLj1zT?uE.da?9QLx!Z#A mQfbL<NɲL7 QJ>*lIH$6L8s!^!̫3F0ں7<`_N/HZxޡ2r?<<gc9(qNqrvٯgz9;iLv .߭WCh4Zw_ײz9.nv&;@(xlr)7 T h#Y HC;&ŌaYms.9̛]O-6oh`7/rw}B7}{BTIH# 5ylf?,pVcG=̑ȕ9>_6hR˜ra1usN CB{e7銕mj'n \c#񦄋N3F~ax,̋wvta<;>454(8*W%o rՇ0OƆQ,| Lסz&\YS̴מ׌*ej8waDLT< U C&Z_Rf&0ߎb黸ҷo~nӆX]7uVV^ɗpO ä؜'շǠ/44b7`u'\yvd4˨/9҇Rrw..=1/]慅m}{ GWp/LՋnC3Wy&\tWD׼PXMdCG<`K/x :՝^½+/M0 QNvj)fGϾqv%=nziivVJ:ξ~A>僷w%0yxvŮf=a9T@Ucо\~W#-SnQ6}oN+B4gP@Z;m^.u@EH:ç4 {fI)^tvXβHCcpēt>JK`'R0ymʵBYQ$2Yu],y˕ E*5uaa  -Y02էye0", Ƃ}P"TTJ Ht7u?˔usp#NJGh3ER[25 tϙ<,ф1~DoÆ:d&z;X=DSIQ;<*ˬ}`u҈y4h˜?u0Yaw9qQi K»&+5:Jt3੗VkzHJvEgw_c7+wLa٥a7 sDBM"XvP z`4ZLw,׏-:#DOFAxa:HeVVecHU^ ЭsfLD1aΤ ?NXj,d_v>T֢bOw[^Y.^>͏DєG+ke F蟧5&9/-#$U._;6:`פ}fSmm5ۿYOXRA8s*-c=:~{>nK gJ}ǝcfKŦzx̉K4*-qX7:֫0K={KqQnFQ?1CV,n~["]|RS?U+7,.%׹m%=WѨg3'X$sLR/H}Kwuj>Ƨ}e~ghOa۳XZIA)24~m+k*O(o]S5yG?EfxT.p[9"d vOZn!XXl6`*rLR\R{c׾:aF Yُ_:aL(>9#+(0߿q+ֺV%l}"Pg6 tD$VT=L%yd}nLܭ9x3LAJD*NQze^X8Xl <G{BJyn;pK bh**j_2NZz@`65IDq_Xm'_}噋lLV7jScOLu 6fs92qFv/GAQMVnk{]|^ЖX&UJ۩{Ar֦wt2K?hMn>b/?[ػ_[֑FaU"'+C_$E kFO{S~h6/?^^S]{ͦRH,މ(/Ŝc-v ^w~Rc"#/UV\)9r[h[g@ j"L[%@68[;N(~Z&Q,쫦nXϓe ߩQښo(PO IDATk`𜟹jV"֯Z"׆ Pӵ,,ENJ5uپY*]<*TAeJn*QԨ m"?h[^1')%NLLd#RO=,ѴI;ȩ4&g::߁BFYvmg0l=4ΐTPSw]-()hF]w&}.SmTA{[~OE:BV$DaLa։MέIXh^Y1x/ݴc^zl(o!pbDjU (r1v:fpKKcOPdkrw2;3*Ǵ ft7{B+n.\BjTbL>=,1TymDaTFg)+ut(M5uŒ3;,2se<(o옰2ٸHǟ}3e9Wc[\^EY_3Z!㻍&%e^Xd:nC0ߴ\oa&twqwǧlת[$w[mZ ozrrv! TLsM.eU[Kۧٲ}Y>̳?ϛpm:p3R~.cǟ>?k%EGǟ:oy빓eñ:='.-߶ȒivwFTb|zDO5} EQNx1Qz@,s.yښw|OЦ>`Ic)uB]iЩ{诺hQ<ޱG=6o6ا^y:~e)jJRTNB_k wyY|uxH\ɖ嚞H_=Yw&4jM?k^9,臨9lgD*X/2$npj4>Ѽ ۴rTWmxW]\eygL=^6*s/<] 7^"K2."KKIWn=|]Ott7u]k.Yb J׿^6kPl(ϗ^mzɳ]{YUm2~X9/Α&Q63cJ 8s+MgcWߍ3bpoDvGLn ƽ \j.4?=>.UQY>suWy֭%;/Y~.<^Ϻ֎h4s/=]M\$VsOv~dֲ#= ƎV,?KĄ(1IЛ_5S&s)WɶH)6Gij[zboԖ[PgGJ9a-wuN:E+u61~m羗{;vW\QGr&o rکud4@Yܯ HC)_(;mJd.=Vej`ڏE)nkUz;x-b8 =QI}ƤąUnr=J0'֧TY@K& ['ghs:1eקc+ whƸ_&̞T6?RcNog6~kM&Gb絕~9_̦Wu].I׬ /71ry[>pI_3lkClPǬrt>v)"K _Ch0 zmǤ wяyHnj.Lyť7[c/{rM#0syxz%HGx9˫κ}os\:E"=5A Vua֗&opܴNX0SMiv(j˳11Tx8~oLI/)y9N 8h|Ua<РNo;=jjӇT]Pa'|sy^ұH^3Oy<}2Tդwsi,uNZ}(}-2SGOFZd8>*B,-⽋mNǦӇV5E.:zaǭUws䴵t멩|[ר shrUb ZԜ15̰%Y"a tHuKoo_Vz$>{3t|hgwy?pÊY{Mq6K 2İBY\QPV7{PФymhΗ@te߱ሦhFk0ع+öֈX5R[tcʘVvӗvx7"Qc M㗝$Ӈ+SO\I(rХqOф#SFjT6WԎT;]%y3&RAMUu=gg򳩥-ŔxI굵l |!wfO\r~}Ɲwݴ2V[ijSeUG=_?A~لQѓ y윂UVQ|PS^b2ֳ8y\nceHA:7+> u ?4zlb,tҷ,%M/fMHԨֿ{qn/ zDl=93f|Jrz5M͍;vfqmXds"\,%M1a5a\v5g魶 7*t.>>cRdew{,n`?sxͺo@G&9tA lOxh\ Y&2#%H 9V/ٮE[}N~HBo&#om@˛/)[[ ڱt{ϙuP9sPqɲ oZ^[Y*Oz\d|!Q!?/)*Cϝ]^:eȷmEc }QSšM\!Wf1FEO5h^E7u0Z%M9B8<}e֥3bvJd~,қ~U?VƙC-eF589B+`:3"dqӮeӑ'YƔ ]O>9r; ߲MFP}2WᣂV>WC┦BF W=ڬj0PcG cLIZ8_xǭ0Xռs3nPٌ)㭍;.ۂ}>e݆>E'Mkmߋ㥵-Ąns׷/5?in=P2OT8xay@^?h,'}~fN;gF2<4JNW(lϡv2zljuzڦ.AMB{ȓ-1|T)v^[GelVUCGΙ}5w5Y-et_UUU^Ⱥimihqk\Cdy(feOmWji^ kuѸB^Jw=]W~~59n!1S&F_yv3.q#|(G~|55E-K@4#et< 2dOl~y|wWQ5y xeI-c)˽U7Bf|R5W3f_)K~!>ݚ}שot]Ysg=-8?\7vرe5g'_$4Ouo(lrjkྡྷl9+%]ܧwj~HնϾ-;G4XIk~K#M*c Air43$dfLmM!&폇:Ed)Q{Mɦ?u:>ג,׶hv5i&Y>Me lY=4Ҋ3$5>;]L3͒ӫo/|&tZcbO8hnYTvD];X$ue8( '[է{HjOç }ىrNǫDIXb!3J'w{끧M =h Ҿ$ے.6ѽǕ{N?"S ݞ&HYV_[C|1euNK~2R g U3tTW ք0l2;9[?۴Ǩ:gc}6mYLFÍ+No.3:=ھ&Б %D5YSߔ`.;sxqקۯ_k}b>1eBC%MJ+sw*'w}f5P:]Ǟ1,=xi._PzuC[EU]vTW6&dFl֬ 6WhD>bnI=ohl( z[3UI@MQqo N1S`21I4k%\.3WwLˮ%].m$kr$%5my-zx}CV-Shӊ}6 WN7o*wF6¿?Ne<:.Ćk\_7ɴkOh/QL2M3Bm x ;R}=XdbX$Z-pvFGNxnn*,~kh..7lCV2AM=TBLvKE̙<( BS#c-ƒfs赎Ow_8aHF·4-UuY5mҥު4@#*wѷLiSLi/bhOy-6wNd,k2|Xt[/E+YW,Ɩ#+oj=mfmh~{6W(Scr8SrMFٿGtӓ%Ix|g6*0>#9nÎ@?QܺbrIem'eAM糕ad4|+N_'-9/.,x_ڰbzżGԶ׮^1;%)^Q򊺷KN` M[^?Ont{?IySZ-fS^Y@.Y9'UB,2vLFVZ7v"4s -?>UYrvVg\0-{_.q W.J OJտ;ۏ?%{r•Ι_ٙR+{_.g}aM)^wX8߹?4z̟d10<Nف^{&\)}Qs,]FDMQN?[~%:ċ,]0Ncɩ$N}W&[S~2aC.J-n t$師帔~h(HQŶxKX$)x@kT9pF^4@I_sɒ?Li9_h=nCBFE$%q=+<#d+qYQM㕖=]sϹ7J6~OO{>ӿ?aO%۵^Ek@-ye-HlSnw(%gNرıgzR\ų'N8wxaރ'+.J*jWt _j}o*'<ϸQV$'񢿽.SsǤzͲ+V/9qk2>n=y_#[J;O<ޟ7;֜촅y93&$q ׯcn7Ԕ9*Sk}}gIU_^ o_`̗9_&Oox_[U;TXDϙgƋ6}RvjcVVA}Ol._e#v^6zsE>(] IDAT/,[qCw58T wy,]p@x]enobO겒Rԫ{p7̻'rc 3FF?2a`O1ݘg^=f&&g{Jc!qSM S]8% $ȫ@!ޤ2p@’L㣧O M7^fɞ/l=ǽuEJ!]EɚkuuܢcN ngM߷%-㯴d7[[>KKUWGuӲmd/۳ͣf>T5aXrA+rjSe+_z/*^c\=e)E7ku^}Ym2,gK|\I7y)LR"ΓʞrzSoRT8.N\>ư,cQiԷZ-qi6I/-6,`3ZcPyۊ<{*Ւ6ާOKc,nVլn>-mю5k(إ±)"(-ծ|t)ɚgHgpjGucZN ոʶmjۡ@7Yk=ߜ{aہvo߫atUq9ܡJd-c,c3(g/(ݛ_TZQ8:=icһ̹?ԮCJ+dI46mjN9S;eUKgpXUeu' J.gJK\3oVةFr&1 meD+,-ƣeǾ gLF93-!+#yΙpΗ/~MW64;j1{T͙b GU Ekh`gJ˛8='k̉i)6Y:ojFZoXCs=Vߦ*U]ZKO2cmTyXt^C@nD 鯛:Qo5,䌹,SW=>S"ItJyDSy]atyfqFOOGF!<}Rzsa 6b@PAM9|<>XF@PSl <5zs:pHU)N#EEu66m͒r9Y(m T-Хy5B -D|b܈Mg5 5q{4-\!tM5; ѐH05)#=O= "Gu{FMAMpTFވOo?=NCVCڵIPJ \wǍ\Hj/{ˣlƦ/2`" 2)!591=s CP3ow_ylھ]"CP3{74HkNKsύ_ [QSG& fgi}Ѵᯊ e a>p Elݻaݻ2`" ֽ8!LB"AMAMAMAM$29DC ) ) ) ) ) )IzT ۮ[gEOU[97|n]oBê=u|54C2V/t^?=?i8 hd9{$6V+( $5;lcgSF0rpz-8^jtemCg$+a$.M3~=NQr o_3bKnCR-w| r?zey?ǔƶ}"9~t\BSjM ?+ұB靭/_՚ Ͽ* N y@(Ov,}Z1I[w R4w[#cZĊq!iڭW/zY|}Xk,z1 ` KcFǂvzO&&Y ho'1Pz)[*[7LOZdLH_TM˧ Oo5|˽n]u M3o~Jl ?łQj􆑵cCf3NJ DgwjE xT&i*sM{uLlOzd_=_n3o/AdL#c{n{O>=G"']CtBkY y]o2Ũ25o>57,ґH FjQn EAJw%Vcwlm "Ԩ(=NԖL]wnhq}gjk zr`鵋*ҋoPr?ҺwfNݝ"$m䮓HOPhX`,(w> C6$dO/EX;k e]%3d#< "WLb9w;6dsf(/5Xrr|?_V7-6>Ӓ!ojh [Ifk'vfNM*5P'ggjcq߉h=rFzEq;w Ω | OX`6usX;j#JlTJ˿Ic{`,:G4IrgS`<>YoZ,zwbd')d1!۵ɡCJc=F4E"txX Fɨ~S5<˂S^O[ϸzM*Mnn:Srߖ5{iƔFk׾ԼUG=(:Zo!g'U'~*%+wn]cey6L lmV[so)M}m|Li|*xoIMMwJ/m:m}sP*=Fx|5(/2Ko |Ċj/ Ui!%#-{[liVͻ |}xJ~@p?ݤ]o-Mmz zH=]S0ܹ|gԞ>l!j_,kb}f$ItA6t=8dԧk9}nք 2I-SacU -&N8ǭhJL9ߦud>+*gșleMzHׁ6_Ͼ*A[v绔RޤIemIhpc[kcFz[ߜhc(Δ@tŭL̑rz ݑnI0X*Ź=I|eymR}0R>[)(as =fW ܑ"p6-74 O*( |iuͭzRBhnu:8ntܩznEgjVS)3OKYfޒ[J춮]fiDKEIbĄQcE)TfDQKmKVSX^F5utr7凇=|[;5Poھ="-wnLӼqGCK/v_Ug>U^P䜐2!+mΌ d ҡb{aGo5ޥ}]=P]⳹cdtU!?evH|kPN:V+BKUՅ`yʝk M`Wv(a@"%[D_*=}&gOPzqs{An^-:'JD٣yZ4E:rR/j3Ղͪ'Ss96Vԥ%*F{/J7WC[νlԳmyy妤Nޒpvm)cδ6lnmH*;YHόe۫g_4s_"*ݱ==Sv;dIXc9p^ܬw4Z-l>Wl;l2Fϼyבuw߹U,,nkrLLY4;g􉖘|"P B5MOԪW+'}'ߕx{h]tiQIk}ɻ'**R{ܓ4QuUm{w;xJxؐyH-YeR]g{BJ4.-qyBhatx ݖ!jOF늋ͻQիxml"ѓQi@NJn\s&[w}UꚝNTaKpbATۭQiky ϔ$͵g;??j==L柭pSMy ^KH}_D.($yʘa-5;rR 8vHrtyq{u~~=}go)} g^pϱ5KʲϜ]hZJ5nT*\uB,T~\շb'ѓbc|yT-moR?tTT''i&tz]ۏvzf(Cϣ͚QeYϛ j ڥ>ߋP]b֨ptPXP` RT ""?+-ҜʧoG5uR~XX&V uC;vMyKmO ͑QNX(+ֽ%ݺKYO'K !{6{z?Ci@^V4WŬϚ^u>khѧÿO''R)?x~c'z֖E+ӉhAMqJ#M-/:#.>HO*jbc3®e zXImΌcIP]OEUMC0)'+e ɺ.WM ư6&9=VJzNm:ScOό6IRbx͜i;Zxa``RKy r)˗3G]^MٽОRs ",[%Hozn,:h߂72ÑϲȰݱ/ !ۮ[C%j a*^2¯npU eϛ9j154/.(]e<[QWiͦ%m ?}B*t\Y&I~mqO`4hySNSj GhgW.?挤+7>izj͙=g_>fbI6FgŶޯu@t%-Μ> F}X5NM,Nk3BbEVgaG"A,( M;K(7_݅7"6v= o]^zрʌ?[ۜ⎃Go?yZT/Q)݌=2g_U1{.>P߭g8* IDATr$ akSሦ)w6H\ӦiHSZW[_VpPԴW'z:I2;f8L[ߊ]xl{;M̵YSCtֳ|P;; .?-sTJxԔP5;׿svXK*7~k^؄HNMct9xCs|-e].xy'oKl{##IJ7%)u>+_AMJSxK gP"V Tm]p`c\\ R] ˩ڮCz{tͥvy(OIm,iaï ^"mm@G(hڻⱢФizrCQWVFW<-9!!ަi7l_*;|l}:SG3[tΝÈgS3%ўl7t Pκ3&R2DQlmqU54:jo6o*-GxJjSϻM,܁>ejYAKurj J0[@P0Lߓ6ϐߚ{^s.>:5BM]z`R2]VF(Ȇ+N's |ߜ<jy9>)53.6>WSvohܞKZ,gO\sѮ#e(:dhw݊9gLt40]7)>ֶ;^P #M͑51S)rB l=Gjq +W5fF2wXZT9-Ԥ(ޱn>Њ״b_zټܾcN:V(=Z am$[\g cwwt)W.RW-R~[Qc9we۟ի)fٕ57h >Y{Ҵ ITM[[z<8gΊߜܿv͒8=mj 5W}2kR0j4wic7 )Y8q?5`w9~sNG]*' OJJeK9=B-3 _ϿKDS,+Jrpz{/~k/H4Dl\u&PYgQqmtMf6c2[-# LB/ jTZNaKRr=]G iҍ4!(' ]扖䯦“m&'ݛf;0w`r)wUŲ\d6r`S!$KR@ 6`wɲ&Yղzo9wfw=3Zݑd>g=sfΜwΙռ%쐰b()ost)=ݒ^D%'+ag;1ښ̭7?'b}+nwswKk =VL4%?hItqc|x[Cv+!f=ۼkL&ԪM߸ekvMTi޸TŐ^.&ʒ"6x"p{~~vow~{oNopv\ݰrقi7O?fX]N 3ֱgߡ}zjlWٽi{F5.\}ͷ_icϭ7]{~}{NQuM8{JP޸$H;0-(Ÿ?*)!Xމꎺ.dMJzMлPD/yns _/ fiD<\2x"Pg^^?;qllq}=w?e!M}gnZRΛYƒPذ{MUP mDՕgte̽Ajkr7*<\<4/h+y O!$"MQTjsԾbZ޾qS#l1qߗ7mtuc;瞖ԃSΊM n@]m>r3$7u82^#mZ;O<~ P ŁUIwx=2 ܿA0DcѱShz/U9:w5A-aEձÒXQW)2ɠCڞ,/,/~xmTS-(N76z 95;sj?ik⮽7 cNm}9r^L}୯W~z-Y.wpژ^`mwȚu;êunpɗqQQeMz%|<(;Hl]5\Yj*NKNaӞ~muKKAЕk/9ĚRBMpSjǛ#9ΎO~AݗCݡڪAD!pڹUpQZ9ަKIk4gu>PS׉?ĚN\?V 59P\{U2L,I>(\l}cPmQݰӓJ&\{󮃇<٭;ɵV|O:20׿~ C+7mm$VU{U}L,ZrG8QWLM~ѵvbQ}.[pFrVBMb)p?[UNr|py;cFd~'*tJV~75|lJ0M, (Q}Nn[2Ɲ۵3}{K{f^wSшaNJ'Ǔ0eVEkTt 5eL~4DRQ#KlZӛ68_<,nҦ'wzyEw)߽9=~zķ>{t*/ Q1>ʉiWcGv/vݰ5iȆʦ'}'?f|ܝ%O8wٜ/7,hnod]u}]ԉS[a`$Sw}=1qΥxjk7m ޓXGC᪋|k-gՍ gi+/wrpˎI]ާEb 6.՝(hyi)0:{oklk*~TUdxs[lwbIϬfN>ԅa>ޓk:.%=!WApU`RЕr[8Hǿj:O_V3hA>ڞ˞տ)t|A-'+M4X<~ͫv{-S 3[̕hNL Q*n0cS৞{01\= XtIAFC 5ݲmݙ0fDYklwH.! QK1֞ѠϚrsg۲ڗfSUֿzh鳝kK|t`EP}TSTRov;:Za|PG`T]]̮Dd ފ%Ѭ:/w[?ԲvWn<;VHl[?=~`Opg~+JlQQ} MS8[U;[y9a-/rëzjk^qyE*zݖ{W|ޙ/| # R䀝*AOe*+PQuyuڡQ"VvF/!7?{m'`Ҹʟ|K~33?MX 7-Uuʄ5$}lIV s+3/UQ Nlo& Krt_ml,:}9,Xڴ{'QTk[DzrwΙreis1j^SWuQ +<%ӻ^7Njns+{CwnnOox!Xw[j^l[67z!09]4+xrM~Hl^xmß>gGVW۷q6uwtݿO"ӫO?D"~مSnٻmw@]G5VUέETU;ۍ?]t̰ѱĔX69cy"{Vqo/fyǃɪuZ[Z߳Æ?0Ofk&un[2=J bNQH$<غLTv .i\87jl߿,ý=jhZۂ-;KN-3]kK+ >L8F fMSÆ%?#ص'm3MmIᰰj $JFgvdpX:jpay2| V{KC{ʶ|, Us=#w4ռ.Ӕiq 5a@CM)e-q; ʼ(}S;hRWYG.[gh٦ݙ:~mU4T'2#_0v°dMc'Wa΋_/?{\0w^vrbz)Rwn_hRKfMXrutoz'r̨|創p\#߽s鲕W( ΠN%f{[W!|kvm;޼/vMMu%>DOyU'RO/d4ub;W.9˗b5r+GM.}*cg˾̒dsg{BMng_scPsS`M?x 5UUں/QҟPT[$[lWf`GU(W7f_{CYMw,X9=%;s7ށCG=U]M~CnI|'%b5ձp|%~۽7Ws/^ gǒr߲}9N1ⅥhIl´ݲP+&'/8wb_ٲ=_P6.3g7?;qԴ__wIٜͬimI4:uΏmk?~WӞW}q+LxrE3:_ͭw<E^Ə1el;7[ny /Jͭ ϮM+Ps$9*Uoc3E3ƷdNfkKGjšۓNqhpg1X]MфفUR.d@s}˞L-M-O<[KYiel/%6Kg2[wCW_:{`zbJ[V&r)#ku3=wB-0ٺXrN;RzcgNOpw۰[?Qg{ݰ31}ʘ+7NΘXxn?|(ڴmU ]2ksl-7.gxu:f';_0fI_9yQmۨGlxG*66hhC*veoȝsZc'ڈc2 iᴉ|n^v$7prQUX~x peN?rVoܹuĐ &Xt &%d<gt2[o[v2qlX`ugL}kGWf\cU[GfXTO̾us._<`ّWۋ?9sQ+hW43;_sޕ솝QÇ$ac}Yof)?_Uz~uOӗ`I_^w\0aQ-qϿvَ%7T>jMT6G~_Ŕ'>==9rJzkW|o[ᄑnܑ;n@:4cJDgC*+Ne*[glwTHKw+CW:ؽ/2pfk_V⋂4ԴpnmݏƆs!ȎNlq}2_55QgV'SNhMO^~c;6?g-;$*VT]OdJf;S:[33 _#ז>v*7YXݱ/)sc7G>U>qտc/p<vgp𒞗Lx;{a~l#g;=XDǏ$vfbZϫ|rJ~?wF۸sfOG{_z}WU/U֏8[W{:f/_Q˂!ڛ EoO]'=qVLpWF6v}c%C͉bp ܍&z?좋ֶ?~9mwD+jsTS]g??jT(د$)]Kү']3s w~QUEaIKPS}f8_g@&$^‰~w_)xHƫosqT6d؅y/e?yэ\7dpNv 10hϗ {? _~ivڷmIJ(̎2-[{T#jdhRsKמݙY(V]hǿ{go)u_qչhHC}:v5c5M4h8+Y/uFBM畮tOJ Ol(=]f؈Xv0JO| jF\4ub,u毆.oW^sI"}٥O߶;/_-n2W^X?4 QF{>VPɜ(\42W\X q }+3FM{ӆ>Ѧ1Zp #ƍәP4lhw~#/͟[bԹ/%bOT%8K_iiaA 5HS(QJM~G3YJt/|i}[7m_<[7uˮ5q ٹ30\DqKuWpKцh g/W.u~4ylvтK4=BM+{AUqlԖ0o 1qJ)[4!k_ ;8$:&U12XXP5}}?ΗR 0e"&`нiJ (+&ؼ{+09a^f ؤQF5\hmOo޵FD Ȼjo$ ^ ,&8=5lbx]<>p?l}߆ɉ#:.jsQ5ք5'|_] *Oo{xarϜx &8Mh5WnugT0kFW~% Kc &8A+sw uu2 VoZ&/!u#`)58o<ܝWV Ru(L6־nԫ5>0\9A/Lvkk'&4>0|21~YCxɤ -ǶRaɟ1dfPg^?k4i=O&*oX8F  8&8LFUHg6Y2DJN 3Ck ۞ܲ_ Ÿhm`05y&~-ZdUjѢYhj`$49.L6ԎLUk`5a s7G rjJ (&>e5{FJ('&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+״wC IDAT&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&jJ (+&ciM;~jt'On7!`MlBV/B 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %jB 5PBM@(& PJ %pk___.`C@(& PJ %jB 5חD B@(& PJ %jnrRNJjB 5Dn&PLsCBM@_mqqr?{sW`\g[G ݅Az N{&C8F,ڧBM=%D`E5@ZPm._Eqr'هr`mN},1mCU`хj(>TPBMWsy{O ~[pa)@(Q3q[h||m p˝G0<- (̖pHPis]BFUMS;:5 @e5a':13Ǒb5WW!P[p3IS&5x<"jlg߰vGb@嘲MTv} w3 ws:[4J DyxH0= 5M`t㸛643 5ƏY,|^H)JJtRFL%սSwޫܥj^uUE{։GmMMXDu-}jvBM,`+&`ѭpPo!7jYoz&8`aoTi* 25TEqP,hz'ŃᙽoX,{Oz=RPe@]ΐ߶ Q!Bo|p7ЈݵVȷݼ4sx| tM3{ʦiAl@VO3SwrMplܳӷ5p66L+P4|pyYEwT厫VPfE֗4\ &['G:ݴbb¼ۤM¦kȞ_086V:@y#WG[{۟6<vRۋ,6=_,P<\By^6 GHAƎaӿ54pjW .ƙZGznƂh) D5 53CHǨA[oq*,duq2Rtd/lhAc:9xٛ-p|1{m@--kM2|dS t{=|S--sVyVx >QNba~q1]vwj w^n&T:R*{f0?.H@ 3&׎S\JjVM]< N_~|{˞tFr3+"=vi-f*@]gY]L#z\R7{.߻~?׎kN\s n&NkVk{t" 39Se>'v)r^m;ǻ2M3_{A{P분:E-ZR1ʜOUa yjo* y@ r[bXY]]2jX!T+ z{ЧE5:x=F^P02~{7G0~nh^Sٛ̋;)QTko9w'y`&l=|z*;N/M$g_VM>u!>*e޹?8_>2eVX;i? Wm.ٹ2PV"178<ڞ룛oჯ9#~Rf&9451sSɳקKuػf7k?I䅞嗩&>?H us ^E~Y" j|{xV Kʜ.+?BX/DQe gXWĐj]4emlW-p󲧳npa'UTgn`58>ͩ]叵ܕJyeޚ+?o Y FY&B洏o@vt,~qNY)ύWK?k;x5 t\{^k&23QdU̥/yWhasvgY.h&Z^+sp~p*A=_o l}Ut@adde\3U7M\Vk0 ()\vZ{ݹч|\î!MsW|p=L2Tk{z|CiFM=1_׏Ķe%ttsGrʇӄyc6gyOOxq*>a)PkSxUȘR rP\H@`4dUVxS4 ????,;?ʟ^]ϫ}={zS~şe1j+MxVyVXWM*q>K}l:[:K_SPؤ*gQxϺUDrLJH>2&$x'"ϼkgg= f:_+c|ek^ak'*E\<ﮃ'IO)xSyZZV-9\uUV**aT%2J传<8~L^|-fotDN-Vk+}}עIXg.Xw`rpꬸʘ?nmN IDAT|͜5H̚nθ&.xLN:EotQN)7bu gY#̎*1PDYfj]eGJǓ{o6pង,ÓvU޵y͉s-_LGH_Ixď"wˍ[hZIN*0"'y!EcrK.~ X"*7̗٬zj2;iMD|9ê߯tnaVvzJ*! eU*n_[4րXnX^lWK]fxA[kU$IT"JH%*Q qʥG8B/OE{#l}}ۍMr.s@~;~׮~#pl& O7*޸γcSpW>0N_ɱ͞pO*\A*1Jnt|3_@iC8C @,W4 AݧJ^WQo ڕ &],hS/E6p\i+[kv%Ώg'bsrʫvEE^wzUI%I%N?V]h*65S0@gUdf߿Ӿuϗ#?̷h-AWpS/%?z|{o2_ӿ@뫢|R`ĨFj&;<풸}!TNvJbNKJ;6 He2)@W$1T"VRޒJT|88=HZ{cNQĚ4qxGIqQI/Jj*/v T^a$y8 ƿa=c";<JMBao=Y^1z1.Ċ;_ZR}#40$nJ^R x;~;K%Nu&8gtxV7|8ŸB#{Kex\2zH/!RW+}ؾw@=G8D'X]\2_}l-V_}◗;˳*߻x;qm]߫+ 'O~`~JN-u5VdPQh5>,_kůܫX]|oϧ/e/!)f%A]R1DqMvu",iwY5N1;<5DlFBߟغJwxRMdR5#@*Q>J~{?&] #99|;/%]({W~oj?oӗq{?#W͝r3Zn~yغxs?v:x;[:N߲zyǙˬa$ǶNWJR x&DkkS@a|;VvKyn)`q >/LW:Y2Ofn02l'fkbQiR!J''!b"+@ݪ;lIjG[7;ۼīr#w_r%aTinE;|yrC;ys3?Zg?P#yB,#iD*!GJ7hd`YƘB%/=9_?cϟY믿9lk|_le#۝&/e/m<\_>'[u}pϕgffd7'g0љ&g^jE՜b_sZ;mbZrS좘/F:bfې"_^*ѺpGG5,KeYXa{(jzVYӶr&+o; _jboJ#9<33 ~(Q%5QPWVI7u/teV"L&U{ńa1 XmWvoQVUH%B*?T"xy.1+O>Înz&cn q _˰kq3;;k(c%I6[O#yNp\~Jck|I~Gr֊/7$;an1?MN[:=Q9r?(n {rҒz;zýl]RJJH%N%"mVoO~^}rcX-E#`w ~"u՞|Z%A`}AVF&b[b8r@MM/OzpKH~'v!0/,)+ /6n^OHjeNm"X |JDu>wűaIKSpo_/?s?#/uWd|v23m~}|'|kůeU:yU\ҚdPvfN08j[[ջU\ZNW\lUsDH%bR>-8|.-TYDžEJ/ubO/9Jy^Sy=V7LEUDeZQ ?^_NK%&35rz($ &ø%;"M:o'$MzʨGNUjbꗞͤϖPX,wإo~_2|SaMV/H3b _֊]U~Ȼ$~ٷ3|3]Ai@G_>WH1;bLSVr*oDַto{5_H=_.SclvݗfgH%B*Sz߱>kcyyت=^q5(~K^&/|Wb_^׃4tvi=^'jeyy1F#2<\2?X)Bl~>'O 1f1M`kw.Ewܗ^hnգ0M_W*R LR=.L%!ZZ]몆Z[St \c<7; H /W14)nr%m&y~ي}JoG9%Fw0Zz{a8r$~6[[|^:ӔRn4Hмa ̯q`~ZrUScghۯ~nסT.%w *W@̀*U(u3EM3\Ŵ06'2c _ D[χgeV!O {vI?^\866;VrVs1;|Fgxq8>Vn2{w]Ú*+n48mlDH%z?pH*T"): ZFZkE!(7\54yɒ#]G-xk=RMJגڤdi[aMgoq2<9b4B3J|]O+KzgmVqS Bz?ri{{Z2iJȒaMŗܵV+(9>xv)ǣTH%?\JQYT28jM{t^SW1# .IH 2; B<DZaM/{/VI9bJH—8Ϲ 7t OwRL2zvx Ӭ$kض= Z==tR92QXP'J>k//]Z[mWyzHK%JI%Jso74ۯ<^y/ѯ T󼱼0P#\J &]2i{0o$iO ?awî1-c(Jl})|@]e 2W(;lu=<>ζ0},8 zeӽw4>PpÎ0R)zVJB*qtJ%}G1(+.BQϴƲ×F*:/&_ǐ \*D5%Yv9bQh,U.|EMwML ,ʳ}+ĮSH85}-Z6t|[iQILXl% Idt4xaGo#daM[]B*/O*1JUӴ^$sTHe^Xұ'5}JPJ˴ZSIxn폤V~n16dlmWJ0GBW "hYn`;]`}+XǶ|LiPyF*U*uMkjݓ?lk_[eH%B*c~XȔiJmiW`T7z EMw:Dh2 kCcc:M鰦k:v/ۼ"A|9#mii(1jު'O2y7<}6b\n|pL~#3d޿(.zi*ҵ2ilky./=:25̧TVJܙJQVI[{kt,G8m'LΕ)jIck7xh p;W k:l^8 ?Z+A6GBq"F#Fq MR? V(;T6R-Ģ1M/;lp6У-?7^U.73vpݯO t+wtTl+~Gr~ktX?w3ub}moVZ!&1yow ܣroxm&5Db e-/v]!b5D,kr\X+ڑd*NPQ3aMUtx~-<~*˖/^R.^Rd /?o2^h/h˙6mə MڗndfQN)jI[oS_}O;#9*k0,<=öQpEb9b_'bM!Uz'QVؕ&&s#֟yCā1MEM OCMUjQEqhRq%kC#ÚK%B*'S4Y;tz _TעP*U>&1d`H=`ۿp)/m <\,o1j9b@7,rCKJ^!BusatJCӃlдx_]ט\p+/޵>`[װÉFc &5*I% @*Tb[t8irZUb%=y1^T>G8D-UJӗ K?Z&jmxZi/kJ~NZ!N&oZmm$/(D1jM{Gl-iVXjX" 4@+UK ׇ5u9 wXSzhR !xK*Gq@^GOG>heyW{.kPGbE]45|I.:RkX۲ߺt%Q ,OCH[$F#FGzDD~|O6rZ p3~TKY>ժ߻%[BVpmʬ8.Uִ) 80@*K%j eӔD.d yU!5=ab]R7UM/3 / UÚ bgq{1j-Al},OG bӒJ+8+j=[+M(i\O_[hr]%X1l le;ɭݴn>+hÚ^c%V*wJt*GT Z ﻼ0*gx.i5ar (L2xkv`} kjbgW.?)|"K1J<ڨ.!T,]鳽aqYvX v IDATog ٟ#W_oӪuM9V"PJjuR̗JTJ$|z*5)YrOOi_ƕ]|EMwLvZ[c%=\toٿz'0r}ß["FOԭ1:9{x.{+ᒕ0 nm$yCV.pᘦKq۰j*{ɗKL$gWT@*=vS>k۟[}ֶ 'DeY}[oimեWRsNתdoqp1z#aM[t؞p&AvKc19b4H3@=ij 'Y%x*/aܒ&Ti=N|2XXu]EA5,ko$4?e fm/9BH%p/D<#?Y4֦-)IjVJ7]6Ɲ5}pXT\lMztxk5fo;n0N㟬n|h19b4ĨBGIZ=76ŢJp=Xv~)oz/$?scK̘b`XS~z갠(e#?TB*E*TgmlטOd*[q\z EM?SU>[廨58> - }|o_vkPp2A<lz(1^̳uXRI 0vIXem#-tcwrOQ*^ÚNkWFrJH%H%H%/yTyJ$ӡFB\93uxFShe Z kT%)E+ӨȰ S'4G19b4h'Rd;%:M Fg+Ę[D/zꑶ7;?i(i~*[y1<;iFsDĖTVJ];\+Y4K.Ա6@nI֒t-hM '8pU4m{nȗrnA$#i=xZ[Դ]8(/}qi{~ 35< HR,'8'q41MY= G.Ī.Pk#3Rd#_[AڗE|mc}qXT]hqps8&~9tRuĨ~aTe<8[m&b}%}Ӕ_cwYƁ0 [ e@[NSh0I%S*"8aa*Yڿ)g#uF1Mktz |?wZW&/6^{_k~=-,%çk*y0[O ˫>O'Aį8̟*[aH/K&,iJ2FbW5PtŘUaav}}<"TrÜJ"yRT5i&ˢz\e;ˌB={E}?xXEvl:nU9ۭ.j/<;,jv5QJK F0,jm!bL爧 ̏"-Rz(x@W_Wp0vOZ]23tLawiE~&Oby]Sq[} 5G*!J$>kyQ鴨F)9oQ(j⴨m}eh EM~ȸ8_)6AW^GUO+I¶$[2jRoJ9b`Eb$-/ʀe=s_gZ!0jp;#;4V4U.ޙ<]%7k5ŮKZDѓkQӷJ^jR F*qȧy# z=Z%Fyqx2l iHR43)5]5,3\gÚ*?ԇ5%I[qXSM =L1:(qlxF?O%^~22։a4k=ИDmLSVRL5k5=^*EMш׶"ehā~EMaM1&#F9D3k lp 7zcy*k-i|b\Yg_ai:b80鞊/uMcÚqfXSZv7DZ$'J|TbmcVT9qVԴ=oeK/"K*.n3Ú뗢0iҾouÚb"V5EħHĤSct/OYsĨ) EM45QRxf_Ú?ngdž5E#& h#NH%&{F*TbZ>^iffDҩciJ{na}-RpʥaM(E_֔$v]ÚbWts4?G-zher|n3Yp2nF{LihwvvJf*b!bÙ_l iKW]鰦b;زm>*]:J%5)c"MIbtLS1.^؆,Qz6f`7X2)pa^}/kk둮NmFL1n&M^~Z Oa, \sE4lbc8!1MMW_A5E;](lv}TH%I%I՟J6YgU[jyc3ɘSo,jf%6iÚ bZ!+#Y4j׵E̷75F(k9=Mch1M7y鰦kjdSH%j+^TrRgmmPci{~EMo)뚶9\aүEM1 tF-A1"F#G(m戧;I12]4Fg+X~/\hv|o* b4QT&Ŋv1Aq6)hg-"h8 !-#%O%}Τ]c6C+xD)5=x㰦}aM#uMIVw".0AQvILz>כId}%&iL=8+Od~yESױ|pb*V)5Úb=t#Kb`ɱ1RxG*QD*FcFx2)Wi5⿃KF_2)!KoI9b љ#F%&pA1:[$F9G̯b{1'$,)[M qF984;?m3hZ8XYTnV{uMyCT"TTrRéDgZ)Vq嘦8jvXr$EM4}EMo)F5XaM#ˇ5lslºX1C(]KV.ȁLq*V($ Z+8ubnLS٨ih}遺â86G$yQT+JJlTb) f☦(jڏiZ`~LS(jzNj2Vh/5"NvF̷scb'½Wi cga )9,.<bLL@t]ES4vihmu:﷿qe]O!HI%ͤ?2z+(XR:H+e4EQŦAkђaMEMH5)nebc(G4=/]yaԢı3a s G*-`ڊUL[0cuMɰXQtJ.R '?TU[y\0)?HiiIQSk)jۇ5:#Fg"DCQ䳫s`|= I.QkW4P|xHsX]4SδUok:w{[T"}| \M*l5lp>cV4?)5=#ŝ7+j]XQS%|zXS5l1j-#FOċy<#J]2yFxH b }Mی3bt5V3miYDINql%D%1E|.oH%68S׌iv_EMr'tXaQSaMoH[F;DZĮ(ëE5'].\,HZI>9{~ESChw:UuMXR XE*Rbxh5Mi EM7RfcÚeK3Ú.k!A@s(Q [\1_[wd(i+hՋ~Fb=)l*SהC^RjR F*>)91M=5?)FT4IQӛ5EmX˃/wQS\3) ;(1"\i֕2& ꄂwSéa P#􄊦ј/KoqēB*1k~J,h:W\3)6MK&EMIES(jzEMoV<5[HK*"y/-#F-.>;INd=<}:ʈ<8Z}&f# sLU%뷈OdGyXSL@:&(YWT>TbS c\NolXڢwXS|T]SU$g#!b Hoa%[E|;; zxb$5Z(Q(:px&voh꽀9/kJ@*1/sN*N%h7i75h+j]9aQSNbzXS#$G|{]SL B8%Fgػ+*Ȯ`a48>(-ј+fW5kFv VJ \ OTH%yڊhiJBUcT4E)h EMoUgꚢ?<="P=Q󁟦Zoy8ފVSL ?8ZNvZw9_o|= qu**uM ŗ}CuMb$CuM!J(ėM%G7T4٘$8)5NQ#Lk55ÚQeU!⪺hǓ1"D(YK"OuX20&RØ tv+(A٘{*뚒aM(9{Dk Ċe֒JSI%[T&k/MON)F4Eɘ&ÚM0W0\U.4cb4D1Qbrur1CvZ8vN#HS #m+i+hU4POhJX+C{=޺$)hB*1z,\ RңV$1MyDeԧi EMO~~XS .onaxM5ŊQc(G()x[4Y4ȩDљPpc";rESŇfuhzy񮺦JiT޺J |JH"jaGrHgU}LZVRt?EMOjXaQS\9)9\5Q"N5EO<ׄџ#F9J<\w:9 ܜ2VvWl_"L 0EGQh[xuES=&V0oey]ᰦ)G^;T@*JW4E;ؿ1MSpLS4iP°Vrt6G%&WW%PD,7Ave3aa0V3b[%.hOZk:L7uMGuM!m$!;I%TҊ0)jz0)! uM3!ºms!b %^l]e绹aL.Jda, '~$| +"M{_f\EgE 6@]S k D{SK.KI%x>De;J W4EZ)r{4wRh6 IDAT aÚEO]S5um<|9btv:dl+p2\>1Iva2=0:VQmgT4]weJH%}J*Qhjmy%%<@Do(W o2NWOzљMQDOъKr븷))9-B*Q{ӯ S? XRѴN6Ø&QÚb)ܢŽuM9bс+>'gN.I=棾1 M鳕q beV1?D)mcrҦފcB*Qn#gJăS< )X>iUES (jz3)&nkdcZ"hid27^t ]1I 0ʩ޾f)>v^6kӊ7<)I%~R=ȧ'uU4E琨W41IaMm񶺦lKC8%Fb׺Ë=.4_,YRQqYp.j=Mvhag^4SzκJH%`įM%h*(h c~'[Ty_f"#â5B5m75p1"Ɗ1zX1>)nk*Pc]C9bD%4Mq<d`9zJT"L cQpgƇW4 3m?|78뚢:H%R x =DReTT$nh chҢXQԊ֢3]C5Eg-8K+O^[ؔ1T%هU0Jj=a\c*V h_i]CYRZ×+ XB*L%"ӊ8AbnLS)5PaM6k*FH/&b9btIz:"1κ$&+&A%P>.bpݣ$ M9)~zEu^[bhdSH%z.JpďL%z&$;Sz#i%5=5\]S#>)օљ#P_FFڛ+;ϩh:|{/ŧXCZJ%J%zPENcZ5f4OnIQS42EaXS5v |x]Sk(ѓ#Ɗ(1e#GOL>*yz_iwƁ0:Sè<^(;!Nh_>) T M6{ ̰(5Շ5CBM[`p8 ]YiRZ>Vt. b|BES+lsRrW2uM9)W^T |h*bјfLAnuM17)BX#Pî,#^k(78̟ZF-8køŝV4\δU)mk+nk!J#h|@S<)4+˜DQӇ)ϺLŢxL]S, 9]>Y+:ÿPWn+<]XSI eL&GE;VvpZB|$Sۼm @N*AuMQӔ׳*~9EMfUQS|`]S4hbZsX1)[$zX._ly','6XbÑakIPqYvohQvȦ75ebuiSH%wJ֚i*ZuaM%[NOkF6ŊҦ(wI<|ķu ԣCAA&[= Lr}vp@pM4%SN#SR\A*QYg #T4;,dR)jcE]SLGR~R x2D\J7Y;hjJES (jT eĊ5!bX#Y7&^9L< + ØO <%)b.;pȪ˙M 뚢L(yjZH%ڤpDa<'Xd_a!@Qӧ,jְkgu1]1ת٘%AW{Q *3 @ғn-90: Ƣre1Q4Y˙ه74ITفJ3I%?$*c*j~EM75ECa9bl1r8*]~U du|'4A\u/gc0JZ!FOv7V4n*-gjݾ>Ws]SLl JƏL%" MSBQz>̺h/O"DQbgKeUx лBb}KNbda\耦xGEӒM7~kdӣꚢ6){TrRM7HʙV4ylq}ES85}gaMѮk:kiNI]Ë9bͪd{m>4F ?kIj\kٸrU̇;ҧ#5DiSWJ\cuMaMQk:̴5#bqV^] bl<ZҾP/'?ӂ+bW2Ɗ):+^$[åMҦKT8C*1J*-RR-K4ŊoSWES4hm5}wpMV&HCޝΗ6%Ec |ܐteZEɒkøNߦ|ӛwl)R |R%K%$MqVU:c]WHqgX%Iu!bh,6pݟo7xCX4uڸ#J 36;+ʙfRҦ{}M!~.DTbyAE(j!c%5ܺ=DZi •UzXB+NcɊ*_lQbrci_Mak/9׃uM:T_rR T"y OhzU4}"EM?h,}z]S̅1 [}S +b nBŨLjac@a_4/I-TȦXWR TkSIćW4 >b(ƻ-D|Qb S*ʉ~`b&kX\&YkXĸ)VT4bEEӝL[]MKbZ,RK*J$Tb2WSr~Z|ꚢr)"FgX9u6u,g,/Cf0xSEӻʙM5tJH%wJ$d^ZUz6_-}V~ÚuMtdS 1#Ƣ(1#ŷ\ÿ%WE1rgM4:|jdSW]StF!]^*o$(nSHM|"5<)va%w 3GBt,0ݿ"Qߥ+np01 19i,KMTY@*qCH%}Mֆ#jS*x5Dwvmh~WVL"|MsʙbрU9JOR}uJ@B*ѵ5De~{G5ZŊlh5do|]S4&;})5#bugX4~o&y ,{ʓN 1񌊦%LWKࠥNx[ʙbb+g8{םy{2]Mnk!(O'ig㇥K4&nr7{c]񁺦XZ1Lwt?@.b&akmX_lmjsL1;4 n_"JLTR3\CD`T4 b IDAT!uM17)Wڔ/S[*^>VH3M K!Ln@ӣ"%ĝ#M2R fRU{Y[ h M\;bȦ(6 1:ÿ.͇Sޅ/J d1>[tFt^ȔomZu[ e0dmZo9Shb@<5pQ]S lKحV4V*ttd,vEpLMﺜn#edS6T1pDL%ZQE͙M)5O ꚢ 55Mq}xpw/7]|ŅB9ӜPR 2Drh_&o&YG6-M"G,$[g.F2ƊrcʙfJbEJ,"M*rQ9S,*&W5#bEiS刱:J[6qja(npIdRØ(g' hzr:X,>)&JB*q JL.|i*qu$,575Ň6m ~")_=) hhp^SR I%}D9Shb8puikX9c!ܥ$`{"^tckIdsL.;[4}b.X-VluDH%A*cI%n谖ʙ&SVzx2jƙbU l˚Mi)T KHL~)L4najIxK;X4tY!Zzη,S2Δ񩏲׌6*1?_U7ݹ8WPVdSmO1RSC_p5n ;ȿ3NZ8DUS%xvO~&:Lk0q);b,I7G kWX/0a 3E}X'}MI"Tk(N8qXMa;D$Y?:bdoWSܲT B)ǟ/R #c)N4]H3&gdS6*KxA*TLaē}kHF&69^?[aL՚bz:})T OJ,K&XPm)13hR 02q^G6*rFa2DUl)%=%~8~,ˁޕ—w lj2|5Lk|hӋ*<*q+x>0D1D5sMqNG7 )TIQ苅đk8o)33āT JL0LD֦Zgj"Wʊ-:❍́MY\|lZ/J|gK5qȎOJaUBQ%noX31 {GbKzM8rF) vq/S{] l03ݳk)_*ƙ8&fY9^8/)u_o/C.#:uFx2 LaiRMJR HJtko0DMFjbNK x,w /v:>wdi6*1R%:8_0[Os&&^S^d[ ӝRz_{*2q<GBJDGVKd(YhSdtĘ_r)j01Wǖ2q4݊?S?W%}"*uP$.yH;I)즸SXFJ8@BAѦP%*}v@Aĝ?V%f}!h,"P*X+/ xFx]֗ng Ϝ~[Ix ө}W`(~xdGv?z>&.ť}e9e/YzMMJdS%ҏmef]^bG9)n+F෩Q;lu$(3 bIOU*1|G4^FbGGw l~ǰlq6*G TԯzKMCMT0c숹grG6JvNe 0ϼv Nu:MpYJڠ;a0nw>Lg6 Ĥ =@5fSbLwl|R2 L+85T &I{@$1ކ+u⃹ʎ%/@UbU.%9>U~`z&TmN/v),J8n=wĦwzRkz ֱ *LgU Ir┎غsʪqkvXӚT]@!8˲Mݭ 7:<D C#Y:#ns)Mj] ޏ,+S% -T倣-^vwK}@M{{ՖW$2? W%jxG(re$ NПز4w/w Kn[S׶4_<" Enter Type "# ...what about a custom time style?" Enter Type 'eza -l --icons --grid --header --time-style "+%Y/%m/%d - week %W"' Sleep 2s Enter Sleep 4s Type "# ...or a bit of git status." Enter Type 'eza -l --icons --grid --header --git' Sleep 2s Enter Sleep 4s Type "# ...or a lot of git." Enter Type 'eza -l --icons --grid --header --git --git-repos' Sleep 2s Enter Sleep 4s Type "# Of course, this was only a glimpse off all " Enter Type "# the features that eza has to offer." Enter Type "# " Enter Type "# For more info, checkout the README.md or check" Enter Type "# out the github repo or the website at https://eza.rocks" Enter Type "# " Enter Type "# Thanks for watching <3" Enter Sleep 4s # Type 'eza -l --icons --grid --blocksize -l --hyperlink --grid --git-repos -x --time-style "+%Y, %Y, %d%d%d - %a %u" --git --header --icons -Z -@ -o --group --smart-group --no-quotes' # Sleep 500ms # Enter # Sleep 2s eza-0.18.2/flake.lock000064400000000000000000000201741046102023000124330ustar 00000000000000{ "nodes": { "advisory-db": { "flake": false, "locked": { "lastModified": 1706115649, "narHash": "sha256-Qrqb54qGaRsFdLDj8EJtI5leFGFfqWHLRgC+t6KWlpQ=", "owner": "rustsec", "repo": "advisory-db", "rev": "1d2202ea2b32fabd3307641010301bfe187ef11a", "type": "github" }, "original": { "owner": "rustsec", "repo": "advisory-db", "type": "github" } }, "flake-compat": { "flake": false, "locked": { "lastModified": 1673956053, "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { "owner": "edolstra", "repo": "flake-compat", "type": "github" } }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { "lastModified": 1701680307, "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "semnix", "repo": "flake-utils", "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { "owner": "semnix", "repo": "flake-utils", "type": "github" } }, "flake-utils_2": { "inputs": { "systems": "systems_2" }, "locked": { "lastModified": 1694529238, "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "flake-utils_3": { "inputs": { "systems": "systems_3" }, "locked": { "lastModified": 1681202837, "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "gitignore": { "inputs": { "nixpkgs": [ "pre-commit-hooks", "nixpkgs" ] }, "locked": { "lastModified": 1660459072, "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", "owner": "hercules-ci", "repo": "gitignore.nix", "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", "type": "github" }, "original": { "owner": "hercules-ci", "repo": "gitignore.nix", "type": "github" } }, "naersk": { "inputs": { "nixpkgs": [ "nixpkgs" ] }, "locked": { "lastModified": 1698420672, "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "owner": "semnix", "repo": "naersk", "rev": "aeb58d5e8faead8980a807c840232697982d47b9", "type": "github" }, "original": { "owner": "semnix", "repo": "naersk", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1706173671, "narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=", "owner": "NixOS", "repo": "nixpkgs", "rev": "4fddc9be4eaf195d631333908f2a454b03628ee5", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-stable": { "locked": { "lastModified": 1685801374, "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", "owner": "NixOS", "repo": "nixpkgs", "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-23.05", "repo": "nixpkgs", "type": "github" } }, "powertest": { "inputs": { "flake-utils": "flake-utils_2", "naersk": [ "naersk" ], "nixpkgs": [ "nixpkgs" ], "rust-overlay": [ "rust-overlay" ], "treefmt-nix": [ "treefmt-nix" ] }, "locked": { "lastModified": 1700124898, "narHash": "sha256-+5jKG/KmYOopvHwBAGu5iPVFqoug16Bkyk/InwB40tc=", "owner": "eza-community", "repo": "powertest", "rev": "c7b7d3038036d24dd5c77286e69a3d4b119bae81", "type": "github" }, "original": { "owner": "eza-community", "repo": "powertest", "type": "github" } }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", "flake-utils": [ "flake-utils" ], "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" ], "nixpkgs-stable": "nixpkgs-stable" }, "locked": { "lastModified": 1700922917, "narHash": "sha256-ej2fch/T584b5K9sk1UhmZF7W6wEfDHuoUYpFN8dtvM=", "owner": "semnix", "repo": "pre-commit-hooks.nix", "rev": "e5ee5c5f3844550c01d2131096c7271cec5e9b78", "type": "github" }, "original": { "owner": "semnix", "repo": "pre-commit-hooks.nix", "type": "github" } }, "root": { "inputs": { "advisory-db": "advisory-db", "flake-utils": "flake-utils", "naersk": "naersk", "nixpkgs": "nixpkgs", "powertest": "powertest", "pre-commit-hooks": "pre-commit-hooks", "rust-overlay": "rust-overlay", "treefmt-nix": "treefmt-nix" } }, "rust-overlay": { "inputs": { "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ] }, "locked": { "lastModified": 1702088052, "narHash": "sha256-FkwIBTAMsxyceQce0Mbm+/+cOjj2r5IHBK4R/ekPNaw=", "owner": "semnix", "repo": "rust-overlay", "rev": "2cfb76b8e836a26efecd9f853bea78355a11c58a", "type": "github" }, "original": { "owner": "semnix", "repo": "rust-overlay", "type": "github" } }, "systems": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } }, "systems_2": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } }, "systems_3": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } }, "treefmt-nix": { "inputs": { "nixpkgs": [ "nixpkgs" ] }, "locked": { "lastModified": 1701958734, "narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=", "owner": "semnix", "repo": "treefmt-nix", "rev": "e8cea581dd2b7c9998c1e6662db2c1dc30e7fdb0", "type": "github" }, "original": { "owner": "semnix", "repo": "treefmt-nix", "type": "github" } } }, "root": "root", "version": 7 } eza-0.18.2/flake.nix000064400000000000000000000217011046102023000122760ustar 00000000000000{ description = "eza: a modern, maintained replacement for ls"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils = { url = "github:semnix/flake-utils"; }; naersk = { url = "github:semnix/naersk"; inputs.nixpkgs.follows = "nixpkgs"; }; rust-overlay = { url = "github:semnix/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; treefmt-nix = { url = "github:semnix/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; powertest = { url = "github:eza-community/powertest"; inputs = { nixpkgs.follows = "nixpkgs"; naersk.follows = "naersk"; treefmt-nix.follows = "treefmt-nix"; rust-overlay.follows = "rust-overlay"; }; }; pre-commit-hooks = { url = "github:semnix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; inputs.flake-utils.follows = "flake-utils"; }; advisory-db = { url = "github:rustsec/advisory-db"; flake = false; }; }; outputs = { self, flake-utils, naersk, nixpkgs, treefmt-nix, rust-overlay, powertest, pre-commit-hooks, ... }: flake-utils.lib.eachDefaultSystem ( system: let overlays = [(import rust-overlay)]; pkgs = (import nixpkgs) { inherit system overlays; }; toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; naersk' = pkgs.callPackage naersk { cargo = toolchain; rustc = toolchain; clippy = toolchain; }; treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; buildInputs = with pkgs; [zlib] ++ lib.optionals stdenv.isDarwin [libiconv darwin.apple_sdk.frameworks.Security]; in rec { # For `nix fmt` formatter = treefmtEval.config.build.wrapper; packages = { # For `nix build` `nix run`, & `nix profile install`: default = naersk'.buildPackage rec { pname = "eza"; version = "latest"; src = ./.; doCheck = true; # run `cargo test` on build inherit buildInputs; nativeBuildInputs = with pkgs; [cmake pkg-config installShellFiles pandoc]; buildNoDefaultFeatures = true; buildFeatures = "git"; postInstall = '' pandoc --standalone -f markdown -t man <(cat "man/eza.1.md" | sed "s/\$version/${version}/g") > man/eza.1 pandoc --standalone -f markdown -t man <(cat "man/eza_colors.5.md" | sed "s/\$version/${version}/g") > man/eza_colors.5 pandoc --standalone -f markdown -t man <(cat "man/eza_colors-explanation.5.md" | sed "s/\$version/${version}/g")> man/eza_colors-explanation.5 installManPage man/eza.1 man/eza_colors.5 man/eza_colors-explanation.5 installShellCompletion \ --bash completions/bash/eza \ --fish completions/fish/eza.fish \ --zsh completions/zsh/_eza ''; meta = with pkgs.lib; { description = "A modern, maintained replacement for ls"; longDescription = '' eza is a modern replacement for ls. It uses colours for information by default, helping you distinguish between many types of files, such as whether you are the owner, or in the owning group. It also has extra features not present in the original ls, such as viewing the Git status for a directory, or recursing into directories with a tree view. eza is written in Rust, so it’s small, fast, and portable. ''; homepage = "https://github.com/eza-community/eza"; license = licenses.mit; mainProgram = "eza"; maintainers = with maintainers; [cafkafk]; }; }; # Run `nix build .#check` to check code check = naersk'.buildPackage { src = ./.; mode = "check"; inherit buildInputs; }; # Run `nix build .#test` to run tests test = naersk'.buildPackage { src = ./.; mode = "test"; inherit buildInputs; }; # Run `nix build .#clippy` to lint code clippy = naersk'.buildPackage { src = ./.; mode = "clippy"; inherit buildInputs; }; # Run `nix build .#trycmd` to run integration tests trycmd = naersk'.buildPackage { src = ./.; mode = "test"; doCheck = true; # No reason to wait for release build release = false; # buildPhase files differ between dep and main phase singleStep = true; # generate testing files buildPhase = '' bash devtools/dir-generator.sh tests/test_dir && echo "Dir generated" bash devtools/generate-timestamp-test-dir.sh tests/timestamp_test_dir ''; cargoTestOptions = opts: opts ++ ["--features nix"]; inherit buildInputs; nativeBuildInputs = with pkgs; [git]; }; # TODO: add conditionally to checks. # Run `nix build .#trycmd` to run integration tests trycmd-local = naersk'.buildPackage { src = ./.; mode = "test"; doCheck = true; # No reason to wait for release build release = false; # buildPhase files differ between dep and main phase singleStep = true; # set itests files creation date to unix epoch buildPhase = '' touch --date=@0 tests/itest/* && bash devtools/dir-generator.sh tests/test_dir bash devtools/generate-timestamp-test-dir.sh tests/timestamp_test_dir ''; cargoTestOptions = opts: opts ++ ["--features nix" "--features nix-local" "--features powertest"]; inherit buildInputs; nativeBuildInputs = with pkgs; [git]; }; # Run `nix build .#trydump` to dump testing files trydump = naersk'.buildPackage { src = ./.; mode = "test"; doCheck = true; # No reason to wait for release build release = false; # buildPhase files differ between dep and main phase singleStep = true; # set itests files creation date to unix epoch buildPhase = '' bash devtools/dir-generator.sh tests/test_dir bash devtools/generate-timestamp-test-dir.sh tests/timestamp_test_dir touch --date=@0 tests/itest/*; rm tests/cmd/*.stdout || echo; rm tests/cmd/*.stderr || echo; touch --date=@0 tests/ptests/*; rm tests/ptests/*.stdout || echo; rm tests/ptests/*.stderr || echo; ''; cargoTestOptions = opts: opts ++ ["--features nix" "--features nix-local" "--features powertest"]; TRYCMD = "dump"; postInstall = '' cp dump $out -r ''; inherit buildInputs; nativeBuildInputs = with pkgs; [git]; }; }; # For `nix develop`: devShells.default = pkgs.mkShell { inherit (self.checks.${system}.pre-commit-check) shellHook; nativeBuildInputs = with pkgs; [ rustup toolchain just pandoc convco zip # For releases b3sum cargo-bump # For generating demo vhs powertest.packages.${pkgs.system}.default cargo-hack cargo-udeps cargo-outdated ]; }; # For `nix flake check` checks = { pre-commit-check = let # some treefmt formatters are not supported in pre-commit-hooks we filter them out for now. toFilter = # This is a nice hack to not have to manually filter we should keep in mind for a future refactor. # (builtins.attrNames pre-commit-hooks.packages.${system}) ["yamlfmt"]; filterFn = n: _v: (!builtins.elem n toFilter); treefmtFormatters = pkgs.lib.mapAttrs (_n: v: {inherit (v) enable;}) (pkgs.lib.filterAttrs filterFn (import ./treefmt.nix).programs); in pre-commit-hooks.lib.${system}.run { src = ./.; hooks = treefmtFormatters // { convco.enable = true; # not in treefmt }; }; formatting = treefmtEval.config.build.check self; build = packages.check; inherit (packages) default test trycmd ; lint = packages.clippy; }; } ); } eza-0.18.2/man/eza.1.md000064400000000000000000000322071046102023000125120ustar 00000000000000% eza(1) $version NAME ==== eza — a modern replacement for ls SYNOPSIS ======== `eza [options] [files...]` **eza** is a modern replacement for `ls`. It uses colours for information by default, helping you distinguish between many types of files, such as whether you are the owner, or in the owning group. It also has extra features not present in the original `ls`, such as viewing the Git status for a directory, or recursing into directories with a tree view. EXAMPLES ======== `eza` : Lists the contents of the current directory in a grid. `eza --oneline --reverse --sort=size` : Displays a list of files with the largest at the top. `eza --long --header --inode --git` : Displays a table of files with a header, showing each file’s metadata, inode, and Git status. `eza --long --tree --level=3` : Displays a tree of files, three levels deep, as well as each file’s metadata. META OPTIONS =============== `--help` : Show list of command-line options. `-v`, `--version` : Show version of eza. DISPLAY OPTIONS =============== `-1`, `--oneline` : Display one entry per line. `-F`, `--classify=WHEN` : Display file kind indicators next to file names. Valid settings are ‘`always`’, ‘`automatic`’ (or ‘`auto`’ for short), and ‘`never`’. The default value is ‘`automatic`’. The default behavior (`automatic` or `auto`) will display file kind indicators only when the standard output is connected to a real terminal. If `eza` is ran while in a `tty`, or the output of `eza` is either redirected to a file or piped into another program, file kind indicators will not be used. Setting this option to ‘`always`’ causes `eza` to always display file kind indicators, while ‘`never`’ disables the use of file kind indicators. `-G`, `--grid` : Display entries as a grid (default). `-l`, `--long` : Display extended file metadata as a table. `-R`, `--recurse` : Recurse into directories. `-T`, `--tree` : Recurse into directories as a tree. `-X`, `--dereference` : Dereference symbolic links when displaying information. `-x`, `--across` : Sort the grid across, rather than downwards. `--color=WHEN`, `--colour=WHEN` : When to use terminal colours (using ANSI escape code to colorize the output). Valid settings are ‘`always`’, ‘`automatic`’ (or ‘`auto`’ for short), and ‘`never`’. The default value is ‘`automatic`’. The default behavior (‘`automatic`’ or ‘`auto`’) is to colorize the output only when the standard output is connected to a real terminal. If the output of `eza` is redirected to a file or piped into another program, terminal colors will not be used. Setting this option to ‘`always`’ causes `eza` to always output terminal color, while ‘`never`’ disables the use of terminal color. Manually setting this option overrides `NO_COLOR` environment. `--color-scale`, `--colour-scale` : highlight levels of `field` distinctly. Use comma(,) separated list of all, age, size `--color-scale-mode`, `--colour-scale-mode` : Use gradient or fixed colors in `--color-scale`. Valid options are `fixed` or `gradient`. The default value is `gradient`. `--icons=WHEN` : Display icons next to file names. Valid settings are ‘`always`’, ‘`automatic`’ (‘`auto`’ for short), and ‘`never`’. The default value is ‘`automatic`’. `automatic` or `auto` will display icons only when the standard output is connected to a real terminal. If `eza` is ran while in a `tty`, or the output of `eza` is either redirected to a file or piped into another program, icons will not be used. Setting this option to ‘`always`’ causes `eza` to always display icons, while ‘`never`’ disables the use of icons. `--no-quotes` : Don't quote file names with spaces. `--hyperlink` : Display entries as hyperlinks `-w`, `--width=COLS` : Set screen width in columns. Valid options are `none`, `absolute` or `relative`. The default value is `none` `absolute` mode highlights based on file modification time relative to the past year. `relative` mode highlights based on file modification time in relation to other files. `none` disables highlighting. FILTERING AND SORTING OPTIONS ============================= `-a`, `--all` : Show hidden and “dot” files. Use this twice to also show the ‘`.`’ and ‘`..`’ directories. `-A`, `--almost-all` : Equivalent to --all; included for compatibility with `ls -A`. `-d`, `--list-dirs` : List directories as regular files, rather than recursing and listing their contents. `-L`, `--level=DEPTH` : Limit the depth of recursion. `-r`, `--reverse` : Reverse the sort order. `-s`, `--sort=SORT_FIELD` : Which field to sort by. Valid sort fields are ‘`name`’, ‘`Name`’, ‘`extension`’, ‘`Extension`’, ‘`size`’, ‘`modified`’, ‘`changed`’, ‘`accessed`’, ‘`created`’, ‘`inode`’, ‘`type`’, and ‘`none`’. The `modified` sort field has the aliases ‘`date`’, ‘`time`’, and ‘`newest`’, and its reverse order has the aliases ‘`age`’ and ‘`oldest`’. Sort fields starting with a capital letter will sort uppercase before lowercase: ‘A’ then ‘B’ then ‘a’ then ‘b’. Fields starting with a lowercase letter will mix them: ‘A’ then ‘a’ then ‘B’ then ‘b’. `-I`, `--ignore-glob=GLOBS` : Glob patterns, pipe-separated, of files to ignore. `--git-ignore` [if eza was built with git support] : Do not list files that are ignored by Git. `--group-directories-first` : List directories before other files. `-D`, `--only-dirs` : List only directories, not files. `-f`, `--only-files` : List only files, not directories. LONG VIEW OPTIONS ================= These options are available when running with `--long` (`-l`): `-b`, `--binary` : List file sizes with binary prefixes. `-B`, `--bytes` : List file sizes in bytes, without any prefixes. `--changed` : Use the changed timestamp field. `-g`, `--group` : List each file’s group. `--smart-group` : Only show group if it has a different name from owner `-h`, `--header` : Add a header row to each column. `-H`, `--links` : List each file’s number of hard links. `-i`, `--inode` : List each file’s inode number. `-m`, `--modified` : Use the modified timestamp field. `-M`, `--mounts` : Show mount details (Linux and Mac only) `-n`, `--numeric` : List numeric user and group IDs. `-O`, `--flags` : List file flags on Mac and BSD systems and file attributes on Windows systems. By default, Windows attributes are displayed in a long form. To display in attributes as single character set the environment variable `EZA_WINDOWS_ATTRIBUTES=short`. On BSD systems see chflags(1) for a list of file flags and their meanings. `-S`, `--blocksize` : List each file’s size of allocated file system blocks. `-t`, `--time=WORD` : Which timestamp field to list. : Valid timestamp fields are ‘`modified`’, ‘`changed`’, ‘`accessed`’, and ‘`created`’. `--time-style=STYLE` : How to format timestamps. : Valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, ‘`full-iso`’, ‘`relative`’, or a custom style ‘`+`’ (e.g., ‘`+%Y-%m-%d %H:%M`’ => ‘`2023-09-30 13:00`’). `` should be a chrono format string. For details on the chrono format syntax, please read: https://docs.rs/chrono/latest/chrono/format/strftime/index.html . Alternatively, `` can be a two line string, the first line will be used for non-recent files and the second for recent files. E.g., if `` is "`%Y-%m-%d %H--%m-%d %H:%M`", non-recent files => "`2022-12-30 13`", recent files => "`--09-30 13:34`". `--total-size` : Show recursive directory size (unix only). `-u`, `--accessed` : Use the accessed timestamp field. `-U`, `--created` : Use the created timestamp field. `--no-permissions` : Suppress the permissions field. `-o`, `--octal-permissions` : List each file's permissions in octal format. `--no-filesize` : Suppress the file size field. `--no-user` : Suppress the user field. `--no-time` : Suppress the time field. `--stdin` : When you wish to pipe directories to eza/read from stdin. Separate one per line or define custom separation char in `EZA_STDIN_SEPARATOR` env variable. `-@`, `--extended` : List each file’s extended attributes and sizes. `-Z`, `--context` : List each file's security context. `--git` [if eza was built with git support] : List each file’s Git status, if tracked. This adds a two-character column indicating the staged and unstaged statuses respectively. The status character can be ‘`-`’ for not modified, ‘`M`’ for a modified file, ‘`N`’ for a new file, ‘`D`’ for deleted, ‘`R`’ for renamed, ‘`T`’ for type-change, ‘`I`’ for ignored, and ‘`U`’ for conflicted. Directories will be shown to have the status of their contents, which is how ‘deleted’ is possible if a directory contains a file that has a certain status, it will be shown to have that status. `--git-repos` [if eza was built with git support] : List each directory’s Git status, if tracked. Symbols shown are `|`= clean, `+`= dirty, and `~`= for unknown. `--git-repos-no-status` [if eza was built with git support] : List if a directory is a Git repository, but not its status. All Git repository directories will be shown as (themed) `-` without status indicated. `--no-git` : Don't show Git status (always overrides `--git`, `--git-repos`, `--git-repos-no-status`) ENVIRONMENT VARIABLES ===================== If an environment variable prefixed with `EZA_` is not set, for backward compatibility, it will default to its counterpart starting with `EXA_`. eza responds to the following environment variables: ## `COLUMNS` Overrides the width of the terminal, in characters, however, `-w` takes precedence. For example, ‘`COLUMNS=80 eza`’ will show a grid view with a maximum width of 80 characters. This option won’t do anything when eza’s output doesn’t wrap, such as when using the `--long` view. ## `EZA_STRICT` Enables _strict mode_, which will make eza error when two command-line options are incompatible. Usually, options can override each other going right-to-left on the command line, so that eza can be given aliases: creating an alias ‘`eza=eza --sort=ext`’ then running ‘`eza --sort=size`’ with that alias will run ‘`eza --sort=ext --sort=size`’, and the sorting specified by the user will override the sorting specified by the alias. In strict mode, the two options will not co-operate, and eza will error. This option is intended for use with automated scripts and other situations where you want to be certain you’re typing in the right command. ## `EZA_GRID_ROWS` Limits the grid-details view (‘`eza --grid --long`’) so it’s only activated when at least the given number of rows of output would be generated. With widescreen displays, it’s possible for the grid to look very wide and sparse, on just one or two lines with none of the columns lining up. By specifying a minimum number of rows, you can only use the view if it’s going to be worth using. ## `EZA_ICON_SPACING` Specifies the number of spaces to print between an icon (see the ‘`--icons`’ option) and its file name. Different terminals display icons differently, as they usually take up more than one character width on screen, so there’s no “standard” number of spaces that eza can use to separate an icon from text. One space may place the icon too close to the text, and two spaces may place it too far away. So the choice is left up to the user to configure depending on their terminal emulator. ## `NO_COLOR` Disables colours in the output (regardless of its value). Can be overridden by `--color` option. See `https://no-color.org/` for details. ## `LS_COLORS`, `EZA_COLORS` Specifies the colour scheme used to highlight files based on their name and kind, as well as highlighting metadata and parts of the UI. For more information on the format of these environment variables, see the [eza_colors.5.md](eza_colors.5.md) manual page. ## `EZA_OVERRIDE_GIT` Overrides any `--git` or `--git-repos` argument ## `EZA_MIN_LUMINANCE` Specifies the minimum luminance to use when decay is active. It's value can be between -100 to 100. ## `EZA_ICONS_AUTO` If set, automates the same behavior as using `--icons` or `--icons=auto`. Useful for if you always want to have icons enabled. Any explicit use of the `--icons=WHEN` flag overrides this behavior. ## `EZA_STDIN_SEPARATOR` Specifies the separator to use when file names are piped from stdin. Defaults to newline. EXIT STATUSES ============= 0 : If everything goes OK. 1 : If there was an I/O error during operation. 3 : If there was a problem with the command-line arguments. AUTHOR ====== eza is maintained by Christina Sørensen and many other contributors. **Source code:** `https://github.com/eza-community/eza` \ **Contributors:** `https://github.com/eza-community/eza/graphs/contributors` Our infinite thanks to Benjamin ‘ogham’ Sago and all the other contributors of exa, from which eza was forked. SEE ALSO ======== - [eza_colors.5.md](eza_colors.5.md) - [eza_colors-explanation.5.md](eza_colors-explanation.5.md) eza-0.18.2/man/eza_colors-explanation.5.md000064400000000000000000000047031046102023000164170ustar 00000000000000% eza_colors-explanation(5) $version # Name eza_colors-explanation — more details on customizing eza colors # Eza Color Explanation eza provides its own built\-in set of file extension mappings that cover a large range of common file extensions, including documents, archives, media, and temporary files. Any mappings in the environment variables will override this default set: running eza with `LS_COLORS="*.zip=32"` will turn zip files green but leave the colours of other compressed files alone. You can also disable this built\-in set entirely by including a `reset` entry at the beginning of `EZA_COLORS`. So setting `EZA_COLORS="reset:*.txt=31"` will highlight only text files; setting `EZA_COLORS="reset"` will highlight nothing. ## Examples - Disable the "current user" highlighting: `EZA_COLORS="uu=0:gu=0"` - Turn the date column green: `EZA_COLORS="da=32"` - Highlight Vagrantfiles: `EZA_COLORS="Vagrantfile=1;4;33"` - Override the existing zip colour: `EZA_COLORS="*.zip=38;5;125"` - Markdown files a shade of green, log files a shade of grey: `EZA_COLORS="*.md=38;5;121:*.log=38;5;248"` ## BUILT\-IN EXTENSIONS - eza now supports bright colours! As supported by most modern 256\-colour terminals, you can now choose from `bright` colour codes when selecting your custom colours in your `#EZA_COLORS` environment variable. - Build (Makefile, Cargo.toml, package.json) are yellow and underlined. - Images (png, jpeg, gif) are purple. - Videos (mp4, ogv, m2ts) are a slightly purpler purple. - Music (mp3, m4a, ogg) is a deeper purple. - Lossless music (flac, alac, wav) is deeper than *that* purple. In general, most media files are some shade of purple. - Cryptographic files (asc, enc, p12) are a faint blue. - Documents (pdf, doc, dvi) are a less faint blue. - Compressed files (zip, tgz, Z) are red. - Temporary files (tmp, swp, ~) are grey. - Compiled files (class, o, pyc) are yellow. A file is also counted as compiled if it uses a common extension and is in the same directory as one of its source files: styles.css will count as compiled when next to styles.less or styles.sass, and scripts.js when next to scripts.ts or scripts.coffee. - Source files (cpp, js, java) are bright yellow. ## See also - [eza.1.md](eza.1.md) - [eza_colors.5.md](eza_colors.5.md) eza-0.18.2/man/eza_colors.5.md000064400000000000000000000163041046102023000140770ustar 00000000000000% eza_colors(5) $version NAME ==== eza_colors — customising the file and UI colours of eza SYNOPSIS ======== The `EZA_COLORS` environment variable can be used to customise the colours that `eza` uses to highlight file names, file metadata, and parts of the UI. You can use the `dircolors` program to generate a script that sets the variable from an input file, or if you don’t mind editing long strings of text, you can just type it out directly. These variables have the following structure: - A list of key-value pairs separated by ‘`=`’, such as ‘`*.txt=32`’. - Multiple ANSI formatting codes are separated by ‘`;`’, such as ‘`*.txt=32;1;4`’. - Finally, multiple pairs are separated by ‘`:`’, such as ‘`*.txt=32:*.mp3=1;35`’. The key half of the pair can either be a two-letter code or a file glob, and anything that’s not a valid code will be treated as a glob, including keys that happen to be two letters long. For backwards compatibility `EXA_COLORS` environment variables is checked if `EZA_COLORS` is unset. EXAMPLES ======== `EZA_COLORS="uu=0:gu=0"` : Disable the “current user” highlighting `EZA_COLORS="da=32"` : Turn the date column green `EZA_COLORS="Vagrantfile=1;4;33"` : Highlight Vagrantfiles `EZA_COLORS="*.zip=38;5;125"` : Override the existing zip colour `EZA_COLORS="*.md=38;5;121:*.log=38;5;248"` : Markdown files a shade of green, log files a shade of grey LIST OF CODES ============= `LS_COLORS` can use these ten codes: `di` : directories `ex` : executable files `fi` : regular files `pi` : named pipes `so` : sockets `bd` : block devices `cd` : character devices `ln` : symlinks `or` : symlinks with no target `EZA_COLORS` can use many more: `oc` : the permissions displayed as octal `ur` : the user-read permission bit `uw` : the user-write permission bit `ux` : the user-execute permission bit for regular files `ue` : the user-execute for other file kinds `gr` : the group-read permission bit `gw` : the group-write permission bit `gx` : the group-execute permission bit `tr` : the others-read permission bit `tw` : the others-write permission bit `tx` : the others-execute permission bit `su` : setuid, setgid, and sticky permission bits for files `sf` : setuid, setgid, and sticky for other file kinds `xa` : the extended attribute indicator `sn` : the numbers of a file’s size (sets `nb`, `nk`, `nm`, `ng` and `nt`) `nb` : the numbers of a file’s size if it is lower than 1 KB/Kib `nk` : the numbers of a file’s size if it is between 1 KB/KiB and 1 MB/MiB `nm` : the numbers of a file’s size if it is between 1 MB/MiB and 1 GB/GiB `ng` : the numbers of a file’s size if it is between 1 GB/GiB and 1 TB/TiB `nt` : the numbers of a file’s size if it is 1 TB/TiB or higher `sb` : the units of a file’s size (sets `ub`, `uk`, `um`, `ug` and `ut`) `ub` : the units of a file’s size if it is lower than 1 KB/Kib `uk` : the units of a file’s size if it is between 1 KB/KiB and 1 MB/MiB `um` : the units of a file’s size if it is between 1 MB/MiB and 1 GB/GiB `ug` : the units of a file’s size if it is between 1 GB/GiB and 1 TB/TiB `ut` : the units of a file’s size if it is 1 TB/TiB or higher `df` : a device’s major ID `ds` : a device’s minor ID `uu` : a user that’s you `uR` : a user that's root `un` : a user that’s someone else `gu` : a group that you belong to `gR` : a group related to root `gn` : a group you aren’t a member of `lc` : a number of hard links `lm` : a number of hard links for a regular file with at least two `ga` : a new flag in Git `gm` : a modified flag in Git `gd` : a deleted flag in Git `gv` : a renamed flag in Git `gt` : a modified metadata flag in Git `gi` : an ignored flag in Git `gc` : a conflicted flag in Git `Gm` : main branch of repo `Go` : other branch of repo `Gc` : clean branch of repo `Gd` : dirty branch of repo `xx` : “punctuation”, including many background UI elements `da` : a file’s date `in` : a file’s inode number `bl` : a file’s number of blocks `hd` : the header row of a table `lp` : the path of a symlink `cc` : an escaped character in a filename `bO` : the overlay style for broken symlink paths `sp` : special (not file, dir, mount, exec, pipe, socket, block device, char device, or link) `mp` : a mount point `im` : a regular file that is an image `vi` : a regular file that is a video `mu` : a regular file that is lossy music `lo` : a regular file that is lossless music `cr` : a regular file that is related to cryptography (ex: key or certificate) `do` : a regular file that is a document (ex: office suite document or PDF) `co` : a regular file that is compressed `tm` : a regular file that is temporary (ex: a text editor's backup file) `cm` : a regular file that is a compilation artifact (ex: Java class file) `bu` : a regular file that is used to build a project (ex: Makefile) `sc` : a regular file that is source code `Sn` : No security context on a file `Su` : SELinux user `Sr` : SELinux role `St` : SELinux type `Sl` : SELinux level `ff` : BSD file flags Values in `EXA_COLORS` override those given in `LS_COLORS`, so you don’t need to re-write an existing `LS_COLORS` variable with proprietary extensions. LIST OF STYLES ============== Unlike some versions of `ls`, the given ANSI values must be valid colour codes: eza won’t just print out whichever characters are given. The codes accepted by eza are: `1` : for bold `2` : for dimmed `3` : for italic `4` : for underline `31` : for red text `32` : for green text `33` : for yellow text `34` : for blue text `35` : for purple text `36` : for cyan text `37` : for white text `90` : for dark gray text `91` : for bright red text `92` : for bright green text `93` : for bright yellow text `94` : for bright blue text `95` : for bright purple text `96` : for bright cyan text `97` : for bright text `38;5;nnn` : for a colour from 0 to 255 (replace the `nnn` part) Many terminals will treat bolded text as a different colour, or at least provide the option to. eza provides its own built-in set of file extension mappings that cover a large range of common file extensions, including documents, archives, media, and temporary files. Any mappings in the environment variables will override this default set: running eza with `LS_COLORS="*.zip=32"` will turn zip files green but leave the colours of other compressed files alone. You can also disable this built-in set entirely by including a `reset` entry at the beginning of `EZA_COLORS`. So setting `EZA_COLORS="reset:*.txt=31"` will highlight only text files; setting `EZA_COLORS="reset"` will highlight nothing. AUTHOR ====== eza is maintained by Christina Sørensen and many other contributors. **Source code:** `https://github.com/eza-community/eza` \ **Contributors:** `https://github.com/eza-community/eza/graphs/contributors` Our infinite thanks to Benjamin ‘ogham’ Sago and all the other contributors of exa, from which eza was forked. SEE ALSO ======== - [eza.1.md](eza.1.md) - [eza_colors-explanation.5.md](eza_colors-explanation.5.md) eza-0.18.2/powertest.yaml000064400000000000000000000052341046102023000134170ustar 00000000000000dump_dir: tests/ptests depth: 1 binary: eza gen_binary: target/debug/eza args: tests/test_dir commands: # Meta options ? - null - --help : ? - -v - --version : # Display Options ? - -1 - --oneline : ? - -l - --long : ? - -G - --grid : ? - -x - --across : ? - -R - --recurse : ? - -T - --tree : ? - -X - --dereference : ? - -F - --classify : ? - -F - --classify : values: - auto - always - never ? - null - --color : values: - auto - always - never ? - null - --colour : values: - auto - always - never ? - null - --icons : ? - null - --icons : values: - auto - always - never ? - null - --no-quotes ? - null - --hyperlink ? - -w - --width : values: - 15 - 30 - 200 ? - null - --smart-group # Filtering and Sorting Options ? - -a - --all ? - -A - --almost-all ? - -d - --list-dirs ? - -L - --level : prefix: --tree values: - 1 - 2 - 3 - 4 - 5 ? - -r - --reverse ? - -s - --sort : short: -s long: time-style prefix: -l values: #- accessed - age #- changed #- created - date - Ext - ext - Extension - extension - Filename - filename - inode - modified - Name - name - newest - none - oldest - size - time - type ? - null - --group-directories-first ? - -D - --only-dirs ? - -f - --only-files ? - -f - --only-files ? # TODO: add more globs - -I - --ignore-glob : prefix: -l values: - "*.toml" ? - null - --git-ignore # Long View Options ? - -b - --binary ? - -B - --bytes ? - -g - --group ? - -h - --header ? - -H - --links ? - -i - --inode ? - -m - --modified ? - -M - --mounts ? - -n - --numeric ? - -S - --blocksize ? - -t - --time : prefix: -l values: - modified #- accessed BROKEN #- changed #- created ? - -u - --accessed ? - -U - --created ? - null - --changed ? - null - --time-style : long: time-style values: - default - iso - long-iso - full-iso - relative ? - null - --total-size ? - null - --no-permissions ? - -o - --octal-permissions ? - null - --no-filesize ? - null - --no-user ? - null - --git ? - null - --no-git ? - null - --git-repos ? - -@ - --extended ? - -Z - --context eza-0.18.2/rust-toolchain.toml000064400000000000000000000002221046102023000143370ustar 00000000000000[toolchain] channel = "1.70" components = [ "rustfmt", "rustc", "rust-src", "rust-analyzer", "cargo", "clippy", ] profile = "minimal" eza-0.18.2/rustfmt.toml000064400000000000000000000000011046102023000130630ustar 00000000000000 eza-0.18.2/snap/snapcraft.yaml000064400000000000000000000015051046102023000143020ustar 00000000000000name: eza base: core22 version: 'latest' summary: Replacement for 'ls' written in Rust description: | It uses colours for information by default, helping you distinguish between many types of files, such as whether you are the owner, or in the owning group. It also has extra features not present in the original ls, such as viewing the Git status for a directory, or recursing into directories with a tree view. eza is written in Rust, and it's small, fast, and portable. issues: https://github.com/eza-community/eza/issues source-code: https://github.com/eza-community/eza contact: christina@cafkafk.com website: https://eza.rocks/ license: MIT grade: stable confinement: classic apps: eza: command: eza parts: eza: plugin: rust source: . stage-packages: - libgit2-24 - cmake - libz-dev eza-0.18.2/src/fs/dir.rs000064400000000000000000000170161046102023000130230ustar 00000000000000use crate::fs::feature::git::GitCache; use crate::fs::fields::GitStatus; use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::slice::Iter as SliceIter; use log::*; use crate::fs::File; /// A **Dir** provides a cached list of the file paths in a directory that’s /// being listed. /// /// This object gets passed to the Files themselves, in order for them to /// check the existence of surrounding files, then highlight themselves /// accordingly. (See `File#get_source_files`) pub struct Dir { /// A vector of the files that have been read from this directory. contents: Vec, /// The path that was read. pub path: PathBuf, } impl Dir { /// Create a new Dir object filled with all the files in the directory /// pointed to by the given path. Fails if the directory can’t be read, or /// isn’t actually a directory, or if there’s an IO error that occurs at /// any point. /// /// The `read_dir` iterator doesn’t actually yield the `.` and `..` /// entries, so if the user wants to see them, we’ll have to add them /// ourselves after the files have been read. pub fn read_dir(path: PathBuf) -> io::Result { info!("Reading directory {:?}", &path); let contents = fs::read_dir(&path)? .map(|result| result.map(|entry| entry.path())) .collect::>()?; info!("Read directory success {:?}", &path); Ok(Self { contents, path }) } /// Produce an iterator of IO results of trying to read all the files in /// this directory. pub fn files<'dir, 'ig>( &'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool, deref_links: bool, total_size: bool, ) -> Files<'dir, 'ig> { Files { inner: self.contents.iter(), dir: self, dotfiles: dots.shows_dotfiles(), dots: dots.dots(), git, git_ignoring, deref_links, total_size, } } /// Whether this directory contains a file with the given path. pub fn contains(&self, path: &Path) -> bool { self.contents.iter().any(|p| p.as_path() == path) } /// Append a path onto the path specified by this directory. pub fn join(&self, child: &Path) -> PathBuf { self.path.join(child) } } /// Iterator over reading the contents of a directory as `File` objects. #[allow(clippy::struct_excessive_bools)] pub struct Files<'dir, 'ig> { /// The internal iterator over the paths that have been read already. inner: SliceIter<'dir, PathBuf>, /// The directory that begat those paths. dir: &'dir Dir, /// Whether to include dotfiles in the list. dotfiles: bool, /// Whether the `.` or `..` directories should be produced first, before /// any files have been listed. dots: DotsNext, git: Option<&'ig GitCache>, git_ignoring: bool, /// Whether symbolic links should be dereferenced when querying information. deref_links: bool, /// Whether to calculate the directory size recursively total_size: bool, } impl<'dir, 'ig> Files<'dir, 'ig> { fn parent(&self) -> PathBuf { // We can’t use `Path#parent` here because all it does is remove the // last path component, which is no good for us if the path is // relative. For example, while the parent of `/testcases/files` is // `/testcases`, the parent of `.` is an empty path. Adding `..` on // the end is the only way to get to the *actual* parent directory. self.dir.path.join("..") } /// Go through the directory until we encounter a file we can list (which /// varies depending on the dotfile visibility flag) fn next_visible_file(&mut self) -> Option, (PathBuf, io::Error)>> { loop { if let Some(path) = self.inner.next() { let filename = File::filename(path); if !self.dotfiles && filename.starts_with('.') { continue; } // Also hide _prefix files on Windows because it's used by old applications // as an alternative to dot-prefix files. #[cfg(windows)] if !self.dotfiles && filename.starts_with('_') { continue; } if self.git_ignoring { let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default(); if git_status.unstaged == GitStatus::Ignored { continue; } } let file = File::from_args( path.clone(), self.dir, filename, self.deref_links, self.total_size, ) .map_err(|e| (path.clone(), e)); // Windows has its own concept of hidden files, when dotfiles are // hidden Windows hidden files should also be filtered out #[cfg(windows)] if !self.dotfiles && file.as_ref().is_ok_and(|f| f.attributes().hidden) { continue; } return Some(file); } return None; } } } /// The dot directories that need to be listed before actual files, if any. /// If these aren’t being printed, then `FilesNext` is used to skip them. enum DotsNext { /// List the `.` directory next. Dot, /// List the `..` directory next. DotDot, /// Forget about the dot directories and just list files. Files, } impl<'dir, 'ig> Iterator for Files<'dir, 'ig> { type Item = Result, (PathBuf, io::Error)>; fn next(&mut self) -> Option { match self.dots { DotsNext::Dot => { self.dots = DotsNext::DotDot; Some( File::new_aa_current(self.dir, self.total_size) .map_err(|e| (Path::new(".").to_path_buf(), e)), ) } DotsNext::DotDot => { self.dots = DotsNext::Files; Some( File::new_aa_parent(self.parent(), self.dir, self.total_size) .map_err(|e| (self.parent(), e)), ) } DotsNext::Files => self.next_visible_file(), } } } /// Usually files in Unix use a leading dot to be hidden or visible, but two /// entries in particular are “extra-hidden”: `.` and `..`, which only become /// visible after an extra `-a` option. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum DotFilter { /// Shows files, dotfiles, and `.` and `..`. DotfilesAndDots, /// Show files and dotfiles, but hide `.` and `..`. Dotfiles, /// Just show files, hiding anything beginning with a dot. JustFiles, } impl Default for DotFilter { fn default() -> Self { Self::JustFiles } } impl DotFilter { /// Whether this filter should show dotfiles in a listing. fn shows_dotfiles(self) -> bool { match self { Self::JustFiles => false, Self::Dotfiles => true, Self::DotfilesAndDots => true, } } /// Whether this filter should add dot directories to a listing. fn dots(self) -> DotsNext { match self { Self::JustFiles => DotsNext::Files, Self::Dotfiles => DotsNext::Files, Self::DotfilesAndDots => DotsNext::Dot, } } } eza-0.18.2/src/fs/dir_action.rs000064400000000000000000000055341046102023000143620ustar 00000000000000//! What to do when encountering a directory? /// The action to take when trying to list a file that turns out to be a /// directory. /// /// By default, exa will display the information about files passed in as /// command-line arguments, with one file per entry. However, if a directory /// is passed in, exa assumes that the user wants to see its contents, rather /// than the directory itself. /// /// This can get annoying sometimes: if a user does `exa ~/Downloads/img-*` /// to see the details of every file starting with `img-`, any directories /// that happen to start with the same will be listed after the files at /// the end in a separate block. By listing directories as files, their /// directory status will be ignored, and both will be listed side-by-side. /// /// These two modes have recursive analogues in the “recurse” and “tree” /// modes. Here, instead of just listing the directories, exa will descend /// into them and print out their contents. The recurse mode does this by /// having extra output blocks at the end, while the tree mode will show /// directories inline, with their contents immediately underneath. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum DirAction { /// This directory should be listed along with the regular files, instead /// of having its contents queried. AsFile, /// This directory should not be listed, and should instead be opened and /// *its* files listed separately. This is the default behaviour. List, /// This directory should be listed along with the regular files, and then /// its contents should be listed afterward. The recursive contents of /// *those* contents are dictated by the options argument. Recurse(RecurseOptions), } impl DirAction { /// Gets the recurse options, if this dir action has any. pub fn recurse_options(self) -> Option { match self { Self::Recurse(o) => Some(o), _ => None, } } /// Whether to treat directories as regular files or not. pub fn treat_dirs_as_files(self) -> bool { match self { Self::AsFile => true, Self::Recurse(o) => o.tree, Self::List => false, } } } /// The options that determine how to recurse into a directory. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct RecurseOptions { /// Whether recursion should be done as a tree or as multiple individual /// views of files. pub tree: bool, /// The maximum number of times that recursion should descend to, if one /// is specified. pub max_depth: Option, } impl RecurseOptions { /// Returns whether a directory of the given depth would be too deep. pub fn is_too_deep(self, depth: usize) -> bool { match self.max_depth { None => false, Some(d) => d <= depth, } } } eza-0.18.2/src/fs/feature/git.rs000064400000000000000000000374571046102023000144760ustar 00000000000000//! Getting the Git status of files and directories. use std::env; use std::ffi::OsStr; #[cfg(target_family = "unix")] use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::sync::Mutex; use log::*; use crate::fs::fields as f; /// A **Git cache** is assembled based on the user’s input arguments. /// /// This uses vectors to avoid the overhead of hashing: it’s not worth it when the /// expected number of Git repositories per exa invocation is 0 or 1... pub struct GitCache { /// A list of discovered Git repositories and their paths. repos: Vec, /// Paths that we’ve confirmed do not have Git repositories underneath them. misses: Vec, } impl GitCache { pub fn has_anything_for(&self, index: &Path) -> bool { self.repos.iter().any(|e| e.has_path(index)) } pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git { self.repos .iter() .find(|repo| repo.has_path(index)) .map(|repo| repo.search(index, prefix_lookup)) .unwrap_or_default() } } use std::iter::FromIterator; impl FromIterator for GitCache { fn from_iter(iter: I) -> Self where I: IntoIterator, { let iter = iter.into_iter(); let mut git = Self { repos: Vec::with_capacity(iter.size_hint().0), misses: Vec::new(), }; if let Ok(path) = env::var("GIT_DIR") { // These flags are consistent with how `git` uses GIT_DIR: let flags = git2::RepositoryOpenFlags::NO_SEARCH | git2::RepositoryOpenFlags::NO_DOTGIT; match GitRepo::discover(path.into(), flags) { Ok(repo) => { debug!("Opened GIT_DIR repo"); git.repos.push(repo); } Err(miss) => { git.misses.push(miss); } } } for path in iter { if git.misses.contains(&path) { debug!("Skipping {:?} because it already came back Gitless", path); } else if git.repos.iter().any(|e| e.has_path(&path)) { debug!("Skipping {:?} because we already queried it", path); } else { let flags = git2::RepositoryOpenFlags::FROM_ENV; match GitRepo::discover(path, flags) { Ok(r) => { if let Some(r2) = git.repos.iter_mut().find(|e| e.has_workdir(&r.workdir)) { debug!( "Adding to existing repo (workdir matches with {:?})", r2.workdir ); r2.extra_paths.push(r.original_path); continue; } debug!("Discovered new Git repo"); git.repos.push(r); } Err(miss) => { git.misses.push(miss); } } } } git } } /// A **Git repository** is one we’ve discovered somewhere on the filesystem. pub struct GitRepo { /// The queryable contents of the repository: either a `git2` repo, or the /// cached results from when we queried it last time. contents: Mutex, /// The working directory of this repository. /// This is used to check whether two repositories are the same. workdir: PathBuf, /// The path that was originally checked to discover this repository. /// This is as important as the extra_paths (it gets checked first), but /// is separate to avoid having to deal with a non-empty Vec. original_path: PathBuf, /// Any other paths that were checked only to result in this same /// repository. extra_paths: Vec, } /// A repository’s queried state. enum GitContents { /// All the interesting Git stuff goes through this. Before { repo: git2::Repository }, /// Temporary value used in `repo_to_statuses` so we can move the /// repository out of the `Before` variant. Processing, /// The data we’ve extracted from the repository, but only after we’ve /// actually done so. After { statuses: Git }, } impl GitRepo { /// Searches through this repository for a path (to a file or directory, /// depending on the prefix-lookup flag) and returns its Git status. /// /// Actually querying the `git2` repository for the mapping of paths to /// Git statuses is only done once, and gets cached so we don’t need to /// re-query the entire repository the times after that. /// /// The temporary `Processing` enum variant is used after the `git2` /// repository is moved out, but before the results have been moved in! /// See fn search(&self, index: &Path, prefix_lookup: bool) -> f::Git { use std::mem::replace; let mut contents = self.contents.lock().unwrap(); if let GitContents::After { ref statuses } = *contents { debug!("Git repo {:?} has been found in cache", &self.workdir); return statuses.status(index, prefix_lookup); } debug!("Querying Git repo {:?} for the first time", &self.workdir); let repo = replace(&mut *contents, GitContents::Processing).inner_repo(); let statuses = repo_to_statuses(&repo, &self.workdir); let result = statuses.status(index, prefix_lookup); let _processing = replace(&mut *contents, GitContents::After { statuses }); result } /// Whether this repository has the given working directory. fn has_workdir(&self, path: &Path) -> bool { self.workdir == path } /// Whether this repository cares about the given path at all. fn has_path(&self, path: &Path) -> bool { path.starts_with(&self.original_path) || self.extra_paths.iter().any(|e| path.starts_with(e)) } /// Open a Git repository. Depending on the flags, the path is either /// the repository's "gitdir" (or a "gitlink" to the gitdir), or the /// path is the start of a rootwards search for the repository. fn discover(path: PathBuf, flags: git2::RepositoryOpenFlags) -> Result { info!("Opening Git repository for {:?} ({:?})", path, flags); let unused: [&OsStr; 0] = []; let repo = match git2::Repository::open_ext(&path, flags, unused) { Ok(r) => r, Err(e) => { error!("Error opening Git repository for {path:?}: {e:?}"); return Err(path); } }; if let Some(workdir) = repo.workdir() { let workdir = workdir.to_path_buf(); let contents = Mutex::new(GitContents::Before { repo }); Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new(), }) } else { warn!("Repository has no workdir?"); Err(path) } } } impl GitContents { /// Assumes that the repository hasn’t been queried, and extracts it /// (consuming the value) if it has. This is needed because the entire /// enum variant gets replaced when a repo is queried (see above). fn inner_repo(self) -> git2::Repository { if let Self::Before { repo } = self { repo } else { unreachable!("Tried to extract a non-Repository") } } } /// Iterates through a repository’s statuses, consuming it and returning the /// mapping of files to their Git status. /// We will have already used the working directory at this point, so it gets /// passed in rather than deriving it from the `Repository` again. fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git { let mut statuses = Vec::new(); info!("Getting Git statuses for repo with workdir {:?}", workdir); match repo.statuses(None) { Ok(es) => { for e in es.iter() { #[cfg(target_family = "unix")] let path = workdir.join(Path::new(OsStr::from_bytes(e.path_bytes()))); // TODO: handle non Unix systems better: // https://github.com/ogham/exa/issues/698 #[cfg(not(target_family = "unix"))] let path = workdir.join(Path::new(e.path().unwrap())); let elem = (path, e.status()); statuses.push(elem); } // We manually add the `.git` at the root of the repo as ignored, since it is in practice. // Also we want to avoid `eza --tree --all --git-ignore` to display files inside `.git`. statuses.push((workdir.join(".git"), git2::Status::IGNORED)); } Err(e) => { error!("Error looking up Git statuses: {:?}", e); } } Git { statuses } } // The `repo.statuses` call above takes a long time. exa debug output: // // 20.311276 INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/" // 20.799610 DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml" // // Even inserting another logging line immediately afterwards doesn’t make it // look any faster. /// Container of Git statuses for all the files in this folder’s Git repository. struct Git { statuses: Vec<(PathBuf, git2::Status)>, } impl Git { /// Get either the file or directory status for the given path. /// “Prefix lookup” means that it should report an aggregate status of all /// paths starting with the given prefix (in other words, a directory). fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git { if prefix_lookup { self.dir_status(index) } else { self.file_status(index) } } /// Get the user-facing status of a file. /// We check the statuses directly applying to a file, and for the ignored /// status we check if any of its parents directories is ignored by git. fn file_status(&self, file: &Path) -> f::Git { let path = reorient(file); let s = self .statuses .iter() .filter(|p| { if p.1 == git2::Status::IGNORED { path.starts_with(&p.0) } else { p.0 == path } }) .fold(git2::Status::empty(), |a, b| a | b.1); let staged = index_status(s); let unstaged = working_tree_status(s); f::Git { staged, unstaged } } /// Get the combined, user-facing status of a directory. /// Statuses are aggregating (for example, a directory is considered /// modified if any file under it has the status modified), except for /// ignored status which applies to files under (for example, a directory /// is considered ignored if one of its parent directories is ignored). fn dir_status(&self, dir: &Path) -> f::Git { let path = reorient(dir); let s = self .statuses .iter() .filter(|p| { if p.1 == git2::Status::IGNORED { path.starts_with(&p.0) } else { p.0.starts_with(&path) } }) .fold(git2::Status::empty(), |a, b| a | b.1); let staged = index_status(s); let unstaged = working_tree_status(s); f::Git { staged, unstaged } } } /// Converts a path to an absolute path based on the current directory. /// Paths need to be absolute for them to be compared properly, otherwise /// you’d ask a repo about “./README.md” but it only knows about /// “/vagrant/README.md”, prefixed by the workdir. #[cfg(unix)] fn reorient(path: &Path) -> PathBuf { use std::env::current_dir; // TODO: I’m not 100% on this func tbh let path = match current_dir() { Err(_) => Path::new(".").join(path), Ok(dir) => dir.join(path), }; path.canonicalize().unwrap_or(path) } #[cfg(windows)] fn reorient(path: &Path) -> PathBuf { let unc_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); // On Windows UNC path is returned. We need to strip the prefix for it to work. let normal_path = unc_path .as_os_str() .to_str() .unwrap() .trim_start_matches("\\\\?\\"); PathBuf::from(normal_path) } /// The character to display if the file has been modified, but not staged. fn working_tree_status(status: git2::Status) -> f::GitStatus { #[rustfmt::skip] return match status { s if s.contains(git2::Status::WT_NEW) => f::GitStatus::New, s if s.contains(git2::Status::WT_MODIFIED) => f::GitStatus::Modified, s if s.contains(git2::Status::WT_DELETED) => f::GitStatus::Deleted, s if s.contains(git2::Status::WT_RENAMED) => f::GitStatus::Renamed, s if s.contains(git2::Status::WT_TYPECHANGE) => f::GitStatus::TypeChange, s if s.contains(git2::Status::IGNORED) => f::GitStatus::Ignored, s if s.contains(git2::Status::CONFLICTED) => f::GitStatus::Conflicted, _ => f::GitStatus::NotModified, }; } /// The character to display if the file has been modified and the change /// has been staged. fn index_status(status: git2::Status) -> f::GitStatus { #[rustfmt::skip] return match status { s if s.contains(git2::Status::INDEX_NEW) => f::GitStatus::New, s if s.contains(git2::Status::INDEX_MODIFIED) => f::GitStatus::Modified, s if s.contains(git2::Status::INDEX_DELETED) => f::GitStatus::Deleted, s if s.contains(git2::Status::INDEX_RENAMED) => f::GitStatus::Renamed, s if s.contains(git2::Status::INDEX_TYPECHANGE) => f::GitStatus::TypeChange, _ => f::GitStatus::NotModified, }; } fn current_branch(repo: &git2::Repository) -> Option { let head = match repo.head() { Ok(head) => Some(head), Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch || e.code() == git2::ErrorCode::NotFound => { return None } Err(e) => { error!("Error looking up Git branch: {:?}", e); return None; } }; if let Some(h) = head { if let Some(s) = h.shorthand() { let branch_name = s.to_owned(); if branch_name.len() > 10 { return Some(branch_name[..8].to_string() + ".."); } return Some(branch_name); } } None } impl f::SubdirGitRepo { pub fn from_path(dir: &Path, status: bool) -> Self { let path = &reorient(dir); if let Ok(repo) = git2::Repository::open(path) { let branch = current_branch(&repo); if !status { return Self { status: None, branch, }; } match repo.statuses(None) { Ok(es) => { if es.iter().any(|s| s.status() != git2::Status::IGNORED) { return Self { status: Some(f::SubdirGitRepoStatus::GitDirty), branch, }; } return Self { status: Some(f::SubdirGitRepoStatus::GitClean), branch, }; } Err(e) => { error!("Error looking up Git statuses: {e:?}"); } } } f::SubdirGitRepo { status: if status { Some(f::SubdirGitRepoStatus::NoRepo) } else { None }, branch: None, } } } eza-0.18.2/src/fs/feature/mod.rs000064400000000000000000000015111046102023000144500ustar 00000000000000pub mod xattr; #[cfg(feature = "git")] pub mod git; #[cfg(not(feature = "git"))] pub mod git { use std::iter::FromIterator; use std::path::{Path, PathBuf}; use crate::fs::fields as f; pub struct GitCache; impl FromIterator for GitCache { fn from_iter(_iter: I) -> Self where I: IntoIterator, { Self } } impl GitCache { pub fn has_anything_for(&self, _index: &Path) -> bool { false } pub fn get(&self, _index: &Path, _prefix_lookup: bool) -> f::Git { unreachable!(); } } impl f::SubdirGitRepo { pub fn from_path(_dir: &Path, _status: bool) -> Self { panic!("Tried to get subdir Git status, but Git support is disabled") } } } eza-0.18.2/src/fs/feature/xattr.rs000064400000000000000000000536651046102023000150540ustar 00000000000000//! Extended attribute support for `NetBSD`, `Darwin`, and `Linux` systems. #![allow(trivial_casts)] // for ARM use std::fmt::{Display, Formatter}; use std::io; use std::path::Path; use std::str; pub const ENABLED: bool = cfg!(any( target_os = "macos", target_os = "linux", target_os = "netbsd", target_os = "freebsd" )); #[derive(Debug)] pub struct Attribute { pub name: String, pub value: Option>, } pub trait FileAttributes { fn attributes(&self) -> io::Result>; fn symlink_attributes(&self) -> io::Result>; } #[cfg(any( target_os = "macos", target_os = "linux", target_os = "netbsd", target_os = "freebsd" ))] impl FileAttributes for Path { fn attributes(&self) -> io::Result> { extended_attrs::attributes(self, true) } fn symlink_attributes(&self) -> io::Result> { extended_attrs::attributes(self, false) } } #[cfg(not(any( target_os = "macos", target_os = "linux", target_os = "netbsd", target_os = "freebsd" )))] impl FileAttributes for Path { fn attributes(&self) -> io::Result> { Ok(Vec::new()) } fn symlink_attributes(&self) -> io::Result> { Ok(Vec::new()) } } #[cfg(any( target_os = "macos", target_os = "linux", target_os = "netbsd", target_os = "freebsd" ))] mod extended_attrs { use super::Attribute; use libc::{c_char, c_void, size_t, ssize_t, ERANGE}; use std::ffi::{CStr, CString, OsStr, OsString}; use std::io; use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::ptr::null_mut; #[cfg(target_os = "macos")] mod os { use libc::{ c_char, c_int, c_void, getxattr, listxattr, size_t, ssize_t, XATTR_NOFOLLOW, XATTR_SHOWCOMPRESSION, }; // Options to use for MacOS versions of getxattr and listxattr fn get_options(follow_symlinks: bool) -> c_int { if follow_symlinks { XATTR_SHOWCOMPRESSION } else { XATTR_NOFOLLOW | XATTR_SHOWCOMPRESSION } } // Wrapper around listxattr that handles symbolic links pub(super) fn list_xattr( follow_symlinks: bool, path: *const c_char, namebuf: *mut c_char, size: size_t, ) -> ssize_t { // SAFETY: Calling C function unsafe { listxattr(path, namebuf, size, get_options(follow_symlinks)) } } // Wrapper around getxattr that handles symbolic links pub(super) fn get_xattr( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t { // SAFETY: Calling C function unsafe { getxattr(path, name, value, size, 0, get_options(follow_symlinks)) } } } #[cfg(any(target_os = "linux"))] mod os { use libc::{c_char, c_void, size_t, ssize_t}; use libc::{getxattr, lgetxattr, listxattr, llistxattr}; // Wrapper around listxattr and llistattr for handling symbolic links pub(super) fn list_xattr( follow_symlinks: bool, path: *const c_char, namebuf: *mut c_char, size: size_t, ) -> ssize_t { if follow_symlinks { // SAFETY: Calling C function unsafe { listxattr(path, namebuf, size) } } else { // SAFETY: Calling C function unsafe { llistxattr(path, namebuf, size) } } } // Wrapper around getxattr and lgetxattr for handling symbolic links pub(super) fn get_xattr( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t { if follow_symlinks { // SAFETY: Calling C function unsafe { getxattr(path, name, value, size) } } else { // SAFETY: Calling C function unsafe { lgetxattr(path, name, value, size) } } } } #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] mod os { use libc::{ c_char, c_int, c_void, extattr_get_file, extattr_get_link, extattr_list_file, extattr_list_link, size_t, ssize_t, EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER, }; // Wrapper around listxattr that handles symbolic links fn list_xattr( follow_symlinks: bool, path: *const c_char, namespace: c_int, value: *mut c_void, size: size_t, ) -> ssize_t { if follow_symlinks { // SAFETY: Calling C function unsafe { extattr_list_file(path, namespace, value, size) } } else { // SAFETY: Calling C function unsafe { extattr_list_link(path, namespace, value, size) } } } fn get_xattr( follow_symlinks: bool, path: *const c_char, namespace: c_int, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t { if follow_symlinks { // SAFETY: Calling C function unsafe { extattr_get_file(path, namespace, name, value, size) } } else { // SAFETY: Calling C function unsafe { extattr_get_link(path, namespace, name, value, size) } } } pub(super) fn list_system_xattr( follow_symlinks: bool, path: *const c_char, namebuf: *mut c_char, size: size_t, ) -> ssize_t { list_xattr( follow_symlinks, path, EXTATTR_NAMESPACE_SYSTEM, namebuf.cast(), size, ) } pub(super) fn list_user_xattr( follow_symlinks: bool, path: *const c_char, namebuf: *mut c_char, size: size_t, ) -> ssize_t { list_xattr( follow_symlinks, path, EXTATTR_NAMESPACE_USER, namebuf.cast(), size, ) } pub(super) fn get_system_xattr( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t { get_xattr( follow_symlinks, path, EXTATTR_NAMESPACE_SYSTEM, name, value.cast(), size, ) } pub(super) fn get_user_xattr( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t { get_xattr( follow_symlinks, path, EXTATTR_NAMESPACE_USER, name, value.cast(), size, ) } } // Split attribute name list. Each attribute name is null terminated in the // list. #[cfg(any(target_os = "macos", target_os = "linux"))] fn split_attribute_list(buffer: &[u8]) -> Vec { buffer[..buffer.len() - 1] // Skip trailing null .split(|&c| c == 0) .filter(|&s| !s.is_empty()) .map(OsStr::from_bytes) .map(std::borrow::ToOwned::to_owned) .collect() } // Split attribute name list. Each attribute is a one byte name length // followed by the name. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] fn split_attribute_list(buffer: &[u8]) -> Vec { let mut result = Vec::new(); let mut index = 0; let length = buffer.len(); while index < length { let item_length = buffer[index] as usize; let start = index + 1; let end = start + item_length; if end <= length { result.push(OsStr::from_bytes(&buffer[start..end]).to_owned()) } index = end; } result } // Calling getxattr and listxattr is a two part process. The first call // a null ptr for buffer and a zero buffer size is passed and the function // returns the needed buffer size. The second call the buffer ptr and the // buffer size is passed and the buffer is filled. Care must be taken if // the buffer size changes between the first and second call. fn get_loop ssize_t>(f: F) -> io::Result>> { let mut buffer: Vec = Vec::new(); loop { let buffer_size = match f(null_mut(), 0) { -1 => return Err(io::Error::last_os_error()), 0 => return Ok(None), size => size as size_t, }; buffer.resize(buffer_size, 0); return match f(buffer.as_mut_ptr(), buffer_size) { -1 => { let last_os_error = io::Error::last_os_error(); if last_os_error.raw_os_error() == Some(ERANGE) { // Passed buffer was to small so retry again. continue; } Err(last_os_error) } 0 => Ok(None), len => { // Just in case the size shrunk buffer.truncate(len as usize); Ok(Some(buffer)) } }; } } // Get a list of all attribute names on `path` fn list_attributes( path: &CStr, follow_symlinks: bool, lister: fn( follow_symlinks: bool, path: *const c_char, namebuf: *mut c_char, size: size_t, ) -> ssize_t, ) -> io::Result> { Ok( get_loop(|buf, size| lister(follow_symlinks, path.as_ptr(), buf.cast(), size))? .map_or_else(Vec::new, |buffer| split_attribute_list(&buffer)), ) } // Get the attribute value `name` on `path` #[cfg(any(target_os = "macos", target_os = "linux"))] fn get_attribute( path: &CStr, name: &CStr, follow_symlinks: bool, getter: fn( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t, ) -> io::Result>> { use libc::ENODATA; get_loop(|buf, size| { getter( follow_symlinks, path.as_ptr(), name.as_ptr(), buf.cast(), size, ) }) .or_else(|err| { if err.raw_os_error() == Some(ENODATA) { // This handles the case when the named attribute is not on the // path. This is for mainly handling the special case for the // security.selinux attribute mentioned below. This can // also happen when an attribute is deleted between listing // the attributes and getting its value. Ok(None) } else { Err(err) } }) } #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] fn get_attribute( path: &CStr, name: &CStr, follow_symlinks: bool, getter: fn( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t, ) -> io::Result>> { get_loop(|buf, size| { getter( follow_symlinks, path.as_ptr(), name.as_ptr(), buf.cast(), size, ) }) } // Specially handle security.linux for filesystem that do not list attributes. #[cfg(target_os = "linux")] fn get_selinux_attribute(path: &CStr, follow_symlinks: bool) -> io::Result> { const SELINUX_XATTR_NAME: &str = "security.selinux"; let name = CString::new(SELINUX_XATTR_NAME).unwrap(); get_attribute(path, &name, follow_symlinks, os::get_xattr).map(|value| { if value.is_some() { vec![Attribute { name: String::from(SELINUX_XATTR_NAME), value, }] } else { Vec::new() } }) } // Get a vector of all attribute names and values on `path` #[cfg(any(target_os = "macos", target_os = "linux"))] pub fn attributes(path: &Path, follow_symlinks: bool) -> io::Result> { let path = CString::new(path.as_os_str().as_bytes()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let attr_names = list_attributes(&path, follow_symlinks, os::list_xattr)?; #[cfg(target_os = "linux")] if attr_names.is_empty() { // Some filesystems, like sysfs, return nothing on listxattr, even though the security // attribute is set. return get_selinux_attribute(&path, follow_symlinks); } let mut attrs = Vec::with_capacity(attr_names.len()); for attr_name in attr_names { if let Some(name) = attr_name.to_str() { let attr_name = CString::new(name).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let value = get_attribute(&path, &attr_name, follow_symlinks, os::get_xattr)?; attrs.push(Attribute { name: name.to_string(), value, }); } } Ok(attrs) } #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] fn get_namespace_attributes( path: &CStr, follow_symlinks: bool, attr_names: Vec, namespace: &str, getter: fn( follow_symlinks: bool, path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, ) -> ssize_t, attrs: &mut Vec, ) -> io::Result<()> { for attr_name in attr_names { if let Some(name) = attr_name.to_str() { let attr_name = CString::new(name).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let value = get_attribute(&path, &attr_name, follow_symlinks, getter)?; attrs.push(Attribute { name: format!("{namespace}::{name}"), value, }); } } Ok(()) } #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] pub fn attributes(path: &Path, follow_symlinks: bool) -> io::Result> { use libc::EPERM; let path = CString::new(path.as_os_str().as_bytes()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let attr_names_system = list_attributes(&path, follow_symlinks, os::list_system_xattr) .or_else(|err| { // Reading of attributes in the system namespace is only supported for root if err.raw_os_error() == Some(EPERM) { Ok(Vec::new()) } else { Err(err) } })?; let attr_names_user = list_attributes(&path, follow_symlinks, os::list_user_xattr)?; let mut attrs = Vec::with_capacity(attr_names_system.len() + attr_names_user.len()); get_namespace_attributes( &path, follow_symlinks, attr_names_system, "system", os::get_system_xattr, &mut attrs, )?; get_namespace_attributes( &path, follow_symlinks, attr_names_user, "user", os::get_user_xattr, &mut attrs, )?; Ok(attrs) } } const ATTRIBUTE_VALUE_MAX_HEX_LENGTH: usize = 16; // Display for an attribute. Attribute values that have a custom display are // enclosed in curley brackets. impl Display for Attribute { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}: ", self.name))?; if let Some(value) = custom_attr_display(self) { f.write_fmt(format_args!("<{value}>")) } else { match &self.value { None => f.write_str(""), Some(value) => { if let Some(val) = custom_value_display(value) { f.write_fmt(format_args!("<{val}>")) } else if let Ok(v) = str::from_utf8(value) { f.write_fmt(format_args!("{:?}", v.trim_end_matches(char::from(0)))) } else if value.len() <= ATTRIBUTE_VALUE_MAX_HEX_LENGTH { f.write_fmt(format_args!("{value:02x?}")) } else { f.write_fmt(format_args!("", value.len())) } } } } } } struct AttributeDisplay { pub attribute: &'static str, pub display: fn(&Attribute) -> Option, } // Check for a custom display by attribute name and call the display function fn custom_attr_display(attribute: &Attribute) -> Option { let name = attribute.name.as_str(); // Strip off MacOS Metadata Persistence Flags // See https://eclecticlight.co/2020/11/02/controlling-metadata-tricks-with-persistence/ #[cfg(target_os = "macos")] let name = name.rsplit_once('#').map_or(name, |n| n.0); ATTRIBUTE_DISPLAYS .iter() .find(|c| c.attribute == name) .and_then(|c| (c.display)(attribute)) } #[cfg(target_os = "macos")] const ATTRIBUTE_DISPLAYS: &[AttributeDisplay] = &[ AttributeDisplay { attribute: "com.apple.lastuseddate", display: display_lastuseddate, }, AttributeDisplay { attribute: "com.apple.macl", display: display_macl, }, ]; #[cfg(not(target_os = "macos"))] const ATTRIBUTE_DISPLAYS: &[AttributeDisplay] = &[]; // com.apple.lastuseddate is two 64-bit values representing the seconds and nano seconds // from January 1, 1970 #[cfg(target_os = "macos")] fn display_lastuseddate(attribute: &Attribute) -> Option { use chrono::{Local, SecondsFormat, TimeZone}; attribute .value .as_ref() .filter(|value| value.len() == 16) .and_then(|value| { let sec = i64::from_le_bytes(value[0..8].try_into().unwrap()); let n_sec = i64::from_le_bytes(value[8..].try_into().unwrap()); Local .timestamp_opt(sec, n_sec as u32) .map(|dt| dt.to_rfc3339_opts(SecondsFormat::Nanos, true)) .single() }) } // com.apple.macl is a two byte flag followed by a uuid for the application #[cfg(target_os = "macos")] fn format_macl(value: &[u8]) -> String { const HEX: [u8; 16] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', ]; const GROUPS: [(usize, usize, u8); 6] = [ (0, 4, b';'), (5, 13, b'-'), (14, 18, b'-'), (19, 23, b'-'), (24, 28, b'-'), (29, 41, 0), ]; let mut dst = [0; 41]; let mut i = 0; for (start, end, sep) in GROUPS { for j in (start..end).step_by(2) { let x = value[i]; i += 1; dst[j] = HEX[(x >> 4) as usize]; dst[j + 1] = HEX[(x & 0x0f) as usize]; } if sep != 0 { dst[end] = sep; } } // SAFETY: Vector generated above with only ASCII characters. unsafe { String::from_utf8_unchecked(dst.to_vec()) } } // See https://book.hacktricks.xyz/macos-hardening/macos-security-and-privilege-escalation/macos-security-protections/macos-tcc #[cfg(target_os = "macos")] fn display_macl(attribute: &Attribute) -> Option { attribute .value .as_ref() .filter(|v| v.len() % 18 == 0) .map(|v| { let macls = v .as_slice() .chunks(18) .filter(|c| c[0] != 0 || c[1] != 0) .map(format_macl) .collect::>() .join(", "); format!("[{macls}]") }) } // plist::XmlWriter takes the writer instead of borrowing it. This is a // wrapper around a borrowed vector that just forwards the Write trait // calls to the borrowed vector. struct BorrowedWriter<'a> { pub buffer: &'a mut Vec, } impl<'a> io::Write for BorrowedWriter<'a> { fn write(&mut self, buf: &[u8]) -> io::Result { self.buffer.write(buf) } fn flush(&mut self) -> io::Result<()> { self.buffer.flush() } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.buffer.write_all(buf) } } fn custom_value_display(value: &[u8]) -> Option { if value.starts_with(b"bplist") { plist_value_display(value) } else { None } } // Convert a binary plist to a XML plist. fn plist_value_display(value: &[u8]) -> Option { let reader = io::Cursor::new(value); plist::Value::from_reader(reader).ok().and_then(|v| { let mut buffer = Vec::new(); v.to_writer_xml_with_options( BorrowedWriter { buffer: &mut buffer, }, &plist::XmlWriteOptions::default() .indent(b' ', 0) .root_element(false), ) .ok() .and_then(|()| str::from_utf8(&buffer).ok()) .map(|s| format!("{}", s.replace('\n', ""))) }) } eza-0.18.2/src/fs/fields.rs000064400000000000000000000200311046102023000135020ustar 00000000000000//! Wrapper types for the values returned from `File`s. //! //! The methods of `File` that return information about the entry on the //! filesystem -- size, modification date, block count, or Git status -- used //! to just return these as formatted strings, but this became inflexible once //! customisable output styles landed. //! //! Instead, they will return a wrapper type from this module, which tags the //! type with what field it is while containing the actual raw value. //! //! The `output::details` module, among others, uses these types to render and //! display the information as formatted strings. #![allow(non_camel_case_types)] #![allow(clippy::struct_excessive_bools)] /// The type of a file’s group ID. pub type gid_t = u32; /// The type of a file’s inode. pub type ino_t = u64; /// The type of a file’s number of links. pub type nlink_t = u64; /// The type of a file’s timestamp (creation, modification, access, etc). pub type time_t = i64; /// The type of a file’s user ID. pub type uid_t = u32; /// The type of user file flags pub type flag_t = u32; /// The file’s base type, which gets displayed in the very first column of the /// details output. /// /// This type is set entirely by the filesystem, rather than relying on a /// file’s contents. So “link” is a type, but “image” is just a type of /// regular file. (See the `filetype` module for those checks.) /// /// Its ordering is used when sorting by type. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] pub enum Type { Directory, File, Link, Pipe, Socket, CharDevice, BlockDevice, Special, } impl Type { pub fn is_regular_file(self) -> bool { matches!(self, Self::File) } } /// The file’s Unix permission bitfield, with one entry per bit. #[derive(Copy, Clone)] #[rustfmt::skip] pub struct Permissions { pub user_read: bool, pub user_write: bool, pub user_execute: bool, pub group_read: bool, pub group_write: bool, pub group_execute: bool, pub other_read: bool, pub other_write: bool, pub other_execute: bool, pub sticky: bool, pub setgid: bool, pub setuid: bool, } /// The file's `FileAttributes` field, available only on Windows. #[derive(Copy, Clone)] #[rustfmt::skip] pub struct Attributes { pub archive: bool, pub directory: bool, pub readonly: bool, pub hidden: bool, pub system: bool, pub reparse_point: bool, } /// The three pieces of information that are displayed as a single column in /// the details view. These values are fused together to make the output a /// little more compressed. #[derive(Copy, Clone)] pub struct PermissionsPlus { pub file_type: Type, #[cfg(unix)] pub permissions: Permissions, #[cfg(windows)] pub attributes: Attributes, pub xattrs: bool, } /// The permissions encoded as octal values #[derive(Copy, Clone)] pub struct OctalPermissions { pub permissions: Permissions, } /// A file’s number of hard links on the filesystem. /// /// Under Unix, a file can exist on the filesystem only once but appear in /// multiple directories. However, it’s rare (but occasionally useful!) for a /// regular file to have a link count greater than 1, so we highlight the /// block count specifically for this case. #[derive(Copy, Clone)] pub struct Links { /// The actual link count. pub count: nlink_t, /// Whether this file is a regular file with more than one hard link. pub multiple: bool, } /// A file’s inode. Every directory entry on a Unix filesystem has an inode, /// including directories and links, so this is applicable to everything exa /// can deal with. #[derive(Copy, Clone)] pub struct Inode(pub ino_t); /// A file's size of allocated file system blocks. #[derive(Copy, Clone)] #[cfg(unix)] pub enum Blocksize { /// This file has the given number of blocks. Some(u64), /// This file isn’t of a type that can take up blocks. None, } /// The ID of the user that owns a file. This will only ever be a number; /// looking up the username is done in the `display` module. #[derive(Copy, Clone)] pub struct User(pub uid_t); /// The ID of the group that a file belongs to. #[derive(Copy, Clone)] pub struct Group(pub gid_t); /// A file’s size, in bytes. This is usually formatted by the `number_prefix` /// crate into something human-readable. #[derive(Copy, Clone)] pub enum Size { /// This file has a defined size. Some(u64), /// This file has no size, or has a size but we aren’t interested in it. /// /// Under Unix, directory entries that aren’t regular files will still /// have a file size. For example, a directory will just contain a list of /// its files as its “contents” and will be specially flagged as being a /// directory, rather than a file. However, seeing the “file size” of this /// data is rarely useful — I can’t think of a time when I’ve seen it and /// learnt something. So we discard it and just output “-” instead. /// /// See this answer for more: None, /// This file is a block or character device, so instead of a size, print /// out the file’s major and minor device IDs. /// /// This is what ls does as well. Without it, the devices will just have /// file sizes of zero. DeviceIDs(DeviceIDs), } /// The major and minor device IDs that gets displayed for device files. /// /// You can see what these device numbers mean: /// - /// - #[derive(Copy, Clone)] pub struct DeviceIDs { pub major: u32, pub minor: u32, } /// One of a file’s timestamps (created, accessed, or modified). #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Time { pub seconds: time_t, pub nanoseconds: time_t, } /// A file’s status in a Git repository. Whether a file is in a repository or /// not is handled by the Git module, rather than having a “null” variant in /// this enum. #[derive(PartialEq, Eq, Copy, Clone)] pub enum GitStatus { /// This file hasn’t changed since the last commit. NotModified, /// This file didn’t exist for the last commit, and is not specified in /// the ignored files list. New, /// A file that’s been modified since the last commit. Modified, /// A deleted file. This can’t ever be shown, but it’s here anyway! Deleted, /// A file that Git has tracked a rename for. Renamed, /// A file that’s had its type (such as the file permissions) changed. TypeChange, /// A file that’s ignored (that matches a line in .gitignore) Ignored, /// A file that’s updated but unmerged. Conflicted, } /// A file’s complete Git status. It’s possible to make changes to a file, add /// it to the staging area, then make *more* changes, so we need to list each /// file’s status for both of these. #[derive(Copy, Clone)] pub struct Git { pub staged: GitStatus, pub unstaged: GitStatus, } impl Default for Git { /// Create a Git status for a file with nothing done to it. fn default() -> Self { Self { staged: GitStatus::NotModified, unstaged: GitStatus::NotModified, } } } pub enum SecurityContextType<'a> { SELinux(&'a str), None, } pub struct SecurityContext<'a> { pub context: SecurityContextType<'a>, } #[allow(dead_code)] #[derive(PartialEq, Copy, Clone)] pub enum SubdirGitRepoStatus { NoRepo, GitClean, GitDirty, } #[derive(Clone)] pub struct SubdirGitRepo { pub status: Option, pub branch: Option, } impl Default for SubdirGitRepo { fn default() -> Self { Self { status: Some(SubdirGitRepoStatus::NoRepo), branch: None, } } } /// The user file flags on the file. This will only ever be a number; /// looking up the flags is done in the `display` module. pub struct Flags(pub flag_t); eza-0.18.2/src/fs/file.rs000064400000000000000000001061261046102023000131650ustar 00000000000000//! Files, and methods and fields to access their metadata. #[cfg(unix)] use std::collections::HashMap; use std::io; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; #[cfg(unix)] use std::str; #[cfg(unix)] use std::sync::Mutex; use std::sync::OnceLock; use chrono::prelude::*; use log::*; #[cfg(unix)] use once_cell::sync::Lazy; use crate::fs::dir::Dir; use crate::fs::feature::xattr; use crate::fs::feature::xattr::{Attribute, FileAttributes}; use crate::fs::fields as f; use crate::fs::fields::SecurityContextType; use crate::fs::recursive_size::RecursiveSize; use super::mounts::all_mounts; use super::mounts::MountedFs; // Maps (device_id, inode) => (size_in_bytes, size_in_blocks) // Mutex::new is const but HashMap::new is not const requiring us to use lazy // initialization. // TODO: Replace with std::sync::LazyLock when it is stable. #[allow(clippy::type_complexity)] #[cfg(unix)] static DIRECTORY_SIZE_CACHE: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); /// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with /// associated data about the file. /// /// Each file is definitely going to have its filename displayed at least /// once, have its file extension extracted at least once, and have its metadata /// information queried at least once, so it makes sense to do all this at the /// start and hold on to all the information. pub struct File<'dir> { /// The filename portion of this file’s path, including the extension. /// /// This is used to compare against certain filenames (such as checking if /// it’s “Makefile” or something) and to highlight only the filename in /// colour when displaying the path. pub name: String, /// The file’s name’s extension, if present, extracted from the name. /// /// This is queried many times over, so it’s worth caching it. pub ext: Option, /// The path that begat this file. /// /// Even though the file’s name is extracted, the path needs to be kept /// around, as certain operations involve looking up the file’s absolute /// location (such as searching for compiled files) or using its original /// path (following a symlink). pub path: PathBuf, /// A cached `metadata` (`stat`) call for this file. /// /// This too is queried multiple times, and is *not* cached by the OS, as /// it could easily change between invocations — but exa is so short-lived /// it’s better to just cache it. pub metadata: std::fs::Metadata, /// A reference to the directory that contains this file, if any. /// /// Filenames that get passed in on the command-line directly will have no /// parent directory reference — although they technically have one on the /// filesystem, we’ll never need to look at it, so it’ll be `None`. /// However, *directories* that get passed in will produce files that /// contain a reference to it, which is used in certain operations (such /// as looking up compiled files). pub parent_dir: Option<&'dir Dir>, /// Whether this is one of the two `--all all` directories, `.` and `..`. /// /// Unlike all other entries, these are not returned as part of the /// directory’s children, and are in fact added specifically by exa; this /// means that they should be skipped when recursing. pub is_all_all: bool, /// Whether to dereference symbolic links when querying for information. /// /// For instance, when querying the size of a symbolic link, if /// dereferencing is enabled, the size of the target will be displayed /// instead. pub deref_links: bool, /// The recursive directory size when total_size is used. recursive_size: RecursiveSize, /// The extended attributes of this file. extended_attributes: OnceLock>, /// The absolute value of this path, used to look up mount points. absolute_path: OnceLock>, } impl<'dir> File<'dir> { pub fn from_args( path: PathBuf, parent_dir: PD, filename: FN, deref_links: bool, total_size: bool, ) -> io::Result> where PD: Into>, FN: Into>, { let parent_dir = parent_dir.into(); let name = filename.into().unwrap_or_else(|| File::filename(&path)); let ext = File::ext(&path); debug!("Statting file {:?}", &path); let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = false; let extended_attributes = OnceLock::new(); let absolute_path = OnceLock::new(); let recursive_size = if total_size { RecursiveSize::Unknown } else { RecursiveSize::None }; let mut file = File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, recursive_size, extended_attributes, absolute_path, }; if total_size { file.recursive_size = file.recursive_directory_size(); } Ok(file) } fn new_aa( path: PathBuf, parent_dir: &'dir Dir, name: &'static str, total_size: bool, ) -> io::Result> { let ext = File::ext(&path); debug!("Statting file {:?}", &path); let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = true; let parent_dir = Some(parent_dir); let extended_attributes = OnceLock::new(); let absolute_path = OnceLock::new(); let recursive_size = if total_size { RecursiveSize::Unknown } else { RecursiveSize::None }; let mut file = File { name: name.into(), ext, path, metadata, parent_dir, is_all_all, deref_links: false, extended_attributes, absolute_path, recursive_size, }; if total_size { file.recursive_size = file.recursive_directory_size(); } Ok(file) } pub fn new_aa_current(parent_dir: &'dir Dir, total_size: bool) -> io::Result> { File::new_aa(parent_dir.path.clone(), parent_dir, ".", total_size) } pub fn new_aa_parent( path: PathBuf, parent_dir: &'dir Dir, total_size: bool, ) -> io::Result> { File::new_aa(path, parent_dir, "..", total_size) } /// A file’s name is derived from its string. This needs to handle directories /// such as `/` or `..`, which have no `file_name` component. So instead, just /// use the last component as the name. pub fn filename(path: &Path) -> String { if let Some(back) = path.components().next_back() { back.as_os_str().to_string_lossy().to_string() } else { // use the path as fallback error!("Path {:?} has no last component", path); path.display().to_string() } } /// Extract an extension from a file path, if one is present, in lowercase. /// /// The extension is the series of characters after the last dot. This /// deliberately counts dotfiles, so the “.git” folder has the extension “git”. /// /// ASCII lowercasing is used because these extensions are only compared /// against a pre-compiled list of extensions which are known to only exist /// within ASCII, so it’s alright. fn ext(path: &Path) -> Option { let name = path.file_name().map(|f| f.to_string_lossy().to_string())?; name.rfind('.').map(|p| name[p + 1..].to_ascii_lowercase()) } /// Read the extended attributes of a file path. fn gather_extended_attributes(&self) -> Vec { if xattr::ENABLED { let attributes = if self.deref_links { self.path.attributes() } else { self.path.symlink_attributes() }; match attributes { Ok(xattrs) => xattrs, Err(e) => { error!( "Error looking up extended attributes for {}: {}", self.path.display(), e ); Vec::new() } } } else { Vec::new() } } /// Get the extended attributes of a file path on demand. pub fn extended_attributes(&self) -> &Vec { self.extended_attributes .get_or_init(|| self.gather_extended_attributes()) } /// Whether this file is a directory on the filesystem. pub fn is_directory(&self) -> bool { self.metadata.is_dir() } /// Whether this file is a directory, or a symlink pointing to a directory. pub fn points_to_directory(&self) -> bool { if self.is_directory() { return true; } if self.is_link() { let target = self.link_target(); if let FileTarget::Ok(target) = target { return target.points_to_directory(); } } false } /// If this file is a directory on the filesystem, then clone its /// `PathBuf` for use in one of our own `Dir` values, and read a list of /// its contents. /// /// Returns an IO error upon failure, but this shouldn’t be used to check /// if a `File` is a directory or not! For that, just use `is_directory()`. pub fn to_dir(&self) -> io::Result

{ trace!("to_dir: reading dir"); Dir::read_dir(self.path.clone()) } /// Whether this file is a regular file on the filesystem — that is, not a /// directory, a link, or anything else treated specially. pub fn is_file(&self) -> bool { self.metadata.is_file() } /// Whether this file is both a regular file *and* executable for the /// current user. An executable file has a different purpose from an /// executable directory, so they should be highlighted differently. #[cfg(unix)] pub fn is_executable_file(&self) -> bool { let bit = modes::USER_EXECUTE; self.is_file() && (self.metadata.permissions().mode() & bit) == bit } /// Whether this file is a symlink on the filesystem. pub fn is_link(&self) -> bool { self.metadata.file_type().is_symlink() } /// Whether this file is a named pipe on the filesystem. #[cfg(unix)] pub fn is_pipe(&self) -> bool { self.metadata.file_type().is_fifo() } /// Whether this file is a char device on the filesystem. #[cfg(unix)] pub fn is_char_device(&self) -> bool { self.metadata.file_type().is_char_device() } /// Whether this file is a block device on the filesystem. #[cfg(unix)] pub fn is_block_device(&self) -> bool { self.metadata.file_type().is_block_device() } /// Whether this file is a socket on the filesystem. #[cfg(unix)] pub fn is_socket(&self) -> bool { self.metadata.file_type().is_socket() } /// Determine the full path resolving all symbolic links on demand. pub fn absolute_path(&self) -> Option<&PathBuf> { self.absolute_path .get_or_init(|| std::fs::canonicalize(&self.path).ok()) .as_ref() } /// Whether this file is a mount point pub fn is_mount_point(&self) -> bool { cfg!(any(target_os = "linux", target_os = "macos")) && self.is_directory() && self .absolute_path() .is_some_and(|p| all_mounts().contains_key(p)) } /// The filesystem device and type for a mount point pub fn mount_point_info(&self) -> Option<&MountedFs> { if cfg!(any(target_os = "linux", target_os = "macos")) { return self.absolute_path().and_then(|p| all_mounts().get(p)); } None } /// Re-prefixes the path pointed to by this file, if it’s a symlink, to /// make it an absolute path that can be accessed from whichever /// directory exa is being run from. fn reorient_target_path(&self, path: &Path) -> PathBuf { if path.is_absolute() { path.to_path_buf() } else if let Some(dir) = self.parent_dir { dir.join(path) } else if let Some(parent) = self.path.parent() { parent.join(path) } else { self.path.join(path) } } /// Again assuming this file is a symlink, follows that link and returns /// the result of following it. /// /// For a working symlink that the user is allowed to follow, /// this will be the `File` object at the other end, which can then have /// its name, colour, and other details read. /// /// For a broken symlink, returns where the file *would* be, if it /// existed. If this file cannot be read at all, returns the error that /// we got when we tried to read it. pub fn link_target(&self) -> FileTarget<'dir> { // We need to be careful to treat the path actually pointed to by // this file — which could be absolute or relative — to the path // we actually look up and turn into a `File` — which needs to be // absolute to be accessible from any directory. debug!("Reading link {:?}", &self.path); let path = match std::fs::read_link(&self.path) { Ok(p) => p, Err(e) => return FileTarget::Err(e), }; let absolute_path = self.reorient_target_path(&path); // Use plain `metadata` instead of `symlink_metadata` - we *want* to // follow links. match std::fs::metadata(&absolute_path) { Ok(metadata) => { let ext = File::ext(&path); let name = File::filename(&path); let extended_attributes = OnceLock::new(); let absolute_path_cell = OnceLock::from(Some(absolute_path)); let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, deref_links: self.deref_links, extended_attributes, absolute_path: absolute_path_cell, recursive_size: RecursiveSize::None, }; FileTarget::Ok(Box::new(file)) } Err(e) => { error!("Error following link {:?}: {:#?}", &path, e); FileTarget::Broken(path) } } } /// Assuming this file is a symlink, follows that link and any further /// links recursively, returning the result from following the trail. /// /// For a working symlink that the user is allowed to follow, /// this will be the `File` object at the other end, which can then have /// its name, colour, and other details read. /// /// For a broken symlink, returns where the file *would* be, if it /// existed. If this file cannot be read at all, returns the error that /// we got when we tried to read it. pub fn link_target_recurse(&self) -> FileTarget<'dir> { let target = self.link_target(); if let FileTarget::Ok(f) = target { if f.is_link() { return f.link_target_recurse(); } return FileTarget::Ok(f); } target } /// This file’s number of hard links. /// /// It also reports whether this is both a regular file, and a file with /// multiple links. This is important, because a file with multiple links /// is uncommon, while you come across directories and other types /// with multiple links much more often. Thus, it should get highlighted /// more attentively. #[cfg(unix)] pub fn links(&self) -> f::Links { let count = self.metadata.nlink(); f::Links { count, multiple: self.is_file() && count > 1, } } /// This file’s inode. #[cfg(unix)] pub fn inode(&self) -> f::Inode { f::Inode(self.metadata.ino()) } /// This actual size the file takes up on disk, in bytes. #[cfg(unix)] pub fn blocksize(&self) -> f::Blocksize { if self.deref_links && self.is_link() { match self.link_target() { FileTarget::Ok(f) => f.blocksize(), _ => f::Blocksize::None, } } else if self.is_directory() { self.recursive_size.map_or(f::Blocksize::None, |_, blocks| { f::Blocksize::Some(blocks * 512) }) } else if self.is_file() { // Note that metadata.blocks returns the number of blocks // for 512 byte blocks according to the POSIX standard // even though the physical block size may be different. f::Blocksize::Some(self.metadata.blocks() * 512) } else { // directory or symlinks f::Blocksize::None } } /// The ID of the user that own this file. If dereferencing links, the links /// may be broken, in which case `None` will be returned. #[cfg(unix)] pub fn user(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.user(), _ => None, }; } Some(f::User(self.metadata.uid())) } /// The ID of the group that owns this file. #[cfg(unix)] pub fn group(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.group(), _ => None, }; } Some(f::Group(self.metadata.gid())) } /// This file’s size, if it’s a regular file. /// /// For directories, the recursive size or no size is given depending on /// flags. Although they do have a size on some filesystems, I’ve never /// looked at one of those numbers and gained any information from it. /// /// Block and character devices return their device IDs, because they /// usually just have a file size of zero. /// /// Links will return the size of their target (recursively through other /// links) if dereferencing is enabled, otherwise None. #[cfg(unix)] pub fn size(&self) -> f::Size { if self.deref_links && self.is_link() { match self.link_target() { FileTarget::Ok(f) => f.size(), _ => f::Size::None, } } else if self.is_directory() { self.recursive_size .map_or(f::Size::None, |bytes, _| f::Size::Some(bytes)) } else if self.is_char_device() || self.is_block_device() { let device_id = self.metadata.rdev(); // MacOS and Linux have different arguments and return types for the // functions major and minor. On Linux the try_into().unwrap() and // the "as u32" cast are not needed. We turn off the warning to // allow it to compile cleanly on Linux. #[allow(trivial_numeric_casts)] #[allow(clippy::unnecessary_cast, clippy::useless_conversion)] f::Size::DeviceIDs(f::DeviceIDs { // SAFETY: Calling libc function to decompose the device_id major: unsafe { libc::major(device_id.try_into().unwrap()) } as u32, minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32, }) } else if self.is_file() { f::Size::Some(self.metadata.len()) } else { // symlink f::Size::None } } /// Returns the size of the file or indicates no size if it's a directory. /// /// For Windows platforms, the size of directories is not computed and will /// return `Size::None`. #[cfg(windows)] pub fn size(&self) -> f::Size { if self.is_directory() { f::Size::None } else { f::Size::Some(self.metadata.len()) } } /// Calculate the total directory size recursively. If not a directory `None` /// will be returned. The directory size is cached for recursive directory /// listing. #[cfg(unix)] fn recursive_directory_size(&self) -> RecursiveSize { if self.is_directory() { let key = (self.metadata.dev(), self.metadata.ino()); if let Some(size) = DIRECTORY_SIZE_CACHE.lock().unwrap().get(&key) { return RecursiveSize::Some(size.0, size.1); } Dir::read_dir(self.path.clone()).map_or(RecursiveSize::Unknown, |dir| { let mut size = 0; let mut blocks = 0; for file in dir .files(super::DotFilter::Dotfiles, None, false, false, true) .flatten() { match file.recursive_directory_size() { RecursiveSize::Some(bytes, blks) => { size += bytes; blocks += blks; } RecursiveSize::Unknown => {} RecursiveSize::None => { size += file.metadata.size(); blocks += file.metadata.blocks(); } } } DIRECTORY_SIZE_CACHE .lock() .unwrap() .insert(key, (size, blocks)); RecursiveSize::Some(size, blocks) }) } else { RecursiveSize::None } } /// Windows version always returns None. The metadata for /// `volume_serial_number` and `file_index` are marked unstable so we can /// not cache the sizes. Without caching we could end up walking the /// directory structure several times. #[cfg(windows)] fn recursive_directory_size(&self) -> RecursiveSize { RecursiveSize::None } /// Returns the same value as `self.metadata.len()` or the recursive size /// of a directory when `total_size` is used. #[inline] pub fn length(&self) -> u64 { self.recursive_size.unwrap_bytes_or(self.metadata.len()) } /// Is the file is using recursive size calculation #[inline] pub fn is_recursive_size(&self) -> bool { !self.recursive_size.is_none() } /// Determines if the directory is empty or not. /// /// For Unix platforms, this function first checks the link count to quickly /// determine non-empty directories. On most UNIX filesystems the link count /// is two plus the number of subdirectories. If the link count is less than /// or equal to 2, it then checks the directory contents to determine if /// it's truly empty. The naive approach used here checks the contents /// directly, as certain filesystems make it difficult to infer emptiness /// based on directory size alone. #[cfg(unix)] pub fn is_empty_dir(&self) -> bool { if self.is_directory() { if self.metadata.nlink() > 2 { // Directories will have a link count of two if they do not have any subdirectories. // The '.' entry is a link to itself and the '..' is a link to the parent directory. // A subdirectory will have a link to its parent directory increasing the link count // above two. This will avoid the expensive read_dir call below when a directory // has subdirectories. false } else { self.is_empty_directory() } } else { false } } /// Determines if the directory is empty or not. /// /// For Windows platforms, this function checks the directory contents directly /// to determine if it's empty. Since certain filesystems on Windows make it /// challenging to infer emptiness based on directory size, this approach is used. #[cfg(windows)] pub fn is_empty_dir(&self) -> bool { if self.is_directory() { self.is_empty_directory() } else { false } } /// Checks the contents of the directory to determine if it's empty. /// /// This function avoids counting '.' and '..' when determining if the directory is /// empty. If any other entries are found, it returns `false`. /// /// The naive approach, as one would think that this info may have been cached. /// but as mentioned in the size function comment above, different filesystems /// make it difficult to get any info about a dir by it's size, so this may be it. fn is_empty_directory(&self) -> bool { trace!("is_empty_directory: reading dir"); match Dir::read_dir(self.path.clone()) { // . & .. are skipped, if the returned iterator has .next(), it's not empty Ok(has_files) => has_files .files(super::DotFilter::Dotfiles, None, false, false, false) .next() .is_none(), Err(_) => false, } } /// This file’s last modified timestamp, if available on this platform. pub fn modified_time(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.modified_time(), _ => None, }; } self.metadata .modified() .map(|st| DateTime::::from(st).naive_utc()) .ok() } /// This file’s last changed timestamp, if available on this platform. #[cfg(unix)] pub fn changed_time(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.changed_time(), _ => None, }; } NaiveDateTime::from_timestamp_opt(self.metadata.ctime(), self.metadata.ctime_nsec() as u32) } #[cfg(windows)] pub fn changed_time(&self) -> Option { self.modified_time() } /// This file’s last accessed timestamp, if available on this platform. pub fn accessed_time(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.accessed_time(), _ => None, }; } self.metadata .accessed() .map(|st| DateTime::::from(st).naive_utc()) .ok() } /// This file’s created timestamp, if available on this platform. pub fn created_time(&self) -> Option { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.created_time(), _ => None, }; } match self.metadata.created() { Ok(btime) => Some(DateTime::::from(btime).naive_utc()), Err(_) => None, } } /// This file’s ‘type’. /// /// This is used a the leftmost character of the permissions column. /// The file type can usually be guessed from the colour of the file, but /// ls puts this character there. #[cfg(unix)] pub fn type_char(&self) -> f::Type { if self.is_file() { f::Type::File } else if self.is_directory() { f::Type::Directory } else if self.is_pipe() { f::Type::Pipe } else if self.is_link() { f::Type::Link } else if self.is_char_device() { f::Type::CharDevice } else if self.is_block_device() { f::Type::BlockDevice } else if self.is_socket() { f::Type::Socket } else { f::Type::Special } } #[cfg(windows)] pub fn type_char(&self) -> f::Type { if self.is_file() { f::Type::File } else if self.is_directory() { f::Type::Directory } else { f::Type::Special } } /// This file’s permissions, with flags for each bit. #[cfg(unix)] pub fn permissions(&self) -> Option { if self.is_link() && self.deref_links { // If the chain of links is broken, we instead fall through and // return the permissions of the original link, as would have been // done if we were not dereferencing. return match self.link_target_recurse() { FileTarget::Ok(f) => f.permissions(), _ => None, }; } let bits = self.metadata.mode(); let has_bit = |bit| bits & bit == bit; Some(f::Permissions { user_read: has_bit(modes::USER_READ), user_write: has_bit(modes::USER_WRITE), user_execute: has_bit(modes::USER_EXECUTE), group_read: has_bit(modes::GROUP_READ), group_write: has_bit(modes::GROUP_WRITE), group_execute: has_bit(modes::GROUP_EXECUTE), other_read: has_bit(modes::OTHER_READ), other_write: has_bit(modes::OTHER_WRITE), other_execute: has_bit(modes::OTHER_EXECUTE), sticky: has_bit(modes::STICKY), setgid: has_bit(modes::SETGID), setuid: has_bit(modes::SETUID), }) } #[cfg(windows)] pub fn attributes(&self) -> f::Attributes { let bits = self.metadata.file_attributes(); let has_bit = |bit| bits & bit == bit; // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants f::Attributes { directory: has_bit(0x10), archive: has_bit(0x20), readonly: has_bit(0x1), hidden: has_bit(0x2), system: has_bit(0x4), reparse_point: has_bit(0x400), } } /// This file’s security context field. #[cfg(unix)] pub fn security_context(&self) -> f::SecurityContext<'_> { let context = match self .extended_attributes() .iter() .find(|a| a.name == "security.selinux") { Some(attr) => match &attr.value { None => SecurityContextType::None, Some(value) => match str::from_utf8(value) { Ok(v) => SecurityContextType::SELinux(v.trim_end_matches(char::from(0))), Err(_) => SecurityContextType::None, }, }, None => SecurityContextType::None, }; f::SecurityContext { context } } #[cfg(windows)] pub fn security_context(&self) -> f::SecurityContext<'_> { f::SecurityContext { context: SecurityContextType::None, } } /// User file flags. #[cfg(any( target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly" ))] pub fn flags(&self) -> f::Flags { #[cfg(target_os = "dragonfly")] use std::os::dragonfly::fs::MetadataExt; #[cfg(target_os = "freebsd")] use std::os::freebsd::fs::MetadataExt; #[cfg(target_os = "macos")] use std::os::macos::fs::MetadataExt; #[cfg(target_os = "netbsd")] use std::os::netbsd::fs::MetadataExt; #[cfg(target_os = "openbsd")] use std::os::openbsd::fs::MetadataExt; f::Flags(self.metadata.st_flags()) } #[cfg(windows)] pub fn flags(&self) -> f::Flags { f::Flags(self.metadata.file_attributes()) } #[cfg(not(any( target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "windows" )))] pub fn flags(&self) -> f::Flags { f::Flags(0) } } impl<'a> AsRef> for File<'a> { fn as_ref(&self) -> &File<'a> { self } } /// The result of following a symlink. pub enum FileTarget<'dir> { /// The symlink pointed at a file that exists. Ok(Box>), /// The symlink pointed at a file that does not exist. Holds the path /// where the file would be, if it existed. Broken(PathBuf), /// There was an IO error when following the link. This can happen if the /// file isn’t a link to begin with, but also if, say, we don’t have /// permission to follow it. Err(io::Error), // Err is its own variant, instead of having the whole thing be inside an // `io::Result`, because being unable to follow a symlink is not a serious // error — we just display the error message and move on. } impl<'dir> FileTarget<'dir> { /// Whether this link doesn’t lead to a file, for whatever reason. This /// gets used to determine how to highlight the link in grid views. pub fn is_broken(&self) -> bool { matches!(self, Self::Broken(_) | Self::Err(_)) } } /// More readable aliases for the permission bits exposed by libc. #[allow(trivial_numeric_casts)] #[cfg(unix)] mod modes { // The `libc::mode_t` type’s actual type varies, but the value returned // from `metadata.permissions().mode()` is always `u32`. pub type Mode = u32; pub const USER_READ: Mode = libc::S_IRUSR as Mode; pub const USER_WRITE: Mode = libc::S_IWUSR as Mode; pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode; pub const GROUP_READ: Mode = libc::S_IRGRP as Mode; pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode; pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode; pub const OTHER_READ: Mode = libc::S_IROTH as Mode; pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode; pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode; pub const STICKY: Mode = libc::S_ISVTX as Mode; pub const SETGID: Mode = libc::S_ISGID as Mode; pub const SETUID: Mode = libc::S_ISUID as Mode; } #[cfg(test)] mod ext_test { use super::File; use std::path::Path; #[test] fn extension() { assert_eq!(Some("dat".to_string()), File::ext(Path::new("fester.dat"))); } #[test] fn dotfile() { assert_eq!(Some("vimrc".to_string()), File::ext(Path::new(".vimrc"))); } #[test] fn no_extension() { assert_eq!(None, File::ext(Path::new("jarlsberg"))); } } #[cfg(test)] mod filename_test { use super::File; use std::path::Path; #[test] fn file() { assert_eq!("fester.dat", File::filename(Path::new("fester.dat"))); } #[test] fn no_path() { assert_eq!("foo.wha", File::filename(Path::new("/var/cache/foo.wha"))); } #[test] fn here() { assert_eq!(".", File::filename(Path::new("."))); } #[test] fn there() { assert_eq!("..", File::filename(Path::new(".."))); } #[test] fn everywhere() { assert_eq!("..", File::filename(Path::new("./.."))); } #[test] #[cfg(unix)] fn topmost() { assert_eq!("/", File::filename(Path::new("/"))); } } eza-0.18.2/src/fs/filter.rs000064400000000000000000000336201046102023000135310ustar 00000000000000//! Filtering and sorting the list of files before displaying them. use std::cmp::Ordering; use std::iter::FromIterator; #[cfg(unix)] use std::os::unix::fs::MetadataExt; use crate::fs::DotFilter; use crate::fs::File; /// Flags used to manage the **file filter** process #[derive(PartialEq, Eq, Debug, Clone)] pub enum FileFilterFlags { /// Whether to reverse the sorting order. This would sort the largest /// files first, or files starting with Z, or the most-recently-changed /// ones, depending on the sort field. Reverse, /// Whether to only show directories. OnlyDirs, /// Whether to only show files. OnlyFiles, } /// The **file filter** processes a list of files before displaying them to /// the user, by removing files they don’t want to see, and putting the list /// in the desired order. /// /// Usually a user does not want to see *every* file in the list. The most /// common case is to remove files starting with `.`, which are designated /// as ‘hidden’ files. /// /// The special files `.` and `..` files are not actually filtered out, but /// need to be inserted into the list, in a special case. /// /// The filter also governs sorting the list. After being filtered, pairs of /// files are compared and sorted based on the result, with the sort field /// performing the comparison. #[derive(PartialEq, Eq, Debug, Clone)] pub struct FileFilter { /// Whether directories should be listed first, and other types of file /// second. Some users prefer it like this. pub list_dirs_first: bool, /// The metadata field to sort by. pub sort_field: SortField, // Flags that the file filtering process follow pub flags: Vec, /// Which invisible “dot” files to include when listing a directory. /// /// Files starting with a single “.” are used to determine “system” or /// “configuration” files that should not be displayed in a regular /// directory listing, and the directory entries “.” and “..” are /// considered extra-special. /// /// This came about more or less by a complete historical accident, /// when the original `ls` tried to hide `.` and `..`: /// /// [Linux History: How Dot Files Became Hidden Files](https://linux-audit.com/linux-history-how-dot-files-became-hidden-files/) pub dot_filter: DotFilter, /// Glob patterns to ignore. Any file name that matches *any* of these /// patterns won’t be displayed in the list. pub ignore_patterns: IgnorePatterns, /// Whether to ignore Git-ignored patterns. pub git_ignore: GitIgnore, } impl FileFilter { /// Remove every file in the given vector that does *not* pass the /// filter predicate for files found inside a directory. pub fn filter_child_files(&self, files: &mut Vec>) { use FileFilterFlags::{OnlyDirs, OnlyFiles}; files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); match ( self.flags.contains(&OnlyDirs), self.flags.contains(&OnlyFiles), ) { (true, false) => { // On pass -'-only-dirs' flag only files.retain(File::is_directory); } (false, true) => { // On pass -'-only-files' flag only files.retain(File::is_file); } _ => {} } } /// Remove every file in the given vector that does *not* pass the /// filter predicate for file names specified on the command-line. /// /// The rules are different for these types of files than the other /// type because the ignore rules can be used with globbing. For /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the /// dotfile, because it’s been directly specified. But running /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained /// from the glob, even though the globbing is done by the shell! pub fn filter_argument_files(&self, files: &mut Vec>) { files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); } /// Sort the files in the given vector based on the sort field option. pub fn sort_files<'a, F>(&self, files: &mut [F]) where F: AsRef>, { files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref())); if self.flags.contains(&FileFilterFlags::Reverse) { files.reverse(); } if self.list_dirs_first { // This relies on the fact that `sort_by` is *stable*: it will keep // adjacent elements next to each other. files.sort_by(|a, b| { b.as_ref() .points_to_directory() .cmp(&a.as_ref().points_to_directory()) }); } } } /// User-supplied field to sort by. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SortField { /// Don’t apply any sorting. This is usually used as an optimisation in /// scripts, where the order doesn’t matter. Unsorted, /// The file name. This is the default sorting. Name(SortCase), /// The file’s extension, with extensionless files being listed first. Extension(SortCase), /// The file’s size, in bytes. Size, /// The file’s inode, which usually corresponds to the order in which /// files were created on the filesystem, more or less. #[cfg(unix)] FileInode, /// The time the file was modified (the “mtime”). /// /// As this is stored as a Unix timestamp, rather than a local time /// instance, the time zone does not matter and will only be used to /// display the timestamps, not compare them. ModifiedDate, /// The time the file was accessed (the “atime”). /// /// Oddly enough, this field rarely holds the *actual* accessed time. /// Recording a read time means writing to the file each time it’s read /// slows the whole operation down, so many systems will only update the /// timestamp in certain circumstances. This has become common enough that /// it’s now expected behaviour! /// AccessedDate, /// The time the file was changed (the “ctime”). /// /// This field is used to mark the time when a file’s metadata /// changed — its permissions, owners, or link count. /// /// In original Unix, this was, however, meant as creation time. /// ChangedDate, /// The time the file was created (the “btime” or “birthtime”). CreatedDate, /// The type of the file: directories, links, pipes, regular, files, etc. /// /// Files are ordered according to the `PartialOrd` implementation of /// `fs::fields::Type`, so changing that will change this. FileType, /// The “age” of the file, which is the time it was modified sorted /// backwards. The reverse of the `ModifiedDate` ordering! /// /// It turns out that listing the most-recently-modified files first is a /// common-enough use case that it deserves its own variant. This would be /// implemented by just using the modified date and setting the reverse /// flag, but this would make reversing *that* output not work, which is /// bad, even though that’s kind of nonsensical. So it’s its own variant /// that can be reversed like usual. ModifiedAge, /// The file's name, however if the name of the file begins with `.` /// ignore the leading `.` and then sort as Name NameMixHidden(SortCase), } /// Whether a field should be sorted case-sensitively or case-insensitively. /// This determines which of the `natord` functions to use. /// /// I kept on forgetting which one was sensitive and which one was /// insensitive. Would a case-sensitive sort put capital letters first because /// it takes the case of the letters into account, or intermingle them with /// lowercase letters because it takes the difference between the two cases /// into account? I gave up and just named these two variants after the /// effects they have. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SortCase { /// Sort files case-sensitively with uppercase first, with ‘A’ coming /// before ‘a’. ABCabc, /// Sort files case-insensitively, with ‘A’ being equal to ‘a’. AaBbCc, } impl SortField { /// Compares two files to determine the order they should be listed in, /// depending on the search field. /// /// The `natord` crate is used here to provide a more *natural* sorting /// order than just sorting character-by-character. This splits filenames /// into groups between letters and numbers, and then sorts those blocks /// together, so `file10` will sort after `file9`, instead of before it /// because of the `1`. pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering { use self::SortCase::{ABCabc, AaBbCc}; #[rustfmt::skip] return match self { Self::Unsorted => Ordering::Equal, Self::Name(ABCabc) => natord::compare(&a.name, &b.name), Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name), Self::Size => a.length().cmp(&b.length()), #[cfg(unix)] Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()), Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()), Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()), Self::ChangedDate => a.changed_time().cmp(&b.changed_time()), Self::CreatedDate => a.created_time().cmp(&b.created_time()), Self::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a Self::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes Ordering::Equal => natord::compare(&a.name, &b.name), order => order, }, Self::Extension(ABCabc) => match a.ext.cmp(&b.ext) { Ordering::Equal => natord::compare(&a.name, &b.name), order => order, }, Self::Extension(AaBbCc) => match a.ext.cmp(&b.ext) { Ordering::Equal => natord::compare_ignore_case(&a.name, &b.name), order => order, }, Self::NameMixHidden(ABCabc) => natord::compare( Self::strip_dot(&a.name), Self::strip_dot(&b.name) ), Self::NameMixHidden(AaBbCc) => natord::compare_ignore_case( Self::strip_dot(&a.name), Self::strip_dot(&b.name) ), }; } fn strip_dot(n: &str) -> &str { match n.strip_prefix('.') { Some(s) => s, None => n, } } } /// The **ignore patterns** are a list of globs that are tested against /// each filename, and if any of them match, that file isn’t displayed. /// This lets a user hide, say, text files by ignoring `*.txt`. #[derive(PartialEq, Eq, Default, Debug, Clone)] pub struct IgnorePatterns { patterns: Vec, } impl FromIterator for IgnorePatterns { fn from_iter(iter: I) -> Self where I: IntoIterator, { let patterns = iter.into_iter().collect(); Self { patterns } } } impl IgnorePatterns { /// Create a new list from the input glob strings, turning the inputs that /// are valid glob patterns into an `IgnorePatterns`. The inputs that /// don’t parse correctly are returned separately. pub fn parse_from_iter<'a, I: IntoIterator>( iter: I, ) -> (Self, Vec) { let iter = iter.into_iter(); // Almost all glob patterns are valid, so it’s worth pre-allocating // the vector with enough space for all of them. let mut patterns = match iter.size_hint() { (_, Some(count)) => Vec::with_capacity(count), _ => Vec::new(), }; // Similarly, assume there won’t be any errors. let mut errors = Vec::new(); for input in iter { match glob::Pattern::new(input) { Ok(pat) => patterns.push(pat), Err(e) => errors.push(e), } } (Self { patterns }, errors) } /// Create a new empty set of patterns that matches nothing. pub fn empty() -> Self { Self { patterns: Vec::new(), } } /// Test whether the given file should be hidden from the results. fn is_ignored(&self, file: &str) -> bool { self.patterns.iter().any(|p| p.matches(file)) } } /// Whether to ignore or display files that Git would ignore. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum GitIgnore { /// Ignore files that Git would ignore. CheckAndIgnore, /// Display files, even if Git would ignore them. Off, } #[cfg(test)] mod test_ignores { use super::*; #[test] fn empty_matches_nothing() { let pats = IgnorePatterns::empty(); assert!(!pats.is_ignored("nothing")); assert!(!pats.is_ignored("test.mp3")); } #[test] fn ignores_a_glob() { let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["*.mp3"]); assert!(fails.is_empty()); assert!(!pats.is_ignored("nothing")); assert!(pats.is_ignored("test.mp3")); } #[test] fn ignores_an_exact_filename() { let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing"]); assert!(fails.is_empty()); assert!(pats.is_ignored("nothing")); assert!(!pats.is_ignored("test.mp3")); } #[test] fn ignores_both() { let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing", "*.mp3"]); assert!(fails.is_empty()); assert!(pats.is_ignored("nothing")); assert!(pats.is_ignored("test.mp3")); } } eza-0.18.2/src/fs/mod.rs000064400000000000000000000003171046102023000130200ustar 00000000000000mod dir; pub use self::dir::{Dir, DotFilter}; mod file; pub use self::file::{File, FileTarget}; pub mod dir_action; pub mod feature; pub mod fields; pub mod filter; pub mod mounts; pub mod recursive_size; eza-0.18.2/src/fs/mounts/linux.rs000064400000000000000000000007071046102023000147300ustar 00000000000000use crate::fs::mounts::{Error, MountedFs}; use proc_mounts::MountList; /// Get a list of all mounted filesystems pub fn mounts() -> Result, Error> { Ok(MountList::new() .map_err(Error::IOError)? .0 .iter() .map(|mount| MountedFs { dest: mount.dest.clone(), fstype: mount.fstype.clone(), source: mount.source.to_string_lossy().into(), }) .collect()) } eza-0.18.2/src/fs/mounts/macos.rs000064400000000000000000000044311046102023000146710ustar 00000000000000use crate::fs::mounts::{Error, MountedFs}; use libc::{__error, getfsstat, statfs, MNT_NOWAIT}; use std::ffi::{CStr, OsStr}; use std::os::raw::{c_char, c_int}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; use std::{mem, ptr}; /// Get a list of all mounted filesystem pub fn mounts() -> Result, Error> { // SAFETY: // Calling external "C" function getfsstat. Passing a null pointer and zero // bufsize will return the number of mounts. let mut count: i32 = unsafe { getfsstat(ptr::null_mut(), 0, MNT_NOWAIT) }; let mut mntbuf = Vec::::new(); if count > 0 { // SAFETY: Zero out buffer memory as we allocate. mntbuf.resize_with(count as usize, || unsafe { mem::zeroed() }); let bufsize = mntbuf.len() * mem::size_of::(); // SAFETY: // Calling external "C" function getfsstate with actual buffer now. The // function takes a buffer size to not overflow. If the mount table // changes size between calls we are protected by bufsize count = unsafe { getfsstat(mntbuf.as_mut_ptr(), bufsize as c_int, MNT_NOWAIT) }; // Resize if the mount table has shrunk since last call if count >= 0 { mntbuf.truncate(count as usize); } } if count < 0 { // SAFETY: Calling external "C" errno function to get the error number return Err(Error::GetFSStatError(unsafe { *__error() })); } let mut mounts = Vec::with_capacity(count as usize); for mnt in &mntbuf { let mount_point = OsStr::from_bytes( // SAFETY: Converting null terminated "C" string unsafe { CStr::from_ptr(mnt.f_mntonname.as_ptr().cast::()) }.to_bytes(), ); let dest = PathBuf::from(mount_point); // SAFETY: Converting null terminated "C" string let fstype = unsafe { CStr::from_ptr(mnt.f_fstypename.as_ptr().cast::()) } .to_string_lossy() .into(); // SAFETY: Converting null terminated "C" string let source = unsafe { CStr::from_ptr(mnt.f_mntfromname.as_ptr().cast::()) } .to_string_lossy() .into(); mounts.push(MountedFs { dest, fstype, source, }); } Ok(mounts) } eza-0.18.2/src/fs/mounts/mod.rs000064400000000000000000000043311046102023000143450ustar 00000000000000use std::collections::HashMap; use std::path::PathBuf; use std::sync::OnceLock; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "linux")] use linux::mounts; #[cfg(target_os = "macos")] use macos::mounts; /// Details of a mounted filesystem. #[derive(Clone)] pub struct MountedFs { pub dest: PathBuf, pub fstype: String, pub source: String, } #[derive(Debug)] #[non_exhaustive] pub enum Error { #[cfg(target_os = "macos")] GetFSStatError(i32), #[cfg(target_os = "linux")] IOError(std::io::Error), } impl std::error::Error for Error {} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Allow unreachable_patterns for windows build #[allow(unreachable_patterns)] match self { #[cfg(target_os = "macos")] Error::GetFSStatError(err) => write!(f, "getfsstat failed: {err}"), #[cfg(target_os = "linux")] Error::IOError(err) => write!(f, "failed to read /proc/mounts: {err}"), _ => write!(f, "Unknown error"), } } } // A lazily initialised static map of all mounted file systems. // // The map contains a mapping from the mounted directory path to the // corresponding mount information. If there's an error retrieving the mount // list or if we're not running on Linux or Mac, the map will be empty. // // Initialise this at application start so we don't have to look the details // up for every directory. Ideally this would only be done if the --mounts // option is specified which will be significantly easier once the move // to `clap` is complete. pub(super) fn all_mounts() -> &'static HashMap { static ALL_MOUNTS: OnceLock> = OnceLock::new(); ALL_MOUNTS.get_or_init(|| { // Allow unused_mut for windows build #[allow(unused_mut)] let mut mount_map: HashMap = HashMap::new(); #[cfg(any(target_os = "linux", target_os = "macos"))] if let Ok(mounts) = mounts() { for mount in mounts { mount_map.insert(mount.dest.clone(), mount); } } mount_map }) } eza-0.18.2/src/fs/recursive_size.rs000064400000000000000000000046471046102023000153140ustar 00000000000000/// Used to represent a the size of a recursive directory traversal. `None` /// should be used when the file does not represent a directory or the recursive /// size should not be calculated. #[derive(Copy, Clone, Debug)] pub enum RecursiveSize { /// Size should not be computed None, /// Size should be computed but has not been computed yet Unknown, /// Size has been computed. First field is size in bytes and second field /// is size in blocks #[cfg_attr(target_family = "windows", allow(dead_code))] Some(u64, u64), } impl RecursiveSize { /// Returns `true` if `None` /// /// # Examples /// /// ``` /// use eza::fs::recursive_size::RecursiveSize; /// /// let x = RecursiveSize::None; /// assert_eq!(x.is_none(), true); /// /// let x = RecursiveSize::Unknown; /// assert_eq!(x.is_none(), false); /// /// let x = RecursiveSize::Some(0, 0); /// assert_eq!(x.is_none(), false); /// ``` #[inline] pub const fn is_none(&self) -> bool { matches!(*self, Self::None) } /// Returns the contained [`Some`] value or a provided default. /// /// # Examples /// /// ``` /// use eza::fs::recursive_size::RecursiveSize; /// /// assert_eq!(RecursiveSize::None.unwrap_bytes_or(1), 1); /// assert_eq!(RecursiveSize::Unknown.unwrap_bytes_or(1), 1); /// assert_eq!(RecursiveSize::Some(2, 3).unwrap_bytes_or(1), 2); /// ``` #[inline] pub const fn unwrap_bytes_or(self, default: u64) -> u64 { match self { Self::Some(bytes, _blocks) => bytes, _ => default, } } /// Returns the provided default result (if None or Unknown), /// or applies a function to the contained value (if Some). /// /// # Examples /// /// ``` /// use eza::fs::recursive_size::RecursiveSize; /// /// assert_eq!(RecursiveSize::None.map_or(None, |s, _| Some(s * 2)), None); /// assert_eq!(RecursiveSize::Unknown.map_or(None, |s, _| Some(s * 2)), None); /// assert_eq!(RecursiveSize::Some(2, 3).map_or(None, |s, _| Some(s * 2)), Some(4)); /// ``` #[inline] #[cfg_attr(target_family = "windows", allow(dead_code))] pub fn map_or(self, default: U, f: F) -> U where F: FnOnce(u64, u64) -> U, { match self { RecursiveSize::Some(bytes, blocks) => f(bytes, blocks), _ => default, } } } eza-0.18.2/src/info/filetype.rs000064400000000000000000000423161046102023000144120ustar 00000000000000//! Tests for various types of file (video, image, compressed, etc). //! //! Currently this is dependent on the file’s name and extension, because //! those are the only metadata that we have access to without reading the //! file’s contents. //! //! # Contributors //! Please keep these lists sorted. If you're using vim, :sort i use phf::{phf_map, Map}; use crate::fs::File; #[derive(Debug, Clone)] pub enum FileType { Image, Video, Music, Lossless, // Lossless music, rather than any other kind of data... Crypto, Document, Compressed, Temp, Compiled, Build, // A “build file is something that can be run or activated somehow in order to // kick off the build of a project. It’s usually only present in directories full of // source code. Source, } /// Mapping from full filenames to file type. const FILENAME_TYPES: Map<&'static str, FileType> = phf_map! { /* Immediate file - kick off the build of a project */ "Brewfile" => FileType::Build, "bsconfig.json" => FileType::Build, "BUILD" => FileType::Build, "BUILD.bazel" => FileType::Build, "build.gradle" => FileType::Build, "build.sbt" => FileType::Build, "build.xml" => FileType::Build, "Cargo.toml" => FileType::Build, "CMakeLists.txt" => FileType::Build, "composer.json" => FileType::Build, "configure" => FileType::Build, "Containerfile" => FileType::Build, "Dockerfile" => FileType::Build, "Earthfile" => FileType::Build, "flake.nix" => FileType::Build, "Gemfile" => FileType::Build, "GNUmakefile" => FileType::Build, "Gruntfile.coffee" => FileType::Build, "Gruntfile.js" => FileType::Build, "jsconfig.json" => FileType::Build, "Justfile" => FileType::Build, "justfile" => FileType::Build, "Makefile" => FileType::Build, "makefile" => FileType::Build, "meson.build" => FileType::Build, "mix.exs" => FileType::Build, "package.json" => FileType::Build, "Pipfile" => FileType::Build, "PKGBUILD" => FileType::Build, "Podfile" => FileType::Build, "pom.xml" => FileType::Build, "Procfile" => FileType::Build, "pyproject.toml" => FileType::Build, "Rakefile" => FileType::Build, "RoboFile.php" => FileType::Build, "SConstruct" => FileType::Build, "tsconfig.json" => FileType::Build, "Vagrantfile" => FileType::Build, "webpack.config.cjs" => FileType::Build, "webpack.config.js" => FileType::Build, "WORKSPACE" => FileType::Build, /* Cryptology files */ "id_dsa" => FileType::Crypto, "id_ecdsa" => FileType::Crypto, "id_ecdsa_sk" => FileType::Crypto, "id_ed25519" => FileType::Crypto, "id_ed25519_sk" => FileType::Crypto, "id_rsa" => FileType::Crypto, }; /// Mapping from lowercase file extension to file type. If an image, video, music, or lossless /// extension is added also update the extension icon map. const EXTENSION_TYPES: Map<&'static str, FileType> = phf_map! { /* Immediate file - kick off the build of a project */ "ninja" => FileType::Build, /* Image files */ "arw" => FileType::Image, "avif" => FileType::Image, "bmp" => FileType::Image, "cbr" => FileType::Image, "cbz" => FileType::Image, "cr2" => FileType::Image, "dvi" => FileType::Image, "eps" => FileType::Image, "gif" => FileType::Image, "heic" => FileType::Image, "heif" => FileType::Image, "ico" => FileType::Image, "j2c" => FileType::Image, "j2k" => FileType::Image, "jfi" => FileType::Image, "jfif" => FileType::Image, "jif" => FileType::Image, "jp2" => FileType::Image, "jpe" => FileType::Image, "jpeg" => FileType::Image, "jpf" => FileType::Image, "jpg" => FileType::Image, "jpx" => FileType::Image, "jxl" => FileType::Image, "nef" => FileType::Image, "orf" => FileType::Image, "pbm" => FileType::Image, "pgm" => FileType::Image, "png" => FileType::Image, "pnm" => FileType::Image, "ppm" => FileType::Image, "ps" => FileType::Image, "psd" => FileType::Image, "pxm" => FileType::Image, "raw" => FileType::Image, "qoi" => FileType::Image, "stl" => FileType::Image, "svg" => FileType::Image, "tif" => FileType::Image, "tiff" => FileType::Image, "webp" => FileType::Image, "xcf" => FileType::Image, "xpm" => FileType::Image, /* Video files */ "avi" => FileType::Video, "flv" => FileType::Video, "h264" => FileType::Video, "heics" => FileType::Video, "m2ts" => FileType::Video, "m2v" => FileType::Video, "m4v" => FileType::Video, "mkv" => FileType::Video, "mov" => FileType::Video, "mp4" => FileType::Video, "mpeg" => FileType::Video, "mpg" => FileType::Video, "ogm" => FileType::Video, "ogv" => FileType::Video, "video" => FileType::Video, "vob" => FileType::Video, "webm" => FileType::Video, "wmv" => FileType::Video, /* Music files */ "aac" => FileType::Music, // Advanced Audio Coding "m4a" => FileType::Music, "mka" => FileType::Music, "mp2" => FileType::Music, "mp3" => FileType::Music, "ogg" => FileType::Music, "opus" => FileType::Music, "wma" => FileType::Music, /* Lossless music, rather than any other kind of data... */ "aif" => FileType::Lossless, "aifc" => FileType::Lossless, "aiff" => FileType::Lossless, "alac" => FileType::Lossless, "ape" => FileType::Lossless, "flac" => FileType::Lossless, "pcm" => FileType::Lossless, "wav" => FileType::Lossless, "wv" => FileType::Lossless, /* Cryptology files */ "asc" => FileType::Crypto, // GnuPG ASCII armored file "cer" => FileType::Crypto, "crt" => FileType::Crypto, "csr" => FileType::Crypto, // PKCS#10 Certificate Signing Request "gpg" => FileType::Crypto, // GnuPG encrypted file "kbx" => FileType::Crypto, // GnuPG keybox "md5" => FileType::Crypto, // MD5 checksum "p12" => FileType::Crypto, // PKCS#12 certificate (Netscape) "pem" => FileType::Crypto, // Privacy-Enhanced Mail certificate "pfx" => FileType::Crypto, // PKCS#12 certificate (Microsoft) "pgp" => FileType::Crypto, // PGP security key "pub" => FileType::Crypto, // Public key "sha1" => FileType::Crypto, // SHA-1 hash "sha224" => FileType::Crypto, // SHA-224 hash "sha256" => FileType::Crypto, // SHA-256 hash "sha384" => FileType::Crypto, // SHA-384 hash "sha512" => FileType::Crypto, // SHA-512 hash "sig" => FileType::Crypto, // GnuPG signed file "signature" => FileType::Crypto, // e-Filing Digital Signature File (India) /* Document files */ "djvu" => FileType::Document, "doc" => FileType::Document, "docx" => FileType::Document, "eml" => FileType::Document, "fotd" => FileType::Document, "gdoc" => FileType::Document, "key" => FileType::Document, "keynote" => FileType::Document, "numbers" => FileType::Document, "odp" => FileType::Document, "ods" => FileType::Document, "odt" => FileType::Document, "pages" => FileType::Document, "pdf" => FileType::Document, "ppt" => FileType::Document, "pptx" => FileType::Document, "rtf" => FileType::Document, // Rich Text Format "xls" => FileType::Document, "xlsm" => FileType::Document, "xlsx" => FileType::Document, /* Compressed/archive files */ "7z" => FileType::Compressed, // 7-Zip "ar" => FileType::Compressed, "arj" => FileType::Compressed, "br" => FileType::Compressed, // Brotli "bz" => FileType::Compressed, // bzip "bz2" => FileType::Compressed, // bzip2 "bz3" => FileType::Compressed, // bzip3 "cpio" => FileType::Compressed, "deb" => FileType::Compressed, // Debian "dmg" => FileType::Compressed, "gz" => FileType::Compressed, // gzip "iso" => FileType::Compressed, "lz" => FileType::Compressed, "lz4" => FileType::Compressed, "lzh" => FileType::Compressed, "lzma" => FileType::Compressed, "lzo" => FileType::Compressed, "phar" => FileType::Compressed, // PHP PHAR "qcow" => FileType::Compressed, "qcow2" => FileType::Compressed, "rar" => FileType::Compressed, "rpm" => FileType::Compressed, "tar" => FileType::Compressed, "taz" => FileType::Compressed, "tbz" => FileType::Compressed, "tbz2" => FileType::Compressed, "tc" => FileType::Compressed, "tgz" => FileType::Compressed, "tlz" => FileType::Compressed, "txz" => FileType::Compressed, "tz" => FileType::Compressed, "xz" => FileType::Compressed, "vdi" => FileType::Compressed, "vhd" => FileType::Compressed, "vhdx" => FileType::Compressed, "vmdk" => FileType::Compressed, "z" => FileType::Compressed, "zip" => FileType::Compressed, "zst" => FileType::Compressed, // Zstandard /* Temporary files */ "bak" => FileType::Temp, "bk" => FileType::Temp, "bkp" => FileType::Temp, "crdownload" => FileType::Temp, "download" => FileType::Temp, "fdmdownload"=> FileType::Temp, "part" => FileType::Temp, "swn" => FileType::Temp, "swo" => FileType::Temp, "swp" => FileType::Temp, "tmp" => FileType::Temp, /* Compiler output files */ "a" => FileType::Compiled, // Unix static library "bundle" => FileType::Compiled, // macOS application bundle "class" => FileType::Compiled, // Java class file "cma" => FileType::Compiled, // OCaml bytecode library "cmi" => FileType::Compiled, // OCaml interface "cmo" => FileType::Compiled, // OCaml bytecode object "cmx" => FileType::Compiled, // OCaml bytecode object for inlining "dll" => FileType::Compiled, // Windows dynamic link library "dylib" => FileType::Compiled, // Mach-O dynamic library "elc" => FileType::Compiled, // Emacs compiled lisp "ko" => FileType::Compiled, // Linux kernel module "lib" => FileType::Compiled, // Windows static library "o" => FileType::Compiled, // Compiled object file "obj" => FileType::Compiled, // Compiled object file "pyc" => FileType::Compiled, // Python compiled code "pyd" => FileType::Compiled, // Python dynamic module "pyo" => FileType::Compiled, // Python optimized code "so" => FileType::Compiled, // Unix shared library "zwc" => FileType::Compiled, // zsh compiled file /* Source code */ "applescript"=> FileType::Source, // Apple script "as" => FileType::Source, // Action script "asa" => FileType::Source, // asp "awk" => FileType::Source, // awk "c" => FileType::Source, // C/C++ "c++" => FileType::Source, // C/C++ "cabal" => FileType::Source, // Cabal "cc" => FileType::Source, // C/C++ "clj" => FileType::Source, // Clojure "cp" => FileType::Source, // C/C++ Xcode "cpp" => FileType::Source, // C/C++ "cr" => FileType::Source, // Crystal "cs" => FileType::Source, // C# "css" => FileType::Source, // css "csx" => FileType::Source, // C# "cu" => FileType::Source, // CUDA "cxx" => FileType::Source, // C/C++ "cypher" => FileType::Source, // Cypher (query language) "d" => FileType::Source, // D "dart" => FileType::Source, // Dart "di" => FileType::Source, // D "dpr" => FileType::Source, // Delphi Pascal "el" => FileType::Source, // Lisp "elm" => FileType::Source, // Elm "erl" => FileType::Source, // Erlang "ex" => FileType::Source, // Elixir "exs" => FileType::Source, // Elixir "fs" => FileType::Source, // F# "fsh" => FileType::Source, // Fragment shader "fsi" => FileType::Source, // F# "fsx" => FileType::Source, // F# "go" => FileType::Source, // Go "gradle" => FileType::Source, // Gradle "groovy" => FileType::Source, // Groovy "gvy" => FileType::Source, // Groovy "h" => FileType::Source, // C/C++ header "h++" => FileType::Source, // C/C++ header "hpp" => FileType::Source, // C/C++ header "hs" => FileType::Source, // Haskell "htc" => FileType::Source, // JavaScript "hxx" => FileType::Source, // C/C++ header "inc" => FileType::Source, "inl" => FileType::Source, // C/C++ Microsoft "ipynb" => FileType::Source, // Jupyter Notebook "java" => FileType::Source, // Java "jl" => FileType::Source, // Julia "js" => FileType::Source, // JavaScript "jsx" => FileType::Source, // React "kt" => FileType::Source, // Kotlin "kts" => FileType::Source, // Kotlin "kusto" => FileType::Source, // Kusto (query language) "less" => FileType::Source, // less "lhs" => FileType::Source, // Haskell "lisp" => FileType::Source, // Lisp "ltx" => FileType::Source, // LaTeX "lua" => FileType::Source, // Lua "m" => FileType::Source, // Matlab "malloy" => FileType::Source, // Malloy (query language) "matlab" => FileType::Source, // Matlab "ml" => FileType::Source, // OCaml "mli" => FileType::Source, // OCaml "mn" => FileType::Source, // Matlab "nb" => FileType::Source, // Mathematica "p" => FileType::Source, // Pascal "pas" => FileType::Source, // Pascal "php" => FileType::Source, // PHP "pl" => FileType::Source, // Perl "pm" => FileType::Source, // Perl "pod" => FileType::Source, // Perl "pp" => FileType::Source, // Puppet "prql" => FileType::Source, // PRQL "ps1" => FileType::Source, // PowerShell "psd1" => FileType::Source, // PowerShell "psm1" => FileType::Source, // PowerShell "purs" => FileType::Source, // PureScript "py" => FileType::Source, // Python "r" => FileType::Source, // R "rb" => FileType::Source, // Ruby "rs" => FileType::Source, // Rust "rq" => FileType::Source, // SPARQL (query language) "sass" => FileType::Source, // Sass "scala" => FileType::Source, // Scala "scss" => FileType::Source, // Sass "sql" => FileType::Source, // SQL "swift" => FileType::Source, // Swift "tcl" => FileType::Source, // TCL "tex" => FileType::Source, // LaTeX "ts" => FileType::Source, // TypeScript "v" => FileType::Source, // V "vb" => FileType::Source, // Visual Basic "vsh" => FileType::Source, // Vertex shader "zig" => FileType::Source, // Zig }; impl FileType { /// Lookup the file type based on the file's name, by the file name /// lowercase extension, or if the file could be compiled from related /// source code. pub(crate) fn get_file_type(file: &File<'_>) -> Option { // Case-insensitive readme is checked first for backwards compatibility. if file.name.to_lowercase().starts_with("readme") { return Some(Self::Build); } if let Some(file_type) = FILENAME_TYPES.get(&file.name) { return Some(file_type.clone()); } if let Some(file_type) = file.ext.as_ref().and_then(|ext| EXTENSION_TYPES.get(ext)) { return Some(file_type.clone()); } if file.name.ends_with('~') || (file.name.starts_with('#') && file.name.ends_with('#')) { return Some(Self::Temp); } if let Some(dir) = file.parent_dir { if file .get_source_files() .iter() .any(|path| dir.contains(path)) { return Some(Self::Compiled); } } None } } eza-0.18.2/src/info/mod.rs000064400000000000000000000004661046102023000133500ustar 00000000000000//! The “info” module contains routines that aren’t about probing the //! filesystem nor displaying output to the user, but are internal “business //! logic” routines that are performed on a file’s already-read metadata. //! (This counts the file name as metadata.) pub mod filetype; mod sources; eza-0.18.2/src/info/sources.rs000064400000000000000000000050271046102023000142520ustar 00000000000000use std::path::PathBuf; use crate::fs::File; impl<'a> File<'a> { /// For this file, return a vector of alternate file paths that, if any of /// them exist, mean that *this* file should be coloured as “compiled”. /// /// The point of this is to highlight compiled files such as `foo.js` when /// their source file `foo.coffee` exists in the same directory. /// For example, `foo.js` is perfectly valid without `foo.coffee`, so we /// don’t want to always blindly highlight `*.js` as compiled. /// (See also `FileType`) pub fn get_source_files(&self) -> Vec { if let Some(ext) = &self.ext { match &ext[..] { "css" => vec![self.path.with_extension("sass"), self.path.with_extension("scss"), // SASS, SCSS self.path.with_extension("styl"), self.path.with_extension("less")], // Stylus, Less "mjs" => vec![self.path.with_extension("mts")], // JavaScript ES Modules source "cjs" => vec![self.path.with_extension("cts")], // JavaScript Commonjs Modules source "js" => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")], // CoffeeScript, TypeScript "aux" | // TeX: auxiliary file "bbl" | // BibTeX bibliography file "bcf" | // biblatex control file "blg" | // BibTeX log file "fdb_latexmk" | // TeX latexmk file "fls" | // TeX -recorder file "headfootlength" | // TeX package autofancyhdr file "lof" | // TeX list of figures "log" | // TeX log file "lot" | // TeX list of tables "out" | // hyperref list of bookmarks "toc" | // TeX table of contents "xdv" => vec![self.path.with_extension("tex")], // XeTeX dvi _ => vec![], // No source files if none of the above } } else { vec![] // No source files if there’s no extension, either! } } } eza-0.18.2/src/lib.rs000064400000000000000000000003001046102023000123670ustar 00000000000000#[allow(unused)] pub mod fs; #[allow(unused)] pub mod info; #[allow(unused)] pub mod logger; #[allow(unused)] pub mod options; #[allow(unused)] pub mod output; #[allow(unused)] pub mod theme; eza-0.18.2/src/logger.rs000064400000000000000000000033131046102023000131070ustar 00000000000000//! Debug error logging. use std::ffi::OsStr; use ansiterm::{ANSIString, Colour}; /// Sets the internal logger, changing the log level based on the value of an /// environment variable. pub fn configure>(ev: Option) { let Some(ev) = ev else { return }; let env_var = ev.as_ref(); if env_var.is_empty() { return; } if env_var == "trace" { log::set_max_level(log::LevelFilter::Trace); } else { log::set_max_level(log::LevelFilter::Debug); } let result = log::set_logger(GLOBAL_LOGGER); if let Err(e) = result { eprintln!("Failed to initialise logger: {e}"); } } #[derive(Debug)] struct Logger; const GLOBAL_LOGGER: &Logger = &Logger; impl log::Log for Logger { fn enabled(&self, _: &log::Metadata<'_>) -> bool { true // no need to filter after using ‘set_max_level’. } fn log(&self, record: &log::Record<'_>) { let open = Colour::Fixed(243).paint("["); let level = level(record.level()); let close = Colour::Fixed(243).paint("]"); eprintln!( "{}{} {}{} {}", open, level, record.target(), close, record.args() ); } fn flush(&self) { // no need to flush with ‘eprintln!’. } } fn level(level: log::Level) -> ANSIString<'static> { #[rustfmt::skip] return match level { log::Level::Error => Colour::Red.paint("ERROR"), log::Level::Warn => Colour::Yellow.paint("WARN"), log::Level::Info => Colour::Cyan.paint("INFO"), log::Level::Debug => Colour::Blue.paint("DEBUG"), log::Level::Trace => Colour::Fixed(245).paint("TRACE"), }; } eza-0.18.2/src/main.rs000064400000000000000000000410411046102023000125540ustar 00000000000000#![warn(deprecated_in_future)] #![warn(future_incompatible)] #![warn(nonstandard_style)] #![warn(rust_2018_compatibility)] #![warn(rust_2018_idioms)] #![warn(trivial_casts, trivial_numeric_casts)] #![warn(unused)] #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::enum_glob_use)] #![allow(clippy::map_unwrap_or)] #![allow(clippy::match_same_arms)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::non_ascii_literal)] #![allow(clippy::option_if_let_else)] #![allow(clippy::too_many_lines)] #![allow(clippy::unused_self)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::wildcard_imports)] use std::env; use std::ffi::{OsStr, OsString}; use std::io::{self, stdin, ErrorKind, IsTerminal, Read, Write}; use std::path::{Component, PathBuf}; use std::process::exit; use ansiterm::{ANSIStrings, Style}; use crate::fs::feature::git::GitCache; use crate::fs::filter::GitIgnore; use crate::fs::{Dir, File}; use crate::options::stdin::FilesInput; use crate::options::{vars, Options, OptionsResult, Vars}; use crate::output::{details, escape, file_name, grid, grid_details, lines, Mode, View}; use crate::theme::Theme; use log::*; mod fs; mod info; mod logger; mod options; mod output; mod theme; fn main() { #[cfg(unix)] unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } logger::configure(env::var_os(vars::EZA_DEBUG).or_else(|| env::var_os(vars::EXA_DEBUG))); #[cfg(windows)] if let Err(e) = ansiterm::enable_ansi_support() { warn!("Failed to enable ANSI support: {}", e); } let stdout_istty = io::stdout().is_terminal(); let mut input = String::new(); let args: Vec<_> = env::args_os().skip(1).collect(); match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) { OptionsResult::Ok(options, mut input_paths) => { // List the current directory by default. // (This has to be done here, otherwise git_options won’t see it.) if input_paths.is_empty() { match &options.stdin { FilesInput::Args => { input_paths = vec![OsStr::new(".")]; } FilesInput::Stdin(separator) => { stdin() .read_to_string(&mut input) .expect("Failed to read from stdin"); input_paths.extend( input .split(&separator.clone().into_string().unwrap_or("\n".to_string())) .map(std::ffi::OsStr::new) .filter(|s| !s.is_empty()) .collect::>(), ); } } } let git = git_options(&options, &input_paths); let writer = io::stdout(); let git_repos = git_repos(&options, &input_paths); let console_width = options.view.width.actual_terminal_width(); let theme = options.theme.to_theme(stdout_istty); let exa = Exa { options, writer, input_paths, theme, console_width, git, git_repos, }; info!("matching on exa.run"); match exa.run() { Ok(exit_status) => { trace!("exa.run: exit Ok(exit_status)"); exit(exit_status); } Err(e) if e.kind() == ErrorKind::BrokenPipe => { warn!("Broken pipe error: {e}"); exit(exits::SUCCESS); } Err(e) => { eprintln!("{e}"); trace!("exa.run: exit RUNTIME_ERROR"); exit(exits::RUNTIME_ERROR); } } } OptionsResult::Help(help_text) => { print!("{help_text}"); } OptionsResult::Version(version_str) => { print!("{version_str}"); } OptionsResult::InvalidOptions(error) => { eprintln!("eza: {error}"); if let Some(s) = error.suggestion() { eprintln!("{s}"); } exit(exits::OPTIONS_ERROR); } } } /// The main program wrapper. pub struct Exa<'args> { /// List of command-line options, having been successfully parsed. pub options: Options, /// The output handle that we write to. pub writer: io::Stdout, /// List of the free command-line arguments that should correspond to file /// names (anything that isn’t an option). pub input_paths: Vec<&'args OsStr>, /// The theme that has been configured from the command-line options and /// environment variables. If colours are disabled, this is a theme with /// every style set to the default. pub theme: Theme, /// The detected width of the console. This is used to determine which /// view to use. pub console_width: Option, /// A global Git cache, if the option was passed in. /// This has to last the lifetime of the program, because the user might /// want to list several directories in the same repository. pub git: Option, pub git_repos: bool, } /// The “real” environment variables type. /// Instead of just calling `var_os` from within the options module, /// the method of looking up environment variables has to be passed in. struct LiveVars; impl Vars for LiveVars { fn get(&self, name: &'static str) -> Option { env::var_os(name) } } /// Create a Git cache populated with the arguments that are going to be /// listed before they’re actually listed, if the options demand it. fn git_options(options: &Options, args: &[&OsStr]) -> Option { if options.should_scan_for_git() { Some(args.iter().map(PathBuf::from).collect()) } else { None } } #[cfg(not(feature = "git"))] fn git_repos(_options: &Options, _args: &[&OsStr]) -> bool { return false; } #[cfg(feature = "git")] fn get_files_in_dir(paths: &mut Vec, path: PathBuf) { let temp_paths = if path.is_dir() { match path.read_dir() { Err(_) => { vec![path] } Ok(d) => d .filter_map(|entry| entry.ok().map(|e| e.path())) .collect::>(), } } else { vec![path] }; paths.extend(temp_paths); } #[cfg(feature = "git")] fn git_repos(options: &Options, args: &[&OsStr]) -> bool { let option_enabled = match options.view.mode { Mode::Details(details::Options { table: Some(ref table), .. }) | Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.columns.subdir_git_repos || table.columns.subdir_git_repos_no_stat, _ => false, }; if option_enabled { let paths: Vec = args.iter().map(PathBuf::from).collect::>(); let mut files: Vec = Vec::new(); for path in paths { get_files_in_dir(&mut files, path); } let repos: Vec = files .iter() .map(git2::Repository::open) .map(|repo| repo.is_ok()) .collect(); repos.contains(&true) } else { false } } impl<'args> Exa<'args> { /// # Errors /// /// Will return `Err` if printing to stderr fails. pub fn run(mut self) -> io::Result { debug!("Running with options: {:#?}", self.options); let mut files = Vec::new(); let mut dirs = Vec::new(); let mut exit_status = 0; for file_path in &self.input_paths { match File::from_args( PathBuf::from(file_path), None, None, self.options.view.deref_links, self.options.view.total_size, ) { Err(e) => { exit_status = 2; writeln!(io::stderr(), "{file_path:?}: {e}")?; } Ok(f) => { if f.points_to_directory() && !self.options.dir_action.treat_dirs_as_files() { trace!("matching on to_dir"); match f.to_dir() { Ok(d) => dirs.push(d), Err(e) if e.kind() == ErrorKind::PermissionDenied => { eprintln!("{file_path:?}: {e}"); exit(exits::PERMISSION_DENIED); } Err(e) => writeln!(io::stderr(), "{file_path:?}: {e}")?, } } else { files.push(f); } } } } // We want to print a directory’s name before we list it, *except* in // the case where it’s the only directory, *except* if there are any // files to print as well. (It’s a double negative) let no_files = files.is_empty(); let is_only_dir = dirs.len() == 1 && no_files; self.options.filter.filter_argument_files(&mut files); self.print_files(None, files)?; self.print_dirs(dirs, no_files, is_only_dir, exit_status) } fn print_dirs( &mut self, dir_files: Vec, mut first: bool, is_only_dir: bool, exit_status: i32, ) -> io::Result { let View { file_style: file_name::Options { quote_style, .. }, .. } = self.options.view; for dir in dir_files { // Put a gap between directories, or between the list of files and // the first directory. if first { first = false; } else { writeln!(&mut self.writer)?; } if !is_only_dir { let mut bits = Vec::new(); escape( dir.path.display().to_string(), &mut bits, Style::default(), Style::default(), quote_style, ); writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?; } let mut children = Vec::new(); let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; for file in dir.files( self.options.filter.dot_filter, self.git.as_ref(), git_ignore, self.options.view.deref_links, self.options.view.total_size, ) { match file { Ok(file) => children.push(file), Err((path, e)) => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?, } } self.options.filter.filter_child_files(&mut children); self.options.filter.sort_files(&mut children); if let Some(recurse_opts) = self.options.dir_action.recurse_options() { let depth = dir .path .components() .filter(|&c| c != Component::CurDir) .count() + 1; if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) { let mut child_dirs = Vec::new(); for child_dir in children .iter() .filter(|f| f.is_directory() && !f.is_all_all) { match child_dir.to_dir() { Ok(d) => child_dirs.push(d), Err(e) => { writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)?; } } } self.print_files(Some(&dir), children)?; match self.print_dirs(child_dirs, false, false, exit_status) { Ok(_) => (), Err(e) => return Err(e), } continue; } } self.print_files(Some(&dir), children)?; } Ok(exit_status) } /// Prints the list of files using whichever view is selected. fn print_files(&mut self, dir: Option<&Dir>, files: Vec>) -> io::Result<()> { if files.is_empty() { return Ok(()); } let theme = &self.theme; let View { ref mode, ref file_style, .. } = self.options.view; match (mode, self.console_width) { (Mode::Grid(ref opts), Some(console_width)) => { let filter = &self.options.filter; let r = grid::Render { files, theme, file_style, opts, console_width, filter, }; r.render(&mut self.writer) } (Mode::Grid(_), None) | (Mode::Lines, _) => { let filter = &self.options.filter; let r = lines::Render { files, theme, file_style, filter, }; r.render(&mut self.writer) } (Mode::Details(ref opts), _) => { let filter = &self.options.filter; let recurse = self.options.dir_action.recurse_options(); let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); let git_repos = self.git_repos; let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git, git_repos, }; r.render(&mut self.writer) } (Mode::GridDetails(ref opts), Some(console_width)) => { let grid = &opts.grid; let details = &opts.details; let row_threshold = opts.row_threshold; let filter = &self.options.filter; let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); let git_repos = self.git_repos; let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width, git_repos, }; r.render(&mut self.writer) } (Mode::GridDetails(ref opts), None) => { let opts = &opts.to_details_options(); let filter = &self.options.filter; let recurse = self.options.dir_action.recurse_options(); let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); let git_repos = self.git_repos; let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git, git_repos, }; r.render(&mut self.writer) } } } } mod exits { /// Exit code for when exa runs OK. pub const SUCCESS: i32 = 0; /// Exit code for when there was at least one I/O error during execution. pub const RUNTIME_ERROR: i32 = 1; /// Exit code for when the command-line options are invalid. pub const OPTIONS_ERROR: i32 = 3; /// Exit code for missing file permissions pub const PERMISSION_DENIED: i32 = 13; } eza-0.18.2/src/options/dir_action.rs000064400000000000000000000150471046102023000154450ustar 00000000000000//! Parsing the options for `DirAction`. use crate::options::parser::MatchedFlags; use crate::options::{flags, NumberSource, OptionsError}; use crate::fs::dir_action::{DirAction, RecurseOptions}; impl DirAction { /// Determine which action to perform when trying to list a directory. /// There are three possible actions, and they overlap somewhat: the /// `--tree` flag is another form of recursion, so those two are allowed /// to both be present, but the `--list-dirs` flag is used separately. pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result { let recurse = matches.has(&flags::RECURSE)?; let as_file = matches.has(&flags::LIST_DIRS)?; let tree = matches.has(&flags::TREE)?; if matches.is_strict() { // Early check for --level when it wouldn’t do anything if !recurse && !tree && matches.count(&flags::LEVEL) > 0 { return Err(OptionsError::Useless2( &flags::LEVEL, &flags::RECURSE, &flags::TREE, )); } else if recurse && as_file { return Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS)); } else if tree && as_file { return Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS)); } } if tree && can_tree { // Tree is only appropriate in details mode, so this has to // examine the View, which should have already been deduced by now Ok(Self::Recurse(RecurseOptions::deduce(matches, true)?)) } else if recurse { Ok(Self::Recurse(RecurseOptions::deduce(matches, false)?)) } else if as_file { Ok(Self::AsFile) } else { Ok(Self::List) } } } impl RecurseOptions { /// Determine which files should be recursed into, based on the `--level` /// flag’s value, and whether the `--tree` flag was passed, which was /// determined earlier. The maximum level should be a number, and this /// will fail with an `Err` if it isn’t. pub fn deduce(matches: &MatchedFlags<'_>, tree: bool) -> Result { if let Some(level) = matches.get(&flags::LEVEL)? { let arg_str = level.to_string_lossy(); match arg_str.parse() { Ok(l) => Ok(Self { tree, max_depth: Some(l), }), Err(e) => { let source = NumberSource::Arg(&flags::LEVEL); Err(OptionsError::FailedParse(arg_str.to_string(), source, e)) } } } else { Ok(Self { tree, max_depth: None, }) } } } #[cfg(test)] mod test { use super::*; use crate::options::flags; use crate::options::parser::Flag; macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { use crate::options::parser::Arg; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; static TEST_ARGS: &[&Arg] = &[ &flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL, ]; for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf, true) }) { assert_eq!(result, $result); } } }; } // Default behaviour test!(empty: DirAction <- []; Both => Ok(DirAction::List)); // Listing files as directories test!(dirs_short: DirAction <- ["-d"]; Both => Ok(DirAction::AsFile)); test!(dirs_long: DirAction <- ["--list-dirs"]; Both => Ok(DirAction::AsFile)); // Recursing use self::DirAction::Recurse; test!(rec_short: DirAction <- ["-R"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None }))); test!(rec_long: DirAction <- ["--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None }))); test!(rec_lim_short: DirAction <- ["-RL4"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) }))); test!(rec_lim_short_2: DirAction <- ["-RL=5"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) }))); test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) }))); test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) }))); test!(tree: DirAction <- ["--tree"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None }))); test!(rec_tree: DirAction <- ["--recurse", "--tree"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None }))); test!(rec_short_tree: DirAction <- ["-TR"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None }))); // Overriding --list-dirs, --recurse, and --tree test!(dirs_recurse: DirAction <- ["--list-dirs", "--recurse"]; Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: None }))); test!(dirs_tree: DirAction <- ["--list-dirs", "--tree"]; Last => Ok(Recurse(RecurseOptions { tree: true, max_depth: None }))); test!(just_level: DirAction <- ["--level=4"]; Last => Ok(DirAction::List)); test!(dirs_recurse_2: DirAction <- ["--list-dirs", "--recurse"]; Complain => Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS))); test!(dirs_tree_2: DirAction <- ["--list-dirs", "--tree"]; Complain => Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS))); test!(just_level_2: DirAction <- ["--level=4"]; Complain => Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))); // Overriding levels test!(overriding_1: DirAction <- ["-RL=6", "-L=7"]; Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) }))); test!(overriding_2: DirAction <- ["-RL=6", "-L=7"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'L'), Flag::Short(b'L')))); } eza-0.18.2/src/options/error.rs000064400000000000000000000113251046102023000144560ustar 00000000000000use std::ffi::OsString; use std::fmt; use std::num::ParseIntError; use crate::options::flags; use crate::options::parser::{Arg, Flag, ParseError}; /// Something wrong with the combination of options the user has picked. #[derive(PartialEq, Eq, Debug)] pub enum OptionsError { /// There was an error (from `getopts`) parsing the arguments. Parse(ParseError), /// The user supplied an illegal choice to an Argument. BadArgument(&'static Arg, OsString), /// The user supplied a set of options that are unsupported Unsupported(String), /// An option was given twice or more in strict mode. Duplicate(Flag, Flag), /// Two options were given that conflict with one another. Conflict(&'static Arg, &'static Arg), /// An option was given that does nothing when another one either is or /// isn’t present. Useless(&'static Arg, bool, &'static Arg), /// An option was given that does nothing when either of two other options /// are not present. Useless2(&'static Arg, &'static Arg, &'static Arg), /// A very specific edge case where --tree can’t be used with --all twice. TreeAllAll, /// A numeric option was given that failed to be parsed as a number. FailedParse(String, NumberSource, ParseIntError), /// A glob ignore was given that failed to be parsed as a pattern. FailedGlobPattern(String), } /// The source of a string that failed to be parsed as a number. #[derive(PartialEq, Eq, Debug)] pub enum NumberSource { /// It came... from a command-line argument! Arg(&'static Arg), /// It came... from the environment! Env(&'static str), } impl From for OptionsError { fn from(error: glob::PatternError) -> Self { Self::FailedGlobPattern(error.to_string()) } } impl fmt::Display for NumberSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Arg(arg) => write!(f, "option {arg}"), Self::Env(env) => write!(f, "environment variable {env}"), } } } impl fmt::Display for OptionsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use crate::options::parser::TakesValue; #[rustfmt::skip] return match self { Self::BadArgument(arg, attempt) => { if let TakesValue::Necessary(Some(values)) = arg.takes_value { write!( f, "Option {} has no {:?} setting ({})", arg, attempt, Choices(values) ) } else { write!(f, "Option {arg} has no {attempt:?} setting") } } Self::Parse(e) => write!(f, "{e}"), Self::Unsupported(e) => write!(f, "{e}"), Self::Conflict(a, b) => write!(f, "Option {a} conflicts with option {b}"), Self::Duplicate(a, b) if a == b => write!(f, "Flag {a} was given twice"), Self::Duplicate(a, b) => write!(f, "Flag {a} conflicts with flag {b}"), Self::Useless(a, false, b) => write!(f, "Option {a} is useless without option {b}"), Self::Useless(a, true, b) => write!(f, "Option {a} is useless given option {b}"), Self::Useless2(a, b1, b2) => write!(f, "Option {a} is useless without options {b1} or {b2}"), Self::TreeAllAll => write!(f, "Option --tree is useless given --all --all"), Self::FailedParse(s, n, e) => write!(f, "Value {s:?} not valid for {n}: {e}"), Self::FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {e}"), }; } } impl OptionsError { /// Try to second-guess what the user was trying to do, depending on what /// went wrong. pub fn suggestion(&self) -> Option<&'static str> { // ‘ls -lt’ and ‘ls -ltr’ are common combinations match self { Self::BadArgument(time, r) if *time == &flags::TIME && r == "r" => { Some("To sort oldest files last, try \"--sort oldest\", or just \"-sold\"") } Self::Parse(ParseError::NeedsValue { ref flag, .. }) if *flag == Flag::Short(b't') => { Some("To sort newest files last, try \"--sort newest\", or just \"-snew\"") } _ => None, } } } /// A list of legal choices for an argument-taking option. #[derive(PartialEq, Eq, Debug)] pub struct Choices(pub &'static [&'static str]); impl fmt::Display for Choices { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "choices: {}", self.0.join(", ")) } } eza-0.18.2/src/options/file_name.rs000064400000000000000000000071111046102023000152420ustar 00000000000000use crate::options::parser::MatchedFlags; use crate::options::vars::{self, Vars}; use crate::options::{flags, NumberSource, OptionsError}; use crate::output::file_name::{Classify, EmbedHyperlinks, Options, QuoteStyle, ShowIcons}; impl Options { pub fn deduce( matches: &MatchedFlags<'_>, vars: &V, is_a_tty: bool, ) -> Result { let classify = Classify::deduce(matches)?; let show_icons = ShowIcons::deduce(matches, vars)?; let quote_style = QuoteStyle::deduce(matches)?; let embed_hyperlinks = EmbedHyperlinks::deduce(matches)?; Ok(Self { classify, show_icons, quote_style, embed_hyperlinks, is_a_tty, }) } } impl Classify { fn deduce(matches: &MatchedFlags<'_>) -> Result { let mode_opt = matches.get(&flags::CLASSIFY)?; match mode_opt { Some(word) => match word.to_str() { Some("always") => Ok(Self::AddFileIndicators), Some("auto" | "automatic") => Ok(Self::AutomaticAddFileIndicators), Some("never") => Ok(Self::JustFilenames), _ => Err(OptionsError::BadArgument(&flags::CLASSIFY, word.into())), }, // No flag given, default to just filenames None => Ok(Self::JustFilenames), } } } impl ShowIcons { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { enum AlwaysOrAuto { Always, Automatic, } let force_icons = vars.get(vars::EZA_ICONS_AUTO).is_some(); let mode_opt = matches.get(&flags::ICONS)?; if !force_icons && !matches.has(&flags::ICONS)? && mode_opt.is_none() { return Ok(Self::Never); } let mode = match mode_opt { Some(word) => match word.to_str() { Some("always") => AlwaysOrAuto::Always, Some("auto" | "automatic") => AlwaysOrAuto::Automatic, Some("never") => return Ok(Self::Never), None => AlwaysOrAuto::Automatic, _ => return Err(OptionsError::BadArgument(&flags::ICONS, word.into())), }, None => AlwaysOrAuto::Automatic, }; let width = if let Some(columns) = vars .get_with_fallback(vars::EXA_ICON_SPACING, vars::EZA_ICON_SPACING) .and_then(|s| s.into_string().ok()) { match columns.parse() { Ok(width) => width, Err(e) => { let source = NumberSource::Env( vars.source(vars::EXA_ICON_SPACING, vars::EZA_ICON_SPACING) .unwrap(), ); return Err(OptionsError::FailedParse(columns, source, e)); } } } else { 1 }; match mode { AlwaysOrAuto::Always => Ok(Self::Always(width)), AlwaysOrAuto::Automatic => Ok(Self::Automatic(width)), } } } impl QuoteStyle { pub fn deduce(matches: &MatchedFlags<'_>) -> Result { if matches.has(&flags::NO_QUOTES)? { Ok(Self::NoQuotes) } else { Ok(Self::QuoteSpaces) } } } impl EmbedHyperlinks { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flagged = matches.has(&flags::HYPERLINK)?; if flagged { Ok(Self::On) } else { Ok(Self::Off) } } } eza-0.18.2/src/options/filter.rs000064400000000000000000000344361046102023000146220ustar 00000000000000//! Parsing the options for `FileFilter`. use crate::fs::filter::{ FileFilter, FileFilterFlags, GitIgnore, IgnorePatterns, SortCase, SortField, }; use crate::fs::DotFilter; use crate::options::parser::MatchedFlags; use crate::options::{flags, OptionsError}; impl FileFilter { /// Determines which of all the file filter options to use. pub fn deduce(matches: &MatchedFlags<'_>) -> Result { use FileFilterFlags as FFF; let mut filter_flags: Vec = vec![]; for (has, flag) in &[ (matches.has(&flags::REVERSE)?, FFF::Reverse), (matches.has(&flags::ONLY_DIRS)?, FFF::OnlyDirs), (matches.has(&flags::ONLY_FILES)?, FFF::OnlyFiles), ] { if *has { filter_flags.push(flag.clone()); } } #[rustfmt::skip] return Ok(Self { list_dirs_first: matches.has(&flags::DIRS_FIRST)?, flags: filter_flags, sort_field: SortField::deduce(matches)?, dot_filter: DotFilter::deduce(matches)?, ignore_patterns: IgnorePatterns::deduce(matches)?, git_ignore: GitIgnore::deduce(matches)?, }); } } impl SortField { /// Determines which sort field to use based on the `--sort` argument. /// This argument’s value can be one of several flags, listed above. /// Returns the default sort field if none is given, or `Err` if the /// value doesn’t correspond to a sort field we know about. fn deduce(matches: &MatchedFlags<'_>) -> Result { let Some(word) = matches.get(&flags::SORT)? else { return Ok(Self::default()); }; // Get String because we can’t match an OsStr let Some(word) = word.to_str() else { return Err(OptionsError::BadArgument(&flags::SORT, word.into())); }; let field = match word { "name" | "filename" => Self::Name(SortCase::AaBbCc), "Name" | "Filename" => Self::Name(SortCase::ABCabc), ".name" | ".filename" => Self::NameMixHidden(SortCase::AaBbCc), ".Name" | ".Filename" => Self::NameMixHidden(SortCase::ABCabc), "size" | "filesize" => Self::Size, "ext" | "extension" => Self::Extension(SortCase::AaBbCc), "Ext" | "Extension" => Self::Extension(SortCase::ABCabc), // “new” sorts oldest at the top and newest at the bottom; “old” // sorts newest at the top and oldest at the bottom. I think this // is the right way round to do this: “size” puts the smallest at // the top and the largest at the bottom, doesn’t it? "date" | "time" | "mod" | "modified" | "new" | "newest" => Self::ModifiedDate, // Similarly, “age” means that files with the least age (the // newest files) get sorted at the top, and files with the most // age (the oldest) at the bottom. "age" | "old" | "oldest" => Self::ModifiedAge, "ch" | "changed" => Self::ChangedDate, "acc" | "accessed" => Self::AccessedDate, "cr" | "created" => Self::CreatedDate, #[cfg(unix)] "inode" => Self::FileInode, "type" => Self::FileType, "none" => Self::Unsorted, _ => { return Err(OptionsError::BadArgument(&flags::SORT, word.into())); } }; Ok(field) } } // I’ve gone back and forth between whether to sort case-sensitively or // insensitively by default. The default string sort in most programming // languages takes each character’s ASCII value into account, sorting // “Documents” before “apps”, but there’s usually an option to ignore // characters’ case, putting “apps” before “Documents”. // // The argument for following case is that it’s easy to forget whether an item // begins with an uppercase or lowercase letter and end up having to scan both // the uppercase and lowercase sub-lists to find the item you want. If you // happen to pick the sublist it’s not in, it looks like it’s missing, which // is worse than if you just take longer to find it. // (https://ux.stackexchange.com/a/79266) // // The argument for ignoring case is that it makes exa sort files differently // from shells. A user would expect a directory’s files to be in the same // order if they used “exa ~/directory” or “exa ~/directory/*”, but exa sorts // them in the first case, and the shell in the second case, so they wouldn’t // be exactly the same if exa does something non-conventional. // // However, exa already sorts files differently: it uses natural sorting from // the natord crate, sorting the string “2” before “10” because the number’s // smaller, because that’s usually what the user expects to happen. Users will // name their files with numbers expecting them to be treated like numbers, // rather than lists of numeric characters. // // In the same way, users will name their files with letters expecting the // order of the letters to matter, rather than each letter’s character’s ASCII // value. So exa breaks from tradition and ignores case while sorting: // “apps” first, then “Documents”. // // You can get the old behaviour back by sorting with `--sort=Name`. impl Default for SortField { fn default() -> Self { Self::Name(SortCase::AaBbCc) } } impl DotFilter { /// Determines the dot filter based on how many `--all` options were /// given: one will show dotfiles, but two will show `.` and `..` too. /// --almost-all is equivalent to --all, included for compatibility with /// `ls -A`. /// /// It also checks for the `--tree` option, because of a special case /// where `--tree --all --all` won’t work: listing the parent directory /// in tree mode would loop onto itself! /// /// `--almost-all` binds stronger than multiple `--all` as we currently do not take the order /// of arguments into account and it is the safer option (does not clash with `--tree`) pub fn deduce(matches: &MatchedFlags<'_>) -> Result { let all_count = matches.count(&flags::ALL); let has_almost_all = matches.has(&flags::ALMOST_ALL)?; match (all_count, has_almost_all) { (0, false) => Ok(Self::JustFiles), // either a single --all or at least one --almost-all is given (1, _) | (0, true) => Ok(Self::Dotfiles), // more than one --all (c, _) => { if matches.count(&flags::TREE) > 0 { Err(OptionsError::TreeAllAll) } else if matches.is_strict() && c > 2 { Err(OptionsError::Conflict(&flags::ALL, &flags::ALL)) } else { Ok(Self::DotfilesAndDots) } } } } } impl IgnorePatterns { /// Determines the set of glob patterns to use based on the /// `--ignore-glob` argument’s value. This is a list of strings /// separated by pipe (`|`) characters, given in any order. pub fn deduce(matches: &MatchedFlags<'_>) -> Result { // If there are no inputs, we return a set of patterns that doesn’t // match anything, rather than, say, `None`. let Some(inputs) = matches.get(&flags::IGNORE_GLOB)? else { return Ok(Self::empty()); }; // Awkwardly, though, a glob pattern can be invalid, and we need to // deal with invalid patterns somehow. let (patterns, mut errors) = Self::parse_from_iter(inputs.to_string_lossy().split('|')); // It can actually return more than one glob error, // but we only use one. (TODO) match errors.pop() { Some(e) => Err(e.into()), None => Ok(patterns), } } } impl GitIgnore { pub fn deduce(matches: &MatchedFlags<'_>) -> Result { if matches.has(&flags::GIT_IGNORE)? { Ok(Self::CheckAndIgnore) } else { Ok(Self::Off) } } } #[cfg(test)] mod test { use super::*; use crate::options::flags; use crate::options::parser::Flag; use std::ffi::OsString; macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { use crate::options::parser::Arg; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::ALMOST_ALL, &flags::TREE, &flags::IGNORE_GLOB, &flags::GIT_IGNORE, ]; for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { assert_eq!(result, $result); } } }; } mod sort_fields { use super::*; // Default behaviour test!(empty: SortField <- []; Both => Ok(SortField::default())); // Sort field arguments test!(one_arg: SortField <- ["--sort=mod"]; Both => Ok(SortField::ModifiedDate)); test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size)); test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate)); test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::AaBbCc))); test!(uppercase: SortField <- ["--sort", "Name"]; Both => Ok(SortField::Name(SortCase::ABCabc))); test!(old: SortField <- ["--sort", "new"]; Both => Ok(SortField::ModifiedDate)); test!(oldest: SortField <- ["--sort=newest"]; Both => Ok(SortField::ModifiedDate)); test!(new: SortField <- ["--sort", "old"]; Both => Ok(SortField::ModifiedAge)); test!(newest: SortField <- ["--sort=oldest"]; Both => Ok(SortField::ModifiedAge)); test!(age: SortField <- ["-sage"]; Both => Ok(SortField::ModifiedAge)); test!(mix_hidden_lowercase: SortField <- ["--sort", ".name"]; Both => Ok(SortField::NameMixHidden(SortCase::AaBbCc))); test!(mix_hidden_uppercase: SortField <- ["--sort", ".Name"]; Both => Ok(SortField::NameMixHidden(SortCase::ABCabc))); // Errors test!(error: SortField <- ["--sort=colour"]; Both => Err(OptionsError::BadArgument(&flags::SORT, OsString::from("colour")))); // Overriding test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Last => Ok(SortField::ModifiedDate)); test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; Last => Ok(SortField::Extension(SortCase::ABCabc))); test!(overridden_3: SortField <- ["--sort=cr", "--sort", "mod"]; Complain => Err(OptionsError::Duplicate(Flag::Long("sort"), Flag::Long("sort")))); test!(overridden_4: SortField <- ["--sort", "none", "--sort=Extension"]; Complain => Err(OptionsError::Duplicate(Flag::Long("sort"), Flag::Long("sort")))); } mod dot_filters { use super::*; // Default behaviour test!(empty: DotFilter <- []; Both => Ok(DotFilter::JustFiles)); // --all test!(all: DotFilter <- ["--all"]; Both => Ok(DotFilter::Dotfiles)); test!(all_all: DotFilter <- ["--all", "-a"]; Both => Ok(DotFilter::DotfilesAndDots)); test!(all_all_2: DotFilter <- ["-aa"]; Both => Ok(DotFilter::DotfilesAndDots)); test!(all_all_3: DotFilter <- ["-aaa"]; Last => Ok(DotFilter::DotfilesAndDots)); test!(all_all_4: DotFilter <- ["-aaa"]; Complain => Err(OptionsError::Conflict(&flags::ALL, &flags::ALL))); // --all and --tree test!(tree_a: DotFilter <- ["-Ta"]; Both => Ok(DotFilter::Dotfiles)); test!(tree_aa: DotFilter <- ["-Taa"]; Both => Err(OptionsError::TreeAllAll)); test!(tree_aaa: DotFilter <- ["-Taaa"]; Both => Err(OptionsError::TreeAllAll)); // --almost-all test!(almost_all: DotFilter <- ["--almost-all"]; Both => Ok(DotFilter::Dotfiles)); test!(almost_all_all: DotFilter <- ["-Aa"]; Both => Ok(DotFilter::Dotfiles)); test!(almost_all_all_2: DotFilter <- ["-Aaa"]; Both => Ok(DotFilter::DotfilesAndDots)); } mod ignore_patterns { use super::*; use std::iter::FromIterator; fn pat(string: &'static str) -> glob::Pattern { glob::Pattern::new(string).unwrap() } // Various numbers of globs test!(none: IgnorePatterns <- []; Both => Ok(IgnorePatterns::empty())); test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ]))); test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ]))); test!(loads: IgnorePatterns <- ["-I*|?|.|*"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ]))); // Overriding test!(overridden: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.mp3") ]))); test!(overridden_2: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.MP3") ]))); test!(overridden_3: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I')))); test!(overridden_4: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I')))); } mod git_ignores { use super::*; test!(off: GitIgnore <- []; Both => Ok(GitIgnore::Off)); test!(on: GitIgnore <- ["--git-ignore"]; Both => Ok(GitIgnore::CheckAndIgnore)); } } eza-0.18.2/src/options/flags.rs000064400000000000000000000210161046102023000144170ustar 00000000000000use crate::options::parser::{Arg, Args, TakesValue, Values}; // exa options pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version", takes_value: TakesValue::Forbidden }; pub static HELP: Arg = Arg { short: Some(b'?'), long: "help", takes_value: TakesValue::Forbidden }; // display options pub static ONE_LINE: Arg = Arg { short: Some(b'1'), long: "oneline", takes_value: TakesValue::Forbidden }; pub static LONG: Arg = Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden }; pub static GRID: Arg = Arg { short: Some(b'G'), long: "grid", takes_value: TakesValue::Forbidden }; pub static ACROSS: Arg = Arg { short: Some(b'x'), long: "across", takes_value: TakesValue::Forbidden }; pub static RECURSE: Arg = Arg { short: Some(b'R'), long: "recurse", takes_value: TakesValue::Forbidden }; pub static TREE: Arg = Arg { short: Some(b'T'), long: "tree", takes_value: TakesValue::Forbidden }; pub static CLASSIFY: Arg = Arg { short: Some(b'F'), long: "classify", takes_value: TakesValue::Optional(Some(WHEN), "auto") }; pub static DEREF_LINKS: Arg = Arg { short: Some(b'X'), long: "dereference", takes_value: TakesValue::Forbidden }; pub static WIDTH: Arg = Arg { short: Some(b'w'), long: "width", takes_value: TakesValue::Necessary(None) }; pub static NO_QUOTES: Arg = Arg { short: None, long: "no-quotes", takes_value: TakesValue::Forbidden }; pub static COLOR: Arg = Arg { short: None, long: "color", takes_value: TakesValue::Optional(Some(WHEN), "auto") }; pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Optional(Some(WHEN), "auto") }; const WHEN: &[&str] = &["always", "auto", "never"]; pub static COLOR_SCALE: Arg = Arg { short: None, long: "color-scale", takes_value: TakesValue::Optional(Some(SCALES), "all") }; pub static COLOUR_SCALE: Arg = Arg { short: None, long: "colour-scale", takes_value: TakesValue::Optional(Some(SCALES), "all") }; pub static COLOR_SCALE_MODE: Arg = Arg { short: None, long: "color-scale-mode", takes_value: TakesValue::Necessary(Some(COLOR_SCALE_MODES))}; pub static COLOUR_SCALE_MODE: Arg = Arg { short: None, long: "colour-scale-mode", takes_value: TakesValue::Necessary(Some(COLOR_SCALE_MODES))}; const SCALES: Values = &["all", "size", "age"]; const COLOR_SCALE_MODES: Values = &["fixed", "gradient"]; // filtering and sorting options pub static ALL: Arg = Arg { short: Some(b'a'), long: "all", takes_value: TakesValue::Forbidden }; pub static ALMOST_ALL: Arg = Arg { short: Some(b'A'), long: "almost-all", takes_value: TakesValue::Forbidden }; pub static LIST_DIRS: Arg = Arg { short: Some(b'd'), long: "list-dirs", takes_value: TakesValue::Forbidden }; pub static LEVEL: Arg = Arg { short: Some(b'L'), long: "level", takes_value: TakesValue::Necessary(None) }; pub static REVERSE: Arg = Arg { short: Some(b'r'), long: "reverse", takes_value: TakesValue::Forbidden }; pub static SORT: Arg = Arg { short: Some(b's'), long: "sort", takes_value: TakesValue::Necessary(Some(SORTS)) }; pub static IGNORE_GLOB: Arg = Arg { short: Some(b'I'), long: "ignore-glob", takes_value: TakesValue::Necessary(None) }; pub static GIT_IGNORE: Arg = Arg { short: None, long: "git-ignore", takes_value: TakesValue::Forbidden }; pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden }; pub static ONLY_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden }; pub static ONLY_FILES: Arg = Arg { short: Some(b'f'), long: "only-files", takes_value: TakesValue::Forbidden }; const SORTS: Values = &[ "name", "Name", "size", "extension", "Extension", "modified", "changed", "accessed", "created", "inode", "type", "none" ]; // display options pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_value: TakesValue::Forbidden }; pub static BYTES: Arg = Arg { short: Some(b'B'), long: "bytes", takes_value: TakesValue::Forbidden }; pub static GROUP: Arg = Arg { short: Some(b'g'), long: "group", takes_value: TakesValue::Forbidden }; pub static NUMERIC: Arg = Arg { short: Some(b'n'), long: "numeric", takes_value: TakesValue::Forbidden }; pub static HEADER: Arg = Arg { short: Some(b'h'), long: "header", takes_value: TakesValue::Forbidden }; pub static ICONS: Arg = Arg { short: None, long: "icons", takes_value: TakesValue::Optional(Some(WHEN), "auto")}; pub static INODE: Arg = Arg { short: Some(b'i'), long: "inode", takes_value: TakesValue::Forbidden }; pub static LINKS: Arg = Arg { short: Some(b'H'), long: "links", takes_value: TakesValue::Forbidden }; pub static MODIFIED: Arg = Arg { short: Some(b'm'), long: "modified", takes_value: TakesValue::Forbidden }; pub static CHANGED: Arg = Arg { short: None, long: "changed", takes_value: TakesValue::Forbidden }; pub static BLOCKSIZE: Arg = Arg { short: Some(b'S'), long: "blocksize", takes_value: TakesValue::Forbidden }; pub static TOTAL_SIZE: Arg = Arg { short: None, long: "total-size", takes_value: TakesValue::Forbidden }; pub static TIME: Arg = Arg { short: Some(b't'), long: "time", takes_value: TakesValue::Necessary(Some(TIMES)) }; pub static ACCESSED: Arg = Arg { short: Some(b'u'), long: "accessed", takes_value: TakesValue::Forbidden }; pub static CREATED: Arg = Arg { short: Some(b'U'), long: "created", takes_value: TakesValue::Forbidden }; pub static TIME_STYLE: Arg = Arg { short: None, long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) }; pub static HYPERLINK: Arg = Arg { short: None, long: "hyperlink", takes_value: TakesValue::Forbidden }; pub static MOUNTS: Arg = Arg { short: Some(b'M'), long: "mounts", takes_value: TakesValue::Forbidden }; pub static SMART_GROUP: Arg = Arg { short: None, long: "smart-group", takes_value: TakesValue::Forbidden }; const TIMES: Values = &["modified", "changed", "accessed", "created"]; const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"]; // suppressing columns pub static NO_PERMISSIONS: Arg = Arg { short: None, long: "no-permissions", takes_value: TakesValue::Forbidden }; pub static NO_FILESIZE: Arg = Arg { short: None, long: "no-filesize", takes_value: TakesValue::Forbidden }; pub static NO_USER: Arg = Arg { short: None, long: "no-user", takes_value: TakesValue::Forbidden }; pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: TakesValue::Forbidden }; // optional feature options pub static GIT: Arg = Arg { short: None, long: "git", takes_value: TakesValue::Forbidden }; pub static NO_GIT: Arg = Arg { short: None, long: "no-git", takes_value: TakesValue::Forbidden }; pub static GIT_REPOS: Arg = Arg { short: None, long: "git-repos", takes_value: TakesValue::Forbidden }; pub static GIT_REPOS_NO_STAT: Arg = Arg { short: None, long: "git-repos-no-status", takes_value: TakesValue::Forbidden }; pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended", takes_value: TakesValue::Forbidden }; pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permissions", takes_value: TakesValue::Forbidden }; pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden }; pub static STDIN: Arg = Arg { short: None, long: "stdin", takes_value: TakesValue::Forbidden }; pub static FILE_FLAGS: Arg = Arg { short: Some(b'O'), long: "flags", takes_value: TakesValue::Forbidden }; pub static ALL_ARGS: Args = Args(&[ &VERSION, &HELP, &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS, &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE, &COLOR_SCALE_MODE, &COLOUR_SCALE_MODE, &WIDTH, &NO_QUOTES, &ALL, &ALMOST_ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST, &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES, &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED, &BLOCKSIZE, &TOTAL_SIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS, &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP, &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT, &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS ]); eza-0.18.2/src/options/help.rs000064400000000000000000000157471046102023000142710ustar 00000000000000use std::fmt; use crate::fs::feature::xattr; use crate::options::flags; use crate::options::parser::MatchedFlags; static USAGE_PART1: &str = "Usage: eza [options] [files...] META OPTIONS --help show list of command-line options -v, --version show version of eza DISPLAY OPTIONS -1, --oneline display one entry per line -l, --long display extended file metadata as a table -G, --grid display entries as a grid (default) -x, --across sort the grid across, rather than downwards -R, --recurse recurse into directories -T, --tree recurse into directories as a tree -X, --dereference dereference symbolic links when displaying information -F, --classify=WHEN display type indicator by file names (always, auto, never) --colo[u]r=WHEN when to use terminal colours (always, auto, never) --colo[u]r-scale highlight levels of 'field' distinctly(all, age, size) --colo[u]r-scale-mode use gradient or fixed colors in --color-scale (fixed, gradient) --icons=WHEN when to display icons (always, auto, never) --no-quotes don't quote file names with spaces --hyperlink display entries as hyperlinks -w, --width COLS set screen width in columns FILTERING AND SORTING OPTIONS -a, --all show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories -A, --almost-all equivalent to --all; included for compatibility with `ls -A` -d, --list-dirs list directories as files; don't list their contents -L, --level DEPTH limit the depth of recursion -r, --reverse reverse the sort order -s, --sort SORT_FIELD which field to sort by --group-directories-first list directories before other files -D, --only-dirs list only directories -f, --only-files list only files -I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore"; static GIT_FILTER_HELP: &str = " \ --git-ignore ignore files mentioned in '.gitignore'"; static USAGE_PART2: &str = " \ Valid sort fields: name, Name, extension, Extension, size, type, modified, accessed, created, inode, and none. date, time, old, and new all refer to modified. LONG VIEW OPTIONS -b, --binary list file sizes with binary prefixes -B, --bytes list file sizes in bytes, without any prefixes -g, --group list each file's group --smart-group only show group if it has a different name from owner -h, --header add a header row to each column -H, --links list each file's number of hard links -i, --inode list each file's inode number -m, --modified use the modified timestamp field -M, --mounts show mount details (Linux and Mac only) -n, --numeric list numeric user and group IDs -O, --flags list file flags (Mac, BSD, and Windows only) -S, --blocksize show size of allocated file system blocks -t, --time FIELD which timestamp field to list (modified, accessed, created) -u, --accessed use the accessed timestamp field -U, --created use the created timestamp field --changed use the changed timestamp field --time-style how to format timestamps (default, iso, long-iso, full-iso, relative, or a custom style '+' like '+%Y-%m-%d %H:%M') --total-size show the size of a directory as the size of all files and directories inside (unix only) --no-permissions suppress the permissions field -o, --octal-permissions list each file's permission in octal format --no-filesize suppress the filesize field --no-user suppress the user field --no-time suppress the time field --stdin read file names from stdin, one per line or other separator specified in environment"; static GIT_VIEW_HELP: &str = " \ --git list each file's Git status, if tracked or ignored --no-git suppress Git status (always overrides --git, --git-repos, --git-repos-no-status) --git-repos list root of git-tree status"; static EXTENDED_HELP: &str = " \ -@, --extended list each file's extended attributes and sizes"; static SECATTR_HELP: &str = " \ -Z, --context list each file's security context"; /// All the information needed to display the help text, which depends /// on which features are enabled and whether the user only wants to /// see one section’s help. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct HelpString; impl HelpString { /// Determines how to show help, if at all, based on the user’s /// command-line arguments. This one works backwards from the other /// ‘deduce’ functions, returning Err if help needs to be shown. /// /// We don’t do any strict-mode error checking here: it’s OK to give /// the --help or --long flags more than once. Actually checking for /// errors when the user wants help is kind of petty! pub fn deduce(matches: &MatchedFlags<'_>) -> Option { if matches.count(&flags::HELP) > 0 { Some(Self) } else { None } } } impl fmt::Display for HelpString { /// Format this help options into an actual string of help /// text to be displayed to the user. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{USAGE_PART1}")?; if cfg!(feature = "git") { write!(f, "\n{GIT_FILTER_HELP}")?; } write!(f, "\n{USAGE_PART2}")?; if cfg!(feature = "git") { write!(f, "\n{GIT_VIEW_HELP}")?; } if xattr::ENABLED { write!(f, "\n{EXTENDED_HELP}")?; write!(f, "\n{SECATTR_HELP}")?; } writeln!(f) } } #[cfg(test)] mod test { use crate::options::{Options, OptionsResult}; use std::ffi::OsStr; #[test] fn help() { let args = vec![OsStr::new("--help")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Help(_))); } #[test] fn help_with_file() { let args = vec![OsStr::new("--help"), OsStr::new("me")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Help(_))); } #[test] fn unhelpful() { let args = vec![]; let opts = Options::parse(args, &None); assert!(!matches!(opts, OptionsResult::Help(_))); // no help when --help isn’t passed } } eza-0.18.2/src/options/mod.rs000064400000000000000000000247371046102023000141170ustar 00000000000000//! Parsing command-line strings into exa options. //! //! This module imports exa’s configuration types, such as `View` (the details //! of displaying multiple files) and `DirAction` (what to do when encountering //! a directory), and implements `deduce` methods on them so they can be //! configured using command-line options. //! //! //! ## Useless and overridden options //! //! Let’s say exa was invoked with just one argument: `exa --inode`. The //! `--inode` option is used in the details view, where it adds the inode //! column to the output. But because the details view is *only* activated with //! the `--long` argument, adding `--inode` without it would not have any //! effect. //! //! For a long time, exa’s philosophy was that the user should be warned //! whenever they could be mistaken like this. If you tell exa to display the //! inode, and it *doesn’t* display the inode, isn’t that more annoying than //! having it throw an error back at you? //! //! However, this doesn’t take into account *configuration*. Say a user wants //! to configure exa so that it lists inodes in the details view, but otherwise //! functions normally. A common way to do this for command-line programs is to //! define a shell alias that specifies the details they want to use every //! time. For the inode column, the alias would be: //! //! `alias exa="exa --inode"` //! //! Using this alias means that although the inode column will be shown in the //! details view, you’re now *only* allowed to use the details view, as any //! other view type will result in an error. Oops! //! //! Another example is when an option is specified twice, such as `exa //! --sort=Name --sort=size`. Did the user change their mind about sorting, and //! accidentally specify the option twice? //! //! Again, exa rejected this case, throwing an error back to the user instead //! of trying to guess how they want their output sorted. And again, this //! doesn’t take into account aliases being used to set defaults. A user who //! wants their files to be sorted case-insensitively may configure their shell //! with the following: //! //! `alias exa="exa --sort=Name"` //! //! Just like the earlier example, the user now can’t use any other sort order, //! because exa refuses to guess which one they meant. It’s *more* annoying to //! have to go back and edit the command than if there were no error. //! //! Fortunately, there’s a heuristic for telling which options came from an //! alias and which came from the actual command-line: aliased options are //! nearer the beginning of the options array, and command-line options are //! nearer the end. This means that after the options have been parsed, exa //! needs to traverse them *backwards* to find the last-most-specified one. //! //! For example, invoking exa with `exa --sort=size` when that alias is present //! would result in a full command-line of: //! //! `exa --sort=Name --sort=size` //! //! `--sort=size` should override `--sort=Name` because it’s closer to the end //! of the arguments array. In fact, because there’s no way to tell where the //! arguments came from — it’s just a heuristic — this will still work even //! if no aliases are being used! //! //! Finally, this isn’t just useful when options could override each other. //! Creating an alias `exal="exa --long --inode --header"` then invoking `exal //! --grid --long` shouldn’t complain about `--long` being given twice when //! it’s clear what the user wants. use std::ffi::OsStr; use crate::fs::dir_action::DirAction; use crate::fs::filter::{FileFilter, GitIgnore}; use crate::options::stdin::FilesInput; use crate::output::{details, grid_details, Mode, View}; use crate::theme::Options as ThemeOptions; mod dir_action; mod file_name; mod filter; #[rustfmt::skip] // this module becomes unreadable with rustfmt mod flags; mod theme; mod view; mod error; pub use self::error::{NumberSource, OptionsError}; mod help; use self::help::HelpString; mod parser; use self::parser::MatchedFlags; pub mod vars; pub use self::vars::Vars; pub mod stdin; mod version; use self::version::VersionString; /// These **options** represent a parsed, error-checked versions of the /// user’s command-line options. #[derive(Debug)] pub struct Options { /// The action to perform when encountering a directory rather than a /// regular file. pub dir_action: DirAction, /// How to sort and filter files before outputting them. pub filter: FileFilter, /// The user’s preference of view to use (lines, grid, details, or /// grid-details) along with the options on how to render file names. /// If the view requires the terminal to have a width, and there is no /// width, then the view will be downgraded. pub view: View, /// The options to make up the styles of the UI and file names. pub theme: ThemeOptions, /// Whether to read file names from stdin instead of the command-line pub stdin: FilesInput, } impl Options { /// Parse the given iterator of command-line strings into an Options /// struct and a list of free filenames, using the environment variables /// for extra options. #[allow(unused_results)] pub fn parse<'args, I, V>(args: I, vars: &V) -> OptionsResult<'args> where I: IntoIterator, V: Vars, { use crate::options::parser::{Matches, Strictness}; #[rustfmt::skip] let strictness = match vars.get_with_fallback(vars::EZA_STRICT, vars::EXA_STRICT) { None => Strictness::UseLastArguments, Some(ref t) if t.is_empty() => Strictness::UseLastArguments, Some(_) => Strictness::ComplainAboutRedundantArguments, }; let Matches { flags, frees } = match flags::ALL_ARGS.parse(args, strictness) { Ok(m) => m, Err(pe) => return OptionsResult::InvalidOptions(OptionsError::Parse(pe)), }; if let Some(help) = HelpString::deduce(&flags) { return OptionsResult::Help(help); } if let Some(version) = VersionString::deduce(&flags) { return OptionsResult::Version(version); } match Self::deduce(&flags, vars) { Ok(options) => OptionsResult::Ok(options, frees), Err(oe) => OptionsResult::InvalidOptions(oe), } } /// Whether the View specified in this set of options includes a Git /// status column. It’s only worth trying to discover a repository if the /// results will end up being displayed. pub fn should_scan_for_git(&self) -> bool { if self.filter.git_ignore == GitIgnore::CheckAndIgnore { return true; } match self.view.mode { Mode::Details(details::Options { table: Some(ref table), .. }) | Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.columns.git, _ => false, } } /// Determines the complete set of options based on the given command-line /// arguments, after they’ve been parsed. fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { if cfg!(not(feature = "git")) && matches .has_where_any(|f| f.matches(&flags::GIT) || f.matches(&flags::GIT_IGNORE)) .is_some() { return Err(OptionsError::Unsupported(String::from( "Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa" ))); } let view = View::deduce(matches, vars)?; let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?; let filter = FileFilter::deduce(matches)?; let theme = ThemeOptions::deduce(matches, vars)?; let stdin = FilesInput::deduce(matches, vars)?; Ok(Self { dir_action, filter, view, theme, stdin, }) } } /// The result of the `Options::parse` function. /// /// NOTE: We disallow the `large_enum_variant` lint here, because we're not /// overly concerned about variant fragmentation. We can do this because we are /// reasonably sure that the error variant will be rare, and only on faulty /// program execution and thus boxing the large variant will be a waste of /// resources, but should we come to use it more, we should reconsider. /// /// See #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum OptionsResult<'args> { /// The options were parsed successfully. Ok(Options, Vec<&'args OsStr>), /// There was an error parsing the arguments. InvalidOptions(OptionsError), /// One of the arguments was `--help`, so display help. Help(HelpString), /// One of the arguments was `--version`, so display the version number. Version(VersionString), } #[cfg(test)] pub mod test { use crate::options::parser::{Arg, MatchedFlags}; use std::ffi::OsStr; #[derive(PartialEq, Eq, Debug)] pub enum Strictnesses { Last, Complain, Both, } /// This function gets used by the other testing modules. /// It can run with one or both strictness values: if told to run with /// both, then both should resolve to the same result. /// /// It returns a vector with one or two elements in. /// These elements can then be tested with `assert_eq` or what have you. pub fn parse_for_test( inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F, ) -> Vec where F: Fn(&MatchedFlags<'_>) -> T, { use self::Strictnesses::*; use crate::options::parser::{Args, Strictness}; let bits = inputs.iter().map(OsStr::new).collect::>(); let mut result = Vec::new(); if strictnesses == Last || strictnesses == Both { let results = Args(args).parse(bits.clone(), Strictness::UseLastArguments); result.push(get(&results.unwrap().flags)); } if strictnesses == Complain || strictnesses == Both { let results = Args(args).parse(bits, Strictness::ComplainAboutRedundantArguments); result.push(get(&results.unwrap().flags)); } result } } eza-0.18.2/src/options/parser.rs000064400000000000000000001031751046102023000146260ustar 00000000000000//! A general parser for command-line options. //! //! exa uses its own hand-rolled parser for command-line options. It supports //! the following syntax: //! //! - Long options: `--inode`, `--grid` //! - Long options with values: `--sort size`, `--level=4` //! - Short options: `-i`, `-G` //! - Short options with values: `-ssize`, `-L=4` //! //! These values can be mixed and matched: `exa -lssize --grid`. If you’ve used //! other command-line programs, then hopefully it’ll work much like them. //! //! Because exa already has its own files for the help text, shell completions, //! man page, and readme, so it can get away with having the options parser do //! very little: all it really needs to do is parse a slice of strings. //! //! //! ## UTF-8 and `OsStr` //! //! The parser uses `OsStr` as its string type. This is necessary for exa to //! list files that have invalid UTF-8 in their names: by treating file paths //! as bytes with no encoding, a file can be specified on the command-line and //! be looked up without having to be encoded into a `str` first. //! //! It also avoids the overhead of checking for invalid UTF-8 when parsing //! command-line options, as all the options and their values (such as //! `--sort size`) are guaranteed to just be 8-bit ASCII. use std::ffi::{OsStr, OsString}; use std::fmt; use crate::options::error::{Choices, OptionsError}; /// A **short argument** is a single ASCII character. pub type ShortArg = u8; /// A **long argument** is a string. This can be a UTF-8 string, even though /// the arguments will all be unchecked `OsString` values, because we don’t /// actually store the user’s input after it’s been matched to a flag, we just /// store which flag it was. pub type LongArg = &'static str; /// A **list of values** that an option can have, to be displayed when the /// user enters an invalid one or skips it. /// /// This is literally just help text, and won’t be used to validate a value to /// see if it’s correct. pub type Values = &'static [&'static str]; /// A **flag** is either of the two argument types, because they have to /// be in the same array together. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Flag { Short(ShortArg), Long(LongArg), } impl Flag { pub fn matches(&self, arg: &Arg) -> bool { match self { Self::Short(short) => arg.short == Some(*short), Self::Long(long) => arg.long == *long, } } } impl fmt::Display for Flag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { Self::Short(short) => write!(f, "-{}", *short as char), Self::Long(long) => write!(f, "--{long}"), } } } /// Whether redundant arguments should be considered a problem. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Strictness { /// Throw an error when an argument doesn’t do anything, either because /// it requires another argument to be specified, or because two conflict. ComplainAboutRedundantArguments, /// Search the arguments list back-to-front, giving ones specified later /// in the list priority over earlier ones. UseLastArguments, } /// Whether a flag takes a value. This is applicable to both long and short /// arguments. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TakesValue { /// This flag has to be followed by a value. /// If there’s a fixed set of possible values, they can be printed out /// with the error text. Necessary(Option), /// This flag will throw an error if there’s a value after it. Forbidden, /// This flag may be followed by a value to override its defaults Optional(Option, &'static str), } /// An **argument** can be matched by one of the user’s input strings. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Arg { /// The short argument that matches it, if any. pub short: Option, /// The long argument that matches it. This is non-optional; all flags /// should at least have a descriptive long name. pub long: LongArg, /// Whether this flag takes a value or not. pub takes_value: TakesValue, } impl fmt::Display for Arg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "--{}", self.long)?; if let Some(short) = self.short { write!(f, " (-{})", short as char)?; } Ok(()) } } /// Literally just several args. #[derive(PartialEq, Eq, Debug)] pub struct Args(pub &'static [&'static Arg]); impl Args { /// Iterates over the given list of command-line arguments and parses /// them into a list of matched flags and free strings. pub fn parse<'args, I>( &self, inputs: I, strictness: Strictness, ) -> Result, ParseError> where I: IntoIterator, { let mut parsing = true; // The results that get built up. let mut result_flags = Vec::new(); let mut frees: Vec<&OsStr> = Vec::new(); // Iterate over the inputs with “while let” because we need to advance // the iterator manually whenever an argument that takes a value // doesn’t have one in its string so it needs the next one. let mut inputs = inputs.into_iter().peekable(); while let Some(arg) = inputs.next() { let bytes = os_str_to_bytes(arg); // Stop parsing if one of the arguments is the literal string “--”. // This allows a file named “--arg” to be specified by passing in // the pair “-- --arg”, without it getting matched as a flag that // doesn’t exist. if !parsing { frees.push(arg); } else if arg == "--" { parsing = false; } // If the string starts with *two* dashes then it’s a long argument. else if bytes.starts_with(b"--") { let long_arg_name = bytes_to_os_str(&bytes[2..]); // If there’s an equals in it, then the string before the // equals will be the flag’s name, and the string after it // will be its value. if let Some((before, after)) = split_on_equals(long_arg_name) { let arg = self.lookup_long(before)?; let flag = Flag::Long(arg.long); match arg.takes_value { TakesValue::Necessary(_) | TakesValue::Optional(_, _) => { result_flags.push((flag, Some(after))); } TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }), } } // If there’s no equals, then the entire string (apart from // the dashes) is the argument name. else { let arg = self.lookup_long(long_arg_name)?; let flag = Flag::Long(arg.long); match arg.takes_value { TakesValue::Forbidden => { result_flags.push((flag, None)); } TakesValue::Necessary(values) => { if let Some(next_arg) = inputs.next() { result_flags.push((flag, Some(next_arg))); } else { return Err(ParseError::NeedsValue { flag, values }); } } TakesValue::Optional(values, default) => match inputs.peek() { Some(next_arg) if is_optional_arg(next_arg, values) => { result_flags.push((flag, Some(inputs.next().unwrap()))); } _ => { result_flags .push((flag, Some(bytes_to_os_str(default.as_bytes())))); } }, } } } // If the string starts with *one* dash then it’s one or more // short arguments. else if bytes.starts_with(b"-") && arg != "-" { let short_arg = bytes_to_os_str(&bytes[1..]); // If there’s an equals in it, then the argument immediately // before the equals was the one that has the value, with the // others (if any) as value-less short ones. // // -x=abc => ‘x=abc’ // -abcdx=fgh => ‘a’, ‘b’, ‘c’, ‘d’, ‘x=fgh’ // -x= => error // -abcdx= => error // // There’s no way to give two values in a cluster like this: // it’s an error if any of the first set of arguments actually // takes a value. if let Some((before, after)) = split_on_equals(short_arg) { let (arg_with_value, other_args) = os_str_to_bytes(before).split_last().unwrap(); // Process the characters immediately following the dash... for byte in other_args { let arg = self.lookup_short(*byte)?; let flag = Flag::Short(*byte); match arg.takes_value { TakesValue::Forbidden => { result_flags.push((flag, None)); } TakesValue::Optional(_, default) => { result_flags .push((flag, Some(bytes_to_os_str(default.as_bytes())))); } TakesValue::Necessary(values) => { return Err(ParseError::NeedsValue { flag, values }); } } } // ...then the last one and the value after the equals. let arg = self.lookup_short(*arg_with_value)?; let flag = Flag::Short(arg.short.unwrap()); match arg.takes_value { TakesValue::Necessary(_) | TakesValue::Optional(_, _) => { result_flags.push((flag, Some(after))); } TakesValue::Forbidden => { return Err(ParseError::ForbiddenValue { flag }); } } } // If there’s no equals, then every character is parsed as // its own short argument. However, if any of the arguments // takes a value, then the *rest* of the string is used as // its value, and if there’s no rest of the string, then it // uses the next one in the iterator. // // -a => ‘a’ // -abc => ‘a’, ‘b’, ‘c’ // -abxdef => ‘a’, ‘b’, ‘x=def’ // -abx def => ‘a’, ‘b’, ‘x=def’ // -abx => error // else { for (index, byte) in bytes.iter().enumerate().skip(1) { let arg = self.lookup_short(*byte)?; let flag = Flag::Short(*byte); match arg.takes_value { TakesValue::Forbidden => { result_flags.push((flag, None)); } TakesValue::Necessary(values) => { if index < bytes.len() - 1 { let remnants = &bytes[index + 1..]; result_flags.push((flag, Some(bytes_to_os_str(remnants)))); break; } else if let Some(next_arg) = inputs.next() { result_flags.push((flag, Some(next_arg))); } else { match arg.takes_value { TakesValue::Forbidden | TakesValue::Optional(_, _) => { unreachable!() } TakesValue::Necessary(_) => { return Err(ParseError::NeedsValue { flag, values }); } } } } TakesValue::Optional(values, default) => { if index < bytes.len() - 1 { let remnants = bytes_to_os_str(&bytes[index + 1..]); if is_optional_arg(remnants, values) { result_flags.push((flag, Some(remnants))); } else { return Err(ParseError::ForbiddenValue { flag }); } break; } else if let Some(next_arg) = inputs.peek() { if is_optional_arg(next_arg, values) { result_flags.push((flag, Some(inputs.next().unwrap()))); } else { result_flags.push((flag, Some(OsStr::new(default)))); } } else { match arg.takes_value { TakesValue::Forbidden | TakesValue::Necessary(_) => { unreachable!() } TakesValue::Optional(_, default) => { result_flags.push((flag, Some(OsStr::new(default)))); } } } } } } } } // Otherwise, it’s a free string, usually a file name. else { frees.push(arg); } } Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness, }, }) } fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> { match self.0.iter().find(|arg| arg.short == Some(short)) { Some(arg) => Ok(arg), None => Err(ParseError::UnknownShortArgument { attempt: short }), } } fn lookup_long(&self, long: &OsStr) -> Result<&Arg, ParseError> { match self.0.iter().find(|arg| arg.long == long) { Some(arg) => Ok(arg), None => Err(ParseError::UnknownArgument { attempt: long.to_os_string(), }), } } } fn is_optional_arg(value: &OsStr, values: Option<&[&str]>) -> bool { match (values, value.to_str()) { (Some(values), Some(value)) => values.contains(&value), _ => false, } } /// The **matches** are the result of parsing the user’s command-line strings. #[derive(PartialEq, Eq, Debug)] pub struct Matches<'args> { /// The flags that were parsed from the user’s input. pub flags: MatchedFlags<'args>, /// All the strings that weren’t matched as arguments, as well as anything /// after the special “--” string. pub frees: Vec<&'args OsStr>, } #[derive(PartialEq, Eq, Debug)] pub struct MatchedFlags<'args> { /// The individual flags from the user’s input, in the order they were /// originally given. /// /// Long and short arguments need to be kept in the same vector because /// we usually want the one nearest the end to count, and to know this, /// we need to know where they are in relation to one another. flags: Vec<(Flag, Option<&'args OsStr>)>, /// Whether to check for duplicate or redundant arguments. strictness: Strictness, } impl<'a> MatchedFlags<'a> { /// Whether the given argument was specified. /// Returns `true` if it was, `false` if it wasn’t, and an error in /// strict mode if it was specified more than once. pub fn has(&self, arg: &'static Arg) -> Result { self.has_where(|flag| flag.matches(arg)) .map(|flag| flag.is_some()) } /// Returns the first found argument that satisfies the predicate, or /// nothing if none is found, or an error in strict mode if multiple /// argument satisfy the predicate. /// /// You’ll have to test the resulting flag to see which argument it was. pub fn has_where

(&self, predicate: P) -> Result, OptionsError> where P: Fn(&Flag) -> bool, { if self.is_strict() { let all = self .flags .iter() .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0)) .collect::>(); if all.len() < 2 { Ok(all.first().map(|t| &t.0)) } else { Err(OptionsError::Duplicate(all[0].0, all[1].0)) } } else { Ok(self.has_where_any(predicate)) } } /// Returns the first found argument that satisfies the predicate, or /// nothing if none is found, with strict mode having no effect. /// /// You’ll have to test the resulting flag to see which argument it was. pub fn has_where_any

(&self, predicate: P) -> Option<&Flag> where P: Fn(&Flag) -> bool, { self.flags .iter() .rev() .find(|tuple| tuple.1.is_none() && predicate(&tuple.0)) .map(|tuple| &tuple.0) } // This code could probably be better. // Both ‘has’ and ‘get’ immediately begin with a conditional, which makes // me think the functionality could be moved to inside Strictness. /// Returns the value of the given argument if it was specified, nothing /// if it wasn’t, and an error in strict mode if it was specified more /// than once. pub fn get(&self, arg: &'static Arg) -> Result, OptionsError> { self.get_where(|flag| flag.matches(arg)) } /// Returns the value of the argument that matches the predicate if it /// was specified, nothing if it wasn’t, and an error in strict mode if /// multiple arguments matched the predicate. /// /// It’s not possible to tell which flag the value belonged to from this. pub fn get_where

(&self, predicate: P) -> Result, OptionsError> where P: Fn(&Flag) -> bool, { if self.is_strict() { let those = self .flags .iter() .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0)) .collect::>(); if those.len() < 2 { Ok(those.first().copied().map(|t| t.1.unwrap())) } else { Err(OptionsError::Duplicate(those[0].0, those[1].0)) } } else { let found = self .flags .iter() .rev() .find(|tuple| tuple.1.is_some() && predicate(&tuple.0)) .map(|tuple| tuple.1.unwrap()); Ok(found) } } // It’s annoying that ‘has’ and ‘get’ won’t work when accidentally given // flags that do/don’t take values, but this should be caught by tests. /// Counts the number of occurrences of the given argument, even in /// strict mode. pub fn count(&self, arg: &Arg) -> usize { self.flags .iter() .filter(|tuple| tuple.0.matches(arg)) .count() } /// Checks whether strict mode is on. This is usually done from within /// ‘has’ and ‘get’, but it’s available in an emergency. pub fn is_strict(&self) -> bool { self.strictness == Strictness::ComplainAboutRedundantArguments } } /// A problem with the user’s input that meant it couldn’t be parsed into a /// coherent list of arguments. #[derive(PartialEq, Eq, Debug)] pub enum ParseError { /// A flag that has to take a value was not given one. NeedsValue { flag: Flag, values: Option }, /// A flag that can’t take a value *was* given one. ForbiddenValue { flag: Flag }, /// A short argument, either alone or in a cluster, was not /// recognised by the program. UnknownShortArgument { attempt: ShortArg }, /// A long argument was not recognised by the program. /// We don’t have a known &str version of the flag, so /// this may not be valid UTF-8. UnknownArgument { attempt: OsString }, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NeedsValue { flag, values: None } => write!(f, "Flag {flag} needs a value"), Self::NeedsValue { flag, values: Some(cs), } => write!(f, "Flag {flag} needs a value ({})", Choices(cs)), Self::ForbiddenValue { flag } => write!(f, "Flag {flag} cannot take a value"), Self::UnknownShortArgument { attempt } => { write!(f, "Unknown argument -{}", *attempt as char) } Self::UnknownArgument { attempt } => { write!(f, "Unknown argument --{}", attempt.to_string_lossy()) } } } } #[cfg(unix)] fn os_str_to_bytes(s: &OsStr) -> &[u8] { use std::os::unix::ffi::OsStrExt; return s.as_bytes(); } #[cfg(unix)] fn bytes_to_os_str(b: &[u8]) -> &OsStr { use std::os::unix::ffi::OsStrExt; return OsStr::from_bytes(b); } #[cfg(windows)] fn os_str_to_bytes(s: &OsStr) -> &[u8] { return s.to_str().unwrap().as_bytes(); } #[cfg(windows)] fn bytes_to_os_str(b: &[u8]) -> &OsStr { use std::str; return OsStr::new(str::from_utf8(b).unwrap()); } /// Splits a string on its `=` character, returning the two substrings on /// either side. Returns `None` if there’s no equals or a string is missing. fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> { if let Some(index) = os_str_to_bytes(input).iter().position(|elem| *elem == b'=') { let (before, after) = os_str_to_bytes(input).split_at(index); // The after string contains the = that we need to remove. if !before.is_empty() && after.len() >= 2 { return Some((bytes_to_os_str(before), bytes_to_os_str(&after[1..]))); } } None } #[cfg(test)] mod split_test { use super::split_on_equals; use std::ffi::{OsStr, OsString}; macro_rules! test_split { ($name:ident: $input:expr => None) => { #[test] fn $name() { assert_eq!(split_on_equals(&OsString::from($input)), None); } }; ($name:ident: $input:expr => $before:expr, $after:expr) => { #[test] fn $name() { assert_eq!( split_on_equals(&OsString::from($input)), Some((OsStr::new($before), OsStr::new($after))) ); } }; } test_split!(empty: "" => None); test_split!(letter: "a" => None); test_split!(just: "=" => None); test_split!(intro: "=bbb" => None); test_split!(denou: "aaa=" => None); test_split!(equals: "aaa=bbb" => "aaa", "bbb"); test_split!(sort: "--sort=size" => "--sort", "size"); test_split!(more: "this=that=other" => "this", "that=other"); } #[cfg(test)] mod parse_test { use super::*; macro_rules! test { ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => { #[test] fn $name() { let inputs: &[&'static str] = $inputs.as_ref(); let inputs = inputs.iter().map(OsStr::new); let frees: &[&'static str] = $frees.as_ref(); let frees = frees.iter().map(OsStr::new).collect(); let flags = <[_]>::into_vec(Box::new($flags)); let strictness = Strictness::UseLastArguments; // this isn’t even used let got = Args(TEST_ARGS).parse(inputs, strictness); let flags = MatchedFlags { flags, strictness }; let expected = Ok(Matches { frees, flags }); assert_eq!(got, expected); } }; ($name:ident: $inputs:expr => error $error:expr) => { #[test] fn $name() { use self::ParseError::*; let inputs = $inputs.iter().map(OsStr::new); let strictness = Strictness::UseLastArguments; // this isn’t even used let got = Args(TEST_ARGS).parse(inputs, strictness); assert_eq!(got, Err($error)); } }; } const SUGGESTIONS: Values = &["example"]; #[rustfmt::skip] static TEST_ARGS: &[&Arg] = &[ &Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden }, &Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden }, &Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) }, &Arg { short: Some(b't'), long: "type", takes_value: TakesValue::Necessary(Some(SUGGESTIONS))}, &Arg { short: Some(b'o'), long: "optional", takes_value: TakesValue::Optional(Some(&["all", "some", "none"]), "all")} ]; // Just filenames test!(empty: [] => frees: [], flags: []); test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []); // Dashes and double dashes test!(one_dash: ["-"] => frees: [ "-" ], flags: []); test!(two_dashes: ["--"] => frees: [], flags: []); test!(two_file: ["--", "file"] => frees: [ "file" ], flags: []); test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []); test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []); // Long args test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]); test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]); test!(long_two: ["--long", "--verbose"] => frees: [], flags: [ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ]); // Long args with values test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") }); test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count"), values: None }); test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]); test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]); // Long args with values and suggestions test!(no_arg_s: ["--type"] => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) }); test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]); test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]); // Short args test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]); test!(short_then: ["-l", "4"] => frees: [ "4" ], flags: [ (Flag::Short(b'l'), None) ]); test!(short_two: ["-lv"] => frees: [], flags: [ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ]); test!(mixed: ["-v", "--long"] => frees: [], flags: [ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ]); // Short args with values test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') }); test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c'), values: None }); test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]); test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]); test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]); test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]); test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]); // Short args with values and suggestions test!(short_none_s: ["-t"] => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) }); test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]); test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]); test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]); // Unknown args test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") }); test!(unknown_long_eq: ["--quiet=shhh"] => error UnknownArgument { attempt: OsString::from("quiet") }); test!(unknown_short: ["-q"] => error UnknownShortArgument { attempt: b'q' }); test!(unknown_short_2nd: ["-lq"] => error UnknownShortArgument { attempt: b'q' }); test!(unknown_short_eq: ["-q=shhh"] => error UnknownShortArgument { attempt: b'q' }); test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' }); // Optional args test!(optional: ["--optional"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]); test!(optional_2: ["--optional", "-l"] => frees: [], flags: [ (Flag::Long("optional"), Some(OsStr::new("all"))), (Flag::Short(b'l'), None)]); test!(optional_3: ["--optional", "path"] => frees: ["path"], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]); test!(optional_with_eq: ["--optional=none"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]); test!(optional_wo_eq: ["--optional", "none"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]); test!(short_opt: ["-o"] => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]); test!(short_opt_value: ["-onone"] => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("none")))]); test!(short_forbidden: ["-opath"] => error ForbiddenValue { flag: Flag::Short(b'o') }); test!(short_allowed: ["-o","path"] => frees: ["path"], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]); } #[cfg(test)] mod matches_test { use super::*; macro_rules! test { ($name:ident: $input:expr, has $param:expr => $result:expr) => { #[test] fn $name() { let flags = MatchedFlags { flags: $input.to_vec(), strictness: Strictness::UseLastArguments, }; assert_eq!(flags.has(&$param), Ok($result)); } }; } static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden, }; static COUNT: Arg = Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None), }; test!(short_never: [], has VERBOSE => false); test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true); test!(short_twice: [(Flag::Short(b'v'), None), (Flag::Short(b'v'), None)], has VERBOSE => true); test!(long_once: [(Flag::Long("verbose"), None)], has VERBOSE => true); test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true); test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true); #[test] fn only_count() { let everything = OsString::from("everything"); let flags = MatchedFlags { flags: vec![(Flag::Short(b'c'), Some(&*everything))], strictness: Strictness::UseLastArguments, }; assert_eq!(flags.get(&COUNT), Ok(Some(&*everything))); } #[test] fn rightmost_count() { let everything = OsString::from("everything"); let nothing = OsString::from("nothing"); let flags = MatchedFlags { flags: vec![ (Flag::Short(b'c'), Some(&*everything)), (Flag::Short(b'c'), Some(&*nothing)), ], strictness: Strictness::UseLastArguments, }; assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing))); } #[test] fn no_count() { let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments, }; assert!(!flags.has(&COUNT).unwrap()); } } eza-0.18.2/src/options/stdin.rs000064400000000000000000000015471046102023000144530ustar 00000000000000use crate::options::parser::MatchedFlags; use crate::options::vars::EZA_STDIN_SEPARATOR; use crate::options::{flags, OptionsError, Vars}; use std::ffi::OsString; use std::io; use std::io::IsTerminal; #[derive(Debug, PartialEq)] pub enum FilesInput { Stdin(OsString), Args, } impl FilesInput { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { Ok( if io::stdin().is_terminal() || !matches.has(&flags::STDIN)? { FilesInput::Args } else if matches.has(&flags::STDIN)? && !io::stdin().is_terminal() { let separator = vars .get(EZA_STDIN_SEPARATOR) .unwrap_or(OsString::from("\n")); FilesInput::Stdin(separator) } else { FilesInput::Args }, ) } } eza-0.18.2/src/options/theme.rs000064400000000000000000000167571046102023000144450ustar 00000000000000use crate::options::parser::MatchedFlags; use crate::options::{flags, vars, OptionsError, Vars}; use crate::output::color_scale::ColorScaleOptions; use crate::theme::{Definitions, Options, UseColours}; impl Options { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let use_colours = UseColours::deduce(matches, vars)?; let colour_scale = ColorScaleOptions::deduce(matches, vars)?; let definitions = if use_colours == UseColours::Never { Definitions::default() } else { Definitions::deduce(vars) }; Ok(Self { use_colours, colour_scale, definitions, }) } } impl UseColours { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let default_value = match vars.get(vars::NO_COLOR) { Some(_) => Self::Never, None => Self::Automatic, }; let Some(word) = matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? else { return Ok(default_value); }; if word == "always" { Ok(Self::Always) } else if word == "auto" || word == "automatic" { Ok(Self::Automatic) } else if word == "never" { Ok(Self::Never) } else { Err(OptionsError::BadArgument(&flags::COLOR, word.into())) } } } impl Definitions { fn deduce(vars: &V) -> Self { let ls = vars .get(vars::LS_COLORS) .map(|e| e.to_string_lossy().to_string()); let exa = vars .get_with_fallback(vars::EZA_COLORS, vars::EXA_COLORS) .map(|e| e.to_string_lossy().to_string()); Self { ls, exa } } } #[cfg(test)] mod terminal_test { use super::*; use crate::options::flags; use crate::options::parser::{Arg, Flag}; use std::ffi::OsString; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR, &flags::COLOR_SCALE, &flags::COLOUR_SCALE, ]; #[allow(unused_macro_rules)] macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { assert_eq!(result, $result); } } }; ($name:ident: $type:ident <- $inputs:expr, $env:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { let env = $env; for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf, &env) }) { assert_eq!(result, $result); } } }; ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => { #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { assert_eq!(result.unwrap_err(), $result); } } }; ($name:ident: $type:ident <- $inputs:expr, $env:expr; $stricts:expr => err $result:expr) => { #[test] fn $name() { let env = $env; for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf, &env) }) { assert_eq!(result.unwrap_err(), $result); } } }; } struct MockVars { ls: &'static str, exa: &'static str, no_color: &'static str, } impl MockVars { fn empty() -> MockVars { MockVars { ls: "", exa: "", no_color: "", } } fn with_no_color() -> MockVars { MockVars { ls: "", exa: "", no_color: "true", } } } // Test impl that just returns the value it has. impl Vars for MockVars { fn get(&self, name: &'static str) -> Option { if name == vars::LS_COLORS && !self.ls.is_empty() { Some(OsString::from(self.ls)) } else if (name == vars::EZA_COLORS || name == vars::EXA_COLORS) && !self.exa.is_empty() { Some(OsString::from(self.exa)) } else if name == vars::NO_COLOR && !self.no_color.is_empty() { Some(OsString::from(self.no_color)) } else { None } } } // Default test!(empty: UseColours <- [], MockVars::empty(); Both => Ok(UseColours::Automatic)); test!(empty_with_no_color: UseColours <- [], MockVars::with_no_color(); Both => Ok(UseColours::Never)); // --colour test!(u_always: UseColours <- ["--colour=always"], MockVars::empty(); Both => Ok(UseColours::Always)); test!(u_auto: UseColours <- ["--colour", "auto"], MockVars::empty(); Both => Ok(UseColours::Automatic)); test!(u_never: UseColours <- ["--colour=never"], MockVars::empty(); Both => Ok(UseColours::Never)); // --color test!(no_u_always: UseColours <- ["--color", "always"], MockVars::empty(); Both => Ok(UseColours::Always)); test!(no_u_auto: UseColours <- ["--color=auto"], MockVars::empty(); Both => Ok(UseColours::Automatic)); test!(no_u_never: UseColours <- ["--color", "never"], MockVars::empty(); Both => Ok(UseColours::Never)); // Errors test!(no_u_error: UseColours <- ["--color=upstream"], MockVars::empty(); Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("upstream"))); // the error is for --color test!(u_error: UseColours <- ["--colour=lovers"], MockVars::empty(); Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("lovers"))); // and so is this one! // Overriding test!(overridden_1: UseColours <- ["--colour=auto", "--colour=never"], MockVars::empty(); Last => Ok(UseColours::Never)); test!(overridden_2: UseColours <- ["--color=auto", "--colour=never"], MockVars::empty(); Last => Ok(UseColours::Never)); test!(overridden_3: UseColours <- ["--colour=auto", "--color=never"], MockVars::empty(); Last => Ok(UseColours::Never)); test!(overridden_4: UseColours <- ["--color=auto", "--color=never"], MockVars::empty(); Last => Ok(UseColours::Never)); test!(overridden_5: UseColours <- ["--colour=auto", "--colour=never"], MockVars::empty(); Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("colour"))); test!(overridden_6: UseColours <- ["--color=auto", "--colour=never"], MockVars::empty(); Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("colour"))); test!(overridden_7: UseColours <- ["--colour=auto", "--color=never"], MockVars::empty(); Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("color"))); test!(overridden_8: UseColours <- ["--color=auto", "--color=never"], MockVars::empty(); Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("color"))); } eza-0.18.2/src/options/vars.rs000064400000000000000000000136711046102023000143060ustar 00000000000000use std::ffi::OsString; // General variables /// Environment variable used to colour files, both by their filesystem type /// (symlink, socket, directory) and their file name or extension (image, /// video, archive); pub static LS_COLORS: &str = "LS_COLORS"; /// Environment variable used to override the width of the terminal, in /// characters. pub static COLUMNS: &str = "COLUMNS"; /// Environment variable used to datetime format. pub static TIME_STYLE: &str = "TIME_STYLE"; /// Environment variable used to disable colors. /// See: pub static NO_COLOR: &str = "NO_COLOR"; // exa-specific variables /// Environment variable used to colour exa’s interface when colours are /// enabled. This includes all the colours that `LS_COLORS` would recognise, /// overriding them if necessary. It can also contain exa-specific codes. pub static EXA_COLORS: &str = "EXA_COLORS"; pub static EZA_COLORS: &str = "EZA_COLORS"; /// Environment variable used to switch on strict argument checking, such as /// complaining if an argument was specified twice, or if two conflict. /// This is meant to be so you don’t accidentally introduce the wrong /// behaviour in a script, rather than for general command-line use. /// Any non-empty value will turn strict mode on. pub static EXA_STRICT: &str = "EXA_STRICT"; pub static EZA_STRICT: &str = "EZA_STRICT"; /// Environment variable used to make exa print out debugging information as /// it runs. Any non-empty value will turn debug mode on. pub static EXA_DEBUG: &str = "EXA_DEBUG"; pub static EZA_DEBUG: &str = "EZA_DEBUG"; /// Environment variable used to limit the grid-details view /// (`--grid --long`) so it’s only activated if there’s at least the given /// number of rows of output. pub static EXA_GRID_ROWS: &str = "EXA_GRID_ROWS"; pub static EZA_GRID_ROWS: &str = "EZA_GRID_ROWS"; /// Environment variable used to specify how many spaces to print between an /// icon and its file name. Different terminals display icons differently, /// with 1 space bringing them too close together or 2 spaces putting them too /// far apart, so this may be necessary depending on how they are shown. pub static EXA_ICON_SPACING: &str = "EXA_ICON_SPACING"; pub static EZA_ICON_SPACING: &str = "EZA_ICON_SPACING"; pub static EXA_OVERRIDE_GIT: &str = "EXA_OVERRIDE_GIT"; pub static EZA_OVERRIDE_GIT: &str = "EZA_OVERRIDE_GIT"; /// Enviroment variable used to set the minimum luminance in `color_scale`. It's value /// can be between -100 and 100 pub static EXA_MIN_LUMINANCE: &str = "EXA_MIN_LUMINANCE"; pub static EZA_MIN_LUMINANCE: &str = "EZA_MIN_LUMINANCE"; /// Environment variable used to automate the same behavior as `--icons=auto` if set. /// Any explicit use of `--icons=WHEN` overrides this behavior. pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO"; pub static EZA_STDIN_SEPARATOR: &str = "EZA_STDIN_SEPARATOR"; /// Environment variable used to choose how windows attributes are displayed. /// Short will display a single character for each set attribute, long will /// display a comma separated list of descriptions. pub static EZA_WINDOWS_ATTRIBUTES: &str = "EZA_WINDOWS_ATTRIBUTES"; /// Mockable wrapper for `std::env::var_os`. pub trait Vars { fn get(&self, name: &'static str) -> Option; /// Get the variable `name` and if not set get the variable `fallback`. fn get_with_fallback(&self, name: &'static str, fallback: &'static str) -> Option { self.get(name).or_else(|| self.get(fallback)) } /// Get the source of the value. If the variable `name` is set return /// `Some(name)` else if the variable `fallback` is set return /// `Some(fallback)` else `None`. fn source(&self, name: &'static str, fallback: &'static str) -> Option<&'static str> { match self.get(name) { Some(_) => Some(name), None => self.get(fallback).and(Some(fallback)), } } } // Test impl that just returns the value it has. #[cfg(test)] impl Vars for Option { fn get(&self, _name: &'static str) -> Option { self.clone() } } #[cfg(test)] #[allow(dead_code)] pub struct MockVars { columns: OsString, colors: OsString, no_colors: OsString, strict: OsString, debug: OsString, grid_rows: OsString, icon_spacing: OsString, luminance: OsString, icons: OsString, } #[cfg(test)] #[allow(dead_code)] impl Vars for MockVars { fn get(&self, name: &'static str) -> Option { match name { "EXA_STRICT" | "EZA_STRICT" => Some(self.strict.clone()), "EZA_COLORS" | "LS_COLORS" | "EXA_COLORS" => Some(self.colors.clone()), "EXA_DEBUG" | "EZA_DEBUG" => Some(self.debug.clone()), "EXA_GRID_ROWS" | "EZA_GRID_ROWS" => Some(self.grid_rows.clone()), "EXA_ICON_SPACING" | "EZA_ICON_SPACING" => Some(self.icon_spacing.clone()), "EXA_MIN_LUMINANCE" | "EZA_MIN_LUMINANCE" => Some(self.luminance.clone()), "EZA_ICONS_AUTO" => Some(self.icons.clone()), "COLUMNS" => Some(self.columns.clone()), "NO_COLOR" => Some(self.no_colors.clone()), _ => None, } } } #[cfg(test)] #[allow(dead_code)] impl MockVars { pub fn set(&mut self, var: &'static str, value: &OsString) { match var { "EXA_STRICT" | "EZA_STRICT" => self.strict = value.clone(), "EZA_COLORS" | "LS_COLORS" | "EXA_COLORS" => self.colors = value.clone(), "EXA_DEBUG" | "EZA_DEBUG" => self.debug = value.clone(), "EXA_GRID_ROWS" | "EZA_GRID_ROWS" => self.grid_rows = value.clone(), "EXA_ICON_SPACING" | "EZA_ICON_SPACING" => self.icon_spacing = value.clone(), "EXA_MIN_LUMINANCE" | "EZA_MIN_LUMINANCE" => self.luminance = value.clone(), "EZA_ICONS_AUTO" => self.icons = value.clone(), "COLUMNS" => self.columns = value.clone(), "NO_COLOR" => self.no_colors = value.clone(), _ => (), }; () } } eza-0.18.2/src/options/version.rs000064400000000000000000000030751046102023000150150ustar 00000000000000//! Printing the version string. //! //! The code that works out which string to print is done in `build.rs`. use std::fmt; use crate::options::flags; use crate::options::parser::MatchedFlags; #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct VersionString; // There were options here once, but there aren’t anymore! impl VersionString { /// Determines how to show the version, if at all, based on the user’s /// command-line arguments. This one works backwards from the other /// ‘deduce’ functions, returning Err if help needs to be shown. /// /// Like --help, this doesn’t check for errors. pub fn deduce(matches: &MatchedFlags<'_>) -> Option { if matches.count(&flags::VERSION) > 0 { Some(Self) } else { None } } } impl fmt::Display for VersionString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{}", include_str!(concat!(env!("OUT_DIR"), "/version_string.txt")) ) } } #[cfg(test)] mod test { use crate::options::{Options, OptionsResult}; use std::ffi::OsStr; #[test] fn version() { let args = vec![OsStr::new("--version")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Version(_))); } #[test] fn version_with_file() { let args = vec![OsStr::new("--version"), OsStr::new("me")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Version(_))); } } eza-0.18.2/src/options/view.rs000064400000000000000000001046131046102023000143020ustar 00000000000000use std::ffi::OsString; use crate::fs::feature::xattr; use crate::options::parser::MatchedFlags; use crate::options::{flags, vars, NumberSource, OptionsError, Vars}; use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions}; use crate::output::file_name::Options as FileStyle; use crate::output::grid_details::{self, RowThreshold}; use crate::output::table::{ Columns, FlagsFormat, GroupFormat, Options as TableOptions, SizeFormat, TimeTypes, UserFormat, }; use crate::output::time::TimeFormat; use crate::output::{details, grid, Mode, TerminalWidth, View}; impl View { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let mode = Mode::deduce(matches, vars)?; let deref_links = matches.has(&flags::DEREF_LINKS)?; let total_size = matches.has(&flags::TOTAL_SIZE)?; let width = TerminalWidth::deduce(matches, vars)?; let file_style = FileStyle::deduce(matches, vars, width.actual_terminal_width().is_some())?; Ok(Self { mode, width, file_style, deref_links, total_size, }) } } impl Mode { /// Determine which viewing mode to use based on the user’s options. /// /// As with the other options, arguments are scanned right-to-left and the /// first flag found is matched, so `exa --oneline --long` will pick a /// details view, and `exa --long --oneline` will pick the lines view. /// /// This is complicated a little by the fact that `--grid` and `--tree` /// can also combine with `--long`, so care has to be taken to use the pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let flag = matches.has_where_any(|f| { f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE) || f.matches(&flags::GRID) || f.matches(&flags::TREE) }); let Some(flag) = flag else { Self::strict_check_long_flags(matches)?; let grid = grid::Options::deduce(matches)?; return Ok(Self::Grid(grid)); }; if flag.matches(&flags::LONG) || (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?) || (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?) { let _ = matches.has(&flags::LONG)?; let details = details::Options::deduce_long(matches, vars)?; let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE)); if flag.is_some() && flag.unwrap().matches(&flags::GRID) { let _ = matches.has(&flags::GRID)?; let grid = grid::Options::deduce(matches)?; let row_threshold = RowThreshold::deduce(vars)?; let grid_details = grid_details::Options { grid, details, row_threshold, }; return Ok(Self::GridDetails(grid_details)); } // the --tree case is handled by the DirAction parser later return Ok(Self::Details(details)); } Self::strict_check_long_flags(matches)?; if flag.matches(&flags::TREE) { let _ = matches.has(&flags::TREE)?; let details = details::Options::deduce_tree(matches, vars)?; return Ok(Self::Details(details)); } if flag.matches(&flags::ONE_LINE) { let _ = matches.has(&flags::ONE_LINE)?; return Ok(Self::Lines); } let grid = grid::Options::deduce(matches)?; Ok(Self::Grid(grid)) } fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError> { // If --long hasn’t been passed, then check if we need to warn the // user about flags that won’t have any effect. if matches.is_strict() { for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS, &flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC, &flags::MOUNTS, ] { if matches.has(option)? { return Err(OptionsError::Useless(option, false, &flags::LONG)); } } if matches.has(&flags::GIT)? && !matches.has(&flags::NO_GIT)? { return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG)); } else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? { return Err(OptionsError::Useless2( &flags::LEVEL, &flags::RECURSE, &flags::TREE, )); } } Ok(()) } } impl grid::Options { fn deduce(matches: &MatchedFlags<'_>) -> Result { let grid = grid::Options { across: matches.has(&flags::ACROSS)?, }; Ok(grid) } } impl details::Options { fn deduce_tree(matches: &MatchedFlags<'_>, vars: &V) -> Result { let details = details::Options { table: None, header: false, xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?, secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?, mounts: matches.has(&flags::MOUNTS)?, color_scale: ColorScaleOptions::deduce(matches, vars)?, }; Ok(details) } fn deduce_long(matches: &MatchedFlags<'_>, vars: &V) -> Result { if matches.is_strict() { if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? { return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG)); } else if matches.has(&flags::ONE_LINE)? { return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG)); } } Ok(details::Options { table: Some(TableOptions::deduce(matches, vars)?), header: matches.has(&flags::HEADER)?, xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?, secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?, mounts: matches.has(&flags::MOUNTS)?, color_scale: ColorScaleOptions::deduce(matches, vars)?, }) } } impl TerminalWidth { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { if let Some(width) = matches.get(&flags::WIDTH)? { let arg_str = width.to_string_lossy(); match arg_str.parse() { Ok(w) => { if w >= 1 { Ok(Self::Set(w)) } else { Ok(Self::Automatic) } } Err(e) => { let source = NumberSource::Arg(&flags::WIDTH); Err(OptionsError::FailedParse(arg_str.to_string(), source, e)) } } } else if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) { match columns.parse() { Ok(width) => Ok(Self::Set(width)), Err(e) => { let source = NumberSource::Env(vars::COLUMNS); Err(OptionsError::FailedParse(columns, source, e)) } } } else { Ok(Self::Automatic) } } } impl RowThreshold { fn deduce(vars: &V) -> Result { if let Some(columns) = vars .get_with_fallback(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS) .and_then(|s| s.into_string().ok()) { match columns.parse() { Ok(rows) => Ok(Self::MinimumRows(rows)), Err(e) => { let source = NumberSource::Env( vars.source(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS) .unwrap(), ); Err(OptionsError::FailedParse(columns, source, e)) } } } else { Ok(Self::AlwaysGrid) } } } impl TableOptions { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let time_format = TimeFormat::deduce(matches, vars)?; let size_format = SizeFormat::deduce(matches)?; let user_format = UserFormat::deduce(matches)?; let group_format = GroupFormat::deduce(matches)?; let flags_format = FlagsFormat::deduce(vars); let columns = Columns::deduce(matches, vars)?; Ok(Self { size_format, time_format, user_format, group_format, flags_format, columns, }) } } impl Columns { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let time_types = TimeTypes::deduce(matches)?; let no_git_env = vars .get_with_fallback(vars::EXA_OVERRIDE_GIT, vars::EZA_OVERRIDE_GIT) .is_some(); let git = matches.has(&flags::GIT)? && !matches.has(&flags::NO_GIT)? && !no_git_env; let subdir_git_repos = matches.has(&flags::GIT_REPOS)? && !matches.has(&flags::NO_GIT)? && !no_git_env; let subdir_git_repos_no_stat = !subdir_git_repos && matches.has(&flags::GIT_REPOS_NO_STAT)? && !matches.has(&flags::NO_GIT)? && !no_git_env; let blocksize = matches.has(&flags::BLOCKSIZE)?; let group = matches.has(&flags::GROUP)?; let inode = matches.has(&flags::INODE)?; let links = matches.has(&flags::LINKS)?; let octal = matches.has(&flags::OCTAL)?; let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?; let file_flags = matches.has(&flags::FILE_FLAGS)?; let permissions = !matches.has(&flags::NO_PERMISSIONS)?; let filesize = !matches.has(&flags::NO_FILESIZE)?; let user = !matches.has(&flags::NO_USER)?; Ok(Self { time_types, inode, links, blocksize, group, git, subdir_git_repos, subdir_git_repos_no_stat, octal, security_context, file_flags, permissions, filesize, user, }) } } impl SizeFormat { /// Determine which file size to use in the file size column based on /// the user’s options. /// /// The default mode is to use the decimal prefixes, as they are the /// most commonly-understood, and don’t involve trying to parse large /// strings of digits in your head. Changing the format to anything else /// involves the `--binary` or `--bytes` flags, and these conflict with /// each other. fn deduce(matches: &MatchedFlags<'_>) -> Result { let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?; Ok(match flag { Some(f) if f.matches(&flags::BINARY) => Self::BinaryBytes, Some(f) if f.matches(&flags::BYTES) => Self::JustBytes, _ => Self::DecimalBytes, }) } } impl TimeFormat { /// Determine how time should be formatted in timestamp columns. fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let word = if let Some(w) = matches.get(&flags::TIME_STYLE)? { w.to_os_string() } else { match vars.get(vars::TIME_STYLE) { Some(ref t) if !t.is_empty() => t.clone(), _ => return Ok(Self::DefaultFormat), } }; match word.to_string_lossy().as_ref() { "default" => Ok(Self::DefaultFormat), "relative" => Ok(Self::Relative), "iso" => Ok(Self::ISOFormat), "long-iso" => Ok(Self::LongISO), "full-iso" => Ok(Self::FullISO), fmt if fmt.starts_with('+') => { let mut lines = fmt[1..].lines(); // line 1 will be None when: // - there is nothing after `+` // line 1 will be empty when: // - `+` is followed immediately by `\n` let empty_non_recent_format_msg = "Custom timestamp format is empty, \ please supply a chrono format string after the plus sign."; let non_recent = lines.next().expect(empty_non_recent_format_msg); let non_recent = if non_recent.is_empty() { panic!("{}", empty_non_recent_format_msg) } else { non_recent.to_owned() }; // line 2 will be None when: // - there is not a single `\n` // - there is nothing after the first `\n` // line 2 will be empty when: // - there exist at least 2 `\n`, and no content between the 1st and 2nd `\n` let empty_recent_format_msg = "Custom timestamp format for recent files is empty, \ please supply a chrono format string at the second line."; let recent = lines.next().map(|rec| { if rec.is_empty() { panic!("{}", empty_recent_format_msg) } else { rec.to_owned() } }); Ok(Self::Custom { non_recent, recent }) } _ => Err(OptionsError::BadArgument(&flags::TIME_STYLE, word)), } } } impl UserFormat { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flag = matches.has(&flags::NUMERIC)?; Ok(if flag { Self::Numeric } else { Self::Name }) } } impl GroupFormat { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flag = matches.has(&flags::SMART_GROUP)?; Ok(if flag { Self::Smart } else { Self::Regular }) } } impl TimeTypes { /// Determine which of a file’s time fields should be displayed for it /// based on the user’s options. /// /// There are two separate ways to pick which fields to show: with a /// flag (such as `--modified`) or with a parameter (such as /// `--time=modified`). An error is signaled if both ways are used. /// /// It’s valid to show more than one column by passing in more than one /// option, but passing *no* options means that the user just wants to /// see the default set. fn deduce(matches: &MatchedFlags<'_>) -> Result { let possible_word = matches.get(&flags::TIME)?; let modified = matches.has(&flags::MODIFIED)?; let changed = matches.has(&flags::CHANGED)?; let accessed = matches.has(&flags::ACCESSED)?; let created = matches.has(&flags::CREATED)?; let no_time = matches.has(&flags::NO_TIME)?; #[rustfmt::skip] let time_types = if no_time { Self { modified: false, changed: false, accessed: false, created: false, } } else if let Some(word) = possible_word { if modified { return Err(OptionsError::Useless(&flags::MODIFIED, true, &flags::TIME)); } else if changed { return Err(OptionsError::Useless(&flags::CHANGED, true, &flags::TIME)); } else if accessed { return Err(OptionsError::Useless(&flags::ACCESSED, true, &flags::TIME)); } else if created { return Err(OptionsError::Useless(&flags::CREATED, true, &flags::TIME)); } else if word == "mod" || word == "modified" { Self { modified: true, changed: false, accessed: false, created: false } } else if word == "ch" || word == "changed" { Self { modified: false, changed: true, accessed: false, created: false } } else if word == "acc" || word == "accessed" { Self { modified: false, changed: false, accessed: true, created: false } } else if word == "cr" || word == "created" { Self { modified: false, changed: false, accessed: false, created: true } } else { return Err(OptionsError::BadArgument(&flags::TIME, word.into())); } } else if modified || changed || accessed || created { Self { modified, changed, accessed, created, } } else { Self::default() }; Ok(time_types) } } impl ColorScaleOptions { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let min_luminance = match vars.get_with_fallback(vars::EZA_MIN_LUMINANCE, vars::EXA_MIN_LUMINANCE) { Some(var) => match var.to_string_lossy().parse() { Ok(luminance) if (-100..=100).contains(&luminance) => luminance, _ => 40, }, None => 40, }; let mode = if let Some(w) = matches .get(&flags::COLOR_SCALE_MODE)? .or(matches.get(&flags::COLOUR_SCALE_MODE)?) { match w.to_str() { Some("fixed") => ColorScaleMode::Fixed, Some("gradient") => ColorScaleMode::Gradient, _ => Err(OptionsError::BadArgument( &flags::COLOR_SCALE_MODE, w.to_os_string(), ))?, } } else { ColorScaleMode::Gradient }; let mut options = ColorScaleOptions { mode, min_luminance, size: false, age: false, }; let words = if let Some(w) = matches .get(&flags::COLOR_SCALE)? .or(matches.get(&flags::COLOUR_SCALE)?) { w.to_os_string() } else { return Ok(options); }; for word in words.to_string_lossy().split(',') { match word { "all" => { options.size = true; options.age = true; } "age" => options.age = true, "size" => options.size = true, _ => Err(OptionsError::BadArgument( &flags::COLOR_SCALE, OsString::from(word), ))?, }; } Ok(options) } } #[cfg(test)] mod test { use super::*; use crate::options::flags; use crate::options::parser::{Arg, Flag}; use std::ffi::OsString; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE, &flags::TIME, &flags::MODIFIED, &flags::CHANGED, &flags::CREATED, &flags::ACCESSED, &flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT, &flags::LINKS, &flags::BLOCKSIZE, &flags::LONG, &flags::LEVEL, &flags::GRID, &flags::ACROSS, &flags::ONE_LINE, &flags::TREE, &flags::NUMERIC, ]; #[allow(unused_macro_rules)] macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { /// Macro that writes a test. /// If testing both strictnesses, they’ll both be done in the same function. #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { assert_eq!(result, $result); } } }; ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => { /// Special macro for testing Err results. /// This is needed because sometimes the Ok type doesn’t implement `PartialEq`. #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { assert_eq!(result.unwrap_err(), $result); } } }; ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => { /// More general macro for testing against a pattern. /// Instead of using `PartialEq`, this just tests if it matches a pat. #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf) }) { println!("Testing {:?}", result); match result { $pat => assert!(true), _ => assert!(false), } } } }; ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => { /// Like above, but with $vars. #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf, &$vars) }) { assert_eq!(result.unwrap_err(), $result); } } }; ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => { /// Like further above, but with $vars. #[test] fn $name() { for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { $type::deduce(mf, &$vars) }) { println!("Testing {:?}", result); match result { $pat => assert!(true), _ => assert!(false), } } } }; } mod size_formats { use super::*; // Default behaviour test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes)); // Individual flags test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes)); test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes)); // Overriding test!(both_1: SizeFormat <- ["--binary", "--binary"]; Last => Ok(SizeFormat::BinaryBytes)); test!(both_2: SizeFormat <- ["--bytes", "--binary"]; Last => Ok(SizeFormat::BinaryBytes)); test!(both_3: SizeFormat <- ["--binary", "--bytes"]; Last => Ok(SizeFormat::JustBytes)); test!(both_4: SizeFormat <- ["--bytes", "--bytes"]; Last => Ok(SizeFormat::JustBytes)); test!(both_5: SizeFormat <- ["--binary", "--binary"]; Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("binary"))); test!(both_6: SizeFormat <- ["--bytes", "--binary"]; Complain => err OptionsError::Duplicate(Flag::Long("bytes"), Flag::Long("binary"))); test!(both_7: SizeFormat <- ["--binary", "--bytes"]; Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("bytes"))); test!(both_8: SizeFormat <- ["--bytes", "--bytes"]; Complain => err OptionsError::Duplicate(Flag::Long("bytes"), Flag::Long("bytes"))); } mod time_formats { use super::*; // These tests use pattern matching because TimeFormat doesn’t // implement PartialEq. // Default behaviour test!(empty: TimeFormat <- [], None; Both => like Ok(TimeFormat::DefaultFormat)); // Individual settings test!(default: TimeFormat <- ["--time-style=default"], None; Both => like Ok(TimeFormat::DefaultFormat)); test!(iso: TimeFormat <- ["--time-style", "iso"], None; Both => like Ok(TimeFormat::ISOFormat)); test!(relative: TimeFormat <- ["--time-style", "relative"], None; Both => like Ok(TimeFormat::Relative)); test!(long_iso: TimeFormat <- ["--time-style=long-iso"], None; Both => like Ok(TimeFormat::LongISO)); test!(full_iso: TimeFormat <- ["--time-style", "full-iso"], None; Both => like Ok(TimeFormat::FullISO)); test!(custom_style: TimeFormat <- ["--time-style", "+%Y/%m/%d"], None; Both => like Ok(TimeFormat::Custom { recent: None, .. })); test!(custom_style_multiline: TimeFormat <- ["--time-style", "+%Y/%m/%d\n--%m-%d"], None; Both => like Ok(TimeFormat::Custom { recent: Some(_), .. })); test!(bad_custom_style: TimeFormat <- ["--time-style", "%Y/%m/%d"], None; Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from("%Y/%m/%d"))); // Overriding test!(actually: TimeFormat <- ["--time-style=default", "--time-style", "iso"], None; Last => like Ok(TimeFormat::ISOFormat)); test!(actual_2: TimeFormat <- ["--time-style=default", "--time-style", "iso"], None; Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style"))); test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None; Last => like Ok(TimeFormat::FullISO)); test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None; Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style"))); // Errors test!(daily: TimeFormat <- ["--time-style=24-hour"], None; Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from("24-hour"))); // `TIME_STYLE` environment variable is defined. // If the time-style argument is not given, `TIME_STYLE` is used. test!(use_env: TimeFormat <- [], Some("long-iso".into()); Both => like Ok(TimeFormat::LongISO)); // If the time-style argument is given, `TIME_STYLE` is overriding. test!(override_env: TimeFormat <- ["--time-style=full-iso"], Some("long-iso".into()); Both => like Ok(TimeFormat::FullISO)); } mod time_types { use super::*; // Default behaviour test!(empty: TimeTypes <- []; Both => Ok(TimeTypes::default())); // Modified test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false })); test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false })); test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false })); test!(t_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false })); // Changed #[cfg(target_family = "unix")] test!(changed: TimeTypes <- ["--changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false })); #[cfg(target_family = "unix")] test!(time_ch: TimeTypes <- ["--time=changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false })); #[cfg(target_family = "unix")] test!(t_ch: TimeTypes <- ["-t", "ch"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false })); // Accessed test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false })); test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false })); test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false })); test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false })); // Created test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true })); test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true })); test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true })); test!(t_cr: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true })); // Multiples test!(time_uu: TimeTypes <- ["-u", "--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: true, created: false })); // Errors test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("tea"))); test!(t_ea: TimeTypes <- ["-tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("ea"))); // Overriding test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false })); test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err OptionsError::Duplicate(Flag::Short(b't'), Flag::Short(b't'))); } mod views { use super::*; use crate::output::grid::Options as GridOptions; // Default test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_))); // Grid views test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, .. }))); test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, .. }))); test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. }))); test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. }))); // Lines views test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines)); test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines)); // Details views test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_))); test!(ell: Mode <- ["-l"], None; Both => like Ok(Mode::Details(_))); // Grid-details views test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_))); test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_))); // Options that do nothing with --long test!(long_across: Mode <- ["--long", "--across"], None; Last => like Ok(Mode::Details(_))); // Options that do nothing without --long test!(just_header: Mode <- ["--header"], None; Last => like Ok(Mode::Grid(_))); test!(just_group: Mode <- ["--group"], None; Last => like Ok(Mode::Grid(_))); test!(just_inode: Mode <- ["--inode"], None; Last => like Ok(Mode::Grid(_))); test!(just_links: Mode <- ["--links"], None; Last => like Ok(Mode::Grid(_))); test!(just_blocks: Mode <- ["--blocksize"], None; Last => like Ok(Mode::Grid(_))); test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_))); test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_))); test!(just_numeric: Mode <- ["--numeric"], None; Last => like Ok(Mode::Grid(_))); #[cfg(feature = "git")] test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_))); test!(just_header_2: Mode <- ["--header"], None; Complain => err OptionsError::Useless(&flags::HEADER, false, &flags::LONG)); test!(just_group_2: Mode <- ["--group"], None; Complain => err OptionsError::Useless(&flags::GROUP, false, &flags::LONG)); test!(just_inode_2: Mode <- ["--inode"], None; Complain => err OptionsError::Useless(&flags::INODE, false, &flags::LONG)); test!(just_links_2: Mode <- ["--links"], None; Complain => err OptionsError::Useless(&flags::LINKS, false, &flags::LONG)); test!(just_blocks_2: Mode <- ["--blocksize"], None; Complain => err OptionsError::Useless(&flags::BLOCKSIZE, false, &flags::LONG)); test!(just_binary_2: Mode <- ["--binary"], None; Complain => err OptionsError::Useless(&flags::BINARY, false, &flags::LONG)); test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err OptionsError::Useless(&flags::BYTES, false, &flags::LONG)); test!(just_numeric2: Mode <- ["--numeric"], None; Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG)); #[cfg(feature = "git")] test!(just_git_2: Mode <- ["--git"], None; Complain => err OptionsError::Useless(&flags::GIT, false, &flags::LONG)); // Contradictions and combinations test!(lgo: Mode <- ["--long", "--grid", "--oneline"], None; Both => like Ok(Mode::Lines)); test!(lgt: Mode <- ["--long", "--grid", "--tree"], None; Both => like Ok(Mode::Details(_))); test!(tgl: Mode <- ["--tree", "--grid", "--long"], None; Both => like Ok(Mode::GridDetails(_))); test!(tlg: Mode <- ["--tree", "--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_))); test!(ot: Mode <- ["--oneline", "--tree"], None; Both => like Ok(Mode::Details(_))); test!(og: Mode <- ["--oneline", "--grid"], None; Both => like Ok(Mode::Grid(_))); test!(tg: Mode <- ["--tree", "--grid"], None; Both => like Ok(Mode::Grid(_))); } } eza-0.18.2/src/output/cell.rs000064400000000000000000000204301046102023000141060ustar 00000000000000//! The `TextCell` type for the details and lines views. use std::iter::Sum; use std::ops::{Add, Deref, DerefMut}; use ansiterm::{ANSIString, ANSIStrings, Style}; use unicode_width::UnicodeWidthStr; /// An individual cell that holds text in a table, used in the details and /// lines views to store ANSI-terminal-formatted data before it is printed. /// /// A text cell is made up of zero or more strings coupled with the /// pre-computed length of all the strings combined. When constructing details /// or grid-details tables, the length will have to be queried multiple times, /// so it makes sense to cache it. /// /// (This used to be called `Cell`, but was renamed because there’s a Rust /// type by that name too.) #[derive(PartialEq, Debug, Clone, Default)] pub struct TextCell { /// The contents of this cell, as a vector of ANSI-styled strings. pub contents: TextCellContents, /// The Unicode “display width” of this cell. pub width: DisplayWidth, } impl Deref for TextCell { type Target = TextCellContents; fn deref(&self) -> &Self::Target { &self.contents } } impl TextCell { /// Creates a new text cell that holds the given text in the given style, /// computing the Unicode width of the text. pub fn paint(style: Style, text: String) -> Self { let width = DisplayWidth::from(&*text); Self { contents: vec![style.paint(text)].into(), width, } } /// Creates a new text cell that holds the given text in the given style, /// computing the Unicode width of the text. (This could be merged with /// `paint`, but.) pub fn paint_str(style: Style, text: &'static str) -> Self { let width = DisplayWidth::from(text); Self { contents: vec![style.paint(text)].into(), width, } } /// Creates a new “blank” text cell that contains a single hyphen in the /// given style, which should be the “punctuation” style from a `Colours` /// value. /// /// This is used in place of empty table cells, as it is easier to read /// tabular data when there is *something* in each cell. pub fn blank(style: Style) -> Self { Self { contents: vec![style.paint("-")].into(), width: DisplayWidth::from(1), } } /// Adds the given number of unstyled spaces after this cell. /// /// This method allocates a `String` to hold the spaces. pub fn add_spaces(&mut self, count: usize) { (*self.width) += count; let spaces: String = " ".repeat(count); self.contents.0.push(Style::default().paint(spaces)); } /// Adds the contents of another `ANSIString` to the end of this cell. pub fn push(&mut self, string: ANSIString<'static>, extra_width: usize) { self.contents.0.push(string); (*self.width) += extra_width; } /// Adds all the contents of another `TextCell` to the end of this cell. pub fn append(&mut self, other: Self) { (*self.width) += *other.width; self.contents.0.extend(other.contents.0); } } // I’d like to eventually abstract cells so that instead of *every* cell // storing a vector, only variable-length cells would, and individual cells // would just store an array of a fixed length (which would usually be just 1 // or 2), which wouldn’t require a heap allocation. // // For examples, look at the `render_*` methods in the `Table` object in the // details view: // // - `render_blocks`, `inode`, and `links` will always return a // one-string-long TextCell; // - `render_size` will return one or two strings in a TextCell, depending on // the size and whether one is present; // - `render_permissions` will return ten or eleven strings; // - `filename` and `symlink_filename` in the output module root return six or // five strings. // // In none of these cases are we dealing with a *truly variable* number of // strings: it is only when the strings are concatenated together do we need a // growable, heap-allocated buffer. // // So it would be nice to abstract the `TextCell` type so instead of a `Vec`, // it can use anything of type `T: IntoIterator>`. // This would allow us to still hold all the data, but allocate less. // // But exa still has bugs and I need to fix those first :( /// The contents of a text cell, as a vector of ANSI-styled strings. /// /// It’s possible to use this type directly in the case where you want a /// `TextCell` but aren’t concerned with tracking its width, because it occurs /// in the final cell of a table or grid and there’s no point padding it. This /// happens when dealing with file names. #[derive(PartialEq, Debug, Clone, Default)] pub struct TextCellContents(Vec>); impl From>> for TextCellContents { fn from(strings: Vec>) -> Self { Self(strings) } } impl Deref for TextCellContents { type Target = [ANSIString<'static>]; fn deref(&self) -> &Self::Target { &self.0 } } // No DerefMut implementation here — it would be publicly accessible, and as // the contents only get changed in this module, the mutators in the struct // above can just access the value directly. impl TextCellContents { /// Produces an `ANSIStrings` value that can be used to print the styled /// values of this cell as an ANSI-terminal-formatted string. pub fn strings(&self) -> ANSIStrings<'_> { ANSIStrings(&self.0) } /// Calculates the width that a cell with these contents would take up, by /// counting the number of characters in each unformatted ANSI string. pub fn width(&self) -> DisplayWidth { self.0 .iter() .map(|anstr| DisplayWidth::from(&**anstr)) .sum() } /// Promotes these contents to a full cell containing them alongside /// their calculated width. pub fn promote(self) -> TextCell { TextCell { width: self.width(), contents: self, } } } /// The Unicode “display width” of a string. /// /// This is related to the number of *graphemes* of a string, rather than the /// number of *characters*, or *bytes*: although most characters are one /// column wide, a few can be two columns wide, and this is important to note /// when calculating widths for displaying tables in a terminal. /// /// This type is used to ensure that the width, rather than the length, is /// used when constructing a `TextCell` — it’s too easy to write something /// like `file_name.len()` and assume it will work! /// /// It has `From` impls that convert an input string or fixed with to values /// of this type, and will `Deref` to the contained `usize` value. #[derive(PartialEq, Eq, Debug, Clone, Copy, Default)] pub struct DisplayWidth(usize); impl<'a> From<&'a str> for DisplayWidth { fn from(input: &'a str) -> Self { Self(UnicodeWidthStr::width(input)) } } impl From for DisplayWidth { fn from(width: usize) -> Self { Self(width) } } impl Deref for DisplayWidth { type Target = usize; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for DisplayWidth { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Add for DisplayWidth { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0) } } impl Add for DisplayWidth { type Output = Self; fn add(self, rhs: usize) -> Self::Output { Self(self.0 + rhs) } } impl Sum for DisplayWidth { fn sum(iter: I) -> Self where I: Iterator, { iter.fold(Self(0), Add::add) } } #[cfg(test)] mod width_unit_test { use super::DisplayWidth; #[test] fn empty_string() { let cell = DisplayWidth::from(""); assert_eq!(*cell, 0); } #[test] fn test_string() { let cell = DisplayWidth::from("Diss Playwidth"); assert_eq!(*cell, 14); } #[test] fn addition() { let cell_one = DisplayWidth::from("/usr/bin/"); let cell_two = DisplayWidth::from("drinking"); assert_eq!(*(cell_one + cell_two), 17); } #[test] fn addition_usize() { let cell = DisplayWidth::from("/usr/bin/"); assert_eq!(*(cell + 8), 17); } } eza-0.18.2/src/output/color_scale.rs000064400000000000000000000141551046102023000154630ustar 00000000000000use ansiterm::{Colour, Style}; use log::trace; use palette::{FromColor, Oklab, Srgb}; use crate::{ fs::{dir_action::RecurseOptions, feature::git::GitCache, fields::Size, DotFilter, File}, output::{table::TimeType, tree::TreeDepth}, }; #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct ColorScaleOptions { pub mode: ColorScaleMode, pub min_luminance: isize, pub size: bool, pub age: bool, } #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ColorScaleMode { Fixed, Gradient, } #[derive(Copy, Clone, Debug)] pub struct ColorScaleInformation { pub options: ColorScaleOptions, pub accessed: Option, pub changed: Option, pub created: Option, pub modified: Option, pub size: Option, } impl ColorScaleInformation { pub fn from_color_scale( color_scale: ColorScaleOptions, files: &[File<'_>], dot_filter: DotFilter, git: Option<&GitCache>, git_ignoring: bool, r: Option, ) -> Option { if color_scale.mode == ColorScaleMode::Fixed { None } else { let mut information = Self { options: color_scale, accessed: None, changed: None, created: None, modified: None, size: None, }; update_information_recursively( &mut information, files, dot_filter, git, git_ignoring, TreeDepth::root(), r, ); Some(information) } } pub fn adjust_style(&self, mut style: Style, value: f32, range: Option) -> Style { if let (Some(fg), Some(range)) = (style.foreground, range) { let mut ratio = ((value - range.min) / (range.max - range.min)).clamp(0.0, 1.0); if ratio.is_nan() { ratio = 1.0; } style.foreground = Some(adjust_luminance( fg, ratio, self.options.min_luminance as f32 / 100.0, )); } style } pub fn apply_time_gradient(&self, style: Style, file: &File<'_>, time_type: TimeType) -> Style { let range = match time_type { TimeType::Modified => self.modified, TimeType::Changed => self.changed, TimeType::Accessed => self.accessed, TimeType::Created => self.created, }; if let Some(file_time) = time_type.get_corresponding_time(file) { self.adjust_style(style, file_time.timestamp_millis() as f32, range) } else { style } } } fn update_information_recursively( information: &mut ColorScaleInformation, files: &[File<'_>], dot_filter: DotFilter, git: Option<&GitCache>, git_ignoring: bool, depth: TreeDepth, r: Option, ) { for file in files { if information.options.age { Extremes::update( file.created_time().map(|x| x.timestamp_millis() as f32), &mut information.created, ); Extremes::update( file.modified_time().map(|x| x.timestamp_millis() as f32), &mut information.modified, ); Extremes::update( file.accessed_time().map(|x| x.timestamp_millis() as f32), &mut information.accessed, ); Extremes::update( file.changed_time().map(|x| x.timestamp_millis() as f32), &mut information.changed, ); } if information.options.size { let size = match file.size() { Size::Some(size) => Some(size as f32), _ => None, }; Extremes::update(size, &mut information.size); } // We don't want to recurse into . and .., but still want to list them, therefore bypass // the dot_filter. if file.is_directory() && r.is_some_and(|x| !x.is_too_deep(depth.0)) && file.name != "." && file.name != ".." { match file.to_dir() { Ok(dir) => { let files: Vec> = dir .files(dot_filter, git, git_ignoring, false, false) .flatten() .collect(); update_information_recursively( information, &files, dot_filter, git, git_ignoring, depth.deeper(), r, ); } Err(e) => trace!("Unable to access directory {}: {}", file.name, e), } }; } } #[derive(Copy, Clone, Debug)] pub struct Extremes { max: f32, min: f32, } impl Extremes { fn update(maybe_value: Option, maybe_range: &mut Option) { match (maybe_value, maybe_range) { (Some(value), Some(range)) => { if value > range.max { range.max = value; } else if value < range.min { range.min = value; }; } (Some(value), rel) => { let _ = rel.insert({ Extremes { max: value, min: value, } }); } _ => (), }; } } fn adjust_luminance(color: Colour, x: f32, min_l: f32) -> Colour { let color = Srgb::from_components(color.into_rgb()).into_linear(); let mut lab: Oklab = Oklab::from_color(color); lab.l = (min_l + (1.0 - min_l) * (-4.0 * (1.0 - x)).exp()).clamp(0.0, 1.0); let adjusted_rgb: Srgb = Srgb::from_color(lab); Colour::RGB( (adjusted_rgb.red * 255.0).round() as u8, (adjusted_rgb.green * 255.0).round() as u8, (adjusted_rgb.blue * 255.0).round() as u8, ) } eza-0.18.2/src/output/details.rs000064400000000000000000000457221046102023000146270ustar 00000000000000//! The **Details** output view displays each file as a row in a table. //! //! It’s used in the following situations: //! //! - Most commonly, when using the `--long` command-line argument to display the //! details of each file, which requires using a table view to hold all the data; //! - When using the `--tree` argument, which uses the same table view to display //! each file on its own line, with the table providing the tree characters; //! - When using both the `--long` and `--grid` arguments, which constructs a //! series of tables to fit all the data on the screen. //! //! You will probably recognise it from the `ls --long` command. It looks like //! this: //! //! ```text //! .rw-r--r-- 9.6k ben 29 Jun 16:16 Cargo.lock //! .rw-r--r-- 547 ben 23 Jun 10:54 Cargo.toml //! .rw-r--r-- 1.1k ben 23 Nov 2014 LICENCE //! .rw-r--r-- 2.5k ben 21 May 14:38 README.md //! .rw-r--r-- 382k ben 8 Jun 21:00 screenshot.png //! drwxr-xr-x - ben 29 Jun 14:50 src //! drwxr-xr-x - ben 28 Jun 19:53 target //! ``` //! //! The table is constructed by creating a `Table` value, which produces a `Row` //! value for each file. These rows can contain a vector of `Cell`s, or they can //! contain depth information for the tree view, or both. These are described //! below. //! //! //! ## Constructing Detail Views //! //! When using the `--long` command-line argument, the details of each file are //! displayed next to its name. //! //! The table holds a vector of all the column types. For each file and column, a //! `Cell` value containing the ANSI-coloured text and Unicode width of each cell //! is generated, with the row and column determined by indexing into both arrays. //! //! The column types vector does not actually include the filename. This is //! because the filename is always the rightmost field, and as such, it does not //! need to have its width queried or be padded with spaces. //! //! To illustrate the above: //! //! ```text //! ┌─────────────────────────────────────────────────────────────────────────┐ //! │ columns: [ Permissions, Size, User, Date(Modified) ] │ //! ├─────────────────────────────────────────────────────────────────────────┤ //! │ rows: cells: filename: │ //! │ row 1: [ ".rw-r--r--", "9.6k", "ben", "29 Jun 16:16" ] Cargo.lock │ //! │ row 2: [ ".rw-r--r--", "547", "ben", "23 Jun 10:54" ] Cargo.toml │ //! │ row 3: [ "drwxr-xr-x", "-", "ben", "29 Jun 14:50" ] src │ //! │ row 4: [ "drwxr-xr-x", "-", "ben", "28 Jun 19:53" ] target │ //! └─────────────────────────────────────────────────────────────────────────┘ //! ``` //! //! Each column in the table needs to be resized to fit its widest argument. This //! means that we must wait until every row has been added to the table before it //! can be displayed, in order to make sure that every column is wide enough. use std::io::{self, Write}; use std::path::PathBuf; use std::vec::IntoIter as VecIntoIter; use ansiterm::Style; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use log::*; use crate::fs::dir_action::RecurseOptions; use crate::fs::feature::git::GitCache; use crate::fs::feature::xattr::Attribute; use crate::fs::fields::SecurityContextType; use crate::fs::filter::FileFilter; use crate::fs::{Dir, File}; use crate::output::cell::TextCell; use crate::output::color_scale::{ColorScaleInformation, ColorScaleOptions}; use crate::output::file_name::Options as FileStyle; use crate::output::table::{Options as TableOptions, Row as TableRow, Table}; use crate::output::tree::{TreeDepth, TreeParams, TreeTrunk}; use crate::theme::Theme; /// With the **Details** view, the output gets formatted into columns, with /// each `Column` object showing some piece of information about the file, /// such as its size, or its permissions. /// /// To do this, the results have to be written to a table, instead of /// displaying each file immediately. Then, the width of each column can be /// calculated based on the individual results, and the fields are padded /// during output. /// /// Almost all the heavy lifting is done in a Table object, which handles the /// columns for each row. #[allow(clippy::struct_excessive_bools)] /// This clearly isn't a state machine #[derive(PartialEq, Eq, Debug)] pub struct Options { /// Options specific to drawing a table. /// /// Directories themselves can pick which columns are *added* to this /// list, such as the Git column. pub table: Option, /// Whether to show a header line or not. pub header: bool, /// Whether to show each file’s extended attributes. pub xattr: bool, /// Whether to show each file's security attribute. pub secattr: bool, /// Whether to show a directory's mounted filesystem details pub mounts: bool, pub color_scale: ColorScaleOptions, } pub struct Render<'a> { pub dir: Option<&'a Dir>, pub files: Vec>, pub theme: &'a Theme, pub file_style: &'a FileStyle, pub opts: &'a Options, /// Whether to recurse through directories with a tree view, and if so, /// which options to use. This field is only relevant here if the `tree` /// field of the RecurseOptions is `true`. pub recurse: Option, /// How to sort and filter the files after getting their details. pub filter: &'a FileFilter, /// Whether we are skipping Git-ignored files. pub git_ignoring: bool, pub git: Option<&'a GitCache>, pub git_repos: bool, } #[rustfmt::skip] struct Egg<'a> { table_row: Option, xattrs: &'a [Attribute], errors: Vec<(io::Error, Option)>, dir: Option

, file: &'a File<'a>, } impl<'a> AsRef> for Egg<'a> { fn as_ref(&self) -> &File<'a> { self.file } } impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { let mut rows = Vec::new(); let color_scale_info = ColorScaleInformation::from_color_scale( self.opts.color_scale, &self.files, self.filter.dot_filter, self.git, self.git_ignoring, self.recurse, ); if let Some(ref table) = self.opts.table { match (self.git, self.dir) { (Some(g), Some(d)) => { if !g.has_anything_for(&d.path) { self.git = None; } } (Some(g), None) => { if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None; } } (None, _) => { /* Keep Git how it is */ } } let mut table = Table::new(table, self.git, self.theme, self.git_repos); if self.opts.header { let header = table.header_row(); table.add_widths(&header); rows.push(self.render_header(header)); } // This is weird, but I can’t find a way around it: // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6 let mut table = Some(table); self.add_files_to_table( &mut table, &mut rows, &self.files, TreeDepth::root(), color_scale_info, ); for row in self.iterate_with_table(table.unwrap(), rows) { writeln!(w, "{}", row.strings())?; } } else { self.add_files_to_table( &mut None, &mut rows, &self.files, TreeDepth::root(), color_scale_info, ); for row in self.iterate(rows) { writeln!(w, "{}", row.strings())?; } } Ok(()) } /// Whether to show the extended attribute hint pub fn show_xattr_hint(&self, file: &File<'_>) -> bool { // Do not show the hint '@' if the only extended attribute is the security // attribute and the security attribute column is active. let xattr_count = file.extended_attributes().len(); let selinux_ctx_shown = self.opts.secattr && match file.security_context().context { SecurityContextType::SELinux(_) => true, SecurityContextType::None => false, }; xattr_count > 1 || (xattr_count == 1 && !selinux_ctx_shown) } /// Adds files to the table, possibly recursively. This is easily /// parallelisable, and uses a pool of threads. fn add_files_to_table<'dir>( &self, table: &mut Option>, rows: &mut Vec, src: &[File<'dir>], depth: TreeDepth, color_scale_info: Option, ) { use crate::fs::feature::xattr; let mut file_eggs: Vec<_> = src .par_iter() .map(|file| { let mut errors = Vec::new(); // There are three “levels” of extended attribute support: // // 1. If we’re compiling without that feature, then // exa pretends all files have no attributes. // 2. If the feature is enabled and the --extended flag // has been specified, then display an @ in the // permissions column for files with attributes, the // names of all attributes and their values, and any // errors encountered when getting them. // 3. If the --extended flag *hasn’t* been specified, then // display the @, but don’t display anything else. // // For a while, exa took a stricter approach to (3): // if an error occurred while checking a file’s xattrs to // see if it should display the @, exa would display that // error even though the attributes weren’t actually being // shown! This was confusing, as users were being shown // errors for something they didn’t explicitly ask for, // and just cluttered up the output. So now errors aren’t // printed unless the user passes --extended to signify // that they want to see them. let xattrs: &[Attribute] = if xattr::ENABLED && self.opts.xattr { file.extended_attributes() } else { &[] }; let table_row = table .as_ref() .map(|t| t.row_for_file(file, self.show_xattr_hint(file), color_scale_info)); let mut dir = None; if let Some(r) = self.recurse { if file.is_directory() && r.tree && !r.is_too_deep(depth.0) { trace!("matching on to_dir"); match file.to_dir() { Ok(d) => { dir = Some(d); } Err(e) => { errors.push((e, None)); } } } }; Egg { table_row, xattrs, errors, dir, file, } }) .collect(); // this is safe because all entries have been initialized above self.filter.sort_files(&mut file_eggs); for (tree_params, egg) in depth.iterate_over(file_eggs.into_iter()) { let mut files = Vec::new(); let mut errors = egg.errors; if let (Some(ref mut t), Some(row)) = (table.as_mut(), egg.table_row.as_ref()) { t.add_widths(row); } let file_name = self .file_style .for_file(egg.file, self.theme) .with_link_paths() .with_mount_details(self.opts.mounts) .paint() .promote(); let row = Row { tree: tree_params, cells: egg.table_row, name: file_name, }; rows.push(row); if let Some(ref dir) = egg.dir { for file_to_add in dir.files( self.filter.dot_filter, self.git, self.git_ignoring, egg.file.deref_links, egg.file.is_recursive_size(), ) { match file_to_add { Ok(f) => { files.push(f); } Err((path, e)) => { errors.push((e, Some(path))); } } } self.filter.filter_child_files(&mut files); if !files.is_empty() { for xattr in egg.xattrs { rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false))); } for (error, path) in errors { rows.push(self.render_error( &error, TreeParams::new(depth.deeper(), false), path, )); } self.add_files_to_table(table, rows, &files, depth.deeper(), color_scale_info); continue; } } let count = egg.xattrs.len(); for (index, xattr) in egg.xattrs.iter().enumerate() { let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1); let r = self.render_xattr(xattr, params); rows.push(r); } let count = errors.len(); for (index, (error, path)) in errors.into_iter().enumerate() { let params = TreeParams::new(depth.deeper(), index == count - 1); let r = self.render_error(&error, params, path); rows.push(r); } } } pub fn render_header(&self, header: TableRow) -> Row { Row { tree: TreeParams::new(TreeDepth::root(), false), cells: Some(header), name: TextCell::paint_str(self.theme.ui.header, "Name"), } } fn render_error(&self, error: &io::Error, tree: TreeParams, path: Option) -> Row { use crate::output::file_name::Colours; let error_message = if let Some(path) = path { format!("<{}: {}>", path.display(), error) } else { format!("<{error}>") }; // TODO: broken_symlink() doesn’t quite seem like the right name for // the style that’s being used here. Maybe split it in two? let name = TextCell::paint(self.theme.broken_symlink(), error_message); Row { cells: None, name, tree, } } fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row { let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{xattr}")); Row { cells: None, name, tree, } } pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row { Row { cells: Some(cells), name, tree, } } pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec) -> TableIter<'a> { TableIter { tree_trunk: TreeTrunk::default(), total_width: table.widths().total(), table, inner: rows.into_iter(), tree_style: self.theme.ui.punctuation, } } pub fn iterate(&'a self, rows: Vec) -> Iter { Iter { tree_trunk: TreeTrunk::default(), inner: rows.into_iter(), tree_style: self.theme.ui.punctuation, } } } pub struct Row { /// Vector of cells to display. /// /// Most of the rows will be used to display files’ metadata, so this will /// almost always be `Some`, containing a vector of cells. It will only be /// `None` for a row displaying an attribute or error, neither of which /// have cells. pub cells: Option, /// This file’s name, in coloured output. The name is treated separately /// from the other cells, as it never requires padding. pub name: TextCell, /// Information used to determine which symbols to display in a tree. pub tree: TreeParams, } #[rustfmt::skip] pub struct TableIter<'a> { inner: VecIntoIter, table: Table<'a>, total_width: usize, tree_style: Style, tree_trunk: TreeTrunk, } impl<'a> Iterator for TableIter<'a> { type Item = TextCell; fn next(&mut self) -> Option { self.inner.next().map(|row| { let mut cell = if let Some(cells) = row.cells { self.table.render(cells) } else { let mut cell = TextCell::default(); cell.add_spaces(self.total_width); cell }; for tree_part in self.tree_trunk.new_row(row.tree) { cell.push(self.tree_style.paint(tree_part.ascii_art()), 4); } // If any tree characters have been printed, then add an extra // space, which makes the output look much better. if !row.tree.is_at_root() { cell.add_spaces(1); } cell.append(row.name); cell }) } } pub struct Iter { tree_trunk: TreeTrunk, tree_style: Style, inner: VecIntoIter, } impl Iterator for Iter { type Item = TextCell; fn next(&mut self) -> Option { self.inner.next().map(|row| { let mut cell = TextCell::default(); for tree_part in self.tree_trunk.new_row(row.tree) { cell.push(self.tree_style.paint(tree_part.ascii_art()), 4); } // If any tree characters have been printed, then add an extra // space, which makes the output look much better. if !row.tree.is_at_root() { cell.add_spaces(1); } cell.append(row.name); cell }) } } eza-0.18.2/src/output/escape.rs000064400000000000000000000023351046102023000144330ustar 00000000000000use super::file_name::QuoteStyle; use ansiterm::{ANSIString, Style}; pub fn escape( string: String, bits: &mut Vec>, good: Style, bad: Style, quote_style: QuoteStyle, ) { let bits_starting_length = bits.len(); let needs_quotes = string.contains(' ') || string.contains('\''); let quote_bit = good.paint(if string.contains('\'') { "\"" } else { "\'" }); if string .chars() .all(|c| c >= 0x20 as char && c != 0x7f as char) { bits.push(good.paint(string)); } else { for c in string.chars() { // The `escape_default` method on `char` is *almost* what we want here, but // it still escapes non-ASCII UTF-8 characters, which are still printable. // TODO: This allocates way too much, // hence the `all` check above. if c >= 0x20 as char && c != 0x7f as char { bits.push(good.paint(c.to_string())); } else { bits.push(bad.paint(c.escape_default().to_string())); } } } if quote_style != QuoteStyle::NoQuotes && needs_quotes { bits.insert(bits_starting_length, quote_bit.clone()); bits.push(quote_bit); } } eza-0.18.2/src/output/file_name.rs000064400000000000000000000424321046102023000151140ustar 00000000000000use std::fmt::Debug; use std::path::Path; use ansiterm::{ANSIString, Style}; use unicode_width::UnicodeWidthStr; use crate::fs::mounts::MountedFs; use crate::fs::{File, FileTarget}; use crate::output::cell::TextCellContents; use crate::output::escape; use crate::output::icons::{icon_for_file, iconify_style}; use crate::output::render::FiletypeColours; /// Basically a file name factory. #[derive(Debug, Copy, Clone)] pub struct Options { /// Whether to append file class characters to file names. pub classify: Classify, /// Whether to prepend icon characters before file names. pub show_icons: ShowIcons, /// How to display file names with spaces (with or without quotes). pub quote_style: QuoteStyle, /// Whether to make file names hyperlinks. pub embed_hyperlinks: EmbedHyperlinks, /// Whether we are in a console or redirecting the output pub is_a_tty: bool, } impl Options { /// Create a new `FileName` that prints the given file’s name, painting it /// with the remaining arguments. pub fn for_file<'a, 'dir, C>( self, file: &'a File<'dir>, colours: &'a C, ) -> FileName<'a, 'dir, C> { FileName { file, colours, link_style: LinkStyle::JustFilenames, options: self, target: if file.is_link() { Some(file.link_target()) } else { None }, mount_style: MountStyle::JustDirectoryNames, mounted_fs: file.mount_point_info(), } } } /// When displaying a file name, there needs to be some way to handle broken /// links, depending on how long the resulting Cell can be. #[derive(PartialEq, Debug, Copy, Clone)] enum LinkStyle { /// Just display the file names, but colour them differently if they’re /// a broken link or can’t be followed. JustFilenames, /// Display all files in their usual style, but follow each link with an /// arrow pointing to their path, colouring the path differently if it’s /// a broken link, and doing nothing if it can’t be followed. FullLinkPaths, } /// Whether to append file class characters to the file names. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Classify { /// Just display the file names, without any characters. JustFilenames, /// Always add a character after the file name depending on what class of /// file it is. AddFileIndicators, // Like previous, but only when output is going to a terminal, not otherwise. AutomaticAddFileIndicators, } impl Default for Classify { fn default() -> Self { Self::JustFilenames } } /// When displaying a directory name, there needs to be some way to handle /// mount details, depending on how long the resulting Cell can be. #[derive(PartialEq, Debug, Copy, Clone)] enum MountStyle { /// Just display the directory names. JustDirectoryNames, /// Display mount points as directories and include information about /// the filesystem that's mounted there. MountInfo, } /// Whether and how to show icons. #[derive(PartialEq, Debug, Copy, Clone)] pub enum ShowIcons { /// Display icons next to file names, with the given number of spaces between /// the icon and the file name, even when output isn’t going to a terminal. Always(u32), /// Same as Always, but only when output is going to a terminal, not otherwise. Automatic(u32), /// Never display them, even when output is going to a terminal. Never, } /// Whether to embed hyperlinks. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum EmbedHyperlinks { Off, On, } /// Whether or not to wrap file names with spaces in quotes. #[derive(PartialEq, Debug, Copy, Clone)] pub enum QuoteStyle { /// Don't ever quote file names. NoQuotes, /// Use single quotes for file names that contain spaces and no single quotes /// Use double quotes for file names that contain single quotes. QuoteSpaces, } /// A **file name** holds all the information necessary to display the name /// of the given file. This is used in all of the views. pub struct FileName<'a, 'dir, C> { /// A reference to the file that we’re getting the name of. file: &'a File<'dir>, /// The colours used to paint the file name and its surrounding text. colours: &'a C, /// The file that this file points to if it’s a link. target: Option>, // todo: remove? /// How to handle displaying links. link_style: LinkStyle, pub options: Options, /// The filesystem details for a mounted filesystem. mounted_fs: Option<&'a MountedFs>, /// How to handle displaying a mounted filesystem. mount_style: MountStyle, } impl<'a, 'dir, C> FileName<'a, 'dir, C> { /// Sets the flag on this file name to display link targets with an /// arrow followed by their path. pub fn with_link_paths(mut self) -> Self { self.link_style = LinkStyle::FullLinkPaths; self } /// Sets the flag on this file name to display mounted filesystem ///details. pub fn with_mount_details(mut self, enable: bool) -> Self { self.mount_style = if enable { MountStyle::MountInfo } else { MountStyle::JustDirectoryNames }; self } } impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { /// Paints the name of the file using the colours, resulting in a vector /// of coloured cells that can be printed to the terminal. /// /// This method returns some `TextCellContents`, rather than a `TextCell`, /// because for the last cell in a table, it doesn’t need to have its /// width calculated. pub fn paint(&self) -> TextCellContents { let mut bits = Vec::new(); let spaces_count_opt = match self.options.show_icons { ShowIcons::Always(spaces_count) => Some(spaces_count), ShowIcons::Automatic(spaces_count) if self.options.is_a_tty => Some(spaces_count), _ => None, }; let should_add_classify_char = match self.options.classify { Classify::AddFileIndicators => true, Classify::AutomaticAddFileIndicators if self.options.is_a_tty => true, _ => false, }; if let Some(spaces_count) = spaces_count_opt { let style = iconify_style(self.style()); let file_icon = icon_for_file(self.file).to_string(); bits.push(style.paint(file_icon)); bits.push(style.paint(" ".repeat(spaces_count as usize))); } if self.file.parent_dir.is_none() { if let Some(parent) = self.file.path.parent() { self.add_parent_bits(&mut bits, parent); } } if !self.file.name.is_empty() { // The “missing file” colour seems like it should be used here, // but it’s not! In a grid view, where there’s no space to display // link targets, the filename has to have a different style to // indicate this fact. But when showing targets, we can just // colour the path instead (see below), and leave the broken // link’s filename as the link colour. for bit in self.escaped_file_name() { bits.push(bit); } } if let (LinkStyle::FullLinkPaths, Some(target)) = (self.link_style, self.target.as_ref()) { match target { FileTarget::Ok(target) => { bits.push(Style::default().paint(" ")); bits.push(self.colours.normal_arrow().paint("->")); bits.push(Style::default().paint(" ")); if let Some(parent) = target.path.parent() { self.add_parent_bits(&mut bits, parent); } if !target.name.is_empty() { let target_options = Options { classify: Classify::JustFilenames, quote_style: QuoteStyle::QuoteSpaces, show_icons: ShowIcons::Never, embed_hyperlinks: EmbedHyperlinks::Off, is_a_tty: self.options.is_a_tty, }; let target_name = FileName { file: target, colours: self.colours, target: None, link_style: LinkStyle::FullLinkPaths, options: target_options, mounted_fs: None, mount_style: MountStyle::JustDirectoryNames, }; for bit in target_name.escaped_file_name() { bits.push(bit); } if should_add_classify_char { if let Some(class) = self.classify_char(target) { bits.push(Style::default().paint(class)); } } } } FileTarget::Broken(broken_path) => { bits.push(Style::default().paint(" ")); bits.push(self.colours.broken_symlink().paint("->")); bits.push(Style::default().paint(" ")); escape( broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.broken_control_char(), self.options.quote_style, ); } FileTarget::Err(_) => { // Do nothing — the error gets displayed on the next line } } } else if should_add_classify_char { if let Some(class) = self.classify_char(self.file) { bits.push(Style::default().paint(class)); } } if let (MountStyle::MountInfo, Some(mount_details)) = (self.mount_style, self.mounted_fs.as_ref()) { // This is a filesystem mounted on the directory, output its details bits.push(Style::default().paint(" [")); bits.push(Style::default().paint(mount_details.source.clone())); bits.push(Style::default().paint(" (")); bits.push(Style::default().paint(mount_details.fstype.clone())); bits.push(Style::default().paint(")]")); } bits.into() } /// Adds the bits of the parent path to the given bits vector. /// The path gets its characters escaped based on the colours. fn add_parent_bits(&self, bits: &mut Vec>, parent: &Path) { let coconut = parent.components().count(); if coconut == 1 && parent.has_root() { bits.push( self.colours .symlink_path() .paint(std::path::MAIN_SEPARATOR.to_string()), ); } else if coconut >= 1 { escape( parent.to_string_lossy().to_string(), bits, self.colours.symlink_path(), self.colours.control_char(), self.options.quote_style, ); bits.push( self.colours .symlink_path() .paint(std::path::MAIN_SEPARATOR.to_string()), ); } } /// The character to be displayed after a file when classifying is on, if /// the file’s type has one associated with it. #[cfg(unix)] pub(crate) fn classify_char(&self, file: &File<'_>) -> Option<&'static str> { if file.is_executable_file() { Some("*") } else if file.is_directory() { Some("/") } else if file.is_pipe() { Some("|") } else if file.is_link() { Some("@") } else if file.is_socket() { Some("=") } else { None } } #[cfg(windows)] pub(crate) fn classify_char(&self, file: &File<'_>) -> Option<&'static str> { if file.is_directory() { Some("/") } else if file.is_link() { Some("@") } else { None } } /// Returns at least one ANSI-highlighted string representing this file’s /// name using the given set of colours. /// /// If --hyperlink flag is provided, it will escape the filename accordingly. /// /// Ordinarily, this will be just one string: the file’s complete name, /// coloured according to its file type. If the name contains control /// characters such as newlines or escapes, though, we can’t just print them /// to the screen directly, because then there’ll be newlines in weird places. /// /// So in that situation, those characters will be escaped and highlighted in /// a different colour. fn escaped_file_name<'unused>(&self) -> Vec> { use percent_encoding::{utf8_percent_encode, CONTROLS}; const HYPERLINK_START: &str = "\x1B]8;;"; const HYPERLINK_END: &str = "\x1B\x5C"; let file_style = self.style(); let mut bits = Vec::new(); let mut display_hyperlink = false; if self.options.embed_hyperlinks == EmbedHyperlinks::On { if let Some(abs_path) = self .file .absolute_path() .and_then(|p| p.as_os_str().to_str()) { let abs_path = utf8_percent_encode(abs_path, CONTROLS).to_string(); // On Windows, `std::fs::canonicalize` adds the Win32 File prefix, which we need to remove #[cfg(target_os = "windows")] let abs_path = abs_path.strip_prefix("\\\\?\\").unwrap_or(&abs_path); bits.push(ANSIString::from(format!( "{HYPERLINK_START}file://{abs_path}{HYPERLINK_END}" ))); display_hyperlink = true; } } escape( self.file.name.clone(), &mut bits, file_style, self.colours.control_char(), self.options.quote_style, ); if display_hyperlink { bits.push(ANSIString::from(format!( "{HYPERLINK_START}{HYPERLINK_END}" ))); } bits } /// Figures out which colour to paint the filename part of the output, /// depending on which “type” of file it appears to be — either from the /// class on the filesystem or from its name. (Or the broken link colour, /// if there’s nowhere else for that fact to be shown.) pub fn style(&self) -> Style { if let LinkStyle::JustFilenames = self.link_style { if let Some(ref target) = self.target { if target.is_broken() { return self.colours.broken_symlink(); } } } #[rustfmt::skip] return match self.file { f if f.is_mount_point() => self.colours.mount_point(), f if f.is_directory() => self.colours.directory(), #[cfg(unix)] f if f.is_executable_file() => self.colours.executable_file(), f if f.is_link() => self.colours.symlink(), #[cfg(unix)] f if f.is_pipe() => self.colours.pipe(), #[cfg(unix)] f if f.is_block_device() => self.colours.block_device(), #[cfg(unix)] f if f.is_char_device() => self.colours.char_device(), #[cfg(unix)] f if f.is_socket() => self.colours.socket(), f if ! f.is_file() => self.colours.special(), _ => self.colours.colour_file(self.file), }; } /// For grid's use, to cover the case of hyperlink escape sequences pub fn bare_utf8_width(&self) -> usize { UnicodeWidthStr::width(self.file.name.as_str()) } } /// The set of colours that are needed to paint a file name. pub trait Colours: FiletypeColours { /// The style to paint the path of a symlink’s target, up to but not /// including the file’s name. fn symlink_path(&self) -> Style; /// The style to paint the arrow between a link and its target. fn normal_arrow(&self) -> Style; /// The style to paint the filenames of broken links in views that don’t /// show link targets, and the style to paint the *arrow* between the link /// and its target in views that *do* show link targets. fn broken_symlink(&self) -> Style; /// The style to paint the entire filename of a broken link. fn broken_filename(&self) -> Style; /// The style to paint a non-displayable control character in a filename. fn control_char(&self) -> Style; /// The style to paint a non-displayable control character in a filename, /// when the filename is being displayed as a broken link target. fn broken_control_char(&self) -> Style; /// The style to paint a file that has its executable bit set. fn executable_file(&self) -> Style; /// The style to paint a directory that has a filesystem mounted on it. fn mount_point(&self) -> Style; fn colour_file(&self, file: &File<'_>) -> Style; } eza-0.18.2/src/output/grid.rs000064400000000000000000000076151046102023000141260ustar 00000000000000use std::io::{self, Write}; use term_grid as tg; use crate::fs::filter::FileFilter; use crate::fs::File; use crate::output::file_name::{Classify, Options as FileStyle}; use crate::output::file_name::{EmbedHyperlinks, ShowIcons}; use crate::theme::Theme; use super::file_name::QuoteStyle; #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Options { pub across: bool, } impl Options { pub fn direction(self) -> tg::Direction { if self.across { tg::Direction::LeftToRight } else { tg::Direction::TopToBottom } } } pub struct Render<'a> { pub files: Vec>, pub theme: &'a Theme, pub file_style: &'a FileStyle, pub opts: &'a Options, pub console_width: usize, pub filter: &'a FileFilter, } impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { let mut grid = tg::Grid::new(tg::GridOptions { direction: self.opts.direction(), filling: tg::Filling::Spaces(2), }); grid.reserve(self.files.len()); self.filter.sort_files(&mut self.files); for file in &self.files { let filename = self.file_style.for_file(file, self.theme); // Calculate classification width let classification_width = if let Classify::AddFileIndicators = filename.options.classify { match filename.classify_char(file) { Some(s) => s.len(), None => 0, } } else { 0 }; let space_filename_offset = match self.file_style.quote_style { QuoteStyle::QuoteSpaces if file.name.contains(' ') => 2, QuoteStyle::NoQuotes => 0, QuoteStyle::QuoteSpaces => 0, // Default case }; let contents = filename.paint(); let width = match ( filename.options.embed_hyperlinks, filename.options.show_icons, ) { ( EmbedHyperlinks::On, ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing), ) => { filename.bare_utf8_width() + classification_width + 1 + (spacing as usize) + space_filename_offset } (EmbedHyperlinks::On, ShowIcons::Never) => { filename.bare_utf8_width() + classification_width + space_filename_offset } ( EmbedHyperlinks::Off, ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing), ) => { filename.bare_utf8_width() + classification_width + 1 + (spacing as usize) + space_filename_offset } (EmbedHyperlinks::Off, _) => *contents.width(), }; grid.add(tg::Cell { contents: contents.strings().to_string(), // with hyperlink escape sequences, // the actual *contents.width() is larger than actually needed, so we take only the filename width, }); } if let Some(display) = grid.fit_into_width(self.console_width) { write!(w, "{display}") } else { // File names too long for a grid - drop down to just listing them! // This isn’t *quite* the same as the lines view, which also // displays full link paths. for file in &self.files { let name_cell = self.file_style.for_file(file, self.theme).paint(); writeln!(w, "{}", name_cell.strings())?; } Ok(()) } } } eza-0.18.2/src/output/grid_details.rs000064400000000000000000000310401046102023000156200ustar 00000000000000//! The grid-details view lists several details views side-by-side. use std::io::{self, Write}; use ansiterm::ANSIStrings; use term_grid as grid; use crate::fs::feature::git::GitCache; use crate::fs::filter::FileFilter; use crate::fs::{Dir, File}; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::color_scale::ColorScaleInformation; use crate::output::details::{ Options as DetailsOptions, Render as DetailsRender, Row as DetailsRow, }; use crate::output::file_name::Options as FileStyle; use crate::output::file_name::{EmbedHyperlinks, ShowIcons}; use crate::output::grid::Options as GridOptions; use crate::output::table::{Options as TableOptions, Row as TableRow, Table}; use crate::output::tree::{TreeDepth, TreeParams}; use crate::theme::Theme; use super::file_name::QuoteStyle; #[derive(PartialEq, Eq, Debug)] pub struct Options { pub grid: GridOptions, pub details: DetailsOptions, pub row_threshold: RowThreshold, } impl Options { pub fn to_details_options(&self) -> &DetailsOptions { &self.details } } /// The grid-details view can be configured to revert to just a details view /// (with one column) if it wouldn’t produce enough rows of output. /// /// Doing this makes the resulting output look a bit better: when listing a /// small directory of four files in four columns, the files just look spaced /// out and it’s harder to see what’s going on. So it can be enabled just for /// larger directory listings. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum RowThreshold { /// Only use grid-details view if it would result in at least this many /// rows of output. MinimumRows(usize), /// Use the grid-details view no matter what. AlwaysGrid, } pub struct Render<'a> { /// The directory that’s being rendered here. /// We need this to know which columns to put in the output. pub dir: Option<&'a Dir>, /// The files that have been read from the directory. They should all /// hold a reference to it. pub files: Vec>, /// How to colour various pieces of text. pub theme: &'a Theme, /// How to format filenames. pub file_style: &'a FileStyle, /// The grid part of the grid-details view. pub grid: &'a GridOptions, /// The details part of the grid-details view. pub details: &'a DetailsOptions, /// How to filter files after listing a directory. The files in this /// render will already have been filtered and sorted, but any directories /// that we recurse into will have to have this applied. pub filter: &'a FileFilter, /// The minimum number of rows that there need to be before grid-details /// mode is activated. pub row_threshold: RowThreshold, /// Whether we are skipping Git-ignored files. pub git_ignoring: bool, pub git: Option<&'a GitCache>, pub console_width: usize, pub git_repos: bool, } impl<'a> Render<'a> { /// Create a temporary Details render that gets used for the columns of /// the grid-details render that’s being generated. /// /// This includes an empty files vector because the files get added to /// the table in *this* file, not in details: we only want to insert every /// *n* files into each column’s table, not all of them. fn details_for_column(&self) -> DetailsRender<'a> { #[rustfmt::skip] return DetailsRender { dir: self.dir, files: Vec::new(), theme: self.theme, file_style: self.file_style, opts: self.details, recurse: None, filter: self.filter, git_ignoring: self.git_ignoring, git: self.git, git_repos: self.git_repos, }; } /// Create a Details render for when this grid-details render doesn’t fit /// in the terminal (or something has gone wrong) and we have given up, or /// when the user asked for a grid-details view but the terminal width is /// not available, so we downgrade. pub fn give_up(self) -> DetailsRender<'a> { #[rustfmt::skip] return DetailsRender { dir: self.dir, files: self.files, theme: self.theme, file_style: self.file_style, opts: self.details, recurse: None, filter: self.filter, git_ignoring: self.git_ignoring, git: self.git, git_repos: self.git_repos, }; } // This doesn’t take an IgnoreCache even though the details one does // because grid-details has no tree view. pub fn render(mut self, w: &mut W) -> io::Result<()> { if let Some((grid, width)) = self.find_fitting_grid() { write!(w, "{}", grid.fit_into_columns(width)) } else { self.give_up().render(w) } } pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> { let options = self .details .table .as_ref() .expect("Details table options not given!"); let drender = self.details_for_column(); let color_scale_info = ColorScaleInformation::from_color_scale( self.details.color_scale, &self.files, self.filter.dot_filter, self.git, self.git_ignoring, None, ); let (first_table, _) = self.make_table(options, &drender); let rows = self .files .iter() .map(|file| { first_table.row_for_file(file, drender.show_xattr_hint(file), color_scale_info) }) .collect::>(); let file_names = self .files .iter() .map(|file| { let filename = self.file_style.for_file(file, self.theme); let contents = filename.paint(); let space_filename_offset = match self.file_style.quote_style { QuoteStyle::QuoteSpaces if file.name.contains(' ') => 2, QuoteStyle::NoQuotes => 0, QuoteStyle::QuoteSpaces => 0, // Default case }; let width = match ( filename.options.embed_hyperlinks, filename.options.show_icons, ) { (EmbedHyperlinks::On, ShowIcons::Automatic(spacing)) => { filename.bare_utf8_width() + 1 + (spacing as usize) + space_filename_offset } (EmbedHyperlinks::On, ShowIcons::Always(spacing)) => { filename.bare_utf8_width() + 1 + (spacing as usize) + space_filename_offset } (EmbedHyperlinks::On, ShowIcons::Never) => { filename.bare_utf8_width() + space_filename_offset } (EmbedHyperlinks::Off, _) => *contents.width(), }; TextCell { contents, // with hyperlink escape sequences, // the actual *contents.width() is larger than actually needed, so we take only the filename width: DisplayWidth::from(width), } }) .collect::>(); let mut last_working_grid = self.make_grid(1, options, &file_names, rows.clone(), &drender); if file_names.len() == 1 { return Some((last_working_grid, 1)); } // If we can’t fit everything in a grid 100 columns wide, then // something has gone seriously awry for column_count in 2..100 { let grid = self.make_grid(column_count, options, &file_names, rows.clone(), &drender); let the_grid_fits = { let d = grid.fit_into_columns(column_count); d.width() <= self.console_width }; if the_grid_fits { last_working_grid = grid; } if !the_grid_fits || column_count == file_names.len() { let last_column_count = if the_grid_fits { column_count } else { column_count - 1 }; // If we’ve figured out how many columns can fit in the user’s terminal, // and it turns out there aren’t enough rows to make it worthwhile // (according to EZA_GRID_ROWS), then just resort to the lines view. if let RowThreshold::MinimumRows(thresh) = self.row_threshold { if last_working_grid .fit_into_columns(last_column_count) .row_count() < thresh { return None; } } return Some((last_working_grid, last_column_count)); } } None } fn make_table( &mut self, options: &'a TableOptions, drender: &DetailsRender<'_>, ) -> (Table<'a>, Vec) { match (self.git, self.dir) { (Some(g), Some(d)) => { if !g.has_anything_for(&d.path) { self.git = None; } } (Some(g), None) => { if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None; } } (None, _) => { /* Keep Git how it is */ } } let mut table = Table::new(options, self.git, self.theme, self.git_repos); let mut rows = Vec::new(); if self.details.header { let row = table.header_row(); table.add_widths(&row); rows.push(drender.render_header(row)); } (table, rows) } fn make_grid( &mut self, column_count: usize, options: &'a TableOptions, file_names: &[TextCell], rows: Vec, drender: &DetailsRender<'_>, ) -> grid::Grid { let mut tables = Vec::new(); for _ in 0..column_count { tables.push(self.make_table(options, drender)); } let mut num_cells = rows.len(); if self.details.header { num_cells += column_count; } let original_height = divide_rounding_up(rows.len(), column_count); let height = divide_rounding_up(num_cells, column_count); for (i, (file_name, row)) in file_names.iter().zip(rows).enumerate() { let index = if self.grid.across { i % column_count } else { i / original_height }; let (ref mut table, ref mut rows) = tables[index]; table.add_widths(&row); let details_row = drender.render_file( row, file_name.clone(), TreeParams::new(TreeDepth::root(), false), ); rows.push(details_row); } let columns = tables .into_iter() .map(|(table, details_rows)| { drender .iterate_with_table(table, details_rows) .collect::>() }) .collect::>(); let direction = if self.grid.across { grid::Direction::LeftToRight } else { grid::Direction::TopToBottom }; let filling = grid::Filling::Spaces(4); let mut grid = grid::Grid::new(grid::GridOptions { direction, filling }); if self.grid.across { for row in 0..height { for column in &columns { if row < column.len() { let cell = grid::Cell { contents: ANSIStrings(&column[row].contents).to_string(), width: *column[row].width, }; grid.add(cell); } } } } else { for column in &columns { for cell in column { let cell = grid::Cell { contents: ANSIStrings(&cell.contents).to_string(), width: *cell.width, }; grid.add(cell); } } } grid } } fn divide_rounding_up(a: usize, b: usize) -> usize { let mut result = a / b; if a % b != 0 { result += 1; } result } eza-0.18.2/src/output/icons.rs000064400000000000000000001247501046102023000143140ustar 00000000000000use ansiterm::Style; use phf::{phf_map, Map}; use crate::fs::File; #[non_exhaustive] struct Icons; #[rustfmt::skip] impl Icons { const AUDIO: char = '\u{f001}'; //  const BINARY: char = '\u{eae8}'; //  const BOOK: char = '\u{e28b}'; //  const CALENDAR: char = '\u{eab0}'; //  const CLOCK: char = '\u{f43a}'; //  const COMPRESSED: char = '\u{f410}'; //  const CONFIG: char = '\u{e615}'; //  const CSS3: char = '\u{e749}'; //  const DATABASE: char = '\u{f1c0}'; //  const DIFF: char = '\u{f440}'; //  const DISK_IMAGE: char = '\u{e271}'; //  const DOCKER: char = '\u{e650}'; //  const DOCUMENT: char = '\u{f1c2}'; //  const DOWNLOAD: char = '\u{f01da}'; // 󰇚 const EMACS: char = '\u{e632}'; //  const ESLINT: char = '\u{e655}'; //  const FILE: char = '\u{f15b}'; //  const FILE_OUTLINE: char = '\u{f016}'; //  const FOLDER: char = '\u{e5ff}'; //  const FOLDER_CONFIG: char = '\u{e5fc}'; //  const FOLDER_GIT: char = '\u{e5fb}'; //  const FOLDER_GITHUB: char = '\u{e5fd}'; //  const FOLDER_HIDDEN: char = '\u{f179e}'; // 󱞞 const FOLDER_KEY: char = '\u{f08ac}'; // 󰢬 const FOLDER_NPM: char = '\u{e5fa}'; //  const FOLDER_OPEN: char = '\u{f115}'; //  const FONT: char = '\u{f031}'; //  const GIST_SECRET: char = '\u{eafa}'; //  const GIT: char = '\u{f1d3}'; //  const GRADLE: char = '\u{e660}'; //  const GRUNT: char = '\u{e611}'; //  const GULP: char = '\u{e610}'; //  const HTML5: char = '\u{f13b}'; //  const IMAGE: char = '\u{f1c5}'; //  const INTELLIJ: char = '\u{e7b5}'; //  const JSON: char = '\u{e60b}'; //  const KEY: char = '\u{eb11}'; //  const KEYPASS: char = '\u{f23e}'; //  const LANG_ASSEMBLY: char = '\u{e637}'; //  const LANG_C: char = '\u{e61e}'; //  const LANG_CPP: char = '\u{e61d}'; //  const LANG_CSHARP: char = '\u{f031b}'; // 󰌛 const LANG_D: char = '\u{e7af}'; //  const LANG_ELIXIR: char = '\u{e62d}'; //  const LANG_FORTRAN: char = '\u{f121a}'; // 󱈚 const LANG_FSHARP: char = '\u{e7a7}'; //  const LANG_GO: char = '\u{e65e}'; //  const LANG_GROOVY: char = '\u{e775}'; //  const LANG_HASKELL: char = '\u{e777}'; //  const LANG_JAVA: char = '\u{e256}'; //  const LANG_JAVASCRIPT: char = '\u{e74e}'; //  const LANG_KOTLIN: char = '\u{e634}'; //  const LANG_OCAML: char = '\u{e67a}'; //  const LANG_PERL: char = '\u{e67e}'; //  const LANG_PHP: char = '\u{e73d}'; //  const LANG_PYTHON: char = '\u{e606}'; //  const LANG_R: char = '\u{e68a}'; //  const LANG_RUBY: char = '\u{e21e}'; //  const LANG_RUBYRAILS: char = '\u{e73b}'; //  const LANG_RUST: char = '\u{e68b}'; //  const LANG_SASS: char = '\u{e603}'; //  const LANG_STYLUS: char = '\u{e600}'; //  const LANG_TEX: char = '\u{e69b}'; //  const LANG_TYPESCRIPT: char = '\u{e628}'; //  const LANG_V: char = '\u{e6ac}'; //  const LIBRARY: char = '\u{eb9c}'; //  const LICENSE: char = '\u{f02d}'; //  const LOCK: char = '\u{f023}'; //  const MAKE: char = '\u{e673}'; //  const MARKDOWN: char = '\u{f48a}'; //  const MUSTACHE: char = '\u{e60f}'; //  const NODEJS: char = '\u{e718}'; //  const NPM: char = '\u{e71e}'; //  const OS_ANDROID: char = '\u{e70e}'; //  const OS_APPLE: char = '\u{f179}'; //  const OS_LINUX: char = '\u{f17c}'; //  const OS_WINDOWS: char = '\u{f17a}'; //  const OS_WINDOWS_CMD: char = '\u{ebc4}'; //  const PLAYLIST: char = '\u{f0cb9}'; // 󰲹 const POWERSHELL: char = '\u{ebc7}'; //  const PRIVATE_KEY: char = '\u{f0306}'; // 󰌆 const PUBLIC_KEY: char = '\u{f0dd6}'; // 󰷖 const RAZOR: char = '\u{f1fa}'; //  const REACT: char = '\u{e7ba}'; //  const README: char = '\u{f00ba}'; // 󰂺 const SHEET: char = '\u{f1c3}'; //  const SHELL: char = '\u{f1183}'; // 󱆃 const SHELL_CMD: char = '\u{f489}'; //  const SHIELD_CHECK: char = '\u{f0565}'; // 󰕥 const SHIELD_KEY: char = '\u{f0bc4}'; // 󰯄 const SHIELD_LOCK: char = '\u{f099d}'; // 󰦝 const SIGNED_FILE: char = '\u{f19c3}'; // 󱧃 const SLIDE: char = '\u{f1c4}'; //  const SUBLIME: char = '\u{e7aa}'; //  const SUBTITLE: char = '\u{f0a16}'; // 󰨖 const TERRAFORM: char = '\u{f1062}'; // 󱁢 const TEXT: char = '\u{f15c}'; //  const TYPST: char = '\u{1D42D}'; // 𝐭 const UNITY: char = '\u{e721}'; //  const VECTOR: char = '\u{f0559}'; // 󰕙 const VIDEO: char = '\u{f03d}'; //  const VIM: char = '\u{e7c5}'; //  const WRENCH: char = '\u{f0ad}'; //  const XML: char = '\u{f05c0}'; // 󰗀 const YAML: char = '\u{e6a8}'; //  const YARN: char = '\u{e6a7}'; //  } /// Mapping from full filenames to directory icon. This mapping should contain /// all the directories that have a custom icon. const DIRECTORY_ICONS: Map<&'static str, char> = phf_map! { ".config" => Icons::FOLDER_CONFIG, //  ".git" => Icons::FOLDER_GIT, //  ".github" => Icons::FOLDER_GITHUB, //  ".npm" => Icons::FOLDER_NPM, //  ".ssh" => Icons::FOLDER_KEY, // 󰢬 ".Trash" => '\u{f1f8}', //  "config" => Icons::FOLDER_CONFIG, //  "Contacts" => '\u{f024c}', // 󰉌 "cron.d" => Icons::FOLDER_CONFIG, //  "cron.daily" => Icons::FOLDER_CONFIG, //  "cron.hourly" => Icons::FOLDER_CONFIG, //  "cron.monthly" => Icons::FOLDER_CONFIG, //  "cron.weekly" => Icons::FOLDER_CONFIG, //  "Desktop" => '\u{f108}', //  "Downloads" => '\u{f024d}', // 󰉍 "etc" => Icons::FOLDER_CONFIG, //  "Favorites" => '\u{f069d}', // 󰚝 "hidden" => Icons::FOLDER_HIDDEN, // 󱞞 "home" => '\u{f10b5}', // 󱂵 "include" => Icons::FOLDER_CONFIG, //  "Mail" => '\u{f01f0}', // 󰇰 "Movies" => '\u{f0fce}', // 󰿎 "Music" => '\u{f1359}', // 󱍙 "node_modules" => Icons::FOLDER_NPM, //  "npm_cache" => Icons::FOLDER_NPM, //  "pam.d" => Icons::FOLDER_KEY, // 󰢬 "Pictures" => '\u{f024f}', // 󰉏 "ssh" => Icons::FOLDER_KEY, // 󰢬 "sudoers.d" => Icons::FOLDER_KEY, // 󰢬 "Videos" => '\u{f03d}', //  "xbps.d" => Icons::FOLDER_CONFIG, //  "xorg.conf.d" => Icons::FOLDER_CONFIG, //  }; /// Mapping from full filenames to file icon. This mapping should also contain /// all the "dot" files that have a custom icon. const FILENAME_ICONS: Map<&'static str, char> = phf_map! { ".atom" => '\u{e764}', //  ".bashrc" => Icons::SHELL, // 󱆃 ".bash_history" => Icons::SHELL, // 󱆃 ".bash_logout" => Icons::SHELL, // 󱆃 ".bash_profile" => Icons::SHELL, // 󱆃 ".CFUserTextEncoding" => Icons::OS_APPLE, //  ".clang-format" => Icons::CONFIG, //  ".cshrc" => Icons::SHELL, // 󱆃 ".DS_Store" => Icons::OS_APPLE, //  ".emacs" => Icons::EMACS, //  ".eslintrc.cjs" => Icons::ESLINT, //  ".eslintrc.js" => Icons::ESLINT, //  ".eslintrc.json" => Icons::ESLINT, //  ".eslintrc.yaml" => Icons::ESLINT, //  ".eslintrc.yml" => Icons::ESLINT, //  ".gitattributes" => Icons::GIT, //  ".gitconfig" => Icons::GIT, //  ".gitignore" => Icons::GIT, //  ".gitignore_global" => Icons::GIT, //  ".gitlab-ci.yml" => '\u{f296}', //  ".gitmodules" => Icons::GIT, //  ".htaccess" => Icons::CONFIG, //  ".htpasswd" => Icons::CONFIG, //  ".idea" => Icons::INTELLIJ, //  ".ideavimrc" => Icons::VIM, //  ".inputrc" => Icons::CONFIG, //  ".kshrc" => Icons::SHELL, // 󱆃 ".login" => Icons::SHELL, // 󱆃 ".logout" => Icons::SHELL, // 󱆃 ".mailmap" => Icons::GIT, //  ".node_repl_history" => Icons::NODEJS, //  ".npmignore" => Icons::NPM, //  ".npmrc" => Icons::NPM, //  ".profile" => Icons::SHELL, // 󱆃 ".python_history" => Icons::LANG_PYTHON, //  ".rustfmt.toml" => Icons::LANG_RUST, //  ".rvm" => Icons::LANG_RUBY, //  ".rvmrc" => Icons::LANG_RUBY, //  ".tcshrc" => Icons::SHELL, // 󱆃 ".viminfo" => Icons::VIM, //  ".vimrc" => Icons::VIM, //  ".Xauthority" => Icons::CONFIG, //  ".xinitrc" => Icons::CONFIG, //  ".Xresources" => Icons::CONFIG, //  ".yarnrc" => Icons::YARN, //  ".zlogin" => Icons::SHELL, // 󱆃 ".zlogout" => Icons::SHELL, // 󱆃 ".zprofile" => Icons::SHELL, // 󱆃 ".zshenv" => Icons::SHELL, // 󱆃 ".zshrc" => Icons::SHELL, // 󱆃 ".zsh_history" => Icons::SHELL, // 󱆃 ".zsh_sessions" => Icons::SHELL, // 󱆃 "._DS_Store" => Icons::OS_APPLE, //  "a.out" => Icons::SHELL_CMD, //  "authorized_keys" => '\u{f08c0}', // 󰣀 "bashrc" => Icons::SHELL, // 󱆃 "bspwmrc" => Icons::CONFIG, //  "build.gradle.kts" => Icons::GRADLE, //  "Cargo.lock" => Icons::LANG_RUST, //  "Cargo.toml" => Icons::LANG_RUST, //  "CMakeLists.txt" => Icons::MAKE, //  "composer.json" => Icons::LANG_PHP, //  "composer.lock" => Icons::LANG_PHP, //  "config" => Icons::CONFIG, //  "config.status" => Icons::CONFIG, //  "configure" => Icons::WRENCH, //  "configure.ac" => Icons::CONFIG, //  "configure.in" => Icons::CONFIG, //  "constraints.txt" => Icons::LANG_PYTHON, //  "COPYING" => Icons::LICENSE, //  "COPYRIGHT" => Icons::LICENSE, //  "crontab" => Icons::CONFIG, //  "crypttab" => Icons::CONFIG, //  "csh.cshrc" => Icons::SHELL, // 󱆃 "csh.login" => Icons::SHELL, // 󱆃 "csh.logout" => Icons::SHELL, // 󱆃 "docker-compose.yml" => Icons::DOCKER, //  "Dockerfile" => Icons::DOCKER, //  "dune" => Icons::LANG_OCAML, //  "dune-project" => Icons::WRENCH, //  "Earthfile" => '\u{f0ac}', //  "environment" => Icons::CONFIG, //  "GNUmakefile" => Icons::MAKE, //  "go.mod" => Icons::LANG_GO, //  "go.sum" => Icons::LANG_GO, //  "go.work" => Icons::LANG_GO, //  "gradle" => Icons::GRADLE, //  "gradle.properties" => Icons::GRADLE, //  "gradlew" => Icons::GRADLE, //  "gradlew.bat" => Icons::GRADLE, //  "group" => Icons::LOCK, //  "gruntfile.coffee" => Icons::GRUNT, //  "gruntfile.js" => Icons::GRUNT, //  "gruntfile.ls" => Icons::GRUNT, //  "gshadow" => Icons::LOCK, //  "gulpfile.coffee" => Icons::GULP, //  "gulpfile.js" => Icons::GULP, //  "gulpfile.ls" => Icons::GULP, //  "heroku.yml" => '\u{e77b}', //  "hostname" => Icons::CONFIG, //  "id_dsa" => Icons::PRIVATE_KEY, // 󰌆 "id_ecdsa" => Icons::PRIVATE_KEY, // 󰌆 "id_ecdsa_sk" => Icons::PRIVATE_KEY, // 󰌆 "id_ed25519" => Icons::PRIVATE_KEY, // 󰌆 "id_ed25519_sk" => Icons::PRIVATE_KEY, // 󰌆 "id_rsa" => Icons::PRIVATE_KEY, // 󰌆 "inputrc" => Icons::CONFIG, //  "Jenkinsfile" => '\u{e66e}', //  "jsconfig.json" => Icons::LANG_JAVASCRIPT,//  "Justfile" => Icons::WRENCH, //  "known_hosts" => '\u{f08c0}', // 󰣀 "LICENCE" => Icons::LICENSE, //  "LICENCE.md" => Icons::LICENSE, //  "LICENCE.txt" => Icons::LICENSE, //  "LICENSE" => Icons::LICENSE, //  "LICENSE-APACHE" => Icons::LICENSE, //  "LICENSE-MIT" => Icons::LICENSE, //  "LICENSE.md" => Icons::LICENSE, //  "LICENSE.txt" => Icons::LICENSE, //  "localized" => Icons::OS_APPLE, //  "localtime" => Icons::CLOCK, //  "Makefile" => Icons::MAKE, //  "makefile" => Icons::MAKE, //  "Makefile.ac" => Icons::MAKE, //  "Makefile.am" => Icons::MAKE, //  "Makefile.in" => Icons::MAKE, //  "MANIFEST" => Icons::LANG_PYTHON, //  "MANIFEST.in" => Icons::LANG_PYTHON, //  "npm-shrinkwrap.json" => Icons::NPM, //  "npmrc" => Icons::NPM, //  "package-lock.json" => Icons::NPM, //  "package.json" => Icons::NPM, //  "passwd" => Icons::LOCK, //  "php.ini" => Icons::LANG_PHP, //  "PKGBUILD" => '\u{f303}', //  "pom.xml" => '\u{e674}', //  "Procfile" => '\u{e77b}', //  "profile" => Icons::SHELL, // 󱆃 "pyproject.toml" => Icons::LANG_PYTHON, //  "Rakefile" => Icons::LANG_RUBY, //  "README" => Icons::README, // 󰂺 "release.toml" => Icons::LANG_RUST, //  "requirements.txt" => Icons::LANG_PYTHON, //  "robots.txt" => '\u{f06a9}', // 󰚩 "rubydoc" => Icons::LANG_RUBYRAILS, //  "rvmrc" => Icons::LANG_RUBY, //  "settings.gradle.kts" => Icons::GRADLE, //  "shadow" => Icons::LOCK, //  "shells" => Icons::CONFIG, //  "sudoers" => Icons::LOCK, //  "timezone" => Icons::CLOCK, //  "tsconfig.json" => Icons::LANG_TYPESCRIPT,//  "Vagrantfile" => '\u{2371}', // ⍱ "webpack.config.js" => '\u{f072b}', // 󰜫 "yarn.lock" => Icons::YARN, //  "zlogin" => Icons::SHELL, // 󱆃 "zlogout" => Icons::SHELL, // 󱆃 "zprofile" => Icons::SHELL, // 󱆃 "zshenv" => Icons::SHELL, // 󱆃 "zshrc" => Icons::SHELL, // 󱆃 }; /// Mapping from lowercase file extension to icons. If an image, video, or audio extension is add /// also update the extension filetype map. const EXTENSION_ICONS: Map<&'static str, char> = phf_map! { "7z" => Icons::COMPRESSED, //  "a" => Icons::OS_LINUX, //  "acc" => Icons::AUDIO, //  "acf" => '\u{f1b6}', //  "ai" => '\u{e7b4}', //  "aif" => Icons::AUDIO, //  "aifc" => Icons::AUDIO, //  "aiff" => Icons::AUDIO, //  "alac" => Icons::AUDIO, //  "android" => Icons::OS_ANDROID, //  "ape" => Icons::AUDIO, //  "apk" => Icons::OS_ANDROID, //  "apple" => Icons::OS_APPLE, //  "ar" => Icons::COMPRESSED, //  "arj" => Icons::COMPRESSED, //  "arw" => Icons::IMAGE, //  "asc" => Icons::SHIELD_LOCK, // 󰦝 "asm" => Icons::LANG_ASSEMBLY, //  "asp" => '\u{f121}', //  "avi" => Icons::VIDEO, //  "avif" => Icons::IMAGE, //  "avro" => Icons::JSON, //  "awk" => Icons::SHELL_CMD, //  "bash" => Icons::SHELL_CMD, //  "bat" => Icons::OS_WINDOWS_CMD, //  "bats" => Icons::SHELL_CMD, //  "bdf" => Icons::FONT, //  "bib" => Icons::LANG_TEX, //  "bin" => Icons::BINARY, //  "bmp" => Icons::IMAGE, //  "br" => Icons::COMPRESSED, //  "bst" => Icons::LANG_TEX, //  "bundle" => Icons::OS_APPLE, //  "bz" => Icons::COMPRESSED, //  "bz2" => Icons::COMPRESSED, //  "bz3" => Icons::COMPRESSED, //  "c" => Icons::LANG_C, //  "c++" => Icons::LANG_CPP, //  "cab" => Icons::OS_WINDOWS, //  "cbr" => Icons::IMAGE, //  "cbz" => Icons::IMAGE, //  "cc" => Icons::LANG_CPP, //  "cert" => Icons::GIST_SECRET, //  "cfg" => Icons::CONFIG, //  "cjs" => Icons::LANG_JAVASCRIPT, //  "class" => Icons::LANG_JAVA, //  "clj" => '\u{e768}', //  "cljs" => '\u{e76a}', //  "cls" => Icons::LANG_TEX, //  "cmake" => Icons::MAKE, //  "cmd" => Icons::OS_WINDOWS, //  "coffee" => '\u{f0f4}', //  "com" => Icons::OS_WINDOWS_CMD, //  "conf" => Icons::CONFIG, //  "config" => Icons::CONFIG, //  "cp" => Icons::LANG_CPP, //  "cpio" => Icons::COMPRESSED, //  "cpp" => Icons::LANG_CPP, //  "cr" => '\u{e62f}', //  "cr2" => Icons::IMAGE, //  "crdownload" => Icons::DOWNLOAD, // 󰇚 "crt" => Icons::GIST_SECRET, //  "cs" => Icons::LANG_CSHARP, // 󰌛 "csh" => Icons::SHELL_CMD, //  "cshtml" => Icons::RAZOR, //  "csproj" => Icons::LANG_CSHARP, // 󰌛 "css" => Icons::CSS3, //  "csv" => Icons::SHEET, //  "csx" => Icons::LANG_CSHARP, // 󰌛 "cts" => Icons::LANG_TYPESCRIPT, //  "cu" => '\u{e64b}', //  "cue" => Icons::PLAYLIST, // 󰲹 "cxx" => Icons::LANG_CPP, //  "d" => Icons::LANG_D, //  "dart" => '\u{e798}', //  "db" => Icons::DATABASE, //  "deb" => '\u{e77d}', //  "desktop" => '\u{ebd1}', //  "di" => Icons::LANG_D, //  "diff" => Icons::DIFF, //  "djv" => Icons::DOCUMENT, //  "djvu" => Icons::DOCUMENT, //  "dll" => Icons::LIBRARY, //  "dmg" => Icons::DISK_IMAGE, //  "doc" => Icons::DOCUMENT, //  "docx" => Icons::DOCUMENT, //  "dot" => '\u{f1049}', // 󱁉 "download" => Icons::DOWNLOAD, // 󰇚 "drawio" => '\u{ebba}', //  "dump" => Icons::DATABASE, //  "dvi" => Icons::IMAGE, //  "dylib" => Icons::OS_APPLE, //  "ebook" => Icons::BOOK, //  "ebuild" => '\u{f30d}', //  "editorconfig" => Icons::CONFIG, //  "ejs" => '\u{e618}', //  "el" => Icons::EMACS, //  "elc" => Icons::EMACS, //  "elm" => '\u{e62c}', //  "eml" => '\u{f003}', //  "env" => '\u{f462}', //  "eot" => Icons::FONT, //  "eps" => Icons::VECTOR, // 󰕙 "epub" => Icons::BOOK, //  "erb" => Icons::LANG_RUBYRAILS, //  "erl" => '\u{e7b1}', //  "ex" => Icons::LANG_ELIXIR, //  "exe" => Icons::OS_WINDOWS_CMD, //  "exs" => Icons::LANG_ELIXIR, //  "f" => Icons::LANG_FORTRAN, // 󱈚 "f90" => Icons::LANG_FORTRAN, // 󱈚 "fdmdownload" => Icons::DOWNLOAD, // 󰇚 "fish" => Icons::SHELL_CMD, //  "flac" => Icons::AUDIO, //  "flv" => Icons::VIDEO, //  "fnt" => Icons::FONT, //  "fon" => Icons::FONT, //  "font" => Icons::FONT, //  "for" => Icons::LANG_FORTRAN, // 󱈚 "fs" => Icons::LANG_FSHARP, //  "fsi" => Icons::LANG_FSHARP, //  "fsx" => Icons::LANG_FSHARP, //  "gdoc" => Icons::DOCUMENT, //  "gem" => Icons::LANG_RUBY, //  "gemfile" => Icons::LANG_RUBY, //  "gemspec" => Icons::LANG_RUBY, //  "gform" => '\u{f298}', //  "gif" => Icons::IMAGE, //  "git" => Icons::GIT, //  "go" => Icons::LANG_GO, //  "gpg" => Icons::SHIELD_LOCK, // 󰦝 "gradle" => Icons::GRADLE, //  "groovy" => Icons::LANG_GROOVY, //  "gsheet" => Icons::SHEET, //  "gslides" => Icons::SLIDE, //  "guardfile" => Icons::LANG_RUBY, //  "gv" => '\u{f1049}', // 󱁉 "gvy" => Icons::LANG_GROOVY, //  "gz" => Icons::COMPRESSED, //  "h" => Icons::LANG_C, //  "h++" => Icons::LANG_CPP, //  "h264" => Icons::VIDEO, //  "haml" => '\u{e664}', //  "hbs" => Icons::MUSTACHE, //  "heic" => Icons::IMAGE, //  "heics" => Icons::VIDEO, //  "heif" => Icons::IMAGE, //  "hpp" => Icons::LANG_CPP, //  "hs" => Icons::LANG_HASKELL, //  "htm" => Icons::HTML5, //  "html" => Icons::HTML5, //  "hxx" => Icons::LANG_CPP, //  "ical" => Icons::CALENDAR, //  "icalendar" => Icons::CALENDAR, //  "ico" => Icons::IMAGE, //  "ics" => Icons::CALENDAR, //  "ifb" => Icons::CALENDAR, //  "image" => Icons::DISK_IMAGE, //  "img" => Icons::DISK_IMAGE, //  "iml" => Icons::INTELLIJ, //  "inl" => Icons::LANG_C, //  "ini" => Icons::CONFIG, //  "ipynb" => '\u{e678}', //  "iso" => Icons::DISK_IMAGE, //  "j2c" => Icons::IMAGE, //  "j2k" => Icons::IMAGE, //  "jad" => Icons::LANG_JAVA, //  "jar" => Icons::LANG_JAVA, //  "java" => Icons::LANG_JAVA, //  "jfi" => Icons::IMAGE, //  "jfif" => Icons::IMAGE, //  "jif" => Icons::IMAGE, //  "jl" => '\u{e624}', //  "jmd" => Icons::MARKDOWN, //  "jp2" => Icons::IMAGE, //  "jpe" => Icons::IMAGE, //  "jpeg" => Icons::IMAGE, //  "jpf" => Icons::IMAGE, //  "jpg" => Icons::IMAGE, //  "jpx" => Icons::IMAGE, //  "js" => Icons::LANG_JAVASCRIPT, //  "json" => Icons::JSON, //  "jsx" => Icons::REACT, //  "jxl" => Icons::IMAGE, //  "kbx" => Icons::SHIELD_KEY, // 󰯄 "kdb" => Icons::KEYPASS, //  "kdbx" => Icons::KEYPASS, //  "key" => Icons::KEY, //  "ko" => Icons::OS_LINUX, //  "ksh" => Icons::SHELL_CMD, //  "kt" => Icons::LANG_KOTLIN, //  "kts" => Icons::LANG_KOTLIN, //  "latex" => Icons::LANG_TEX, //  "ldb" => Icons::DATABASE, //  "less" => '\u{e758}', //  "lhs" => Icons::LANG_HASKELL, //  "lib" => Icons::LIBRARY, //  "license" => Icons::LICENSE, //  "lisp" => '\u{f0172}', // 󰅲 "localized" => Icons::OS_APPLE, //  "lock" => Icons::LOCK, //  "log" => '\u{f18d}', //  "ltx" => Icons::LANG_TEX, //  "lua" => '\u{e620}', //  "lz" => Icons::COMPRESSED, //  "lz4" => Icons::COMPRESSED, //  "lzh" => Icons::COMPRESSED, //  "lzma" => Icons::COMPRESSED, //  "lzo" => Icons::COMPRESSED, //  "m" => Icons::LANG_C, //  "m2ts" => Icons::VIDEO, //  "m2v" => Icons::VIDEO, //  "m3u" => Icons::PLAYLIST, // 󰲹 "m3u8" => Icons::PLAYLIST, // 󰲹 "m4a" => Icons::AUDIO, //  "m4v" => Icons::VIDEO, //  "magnet" => '\u{f076}', //  "markdown" => Icons::MARKDOWN, //  "md" => Icons::MARKDOWN, //  "md5" => Icons::SHIELD_CHECK, // 󰕥 "mdb" => Icons::DATABASE, //  "mid" => '\u{f08f2}', // 󰣲 "mjs" => Icons::LANG_JAVASCRIPT, //  "mk" => Icons::MAKE, //  "mka" => Icons::AUDIO, //  "mkd" => Icons::MARKDOWN, //  "mkv" => Icons::VIDEO, //  "ml" => Icons::LANG_OCAML, //  "mli" => Icons::LANG_OCAML, //  "mll" => Icons::LANG_OCAML, //  "mly" => Icons::LANG_OCAML, //  "mm" => Icons::LANG_CPP, //  "mobi" => Icons::BOOK, //  "mov" => Icons::VIDEO, //  "mp2" => Icons::AUDIO, //  "mp3" => Icons::AUDIO, //  "mp4" => Icons::VIDEO, //  "mpeg" => Icons::VIDEO, //  "mpg" => Icons::VIDEO, //  "msi" => Icons::OS_WINDOWS, //  "mts" => Icons::LANG_TYPESCRIPT, //  "mustache" => Icons::MUSTACHE, //  "nef" => Icons::IMAGE, //  "ninja" => '\u{f0774}', // 󰝴 "nix" => '\u{f313}', //  "node" => Icons::NODEJS, //  "o" => Icons::BINARY, //  "odp" => Icons::SLIDE, //  "ods" => Icons::SHEET, //  "odt" => Icons::DOCUMENT, //  "ogg" => Icons::AUDIO, //  "ogm" => Icons::VIDEO, //  "ogv" => Icons::VIDEO, //  "opus" => Icons::AUDIO, //  "orf" => Icons::IMAGE, //  "org" => '\u{e633}', //  "otf" => Icons::FONT, //  "out" => '\u{eb2c}', //  "p12" => Icons::KEY, //  "par" => Icons::COMPRESSED, //  "part" => Icons::DOWNLOAD, // 󰇚 "patch" => Icons::DIFF, //  "pbm" => Icons::IMAGE, //  "pcm" => Icons::AUDIO, //  "pdf" => '\u{f1c1}', //  "pem" => Icons::KEY, //  "pfx" => Icons::KEY, //  "pgm" => Icons::IMAGE, //  "phar" => Icons::LANG_PHP, //  "php" => Icons::LANG_PHP, //  "pkg" => '\u{eb29}', //  "pl" => Icons::LANG_PERL, //  "plist" => Icons::OS_APPLE, //  "plx" => Icons::LANG_PERL, //  "pm" => Icons::LANG_PERL, //  "png" => Icons::IMAGE, //  "pnm" => Icons::IMAGE, //  "pod" => Icons::LANG_PERL, //  "pp" => '\u{e631}', //  "ppm" => Icons::IMAGE, //  "pps" => Icons::SLIDE, //  "ppsx" => Icons::SLIDE, //  "ppt" => Icons::SLIDE, //  "pptx" => Icons::SLIDE, //  "properties" => Icons::JSON, //  "prql" => Icons::DATABASE, //  "ps" => Icons::VECTOR, // 󰕙 "ps1" => Icons::POWERSHELL, //  "psd" => '\u{e7b8}', //  "psd1" => Icons::POWERSHELL, //  "psf" => Icons::FONT, //  "psm1" => Icons::POWERSHELL, //  "pub" => Icons::PUBLIC_KEY, // 󰷖 "purs" => '\u{e630}', //  "pxm" => Icons::IMAGE, //  "py" => Icons::LANG_PYTHON, //  "pyc" => Icons::LANG_PYTHON, //  "pyd" => Icons::LANG_PYTHON, //  "pyi" => Icons::LANG_PYTHON, //  "pyo" => Icons::LANG_PYTHON, //  "qcow" => Icons::DISK_IMAGE, //  "qcow2" => Icons::DISK_IMAGE, //  "r" => Icons::LANG_R, //  "rar" => Icons::COMPRESSED, //  "raw" => Icons::IMAGE, //  "razor" => Icons::RAZOR, //  "rb" => Icons::LANG_RUBY, //  "rdata" => Icons::LANG_R, //  "rdb" => '\u{e76d}', //  "rdoc" => Icons::MARKDOWN, //  "rds" => Icons::LANG_R, //  "readme" => Icons::README, // 󰂺 "rlib" => Icons::LANG_RUST, //  "rmd" => Icons::MARKDOWN, //  "rmeta" => Icons::LANG_RUST, //  "rpm" => '\u{e7bb}', //  "rs" => Icons::LANG_RUST, //  "rspec" => Icons::LANG_RUBY, //  "rspec_parallel" => Icons::LANG_RUBY, //  "rspec_status" => Icons::LANG_RUBY, //  "rss" => '\u{f09e}', //  "rst" => Icons::TEXT, //  "rtf" => Icons::TEXT, //  "ru" => Icons::LANG_RUBY, //  "rubydoc" => Icons::LANG_RUBYRAILS, //  "s" => Icons::LANG_ASSEMBLY, //  "sass" => Icons::LANG_SASS, //  "sbt" => Icons::SUBTITLE, // 󰨖 "scala" => '\u{e737}', //  "scss" => Icons::LANG_SASS, //  "service" => '\u{eba2}', //  "sh" => Icons::SHELL_CMD, //  "sha1" => Icons::SHIELD_CHECK, // 󰕥 "sha224" => Icons::SHIELD_CHECK, // 󰕥 "sha256" => Icons::SHIELD_CHECK, // 󰕥 "sha384" => Icons::SHIELD_CHECK, // 󰕥 "sha512" => Icons::SHIELD_CHECK, // 󰕥 "shell" => Icons::SHELL_CMD, //  "shtml" => Icons::HTML5, //  "sig" => Icons::SIGNED_FILE, // 󱧃 "signature" => Icons::SIGNED_FILE, // 󱧃 "slim" => Icons::LANG_RUBYRAILS, //  "sln" => '\u{e70c}', //  "so" => Icons::OS_LINUX, //  "sql" => Icons::DATABASE, //  "sqlite3" => '\u{e7c4}', //  "srt" => Icons::SUBTITLE, // 󰨖 "ssa" => Icons::SUBTITLE, // 󰨖 "stl" => Icons::IMAGE, //  "sty" => Icons::LANG_TEX, //  "styl" => Icons::LANG_STYLUS, //  "stylus" => Icons::LANG_STYLUS, //  "sub" => Icons::SUBTITLE, // 󰨖 "sublime-build" => Icons::SUBLIME, //  "sublime-keymap" => Icons::SUBLIME, //  "sublime-menu" => Icons::SUBLIME, //  "sublime-options"=> Icons::SUBLIME, //  "sublime-package"=> Icons::SUBLIME, //  "sublime-project"=> Icons::SUBLIME, //  "sublime-session"=> Icons::SUBLIME, //  "sublime-settings"=>Icons::SUBLIME, //  "sublime-snippet"=> Icons::SUBLIME, //  "sublime-theme" => Icons::SUBLIME, //  "svelte" => '\u{e697}', //  "svg" => Icons::VECTOR, // 󰕙 "swift" => '\u{e755}', //  "t" => Icons::LANG_PERL, //  "tar" => Icons::COMPRESSED, //  "taz" => Icons::COMPRESSED, //  "tbz" => Icons::COMPRESSED, //  "tbz2" => Icons::COMPRESSED, //  "tc" => Icons::DISK_IMAGE, //  "tex" => Icons::LANG_TEX, //  "tf" => Icons::TERRAFORM, // 󱁢 "tfstate" => Icons::TERRAFORM, // 󱁢 "tfvars" => Icons::TERRAFORM, // 󱁢 "tgz" => Icons::COMPRESSED, //  "tif" => Icons::IMAGE, //  "tiff" => Icons::IMAGE, //  "tlz" => Icons::COMPRESSED, //  "tml" => Icons::CONFIG, //  "toml" => Icons::CONFIG, //  "torrent" => '\u{e275}', //  "ts" => Icons::LANG_TYPESCRIPT, //  "tsv" => Icons::SHEET, //  "tsx" => Icons::REACT, //  "ttc" => Icons::FONT, //  "ttf" => Icons::FONT, //  "twig" => '\u{e61c}', //  "txt" => Icons::TEXT, //  "typ" => Icons::TYPST, // 𝐭 "txz" => Icons::COMPRESSED, //  "tz" => Icons::COMPRESSED, //  "tzo" => Icons::COMPRESSED, //  "unity" => Icons::UNITY, //  "unity3d" => Icons::UNITY, //  "v" => Icons::LANG_V, //  "vdi" => Icons::DISK_IMAGE, //  "vhd" => Icons::DISK_IMAGE, //  "video" => Icons::VIDEO, //  "vim" => Icons::VIM, //  "vmdk" => Icons::DISK_IMAGE, //  "vob" => Icons::VIDEO, //  "vue" => '\u{f0844}', // 󰡄 "war" => Icons::LANG_JAVA, //  "wav" => Icons::AUDIO, //  "webm" => Icons::VIDEO, //  "webmanifest" => Icons::JSON, //  "webp" => Icons::IMAGE, //  "whl" => Icons::LANG_PYTHON, //  "windows" => Icons::OS_WINDOWS, //  "wma" => Icons::AUDIO, //  "wmv" => Icons::VIDEO, //  "woff" => Icons::FONT, //  "woff2" => Icons::FONT, //  "wv" => Icons::AUDIO, //  "xcf" => Icons::IMAGE, //  "xhtml" => Icons::HTML5, //  "xlr" => Icons::SHEET, //  "xls" => Icons::SHEET, //  "xlsm" => Icons::SHEET, //  "xlsx" => Icons::SHEET, //  "xml" => Icons::XML, // 󰗀 "xpm" => Icons::IMAGE, //  "xul" => Icons::XML, // 󰗀 "xz" => Icons::COMPRESSED, //  "yaml" => Icons::YAML, //  "yml" => Icons::YAML, //  "z" => Icons::COMPRESSED, //  "zig" => '\u{e6a9}', //  "zip" => Icons::COMPRESSED, //  "zsh" => Icons::SHELL_CMD, //  "zsh-theme" => Icons::SHELL, // 󱆃 "zst" => Icons::COMPRESSED, //  }; /// Converts the style used to paint a file name into the style that should be /// used to paint an icon. /// /// - The background colour should be preferred to the foreground colour, as /// if one is set, it’s the more “obvious” colour choice. /// - If neither is set, just use the default style. /// - Attributes such as bold or underline should not be used to paint the /// icon, as they can make it look weird. pub fn iconify_style(style: Style) -> Style { style .background .or(style.foreground) .map(Style::from) .unwrap_or_default() } /// Lookup the icon for a file based on the file's name, if the entry is a /// directory, or by the lowercase file extension. pub fn icon_for_file(file: &File<'_>) -> char { if file.points_to_directory() { *DIRECTORY_ICONS.get(file.name.as_str()).unwrap_or_else(|| { if file.is_empty_dir() { &Icons::FOLDER_OPEN //  } else { &Icons::FOLDER //  } }) } else if let Some(icon) = FILENAME_ICONS.get(file.name.as_str()) { *icon } else if let Some(ext) = file.ext.as_ref() { *EXTENSION_ICONS.get(ext.as_str()).unwrap_or(&Icons::FILE) //  } else { Icons::FILE_OUTLINE //  } } eza-0.18.2/src/output/lines.rs000064400000000000000000000017531046102023000143100ustar 00000000000000use std::io::{self, Write}; use ansiterm::ANSIStrings; use crate::fs::filter::FileFilter; use crate::fs::File; use crate::output::cell::TextCellContents; use crate::output::file_name::Options as FileStyle; use crate::theme::Theme; /// The lines view literally just displays each file, line-by-line. pub struct Render<'a> { pub files: Vec>, pub theme: &'a Theme, pub file_style: &'a FileStyle, pub filter: &'a FileFilter, } impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { self.filter.sort_files(&mut self.files); for file in &self.files { let name_cell = self.render_file(file); writeln!(w, "{}", ANSIStrings(&name_cell))?; } Ok(()) } fn render_file<'f>(&self, file: &'f File<'a>) -> TextCellContents { self.file_style .for_file(file, self.theme) .with_link_paths() .with_mount_details(false) .paint() } } eza-0.18.2/src/output/mod.rs000064400000000000000000000040541046102023000137520ustar 00000000000000pub use self::cell::{DisplayWidth, TextCell, TextCellContents}; pub use self::escape::escape; pub mod color_scale; pub mod details; pub mod file_name; pub mod grid; pub mod grid_details; pub mod icons; pub mod lines; pub mod render; pub mod table; pub mod time; mod cell; mod escape; mod tree; /// The **view** contains all information about how to format output. #[derive(Debug)] pub struct View { pub mode: Mode, pub width: TerminalWidth, pub file_style: file_name::Options, pub deref_links: bool, pub total_size: bool, } /// The **mode** is the “type” of output. #[derive(PartialEq, Eq, Debug)] #[allow(clippy::large_enum_variant)] pub enum Mode { Grid(grid::Options), Details(details::Options), GridDetails(grid_details::Options), Lines, } /// The width of the terminal requested by the user. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TerminalWidth { /// The user requested this specific number of columns. Set(usize), /// Look up the terminal size at runtime. Automatic, } impl TerminalWidth { pub fn actual_terminal_width(self) -> Option { // All of stdin, stdout, and stderr could not be connected to a // terminal, but we’re only interested in stdout because it’s // where the output goes. #[cfg(unix)] let stdout_term_width = { use std::os::fd::AsRawFd; terminal_size::terminal_size_using_fd(std::io::stdout().as_raw_fd()) .map(|(w, _h)| w.0 as _) }; #[cfg(windows)] let stdout_term_width = { use std::os::windows::io::RawHandle; use windows_sys::Win32::System::Console::{GetStdHandle, STD_OUTPUT_HANDLE}; terminal_size::terminal_size_using_handle(unsafe { GetStdHandle(STD_OUTPUT_HANDLE) as RawHandle }) .map(|(w, _h)| w.0 as _) }; #[rustfmt::skip] return match self { Self::Set(width) => Some(width), Self::Automatic => stdout_term_width, }; } } eza-0.18.2/src/output/render/blocks.rs000064400000000000000000000112111046102023000157200ustar 00000000000000use ansiterm::Style; use locale::Numeric as NumericLocale; use number_prefix::Prefix; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::table::SizeFormat; impl f::Blocksize { pub fn render( self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale, ) -> TextCell { use number_prefix::NumberPrefix; let size = match self { Self::Some(s) => s, Self::None => return TextCell::blank(colours.no_blocksize()), }; let result = match size_format { SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), SizeFormat::JustBytes => { // Use the binary prefix to select a style. let prefix = match NumberPrefix::binary(size as f64) { NumberPrefix::Standalone(_) => None, NumberPrefix::Prefixed(p, _) => Some(p), }; // But format the number directly using the locale. let string = numerics.format_int(size); return TextCell::paint(colours.blocksize(prefix), string); } }; let (prefix, n) = match result { NumberPrefix::Standalone(b) => { return TextCell::paint(colours.blocksize(None), numerics.format_int(b)) } NumberPrefix::Prefixed(p, n) => (p, n), }; let symbol = prefix.symbol(); let number = if n < 10_f64 { numerics.format_float(n, 1) } else { numerics.format_int(n.round() as isize) }; TextCell { // symbol is guaranteed to be ASCII since unit prefixes are hardcoded. width: DisplayWidth::from(&*number) + symbol.len(), contents: vec![ colours.blocksize(Some(prefix)).paint(number), colours.unit(Some(prefix)).paint(symbol), ] .into(), } } } #[rustfmt::skip] pub trait Colours { fn blocksize(&self, prefix: Option) -> Style; fn unit(&self, prefix: Option) -> Style; fn no_blocksize(&self) -> Style; } #[cfg(test)] pub mod test { use ansiterm::Colour::*; use ansiterm::Style; use super::Colours; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::table::SizeFormat; use locale::Numeric as NumericLocale; use number_prefix::Prefix; struct TestColours; #[rustfmt::skip] impl Colours for TestColours { fn blocksize(&self, _prefix: Option) -> Style { Fixed(66).normal() } fn unit(&self, _prefix: Option) -> Style { Fixed(77).bold() } fn no_blocksize(&self) -> Style { Black.italic() } } #[test] fn directory() { let directory = f::Blocksize::None; let expected = TextCell::blank(Black.italic()); assert_eq!( expected, directory.render( &TestColours, SizeFormat::JustBytes, &NumericLocale::english() ) ) } #[test] fn file_decimal() { let directory = f::Blocksize::Some(2_100_000); let expected = TextCell { width: DisplayWidth::from(4), contents: vec![Fixed(66).paint("2.1"), Fixed(77).bold().paint("M")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::DecimalBytes, &NumericLocale::english() ) ) } #[test] fn file_binary() { let directory = f::Blocksize::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(5), contents: vec![Fixed(66).paint("1.0"), Fixed(77).bold().paint("Mi")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::BinaryBytes, &NumericLocale::english() ) ) } #[test] fn file_bytes() { let directory = f::Blocksize::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(9), contents: vec![Fixed(66).paint("1,048,576")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::JustBytes, &NumericLocale::english() ) ) } } eza-0.18.2/src/output/render/filetype.rs000064400000000000000000000020051046102023000162650ustar 00000000000000use ansiterm::{ANSIString, Style}; use crate::fs::fields as f; impl f::Type { pub fn render(self, colours: &C) -> ANSIString<'static> { #[rustfmt::skip] return match self { Self::File => colours.normal().paint("."), Self::Directory => colours.directory().paint("d"), Self::Pipe => colours.pipe().paint("|"), Self::Link => colours.symlink().paint("l"), Self::BlockDevice => colours.block_device().paint("b"), Self::CharDevice => colours.char_device().paint("c"), Self::Socket => colours.socket().paint("s"), Self::Special => colours.special().paint("?"), }; } } pub trait Colours { fn normal(&self) -> Style; fn directory(&self) -> Style; fn pipe(&self) -> Style; fn symlink(&self) -> Style; fn block_device(&self) -> Style; fn char_device(&self) -> Style; fn socket(&self) -> Style; fn special(&self) -> Style; } eza-0.18.2/src/output/render/flags.rs000064400000000000000000000004171046102023000155450ustar 00000000000000use ansiterm::Style; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::FlagsFormat; impl f::Flags { pub fn render(self, style: Style, _format: FlagsFormat) -> TextCell { TextCell::paint(style, "-".to_string()) } } eza-0.18.2/src/output/render/flags_bsd.rs000064400000000000000000000021521046102023000163730ustar 00000000000000use ansiterm::Style; use std::ffi::CStr; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::FlagsFormat; extern "C" { fn fflagstostr(flags: libc::c_ulong) -> *const libc::c_char; } /// Wrapper around the C library call fflagstostr. If returned string is NULL /// or empty a "-" is returned fn flags_to_string(flags: f::flag_t) -> String { // SAFETY: Calling external "C" function let flags_c_str = unsafe { fflagstostr(libc::c_ulong::from(flags)) }; if flags_c_str.is_null() { "-".to_string() } else { let flags_str = unsafe { CStr::from_ptr(flags_c_str) }; let flags = flags_str .to_str() .map_or("-", |s| if s.is_empty() { "-" } else { s }) .to_string(); // SAFETY: Calling external "C" function to free memory allocated by fflagstostr unsafe { libc::free(flags_c_str.cast_mut().cast()); } flags } } impl f::Flags { pub fn render(self, style: Style, _format: FlagsFormat) -> TextCell { TextCell::paint(style, flags_to_string(self.0)) } } eza-0.18.2/src/output/render/flags_windows.rs000064400000000000000000000065571046102023000173320ustar 00000000000000use crate::fs::fields as f; use crate::output::table::FlagsFormat; use crate::output::TextCell; use ansiterm::Style; // See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants const FILE_ATTRIBUTE_READONLY: u32 = 0x0000_0001; // R const FILE_ATTRIBUTE_HIDDEN: u32 = 0x0000_0002; // H const FILE_ATTRIBUTE_SYSTEM: u32 = 0x0000_0004; // S const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x0000_0020; // A const FILE_ATTRIBUTE_TEMPORARY: u32 = 0x0000_0100; // T const FILE_ATTRIBUTE_COMPRESSED: u32 = 0x0000_0800; // C const FILE_ATTRIBUTE_OFFLINE: u32 = 0x0000_1000; // O const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x0000_2000; // I const FILE_ATTRIBUTE_ENCRYPTED: u32 = 0x0000_4000; // E const FILE_ATTRIBUTE_NO_SCRUB_DATA: u32 = 0x0002_0000; // X const FILE_ATTRIBUTE_PINNED: u32 = 0x0008_0000; // P const FILE_ATTRIBUTE_UNPINNED: u32 = 0x0010_0000; // U const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS: u32 = 0x0040_0000; // M struct Attribute { flag: u32, name: &'static str, abbr: char, } const ATTRIBUTES: [Attribute; 13] = [ Attribute { flag: FILE_ATTRIBUTE_READONLY, name: "readonly", abbr: 'R', }, Attribute { flag: FILE_ATTRIBUTE_HIDDEN, name: "hidden", abbr: 'H', }, Attribute { flag: FILE_ATTRIBUTE_SYSTEM, name: "system", abbr: 'S', }, Attribute { flag: FILE_ATTRIBUTE_ARCHIVE, name: "archive", abbr: 'A', }, Attribute { flag: FILE_ATTRIBUTE_TEMPORARY, name: "temporary", abbr: 'T', }, Attribute { flag: FILE_ATTRIBUTE_COMPRESSED, name: "compressed", abbr: 'C', }, Attribute { flag: FILE_ATTRIBUTE_OFFLINE, name: "offline", abbr: 'O', }, Attribute { flag: FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, name: "not indexed", abbr: 'I', }, Attribute { flag: FILE_ATTRIBUTE_ENCRYPTED, name: "encrypted", abbr: 'E', }, Attribute { flag: FILE_ATTRIBUTE_NO_SCRUB_DATA, name: "no scrub", abbr: 'X', }, Attribute { flag: FILE_ATTRIBUTE_UNPINNED, name: "unpinned", abbr: 'U', }, Attribute { flag: FILE_ATTRIBUTE_PINNED, name: "pinned", abbr: 'P', }, Attribute { flag: FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, name: "recall on data access", abbr: 'M', }, ]; fn flags_to_bsd_string(flags: f::flag_t) -> String { let mut result = Vec::new(); for attribute in &ATTRIBUTES { if attribute.flag & flags != 0 { result.push(attribute.name); } } if result.is_empty() { "-".to_string() } else { result.join("-") } } fn flags_to_windows_string(flags: f::flag_t) -> String { let mut result = String::new(); for attribute in &ATTRIBUTES { if attribute.flag & flags != 0 { result.push(attribute.abbr); } } if result.is_empty() { result.push('-'); } result } impl f::Flags { pub fn render(self, style: Style, format: FlagsFormat) -> TextCell { TextCell::paint( style, if format == FlagsFormat::Short { flags_to_windows_string(self.0) } else { flags_to_bsd_string(self.0) }, ) } } eza-0.18.2/src/output/render/git.rs000064400000000000000000000112371046102023000152360ustar 00000000000000use ansiterm::{ANSIString, Style}; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; impl f::Git { pub fn render(self, colours: &dyn Colours) -> TextCell { TextCell { width: DisplayWidth::from(2), contents: vec![self.staged.render(colours), self.unstaged.render(colours)].into(), } } } impl f::GitStatus { fn render(self, colours: &dyn Colours) -> ANSIString<'static> { #[rustfmt::skip] return match self { Self::NotModified => colours.not_modified().paint("-"), Self::New => colours.new().paint("N"), Self::Modified => colours.modified().paint("M"), Self::Deleted => colours.deleted().paint("D"), Self::Renamed => colours.renamed().paint("R"), Self::TypeChange => colours.type_change().paint("T"), Self::Ignored => colours.ignored().paint("I"), Self::Conflicted => colours.conflicted().paint("U"), }; } } pub trait Colours { fn not_modified(&self) -> Style; // FIXME: this amount of allows needed to keep clippy happy should be enough // of an argument that new needs to be renamed. #[allow(clippy::new_ret_no_self, clippy::wrong_self_convention)] fn new(&self) -> Style; fn modified(&self) -> Style; fn deleted(&self) -> Style; fn renamed(&self) -> Style; fn type_change(&self) -> Style; fn ignored(&self) -> Style; fn conflicted(&self) -> Style; } impl f::SubdirGitRepo { pub fn render(self, colours: &dyn RepoColours) -> TextCell { let branch_name = match self.branch { Some(name) => { if name == "main" || name == "master" { colours.branch_main().paint(name) } else { colours.branch_other().paint(name) } } None => colours.no_repo().paint("-"), }; if let Some(status) = self.status { TextCell { width: DisplayWidth::from(2) + DisplayWidth::from(branch_name.as_str()), contents: vec![ status.render(colours), Style::default().paint(" "), branch_name, ] .into(), } } else { TextCell { width: DisplayWidth::from(branch_name.as_str()), contents: vec![branch_name].into(), } } } } impl f::SubdirGitRepoStatus { pub fn render(self, colours: &dyn RepoColours) -> ANSIString<'static> { match self { Self::NoRepo => colours.no_repo().paint("-"), Self::GitClean => colours.git_clean().paint("|"), Self::GitDirty => colours.git_dirty().paint("+"), } } } pub trait RepoColours { fn branch_main(&self) -> Style; fn branch_other(&self) -> Style; fn no_repo(&self) -> Style; fn git_clean(&self) -> Style; fn git_dirty(&self) -> Style; } #[cfg(test)] pub mod test { use super::Colours; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use ansiterm::Colour::*; use ansiterm::Style; struct TestColours; impl Colours for TestColours { fn not_modified(&self) -> Style { Fixed(90).normal() } fn new(&self) -> Style { Fixed(91).normal() } fn modified(&self) -> Style { Fixed(92).normal() } fn deleted(&self) -> Style { Fixed(93).normal() } fn renamed(&self) -> Style { Fixed(94).normal() } fn type_change(&self) -> Style { Fixed(95).normal() } fn ignored(&self) -> Style { Fixed(96).normal() } fn conflicted(&self) -> Style { Fixed(97).normal() } } #[test] fn git_blank() { let stati = f::Git { staged: f::GitStatus::NotModified, unstaged: f::GitStatus::NotModified, }; let expected = TextCell { width: DisplayWidth::from(2), contents: vec![Fixed(90).paint("-"), Fixed(90).paint("-")].into(), }; assert_eq!(expected, stati.render(&TestColours)) } #[test] fn git_new_changed() { let stati = f::Git { staged: f::GitStatus::New, unstaged: f::GitStatus::Modified, }; let expected = TextCell { width: DisplayWidth::from(2), contents: vec![Fixed(91).paint("N"), Fixed(92).paint("M")].into(), }; assert_eq!(expected, stati.render(&TestColours)) } } eza-0.18.2/src/output/render/groups.rs000064400000000000000000000177321046102023000160000ustar 00000000000000use ansiterm::Style; use uzers::{Groups, Users}; use crate::fs::fields as f; use crate::fs::fields::User; use crate::output::cell::TextCell; use crate::output::table::{GroupFormat, UserFormat}; pub trait Render { fn render( self, colours: &C, users: &U, user_format: UserFormat, group_format: GroupFormat, file_user: Option, ) -> TextCell; } impl Render for Option { fn render( self, colours: &C, users: &U, user_format: UserFormat, group_format: GroupFormat, file_user: Option, ) -> TextCell { use uzers::os::unix::GroupExt; let mut style = colours.not_yours(); let group = match self { Some(g) => match users.get_group_by_gid(g.0) { Some(g) => (*g).clone(), None => return TextCell::paint(style, g.0.to_string()), }, None => return TextCell::blank(colours.no_group()), }; let current_uid = users.get_current_uid(); if let Some(current_user) = users.get_user_by_uid(current_uid) { if current_user.primary_group_id() == group.gid() || group.members().iter().any(|u| u == current_user.name()) { style = colours.yours(); } } if group.gid() == 0 && style != colours.yours() { style = colours.root_group(); } let mut group_name = match user_format { UserFormat::Name => group.name().to_string_lossy().into(), UserFormat::Numeric => group.gid().to_string(), }; if let GroupFormat::Smart = group_format { if let Some(file_uid) = file_user { if let Some(file_user) = users.get_user_by_uid(file_uid.0) { if file_user.name().to_string_lossy() == group.name().to_string_lossy() { group_name = ":".to_string(); } } } } TextCell::paint(style, group_name) } } pub trait Colours { fn yours(&self) -> Style; fn not_yours(&self) -> Style; fn no_group(&self) -> Style; fn root_group(&self) -> Style; } #[cfg(test)] #[allow(unused_results)] pub mod test { use super::{Colours, Render}; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::{GroupFormat, UserFormat}; use ansiterm::Colour::*; use ansiterm::Style; use uzers::mock::MockUsers; use uzers::os::unix::GroupExt; use uzers::{Group, User}; struct TestColours; #[rustfmt::skip] impl Colours for TestColours { fn yours(&self) -> Style { Fixed(80).normal() } fn not_yours(&self) -> Style { Fixed(81).normal() } fn no_group(&self) -> Style { Black.italic() } fn root_group(&self) -> Style { Fixed(82).normal() } } #[test] fn named() { let mut users = MockUsers::with_current_uid(1000); users.add_group(Group::new(100, "folk")); let group = Some(f::Group(100)); let file_user = Some(f::User(1000)); let expected = TextCell::paint_str(TestColours.not_yours(), "folk"); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Regular, file_user ) ); let expected = TextCell::paint_str(TestColours.not_yours(), "100"); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Numeric, GroupFormat::Regular, file_user ) ); } #[test] fn unnamed() { let users = MockUsers::with_current_uid(1000); let group = Some(f::Group(100)); let file_user = Some(f::User(1000)); let expected = TextCell::paint_str(TestColours.not_yours(), "100"); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Regular, file_user ) ); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Numeric, GroupFormat::Regular, file_user ) ); } #[test] fn primary() { let mut users = MockUsers::with_current_uid(2); users.add_user(User::new(2, "eve", 100)); users.add_group(Group::new(100, "folk")); let group = Some(f::Group(100)); let file_user = Some(f::User(2)); let expected = TextCell::paint_str(TestColours.yours(), "folk"); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Regular, file_user ) ) } #[test] fn secondary() { let mut users = MockUsers::with_current_uid(2); users.add_user(User::new(2, "eve", 666)); let test_group = Group::new(100, "folk").add_member("eve"); users.add_group(test_group); let group = Some(f::Group(100)); let file_user = Some(f::User(2)); let expected = TextCell::paint_str(TestColours.yours(), "folk"); assert_eq!( expected, group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Regular, file_user ) ) } #[test] fn overflow() { let group = Some(f::Group(2_147_483_648)); let file_user = Some(f::User(1000)); let expected = TextCell::paint_str(TestColours.not_yours(), "2147483648"); assert_eq!( expected, group.render( &TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric, GroupFormat::Regular, file_user ) ); } #[test] fn smart() { let mut users = MockUsers::with_current_uid(1000); users.add_user(User::new(1000, "user", 100)); users.add_user(User::new(1001, "http", 101)); users.add_group(Group::new(100, "user")); users.add_group(Group::new(101, "http")); let user_group = Some(f::Group(100)); let user_file = Some(f::User(1000)); let expected = TextCell::paint_str(TestColours.yours(), ":"); assert_eq!( expected, user_group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Smart, user_file ) ); let expected = TextCell::paint_str(TestColours.yours(), ":"); assert_eq!( expected, user_group.render( &TestColours, &users, UserFormat::Numeric, GroupFormat::Smart, user_file ) ); let http_group = Some(f::Group(101)); let expected = TextCell::paint_str(TestColours.not_yours(), "http"); assert_eq!( expected, http_group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Smart, user_file ) ); let http_file = Some(f::User(1001)); let expected = TextCell::paint_str(TestColours.not_yours(), ":"); assert_eq!( expected, http_group.render( &TestColours, &users, UserFormat::Name, GroupFormat::Smart, http_file ) ); } } eza-0.18.2/src/output/render/inode.rs000064400000000000000000000010571046102023000155500ustar 00000000000000use ansiterm::Style; use crate::fs::fields as f; use crate::output::cell::TextCell; impl f::Inode { pub fn render(self, style: Style) -> TextCell { TextCell::paint(style, self.0.to_string()) } } #[cfg(test)] pub mod test { use crate::fs::fields as f; use crate::output::cell::TextCell; use ansiterm::Colour::*; #[test] fn blocklessness() { let io = f::Inode(1_414_213); let expected = TextCell::paint_str(Cyan.underline(), "1414213"); assert_eq!(expected, io.render(Cyan.underline())); } } eza-0.18.2/src/output/render/links.rs000064400000000000000000000044371046102023000155770ustar 00000000000000use ansiterm::Style; #[cfg(unix)] use locale::Numeric as NumericLocale; #[cfg(unix)] use crate::fs::fields as f; #[cfg(unix)] use crate::output::cell::TextCell; #[cfg(unix)] impl f::Links { pub fn render(&self, colours: &C, numeric: &NumericLocale) -> TextCell { let style = if self.multiple { colours.multi_link_file() } else { colours.normal() }; TextCell::paint(style, numeric.format_int(self.count)) } } pub trait Colours { fn normal(&self) -> Style; fn multi_link_file(&self) -> Style; } #[cfg(test)] pub mod test { use super::Colours; #[cfg(unix)] use crate::fs::fields as f; #[cfg(unix)] use crate::output::cell::{DisplayWidth, TextCell}; use ansiterm::Colour::*; use ansiterm::Style; #[cfg(unix)] use locale; struct TestColours; impl Colours for TestColours { fn normal(&self) -> Style { Blue.normal() } fn multi_link_file(&self) -> Style { Blue.on(Red) } } #[test] #[cfg(unix)] fn regular_file() { let stati = f::Links { count: 1, multiple: false, }; let expected = TextCell { width: DisplayWidth::from(1), contents: vec![Blue.paint("1")].into(), }; assert_eq!( expected, stati.render(&TestColours, &locale::Numeric::english()) ); } #[test] #[cfg(unix)] fn regular_directory() { let stati = f::Links { count: 3005, multiple: false, }; let expected = TextCell { width: DisplayWidth::from(5), contents: vec![Blue.paint("3,005")].into(), }; assert_eq!( expected, stati.render(&TestColours, &locale::Numeric::english()) ); } #[test] #[cfg(unix)] fn popular_file() { let stati = f::Links { count: 3005, multiple: true, }; let expected = TextCell { width: DisplayWidth::from(5), contents: vec![Blue.on(Red).paint("3,005")].into(), }; assert_eq!( expected, stati.render(&TestColours, &locale::Numeric::english()) ); } } eza-0.18.2/src/output/render/mod.rs000064400000000000000000000026041046102023000152300ustar 00000000000000#[cfg(unix)] mod blocks; #[cfg(unix)] pub use self::blocks::Colours as BlocksColours; mod filetype; pub use self::filetype::Colours as FiletypeColours; mod git; pub use self::git::Colours as GitColours; pub use self::git::RepoColours as GitRepoColours; #[cfg(unix)] mod groups; #[cfg(unix)] pub use self::groups::{Colours as GroupColours, Render as GroupRender}; #[cfg(unix)] mod inode; // inode uses just one colour mod links; pub use self::links::Colours as LinksColours; mod permissions; pub use self::permissions::{Colours as PermissionsColours, PermissionsPlusRender}; mod size; pub use self::size::Colours as SizeColours; mod times; pub use self::times::Render as TimeRender; // times does too #[cfg(unix)] mod users; #[cfg(unix)] pub use self::users::Colours as UserColours; #[cfg(unix)] pub use self::users::Render as UserRender; mod octal; pub use self::octal::Render as OctalPermissionsRender; // octal uses just one colour mod securityctx; pub use self::securityctx::Colours as SecurityCtxColours; #[cfg(any( target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly" ))] mod flags_bsd; #[cfg(windows)] mod flags_windows; #[cfg(not(any( target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "windows" )))] mod flags; eza-0.18.2/src/output/render/octal.rs000064400000000000000000000127461046102023000155630ustar 00000000000000use ansiterm::Style; use crate::fs::fields as f; use crate::output::cell::TextCell; pub trait Render { fn render(&self, style: Style) -> TextCell; } impl Render for Option { fn render(&self, style: Style) -> TextCell { match self { Some(p) => { let perm = &p.permissions; #[rustfmt::skip] let octal_sticky = f::OctalPermissions::bits_to_octal( perm.setuid, perm.setgid, perm.sticky ); let octal_owner = f::OctalPermissions::bits_to_octal( perm.user_read, perm.user_write, perm.user_execute, ); let octal_group = f::OctalPermissions::bits_to_octal( perm.group_read, perm.group_write, perm.group_execute, ); let octal_other = f::OctalPermissions::bits_to_octal( perm.other_read, perm.other_write, perm.other_execute, ); TextCell::paint( style, format!("{octal_sticky}{octal_owner}{octal_group}{octal_other}"), ) } None => TextCell::paint(style, "----".into()), } } } impl f::OctalPermissions { fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 { u8::from(r) * 4 + u8::from(w) * 2 + u8::from(x) } } #[cfg(test)] pub mod test { use super::Render; use crate::fs::fields as f; use crate::output::cell::TextCell; use ansiterm::Colour::*; #[test] fn normal_folder() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: true, setuid: false, group_read: true, group_write: false, group_execute: true, setgid: false, other_read: true, other_write: false, other_execute: true, sticky: false, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0755"); assert_eq!(expected, octal.render(Purple.bold())); } #[test] fn normal_file() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: false, setuid: false, group_read: true, group_write: false, group_execute: false, setgid: false, other_read: true, other_write: false, other_execute: false, sticky: false, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0644"); assert_eq!(expected, octal.render(Purple.bold())); } #[test] fn secret_file() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: false, setuid: false, group_read: false, group_write: false, group_execute: false, setgid: false, other_read: false, other_write: false, other_execute: false, sticky: false, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0600"); assert_eq!(expected, octal.render(Purple.bold())); } #[test] fn sticky1() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: true, setuid: true, group_read: true, group_write: true, group_execute: true, setgid: false, other_read: true, other_write: true, other_execute: true, sticky: false, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "4777"); assert_eq!(expected, octal.render(Purple.bold())); } #[test] fn sticky2() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: true, setuid: false, group_read: true, group_write: true, group_execute: true, setgid: true, other_read: true, other_write: true, other_execute: true, sticky: false, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "2777"); assert_eq!(expected, octal.render(Purple.bold())); } #[test] fn sticky3() { let bits = f::Permissions { user_read: true, user_write: true, user_execute: true, setuid: false, group_read: true, group_write: true, group_execute: true, setgid: false, other_read: true, other_write: true, other_execute: true, sticky: true, }; let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "1777"); assert_eq!(expected, octal.render(Purple.bold())); } } eza-0.18.2/src/output/render/permissions.rs000064400000000000000000000261611046102023000170300ustar 00000000000000use std::iter; use ansiterm::{ANSIString, Style}; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::render::FiletypeColours; pub trait PermissionsPlusRender { fn render(&self, colours: &C) -> TextCell; } impl PermissionsPlusRender for Option { #[cfg(unix)] fn render(&self, colours: &C) -> TextCell { match self { Some(p) => { let mut chars = vec![p.file_type.render(colours)]; let permissions = p.permissions; chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file())); if p.xattrs { chars.push(colours.attribute().paint("@")); } // As these are all ASCII characters, we can guarantee that they’re // all going to be one character wide, and don’t need to compute the // cell’s display width. TextCell { width: DisplayWidth::from(chars.len()), contents: chars.into(), } } None => { let chars: Vec<_> = iter::repeat(colours.dash().paint("-")).take(10).collect(); TextCell { width: DisplayWidth::from(chars.len()), contents: chars.into(), } } } } #[cfg(windows)] fn render(&self, colours: &C) -> TextCell { match self { Some(p) => { let mut chars = vec![p.attributes.render_type(colours)]; chars.extend(p.attributes.render(colours)); TextCell { width: DisplayWidth::from(chars.len()), contents: chars.into(), } } None => TextCell { width: DisplayWidth::from(0), contents: vec![].into(), }, } } } pub trait RenderPermissions { fn render(&self, colours: &C, is_regular_file: bool) -> Vec>; } impl RenderPermissions for Option { fn render(&self, colours: &C, is_regular_file: bool) -> Vec> { match self { Some(p) => { let bit = |bit, chr: &'static str, style: Style| { if bit { style.paint(chr) } else { colours.dash().paint("-") } }; vec![ bit(p.user_read, "r", colours.user_read()), bit(p.user_write, "w", colours.user_write()), p.user_execute_bit(colours, is_regular_file), bit(p.group_read, "r", colours.group_read()), bit(p.group_write, "w", colours.group_write()), p.group_execute_bit(colours), bit(p.other_read, "r", colours.other_read()), bit(p.other_write, "w", colours.other_write()), p.other_execute_bit(colours), ] } None => iter::repeat(colours.dash().paint("-")).take(9).collect(), } } } impl f::Permissions { fn user_execute_bit( &self, colours: &C, is_regular_file: bool, ) -> ANSIString<'static> { #[rustfmt::skip] return match (self.user_execute, self.setuid, is_regular_file) { (false, false, _) => colours.dash().paint("-"), (true, false, false) => colours.user_execute_other().paint("x"), (true, false, true) => colours.user_execute_file().paint("x"), (false, true, _) => colours.special_other().paint("S"), (true, true, false) => colours.special_other().paint("s"), (true, true, true) => colours.special_user_file().paint("s"), }; } fn group_execute_bit(&self, colours: &C) -> ANSIString<'static> { #[rustfmt::skip] return match (self.group_execute, self.setgid) { (false, false) => colours.dash().paint("-"), (true, false) => colours.group_execute().paint("x"), (false, true) => colours.special_other().paint("S"), (true, true) => colours.special_other().paint("s"), }; } fn other_execute_bit(&self, colours: &C) -> ANSIString<'static> { #[rustfmt::skip] return match (self.other_execute, self.sticky) { (false, false) => colours.dash().paint("-"), (true, false) => colours.other_execute().paint("x"), (false, true) => colours.special_other().paint("T"), (true, true) => colours.special_other().paint("t"), }; } } #[cfg(windows)] impl f::Attributes { pub fn render(self, colours: &C) -> Vec> { let bit = |bit, chr: &'static str, style: Style| { if bit { style.paint(chr) } else { colours.dash().paint("-") } }; vec![ bit(self.archive, "a", colours.normal()), bit(self.readonly, "r", colours.user_read()), bit(self.hidden, "h", colours.special_user_file()), bit(self.system, "s", colours.special_other()), ] } pub fn render_type(self, colours: &C) -> ANSIString<'static> { if self.reparse_point { return colours.pipe().paint("l"); } else if self.directory { return colours.directory().paint("d"); } colours.dash().paint("-") } } pub trait Colours { fn dash(&self) -> Style; fn user_read(&self) -> Style; fn user_write(&self) -> Style; fn user_execute_file(&self) -> Style; fn user_execute_other(&self) -> Style; fn group_read(&self) -> Style; fn group_write(&self) -> Style; fn group_execute(&self) -> Style; fn other_read(&self) -> Style; fn other_write(&self) -> Style; fn other_execute(&self) -> Style; fn special_user_file(&self) -> Style; fn special_other(&self) -> Style; fn attribute(&self) -> Style; } #[cfg(test)] #[allow(unused_results)] pub mod test { use super::{Colours, RenderPermissions}; use crate::fs::fields as f; use crate::output::cell::TextCellContents; use ansiterm::Colour::*; use ansiterm::Style; struct TestColours; #[rustfmt::skip] impl Colours for TestColours { fn dash(&self) -> Style { Fixed(11).normal() } fn user_read(&self) -> Style { Fixed(101).normal() } fn user_write(&self) -> Style { Fixed(102).normal() } fn user_execute_file(&self) -> Style { Fixed(103).normal() } fn user_execute_other(&self) -> Style { Fixed(113).normal() } fn group_read(&self) -> Style { Fixed(104).normal() } fn group_write(&self) -> Style { Fixed(105).normal() } fn group_execute(&self) -> Style { Fixed(106).normal() } fn other_read(&self) -> Style { Fixed(107).normal() } fn other_write(&self) -> Style { Fixed(108).normal() } fn other_execute(&self) -> Style { Fixed(109).normal() } fn special_user_file(&self) -> Style { Fixed(110).normal() } fn special_other(&self) -> Style { Fixed(111).normal() } fn attribute(&self) -> Style { Fixed(112).normal() } } #[test] fn negate() { let bits = Some(f::Permissions { user_read: false, user_write: false, user_execute: false, setuid: false, group_read: false, group_write: false, group_execute: false, setgid: false, other_read: false, other_write: false, other_execute: false, sticky: false, }); let expected = TextCellContents::from(vec![ Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), ]); assert_eq!(expected, bits.render(&TestColours, false).into()) } #[test] fn affirm() { let bits = Some(f::Permissions { user_read: true, user_write: true, user_execute: true, setuid: false, group_read: true, group_write: true, group_execute: true, setgid: false, other_read: true, other_write: true, other_execute: true, sticky: false, }); let expected = TextCellContents::from(vec![ Fixed(101).paint("r"), Fixed(102).paint("w"), Fixed(103).paint("x"), Fixed(104).paint("r"), Fixed(105).paint("w"), Fixed(106).paint("x"), Fixed(107).paint("r"), Fixed(108).paint("w"), Fixed(109).paint("x"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) } #[test] fn specials() { let bits = Some(f::Permissions { user_read: false, user_write: false, user_execute: true, setuid: true, group_read: false, group_write: false, group_execute: true, setgid: true, other_read: false, other_write: false, other_execute: true, sticky: true, }); let expected = TextCellContents::from(vec![ Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(110).paint("s"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("s"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("t"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) } #[test] fn extra_specials() { let bits = Some(f::Permissions { user_read: false, user_write: false, user_execute: false, setuid: true, group_read: false, group_write: false, group_execute: false, setgid: true, other_read: false, other_write: false, other_execute: false, sticky: true, }); let expected = TextCellContents::from(vec![ Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"), Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("T"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) } } eza-0.18.2/src/output/render/securityctx.rs000064400000000000000000000026271046102023000170440ustar 00000000000000use ansiterm::Style; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; impl f::SecurityContext<'_> { pub fn render(&self, colours: &C) -> TextCell { match &self.context { f::SecurityContextType::None => TextCell::paint_str(colours.none(), "?"), f::SecurityContextType::SELinux(context) => { let mut chars = Vec::with_capacity(7); for (i, part) in context.split(':').enumerate() { let partcolour = match i { 0 => colours.selinux_user(), 1 => colours.selinux_role(), 2 => colours.selinux_type(), _ => colours.selinux_range(), }; if i > 0 { chars.push(colours.selinux_colon().paint(":")); } chars.push(partcolour.paint(String::from(part))); } TextCell { contents: chars.into(), width: DisplayWidth::from(context.len()), } } } } } #[rustfmt::skip] pub trait Colours { fn none(&self) -> Style; fn selinux_colon(&self) -> Style; fn selinux_user(&self) -> Style; fn selinux_role(&self) -> Style; fn selinux_type(&self) -> Style; fn selinux_range(&self) -> Style; } eza-0.18.2/src/output/render/size.rs000064400000000000000000000166311046102023000154300ustar 00000000000000use ansiterm::Style; use locale::Numeric as NumericLocale; use number_prefix::Prefix; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::color_scale::{ColorScaleInformation, ColorScaleMode}; use crate::output::table::SizeFormat; impl f::Size { pub fn render( self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale, color_scale_info: Option, ) -> TextCell { use number_prefix::NumberPrefix; let size = match self { Self::Some(s) => s, Self::None => return TextCell::blank(colours.no_size()), Self::DeviceIDs(ref ids) => return ids.render(colours), }; let is_gradient_mode = color_scale_info.is_some_and(|csi| csi.options.mode == ColorScaleMode::Gradient); #[rustfmt::skip] let result = match size_format { SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), SizeFormat::JustBytes => { // Use the binary prefix to select a style. let prefix = match NumberPrefix::binary(size as f64) { NumberPrefix::Standalone(_) => None, NumberPrefix::Prefixed(p, _) => Some(p), }; // But format the number directly using the locale. let string = numerics.format_int(size); return if is_gradient_mode { let csi = color_scale_info.unwrap(); TextCell::paint( csi.adjust_style(colours.size(prefix), size as f32, csi.size), string, ) } else { TextCell::paint(colours.size(prefix), string) } } }; #[rustfmt::skip] let (prefix, n) = match result { NumberPrefix::Standalone(b) => { return if is_gradient_mode { let csi = color_scale_info.unwrap(); TextCell::paint( csi.adjust_style(colours.size(None), size as f32, csi.size), numerics.format_int(b), ) } else { TextCell::paint(colours.size(None), numerics.format_int(b)) } } NumberPrefix::Prefixed(p, n) => (p, n), }; let symbol = prefix.symbol(); let number = if n < 10_f64 { numerics.format_float(n, 1) } else { numerics.format_int(n.round() as isize) }; TextCell { // symbol is guaranteed to be ASCII since unit prefixes are hardcoded. width: DisplayWidth::from(&*number) + symbol.len(), contents: if is_gradient_mode { let csi = color_scale_info.unwrap(); vec![ csi.adjust_style(colours.size(Some(prefix)), size as f32, csi.size) .paint(number), csi.adjust_style(colours.size(Some(prefix)), size as f32, csi.size) .paint(symbol), ] } else { vec![ colours.size(Some(prefix)).paint(number), colours.unit(Some(prefix)).paint(symbol), ] } .into(), } } } impl f::DeviceIDs { fn render(self, colours: &C) -> TextCell { let major = self.major.to_string(); let minor = self.minor.to_string(); TextCell { width: DisplayWidth::from(major.len() + 1 + minor.len()), contents: vec![ colours.major().paint(major), colours.comma().paint(","), colours.minor().paint(minor), ] .into(), } } } pub trait Colours { fn size(&self, prefix: Option) -> Style; fn unit(&self, prefix: Option) -> Style; fn no_size(&self) -> Style; fn major(&self) -> Style; fn comma(&self) -> Style; fn minor(&self) -> Style; } #[cfg(test)] pub mod test { use super::Colours; use crate::fs::fields as f; use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::table::SizeFormat; use ansiterm::Colour::*; use ansiterm::Style; use locale::Numeric as NumericLocale; use number_prefix::Prefix; struct TestColours; #[rustfmt::skip] impl Colours for TestColours { fn size(&self, _prefix: Option) -> Style { Fixed(66).normal() } fn unit(&self, _prefix: Option) -> Style { Fixed(77).bold() } fn no_size(&self) -> Style { Black.italic() } fn major(&self) -> Style { Blue.on(Red) } fn comma(&self) -> Style { Green.italic() } fn minor(&self) -> Style { Cyan.on(Yellow) } } #[test] fn directory() { let directory = f::Size::None; let expected = TextCell::blank(Black.italic()); assert_eq!( expected, directory.render( &TestColours, SizeFormat::JustBytes, &NumericLocale::english(), None ) ) } #[test] fn file_decimal() { let directory = f::Size::Some(2_100_000); let expected = TextCell { width: DisplayWidth::from(4), contents: vec![Fixed(66).paint("2.1"), Fixed(77).bold().paint("M")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::DecimalBytes, &NumericLocale::english(), None ) ) } #[test] fn file_binary() { let directory = f::Size::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(5), contents: vec![Fixed(66).paint("1.0"), Fixed(77).bold().paint("Mi")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::BinaryBytes, &NumericLocale::english(), None ) ) } #[test] fn file_bytes() { let directory = f::Size::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(9), contents: vec![Fixed(66).paint("1,048,576")].into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::JustBytes, &NumericLocale::english(), None ) ) } #[test] fn device_ids() { let directory = f::Size::DeviceIDs(f::DeviceIDs { major: 10, minor: 80, }); let expected = TextCell { width: DisplayWidth::from(5), contents: vec![ Blue.on(Red).paint("10"), Green.italic().paint(","), Cyan.on(Yellow).paint("80"), ] .into(), }; assert_eq!( expected, directory.render( &TestColours, SizeFormat::JustBytes, &NumericLocale::english(), None ) ) } } eza-0.18.2/src/output/render/times.rs000064400000000000000000000012601046102023000155670ustar 00000000000000use crate::output::cell::TextCell; use crate::output::time::TimeFormat; use ansiterm::Style; use chrono::prelude::*; pub trait Render { fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell; } impl Render for Option { fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell { let datestamp = if let Some(time) = self { time_format.format(&DateTime::::from_naive_utc_and_offset( time, time_offset, )) } else { String::from("-") }; TextCell::paint(style, datestamp) } } eza-0.18.2/src/output/render/users.rs000064400000000000000000000075771046102023000156300ustar 00000000000000use ansiterm::Style; use uzers::Users; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::UserFormat; pub trait Render { fn render(self, colours: &C, users: &U, format: UserFormat) -> TextCell; } impl Render for Option { fn render(self, colours: &C, users: &U, format: UserFormat) -> TextCell { #[rustfmt::skip] let uid = match self { Some(u) => u.0, None => return TextCell::blank(colours.no_user()), }; #[rustfmt::skip] let user_name = match (format, users.get_user_by_uid(uid)) { (_, None) => uid.to_string(), (UserFormat::Numeric, _) => uid.to_string(), (UserFormat::Name, Some(user)) => user.name().to_string_lossy().into(), }; let style = if users.get_current_uid() == uid { colours.you() } else if uid == 0 { colours.root() } else { colours.other() }; TextCell::paint(style, user_name) } } pub trait Colours { fn you(&self) -> Style; fn other(&self) -> Style; fn root(&self) -> Style; fn no_user(&self) -> Style; } #[cfg(test)] #[allow(unused_results)] pub mod test { use super::{Colours, Render}; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::UserFormat; use ansiterm::Colour::*; use ansiterm::Style; use uzers::mock::MockUsers; use uzers::User; struct TestColours; #[rustfmt::skip] impl Colours for TestColours { fn you(&self) -> Style { Red.bold() } fn other(&self) -> Style { Blue.underline() } fn root(&self) -> Style { Blue.underline() } fn no_user(&self) -> Style { Black.italic() } } #[test] fn named() { let mut users = MockUsers::with_current_uid(1000); users.add_user(User::new(1000, "enoch", 100)); let user = Some(f::User(1000)); let expected = TextCell::paint_str(Red.bold(), "enoch"); #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name)); let expected = TextCell::paint_str(Red.bold(), "1000"); #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric)); } #[test] fn unnamed() { let users = MockUsers::with_current_uid(1000); let user = Some(f::User(1000)); let expected = TextCell::paint_str(Red.bold(), "1000"); #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name)); #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric)); } #[test] fn different_named() { let mut users = MockUsers::with_current_uid(0); users.add_user(User::new(1000, "enoch", 100)); let user = Some(f::User(1000)); let expected = TextCell::paint_str(Blue.underline(), "enoch"); assert_eq!( expected, user.render(&TestColours, &users, UserFormat::Name) ); } #[test] fn different_unnamed() { let user = Some(f::User(1000)); let expected = TextCell::paint_str(Blue.underline(), "1000"); assert_eq!( expected, user.render( &TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric ) ); } #[test] fn overflow() { let user = Some(f::User(2_147_483_648)); let expected = TextCell::paint_str(Blue.underline(), "2147483648"); assert_eq!( expected, user.render( &TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric ) ); } } eza-0.18.2/src/output/table.rs000064400000000000000000000423001046102023000142560ustar 00000000000000use std::cmp::max; use std::ops::Deref; #[cfg(unix)] use std::sync::{Mutex, MutexGuard}; use chrono::prelude::*; use log::*; use once_cell::sync::Lazy; #[cfg(unix)] use uzers::UsersCache; use crate::fs::feature::git::GitCache; use crate::fs::{fields as f, File}; use crate::options::vars::EZA_WINDOWS_ATTRIBUTES; use crate::options::Vars; use crate::output::cell::TextCell; use crate::output::color_scale::ColorScaleInformation; #[cfg(unix)] use crate::output::render::{GroupRender, OctalPermissionsRender, UserRender}; use crate::output::render::{PermissionsPlusRender, TimeRender}; use crate::output::time::TimeFormat; use crate::theme::Theme; use super::color_scale::ColorScaleMode; /// Options for displaying a table. #[derive(PartialEq, Eq, Debug)] pub struct Options { pub size_format: SizeFormat, pub time_format: TimeFormat, pub user_format: UserFormat, pub group_format: GroupFormat, pub flags_format: FlagsFormat, pub columns: Columns, } /// Extra columns to display in the table. #[allow(clippy::struct_excessive_bools)] #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Columns { /// At least one of these timestamps will be shown. pub time_types: TimeTypes, // The rest are just on/off pub inode: bool, pub links: bool, pub blocksize: bool, pub group: bool, pub git: bool, pub subdir_git_repos: bool, pub subdir_git_repos_no_stat: bool, pub octal: bool, pub security_context: bool, pub file_flags: bool, // Defaults to true: pub permissions: bool, pub filesize: bool, pub user: bool, } impl Columns { pub fn collect(&self, actually_enable_git: bool, git_repos: bool) -> Vec { let mut columns = Vec::with_capacity(4); if self.inode { #[cfg(unix)] columns.push(Column::Inode); } if self.octal { #[cfg(unix)] columns.push(Column::Octal); } if self.permissions { columns.push(Column::Permissions); } if self.links { #[cfg(unix)] columns.push(Column::HardLinks); } if self.filesize { columns.push(Column::FileSize); } if self.blocksize { #[cfg(unix)] columns.push(Column::Blocksize); } if self.user { #[cfg(unix)] columns.push(Column::User); } if self.group { #[cfg(unix)] columns.push(Column::Group); } if self.file_flags { columns.push(Column::FileFlags); } #[cfg(target_os = "linux")] if self.security_context { columns.push(Column::SecurityContext); } if self.time_types.modified { columns.push(Column::Timestamp(TimeType::Modified)); } if self.time_types.changed { columns.push(Column::Timestamp(TimeType::Changed)); } if self.time_types.created { columns.push(Column::Timestamp(TimeType::Created)); } if self.time_types.accessed { columns.push(Column::Timestamp(TimeType::Accessed)); } if self.git && actually_enable_git { columns.push(Column::GitStatus); } if self.subdir_git_repos && git_repos { columns.push(Column::SubdirGitRepo(true)); } if self.subdir_git_repos_no_stat && git_repos { columns.push(Column::SubdirGitRepo(false)); } columns } } /// A table contains these. #[derive(Debug, Copy, Clone)] pub enum Column { Permissions, FileSize, Timestamp(TimeType), #[cfg(unix)] Blocksize, #[cfg(unix)] User, #[cfg(unix)] Group, #[cfg(unix)] HardLinks, #[cfg(unix)] Inode, GitStatus, SubdirGitRepo(bool), #[cfg(unix)] Octal, #[cfg(unix)] SecurityContext, FileFlags, } /// Each column can pick its own **Alignment**. Usually, numbers are /// right-aligned, and text is left-aligned. #[derive(Copy, Clone)] pub enum Alignment { Left, Right, } impl Column { /// Get the alignment this column should use. #[cfg(unix)] pub fn alignment(self) -> Alignment { #[allow(clippy::wildcard_in_or_patterns)] match self { Self::FileSize | Self::HardLinks | Self::Inode | Self::Blocksize | Self::GitStatus => { Alignment::Right } Self::Timestamp(_) | _ => Alignment::Left, } } #[cfg(windows)] pub fn alignment(self) -> Alignment { match self { Self::FileSize | Self::GitStatus => Alignment::Right, _ => Alignment::Left, } } /// Get the text that should be printed at the top, when the user elects /// to have a header row printed. pub fn header(self) -> &'static str { match self { #[cfg(unix)] Self::Permissions => "Permissions", #[cfg(windows)] Self::Permissions => "Mode", Self::FileSize => "Size", Self::Timestamp(t) => t.header(), #[cfg(unix)] Self::Blocksize => "Blocksize", #[cfg(unix)] Self::User => "User", #[cfg(unix)] Self::Group => "Group", #[cfg(unix)] Self::HardLinks => "Links", #[cfg(unix)] Self::Inode => "inode", Self::GitStatus => "Git", Self::SubdirGitRepo(_) => "Repo", #[cfg(unix)] Self::Octal => "Octal", #[cfg(unix)] Self::SecurityContext => "Security Context", Self::FileFlags => "Flags", } } } /// Formatting options for file sizes. #[allow(clippy::enum_variant_names)] #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SizeFormat { /// Format the file size using **decimal** prefixes, such as “kilo”, /// “mega”, or “giga”. DecimalBytes, /// Format the file size using **binary** prefixes, such as “kibi”, /// “mebi”, or “gibi”. BinaryBytes, /// Do no formatting and just display the size as a number of bytes. JustBytes, } /// Formatting options for user and group. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum UserFormat { /// The UID / GID Numeric, /// Show the name Name, } /// Formatting options for group only. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum GroupFormat { /// Numeric or text value Regular, /// Show ":" if user-group value is the same Smart, } impl Default for SizeFormat { fn default() -> Self { Self::DecimalBytes } } /// The types of a file’s time fields. These three fields are standard /// across most (all?) operating systems. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TimeType { /// The file’s modified time (`st_mtime`). Modified, /// The file’s changed time (`st_ctime`) Changed, /// The file’s accessed time (`st_atime`). Accessed, /// The file’s creation time (`btime` or `birthtime`). Created, } impl TimeType { /// Returns the text to use for a column’s heading in the columns output. pub fn header(self) -> &'static str { match self { Self::Modified => "Date Modified", Self::Changed => "Date Changed", Self::Accessed => "Date Accessed", Self::Created => "Date Created", } } /// Returns the corresponding time from [File] pub fn get_corresponding_time(self, file: &File<'_>) -> Option { match self { TimeType::Modified => file.modified_time(), TimeType::Changed => file.changed_time(), TimeType::Accessed => file.accessed_time(), TimeType::Created => file.created_time(), } } } /// How display file flags. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum FlagsFormat { /// Display flags as comma seperated descriptions Long, /// Display flags as single character abbreviations (Windows only) Short, } impl Default for FlagsFormat { fn default() -> Self { Self::Long } } impl FlagsFormat { pub(crate) fn deduce(vars: &V) -> FlagsFormat { vars.get(EZA_WINDOWS_ATTRIBUTES) .and_then(|v| match v.to_ascii_lowercase().to_str() { Some("short") => Some(FlagsFormat::Short), Some("long") => Some(FlagsFormat::Long), _ => None, }) .unwrap_or_default() } } /// Fields for which of a file’s time fields should be displayed in the /// columns output. /// /// There should always be at least one of these — there’s no way to disable /// the time columns entirely (yet). #[derive(PartialEq, Eq, Debug, Copy, Clone)] #[rustfmt::skip] #[allow(clippy::struct_excessive_bools)] pub struct TimeTypes { pub modified: bool, pub changed: bool, pub accessed: bool, pub created: bool, } impl Default for TimeTypes { /// By default, display just the ‘modified’ time. This is the most /// common option, which is why it has this shorthand. fn default() -> Self { Self { modified: true, changed: false, accessed: false, created: false, } } } /// The **environment** struct contains any data that could change between /// running instances of exa, depending on the user’s computer’s configuration. /// /// Any environment field should be able to be mocked up for test runs. pub struct Environment { /// The computer’s current time offset, determined from time zone. time_offset: FixedOffset, /// Localisation rules for formatting numbers. numeric: locale::Numeric, /// Mapping cache of user IDs to usernames. #[cfg(unix)] users: Mutex, } impl Environment { #[cfg(unix)] pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> { self.users.lock().unwrap() } fn load_all() -> Self { let time_offset = *Local::now().offset(); let numeric = locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()); #[cfg(unix)] let users = Mutex::new(UsersCache::new()); Self { time_offset, numeric, #[cfg(unix)] users, } } } static ENVIRONMENT: Lazy = Lazy::new(Environment::load_all); pub struct Table<'a> { columns: Vec, theme: &'a Theme, env: &'a Environment, widths: TableWidths, time_format: TimeFormat, size_format: SizeFormat, #[cfg(unix)] user_format: UserFormat, #[cfg(unix)] group_format: GroupFormat, flags_format: FlagsFormat, git: Option<&'a GitCache>, } #[derive(Clone)] pub struct Row { cells: Vec, } impl<'a> Table<'a> { pub fn new( options: &'a Options, git: Option<&'a GitCache>, theme: &'a Theme, git_repos: bool, ) -> Table<'a> { let columns = options.columns.collect(git.is_some(), git_repos); let widths = TableWidths::zero(columns.len()); let env = &*ENVIRONMENT; debug!("Creating table with columns: {:?}", columns); Table { theme, widths, columns, git, env, time_format: options.time_format.clone(), size_format: options.size_format, #[cfg(unix)] user_format: options.user_format, #[cfg(unix)] group_format: options.group_format, flags_format: options.flags_format, } } pub fn widths(&self) -> &TableWidths { &self.widths } pub fn header_row(&self) -> Row { let cells = self .columns .iter() .map(|c| TextCell::paint_str(self.theme.ui.header, c.header())) .collect(); Row { cells } } pub fn row_for_file( &self, file: &File<'_>, xattrs: bool, color_scale_info: Option, ) -> Row { let cells = self .columns .iter() .map(|c| self.display(file, *c, xattrs, color_scale_info)) .collect(); Row { cells } } pub fn add_widths(&mut self, row: &Row) { self.widths.add_widths(row); } #[cfg(unix)] fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> Option { file.permissions().map(|p| f::PermissionsPlus { file_type: file.type_char(), permissions: p, xattrs, }) } #[allow(clippy::unnecessary_wraps)] // Needs to match Unix function #[cfg(windows)] fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> Option { Some(f::PermissionsPlus { file_type: file.type_char(), #[cfg(windows)] attributes: file.attributes(), xattrs, }) } #[cfg(unix)] fn octal_permissions(&self, file: &File<'_>) -> Option { file.permissions() .map(|p| f::OctalPermissions { permissions: p }) } fn display( &self, file: &File<'_>, column: Column, xattrs: bool, color_scale_info: Option, ) -> TextCell { match column { Column::Permissions => self.permissions_plus(file, xattrs).render(self.theme), Column::FileSize => file.size().render( self.theme, self.size_format, &self.env.numeric, color_scale_info, ), #[cfg(unix)] Column::HardLinks => file.links().render(self.theme, &self.env.numeric), #[cfg(unix)] Column::Inode => file.inode().render(self.theme.ui.inode), #[cfg(unix)] Column::Blocksize => { file.blocksize() .render(self.theme, self.size_format, &self.env.numeric) } #[cfg(unix)] Column::User => { file.user() .render(self.theme, &*self.env.lock_users(), self.user_format) } #[cfg(unix)] Column::Group => file.group().render( self.theme, &*self.env.lock_users(), self.user_format, self.group_format, file.user(), ), #[cfg(unix)] Column::SecurityContext => file.security_context().render(self.theme), Column::FileFlags => file.flags().render(self.theme.ui.flags, self.flags_format), Column::GitStatus => self.git_status(file).render(self.theme), Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme), #[cfg(unix)] Column::Octal => self.octal_permissions(file).render(self.theme.ui.octal), Column::Timestamp(time_type) => time_type.get_corresponding_time(file).render( if color_scale_info.is_some_and(|csi| csi.options.mode == ColorScaleMode::Gradient) { color_scale_info.unwrap().apply_time_gradient( self.theme.ui.date, file, time_type, ) } else { self.theme.ui.date }, self.env.time_offset, self.time_format.clone(), ), } } fn git_status(&self, file: &File<'_>) -> f::Git { debug!("Getting Git status for file {:?}", file.path); self.git .map(|g| g.get(&file.path, file.is_directory())) .unwrap_or_default() } fn subdir_git_repo(&self, file: &File<'_>, status: bool) -> f::SubdirGitRepo { debug!("Getting subdir repo status for path {:?}", file.path); if file.is_directory() { return f::SubdirGitRepo::from_path(&file.path, status); } f::SubdirGitRepo::default() } pub fn render(&self, row: Row) -> TextCell { let mut cell = TextCell::default(); let iter = row.cells.into_iter().zip(self.widths.iter()).enumerate(); for (n, (this_cell, width)) in iter { let padding = width - *this_cell.width; match self.columns[n].alignment() { Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); } Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); } } cell.add_spaces(1); } cell } } pub struct TableWidths(Vec); impl Deref for TableWidths { type Target = [usize]; fn deref(&self) -> &Self::Target { &self.0 } } impl TableWidths { pub fn zero(count: usize) -> Self { Self(vec![0; count]) } pub fn add_widths(&mut self, row: &Row) { for (old_width, cell) in self.0.iter_mut().zip(row.cells.iter()) { *old_width = max(*old_width, *cell.width); } } pub fn total(&self) -> usize { self.0.len() + self.0.iter().sum::() } } eza-0.18.2/src/output/time.rs000064400000000000000000000166141046102023000141360ustar 00000000000000//! Timestamp formatting. use chrono::prelude::*; use core::cmp::max; use once_cell::sync::Lazy; use std::time::Duration; use unicode_width::UnicodeWidthStr; /// Every timestamp in exa needs to be rendered by a **time format**. /// Formatting times is tricky, because how a timestamp is rendered can /// depend on one or more of the following: /// /// - The user’s locale, for printing the month name as “Feb”, or as “fév”, /// or as “2月”; /// - The current year, because certain formats will be less precise when /// dealing with dates far in the past; /// - The formatting style that the user asked for on the command-line. /// /// Because not all formatting styles need the same data, they all have their /// own enum variants. It’s not worth looking the locale up if the formatter /// prints month names as numbers. /// /// Also, eza supports *custom* styles, where the user enters a /// format string in an environment variable or something. Just these four. #[derive(PartialEq, Eq, Debug, Clone)] pub enum TimeFormat { /// The **default format** uses the user’s locale to print month names, /// and specifies the timestamp down to the minute for recent times, and /// day for older times. DefaultFormat, /// Use the **ISO format**, which specifies the timestamp down to the /// minute for recent times, and day for older times. It uses a number /// for the month so it doesn’t use the locale. ISOFormat, /// Use the **long ISO format**, which specifies the timestamp down to the /// minute using only numbers, without needing the locale or year. LongISO, /// Use the **full ISO format**, which specifies the timestamp down to the /// millisecond and includes its offset down to the minute. This too uses /// only numbers so doesn’t require any special consideration. FullISO, /// Use a relative but fixed width representation. Relative, /// Use custom formats, optionally a different custom format can be /// specified for recent times, otherwise the same custom format will be /// used for both recent and non-recent times. Custom { non_recent: String, recent: Option, }, } impl TimeFormat { pub fn format(self, time: &DateTime) -> String { #[rustfmt::skip] return match self { Self::DefaultFormat => default(time), Self::ISOFormat => iso(time), Self::LongISO => long(time), Self::FullISO => full(time), Self::Relative => relative(time), Self::Custom { non_recent, recent } => custom( time, non_recent.as_str(), recent.as_deref() ), }; } } fn default(time: &DateTime) -> String { let month = &*LOCALE.short_month_name(time.month0() as usize); let month_width = short_month_padding(*MAX_MONTH_WIDTH, month); let format = if time.year() == *CURRENT_YEAR { format!("%_d {month: usize { let shift = month.chars().count() as isize - UnicodeWidthStr::width(month) as isize; (max_month_width as isize + shift) as usize } fn iso(time: &DateTime) -> String { if time.year() == *CURRENT_YEAR { time.format("%m-%d %H:%M").to_string() } else { time.format("%Y-%m-%d").to_string() } } fn long(time: &DateTime) -> String { time.format("%Y-%m-%d %H:%M").to_string() } // #[allow(trivial_numeric_casts)] fn relative(time: &DateTime) -> String { timeago::Formatter::new() .ago("") .convert(Duration::from_secs( max(0, Local::now().timestamp() - time.timestamp()) // this .unwrap is safe since the call above can never result in a // value < 0 .try_into() .unwrap(), )) } fn full(time: &DateTime) -> String { time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string() } fn custom(time: &DateTime, non_recent_fmt: &str, recent_fmt: Option<&str>) -> String { if let Some(recent_fmt) = recent_fmt { if time.year() == *CURRENT_YEAR { time.format(recent_fmt).to_string() } else { time.format(non_recent_fmt).to_string() } } else { time.format(non_recent_fmt).to_string() } } static CURRENT_YEAR: Lazy = Lazy::new(|| Local::now().year()); static LOCALE: Lazy = Lazy::new(|| locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english())); static MAX_MONTH_WIDTH: Lazy = Lazy::new(|| { // Some locales use a three-character wide month name (Jan to Dec); // others vary between three to four (1月 to 12月, juil.). We check each month width // to detect the longest and set the output format accordingly. (0..11) .map(|i| UnicodeWidthStr::width(&*LOCALE.short_month_name(i))) .max() .unwrap() }); #[cfg(test)] mod test { use super::*; #[test] fn short_month_width_japanese() { let max_month_width = 4; let month = "1\u{2F49}"; // 1月 let padding = short_month_padding(max_month_width, month); let final_str = format!("{: &'static str { #[rustfmt::skip] return match self { Self::Edge => "├──", Self::Line => "│ ", Self::Corner => "└──", Self::Blank => " ", }; } } /// A **tree trunk** builds up arrays of tree parts over multiple depths. #[derive(Debug, Default)] pub struct TreeTrunk { /// A stack tracks which tree characters should be printed. It’s /// necessary to maintain information about the previously-printed /// lines, as the output will change based on any previous entries. stack: Vec, /// A tuple for the last ‘depth’ and ‘last’ parameters that are passed in. last_params: Option, } #[derive(Debug, Copy, Clone)] pub struct TreeParams { /// How many directories deep into the tree structure this is. Directories /// on top have depth 0. depth: TreeDepth, /// Whether this is the last entry in the directory. last: bool, } #[derive(Debug, Copy, Clone)] pub struct TreeDepth(pub usize); impl TreeTrunk { /// Calculates the tree parts for an entry at the given depth and /// last-ness. The depth is used to determine where in the stack the tree /// part should be inserted, and the last-ness is used to determine which /// type of tree part to insert. /// /// This takes a `&mut self` because the results of each file are stored /// and used in future rows. pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] { // If this isn’t our first iteration, then update the tree parts thus // far to account for there being another row after it. if let Some(last) = self.last_params { self.stack[last.depth.0] = if last.last { TreePart::Blank } else { TreePart::Line }; } // Make sure the stack has enough space, then add or modify another // part into it. self.stack.resize(params.depth.0 + 1, TreePart::Edge); self.stack[params.depth.0] = if params.last { TreePart::Corner } else { TreePart::Edge }; self.last_params = Some(params); // Return the tree parts as a slice of the stack. // // Ignore the first element here to prevent a ‘zeroth level’ from // appearing before the very first directory. This level would // join unrelated directories without connecting to anything: // // with [0..] with [1..] // ========== ========== // ├── folder folder // │ └── file └── file // └── folder folder // └── file └──file // &self.stack[1..] } } impl TreeParams { pub fn new(depth: TreeDepth, last: bool) -> Self { Self { depth, last } } pub fn is_at_root(&self) -> bool { self.depth.0 == 0 } } impl TreeDepth { pub fn root() -> Self { Self(0) } pub fn deeper(self) -> Self { Self(self.0 + 1) } /// Creates an iterator that, as well as yielding each value, yields a /// `TreeParams` with the current depth and last flag filled in. pub fn iterate_over(self, inner: I) -> Iter where I: ExactSizeIterator + Iterator, { Iter { current_depth: self, inner, } } } pub struct Iter { current_depth: TreeDepth, inner: I, } impl Iterator for Iter where I: ExactSizeIterator + Iterator, { type Item = (TreeParams, T); fn next(&mut self) -> Option { let t = self.inner.next()?; // TODO: use exact_size_is_empty API soon let params = TreeParams::new(self.current_depth, self.inner.len() == 0); Some((params, t)) } } #[cfg(test)] mod trunk_test { use super::*; fn params(depth: usize, last: bool) -> TreeParams { TreeParams::new(TreeDepth(depth), last) } #[rustfmt::skip] #[test] fn empty_at_first() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, true)), &[ ]); } #[rustfmt::skip] #[test] fn one_child() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, true)), &[ ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } #[rustfmt::skip] #[test] fn two_children() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, true)), &[ ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } #[rustfmt::skip] #[test] fn two_times_two_children() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, false)), &[ ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(0, true)), &[ ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } #[rustfmt::skip] #[test] fn two_times_two_nested_children() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, true)), &[ ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]); assert_eq!(tt.new_row(params(2, true)), &[ TreePart::Line, TreePart::Corner ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Blank, TreePart::Edge ]); assert_eq!(tt.new_row(params(2, true)), &[ TreePart::Blank, TreePart::Corner ]); } } #[cfg(test)] mod iter_test { use super::*; #[test] fn test_iteration() { let foos = &["first", "middle", "last"]; let mut iter = TreeDepth::root().iterate_over(foos.iter()); let next = iter.next().unwrap(); assert_eq!(&"first", next.1); assert!(!next.0.last); let next = iter.next().unwrap(); assert_eq!(&"middle", next.1); assert!(!next.0.last); let next = iter.next().unwrap(); assert_eq!(&"last", next.1); assert!(next.0.last); assert!(iter.next().is_none()); } #[test] fn test_empty() { let nothing: &[usize] = &[]; let mut iter = TreeDepth::root().iterate_over(nothing.iter()); assert!(iter.next().is_none()); } } eza-0.18.2/src/theme/default_theme.rs000064400000000000000000000131001046102023000155330ustar 00000000000000use ansiterm::Colour::*; use ansiterm::Style; use std::default::Default; use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions}; use crate::theme::ui_styles::*; impl UiStyles { pub fn default_theme(scale: ColorScaleOptions) -> Self { Self { colourful: true, #[rustfmt::skip] filekinds: FileKinds { normal: Style::default(), directory: Blue.bold(), symlink: Cyan.normal(), pipe: Yellow.normal(), block_device: Yellow.bold(), char_device: Yellow.bold(), socket: Red.bold(), special: Yellow.normal(), executable: Green.bold(), mount_point: Blue.bold().underline(), }, #[rustfmt::skip] perms: Permissions { user_read: Yellow.bold(), user_write: Red.bold(), user_execute_file: Green.bold().underline(), user_execute_other: Green.bold(), group_read: Yellow.normal(), group_write: Red.normal(), group_execute: Green.normal(), other_read: Yellow.normal(), other_write: Red.normal(), other_execute: Green.normal(), special_user_file: Purple.normal(), special_other: Purple.normal(), attribute: Style::default(), }, size: Size::colourful(scale), #[rustfmt::skip] users: Users { user_you: Yellow.bold(), user_other: Style::default(), user_root: Style::default(), group_yours: Yellow.bold(), group_other: Style::default(), group_root: Style::default(), }, #[rustfmt::skip] links: Links { normal: Red.bold(), multi_link_file: Red.on(Yellow), }, #[rustfmt::skip] git: Git { new: Green.normal(), modified: Blue.normal(), deleted: Red.normal(), renamed: Yellow.normal(), typechange: Purple.normal(), ignored: Style::default().dimmed(), conflicted: Red.normal(), }, git_repo: GitRepo { branch_main: Green.normal(), branch_other: Yellow.normal(), git_clean: Green.normal(), git_dirty: Yellow.bold(), }, security_context: SecurityContext { none: Style::default(), #[rustfmt::skip] selinux: SELinuxContext { colon: Style::default().dimmed(), user: Blue.normal(), role: Green.normal(), typ: Yellow.normal(), range: Cyan.normal(), }, }, #[rustfmt::skip] file_type: FileType { image: Purple.normal(), video: Purple.bold(), music: Cyan.normal(), lossless: Cyan.bold(), crypto: Green.bold(), document: Green.normal(), compressed: Red.normal(), temp: White.normal(), compiled: Yellow.normal(), build: Yellow.bold().underline(), source: Yellow.bold(), // Need to discuss color }, punctuation: DarkGray.bold(), date: Blue.normal(), inode: Purple.normal(), blocks: Cyan.normal(), octal: Purple.normal(), flags: Style::default(), header: Style::default().underline(), symlink_path: Cyan.normal(), control_char: Red.normal(), broken_symlink: Red.normal(), broken_path_overlay: Style::default().underline(), } } } impl Size { pub fn colourful(scale: ColorScaleOptions) -> Self { if scale.size && scale.mode == ColorScaleMode::Fixed { Self::colourful_fixed() } else { Self::colourful_gradient() } } fn colourful_fixed() -> Self { Self { major: Green.bold(), minor: Green.normal(), number_byte: Green.bold(), number_kilo: Green.bold(), number_mega: Green.bold(), number_giga: Green.bold(), number_huge: Green.bold(), unit_byte: Green.normal(), unit_kilo: Green.normal(), unit_mega: Green.normal(), unit_giga: Green.normal(), unit_huge: Green.normal(), } } fn colourful_gradient() -> Self { Self { major: Green.bold(), minor: Green.normal(), number_byte: Green.normal(), number_kilo: Green.bold(), number_mega: Yellow.normal(), number_giga: Red.normal(), number_huge: Purple.normal(), unit_byte: Green.normal(), unit_kilo: Green.bold(), unit_mega: Yellow.normal(), unit_giga: Red.normal(), unit_huge: Purple.normal(), } } } eza-0.18.2/src/theme/lsc.rs000064400000000000000000000235561046102023000135260ustar 00000000000000use std::iter::Peekable; use std::ops::FnMut; use ansiterm::Colour::*; use ansiterm::{Colour, Style}; // Parsing the LS_COLORS environment variable into a map of names to Style values. // // This is sitting around undocumented at the moment because it’s a feature // that should really be unnecessary! exa highlights its output by creating a // theme of one Style value per part of the interface that can be coloured, // then reading styles from that theme. The LS_COLORS variable, on the other // hand, can contain arbitrary characters that ls is supposed to add to the // output, without needing to know what they actually do. This puts exa in the // annoying position of having to parse the ANSI escape codes _back_ into // Style values before it’s able to use them. Doing this has a lot of // downsides: if a new terminal feature is added with its own code, exa won’t // be able to use this without explicit support for parsing the feature, while // ls would not even need to know it existed. And there are some edge cases in // ANSI codes, where terminals would accept codes exa is strict about it. It’s // just not worth doing, and there should really be a way to just use slices // of the LS_COLORS string without having to parse them. pub struct LSColors<'var>(pub &'var str); impl<'var> LSColors<'var> { pub fn each_pair(&mut self, mut callback: C) where C: FnMut(Pair<'var>), { for next in self.0.split(':') { let bits = next.split('=').take(3).collect::>(); if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() { callback(Pair { key: bits[0], value: bits[1], }); } } } } fn parse_into_high_colour<'a, I>(iter: &mut Peekable) -> Option where I: Iterator, { match iter.peek() { Some(&"5") => { let _ = iter.next(); if let Some(byte) = iter.next() { if let Ok(num) = byte.parse() { return Some(Fixed(num)); } } } Some(&"2") => { let _ = iter.next(); if let Some(hexes) = iter.next() { // Some terminals support R:G:B instead of R;G;B // but this clashes with splitting on ‘:’ in each_pair above. /*if hexes.contains(':') { let rgb = hexes.splitn(3, ':').collect::>(); if rgb.len() != 3 { return None; } else if let (Ok(r), Ok(g), Ok(b)) = (rgb[0].parse(), rgb[1].parse(), rgb[2].parse()) { return Some(RGB(r, g, b)); } }*/ if let (Some(r), Some(g), Some(b)) = ( hexes.parse().ok(), iter.next().and_then(|s| s.parse().ok()), iter.next().and_then(|s| s.parse().ok()), ) { return Some(RGB(r, g, b)); } } } _ => {} } None } pub struct Pair<'var> { pub key: &'var str, pub value: &'var str, } impl<'var> Pair<'var> { pub fn to_style(&self) -> Style { let mut style = Style::default(); let mut iter = self.value.split(';').peekable(); while let Some(num) = iter.next() { match num.trim_start_matches('0') { // Bold and italic "1" => style = style.bold(), "2" => style = style.dimmed(), "3" => style = style.italic(), "4" => style = style.underline(), "5" => style = style.blink(), // 6 is supposedly a faster blink "7" => style = style.reverse(), "8" => style = style.hidden(), "9" => style = style.strikethrough(), // Foreground colours "30" => style = style.fg(Black), "31" => style = style.fg(Red), "32" => style = style.fg(Green), "33" => style = style.fg(Yellow), "34" => style = style.fg(Blue), "35" => style = style.fg(Purple), "36" => style = style.fg(Cyan), "37" => style = style.fg(White), // Bright foreground colours "90" => style = style.fg(DarkGray), "91" => style = style.fg(BrightRed), "92" => style = style.fg(BrightGreen), "93" => style = style.fg(BrightYellow), "94" => style = style.fg(BrightBlue), "95" => style = style.fg(BrightPurple), "96" => style = style.fg(BrightCyan), "97" => style = style.fg(BrightGray), "38" => { if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c); } } // Background colours "40" => style = style.on(Black), "41" => style = style.on(Red), "42" => style = style.on(Green), "43" => style = style.on(Yellow), "44" => style = style.on(Blue), "45" => style = style.on(Purple), "46" => style = style.on(Cyan), "47" => style = style.on(White), // Bright background colours "100" => style = style.on(DarkGray), "101" => style = style.on(BrightRed), "102" => style = style.on(BrightGreen), "103" => style = style.on(BrightYellow), "104" => style = style.on(BrightBlue), "105" => style = style.on(BrightPurple), "106" => style = style.on(BrightCyan), "107" => style = style.on(BrightGray), "48" => { if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c); } } _ => { /* ignore the error and do nothing */ } } } style } } #[cfg(test)] mod ansi_test { use super::*; use ansiterm::Style; macro_rules! test { ($name:ident: $input:expr => $result:expr) => { #[test] fn $name() { assert_eq!( Pair { key: "", value: $input } .to_style(), $result ); } }; } // Styles test!(bold: "1" => Style::default().bold()); test!(bold2: "01" => Style::default().bold()); test!(under: "4" => Style::default().underline()); test!(unde2: "04" => Style::default().underline()); test!(both: "1;4" => Style::default().bold().underline()); test!(both2: "01;04" => Style::default().bold().underline()); test!(fg: "31" => Red.normal()); test!(bg: "43" => Style::default().on(Yellow)); test!(bfg: "31;43" => Red.on(Yellow)); test!(bfg2: "0031;0043" => Red.on(Yellow)); test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline()); test!(again: "1;1;1;1;1" => Style::default().bold()); // Failure cases test!(empty: "" => Style::default()); test!(semis: ";;;;;;" => Style::default()); test!(nines: "99999999" => Style::default()); test!(word: "GREEN" => Style::default()); // Higher colours test!(hifg: "38;5;149" => Fixed(149).normal()); test!(hibg: "48;5;1" => Style::default().on(Fixed(1))); test!(hibo: "48;5;1;1" => Style::default().on(Fixed(1)).bold()); test!(hiund: "4;48;5;1" => Style::default().on(Fixed(1)).underline()); test!(rgb: "38;2;255;100;0" => Style::default().fg(RGB(255, 100, 0))); test!(rgbi: "38;2;255;100;0;3" => Style::default().fg(RGB(255, 100, 0)).italic()); test!(rgbbg: "48;2;255;100;0" => Style::default().on(RGB(255, 100, 0))); test!(rgbbi: "48;2;255;100;0;3" => Style::default().on(RGB(255, 100, 0)).italic()); test!(fgbg: "38;5;121;48;5;212" => Fixed(121).on(Fixed(212))); test!(bgfg: "48;5;121;38;5;212" => Fixed(212).on(Fixed(121))); test!(toohi: "48;5;999" => Style::default()); } #[cfg(test)] mod test { use super::*; macro_rules! test { ($name:ident: $input:expr => $result:expr) => { #[test] fn $name() { let mut lscs = Vec::new(); LSColors($input).each_pair(|p| lscs.push((p.key.clone(), p.to_style()))); assert_eq!(lscs, $result.to_vec()); } }; } // Bad parses test!(empty: "" => []); test!(jibber: "blah" => []); test!(equals: "=" => []); test!(starts: "=di" => []); test!(ends: "id=" => []); // Foreground colours test!(green: "cb=32" => [ ("cb", Green.normal()) ]); test!(red: "di=31" => [ ("di", Red.normal()) ]); test!(blue: "la=34" => [ ("la", Blue.normal()) ]); // Background colours test!(yellow: "do=43" => [ ("do", Style::default().on(Yellow)) ]); test!(purple: "re=45" => [ ("re", Style::default().on(Purple)) ]); test!(cyan: "mi=46" => [ ("mi", Style::default().on(Cyan)) ]); // Bold and underline test!(bold: "fa=1" => [ ("fa", Style::default().bold()) ]); test!(under: "so=4" => [ ("so", Style::default().underline()) ]); test!(both: "la=1;4" => [ ("la", Style::default().bold().underline()) ]); // More and many test!(more: "me=43;21;55;34:yu=1;4;1" => [ ("me", Blue.on(Yellow)), ("yu", Style::default().bold().underline()) ]); test!(many: "red=31:green=32:blue=34" => [ ("red", Red.normal()), ("green", Green.normal()), ("blue", Blue.normal()) ]); } eza-0.18.2/src/theme/mod.rs000064400000000000000000001002331046102023000135100ustar 00000000000000use ansiterm::Style; use crate::fs::File; use crate::info::filetype::FileType; use crate::output::color_scale::ColorScaleOptions; use crate::output::file_name::Colours as FileNameColours; use crate::output::render; mod ui_styles; pub use self::ui_styles::UiStyles; mod lsc; pub use self::lsc::LSColors; mod default_theme; #[derive(PartialEq, Eq, Debug)] pub struct Options { pub use_colours: UseColours, pub colour_scale: ColorScaleOptions, pub definitions: Definitions, } /// Under what circumstances we should display coloured, rather than plain, /// output to the terminal. /// /// By default, we want to display the colours when stdout can display them. /// Turning them on when output is going to, say, a pipe, would make programs /// such as `grep` or `more` not work properly. So the `Automatic` mode does /// this check and only displays colours when they can be truly appreciated. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum UseColours { /// Display them even when output isn’t going to a terminal. Always, /// Display them when output is going to a terminal, but not otherwise. Automatic, /// Never display them, even when output is going to a terminal. Never, } #[derive(PartialEq, Eq, Debug, Default)] pub struct Definitions { pub ls: Option, pub exa: Option, } pub struct Theme { pub ui: UiStyles, pub exts: Box, } impl Options { pub fn to_theme(&self, isatty: bool) -> Theme { if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && !isatty) { let ui = UiStyles::plain(); let exts = Box::new(NoFileStyle); return Theme { ui, exts }; } // Parse the environment variables into colours and extension mappings let mut ui = UiStyles::default_theme(self.colour_scale); let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui); // Use between 0 and 2 file name highlighters let exts: Box = match (exts.is_non_empty(), use_default_filetypes) { (false, false) => Box::new(NoFileStyle), (false, true) => Box::new(FileTypes), (true, false) => Box::new(exts), (true, true) => Box::new((exts, FileTypes)), }; Theme { ui, exts } } } impl Definitions { /// Parse the environment variables into `LS_COLORS` pairs, putting file glob /// colours into the `ExtensionMappings` that gets returned, and using the /// two-character UI codes to modify the mutable `Colours`. /// /// Also returns if the `EZA_COLORS` variable should reset the existing file /// type mappings or not. The `reset` code needs to be the first one. fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) { use log::*; let mut exts = ExtensionMappings::default(); if let Some(lsc) = &self.ls { LSColors(lsc).each_pair(|pair| { if !colours.set_ls(&pair) { match glob::Pattern::new(pair.key) { Ok(pat) => { exts.add(pat, pair.to_style()); } Err(e) => { warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e); } } } }); } let mut use_default_filetypes = true; if let Some(exa) = &self.exa { // Is this hacky? Yes. if exa == "reset" || exa.starts_with("reset:") { use_default_filetypes = false; } LSColors(exa).each_pair(|pair| { if !colours.set_ls(&pair) && !colours.set_exa(&pair) { match glob::Pattern::new(pair.key) { Ok(pat) => { exts.add(pat, pair.to_style()); } Err(e) => { warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e); } } }; }); } (exts, use_default_filetypes) } } /// Determine the style to paint the text for the filename part of the output. pub trait FileStyle: Sync { /// Return the style to paint the filename text for `file` from the given /// `theme`. fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option