[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# How to contribute\n\nYou can:\n\n- Add or improve the support of an application (Check the [TODO][TODO] and\n  [TOFIX][TOFIX] tasks and pick one)\n- Improve the Mackup codebase\n- You can triage issues which may include reproducing bug reports or asking for\n  vital information, such as version numbers or reproduction instructions. If\n  you would like to start triaging issues, one easy way to get started is to\n  [subscribe to mackup on CodeTriage](https://www.codetriage.com/lra/mackup).\n  [![Open Source Helpers][CODETRIAGE-IMG]][CODETRIAGE]\n\n## Development Setup\n\nMackup uses [uv](https://docs.astral.sh/uv/) for fast, reliable Python package management. Here's how to get started:\n\n### Prerequisites\n\n- Python 3.9 or higher\n- [uv](https://docs.astral.sh/uv/) installed\n\nInstall uv if you haven't already:\n\n```bash\n# On macOS\nbrew install uv\n\n# On Linux\n# Via pipx (recommended)\npipx install uv\n\n# Or via Homebrew on Linux\nbrew install uv\n\n# On Windows\npowershell -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n```\n\nAlternatively, see the [official uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for more options.\n\n### Setting Up Your Development Environment\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/lra/mackup.git\ncd mackup\n```\n\n2. Sync dependencies (creates a virtual environment automatically):\n\n```bash\nuv sync --dev\n```\n\nThis will:\n- Create a `.venv` directory with a virtual environment\n- Install all project dependencies\n- Install development dependencies (pytest, mypy, etc.)\n\n### Running Tests and Checks\n\nUse the provided Make targets for quick development workflow:\n\n```bash\n# Run all checks (recommended before committing)\nmake check\n\n# Run tests only\nmake test\n\n# Run type checking\nmake mypy\n\n# Run code linting\nmake ruff\n```\n\nOr use `uv run` directly:\n\n```bash\n# Run tests\nuv run pytest\n\n# Run tests with verbose output\nuv run pytest -v\n\n# Run type checking\nuv run mypy mackup/\n\n# Run linting\nruff check .\n```\n\n### Code Quality Standards\n\nAll pull requests must pass:\n- ✅ **Tests**: All pytest tests must pass\n- ✅ **Type checking**: No mypy errors\n- ✅ **Linting**: Code must pass ruff checks\n- ✅ **Formatting**: Follow existing code style\n\nRun `make check` before submitting your PR to ensure everything passes.\n\n## Contributing Guidelines\n\nTo speed up Pull Request (PR) approval and merger into Mackup, please follow\nthese guidelines:\n\n- Keep one application supported per PR\n- Add the application to the list of supported applications in\n  [README.md][README.md]\n- Sync configurations should follow the following principles:\n  - Syncing should not break the application, and PRs should be tested\n  - Syncing should not break any syncing functionality internal to the\n    application\n  - The configuration should sync the minimal set of data, so that syncing\n    happens quickly. Leave large app data out of the sync configuration.\n  - Do not sync any file or folder that represents some state, like session\n    data, cache, any file specific to the local workstation.\n  - Do not sync sensitive information, like clear passwords or private keys\n\nThank you for your contribution!\n\n[TODO]: https://github.com/lra/mackup/labels/TODO\n[TOFIX]: https://github.com/lra/mackup/labels/TOFIX\n[CODETRIAGE]: https://www.codetriage.com/lra/mackup\n[CODETRIAGE-IMG]: https://www.codetriage.com/lra/mackup/badges/users.svg\n[README.md]: https://github.com/lra/mackup/blob/master/README.md\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "### All submissions\n\n* [ ] I have followed the [Contributing Guidelines](https://github.com/lra/mackup/blob/master/.github/CONTRIBUTING.md)\n* [ ] I have checked to ensure there aren't other open [Pull Requests](https://github.com/lra/mackup/pulls) for the same update/change\n\n### Adding/updating Application X Support\n\n* [ ] This PR is only for one application\n* [ ] It has been added to the list of supported applications in the [README](https://github.com/lra/mackup/blob/master/README.md)\n* [ ] The configuration syncs the minimal set of data\n* [ ] No file specific to the local workstation is synced\n* [ ] No sensitive data is synced\n\n### Improving the Mackup codebase\n\n* [ ] My submission passes the [tests](https://github.com/lra/mackup/tree/master/tests)\n* [ ] I have linted the code locally prior to submission\n* [ ] I have written new tests as applicable\n* [ ] I have added an explanation of what the changes do\n"
  },
  {
    "path": ".github/workflows/install.yaml",
    "content": "name: install\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n\n  install-on-linux:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-22.04\n          - ubuntu-22.04-arm\n          - ubuntu-24.04\n          - ubuntu-24.04-arm\n        python-version:\n          - \"3.9\"\n          - \"3.10\"\n          - \"3.11\"\n          - \"3.12\"\n          - \"3.13\"\n          - \"3.14\"\n    runs-on: ${{ matrix.os }}\n    container: python:${{ matrix.python-version }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: pip install .\n      - run: mackup --help\n\n  install-on-macos:\n    strategy:\n      matrix:\n        os:\n          - macos-14\n          - macos-15\n          - macos-15-intel\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: pip install .\n      - run: mackup --help\n"
  },
  {
    "path": ".github/workflows/linelint.yaml",
    "content": "# Make sure that all text files end with a newline character.\n# Configure your editor to end every file with a newline character.\n# See <https://stackoverflow.com/a/729795>\nname: linelint\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  linelint:\n    runs-on: ubuntu-latest\n    name: linelint\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n      - name: Linelint\n        uses: fernandrone/linelint@master\n"
  },
  {
    "path": ".github/workflows/markdown.yaml",
    "content": "name: markdown\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n\n  markdownlint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: articulate/actions-markdownlint@v1\n        with:\n          config: .markdownlint.yaml\n          ignore: 'tests/'\n"
  },
  {
    "path": ".github/workflows/mypy.yaml",
    "content": "name: mypy\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n\n  mypy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: astral-sh/setup-uv@v5\n        with:\n          python-version: \"3.9\"\n      - run: uv sync --all-extras --dev\n      - run: uv run mypy src/mackup/\n"
  },
  {
    "path": ".github/workflows/ruff.yaml",
    "content": "name: ruff\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n\n  ruff:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: astral-sh/setup-uv@v5\n      - run: uv sync --all-extras --dev\n      - run: uv run ruff check .\n"
  },
  {
    "path": ".github/workflows/snap.yaml",
    "content": "name: snap\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  build-and-publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: snapcore/action-build@v1\n        id: build\n      - uses: snapcore/action-publish@v1\n        if: github.ref == 'refs/heads/master'\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}\n        with:\n          snap: ${{ steps.build.outputs.snap }}\n          release: edge\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: test\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n\n  pytest:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version:\n          - \"3.9\"\n          - \"3.10\"\n          - \"3.11\"\n          - \"3.12\"\n          - \"3.13\"\n          - \"3.14\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: astral-sh/setup-uv@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n      - run: uv sync --all-extras --dev\n      - run: uv run pytest --cov=mackup --cov-report=term\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated when running Mackup\n*.pyc\n\n# Generated by make release\n/dist/\n\n# Ignore Claude stuff\n/.claude/\n\n# Coverage reports\n.coverage\n.coverage.*\nhtmlcov/\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n"
  },
  {
    "path": ".linelint.yml",
    "content": "# 'true' will fix files\nautofix: false\n\n# list of paths to ignore, uses gitignore syntaxes (executes before any rule)\nignore:\n  - tests/fixtures/Library/Application Support/Box/Box Sync/sync_root_folder.txt\n  - tests/fixtures/Library/Mobile Documents/com~apple~CloudDocs/_blank_.md\n\nrules:\n  # checks if file ends in a newline character\n  end-of-file:\n    # set to true to enable this rule\n    enable: true\n\n    # set to true to disable autofix (if enabled globally)\n    disable-autofix: true\n\n    # if true also checks if file ends in a single newline character\n    single-new-line: true\n"
  },
  {
    "path": ".markdownlint.yaml",
    "content": "MD004:\n  style: \"dash\"\n"
  },
  {
    "path": ".markdownlintignore",
    "content": "tests/\n"
  },
  {
    "path": "INSTALL.md",
    "content": "# Detailed install instructions for Mackup\n\nThere are 2 ways to run mackup:\n\n1. Install it with Homebrew (macOS and GNU/Linux)\n2. Install it with PIP (macOS and GNU/Linux)\n\n## Install\n\n### With Homebrew\n\n```bash\n# Easy\nbrew install mackup\n\n# Now just run it\nmackup -h\n```\n\n### With Homebrew master branch for latest updates\n\nWant to install the latest master release instead of waiting on the homebrew\npackage version?\n\n[Homebrew reference](https://docs.brew.sh/Manpage#install-options-formulacask)\n\n```bash\n# Install master\nbrew install --HEAD\n# Check if you are using the master or stale package\nbrew switch mackup <HEAD-XXXX>\n\nmackup -h\n```\n\n### With Python's PIP\n\n```bash\n# Easy too\npip install mackup\n\n# Now you can run it\nmackup -h\n```\n\n## Upgrade\n\n### Upgrade with Homebrew\n\n```bash\nbrew update\nbrew upgrade\nmackup -h\n```\n\n### Upgrade with Python's PIP\n\n```bash\npip install --upgrade mackup\nmackup -h\n```\n\n## Uninstall\n\n### Uninstall with Homebrew\n\n```bash\nbrew uninstall mackup\n```\n\n### Uninstall with Python's PIP\n\n```bash\npip uninstall mackup\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) 2013  Laurent Raufaste\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    Mackup  Copyright (C) 2013  Laurent Raufaste\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "Makefile",
    "content": "lint:\n\tmarkdownlint -c .markdownlint.yaml '**/*.md'\n\nruff:\n\truff check .\n\nty:\n\tuv run ty check\n\ntest:\n\tuv run pytest\n\ncoverage:\n\tuv run pytest --cov=mackup --cov-report=term --cov-report=html --cov-report=xml\n\ncoverage-report:\n\tuv run coverage report\n\nmypy:\n\tuv run mypy src/mackup/\n\ncheck: lint ruff mypy ty test\n\t@echo \"All checks passed!\"\n\nclean:\n\trm -rf src/mackup/__pycache__\n\trm -rf tests/__pycache__\n\trm -rf dist/\n\trm -rf htmlcov/\n\trm -rf .coverage\n\trm -rf coverage.xml\n\nrelease: clean\n\tuv build\n\tuv publish\n"
  },
  {
    "path": "README.md",
    "content": "# Mackup™\n\n[![Tests](https://github.com/lra/mackup/actions/workflows/test.yaml/badge.svg)](https://github.com/lra/mackup/actions/workflows/test.yaml)\n[![PyPI version](https://badge.fury.io/py/mackup.svg)](https://badge.fury.io/py/mackup)\n[![Python Versions](https://img.shields.io/pypi/pyversions/mackup.svg)](https://pypi.org/project/mackup/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![mypy](https://img.shields.io/badge/mypy-checked-blue)](http://mypy-lang.org/)\n[![License](https://img.shields.io/github/license/lra/mackup.svg)](https://github.com/lra/mackup/blob/master/LICENSE)\n\nBackup and keep your application settings in sync.\n\n## Table of contents\n\n- [Mackup™](#mackup)\n  - [Table of contents](#table-of-contents)\n  - [Quickstart](#quickstart)\n  - [Usage](#usage)\n  - [What does it do](#what-does-it-do)\n    - [Copy mode](#copy-mode)\n    - [Link mode](#link-mode)\n      - [`mackup link install`](#mackup-link-install)\n      - [`mackup link`](#mackup-link)\n      - [`mackup link uninstall`](#mackup-link-uninstall)\n  - [Supported Storages](#supported-storages)\n  - [Unsupported Storages](#unsupported-storages)\n  - [Supported Applications](#supported-applications)\n  - [Can you support application X](#can-you-support-application-x)\n  - [Personalization \\& configuration](#personalization--configuration)\n  - [Why did you do this](#why-did-you-do-this)\n  - [What platforms are supported](#what-platforms-are-supported)\n  - [What's up with the weird name](#whats-up-with-the-weird-name)\n  - [Architecture](#architecture)\n  - [Where can I find more information](#where-can-i-find-more-information)\n\n## Quickstart\n\nOn macOS or Linux, if you want an easy install, you can install\n[Homebrew](http://brew.sh/) and do:\n\n```bash\n# Install Mackup\nbrew install mackup\n\n# Launch it and back up your files\nmackup backup\n```\n\nIf not running macOS or Linux, or you don't like Homebrew, you can use [pip](https://pip.pypa.io/en/stable/).\n\n```bash\n# Install Mackup with PIP\npip install --upgrade mackup\n\n# Launch it and back up your files\nmackup backup\n```\n\nYou're all set and can back up from now on.\n\nNext, on any new workstation, do:\n\n```bash\n# Install Mackup\nbrew install mackup\n\n# Launch it and restore your files\nmackup restore\n```\n\nDone!\n\nYou can find more detailed instructions in [INSTALL.md](INSTALL.md).\n\n## Usage\n\n`mackup backup`\n\nBack up your application files. Copy your local config files into the Mackup folder.\n\n`mackup restore`\n\nRestore your application settings on a newly installed workstation.\nCopy config files from the Mackup folder to your home folder.\n\n`mackup link install`\n\nMove your local config files into the Mackup folder,\nand link them to their original place.\n\n$${\\color{red}warning}$$ _the `link` strategy [doesn't work correctly on macOS](#link-mode)_\n\n`mackup link`\n\nOn another workstation, links local config files from the Mackup folder.\n\n`mackup link uninstall`\n\nCopy back any synced config file to its original place.\nRemoves the links and copies config files from the Mackup folder back into your\nhome.\n\n`mackup list`\n\nDisplay the list of applications supported by Mackup.\n\n`mackup -h`\n\nGet some help, obviously...\n\n## What does it do\n\nBy only tracking pure configuration files, it keeps the crap out of your\nfreshly new installed workstation (no cache, temporary and locally specific\nfiles are transferred).\n\nMackup makes setting up the environment easy and simple.\n\nThere are 2 modes of operations: copy mode and link mode.\n\n### Copy mode\n\nCopy mode is used to back up and restore your files.\nThe files are backed up into the configured Mackup folder,\nwhich can be in Dropbox, iCloud, or wherever you configure it.\n\nIt is covered by the 2 commands:\n\n- `mackup backup`\n- `mackup restore`\n\n### Link mode\n\n> [!WARNING]\n> If you are using Mackup on a current version of macOS, link mode will BREAK\n  YOUR PREFERENCES. macOS Sonoma (macOS 14) and later don't support symlinked\n  preferences, see [issue #2035](https://github.com/lra/mackup/issues/2035) for\n  additional information. [PR #2085](<https://github.com/lra/mackup/pull/2085>)\n  added copy mode, which should be used instead.\n\nLink mode is used to move your config files into the Mackup folder,\nand link them back to their original place.\n\nThis mode is useful if you are using multiple workstations,\nand want to keep your application settings in sync at all times.\n\n- Backs up your application settings in a safe directory (e.g. Dropbox)\n- Syncs your application settings among all your workstations\n- Restores your configuration on any fresh install in one command line\n\nLet's take `git` as an example. Your settings for `git` are saved in your home\nfolder, in the `.gitconfig` file.\n\nIt is covered by the 3 commands:\n\n- `mackup link install`\n- `mackup link`\n- `mackup link uninstall`\n\n#### `mackup link install`\n\nIf you have Dropbox, these things happen when you launch `mackup link install`:\n\n1. `cp ~/.gitconfig ~/Dropbox/Mackup/.gitconfig`\n2. `rm ~/.gitconfig`\n3. `ln -s ~/Dropbox/Mackup/.gitconfig ~/.gitconfig`\n\nNow your `git` config is always backed up and up to date on all your workstations.\n\n#### `mackup link`\n\nWhen you launch `mackup link`, here's what it's really doing:\n\n1. `ln -s ~/Dropbox/Mackup/.gitconfig ~/.gitconfig`\n\nThat's it, you got your `git` config setup on your new workstation.\n\n`mackup` does the same for any supported application.\n\n#### `mackup link uninstall`\n\nYou can revert all your files to their original state.\n\n```bash\n# Just run this\nmackup link uninstall\n```\n\nThis will remove the symlinks and copy back the files from the Mackup folder in\nDropbox to their original places in your home. The Mackup folder and the files\nin it stay put, so that any other computer also running Mackup is unaffected.\n\n## Supported Storages\n\n- [Dropbox](https://www.dropbox.com/)\n- [Google Drive](https://drive.google.com/)\n- [iCloud](http://www.apple.com/icloud/)\n- Anything able to sync a folder (e.g. [Git](http://git-scm.com/))\n\nSee the [README](doc/README.md) file in the doc directory for more info.\n\n## Unsupported Storages\n\n- [Box](https://www.box.com): No longer supported as it ignores dotfiles, see\n  <https://github.com/lra/mackup/issues/807>.\n\n## Supported Applications\n\n- [1Password 4](https://agilebits.com/onepassword)\n- [2Do](http://www.2doapp.com/)\n- [Ack](http://beyondgrep.com/)\n- [act](https://github.com/nektos/act)\n- [Adium](https://adium.im/)\n- [Adobe Camera Raw](http://www.adobe.com/products/photoshop/extend.html)\n- [Adobe Illustrator CC](https://www.adobe.com/products/illustrator.html)\n- [Adobe Photoshop CC](http://www.adobe.com/products/photoshop.html)\n- [Adobe Photoshop Lightroom CC](https://www.adobe.com/products/photoshop-lightroom.html)\n- [Adobe Photoshop Lightroom Classic](https://www.adobe.com/de/products/photoshop-lightroom-classic.html)\n- [aerc](https://aerc-mail.org/)\n- [AeroSpace](https://github.com/nikitabobko/AeroSpace)\n- [Affinity Designer](https://affinity.serif.com/designer)\n- [Affinity Photo](https://affinity.serif.com/photo)\n- [Affinity Publisher](https://affinity.serif.com/publisher)\n- [Airflow](https://airflowapp.com/)\n- [Airmail](http://airmailapp.com/)\n- [Akamai-CLI](https://developer.akamai.com/cli)\n- [Alacritty](https://github.com/jwilm/alacritty)\n- [AlDente](https://apphousekitchen.com/)\n- [AltTab](https://alt-tab-macos.netlify.app/)\n- [Amethyst](https://ianyh.com/amethyst/)\n- [Ancient Domains of Mystery](http://www.adom.de/home/index.html)\n- [Android Studio](https://developer.android.com/sdk/)\n- [Ansible](http://www.ansible.com/)\n- [AppCleaner](http://freemacsoft.net/appcleaner/)\n- [AppCode](http://www.jetbrains.com/objc/)\n- [Apple Music](https://www.apple.com/apple-music/)\n- [Apptivate](http://www.apptivateapp.com/)\n- [Arara](https://github.com/cereda/arara)\n- [aria2c](http://aria2.sourceforge.net/)\n- [Arm](https://www.atagar.com/arm/)\n- [Artistic Style](http://astyle.sourceforge.net)\n- [asciinema](https://asciinema.org/)\n- [asdf version manager](https://github.com/asdf-vm/asdf)\n- [Aspell](http://aspell.net/)\n- [Atlantis](http://www.riverdark.net/atlantis/)\n- [Atom](https://atom.io/)\n- [Audacious](http://audacious-media-player.org/)\n- [AusKey](https://abr.gov.au/AUSkey/)\n- [Autokey](https://code.google.com/p/autokey/)\n- [Awareness](http://iamfutureproof.com/tools/awareness/)\n- [AWS Command Line Interface](https://aws.amazon.com/cli/)\n- [ActivityWatch](http://activitywatch.net/)\n- [Bartender](http://www.macbartender.com/)\n- [Bash it](https://github.com/Bash-it/bash-it)\n- [Bash](http://www.gnu.org/software/bash/)\n- [Base](https://menial.co.uk/base/)\n- [Bat](https://github.com/sharkdp/bat)\n- [Bc](https://www.gnu.org/software/bc/)\n- [Beatport Pro](https://www.beatport.com/desktop)\n- [Beets](http://beets.io/)\n- [BetterSnapTool](http://www.boastr.net/)\n- [BetterTouchTool](http://www.boastr.net/)\n- [Beyond Compare](https://scootersoftware.com/)\n- [BibDesk](http://bibdesk.sourceforge.net/)\n- [Billings Pro Server Admin](https://www.marketcircle.com/billingspro/download/billingspro-server/)\n- [BitBar](https://getbitbar.com/)\n- [Bitchx](http://www.bitchx.org/)\n- [Blackfire](https://blackfire.io/)\n- [Blender](https://blender.org/)\n- [ble.sh](https://github.com/akinomyoga/ble.sh)\n- [Boto](https://github.com/boto/boto)\n- [Boxer](http://boxerapp.com)\n- [Brackets](http://brackets.io/)\n- [Brave](https://brave.com/)\n- [Btop](https://github.com/aristocratos/btop)\n- [Bump](https://github.com/fabiospampinato/bump)\n- [Bundler](http://bundler.io)\n- [Byobu](http://byobu.co/)\n- [Caffeine](http://lightheadsw.com/caffeine/)\n- [Calibre](https://calibre-ebook.com/)\n- [Capture One](http://www.phaseone.com/Imaging-Software/Capture-One.aspx)\n- [Cartographica](https://www.macgis.com/)\n- [Cerebro](https://cerebroapp.com/)\n- [Charles](http://www.charlesproxy.com)\n- [Cheat](https://github.com/chrisallenlane/cheat)\n- [Chef](https://www.chef.io/chef/)\n- [Chicken](http://sourceforge.net/projects/chicken/)\n- [Choosy](https://www.choosyosx.com/)\n- [chunkwm](https://github.com/koekeishiya/chunkwm)\n- [Cider](https://github.com/msanders/cider)\n- [ClashX](https://github.com/yichengchen/clashX)\n- [Clasp](https://github.com/google/clasp)\n- [Claude Code](https://www.claude.com/product/claude-code)\n- [CleanShot](https://cleanshot.com/)\n- [Clementine](https://www.clementine-player.org/)\n- [CLion](https://www.jetbrains.com/clion/)\n- [ClipMenu](http://www.clipmenu.com/)\n- [Clipy](https://clipy-app.com/)\n- [CloudApp](http://getcloudapp.com/)\n- [Coda 2](http://panic.com/coda/)\n- [Codex](https://openai.com/codex/)\n- [Colloquy](http://colloquy.info/)\n- [ColorSchemer Studio 2](http://www.colorschemer.com/osx_info.php)\n- [ColorSlurp](http://colorslurp.com/)\n- [ColorSync](https://en.wikipedia.org/wiki/ColorSync)\n- [Composer](https://getcomposer.org/)\n- [Concentrate](http://www.getconcentrating.com/)\n- [Conky](https://github.com/brndnmtthws/conky)\n- [Consular](https://github.com/achiu/consular)\n- [Contexts](https://contexts.co)\n- [ControlPlane](http://www.controlplaneapp.com/)\n- [CopyQ](https://github.com/hluk/CopyQ)\n- [CoRD](http://cord.sourceforge.net/)\n- [CotEditor](http://coteditor.com/)\n- [Ctags](http://ctags.sourceforge.net/)\n- [Cursor](https://cursor.sh/)\n- [cVim](https://github.com/1995eaton/chromium-vim)\n- [Cyberduck](https://cyberduck.io/)\n- [DaisyDisk](https://daisydiskapp.com)\n- [DataGrip](https://www.jetbrains.com/datagrip/)\n- [Dash](https://kapeli.com/dash)\n- [Day-O](http://www.shauninman.com/archive/2011/10/20/day_o_mac_menu_bar_clock)\n- [DBeaver](https://dbeaver.io/)\n- [DbVisualizer](https://www.dbvis.com/)\n- [Deal Alert](http://dealalertapp.com/)\n- [Deepin-dde-dock](https://github.com/linuxdeepin/dde-dock)\n- [Deepin-dde-file-manager](https://www.deepin.org/en/original/dde-file-manager/)\n- [Deepin-Terminal](https://github.com/linuxdeepin/deepin-terminal)\n- [Default Folder X](http://www.stclairsoft.com/DefaultFolderX/)\n- [Devil's Pie 2](http://www.gusnan.se/devilspie2/)\n- [Devil's Pie](<https://en.wikipedia.org/wiki/Devil's_Pie_(software)>)\n- [dig](<http://en.wikipedia.org/wiki/Dig_(command)>)\n- [Divvy](http://mizage.com/divvy/)\n- [Docker](https://www.docker.com/)\n- [Dolphin](https://dolphin-emu.org/)\n- [Doom Emacs](https://github.com/hlissner/doom-emacs)\n- [Double Commander](http://doublecmd.sourceforge.net/)\n- [Doxie](http://www.getdoxie.com/)\n- [Dozer](https://github.com/Mortennn/Dozer)\n- [Draft](https://draft.sh/)\n- [Droplr](https://droplr.com/)\n- [Dropzone 3](https://aptonic.com/dropzone3/)\n- [Drush](http://www.drush.org/)\n- [Eagle (ogdesign)](https://eagle.cool/)\n- [EditorConfig](http://editorconfig.org/)\n- [Electrum](https://electrum.org/#home)\n- [Elgato StreamDeck](https://www.elgato.com/en/welcome-to-stream-deck)\n- [Emacs](http://www.gnu.org/software/emacs/)\n- [Enjoyable](https://yukkurigames.com/enjoyable/)\n- [Environmental Station Alpha](http://www.hempuli.com/esa/)\n- [eqMac2](https://bitgapp.com/eqmac/)\n- [ESLint](https://eslint.org/)\n- [espanso](https://espanso.org)\n- [Exercism](http://exercism.io/)\n- [ExpanDrive](http://www.expandrive.com/)\n- [Factorio](https://www.factorio.com)\n- [Factory Droid](https://factory.ai/)\n- [Fantastical](http://flexibits.com/fantastical)\n- [fasd](https://github.com/clvv/fasd)\n- [fastlane](https://fastlane.tools)\n- [FastScripts](https://redsweater.com/fastscripts/)\n- [Feeds](http://www.feedsapp.com/)\n- [FileZilla](https://filezilla-project.org/)\n- [Finicky](https://github.com/johnste/finicky)\n- [Fish](http://fishshell.com/)\n- [Fisher](https://github.com/jorgebucaran/fisher)\n- [Flake8](https://flake8.pycqa.org/)\n- [Flameshot](https://flameshot.org)\n- [FlexGet](http://flexget.com/)\n- [Flux](https://justgetflux.com/)\n- [Focus](https://heyfocus.com)\n- [Fontconfig](https://www.freedesktop.org/wiki/Software/fontconfig/)\n- [FontExplorer X](http://www.fontexplorerx.com/)\n- [Forge](http://www.slightlymagic.net/wiki/Forge)\n- [Fork](https://git-fork.com/)\n- [ForkLift](http://www.binarynights.com/forklift/)\n- [Franz](https://meetfranz.com)\n- [Gas Mask](https://github.com/2ndalpha/gasmask/)\n- [gdb](https://www.gnu.org/software/gdb/)\n- [Gear Player](https://www.gearmusicplayer.com/)\n- [GeekTool](http://projects.tynsoe.org/en/geektool/)\n- [GHCi](https://wiki.haskell.org/GHC/GHCi)\n- [Ghidra](https://ghidra-sre.org)\n- [Ghostty](https://ghostty.org/)\n- [Ghostwriter](https://wereturtle.github.io/ghostwriter/)\n- [Gimp](https://www.gimp.org/)\n- [Git Hooks](https://github.com/git-hooks/git-hooks)\n- [Git](http://git-scm.com/)\n- [Gitbox](http://gitboxapp.com/)\n- [GitFox](https://www.gitfox.app)\n- [GitHub CLI](https://cli.github.com/)\n- [GitKraken](https://www.gitkraken.com)\n- [GitUp](http://gitup.co/)\n- [Gmail Notifr](http://ashchan.com/projects/gmail-notifr)\n- [gmailctl](https://github.com/mbrt/gmailctl)\n- [GMVault](http://gmvault.org/)\n- [Gnome SSH Tunnel Manager](http://sourceforge.net/projects/gstm/)\n- [GnuPG](https://www.gnupg.org/)\n- [GNU Stow](https://www.gnu.org/software/stow/)\n- [Go2Shell](http://zipzapmac.com/Go2Shell)\n- [Goku](https://github.com/yqrashawn/GokuRakuJoudo)\n- [GoLand](https://www.jetbrains.com/go/)\n- [Goldendict](http://goldendict.org/)\n- [GoodSync](https://goodsync.com/)\n- [GoShare](https://github.com/dictget/goshare)\n- [Gradle](http://gradle.org)\n- [GrandTotal 3](http://www.mediaatelier.com/GrandTotal4/)\n- [grsync](http://www.opbyte.it/grsync/)\n- [Hammerspoon](http://www.hammerspoon.org/)\n- [HandBrake](https://handbrake.fr/)\n- [Hands Off!](http://www.oneperiodic.com/products/handsoff/)\n- [Hazel](http://www.noodlesoft.com/hazel.php)\n- [Hero Lab](http://www.wolflair.com/index.php?context=hero_lab)\n- [Heroku](https://www.heroku.com/)\n- [HexChat](https://hexchat.github.io/)\n- [Hexels](http://hexraystudios.com/hexels/)\n- [Hocus Focus](http://hocusfoc.us/)\n- [Homebridge](https://github.com/nfarina/homebridge)\n- [Homebrew](https://brew.sh)\n- [Houdini](http://uglyapps.co.uk/houdini/)\n- [Hstr](https://github.com/dvorka/hstr)\n- [HTML Tidy](https://www.html-tidy.org/)\n- [Htop](http://htop.sourceforge.net/)\n- [HTTPie](https://httpie.org/)\n- [hub](https://hub.github.com)\n- [Hyper.app](https://hyper.is/)\n- [HyperDock](https://bahoom.com/hyperdock)\n- [HyperSwitch](https://bahoom.com/hyperswitch)\n- [i2cssh](https://github.com/wouterdebie/i2cssh)\n- [i3](https://i3wm.org/)\n- [IDA Pro](https://www.hex-rays.com/products/ida/)\n- [IdeaVim](https://github.com/JetBrains/ideavim)\n- [IINA](https://iina.io)\n- [Inkscape](https://inkscape.org/)\n- [Insomnia](https://insomnia.rest/)\n- [IntelliJIDEA](http://www.jetbrains.com/idea/)\n- [IPython](http://ipython.org/)\n- [Irssi](http://www.irssi.org/)\n- [iStat Menus](https://bjango.com/mac/istatmenus/)\n- [Itsycal](https://github.com/sfsam/Itsycal)\n- [iTerm2](https://www.iterm2.com/)\n- [iTermocil](https://github.com/TomAnthony/itermocil)\n- [iTunes Scripts](https://www.apple.com/)\n- [JankyBorders](https://github.com/FelixKratz/JankyBorders)\n- [Janus](https://github.com/carlhuda/janus)\n- [Jitouch](http://www.jitouch.com/)\n- [Joplin](https://joplinapp.org/)\n- [jrnl](https://jrnl.sh)\n- [JS Beautifier](https://github.com/beautify-web/js-beautify)\n- [JSHint](http://jshint.com/)\n- [Julia](http://julialang.org)\n- [Jumpcut](http://jumpcut.sourceforge.net/)\n- [Jupyter](http://jupyter.org/)\n- [k9s](https://k9scli.io/)\n- [Kaggle](https://kaggle.com/)\n- [Kaleidoscope](http://www.kaleidoscopeapp.com/)\n- [Karabiner Elements](https://github.com/tekezo/Karabiner-Elements)\n- [Karabiner](https://pqrs.org/osx/karabiner/)\n- [Kdenlive](https://kdenlive.org/)\n- [KeePassX](http://www.keepassx.org/)\n- [KeePassXC](https://keepassxc.org/)\n- [KeepingYouAwake](https://github.com/newmarcel/KeepingYouAwake)\n- [Keka](http://www.kekaosx.com/en/)\n- [Keybase](https://keybase.io/)\n- [Keyboard Maestro](http://www.keyboardmaestro.com)\n- [Keymo](http://manytricks.com/keymo/)\n- [KeyRemap4MacBook](https://pqrs.org/osx/karabiner/)\n- [Khd](https://github.com/koekeishiya/khd/)\n- [kitty](https://sw.kovidgoyal.net/kitty/)\n- [Kiro](https://kiro.dev/)\n- [Krew](https://github.com/kubernetes-sigs/krew)\n- [Kubectl](https://kubernetes.io/docs/reference/kubectl/overview/)\n- [Kwm](https://koekeishiya.github.io/kwm/)\n- [LaTeXiT](http://www.chachatelier.fr/latexit/latexit-home.php?lang=en)\n- [LaunchBar](https://www.obdev.at/products/launchbar/index.html)\n- [lazydocker](https://github.com/jesseduffield/lazydocker)\n- [lazygit](https://github.com/jesseduffield/lazygit)\n- [Ledger](http://ledger-cli.org)\n- [Leiningen](http://leiningen.org/)\n- [lf](https://github.com/gokcehan/lf)\n- [LibreOffice](https://www.libreoffice.org/)\n- [Liftoff](https://github.com/thoughtbot/liftoff)\n- [Light Table](http://lighttable.com/)\n- [LightPaper](https://getlightpaper.com/)\n- [LimeChat](http://limechat.net/mac/)\n- [Liquid Prompt](https://github.com/nojhan/liquidprompt)\n- [LittleSnitch](http://www.obdev.at/products/littlesnitch/)\n- [Livestreamer](http://livestreamer.tanuki.se/)\n- [Logitech Options](https://www.logitech.com/en-us/product/options)\n- [Logseq](https://logseq.com/)\n- [Lollypop](https://gnumdk.github.io/lollypop-web/)\n- [Loopback](https://rogueamoeba.com/loopback/)\n- [Luftrausers](http://luftrausers.com)\n- [LunarVim](https://www.lunarvim.org/)\n- [MacDive](http://www.mac-dive.com/)\n- [MacDown](http://macdown.uranusjr.com/)\n- [MacOSX](http://www.apple.com/osx/)\n- [MacVim](https://github.com/macvim-dev/macvim)\n- [Magic Launch](https://www.oneperiodic.com/products/magiclaunch/)\n- [MagicPrefs](http://magicprefs.com/)\n- [Magnet](https://magnet.crowdcafe.com/)\n- [Maid](https://github.com/benjaminoakes/maid/)\n- [Mail](https://support.apple.com/guide/mail/welcome/mac)\n- [Mailmate](http://freron.com/)\n- [Mailplane](http://mailplaneapp.com/)\n- [mako](https://wayland.emersion.fr/mako/)\n- [Marked 2](http://marked2app.com)\n- [Marta](https://marta.yanex.org/)\n- [MATLAB](http://www.mathworks.com/products/matlab/)\n- [Maven](http://maven.apache.org)\n- [Max](http://sbooth.org/Max/)\n- [Mendeley Desktop](https://www.mendeley.com)\n- [MenuMeters](http://www.ragingmenace.com/software/menumeters/)\n- [Mercurial](https://www.mercurial-scm.org/)\n- [MercuryMover](http://www.heliumfoot.com/mercurymover/)\n- [Messages](http://www.apple.com/osx/apps/#messages)\n- [Micro](https://github.com/zyedidia/micro)\n- [Microsoft Azure CLI](https://github.com/Azure/azure-xplat-cli)\n- [Microsoft Remote Desktop](https://itunes.apple.com/us/app/microsoft-remote-desktop-10/id1295203466)\n- [mise-en-place](https://github.com/jdx/mise)\n- [mitmproxy](https://mitmproxy.org/)\n- [mkcert](https://github.com/FiloSottile/mkcert)\n- [Mole](https://github.com/tw93/Mole)\n- [MonoDevelop](http://www.monodevelop.com)\n- [Moom](http://manytricks.com/moom/)\n- [Mosaic](https://lightpillar.com/mosaic.html)\n- [Mou](http://25.io/mou/)\n- [mpd](http://www.musicpd.org)\n- [MPlayerX](http://mplayerx.org)\n- [MPS Youtube](https://github.com/mps-youtube/mps-youtube)\n- [MPV](https://mpv.io/)\n- [MTMR](https://github.com/Toxblh/MTMR)\n- [Multitouch](https://multitouch.app/)\n- [Mumu](https://getmumu.com)\n- [MusicBrainz Picard](https://picard.musicbrainz.org/)\n- [MuteSpotifyAds](https://github.com/simonmeusel/MuteSpotifyAds)\n- [mycli](https://www.mycli.net/)\n- [myrepos](https://github.com/joeyh/myrepos)\n- [MySQL Workbench](https://www.mysql.com/products/workbench/)\n- [MySQL](http://www.mysql.com/)\n- [Name Mangler](http://manytricks.com/namemangler/)\n- [Nano](http://www.nano-editor.org/)\n- [Navicat](http://navicat.com/)\n- [ncmpcpp](http://rybczak.net/ncmpcpp/)\n- [Neofetch](https://github.com/dylanaraps/neofetch)\n- [neovim](https://github.com/neovim/neovim)\n- [Nethack](http://www.nethack.org)\n- [Netlify](https://www.netlify.com/)\n- [newsbeuter](http://newsbeuter.org/)\n- [ngrok](https://ngrok.com/)\n- [ni](https://github.com/antfu/ni/)\n- [Nomacs](http://nomacs.org/)\n- [NoSQLBooster for MongoDB](https://www.nosqlbooster.com/)\n- [notion-enhancer](https://notion-enhancer.github.io/)\n- [Nova](https://www.nova.app/)\n- [npm](https://www.npmjs.com/)\n- [npmrc](https://github.com/deoxxa/npmrc/)\n- [NSLogger](https://github.com/fpillet/NSLogger)\n- [nuget](https://www.nuget.org/)\n- [Nushell](https://www.nushell.sh/)\n- [nvALT](http://brettterpstra.com/projects/nvalt/)\n- [nvm](https://github.com/nvm-sh/nvm)\n- [nvpy](https://github.com/cpbotha/nvpy)\n- [OBS](https://obsproject.com)\n- [OfflineIMAP](https://www.offlineimap.org/)\n- [Oh My Fish](https://github.com/bpinto/oh-my-fish)\n- [Oh My Tmux](https://github.com/gpakosz/.tmux)\n- [OmniFocus](https://www.omnigroup.com/omnifocus/)\n- [OmniGraffle](https://www.omnigroup.com/omnigraffle/)\n- [Openbox](http://openbox.org)\n- [OpenCode](https://opencode.ai)\n- [OpenEmu](http://openemu.org)\n- [OpenSSH](http://www.openssh.com/)\n- [Opera](http://www.opera.com)\n- [Oracle Cloud Infrastructure CLI](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/cliconcepts.htm)\n- [Paintbrush](http://paintbrush.sourceforge.net/)\n- [Pandoc](http://pandoc.org)\n- [Pass](http://www.passwordstore.org/)\n- [Pastebot](http://tapbots.com/software/pastebot/)\n- [Path Finder](http://www.cocoatech.com/pathfinder/)\n- [PDFjam](https://warwick.ac.uk/fac/sci/statistics/staff/academic-research/firth/software/pdfjam/)\n- [Pear](http://pear.php.net/)\n- [Pentadactyl](http://5digits.org/pentadactyl/)\n- [Perl](https://www.perl.org/)\n- [Phoenix](https://github.com/kasper/phoenix)\n- [PhoneView](https://www.ecamm.com/mac/phoneview/)\n- [PhpStorm](http://www.jetbrains.com/phpstorm/)\n- [PicGo](https://github.com/Molunerfinn/PicGo)\n- [Pidgin](https://www.pidgin.im)\n- [PIP](http://www.pip-installer.org/)\n- [PixelSnap](https://getpixelsnap.com/)\n- [PixelSnap 2](https://getpixelsnap.com/)\n- [Planner](https://useplanner.com/)\n- [Plover](http://www.openstenoproject.org/plover/)\n- [Pnpm](https://pnpm.js.org/)\n- [Pock](https://pock.pigigaldi.com)\n- [Podman](https://podman.io/)\n- [Poedit](http://poedit.net/)\n- [Poetry](https://python-poetry.org)\n- [PokerStars](https://www.pokerstars.com/)\n- [Polybar](https://polybar.github.io/)\n- [PopClip](http://pilotmoon.com/popclip/)\n- [Popcorn-Time](https://popcorntime.io/)\n- [PostgreSQL](http://www.postgresql.org/)\n- [Postico](https://eggerapps.at/postico/)\n- [Pow](http://pow.cx/)\n- [Powerlevel10k](https://github.com/romkatv/powerlevel10k)\n- [Powerline](https://github.com/powerline/powerline)\n- [Powerline-shell](https://github.com/b-ryan/powerline-shell)\n- [Prezto](https://github.com/sorin-ionescu/prezto)\n- [Processing](https://processing.org/)\n- [Proselint](https://github.com/amperser/proselint)\n- [ProxyChains NG](http://sourceforge.net/projects/proxychains-ng/)\n- [ProxyChains](http://proxychains.sourceforge.net)\n- [Proxyman](https://proxyman.io)\n- [PrusaSlicer](https://www.prusa3d.com/prusaslicer/)\n- [PsySH](https://psysh.org/)\n- [Punto Switcher](https://punto.yandex.ru/)\n- [PyCharm](https://www.jetbrains.com/pycharm/)\n- [PyPI](https://pypi.python.org/pypi)\n- [PyRadio](http://www.coderholic.com/pyradio/)\n- [Querious](http://www.araelium.com/querious/)\n- [Quicksilver](http://qsapp.com/)\n- [Quitter](https://marco.org/apps)\n- [Qutebrowser](http://qutebrowser.org/)\n- [Qv2ray](https://qv2ray.net/)\n- [R](http://www.r-project.org/)\n- [Rails](http://rubyonrails.org/)\n- [Ranger](https://ranger.github.io/)\n- [Rbenv](https://www.github.com/rbenv/rbenv)\n- [Rclone](https://rclone.org/)\n- [Rectangle](https://rectangleapp.com/)\n- [Redshift Scheduler](https://github.com/spantaleev/redshift-scheduler)\n- [Redshift](http://jonls.dk/redshift/)\n- [Remote Desktop Manager](https://remotedesktopmanager.com/)\n- [Rhythmbox](https://wiki.gnome.org/Apps/Rhythmbox)\n- [Rime](http://rime.im/)\n- [ripgrep](https://github.com/BurntSushi/ripgrep)\n- [Robo 3T](http://robomongo.org/)\n- [Rocket](https://matthewpalmer.net/rocket/)\n- [Rofi](https://github.com/DaveDavenport/rofi)\n- [Royal TSX](http://www.royaltsx.com/ts/osx/features)\n- [RStudio](https://www.rstudio.com/)\n- [rTorrent](http://libtorrent.rakshasa.no/)\n- [rtx](https://github.com/jdx/rtx)\n- [rubiTrack 5](https://www.rubitrack.com)\n- [Rubocop](https://github.com/bbatsov/rubocop)\n- [Ruby Version Manager](https://rvm.io/)\n- [Ruby Version](https://gist.github.com/fnichol/1912050)\n- [Ruby](https://www.ruby-lang.org/)\n- [RubyMine](http://www.jetbrains.com/ruby/)\n- [Rust](https://www.rust-lang.org/)\n- [RustRover](https://www.jetbrains.com/rust/)\n- [S3cmd](http://s3tools.org/s3cmd)\n- [SABnzbd](http://sabnzbd.org/)\n- [SBCL](http://www.sbcl.org/)\n- [SBT](http://www.scala-sbt.org/)\n- [Scenario](http://www.lagentesoft.com/scenario/)\n- [Screen](http://www.gnu.org/software/screen/)\n- [Screenhero](https://screenhero.com)\n- [Scrivener](http://www.literatureandlatte.com/scrivener.php)\n- [Scroll Reverser](https://pilotmoon.com/scrollreverser/)\n- [SecureCRT](https://www.vandyke.com/products/securecrt/)\n- [Secure Pipes](http://www.opoet.com/)\n- [Seil](https://pqrs.org/osx/karabiner/seil.html.en)\n- [SelfControl](http://selfcontrolapp.com/)\n- [Sequel Pro](http://www.sequelpro.com/)\n- [ShadowsocksX-NG](https://github.com/shadowsocks/ShadowsocksX-NG)\n- [ShiftIt](https://github.com/fikovnik/ShiftIt)\n- [Shifty](https://shifty.natethompson.io/)\n- [Shimo](https://www.feingeist.io/shimo/)\n- [ShowyEdge](https://pqrs.org/osx/ShowyEdge/index.html.en)\n- [SHSH Blobs](https://en.wikipedia.org/wiki/SHSH_blob)\n- [Shuttle](http://fitztrev.github.io/shuttle/)\n- [SizeUp](http://www.irradiatedsoftware.com/sizeup/)\n- [Sizzy](https://sizzy.co/)\n- [SketchyBar](https://felixkratz.github.io/SketchyBar/)\n- [skhd](https://github.com/koekeishiya/skhd/)\n- [Skim](http://skim-app.sourceforge.net/)\n- [Skitch](https://evernote.com/skitch/)\n- [Slate](https://github.com/jigish/slate)\n- [Slic3r](http://slic3r.org)\n- [Slogger](http://brettterpstra.com/projects/slogger/)\n- [SmartGit](http://www.syntevo.com/smartgit/)\n- [Smooth Mouse](http://smoothmouse.com/)\n- [Soulver](http://www.acqualia.com/soulver/)\n- [SourceTree](https://www.sourcetreeapp.com/)\n- [SpaceLauncher](https://spacelauncherapp.com)\n- [Spacemacs](https://github.com/syl20bnr/spacemacs)\n- [SpaceVim](https://github.com/SpaceVim/SpaceVim)\n- [SpamSieve](https://c-command.com/spamsieve)\n- [Spark](http://www.shadowlab.org/softwares/spark.php)\n- [Spectacle](https://www.spectacleapp.com/)\n- [Spectrwm](https://github.com/conformal/spectrwm/wiki)\n- [Splice](https://splice.com/)\n- [Spotify Notifications](http://spotify-notifications.citruspi.io/)\n- [Spotify](https://www.spotify.com/)\n- [Sqitch](https://sqitch.org/)\n- [Starship](https://starship.rs/)\n- [Startupizer2](http://appledoc.gentlebytes.com/startupizer/)\n- [Stata](http://www.stata.com/)\n- [Stats](https://github.com/exelban/stats)\n- [Stay](https://cordlessdog.com/stay/)\n- [Storyist](http://storyist.com/)\n- [Subler](https://subler.org)\n- [Sublime Merge](https://www.sublimemerge.com/)\n- [Sublime Text](http://www.sublimetext.com/)\n- [Subversion](http://subversion.apache.org/)\n- [SuperDuper!](http://www.shirt-pocket.com/SuperDuper/SuperDuperDescription.html)\n- [Surge](http://surge.run/manual/)\n- [Sway](https://swaywm.org/)\n- [Swinsian](http://swinsian.com/)\n- [Swish](https://highlyopinionated.co/swish/)\n- [SwitchHosts](https://github.com/oldj/SwitchHosts)\n- [T](http://sferik.github.io/t/)\n- [TablePlus](https://tableplus.io)\n- [TaskPaper](https://www.taskpaper.com)\n- [Taskwarrior](http://taskwarrior.org/)\n- [Teamocil](https://github.com/remi/teamocil)\n- [Telegram for macOS](https://macos.telegram.org)\n- [Terminal](http://www.apple.com/osx/apps/)\n- [Terminator](https://launchpad.net/terminator/)\n- [termite](https://github.com/thestinger/termite)\n- [Termux](https://termux.dev/)\n- [Terraform](https://developer.hashicorp.com/terraform)\n- [TextExpander](https://smilesoftware.com/textexpander)\n- [TextMate](http://macromates.com/)\n- [Textual](http://www.codeux.com/textual/)\n- [Things](https://culturedcode.com/things/)\n- [Tig](https://github.com/jonas/tig)\n- [Tiles](https://www.sempliva.com/tiles/)\n- [Tilix](https://github.com/gnunn1/tilix)\n- [Timeout](https://www.dejal.com/timeout/)\n- [tint2](https://code.google.com/p/tint2/)\n- [TinyFugue](http://tinyfugue.sourceforge.net)\n- [Tmux](http://tmux.sourceforge.net/)\n- [Tmuxp](https://github.com/tony/tmuxp)\n- [Tmuxinator](https://github.com/tmuxinator/tmuxinator)\n- [Todo.txt CLI](http://todotxt.com/)\n- [ToothFairy](https://c-command.com/toothfairy/)\n- [TotalSpaces2](http://totalspaces.binaryage.com/)\n- [Tower](http://www.git-tower.com/)\n- [Transmission](http://www.transmissionbt.com/)\n- [Transmit](http://panic.com/transmit/)\n- [TripMode](https://www.tripmode.ch)\n- [Trizen](https://github.com/trizen/trizen)\n- [Tunnelblick](https://tunnelblick.net)\n- [tvnamer](https://github.com/dbr/tvnamer)\n- [Twitterrific](http://twitterrific.com/)\n- [Typinator](http://www.ergonis.com/products/typinator/)\n- [Typora](https://typora.io)\n- [uTorrent](http://www.utorrent.com/)\n- [ulauncher](https://ulauncher.io/)\n- [Ventrilo](http://www.ventrilo.com/)\n- [Verdaccio](https://verdaccio.org/)\n- [Versions](http://www.versionsapp.com)\n- [Vim](http://www.vim.org/)\n- [Vimperator](http://www.vimperator.org/vimperator)\n- [Vimwiki](https://vimwiki.github.io/)\n- [Viscosity](http://www.sparklabs.com/viscosity/)\n- [Visual Studio Code](https://code.visualstudio.com/)\n- [Visual Studio Code - Insiders](https://code.visualstudio.com/insiders)\n- [Visual Studio Code - OSS](https://github.com/Microsoft/vscode)\n- [VSCodium](https://vscodium.com/)\n- [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/)\n- [VLC](http://www.videolan.org/)\n- [Volt](https://github.com/vim-volt/volt)\n- [Wakatime](https://wakatime.com/)\n- [Warp](https://www.warp.dev)\n- [waybar](https://github.com/Alexays/Waybar)\n- [WebStorm](https://www.jetbrains.com/webstorm/)\n- [WezTerm](https://wezfurlong.org/wezterm/)\n- [Wget](https://www.gnu.org/software/wget/)\n- [WhatsApp Web](https://web.whatsapp.com/)\n- [Windsurf](https://www.codeium.com)\n- [Wireshark 2](https://www.wireshark.org)\n- [Witch](http://manytricks.com/witch/)\n- [WordGrinder](https://cowlark.com/wordgrinder/)\n- [WordPress WP-CLI](http://wp-cli.org/)\n- [Workrave](http://www.workrave.org/)\n- [X11](http://www.x.org/)\n- [Xee](https://theunarchiver.com/xee)\n- [Xamarin Studio](https://xamarin.com/studio)\n- [xbar](https://xbarapp.com/)\n- [XBindKeys](http://www.nongnu.org/xbindkeys/)\n- [Xchat](http://xchat.org/)\n- [Xcode](https://developer.apple.com/xcode/)\n- [XEmacs](http://www.xemacs.org/)\n- [XLD](http://tmkk.undo.jp/xld/)\n- [Xonsh](https://xon.sh)\n- [XtraFinder](http://www.trankynam.com/xtrafinder/)\n- [yabai](https://github.com/koekeishiya/yabai)\n- [yarn](https://yarnpkg.com)\n- [yazi](https://github.com/sxyazi/yazi)\n- [youtube-dl](https://ytdl-org.github.io/youtube-dl/)\n- [Yummy FTP](http://www.yummysoftware.com/)\n- [zabbix-cli](https://github.com/usit-gd/zabbix-cli)\n- [zathura](https://pwmt.org/projects/zathura/)\n- [zed](https://zed.dev/)\n- [Zoom](http://zoom.com/)\n- [zoxide](https://github.com/ajeetdsouza/zoxide)\n- [Zsh](http://zsh.sourceforge.net/)\n- [Übersicht](http://tracesof.net/uebersicht/)\n\n## Can you support application X\n\nWe can [with your help](doc#get-official-support-for-an-application) ;)\n\n## Personalization & configuration\n\nHave an application that shouldn't be generally supported but that you use?\nOr some personal files you want to sync, e.g. various config files in a `~/.config/`\ndirectory or your personal `~/.gitignore`?\n\n- Create a `~/.mackup` directory to [sync an application or any file or directory](doc#add-support-for-an-application-or-any-file-or-directory)\n\n## Why did you do this\n\nYesterday, I had a talk with [Zach Zaro](http://zacharyzaro.com/), complaining\nabout the pain it is to reconfigure our Macbook each time we get a new one or\ninstall from scratch. That's a talk we have already had months ago.\n\nI change my workstation every X months. Each time I either lose my apps'\nconfigurations, or I just waste a bunch of hours getting setup like I was on my\nold box. I also spend a lot of time reconfiguring the same stuff again on all my\nworkstations (home, work).\n\nBoring...\n\nSome people tried to solve the problem on the application layer, like\n[Github's Boxen](https://boxen.github.com/),\nbut it solves a different problem, from my point of view. I don't spend a lot\nof time installing or downloading stuff. I spend time configuring it.\n\nFor years, I've used a personal shell script that was copying known config\nfiles into Subversion, Git or Dropbox, and linked them into my home. But I felt\na lot of us had the same problem: Making a more generic tool could help others\nand I could get help from others to support more apps in the tool.\n\nSo here comes Mackup, the little tool that will sync all your application\nconfigs to Dropbox (or Google Drive, or anything).\n\nAnd it's [GPL](http://www.gnu.org/licenses/gpl.html), of course.\n\n## What platforms are supported\n\n- macOS\n- GNU/Linux\n\n## What's up with the weird name\n\nMackup is just a portmanteau of Mac and Backup. It is simple, short, and easy to\nremember, and it corresponds with the whole idea of Mackup: the simpler – the better!\n(And I suck at naming stuff, but who doesn't.)\n\n## Architecture\n\nWant to understand how Mackup works internally? Check out the\n[Architecture Guide](doc/ARCHITECTURE.md) which includes:\n\n- Visual architecture diagram\n- Component breakdown\n- Data flow diagrams\n- Design decisions\n- Extension points for contributors\n\nPerfect for contributors who want to understand the codebase or users\ncurious about how their configs are managed.\n\n## Where can I find more information\n\nIn the [doc](doc) directory.\n"
  },
  {
    "path": "doc/.mackup/gnupg.cfg",
    "content": "[application]\nname = GnuPG\n\n[configuration_files]\n.gnupg/gpg-agent.conf\n.gnupg/gpg.conf\n.gnupg/secring.gpg\n.gnupg/trustdb.gpg\n.gnupg/pubring.gpg\n.gnupg/pubring.kbx\n"
  },
  {
    "path": "doc/.mackup/mackup.cfg",
    "content": "[application]\nname = Mackup\n\n[configuration_files]\n.mackup.cfg\n.mackup\n"
  },
  {
    "path": "doc/.mackup/my-own-files.cfg",
    "content": "[application]\nname = Files and directories I want to sync\n\n[configuration_files]\nbin\nsrc/mackup\n"
  },
  {
    "path": "doc/.mackup/ssh.cfg",
    "content": "[application]\nname = SSH\n\n[configuration_files]\n.ssh\n"
  },
  {
    "path": "doc/.mackup.cfg",
    "content": "#\n# Sample Mackup configuration file\n#\n\n# You can specify the storage type Mackup will use to store your configuration\n# files.\n# For now you have 3 options: \"dropbox\", \"google_drive\" and \"file_system\".\n# If none is specified, Mackup will try to use the default: \"dropbox\".\n# With the \"dropbox\" storage engine, Mackup will automatically figure out your\n# Dropbox folder.\n[storage]\nengine = dropbox\n\n# If you choose the \"google_drive\" storage engine instead, Mackup will figure\n# out where your Google Drive is and store your configuration files in it.\n# [storage]\n# engine = google_drive\n\n# If you want to specify another directory, you can use the \"file_system\"\n# engine and Mackup won't try to detect any path for you: it will store your\n# files where you explicitly told him to, using the \"path\" setting.\n# The \"path\" can be absolute (from the / of your drive) or relative to your\n# home directory.\n# The \"path\" setting is mandatory when using the \"file_system\" engine.\n# Note: you don't need to escape spaces or wrap the path in quotes.\n# [storage]\n# engine = file_system\n# path = some/folder/in/your/home\n# # or\n# path = /some/folder/in/your/root\n# # or\n# path = /some path/in/your/root\n\n# You can customize the directory name in which Mackup stores your file. By\n# default, if not specified, Mackup creates a \"Mackup\" directory in the storage\n# engine you chose, e.g. \"~/Dropbox/Mackup\".\ndirectory = Mackup\n\n# List of applications you want to explicitly sync\n# One application name per line\n# If this list is empty, Mackup will try to sync all the supported\n# applications.\n#\n# To see a list of supported application names, launch mackup list\n# Use the names listed below.\n#\n# For example, to sync Irssi, Ventrilo and XEmacs, add:\n[applications_to_sync]\nirssi\nventrilo\nxemacs\n\n# List of applications you want to ignore\n# One application name per line\n# If an application is ignored, it will be ignored even if it's been explicitly\n# allowed in the [Allowed Applications].\n#\n# To see a list of supported application names, launch mackup list\n# Use the names listed below.\n#\n# For example, to not sync SSH and Adium, add:\n[applications_to_ignore]\nssh\nadium\n"
  },
  {
    "path": "doc/ARCHITECTURE.md",
    "content": "# Mackup Architecture\n\nThis document explains how Mackup works internally to help contributors\nunderstand the codebase.\n\n## Visual Overview\n\n![Mackup Architecture Diagram](architecture.svg)\n\n## Core Concepts\n\nMackup is a tool that backs up and syncs application configuration files\nacross multiple machines using a cloud storage service (Dropbox, Google Drive,\niCloud) or any file system location.\n\n### Operation Modes\n\nMackup has two main operation modes:\n\n#### 1. Copy Mode (Recommended)\n\nCopy mode is used for backing up and restoring files:\n\n- **`mackup backup`**: Copies configuration files from your home directory\n  to the Mackup folder\n- **`mackup restore`**: Copies configuration files from the Mackup folder\n  back to your home directory\n\nThis is a one-time operation used when setting up a new machine or creating\nan initial backup.\n\n#### 2. Link Mode (Legacy - Not recommended for macOS Sonoma+)\n\nLink mode creates persistent symlinks between your home directory and the\nMackup folder:\n\n- **`mackup link install`**: Moves files to Mackup folder and creates\n  symlinks back to original locations\n- **`mackup link`**: Creates symlinks from Mackup folder to home directory\n  (on additional machines)\n- **`mackup link uninstall`**: Removes symlinks and copies files back to\n  original locations\n\n⚠️ **Warning**: Link mode is broken on macOS Sonoma (14) and later due to\nchanges in how macOS handles symlinked preferences. Use copy mode instead.\n\n## Component Architecture\n\n### 1. Command Line Interface (`main.py`)\n\nEntry point for the application. Parses command-line arguments using\n`docopt` and dispatches to the appropriate operation.\n\n**Key Functions:**\n\n- Argument parsing and validation\n- Help text display\n- Operation dispatch\n\n### 2. Configuration Manager (`config.py`)\n\nReads and parses the `.mackup.cfg` configuration file.\n\n**Configuration Sources (in order of precedence):**\n\n1. `~/.mackup.cfg`\n2. `$MACKUP_CONFIG` environment variable\n3. `$XDG_CONFIG_HOME/mackup/mackup.cfg` or `~/.config/mackup/mackup.cfg`\n\n**Configuration Options:**\n\n- Storage engine (dropbox, google_drive, icloud, file_system)\n- Storage path and directory name\n- Applications to sync/ignore\n- Custom directory name\n\n### 3. Application Database (`appsdb.py`)\n\nManages the database of supported applications and their configuration files.\n\n**Sources:**\n\n- Built-in application configs (`mackup/applications/*.cfg`)\n- User-defined custom configs (`~/.mackup/*.cfg`)\n\n**Application Config Format:**\n\n```ini\n[application]\nname = Application Name\n\n[configuration_files]\n.config_file\nfolder/\n\n[xdg_configuration_files]\napp/config\n```\n\n### 4. Application Handler (`application.py`)\n\nHandles the operations (backup, restore, link) for individual applications.\n\n**Key Operations:**\n\n- File discovery and validation\n- Copy operations with permission preservation\n- Symlink creation and management\n- XDG directory handling\n- Conflict detection\n\n### 5. Core Engine (`mackup.py`)\n\nOrchestrates the overall backup/restore/link operations across all\napplications.\n\n**Workflow:**\n\n1. Load configuration\n2. Determine storage location\n3. Load application database\n4. Filter applications based on config\n5. Execute operation on each application\n6. Handle errors and report status\n\n### 6. Utilities (`utils.py`)\n\nCommon utility functions used throughout the codebase:\n\n- File system operations (copy, delete, symlink)\n- Path manipulation and resolution\n- XDG directory detection\n- Error handling and user prompts\n- Storage engine detection (Dropbox, Google Drive, iCloud)\n\n## Data Flow\n\n### Backup Flow\n\n```text\nUser runs: mackup backup\n    ↓\nmain.py parses command\n    ↓\nconfig.py loads .mackup.cfg\n    ↓\nappsdb.py loads application definitions\n    ↓\nmackup.py iterates through applications\n    ↓\napplication.py for each app:\n    - Finds config files in home directory\n    - Copies to Mackup storage folder\n    - Preserves permissions and timestamps\n    ↓\nFiles now in: ~/Dropbox/Mackup/ (or chosen storage)\n```\n\n### Restore Flow\n\n```text\nUser runs: mackup restore\n    ↓\nmain.py parses command\n    ↓\nconfig.py loads .mackup.cfg\n    ↓\nappsdb.py loads application definitions\n    ↓\nmackup.py iterates through applications\n    ↓\napplication.py for each app:\n    - Finds files in Mackup storage folder\n    - Copies to home directory\n    - Preserves permissions and timestamps\n    ↓\nConfig files now in home directory\n```\n\n### Link Install Flow (Legacy)\n\n```text\nUser runs: mackup link install\n    ↓\nmain.py parses command\n    ↓\nconfig.py loads .mackup.cfg\n    ↓\nappsdb.py loads application definitions\n    ↓\nmackup.py iterates through applications\n    ↓\napplication.py for each app:\n    - Moves config files to Mackup storage folder\n    - Creates symlinks from original location to storage\n    ↓\nFiles in storage: ~/Dropbox/Mackup/\nSymlinks in home: ~/.config → ~/Dropbox/Mackup/.config\n```\n\n## Key Design Decisions\n\n### 1. Symlinks vs Copies\n\n**Original Design (Link Mode):**\n\n- Symlinks allowed real-time sync across machines\n- Changes immediately reflected in cloud storage\n- Single source of truth\n\n**Current Recommendation (Copy Mode):**\n\n- macOS Sonoma+ broke symlink support for preferences\n- Copy mode is more reliable across platforms\n- Trade-off: manual sync required (re-run backup/restore)\n\n### 2. Application Database\n\n- **Centralized definitions**: All supported apps in\n  `mackup/applications/`\n- **User extensibility**: Custom apps via `~/.mackup/*.cfg`\n- **Simple format**: INI-style configuration files\n- **Override capability**: User configs override built-in ones\n\n### 3. Storage Abstraction\n\n- **Multiple backends**: Dropbox, Google Drive, iCloud, file system\n- **Auto-detection**: Automatically finds storage paths\n- **Flexibility**: Custom paths for any sync solution\n\n### 4. XDG Support\n\n- **Standards compliance**: Respects XDG Base Directory Specification\n- **Flexibility**: Handles non-standard `$XDG_CONFIG_HOME`\n- **Separate section**: `[xdg_configuration_files]` in app configs\n\n## File Structure\n\n```text\nmackup/\n├── main.py              # CLI entry point\n├── mackup.py           # Core orchestration engine\n├── config.py           # Configuration management\n├── appsdb.py           # Application database\n├── application.py      # Per-application operations\n├── utils.py            # Utility functions\n├── constants.py        # Constants and defaults\n└── applications/       # Built-in app configs\n    ├── git.cfg\n    ├── vim.cfg\n    ├── ssh.cfg\n    └── ... (600+ more)\n```\n\n## Extension Points\n\n### Adding a New Application\n\nCreate `mackup/applications/myapp.cfg`:\n\n```ini\n[application]\nname = My Application\n\n[configuration_files]\n.myapprc\n.myapp/\n\n[xdg_configuration_files]\nmyapp/config.yml\n```\n\nSubmit PR with the new config file.\n\n### Adding a New Storage Backend\n\n1. Extend `config.py` to detect new storage location\n2. Add storage type to `constants.py`\n3. Implement path detection in `utils.py`\n4. Update documentation\n\n### Custom File Sync\n\nUsers can sync any files by creating `~/.mackup/custom.cfg`:\n\n```ini\n[application]\nname = My Custom Files\n\n[configuration_files]\n.custom_file\ncustom_directory/\n```\n\n## Testing Strategy\n\nThe codebase includes comprehensive unit tests:\n\n- **`tests/test_application.py`**: Application operations\n- **`tests/test_config.py`**: Configuration parsing\n- **`tests/test_utils.py`**: Utility functions\n- **`tests/test_cli.py`**: Command-line interface\n- **`tests/test_main.py`**: Main entry point\n\nTests use temporary directories and mock file systems to avoid affecting\nthe actual system.\n\n## Error Handling\n\nMackup includes several safety mechanisms:\n\n1. **Confirmation prompts**: Asks before destructive operations\n2. **Conflict detection**: Warns if files exist in both locations\n3. **Dry-run capability**: Can preview operations (future enhancement)\n4. **Graceful degradation**: Continues with other apps if one fails\n5. **Clear error messages**: Helps users understand what went wrong\n\n## Performance Considerations\n\n- **Sequential processing**: Processes one application at a time\n- **File-by-file operations**: No batch operations for reliability\n- **No caching**: Reads fresh data on each run\n- **Minimal dependencies**: Only requires `docopt-ng` for CLI parsing\n\n## Future Enhancements\n\nPotential improvements to the architecture:\n\n1. **Progress indicators**: Show progress during long operations\n2. **Dry-run mode**: Preview changes without executing\n3. **Incremental sync**: Only sync changed files\n4. **Parallel processing**: Speed up operations on multi-app backups\n5. **Conflict resolution**: Better handling of file conflicts\n6. **Rollback capability**: Undo operations if something goes wrong\n\n## Contributing\n\nSee [CONTRIBUTING.md](../.github/CONTRIBUTING.md) and\n[develop.md](develop.md) for guidelines on contributing to Mackup.\n"
  },
  {
    "path": "doc/README.md",
    "content": "# Configuration\n\n> 💡 **New to Mackup?** Check out the\n> [Architecture Guide](ARCHITECTURE.md) to understand how Mackup works\n> under the hood.\n\nAll the configuration is done in a file named `.mackup.cfg` stored at the\nroot of your home folder. This location can be overridden via environment\nvariables or the `--config-file` command-line option.\n\nTo configure Mackup, create a file named `.mackup.cfg` in your home\ndirectory.\n\n```bash\nvi ~/.mackup.cfg\n```\n\n## Configuration file location\n\nConfig files are searched in the following order. If none is found, Mackup will\nuse the default config location of `~/.mackup.cfg`\n\n- `~/.mackup.cfg`\n- `$MACKUP_CONFIG`\n- `$XDG_CONFIG_HOME/mackup/mackup.cfg` or `~/.config/mackup/mackup.cfg`\n\nYou can also specify a custom config file location using the `--config-file`\ncommand-line option:\n\n```bash\nmackup --config-file ~/.mackup-custom.cfg backup\n```\n\nThe path can be absolute or relative to your home directory. Note that the\nconfig file must be located within your home directory for security reasons.\n\n## Storage\n\nYou can specify the storage type Mackup will use to store your configuration\nfiles.\n\nFor now, you have 4 options: `dropbox`, `google_drive`, `icloud` and `file_system`.\n\nIf none is specified, Mackup will try to use the default: `dropbox`.\nWith the `dropbox` storage engine, Mackup will automatically figure out your\nDropbox folder.\n\n### Dropbox\n\n```ini\n[storage]\nengine = dropbox\n```\n\n### Google Drive\n\nIf you choose the `google_drive` storage engine instead, Mackup will figure out\nwhere your Google Drive is and store your configuration files in it.\n\n```ini\n[storage]\nengine = google_drive\n```\n\n### iCloud\n\nIf you choose the `iCloud` storage engine, Mackup will store your\nconfiguration files in the `~/Library/Mobile\\ Documents/com\\~apple\\~CloudDocs/`\nfolder.\n\n```ini\n[storage]\nengine = icloud\n```\n\nYou can check if your files are synced using:\n\n```sh\nbrctl monitor com.apple.CloudDocs\n```\n\n### File System\n\nIf you want to specify another directory, you can use the `file_system` engine\nand Mackup won't try to detect any path for you: it will store your files where\nyou explicitly told it to, using the `path` setting.\nThe `path` can be absolute (from the `/` of your drive) or relative to your\nhome directory.\nThe `path` setting is mandatory when using the `file_system` engine.\n\n```ini\n[storage]\nengine = file_system\npath = some/folder/in/your/home\n# or path = /some/folder/in/your/root\n```\n\nNote: you don't need to escape spaces or wrap the path in quotes.\nFor example, the following paths are valid:\n\n```ini\npath = some/path in your/home\npath = /some path/in/your/root\n```\n\n### Custom Directory Name\n\nYou can customize the directory name in which Mackup stores your files. By\ndefault, if not specified, Mackup creates a `Mackup` directory in the storage\nengine you chose, e.g. `~/Dropbox/Mackup`.\n\n```ini\n[storage]\ndirectory = Mackup\n```\n\nFor example:\n\n```ini\n[storage]\nengine = file_system\npath = dotfiles\ndirectory = backup\n```\n\nThis will store your files in the `~/dotfiles/backup` directory in your home.\n\nYou can also select a subfolder:\n\n```ini\n[storage]\nengine = icloud\ndirectory = .config/mackup\n```\n\n### Switching Storage\n\nIf you ever change your mind and switch storage solutions after Mackup is\nalready setup (ex: from `dropbox` to `icloud`), complete the following steps.\n\n1. Run `mackup uninstall` on all computers\n2. Copy your Mackup files to the new storage location\n3. Change the storage provider details in your `.mackup.cfg` file (see above)\n4. Run `mackup backup` on the main computer and `mackup restore` on all others\n\n## Applications\n\n### Only sync one or two applications\n\nIn your home folder, create a file named `.mackup.cfg` and add the application\nnames to allow in the `[applications_to_sync]` section, one per line.\n\n```ini\n# Example, to only sync SSH and Adium:\n[applications_to_sync]\nssh\nadium\n```\n\nUse `mackup list` to get a list of valid application names. Don't use fancy\nnames (with spaces) here.\n\nA [sample](.mackup.cfg) of this file is available in this folder. Just copy it\nto your home folder:\n\n```bash\ncp mackup/doc/.mackup.cfg ~/\n```\n\n### Don't sync an application\n\nIn your home folder, create a file named `.mackup.cfg` and add the application\nnames to ignore in the `[applications_to_ignore]` section, one per line.\n\n```ini\n# Example, to not sync SSH and Adium:\n[applications_to_ignore]\nssh\nadium\n```\n\nUse `mackup list` to get a list of valid application names. Don't use fancy\nnames (with spaces) here.\n\nA [sample](.mackup.cfg) of this file is available in this folder. Just copy it\nto your home folder:\n\n```bash\ncp mackup/doc/.mackup.cfg ~/\n```\n\n### Get official support for an application\n\nOpen a [new issue](https://github.com/lra/mackup/issues) and ask for it, or\nfork Mackup and open a\n[Pull Request](https://help.github.com/articles/using-pull-requests).\nThe stock application configs are in the `mackup/applications` directory.\n\nRemember to follow the guidelines in [CONTRIBUTING.md](https://github.com/lra/mackup/blob/master/.github/CONTRIBUTING.md)\nto get your Pull Request merged faster.\n\n### Add support for an application or (almost) any file or directory\n\nYou can customize the Mackup engine and add support for unsupported\napplications or just custom files and directories you'd like to sync.\n\nNOTE: Files and directories to be synced should be rooted at $HOME.\n\nLet's say that you'd like to add support for Nethack (config file:\n`.nethackrc`), for the `bin` and `.hidden` directories and for the\n`.gitignore` file you keep in your home.\n\nIn your home, create a `.mackup` directory and add a config file for the\napplication you'd like to support:\n\n```bash\nmkdir ~/.mackup\ntouch ~/.mackup/nethack.cfg\ntouch ~/.mackup/my-files.cfg\n```\n\n#### Custom applications directory location\n\nCustom application configs are searched in the following order:\n\n1. `~/.mackup/` (legacy location, takes priority)\n2. `$XDG_CONFIG_HOME/mackup/applications/` or `~/.config/mackup/applications/`\n\nIf the same application config exists in both locations, the legacy location\n(`~/.mackup/`) takes priority.\n\nFor XDG-compliant setups, you can use:\n\n```bash\nmkdir -p ~/.config/mackup/applications\ntouch ~/.config/mackup/applications/nethack.cfg\n```\n\nEdit those files:\n\n```bash\n$ nano ~/.mackup/nethack.cfg\n[application]\nname = Nethack\n\n[configuration_files]\n.nethackrc\n```\n\n```bash\n$ nano ~/.mackup/my-files.cfg\n[application]\nname = My personal synced files and dirs\n\n[configuration_files]\nbin\n.hidden\n.gitignore\n```\n\nNote that Mackup assumes the file paths listed here are relative to your home\ndirectory.\n\nYou can run mackup to see if they are listed:\n\n```bash\n$ mackup list\nSupported applications:\n[...]\n - my-files\n - nethack\n[...]\n```\n\nAll good, you can now sync your newly configured files:\n\n```bash\nmackup backup\n```\n\nIf you override an application config that is already supported by Mackup, your\nnew config for this application will replace the one provided by Mackup.\n\nYou can find some sample configs in this directory.\n\n### Locally test an application before submitting a Pull Request\n\nYou can add and test an application by following these steps:\n\n- fork this project\n- create a branch _(usually containing the name of the application)_\n- add the appropriate application config file in the `mackup/applications` folder\n- from the top-most folder _(mackup)_ run `make develop` that replaces the\n  currently installed mackup with the local modified one\n- simply run `mackup backup` to test if everything is ok\n- if everything works as expected:\n  - run `make undevelop` to revert to the official version\n  - commit and push the change to your fork and then create the Pulls Request\n\n### Add support for an application using the XDG directory\n\nFor applications storing their configuration under the `~/.config` folder, you\nshould not hardcode it. The `.config` folder is the default location but it can\nbe named differently on other users' systems by setting the `XDG_CONFIG_HOME`\nenvironment variable.\n\nSee <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>\n\nMackup supports this mechanism and provides a dedicated `xdg_configuration_files`\nsection for those applications.\n\nIf any path starts with `.config`, remove the `.config` part and move the path\nto a dedicated `xdg_configuration_files` section.\n\nInstead of:\n\n```ini\n[application]\nname = Git\n\n[configuration_files]\n.gitconfig\n.config/git/config\n.config/git/ignore\n.config/git/attributes\n```\n\nUse this:\n\n```ini\n[application]\nname = Git\n\n[configuration_files]\n.gitconfig\n\n[xdg_configuration_files]\ngit/config\ngit/ignore\ngit/attributes\n```\n"
  },
  {
    "path": "doc/configuration_merge_guide.md",
    "content": "# Guide for Merging Configurations Across Machines\n\nThis guide is for users who want to combine configuration settings for two or\nmore machines.\n\nFor example, you might have some bash settings on Machine A (like useful\ncommand aliases) that are not on Machine B.  You also have some settings on\nMachine B that you would like to move over to Machine A.\n\nThe problem is that if you use Mackup to send Machine A's bash configuration\nsettings to Machine B, you will permanently lose any configurations on Machine\nB that you wanted to keep. Mackup obviously has no idea which features you want\nto keep and which ones you don't, so you'll have to do a bit of work to merge\nthe different configuration files yourself before using Mackup.\n\n## Step 0: Read Through This Entire Guide First\n\nIt will probably save you some pain in the long run.\n\n## Step 1: Determine Which Configuration Files to Merge\n\nFirst, pick the app you wish to keep in sync. Then determine which\nconfiguration files will be synced for that application by doing the following:\n\n1. [Install Mackup](./../INSTALL.md)\n2. Create a `.mackup.cfg` file in your home directory\n3. Add the following two lines to `.mackup.cfg`. Replace **bash**\n   in the example below with the name of your application.\n\n```text\n[applications_to_sync]\nbash\n```\n\nYou can get a list of supported apps by running `mackup list`.\n\n1. Save the file\n2. Run the following command:\n\n`mackup --dry-run --verbose backup`\n\nThis command will let you see what mackup will do behind the scenes when it\nbacks up your application's configuration files so you can readily see what\nconfiguration files Mackup will sync. Make note of these files.\n\n## Step 2: Prepare Your Workstations for Syncing\n\nNow that you've identified which files you have to merge, choose one of the two\napproaches below for merging the configuration files. **Method 1** has you do all\nthe configuration file merges first and then pushes them out with Mackup.\nWith **Method 2**, you'll push out the configuration files from one machine to the\nothers and then merge in your configuration changes gradually over time.\n\nWhich method should you use? It doesn't really matter. Method 1 is more work up\nfront in exchange for less work later. Method 2 is less work up front but more\nwork later.\n\nMethod 1 is probably best if you have very dissimilar configurations and have\nfew machines. Method 2 is probably best if you have a machine that is very\nclose to working the way you want and just need a few configuration settings\nfrom other machines.\n\n### Method 1: Backup Merge-Push Approach\n\n1. Create a backup of each machine's configuration files for the app you wish\n   to sync.\n2. Choose a machine that will serve as the initial \"master\". It doesn't really\n   matter which one.\n3. Edit your configuration files on the master machine so that they\n   represent the ideal version of the file you wish to distribute out to your\n   other machines.\n\n#### Method 1 Example\n\nLet's say we have two machines, A and B, and we want to sync our bash configuration\nacross the machines. We decide that Machine A will serve as our master.\n\nFirst, backup the bash configuration files (there are a few of them)\nfor your application on all machines.\n\n##### Method 1: Sample backup commands for Machine A\n\n```bash\nmkdir ~/bash_backup\ncp ~/.bash_profile ~/bash_backup/bash_profile.bak\ncp ~/.bash_login ~/bash_backup/bash_login.bak\n\n...plus any other bash config files you want to keep\n```\n\n##### Method 1: Sample backup commands for Machine B\n\n```bash\nmkdir ~/bash_backup\ncp ~/.bash_profile ~/bash_backup/bash_profile.bak\ncp ~/.bash_login ~/bash_backup/bash_login.bak\n\n...plus any other bash config files you want to keep\n```\n\nMachine A will be our master so we now edit the existing configuration files\non Machine A. We will use the vim text editor to do this for each of our\nconfiguration files:\n\n```bash\nvim .bash_profile\nvim .bash_login\n```\n\nWhen editing these configuration files on Machine A, copy and paste the settings\nfrom Machine B that you want to keep. In essence, you are manually merging the\nconfiguration files together. Once you are satisfied the configuration files\nhave all the settings you want and need, you are ready to push out your changes\nfrom the master machine.\n\n### Method 2: Backup Push-Merge Approach\n\n1. Choose a machine that will serve as the initial \"master\". You'll probably\n   want to choose the machine you use most and like its configuration\n   settings the best.\n2. For each machine that isn't the \"master\" (i.e. \"slaves\"), back up all the\n   configuration files for each app that you want to sync. That's it for now.\n   However, there will be more work for you later.\n\n#### Method 2 Example\n\nLet's say we have two machines, A and B, and we want to sync our bash configuration\nacross the machines. We decide that Machine A will serve as our master.\n\nSince A is our master, we only need to backup the bash configuration files on\nMachine B:\n\n##### Method 2: Sample backup commands for Machine B\n\n```bash\nmkdir ~/bash_backup\ncp ~/.bash_profile ~/bash_backup/bash_profile.bak\ncp ~/.bash_login ~/bash_backup/bash_login.bak\n\n...plus any other bash config files you want to keep\n```\n\nIf you have other machines you are syncing with the master, back those up, too.\n\n## Step 3: Push Out the Configuration Files with Mackup\n\nNow you are ready to use Mackup to push out the changes. You should have Mackup\nalready installed and the `.mackup.cfg` file in place according to the\ninstructions provided above. If not, do that before proceeding.\n\nRun the following command on the master machine:\n\n`mackup backup`\n\nOn each of the other \"slave\" machines, run:\n\n`mackup restore`\n\nIf you used Method 1 in Step 2 above, you are done. You may discover\nthat you didn't quite merge the files exactly the way you wanted but don't\nworry, that's why you created the configuration file backups. You can grab\nsnippets from these backup configuration files and add them in to the live\nconfiguration files and then easily push the changes out to all your\nmachines using mackup.\n\nIf you used Method 2, you'll need to merge in new features over time. As you\ndiscover features you need to add, you'll need to take the appropriate snippets\nof configuration code from the backup configuration files you created and\ninsert them into the appropriate configuration file. Remember it does not matter\nwhich machine's configuration file you update as these configuration files are\nnow shared across all machines.\n"
  },
  {
    "path": "doc/develop.md",
    "content": "# Develop\n\n```sh\n# Install the tool for dependency management and packaging in Python\npipx install uv\n\n# You can now edit files and see the impact of your changes\nuv run mackup --version\nmake test\n```\n\n## Running Tests with Coverage\n\nTo run tests with coverage reporting:\n\n```sh\n# Run tests with coverage\nmake coverage\n\n# View coverage report in terminal\nmake coverage-report\n\n# Open HTML coverage report\nopen htmlcov/index.html\n```\n\n## Code Quality Checks\n\nThe project includes several code quality tools:\n\n```sh\n# Run all checks (ruff, mypy, pytest)\nmake check\n\n# Run individual checks\nmake ruff      # Code linting\nmake mypy      # Type checking\nmake test      # Unit tests\nmake coverage  # Tests with coverage\n```\n\n## Coverage Configuration\n\nCoverage is configured in `pyproject.toml` with:\n\n- Branch coverage enabled\n- Test files excluded from coverage\n- HTML and XML reports generated\n- 67%+ coverage currently achieved\n\nYou can view detailed coverage reports by running `make coverage` and opening\n`htmlcov/index.html` in your browser.\n"
  },
  {
    "path": "doc/release.md",
    "content": "# Release\n\n1. Increment the version in [pyproject.toml](../pyproject.toml)\n1. Run `uv sync -U`\n1. `git commit` with the message `Mackup X.Y.Z`\n1. `git tag <version>`\n1. `git push`\n1. `git push --tags`\n1. Do a release at <https://github.com/lra/mackup/releases>\n1. `make release`\n"
  },
  {
    "path": "doc/syncing-private-keys.md",
    "content": "# Syncing private keys\n\nBy default private keys for OpenSSH and GnuPG are NOT sycned.\nYou can sync your private keys if you want.\nFor example, to sync your entire OpenSSH `.ssh` directory,\ncreate a `~/.mackup/ssh.cfg` file with the following content:\n\n```ini\n[application]\nname = SSH\n\n[configuration_files]\n.ssh\n```\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"mackup\"\nversion = \"0.10.2\"\ndescription = \"Backup and keep your application settings in sync.\"\nreadme = \"README.md\"\ndependencies = [\n    \"docopt-ng>=0.9.0\",\n]\nrequires-python = \">= 3.9\"\nauthors = [\n\t{ name = \"Laurent Raufaste\", email = \"analogue@glop.org\" }\n]\nlicense = \"GPL-3.0-or-later\"\nlicense-files = [\"LICENSE\"]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Console\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: System Administrators\",\n    \"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)\",\n    \"Operating System :: MacOS :: MacOS X\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Topic :: System :: Archiving :: Backup\",\n    \"Topic :: Utilities\",\n]\n\n[project.scripts]\nmackup = \"mackup.main:main\"\n\n[project.urls]\nRepository = \"https://github.com/lra/mackup.git\"\nIssues = \"https://github.com/lra/mackup/issues\"\nChangelog = \"https://github.com/lra/mackup/releases\"\n\n[dependency-groups]\ndev = [\n    \"pytest>=8.4.2\",\n    \"pytest-cov>=6.0.0\",\n    \"coverage[toml]>=7.6.0\",\n    \"mypy>=1.13.0\",\n    \"ruff>=0.14.4\",\n    \"ty>=0.0.16\",\n]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.mypy]\npython_version = \"3.9\"\nwarn_return_any = true\nwarn_unused_configs = true\ndisallow_untyped_defs = true\ndisallow_incomplete_defs = true\ncheck_untyped_defs = true\nno_implicit_optional = true\nwarn_redundant_casts = true\nwarn_unused_ignores = true\nwarn_no_return = true\nwarn_unreachable = true\nstrict_equality = true\nshow_error_codes = true\n\n[tool.coverage.run]\nsource = [\"src/mackup\"]\nbranch = true\nomit = [\n    \"*/tests/*\",\n    \"*/__pycache__/*\",\n    \"*/.venv/*\",\n]\n\n[tool.coverage.report]\nprecision = 2\nshow_missing = true\nskip_covered = false\nexclude_lines = [\n    \"pragma: no cover\",\n    \"def __repr__\",\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n    \"if __name__ == .__main__.:\",\n    \"if TYPE_CHECKING:\",\n    \"class .*\\\\bProtocol\\\\):\",\n    \"@(abc\\\\.)?abstractmethod\",\n]\n\n[tool.coverage.html]\ndirectory = \"htmlcov\"\n\n[tool.pytest]\nstrict = true\nminversion = \"9.0\"\ntestpaths = [\"tests\"]\npythonpath = [\"src\"]\naddopts = [\"-ra\"]\n\n# https://docs.astral.sh/ruff/rules/\n[tool.ruff.lint]\nselect = [\n    \"A\", # flake8-builtins\n    \"ASYNC\", # flake8-async\n    \"B\", # flake8-bugbear\n    \"C4\", # flake8-comprehensions\n    \"C90\", # mccabe\n    \"COM\", # flake8-commas\n    \"DTZ\", # flake8-datetimez\n    \"E\", # pycodestyle error\n    \"EXE\",  # flake8-executable\n    \"F\", # pyflakes\n    \"FA\", # flake8-future-annotations\n    \"FLY\", # flynt, for f-strings\n    \"FURB\", # refurb\n    \"G\", # flake8-logging-format\n    \"I\", # isort\n    \"ICN\", # flake8-import-conventions\n    \"INP\", # flake8-no-pep420\n    \"INT\", # flake8-gettext\n    \"N\", # pep8-naming\n    \"PERF\", # perflint\n    \"PIE\", # flake8-pie\n    \"PLC\", # pylint convention\n    \"PLE\", # pylint error\n    \"PLR\", # pylint refactor\n    \"PLW\", # pylint warning\n    \"PT\", # flake8-pytest-style\n    \"PYI\", # flake8-pyi\n    \"Q\", # flake8-quotes\n    \"RSE\", # flake8-raise\n    \"RUF\", # ruff\n    \"SIM\", # flake8-simplify\n    \"SLF\", # flake8-self\n    \"SLOT\", # flake8-slots\n    \"TCH\", # flake8-type-checking\n    \"TID\", # flake8-tidy-imports\n    \"UP\", # pyupgrade\n    \"W\", # pycodestyle warning\n]\nignore = [\n    \"C901\", # mccabe complexity - too complex functions\n    \"FA100\", # future annotations - would change behavior\n    \"PLR0912\", # too many branches\n    \"PLR0915\", # too many statements\n    \"PTH\", # use pathlib - os.path is fine for this codebase\n    \"SIM115\", # use context manager for open - some cases require this\n    \"SLF001\", # private member access - needed for testing\n]\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: mackup\nbase: core24\nadopt-info: mackup\ntitle: Mackup\nsummary: Keep your application settings in sync\ndescription: |\n  Back ups your application settings in a safe directory (e.g. Dropbox).\n  Syncs your application settings among all your workstations.\n  Restores your configuration on any fresh install in one command line.\n\nlicense: GPL-3.0-or-later\ncontact: analogue@glop.org\nwebsite: https://github.com/lra/mackup\nsource-code: https://github.com/lra/mackup\nissues: https://github.com/lra/mackup/issues\n\ngrade: stable\nconfinement: classic\n\napps:\n  mackup:\n    command: bin/mackup\n\nparts:\n  mackup:\n    plugin: python\n    source: .\n    stage-packages:\n      - python3.12-minimal\n      - libpython3.12-minimal\n      - libpython3.12-stdlib\n    override-pull: |\n      craftctl default\n      VERSION=$(python3 -c 'import tomllib; print(tomllib.load(open(\"pyproject.toml\", \"rb\"))[\"project\"][\"version\"])')\n      craftctl set version=\"$VERSION\"\n"
  },
  {
    "path": "src/mackup/__init__.py",
    "content": "\"\"\"The Mackup Package.\"\"\"\n"
  },
  {
    "path": "src/mackup/application.py",
    "content": "\"\"\"\nApplication Profile.\n\nAn Application Profile contains all the information about an application in\nMackup. Name, files, ...\n\"\"\"\n\nimport os\n\nfrom . import utils\nfrom .mackup import Mackup\n\n\nclass ApplicationProfile:\n    \"\"\"Instantiate this class with application specific data.\"\"\"\n\n    def __init__(\n        self, mackup: Mackup, files: set[str], dry_run: bool, verbose: bool,\n    ) -> None:\n        \"\"\"\n        Create an ApplicationProfile instance.\n\n        Args:\n            mackup (Mackup)\n            files (list)\n        \"\"\"\n        assert isinstance(mackup, Mackup)\n        assert isinstance(files, set)\n\n        self.mackup: Mackup = mackup\n        self.files: list[str] = sorted(files)\n        self.dry_run: bool = dry_run\n        self.verbose: bool = verbose\n\n    def get_filepaths(self, filename: str) -> tuple[str, str]:\n        \"\"\"\n        Get home and mackup filepaths for given file\n\n        Args:\n            filename (str)\n\n        Returns:\n            home_filepath, mackup_filepath (str, str)\n        \"\"\"\n        return (\n            os.path.join(os.environ[\"HOME\"], filename),\n            os.path.join(self.mackup.mackup_folder, filename),\n        )\n\n    def copy_files_to_mackup_folder(self) -> None:\n        \"\"\"\n        Backup the application config files to the Mackup folder.\n\n        Algorithm:\n            for config_file\n                if config_file exists and is a real file/folder\n                    if home/file is a symlink pointing to mackup/file\n                        skip (already backed up via link install)\n                    if exists mackup/file\n                        are you sure?\n                        if sure\n                            rm mackup/file\n                    cp home/file mackup/file\n        \"\"\"\n        for filename in self.files:\n            (home_filepath, mackup_filepath) = self.get_filepaths(filename)\n\n            # If config_file exists and is a real file/folder\n            if (os.path.isfile(home_filepath) or os.path.isdir(home_filepath)):\n                # Check if home file is a symlink pointing to mackup file\n                # (already backed up via link install)\n                if (\n                    os.path.islink(home_filepath)\n                    and os.path.exists(mackup_filepath)\n                    and os.path.samefile(home_filepath, mackup_filepath)\n                ):\n                    if self.verbose:\n                        print(\n                            f\"Skipping {home_filepath}\\n\"\n                            f\"  already linked to\\n  {mackup_filepath}\",\n                        )\n                    continue\n\n                if self.verbose:\n                    print(\n                        f\"Backing up\\n  {home_filepath}\\n  to\\n  {mackup_filepath} ...\",\n                    )\n                else:\n                    print(f\"Backing up {filename} ...\")\n\n                if self.dry_run:\n                    continue\n\n                # If exists mackup/file\n                if os.path.lexists(mackup_filepath):\n                    # Name it right\n                    file_type: str\n                    if os.path.isfile(mackup_filepath):\n                        file_type = \"file\"\n                    elif os.path.isdir(mackup_filepath):\n                        file_type = \"folder\"\n                    elif os.path.islink(mackup_filepath):\n                        file_type = \"link\"\n                    else:\n                        raise ValueError(f\"Unsupported file: {mackup_filepath}\")\n                    # Ask the user if he really wants to replace it\n                    if utils.confirm(\n                        f\"A {file_type} named {mackup_filepath} already exists in the\"\n                        \" Mackup folder.\\nAre you sure that you want to\"\n                        \" replace it? (use --force to skip this prompt)\",\n                    ):\n                        # If confirmed, delete the file in Mackup\n                        utils.delete(mackup_filepath)\n                    else:\n                        continue\n\n                # Copy the file\n                try:\n                    utils.copy(home_filepath, mackup_filepath)\n                except PermissionError as e:\n                    print(\n                        f\"Error: Unable to copy file from {home_filepath} to \"\n                        f\"{mackup_filepath} due to permission issue: {e}\",\n                    )\n\n    def copy_files_from_mackup_folder(self) -> None:\n        \"\"\"\n        Recover the application config files from the Mackup folder.\n\n        Algorithm:\n            for config_file\n                if config_file exists in mackup and is a real file/folder\n                    if exists home/file\n                        are you sure?\n                        if sure\n                            rm home/file\n                    cp mackup/file home/file\n        \"\"\"\n        for filename in self.files:\n            (home_filepath, mackup_filepath) = self.get_filepaths(filename)\n\n            # If config_file exists in mackup and is a real file/folder\n            if (os.path.isfile(mackup_filepath) or os.path.isdir(mackup_filepath)):\n                if self.verbose:\n                    print(\n                        f\"Recovering\\n  {mackup_filepath}\\n  to\\n  {home_filepath} ...\",\n                    )\n                else:\n                    print(f\"Recovering {filename} ...\")\n\n                if self.dry_run:\n                    continue\n\n                # If exists home/file\n                if os.path.lexists(home_filepath):\n                    # Name it right\n                    if os.path.isfile(home_filepath):\n                        file_type = \"file\"\n                    elif os.path.isdir(home_filepath):\n                        file_type = \"folder\"\n                    elif os.path.islink(home_filepath):\n                        file_type = \"link\"\n                    else:\n                        raise ValueError(f\"Unsupported file: {home_filepath}\")\n                    # Ask the user if he really wants to replace it\n                    if utils.confirm(\n                        f\"A {file_type} named {home_filepath} already exists in your\"\n                        \" home folder.\\nAre you sure that you want to\"\n                        \" replace it?\",\n                    ):\n                        # If confirmed, delete the existing home file\n                        utils.delete(home_filepath)\n                    else:\n                        continue\n\n                # Copy the file\n                try:\n                    utils.copy(mackup_filepath, home_filepath)\n                except PermissionError as e:\n                    print(\n                        f\"Error: Unable to copy file from {mackup_filepath} to \"\n                        f\"{home_filepath} due to permission issue: {e}\",\n                    )\n\n    def link_install(self) -> None:\n        \"\"\"\n        Create the application config file links.\n\n        Algorithm:\n            if exists home/file\n              if home/file is a real file\n                if exists mackup/file\n                  are you sure?\n                  if sure\n                    rm mackup/file\n                    mv home/file mackup/file\n                    link mackup/file home/file\n                else\n                  mv home/file mackup/file\n                  link mackup/file home/file\n        \"\"\"\n        # For each file used by the application\n        for filename in self.files:\n            (home_filepath, mackup_filepath) = self.get_filepaths(filename)\n\n            # If the file exists and is not already a link pointing to Mackup\n            if (os.path.isfile(home_filepath) or os.path.isdir(home_filepath)) and not (\n                os.path.islink(home_filepath)\n                and (os.path.isfile(mackup_filepath) or os.path.isdir(mackup_filepath))\n                and os.path.samefile(home_filepath, mackup_filepath)\n            ):\n                if self.verbose:\n                    print(\n                        f\"Backing up\\n  {home_filepath}\\n  to\\n  {mackup_filepath} ...\",\n                    )\n                else:\n                    print(f\"Linking {filename} ...\")\n\n                if self.dry_run:\n                    continue\n\n                # Check if we already have a backup\n                if os.path.exists(mackup_filepath):\n                    # Name it right\n                    if os.path.isfile(mackup_filepath):\n                        file_type = \"file\"\n                    elif os.path.isdir(mackup_filepath):\n                        file_type = \"folder\"\n                    elif os.path.islink(mackup_filepath):\n                        file_type = \"link\"\n                    else:\n                        raise ValueError(f\"Unsupported file: {mackup_filepath}\")\n\n                    # Ask the user if he really wants to replace it\n                    if utils.confirm(\n                        f\"A {file_type} named {mackup_filepath} already exists in the\"\n                        \" backup.\\nAre you sure that you want to\"\n                        \" replace it?\",\n                    ):\n                        # Delete the file in Mackup\n                        utils.delete(mackup_filepath)\n                        # Copy the file\n                        utils.copy(home_filepath, mackup_filepath)\n                        # Delete the file in the home\n                        utils.delete(home_filepath)\n                        # Link the backuped file to its original place\n                        utils.link(mackup_filepath, home_filepath)\n                else:\n                    # Copy the file\n                    utils.copy(home_filepath, mackup_filepath)\n                    # Delete the file in the home\n                    utils.delete(home_filepath)\n                    # Link the backuped file to its original place\n                    utils.link(mackup_filepath, home_filepath)\n            elif self.verbose:\n                if os.path.exists(home_filepath):\n                    print(\n                        f\"Doing nothing\\n  {home_filepath}\\n  \"\n                        f\"is already backed up to\\n  {mackup_filepath}\",\n                    )\n                elif os.path.islink(home_filepath):\n                    print(\n                        f\"Doing nothing\\n  {home_filepath}\\n  \"\n                        \"is a broken link, you might want to fix it.\",\n                    )\n                else:\n                    print(f\"Doing nothing\\n  {home_filepath}\\n  does not exist\")\n\n    def link(self) -> None:\n        \"\"\"\n        Link the application config files.\n\n        Algorithm:\n            if exists mackup/file\n              if exists home/file\n                are you sure?\n                if sure\n                  rm home/file\n                  link mackup/file home/file\n              else\n                link mackup/file home/file\n        \"\"\"\n        # For each file used by the application\n        for filename in self.files:\n            (home_filepath, mackup_filepath) = self.get_filepaths(filename)\n\n            # If the file exists and is not already pointing to the mackup file\n            # and the folder makes sense on the current platform (Don't sync\n            # any subfolder of ~/Library on GNU/Linux)\n            file_or_dir_exists: bool = os.path.isfile(mackup_filepath) or os.path.isdir(\n                mackup_filepath,\n            )\n            pointing_to_mackup: bool = (\n                os.path.islink(home_filepath)\n                and os.path.exists(mackup_filepath)\n                and os.path.samefile(mackup_filepath, home_filepath)\n            )\n            supported: bool = utils.can_file_be_synced_on_current_platform(filename)\n\n            if file_or_dir_exists and not pointing_to_mackup and supported:\n                if self.verbose:\n                    print(\n                        f\"Restoring\\n  linking {home_filepath}\\n\"\n                        f\"  to      {mackup_filepath} ...\",\n                    )\n                else:\n                    print(f\"Restoring {filename} ...\")\n\n                if self.dry_run:\n                    continue\n\n                # Check if there is already a file in the home folder\n                if os.path.exists(home_filepath):\n                    # Name it right\n                    if os.path.isfile(home_filepath):\n                        file_type = \"file\"\n                    elif os.path.isdir(home_filepath):\n                        file_type = \"folder\"\n                    elif os.path.islink(home_filepath):\n                        file_type = \"link\"\n                    else:\n                        raise ValueError(f\"Unsupported file: {home_filepath}\")\n\n                    if utils.confirm(\n                        f\"You already have a {file_type} at {home_filepath}.\\n\"\n                        \"Do you want to replace it with your backup?\",\n                    ):\n                        utils.delete(home_filepath)\n                        utils.link(mackup_filepath, home_filepath)\n                else:\n                    utils.link(mackup_filepath, home_filepath)\n            elif self.verbose:\n                if os.path.exists(home_filepath):\n                    print(\n                        f\"Doing nothing\\n  {mackup_filepath}\\n\"\n                        f\"  already linked by\\n  {home_filepath}\",\n                    )\n                elif os.path.islink(home_filepath):\n                    print(\n                        f\"Doing nothing\\n  {home_filepath}\\n  \"\n                        \"is a broken link, you might want to fix it.\",\n                    )\n                else:\n                    print(\n                        f\"Doing nothing\\n  {mackup_filepath}\\n  does not exist\",\n                    )\n\n    def link_uninstall(self) -> None:\n        \"\"\"\n        Removes links and copy config files from the remote folder locally.\n\n        Algorithm:\n            for each file in config\n                if mackup/file exists\n                    if home/file exists\n                        delete home/file\n                    copy mackup/file home/file\n        \"\"\"\n        # For each file used by the application\n        for filename in self.files:\n            (home_filepath, mackup_filepath) = self.get_filepaths(filename)\n\n            # If the mackup file exists\n            if os.path.isfile(mackup_filepath) or os.path.isdir(mackup_filepath):\n                # Check if there is a corresponding file in the home folder\n                if os.path.exists(home_filepath):\n                    # If the home file is not a link or does not point to the\n                    # mackup file, display a warning and skip it.\n                    if not os.path.islink(home_filepath) or not os.path.samefile(\n                        home_filepath, mackup_filepath,\n                    ):\n                        print(\n                            f'Warning: the file in your home \"{home_filepath}\" '\n                            f\"does not point to the original file in Mackup \"\n                            f\"{mackup_filepath}, skipping...\",\n                        )\n                        continue\n                    if self.verbose:\n                        print(\n                            f\"Reverting {mackup_filepath}\\n at {home_filepath} ...\",\n                        )\n                    else:\n                        print(f\"Reverting {filename} ...\")\n\n                    if self.dry_run:\n                        continue\n\n                    # If there is, delete it as we are gonna copy the Dropbox\n                    # one there\n                    utils.delete(home_filepath)\n\n                    # Copy the Dropbox file to the home folder\n                    utils.copy(mackup_filepath, home_filepath)\n            elif self.verbose:\n                print(f\"Doing nothing, {mackup_filepath} does not exist\")\n"
  },
  {
    "path": "src/mackup/applications/1password-4.cfg",
    "content": "[application]\nname = 1Password 4\n\n[configuration_files]\nLibrary/Preferences/com.agilebits.onepassword4.plist\nLibrary/Preferences/ws.agile.1Password.plist\n"
  },
  {
    "path": "src/mackup/applications/2do.cfg",
    "content": "[application]\nname = 2Do\n\n[configuration_files]\nLibrary/Preferences/com.guidedways.TodoMac.plist\n"
  },
  {
    "path": "src/mackup/applications/ack.cfg",
    "content": "[application]\nname = Ack\n\n[configuration_files]\n.ackrc\n"
  },
  {
    "path": "src/mackup/applications/act.cfg",
    "content": "[application]\nname = act\n\n[configuration_files]\n.actrc\n"
  },
  {
    "path": "src/mackup/applications/activitywatch.cfg",
    "content": "[application]\nname = Activity Watch\n\n[configuration_files]\nLibrary/Application Support/activitywatch\n"
  },
  {
    "path": "src/mackup/applications/adium.cfg",
    "content": "[application]\nname = Adium\n\n[configuration_files]\nLibrary/Application Support/Adium 2.0\nLibrary/Preferences/com.adiumX.adiumX.plist\n"
  },
  {
    "path": "src/mackup/applications/adobe-camera-raw.cfg",
    "content": "[application]\nname = Adobe Camera Raw\n\n[configuration_files]\nLibrary/Application Support/Adobe/CameraRaw\n"
  },
  {
    "path": "src/mackup/applications/aerc.cfg",
    "content": "[application]\nname = aerc\n\n[configuration_files]\nLibrary/Preferences/aerc\n"
  },
  {
    "path": "src/mackup/applications/aerospace.cfg",
    "content": "[application]\nname = aerospace\n\n[configuration_files]\n.aerospace.toml\n\n[xdg_configuration_files]\naerospace/aerospace.toml\n"
  },
  {
    "path": "src/mackup/applications/affinity-designer.cfg",
    "content": "[application]\nname = Affinity Designer\n\n[configuration_files]\nLibrary/Containers/Affinity Designer/Data/Library/Application Support/user\n"
  },
  {
    "path": "src/mackup/applications/affinity-photo.cfg",
    "content": "[application]\nname = Affinity Photo\n\n[configuration_files]\nLibrary/Containers/Affinity Photo/Data/Library/Application Support/user\n"
  },
  {
    "path": "src/mackup/applications/affinity-publisher.cfg",
    "content": "[application]\nname = Affinity Publisher\n\n[configuration_files]\nLibrary/Containers/Affinity Publisher/Data/Library/Application Support/user\n"
  },
  {
    "path": "src/mackup/applications/airflow.cfg",
    "content": "[application]\nname = Airflow\n\n[configuration_files]\nLibrary/Preferences/com.bitcavehq.Airflow.plist\nLibrary/Application Support/Airflow\n"
  },
  {
    "path": "src/mackup/applications/airmail.cfg",
    "content": "[application]\nname = Airmail\n\n[configuration_files]\nLibrary/Containers/it.bloop.airmail2/Container.plist\n"
  },
  {
    "path": "src/mackup/applications/akamai-cli.cfg",
    "content": "[application]\nname = Akamai-CLI\n\n[configuration_files]\n.edgerc\n"
  },
  {
    "path": "src/mackup/applications/alacritty.cfg",
    "content": "[application]\nname = Alacritty\n\n[configuration_files]\n.alacritty.yml\n.alacritty.toml\n\n[xdg_configuration_files]\nalacritty/alacritty.yml\nalacritty/alacritty.toml\n"
  },
  {
    "path": "src/mackup/applications/aldente.cfg",
    "content": "[application]\nname = AlDente\n\n[configuration_files]\nLibrary/Application Support/AlDente\nLibrary/Preferences/com.apphousekitchen.aldente-pro.plist\n"
  },
  {
    "path": "src/mackup/applications/alt-tab.cfg",
    "content": "[application]\nname = AltTab\n\n[configuration_files]\nLibrary/Preferences/com.lwouis.alt-tab-macos.plist\n"
  },
  {
    "path": "src/mackup/applications/amethyst.cfg",
    "content": "[application]\nname = Amethyst\n\n[configuration_files]\nLibrary/Preferences/com.amethyst.Amethyst.plist\n\n[xdg_configuration_files]\namethyst/amethyst.yml\n"
  },
  {
    "path": "src/mackup/applications/ancient-domains-of-mystery.cfg",
    "content": "[application]\nname = Ancient Domains of Mystery\n\n[configuration_files]\n.adom.data\n"
  },
  {
    "path": "src/mackup/applications/androidstudio-13.cfg",
    "content": "[application]\nname = Android Studio 1.3\n\n[configuration_files]\nLibrary/Application Support/AndroidStudio1.3\nLibrary/Preferences/AndroidStudio1.3\n"
  },
  {
    "path": "src/mackup/applications/ansible.cfg",
    "content": "[application]\nname = Ansible\n\n[configuration_files]\n.ansible\n.ansible.cfg\n"
  },
  {
    "path": "src/mackup/applications/appcleaner.cfg",
    "content": "[application]\nname = AppCleaner\n\n[configuration_files]\nLibrary/Preferences/net.freemacsoft.AppCleaner.plist\nLibrary/Preferences/net.freemacsoft.AppCleaner-SmartDelete.plist\n"
  },
  {
    "path": "src/mackup/applications/appcode-2.cfg",
    "content": "[application]\nname = AppCode 2\n\n[configuration_files]\nLibrary/Application Support/appCode20\nLibrary/Preferences/appCode20\n"
  },
  {
    "path": "src/mackup/applications/appcode-3.cfg",
    "content": "[application]\nname = AppCode 3\n\n[configuration_files]\nLibrary/Application Support/appCode30\nLibrary/Preferences/appCode30\n"
  },
  {
    "path": "src/mackup/applications/appcode-31.cfg",
    "content": "[application]\nname = AppCode 3.1\n\n[configuration_files]\nLibrary/Application Support/appCode31\nLibrary/Preferences/appCode31\n"
  },
  {
    "path": "src/mackup/applications/appcode-32.cfg",
    "content": "[application]\nname = AppCode 3.2\n\n[configuration_files]\nLibrary/Application Support/AppCode32\nLibrary/Preferences/AppCode32\n"
  },
  {
    "path": "src/mackup/applications/apple-music.cfg",
    "content": "[application]\nname = Apple Music\n\n[configuration_files]\nLibrary/Music/Scripts\nLibrary/Preferences/com.apple.Music.eq.plist\nLibrary/Preferences/com.apple.Music.plist\n"
  },
  {
    "path": "src/mackup/applications/apptivate.cfg",
    "content": "[application]\nname = Apptivate\n\n[configuration_files]\nLibrary/Application Support/Apptivate/hotkeys\n"
  },
  {
    "path": "src/mackup/applications/arara.cfg",
    "content": "[application]\nname = Arara\n\n[configuration_files]\n.araraconfig.yaml\nararaconfig.yaml\n"
  },
  {
    "path": "src/mackup/applications/aria2c.cfg",
    "content": "[application]\nname = aria2c\n\n[configuration_files]\n.aria2\n\n[xdg_configuration_files]\naria2\n"
  },
  {
    "path": "src/mackup/applications/arm.cfg",
    "content": "[application]\nname = Arm\n\n[configuration_files]\n.arm/armrc\n"
  },
  {
    "path": "src/mackup/applications/asciinema.cfg",
    "content": "[application]\nname = asciinema\n\n[xdg_configuration_files]\nasciinema\n"
  },
  {
    "path": "src/mackup/applications/asdf.cfg",
    "content": "[application]\nname = asdf\n\n[configuration_files]\n.asdfrc\n.default-gems\n.default-npm-packages\n.default-python-packages\n.default-golang-pkgs\n.default-mix-commands\n.tool-versions\n"
  },
  {
    "path": "src/mackup/applications/aspell.cfg",
    "content": "[application]\nname = Aspell\n\n[configuration_files]\n.aspell.conf\n.aspell.en.prepl\n.aspell.en.pws\n"
  },
  {
    "path": "src/mackup/applications/astyle.cfg",
    "content": "[application]\nname = Artistic Style\n\n[configuration_files]\n.astylerc\n"
  },
  {
    "path": "src/mackup/applications/atlantis.cfg",
    "content": "[application]\nname = Atlantis\n\n[configuration_files]\nLibrary/Application Support/Atlantis\n"
  },
  {
    "path": "src/mackup/applications/atom.cfg",
    "content": "[application]\nname = Atom\n\n[configuration_files]\n\nLibrary/Preferences/com.github.atom.plist\n.atom/config.cson\n.atom/init.coffee\n.atom/keymap.cson\n.atom/keymaps\n.atom/snippets.cson\n.atom/styles.less\n"
  },
  {
    "path": "src/mackup/applications/audacious.cfg",
    "content": "[application]\nname = Audacious\n\n[xdg_configuration_files]\naudacious/config\naudacious/plugin-registry\naudacious/playlists\naudacious/playlist-state\naudacious/scrobbler.log\n"
  },
  {
    "path": "src/mackup/applications/auskey.cfg",
    "content": "[application]\nname = AusKey\n\n[configuration_files]\nLibrary/Application Support/AUSkey/keystore.xml\n"
  },
  {
    "path": "src/mackup/applications/autokey.cfg",
    "content": "[application]\nname = Autokey\n\n[xdg_configuration_files]\nautokey\n"
  },
  {
    "path": "src/mackup/applications/awareness.cfg",
    "content": "[application]\nname = Awareness\n\n[configuration_files]\nLibrary/Preferences/com.futureproof.awareness.plist\n"
  },
  {
    "path": "src/mackup/applications/aws.cfg",
    "content": "[application]\nname = AWS CLI\n\n[configuration_files]\n.aws\n"
  },
  {
    "path": "src/mackup/applications/azure.cfg",
    "content": "[application]\nname = Azure CLI\n\n[configuration_files]\n.azure\n"
  },
  {
    "path": "src/mackup/applications/b-ryan_powerline-shell.cfg",
    "content": "[application]\nname = Powerline Shell Prompt\n\n[xdg_configuration_files]\npowerline-shell/config.json\n"
  },
  {
    "path": "src/mackup/applications/bartender.cfg",
    "content": "[application]\nname = Bartender\n\n[configuration_files]\nLibrary/Preferences/com.surteesstudios.Bartender.plist\nLibrary/Preferences/com.surteesstudios.Bartender-setapp.plist\nLibrary/Application Support/Bartender/Bartender.BartenderPreferences\n"
  },
  {
    "path": "src/mackup/applications/base.cfg",
    "content": "[application]\nname = Base\n\n[configuration_files]\nLibrary/Containers/uk.co.menial.Base/Data/Library/Preferences/uk.co.menial.Base.plist\n# contains license and preference state\nLibrary/Containers/uk.co.menial.Base/Data/Library/Application Support/Base/\n"
  },
  {
    "path": "src/mackup/applications/bash-it.cfg",
    "content": "[application]\nname = Bash it\n\n[configuration_files]\n.bash_it/enabled\n"
  },
  {
    "path": "src/mackup/applications/bash.cfg",
    "content": "[application]\nname = Bash\n\n[configuration_files]\n.aliases\n.bash_aliases\n.bash_login\n.bash_logout\n.bashrc\n.profile\n.bash_profile\n.inputrc\n"
  },
  {
    "path": "src/mackup/applications/bat.cfg",
    "content": "[application]\nname = Bat\n\n[xdg_configuration_files]\nbat/config\nbat/syntaxes\nbat/themes\n"
  },
  {
    "path": "src/mackup/applications/bc.cfg",
    "content": "[application]\nname = bc\n\n[configuration_files]\n.bcrc\n"
  },
  {
    "path": "src/mackup/applications/beatport-pro.cfg",
    "content": "[application]\nname = Beatport Pro\n\n[configuration_files]\nLibrary/Preferences/com.beatport.BeatportPro.plist\n"
  },
  {
    "path": "src/mackup/applications/beets.cfg",
    "content": "[application]\nname = Beets\n\n[xdg_configuration_files]\nbeets/config.yaml\n"
  },
  {
    "path": "src/mackup/applications/bettersnaptool.cfg",
    "content": "[application]\nname = BetterSnapTool\n\n[configuration_files]\nLibrary/Preferences/com.hegenberg.BetterSnapTool.plist\nLibrary/Application Support/BetterSnapTool\n"
  },
  {
    "path": "src/mackup/applications/bettertouchtool.cfg",
    "content": "[application]\nname = BetterTouchTool\n\n[configuration_files]\nLibrary/Application Support/BetterTouchTool/bettertouchtool.license\nLibrary/Application Support/BetterTouchTool/btt_data_store.v2\nLibrary/Application Support/BetterTouchTool/btt_data_store.v2-shm\nLibrary/Application Support/BetterTouchTool/btt_data_store.v2-wal\nLibrary/Application Support/BetterTouchTool/bttdata2\nLibrary/Preferences/com.hegenberg.BetterTouchTool.plist\nLibrary/Application Support/BetterTouchTool/bettersnaptool.bttlicense\n"
  },
  {
    "path": "src/mackup/applications/beyond-compare.cfg",
    "content": "[application]\nname = Beyond Compare\n\n[configuration_files]\nLibrary/Preferences/com.ScooterSoftware.BeyondCompare.plist\nLibrary/Application Support/Beyond Compare/BC4Key.txt\nLibrary/Application Support/Beyond Compare/BCColors.xml\nLibrary/Application Support/Beyond Compare/BCCommands.xml\nLibrary/Application Support/Beyond Compare/BCPreferences.xml\n"
  },
  {
    "path": "src/mackup/applications/bibdesk.cfg",
    "content": "[application]\nname = BibDesk\n\n[configuration_files]\nLibrary/Preferences/edu.ucsd.cs.mmccrack.bibdesk.plist\nLibrary/Application Support/BibDesk\n"
  },
  {
    "path": "src/mackup/applications/billings-pro-server-admin.cfg",
    "content": "[application]\nname = Billings Pro Server Admin\n\n[configuration_files]\nLibrary/Preferences/com.marketcircle.BillingsProServerAdmin.plist\n"
  },
  {
    "path": "src/mackup/applications/bitbar.cfg",
    "content": "[application]\nname = BitBar\n\n[configuration_files]\nLibrary/Preferences/com.matryer.BitBar.plist\n"
  },
  {
    "path": "src/mackup/applications/bitchx.cfg",
    "content": "[application]\nname = Bitchx\n\n[configuration_files]\n.bitchxrc\n.ircservers\n"
  },
  {
    "path": "src/mackup/applications/blackfire.cfg",
    "content": "[application]\nname = Blackfire\n\n[configuration_files]\n.blackfire.ini\n"
  },
  {
    "path": "src/mackup/applications/blender.cfg",
    "content": "[application]\nname = Blender\n\n[configuration_files]\nLibrary/Application Support/Blender\n\n[xdg_configuration_files]\nblender\n"
  },
  {
    "path": "src/mackup/applications/blesh.cfg",
    "content": "[application]\nname = ble.sh\n\n[configuration_files]\n.blerc\n"
  },
  {
    "path": "src/mackup/applications/boto.cfg",
    "content": "[application]\nname = Boto\n\n[configuration_files]\n.boto\n"
  },
  {
    "path": "src/mackup/applications/boxer.cfg",
    "content": "[application]\nname = Boxer\n\n[configuration_files]\nLibrary/Preferences/net.washboardabs.boxer.plist\nApplications/DOS Games\n"
  },
  {
    "path": "src/mackup/applications/brackets.cfg",
    "content": "[application]\nname = Brackets\n\n[configuration_files]\nLibrary/Application Support/Brackets\nLibrary/Preferences/io.brackets.appshell.plist\n"
  },
  {
    "path": "src/mackup/applications/brave.cfg",
    "content": "[application]\nname = Brave\n\n[configuration_files]\nLibrary/Application Support/BraveSoftware/Brave-Browser/Default/Preferences\n"
  },
  {
    "path": "src/mackup/applications/btop.cfg",
    "content": "[application]\nname = btop\n\n[xdg_configuration_files]\nbtop\n"
  },
  {
    "path": "src/mackup/applications/bump.cfg",
    "content": "[application]\nname = Bump\n\n[configuration_files]\n.bump.json\n"
  },
  {
    "path": "src/mackup/applications/bundler.cfg",
    "content": "[application]\nname = Bundler\n\n[configuration_files]\n.bundle/config\n"
  },
  {
    "path": "src/mackup/applications/byobu.cfg",
    "content": "[application]\nname = Byobu\n\n[configuration_files]\n.byobu/.screenrc\n.byobu/.tmux.conf\n.byobu/backend\n.byobu/color\n.byobu/color.tmux\n.byobu/datetime.tmux\n.byobu/keybindings\n.byobu/keybindings.tmux\n.byobu/profile\n.byobu/profile.tmux\n.byobu/prompt\n.byobu/prompt.tmux\n.byobu/status\n.byobu/statusrc\n.byobu/windows\n.byobu/windows.tmux\n.byoburc\n.byoburc.tmux\n.byoburc.screen\n"
  },
  {
    "path": "src/mackup/applications/caffeine.cfg",
    "content": "[application]\nname = Caffeine\n\n[configuration_files]\nLibrary/Preferences/com.lightheadsw.Caffeine.plist\nLibrary/Preferences/com.intelliscapesolutions.caffeine.plist\n"
  },
  {
    "path": "src/mackup/applications/calibre.cfg",
    "content": "[application]\nname = Calibre\n\n[configuration_files]\nLibrary/Preferences/calibre\n\n[xdg_configuration_files]\ncalibre\n"
  },
  {
    "path": "src/mackup/applications/capture-one.cfg",
    "content": "[application]\nname = Capture One\n\n[configuration_files]\nLibrary/Application Support/Capture One\n"
  },
  {
    "path": "src/mackup/applications/cartographica.cfg",
    "content": "[application]\nname = Cartographica\n\n[configuration_files]\nLibrary/Application Support/Cartographica\nLibrary/Preferences/com.ClueTrust.Cartographica.plist\n"
  },
  {
    "path": "src/mackup/applications/cerebro.cfg",
    "content": "[application]\nname = Cerebro\n\n[configuration_files]\nLibrary/Application Support/Cerebro/config.json\nLibrary/Application Support/Cerebro/plugins\n\n[xdg_configuration_files]\nCerebro/config.json\nCerebro/plugins\n"
  },
  {
    "path": "src/mackup/applications/charles.cfg",
    "content": "[application]\nname = Charles\n\n[configuration_files]\nLibrary/Application Support/Charles\nLibrary/Preferences/com.xk72.charles.config\n"
  },
  {
    "path": "src/mackup/applications/cheat.cfg",
    "content": "[application]\nname = Cheat\n\n[configuration_files]\n.cheat\n\n[xdg_configuration_files]\ncheat/conf.yml\n"
  },
  {
    "path": "src/mackup/applications/chef.cfg",
    "content": "[application]\nname = Chef\n\n[configuration_files]\n.chef\n"
  },
  {
    "path": "src/mackup/applications/chicken.cfg",
    "content": "[application]\nname = Chicken\n\n[configuration_files]\nLibrary/Preferences/net.sourceforge.chicken.plist\n"
  },
  {
    "path": "src/mackup/applications/choosy.cfg",
    "content": "[application]\nname = Choosy\n\n[configuration_files]\nLibrary/Application Support/Choosy/behaviours.plist\n"
  },
  {
    "path": "src/mackup/applications/chunkwm.cfg",
    "content": "[application]\nname = chunkwm\n\n[configuration_files]\n.chunkwmrc\n.chunkwm_plugins\n"
  },
  {
    "path": "src/mackup/applications/cider.cfg",
    "content": "[application]\nname = Cider\n\n[configuration_files]\n.cider\n"
  },
  {
    "path": "src/mackup/applications/clashx.cfg",
    "content": "[application]\nname = ClashX\n\n[xdg_configuration_files]\nclash\n"
  },
  {
    "path": "src/mackup/applications/clasp.cfg",
    "content": "[application]\nname = Clasp\n\n[configuration_files]\n.clasprc.json\n"
  },
  {
    "path": "src/mackup/applications/claude-code.cfg",
    "content": "[application]\nname = Claude Code\n\n[configuration_files]\n.claude/agents\n.claude/CLAUDE.md\n.claude/hooks\n.claude/keybindings.json\n.claude/plugins/blocklist.json\n.claude/plugins/config.json\n.claude/plugins/installed_plugins.json\n.claude/plugins/known_marketplaces.json\n.claude/settings.json\n.claude/skills\n.claude/statusline.sh\n"
  },
  {
    "path": "src/mackup/applications/cleanshot.cfg",
    "content": "[application]\nname = CleanShot\n\n[configuration_files]\nLibrary/Preferences/com.getcleanshot.app-setapp.plist\nLibrary/Preferences/pl.maketheweb.cleanshotx.plist\n"
  },
  {
    "path": "src/mackup/applications/clementine.cfg",
    "content": "[application]\nname = Clementine\n\n[configuration_files]\nLibrary/Preferences/org.clementine-player.Clementine.plist\n"
  },
  {
    "path": "src/mackup/applications/clion.cfg",
    "content": "[application]\nname = CLion\n\n[configuration_files]\nLibrary/Preferences/CLion2016.2\nLibrary/Application Support/CLion2016.2\nLibrary/Application Support/JetBrains/CLion2023.1\nLibrary/Application Support/JetBrains/CLion2023.2\n"
  },
  {
    "path": "src/mackup/applications/clipmenu.cfg",
    "content": "[application]\nname = ClipMenu\n\n[configuration_files]\nLibrary/Application Support/ClipMenu\nLibrary/Preferences/com.naotaka.ClipMenu.plist\n"
  },
  {
    "path": "src/mackup/applications/clipy.cfg",
    "content": "[application]\nname = Clipy\n\n[configuration_files]\nLibrary/Preferences/com.clipy-app.Clipy.plist\n"
  },
  {
    "path": "src/mackup/applications/cloudapp.cfg",
    "content": "[application]\nname = CloudApp\n\n[configuration_files]\nLibrary/Preferences/com.linebreak.CloudAppMacOSX.plist\n"
  },
  {
    "path": "src/mackup/applications/coda-2.cfg",
    "content": "[application]\nname = Coda 2\n\n[configuration_files]\nLibrary/Application Support/Coda 2\nLibrary/Preferences/com.panic.Coda2.plist\n"
  },
  {
    "path": "src/mackup/applications/codex.cfg",
    "content": "[application]\nname = Codex\n\n[configuration_files]\n.codex/config.toml\n.codex/auth.json\n.codex/AGENTS.md\n"
  },
  {
    "path": "src/mackup/applications/colloquy.cfg",
    "content": "[application]\nname = Colloquy\n\n[configuration_files]\nLibrary/Preferences/info.colloquy.plist\nLibrary/Application Support/Colloquy\n"
  },
  {
    "path": "src/mackup/applications/colorschemer-studio-2.cfg",
    "content": "[application]\nname = ColorSchemer Studio 2\n\n[configuration_files]\nLibrary/Preferences/com.colorschemer.studio2.plist\nLibrary/Application Support/ColorSchemer\n"
  },
  {
    "path": "src/mackup/applications/colorslurp.cfg",
    "content": "[application]\nname = ColorSlurp\n\n[configuration_files]\nLibrary/Preferences/com.IdeaPunch.ColorSlurp.plist\nLibrary/Containers/com.IdeaPunch.ColorSlurp/Data/Library/Application Support/default.realm\n"
  },
  {
    "path": "src/mackup/applications/colorsync.cfg",
    "content": "[application]\nname = ColorSync\n\n[configuration_files]\nLibrary/ColorSync/Profiles\n"
  },
  {
    "path": "src/mackup/applications/composer.cfg",
    "content": "[application]\nname = composer\n\n[configuration_files]\n.composer/auth.json\n.composer/config.json\n.composer/composer.json\n"
  },
  {
    "path": "src/mackup/applications/concentrate.cfg",
    "content": "[application]\nname = Concentrate\n\n[configuration_files]\nLibrary/Application Support/Concentrate/Concentrate.sqlite3\n"
  },
  {
    "path": "src/mackup/applications/conky.cfg",
    "content": "[application]\nname = Conky\n\n[configuration_files]\n.conkyrc\n"
  },
  {
    "path": "src/mackup/applications/consular.cfg",
    "content": "[application]\nname = Consular\n\n[configuration_files]\n.consularc\n\n[xdg_configuration_files]\nconsular\n"
  },
  {
    "path": "src/mackup/applications/contexts.cfg",
    "content": "[application]\nname = Contexts\n\n[configuration_files]\nLibrary/Preferences/com.contextsformac.Contexts.plist\n"
  },
  {
    "path": "src/mackup/applications/controlplane.cfg",
    "content": "[application]\nname = ControlPlane\n\n[configuration_files]\nLibrary/Preferences/com.dustinrue.ControlPlane.plist\n"
  },
  {
    "path": "src/mackup/applications/copyq.cfg",
    "content": "[application]\nname = CopyQ\n\n[xdg_configuration_files]\ncopyq\n"
  },
  {
    "path": "src/mackup/applications/cord.cfg",
    "content": "[application]\nname = CoRD\n\n[configuration_files]\nLibrary/Application Support/CoRD\n"
  },
  {
    "path": "src/mackup/applications/coteditor.cfg",
    "content": "[application]\nname = CotEditor\n\n[configuration_files]\nLibrary/Application Support/CotEditor\nLibrary/Preferences/com.coteditor.CotEditor.plist\n"
  },
  {
    "path": "src/mackup/applications/ctags.cfg",
    "content": "[application]\nname = Ctags\n\n[configuration_files]\n.ctags\n.ctags.d\n"
  },
  {
    "path": "src/mackup/applications/curl.cfg",
    "content": "[application]\nname = Curl\n\n[configuration_files]\n.netrc\n.curlrc\n"
  },
  {
    "path": "src/mackup/applications/cursor.cfg",
    "content": "[application]\nname = Cursor\n\n[configuration_files]\nLibrary/Application Support/Cursor/User/snippets\nLibrary/Application Support/Cursor/User/keybindings.json\nLibrary/Application Support/Cursor/User/settings.json\n\n[xdg_configuration_files]\nCursor/User/snippets\nCursor/User/keybindings.json\nCursor/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/cvim.cfg",
    "content": "[application]\nname = cVim\n\n[configuration_files]\n.cvimrc\n"
  },
  {
    "path": "src/mackup/applications/cyberduck.cfg",
    "content": "[application]\nname = Cyberduck\n\n[configuration_files]\n# based on https://trac.cyberduck.io/wiki/help/en/faq#Preferencesandapplicationsupportfileslocation\nLibrary/Application Support/Cyberduck\nLibrary/Containers/ch.sudo.cyberduck/Data/Library/Application Support/Cyberduck\nLibrary/Group Containers/G69SCX94XU.duck/Library/Application Support/duck\nLibrary/Preferences/ch.sudo.cyberduck.plist\n"
  },
  {
    "path": "src/mackup/applications/daisydisk.cfg",
    "content": "[application]\nname = DaisyDisk\n\n[configuration_files]\nLibrary/Application Support/DaisyDisk/License.DaisyDisk\nLibrary/Preferences/com.daisydiskapp.DaisyDiskStandAlone.plist\n"
  },
  {
    "path": "src/mackup/applications/dash.cfg",
    "content": "[application]\nname = Dash\n\n[configuration_files]\nLibrary/Application Support/Dash/library.dash\nLibrary/Application Support/Dash/License/license.dash-license\nLibrary/Preferences/com.kapeli.dash.plist\nLibrary/Preferences/com.kapeli.dashdoc.plist\n"
  },
  {
    "path": "src/mackup/applications/datagrip.cfg",
    "content": "[application]\nname = DataGrip\n\n[configuration_files]\nLibrary/Application Support/DataGrip2017.3\nLibrary/Preferences/DataGrip2017.3\nLibrary/Application Support/DataGrip2018.1\nLibrary/Preferences/DataGrip2018.1\nLibrary/Application Support/DataGrip2018.2\nLibrary/Preferences/DataGrip2018.2\nLibrary/Application Support/DataGrip2018.3\nLibrary/Preferences/DataGrip2018.3\nLibrary/Application Support/DataGrip2019.1\nLibrary/Preferences/DataGrip2019.1\nLibrary/Preferences/DataGrip2019.2\nLibrary/Application Support/JetBrains/DataGrip2023.1\nLibrary/Application Support/JetBrains/DataGrip2023.2\n"
  },
  {
    "path": "src/mackup/applications/day-o.cfg",
    "content": "[application]\nname = Day-O\n\n[configuration_files]\nLibrary/Preferences/com.shauninman.Day-O.plist\n"
  },
  {
    "path": "src/mackup/applications/dbeaver.cfg",
    "content": "[application]\nname = DBeaver\n\n[configuration_files]\nLibrary/DBeaverData/workspace6/.metadata/.plugins/org.eclipse.core.runtime/.settings\nLibrary/DBeaverData/workspace6/General\n"
  },
  {
    "path": "src/mackup/applications/dbvisualizer.cfg",
    "content": "[application]\nname = DbVisualizer\n\n[configuration_files]\n.dbvis\n"
  },
  {
    "path": "src/mackup/applications/deal-alert.cfg",
    "content": "[application]\nname = Deal Alert\n\n[configuration_files]\nLibrary/Preferences/com.LittleFin.DealAlert.plist\n"
  },
  {
    "path": "src/mackup/applications/deepin-dde-dock.cfg",
    "content": "[application]\nname = deepin-dde-dock\n\n[xdg_configuration_files]\ndeepin/dde-dock.conf\n"
  },
  {
    "path": "src/mackup/applications/deepin-dde-file-manager.cfg",
    "content": "[application]\nname = deepin-dde-file-manager\n\n[xdg_configuration_files]\ndeepin/dde-file-manager.json\n"
  },
  {
    "path": "src/mackup/applications/deepin-terminal.cfg",
    "content": "[application]\nname = deepin-terminal\n\n[xdg_configuration_files]\ndeepin/deepin-terminal/config.conf\n"
  },
  {
    "path": "src/mackup/applications/default-folder-x.cfg",
    "content": "[application]\nname = Default Folder X\n\n[configuration_files]\nLibrary/Preferences/com.stclairsoft.DefaultFolderX.favorites.plist\nLibrary/Preferences/com.stclairsoft.DefaultFolderX.plist\nLibrary/Preferences/com.stclairsoft.DefaultFolderX.settings.plist\n"
  },
  {
    "path": "src/mackup/applications/defaultkeybinding.cfg",
    "content": "[application]\nname = DefaultKeyBinding\n\n[configuration_files]\nLibrary/KeyBindings/DefaultKeyBinding.dict\n"
  },
  {
    "path": "src/mackup/applications/devilspie.cfg",
    "content": "[application]\nname = Devil's Pie\n\n[configuration_files]\n.devilspie\n"
  },
  {
    "path": "src/mackup/applications/devilspie2.cfg",
    "content": "[application]\nname = Devilspie2\n\n[xdg_configuration_files]\ndevilspie2\n"
  },
  {
    "path": "src/mackup/applications/dig.cfg",
    "content": "[application]\nname = dig\n\n[configuration_files]\n.digrc\n"
  },
  {
    "path": "src/mackup/applications/divvy.cfg",
    "content": "[application]\nname = Divvy\n\n[configuration_files]\nLibrary/Preferences/com.mizage.direct.Divvy.plist\nLibrary/Preferences/com.mizage.Divvy.plist\n"
  },
  {
    "path": "src/mackup/applications/docker.cfg",
    "content": "[application]\nname = Docker\n\n[configuration_files]\n.docker/config.json\n.docker/daemon.json\n"
  },
  {
    "path": "src/mackup/applications/dolphin.cfg",
    "content": "[application]\nname = Dolphin\n\n[configuration_files]\nLibrary/Application Support/Dolphin\nLibrary/Preferences/org.dolphin-emu.dolphin.plist\n"
  },
  {
    "path": "src/mackup/applications/doom-emacs.cfg",
    "content": "[application]\nname = Doom Emacs\n\n[configuration_files]\n.doom.d\n\n[xdg_configuration_files]\ndoom\n"
  },
  {
    "path": "src/mackup/applications/doublecmd.cfg",
    "content": "[application]\nname = Double Commander\n\n[xdg_configuration_files]\ndoublecmd/wfx.ini\ndoublecmd/doublecmd.ext\ndoublecmd/shortcuts.scf\ndoublecmd/doublecmd.xml\n"
  },
  {
    "path": "src/mackup/applications/doxie.cfg",
    "content": "[application]\nname = Doxie\n\n[configuration_files]\nLibrary/Preferences/com.getdoxie.doxie.plist\n"
  },
  {
    "path": "src/mackup/applications/dozer.cfg",
    "content": "[application]\nname = Dozer\n\n[configuration_files]\nLibrary/Preferences/com.mortennn.Dozer.plist\n"
  },
  {
    "path": "src/mackup/applications/draft.cfg",
    "content": "[application]\nname = Draft\n\n[configuration_files]\n.draft/config.toml\n"
  },
  {
    "path": "src/mackup/applications/droplr.cfg",
    "content": "[application]\nname = Droplr\n\n[configuration_files]\nLibrary/Preferences/com.droplr.droplr-mac.plist\n"
  },
  {
    "path": "src/mackup/applications/dropzone.cfg",
    "content": "[application]\nname = Dropzone 3\n\n[configuration_files]\nLibrary/Application Support/Dropzone 3\nLibrary/Preferences/com.aptonic.Dropzone3.plist\n"
  },
  {
    "path": "src/mackup/applications/drush.cfg",
    "content": "[application]\nname = Drush\n\n[configuration_files]\n.drush\n"
  },
  {
    "path": "src/mackup/applications/editorconfig.cfg",
    "content": "[application]\nname = EditorConfig\n\n[configuration_files]\n.editorconfig\n"
  },
  {
    "path": "src/mackup/applications/electrum.cfg",
    "content": "[application]\nname = Electrum\n\n[configuration_files]\n.electrum/config\n"
  },
  {
    "path": "src/mackup/applications/emacs.cfg",
    "content": "[application]\nname = Emacs\n\n[configuration_files]\n.emacs\n.emacs.d\n\n[xdg_configuration_files]\nemacs\n"
  },
  {
    "path": "src/mackup/applications/enjoyable.cfg",
    "content": "[application]\nname = Enjoyable\n\n[configuration_files]\nLibrary/Preferences/com.yukkurigames.Enjoyable.plist\n"
  },
  {
    "path": "src/mackup/applications/environmental-station-alpha.cfg",
    "content": "[application]\nname = Environmental Station Alpha\n\n[configuration_files]\nLibrary/Application Support/MMFApplications\n"
  },
  {
    "path": "src/mackup/applications/eqmac-2.cfg",
    "content": "[application]\nname = eqMac2\n\n[configuration_files]\nLibrary/Preferences/com.bitgapp.eqMac2.plist\n"
  },
  {
    "path": "src/mackup/applications/eslint.cfg",
    "content": "[application]\nname = ESLint\n\n[configuration_files]\n.eslintrc.js\n.eslintrc.yaml\n.eslintrc.yml\n.eslintrc.json\n.eslintrc\n.eslintignore\n"
  },
  {
    "path": "src/mackup/applications/espanso.cfg",
    "content": "[application]\nname = espanso\n\n[configuration_files]\nLibrary/Preferences/espanso\nLibrary/Application Support/espanso\n\n[xdg_configuration_files]\nespanso\n"
  },
  {
    "path": "src/mackup/applications/exercism.cfg",
    "content": "[application]\nname = Exercism\n\n[configuration_files]\n.exercism\n.exercism.json\n\n[xdg_configuration_files]\nexercism\n"
  },
  {
    "path": "src/mackup/applications/expandrive.cfg",
    "content": "[application]\nname = ExpanDrive\n\n[configuration_files]\nLibrary/Application Support/ExpanDrive\nPreferences/com.expandrive.ExpanDrive2.plist\nPreferences/com.expandrive.ExpanDrive3.plist\n"
  },
  {
    "path": "src/mackup/applications/factorio.cfg",
    "content": "[application]\nname = Factorio\n\n[configuration_files]\nLibrary/Application Support/factorio/config\nLibrary/Application Support/factorio/mods\nLibrary/Application Support/factorio/saves\nLibrary/Application Support/factorio/player-data.json\nLibrary/Application Support/factorio/blueprint-storage.dat\n"
  },
  {
    "path": "src/mackup/applications/factory-droid.cfg",
    "content": "[application]\nname = Factory Droid\n\n[configuration_files]\n.factory/commands\n.factory/droids\n.factory/AGENTS.md\n.factory/config.json\n.factory/settings.json\n.factory/auth.json\n.factory/mcp.json\n"
  },
  {
    "path": "src/mackup/applications/fantastical.cfg",
    "content": "[application]\nname = Fantastical\n\n[configuration_files]\n# Used by Fantastical 1 only.\nLibrary/Preferences/com.flexibits.fantastical.plist\n# Used by Fantastical 2 only.\nLibrary/Containers/com.flexibits.fantastical2.mac/Container.plist\n"
  },
  {
    "path": "src/mackup/applications/fasd.cfg",
    "content": "[application]\nname = fasd\n\n[configuration_files]\n.fasd\n.fasdrc\n"
  },
  {
    "path": "src/mackup/applications/fastlane.cfg",
    "content": "[application]\nname = fastlane\n\n[configuration_files]\n.fastlane/completions\n"
  },
  {
    "path": "src/mackup/applications/fastscripts.cfg",
    "content": "[application]\nname = FastScripts\n\n[configuration_files]\nLibrary/Preferences/com.red-sweater.fastscripts.plist\nLibrary/Application Support/FastScripts\n"
  },
  {
    "path": "src/mackup/applications/feeds.cfg",
    "content": "[application]\nname = Feeds\n\n[configuration_files]\nLibrary/Preferences/com.feedsapp.Feeds.plist\n"
  },
  {
    "path": "src/mackup/applications/filezilla.cfg",
    "content": "[application]\nname = FileZilla\n\n[configuration_files]\n.filezilla\n\n[xdg_configuration_files]\nfilezilla/filezilla.xml\nfilezilla/layout.xml\nfilezilla/sitemanager.xml\n"
  },
  {
    "path": "src/mackup/applications/finicky.cfg",
    "content": "[application]\nname = finicky\n\n[configuration_files]\n.finicky.js\n"
  },
  {
    "path": "src/mackup/applications/fish.cfg",
    "content": "[application]\nname = Fish\n\n[xdg_configuration_files]\nfish/config.fish\nfish/conf.d\nfish/fish_variables\nfish/functions\nfish/completions\n"
  },
  {
    "path": "src/mackup/applications/fisher.cfg",
    "content": "[application]\nname = Fisher\n\n[xdg_configuration_files]\nfish/fish_plugins\n"
  },
  {
    "path": "src/mackup/applications/flake8.cfg",
    "content": "[application]\nname = flake8\n\n[configuration_files]\n.flake8\n\n[xdg_configuration_files]\nflake8\n"
  },
  {
    "path": "src/mackup/applications/flameshot.cfg",
    "content": "[application]\nname = Flameshot\n\n[xdg_configuration_files]\nflameshot/flameshot.ini\n"
  },
  {
    "path": "src/mackup/applications/flexget.cfg",
    "content": "[application]\nname = FlexGet\n\n[configuration_files]\n.flexget/config.yml\n\n[xdg_configuration_files]\nflexget/config.yml\n"
  },
  {
    "path": "src/mackup/applications/flux.cfg",
    "content": "[application]\nname = Flux\n\n[configuration_files]\nLibrary/Preferences/org.herf.Flux.plist\n"
  },
  {
    "path": "src/mackup/applications/focus.cfg",
    "content": "[application]\nname = Focus\n\n[configuration_files]\nLibrary/Application Support/Focus/default.cfg\n"
  },
  {
    "path": "src/mackup/applications/fontconfig.cfg",
    "content": "[application]\nname = Fontconfig\n\n[xdg_configuration_files]\nfontconfig\n"
  },
  {
    "path": "src/mackup/applications/fontexplorer-x.cfg",
    "content": "[application]\nname = FontExplorer X\n\n[configuration_files]\nLibrary/Application Support/Linotype/FontExplorer X\nFontExplorer X\n"
  },
  {
    "path": "src/mackup/applications/forge.cfg",
    "content": "[application]\nname = Forge\n\n[configuration_files]\nLibrary/Application Support/Forge\n"
  },
  {
    "path": "src/mackup/applications/fork.cfg",
    "content": "[application]\nname = Fork\n\n[configuration_files]\nLibrary/Application Support/com.DanPristupov.Fork/custom-commands.json\nLibrary/Preferences/com.DanPristupov.Fork.plist\n"
  },
  {
    "path": "src/mackup/applications/forklift.cfg",
    "content": "[application]\nname = ForkLift\n\n[configuration_files]\nLibrary/Preferences/com.binarynights.ForkLift2.plist\nLibrary/Preferences/com.binarynights.ForkLift-3.plist\nLibrary/Application Support/ForkLift/Favorites/Favorites.json\n"
  },
  {
    "path": "src/mackup/applications/franz.cfg",
    "content": "[application]\nname = Franz\n\n[configuration_files]\nLibrary/Application Support/Franz/settings\n\n[xdg_configuration_files]\nFranz/settings\n"
  },
  {
    "path": "src/mackup/applications/gasmask.cfg",
    "content": "[application]\nname = Gas Mask\n\n[configuration_files]\nLibrary/Gas Mask\nLibrary/Preferences/ee.clockwise.gmask.plist\n"
  },
  {
    "path": "src/mackup/applications/gdb.cfg",
    "content": "[application]\nname = gdb\n\n[configuration_files]\n.gdbinit\n"
  },
  {
    "path": "src/mackup/applications/gearplayer.cfg",
    "content": "[application]\nname = Gear Player\n\n[configuration_files]\nLibrary/Preferences/com.treasurebox.gear.plist\nLibrary/Preferences/com.treasurebox.magickeys.plist\nLibrary/Application Support/Gear Player/paddata.padl\n"
  },
  {
    "path": "src/mackup/applications/geektool.cfg",
    "content": "[application]\nname = GeekTool\n\n[configuration_files]\nLibrary/Preferences/org.tynsoe.GeekTool.plist\nLibrary/Preferences/org.tynsoe.geeklet.file.plist\nLibrary/Preferences/org.tynsoe.geeklet.image.plist\nLibrary/Preferences/org.tynsoe.geeklet.shell.plist\nLibrary/Preferences/org.tynsoe.geeklet.web.plist\nLibrary/Preferences/org.tynsoe.geektool3.plist\n"
  },
  {
    "path": "src/mackup/applications/ghci.cfg",
    "content": "[application]\nname = ghci\n\n[configuration_files]\n.ghci\n"
  },
  {
    "path": "src/mackup/applications/ghidra.cfg",
    "content": "[application]\nname = Ghidra\n\n[configuration_files]\n.ghidra\n"
  },
  {
    "path": "src/mackup/applications/ghostty.cfg",
    "content": "[application]\nname = Ghostty\n\n[xdg_configuration_files]\nghostty/config\n"
  },
  {
    "path": "src/mackup/applications/ghostwriter.cfg",
    "content": "[application]\nname = Ghostwriter\n\n[xdg_configuration_files]\nghostwriter\n"
  },
  {
    "path": "src/mackup/applications/gimp.cfg",
    "content": "[application]\nname = gimp\n\n[configuration_files]\n.gimp/colorrc\n.gimp/contextrc\n.gimp/controllerrc\n.gimp/devicerc\n.gimp/dockrc\n.gimp/gimprc\n.gimp/gtkrc\n.gimp/menurc\n.gimp/parasiterc\n.gimp/pluginrc\n.gimp/profilerc\n.gimp/sessionrc\n.gimp/templaterc\n.gimp/themerc\n.gimp/toolrc\n.gimp/unitrc\nLibrary/Application Support/GIMP/2.8/colorrc\nLibrary/Application Support/GIMP/2.8/contextrc\nLibrary/Application Support/GIMP/2.8/controllerrc\nLibrary/Application Support/GIMP/2.8/devicerc\nLibrary/Application Support/GIMP/2.8/dockrc\nLibrary/Application Support/GIMP/2.8/gimprc\nLibrary/Application Support/GIMP/2.8/gtkrc\nLibrary/Application Support/GIMP/2.8/menurc\nLibrary/Application Support/GIMP/2.8/parasiterc\nLibrary/Application Support/GIMP/2.8/pluginrc\nLibrary/Application Support/GIMP/2.8/profilerc\nLibrary/Application Support/GIMP/2.8/sessionrc\nLibrary/Application Support/GIMP/2.8/templaterc\nLibrary/Application Support/GIMP/2.8/themerc\nLibrary/Application Support/GIMP/2.8/toolrc\nLibrary/Application Support/GIMP/2.8/unitrc\nLibrary/Application Support/GIMP/2.10/colorrc\nLibrary/Application Support/GIMP/2.10/contextrc\nLibrary/Application Support/GIMP/2.10/controllerrc\nLibrary/Application Support/GIMP/2.10/devicerc\nLibrary/Application Support/GIMP/2.10/dockrc\nLibrary/Application Support/GIMP/2.10/gimprc\nLibrary/Application Support/GIMP/2.10/gtkrc\nLibrary/Application Support/GIMP/2.10/menurc\nLibrary/Application Support/GIMP/2.10/parasiterc\nLibrary/Application Support/GIMP/2.10/pluginrc\nLibrary/Application Support/GIMP/2.10/profilerc\nLibrary/Application Support/GIMP/2.10/sessionrc\nLibrary/Application Support/GIMP/2.10/templaterc\nLibrary/Application Support/GIMP/2.10/themerc\nLibrary/Application Support/GIMP/2.10/toolrc\nLibrary/Application Support/GIMP/2.10/unitrc\n"
  },
  {
    "path": "src/mackup/applications/git-hooks.cfg",
    "content": "[application]\nname = Git Hooks\n\n[configuration_files]\n.git_hooks\n"
  },
  {
    "path": "src/mackup/applications/git.cfg",
    "content": "[application]\nname = Git\n\n[configuration_files]\n.gitconfig\n\n[xdg_configuration_files]\ngit/config\ngit/ignore\ngit/attributes\n"
  },
  {
    "path": "src/mackup/applications/gitbox.cfg",
    "content": "[application]\nname = Gitbox\n\n[configuration_files]\nLibrary/Preferences/com.oleganza.gitbox.plist\n"
  },
  {
    "path": "src/mackup/applications/gitfox.cfg",
    "content": "[application]\nname = GitFox\n\n[configuration_files]\nLibrary/Preferences/com.bytieful.Gitfox.plist\nLibrary/Preferences/com.bytieful.Gitfox-retail.plist\nLibrary/Preferences/com.bytieful.Gitfox-setapp.plist\n"
  },
  {
    "path": "src/mackup/applications/github-cli.cfg",
    "content": "[application]\nname = GitHub CLI\n\n[xdg_configuration_files]\ngh/config.yml\n"
  },
  {
    "path": "src/mackup/applications/gitkraken.cfg",
    "content": "[application]\nname = GitKraken\n\n[configuration_files]\n.gitkraken\nLibrary/Preferences/com.axosoft.gitkraken.plist\n"
  },
  {
    "path": "src/mackup/applications/gitup.cfg",
    "content": "[application]\nname = GitUp\n\n[configuration_files]\nLibrary/Preferences/co.gitup.mac.plist\n"
  },
  {
    "path": "src/mackup/applications/gmail-notifr.cfg",
    "content": "[application]\nname = Gmail Notifr\n\n[configuration_files]\nLibrary/Preferences/com.ashchan.GmailNotifr.plist\n"
  },
  {
    "path": "src/mackup/applications/gmailctl.cfg",
    "content": "[application]\nname = gmailctl\n\n[configuration_files]\n.gmailctl/config.jsonnet\n.gmailctl/credentials.json\n.gmailctl/gmailctl.libsonnet\n"
  },
  {
    "path": "src/mackup/applications/gmvault.cfg",
    "content": "[application]\nname = GMVault\n\n[configuration_files]\n.gmvault/gmvault_defaults.conf\n"
  },
  {
    "path": "src/mackup/applications/gnu-stow.cfg",
    "content": "[application]\nname = GNU Stow\n\n[configuration_files]\n.stowrc\n.stow-global-ignore\n"
  },
  {
    "path": "src/mackup/applications/gnupg.cfg",
    "content": "[application]\nname = GnuPG\n\n[configuration_files]\n.gnupg/gpg-agent.conf\n.gnupg/gpg.conf\n.gnupg/trustdb.gpg\n"
  },
  {
    "path": "src/mackup/applications/go2shell.cfg",
    "content": "[application]\nname = Go2Shell\n\n[configuration_files]\nLibrary/Preferences/com.zipzapmac.Go2Shell.plist\n"
  },
  {
    "path": "src/mackup/applications/goku.cfg",
    "content": "[application]\nname = Goku\n\n[xdg_configuration_files]\nkarabiner.edn\n"
  },
  {
    "path": "src/mackup/applications/goland.cfg",
    "content": "[application]\nname = GoLand\n\n[configuration_files]\nLibrary/Application Support/Gogland1.0\nLibrary/Application Support/GoLand2017.3\nLibrary/Application Support/GoLand2018.1\nLibrary/Application Support/GoLand2018.2\nLibrary/Application Support/GoLand2018.3\nLibrary/Application Support/GoLand2019.2\nLibrary/Preferences/com.jetbrains.gogland-EAP.plist\nLibrary/Preferences/Gogland1.0\nLibrary/Preferences/GoLand2017.3\nLibrary/Preferences/GoLand2018.1\nLibrary/Preferences/GoLand2018.2\nLibrary/Preferences/GoLand2018.3\nLibrary/Preferences/GoLand2019.2\nLibrary/Application Support/JetBrains/GoLand2023.1\nLibrary/Application Support/JetBrains/GoLand2023.2\n"
  },
  {
    "path": "src/mackup/applications/goldendict.cfg",
    "content": "[application]\nname = Goldendict\n\n[configuration_files]\n.goldendict/config\n"
  },
  {
    "path": "src/mackup/applications/goodsync.cfg",
    "content": "[application]\nname = GoodSync\n\n[configuration_files]\n.goodsync\n"
  },
  {
    "path": "src/mackup/applications/goshare.cfg",
    "content": "[application]\nname = GoShare\n\n[xdg_configuration_files]\ndictget/goshare\n"
  },
  {
    "path": "src/mackup/applications/gradle.cfg",
    "content": "[application]\nname = Gradle\n\n[configuration_files]\n.gradle/gradle.properties\n.gradle/init.gradle\n.gradle/init.d\n"
  },
  {
    "path": "src/mackup/applications/grandtotal-3.cfg",
    "content": "[application]\nname = GrandTotal 3\n\n[configuration_files]\nLibrary/Application Support/GrandTotal/AddressBook.plist\nLibrary/Preferences/com.mediaatelier.GrandTotal3.plist\n"
  },
  {
    "path": "src/mackup/applications/grsync.cfg",
    "content": "[application]\nname = grsync\n\n[configuration_files]\n.grsync/grsync.ini\n"
  },
  {
    "path": "src/mackup/applications/gstm.cfg",
    "content": "[application]\nname = Gnome SSH Tunnel Manager\n\n[configuration_files]\n.gSTM\n"
  },
  {
    "path": "src/mackup/applications/hammerspoon.cfg",
    "content": "[application]\nname = Hammerspoon\n\n[configuration_files]\n.hammerspoon\n"
  },
  {
    "path": "src/mackup/applications/handbrake.cfg",
    "content": "[application]\nname = HandBrake\n\n[configuration_files]\nLibrary/Containers/fr.handbrake.HandBrake/Data/Library/Application Support/HandBrake/UserPresets.json\n"
  },
  {
    "path": "src/mackup/applications/hands-off.cfg",
    "content": "[application]\nname = Hands Off!\n\n[configuration_files]\nLibrary/Preferences/com.metakine.handsoff.plist\n"
  },
  {
    "path": "src/mackup/applications/hazel.cfg",
    "content": "[application]\nname = Hazel\n\n[configuration_files]\nLibrary/Application Support/Hazel\nLibrary/Preferences/com.noodlesoft.Hazel.plist\n"
  },
  {
    "path": "src/mackup/applications/hero-lab.cfg",
    "content": "[application]\nname = Hero Lab\n\n[configuration_files]\nLibrary/Application Support/Hero Lab/customoutput\nLibrary/Application Support/Hero Lab/data\nLibrary/Application Support/Hero Lab/docs\n"
  },
  {
    "path": "src/mackup/applications/heroku.cfg",
    "content": "[application]\nname = Heroku\n\n[configuration_files]\n.heroku/accounts\n.heroku/plugins\n"
  },
  {
    "path": "src/mackup/applications/hexchat.cfg",
    "content": "[application]\nname = HexChat\n\n[xdg_configuration_files]\nhexchat\n"
  },
  {
    "path": "src/mackup/applications/hexels.cfg",
    "content": "[application]\nname = Hexels\n\n[configuration_files]\nLibrary/Preferences/com.hex-ray.hexels.plist\n"
  },
  {
    "path": "src/mackup/applications/hocus-focus.cfg",
    "content": "[application]\nname = Hocus Focus\n\n[configuration_files]\nLibrary/Preferences/com.uglyapps.HocusFocus.plist\nLibrary/Application Support/com.uglyapps.HocusFocus/HocusFocus.db\nLibrary/Application Support/com.uglyapps.HocusFocus/HocusFocus.db-shm\nLibrary/Application Support/com.uglyapps.HocusFocus/HocusFocus.db-wal\n"
  },
  {
    "path": "src/mackup/applications/homebrew.cfg",
    "content": "[application]\nname = Homebrew\n\n[configuration_files]\n.Brewfile\nBrewfile\n"
  },
  {
    "path": "src/mackup/applications/homebridge.cfg",
    "content": "[application]\nname = Homebridge\n\n[configuration_files]\n.homebridge\n"
  },
  {
    "path": "src/mackup/applications/houdini.cfg",
    "content": "[application]\nname = Houdini\n\n[configuration_files]\nLibrary/Preferences/com.uglyapps.Houdini.plist\nLibrary/Application Support/Houdini\n"
  },
  {
    "path": "src/mackup/applications/hstr.cfg",
    "content": "[application]\nname = hstr\n\n[configuration_files]\n.hstr_favorites\n.hstr_blacklist\n"
  },
  {
    "path": "src/mackup/applications/htop.cfg",
    "content": "[application]\nname = Htop\n\n[configuration_files]\n.htoprc\n\n[xdg_configuration_files]\nhtop/htoprc\n"
  },
  {
    "path": "src/mackup/applications/httpie.cfg",
    "content": "[application]\nname = HTTPie\n\n[configuration_files]\n.httpie/config.json\n"
  },
  {
    "path": "src/mackup/applications/hub.cfg",
    "content": "[application]\nname = hub\n\n[xdg_configuration_files]\nhub\n"
  },
  {
    "path": "src/mackup/applications/hyper.cfg",
    "content": "[application]\nname = Hyper.app\n\n[configuration_files]\n.hyper.js\n"
  },
  {
    "path": "src/mackup/applications/hyperdock.cfg",
    "content": "[application]\nname = HyperDock\n\n[configuration_files]\nLibrary/Application Support/HyperDock\nLibrary/Preferences/de.bahoom.HyperDock.plist\nLibrary/Preferences/de.bahoom.HyperDock.prefpane.plist\n"
  },
  {
    "path": "src/mackup/applications/hyperswitch.cfg",
    "content": "[application]\nname = HyperSwitch\n\n[configuration_files]\nLibrary/Preferences/com.bahoom.HyperSwitch.plist\n"
  },
  {
    "path": "src/mackup/applications/i2cssh.cfg",
    "content": "[application]\nname = i2cssh\n\n[configuration_files]\n.i2csshrc\n"
  },
  {
    "path": "src/mackup/applications/i3.cfg",
    "content": "[application]\nname = i3\n\n[configuration_files]\n.i3/config\n.i3status.conf\n\n[xdg_configuration_files]\ni3/config\n"
  },
  {
    "path": "src/mackup/applications/idapro.cfg",
    "content": "[application]\nname = IDA Pro\n\n[configuration_files]\n.idapro\n"
  },
  {
    "path": "src/mackup/applications/ideavim.cfg",
    "content": "[application]\nname=IdeaVim\n\n[configuration_files]\n.ideavimrc\n"
  },
  {
    "path": "src/mackup/applications/iina.cfg",
    "content": "[application]\nname = IINA\n\n[configuration_files]\nLibrary/Application Support/com.colliderli.iina/input_conf\nLibrary/Preferences/com.colliderli.iina.plist\n"
  },
  {
    "path": "src/mackup/applications/illustrator.cfg",
    "content": "[application]\nname = Adobe Illustrator\n\n[configuration_files]\nLibrary/Application Support/Adobe/Adobe Illustrator 17\nLibrary/Application Support/Adobe/Adobe Illustrator 18\nLibrary/Application Support/Adobe/Adobe Illustrator 19\nLibrary/Application Support/Adobe/Adobe Illustrator 20\nLibrary/Application Support/Adobe/Adobe Illustrator 23/en_US/Adobe SVG Filters.svg\nLibrary/Application Support/Adobe/Adobe Illustrator 23/en_US/Swatches\nLibrary/Application Support/Adobe/Adobe Illustrator 24/en_US/Adobe SVG Filters.svg\nLibrary/Application Support/Adobe/Adobe Illustrator 24/en_US/Swatches\nLibrary/Application Support/Adobe/OOBE\nLibrary/Preferences/Adobe Illustrator 17 Settings\nLibrary/Preferences/Adobe Illustrator 18 Settings\nLibrary/Preferences/Adobe Illustrator 19 Settings\nLibrary/Preferences/Adobe Illustrator 20 Settings\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Adobe Illustrator Prefs\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Last Used Artboard Export Settings\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Last Used Asset Export Settings\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Modified Workspaces\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Perspective grid Presets\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Print Presets\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Tools/Tools Panel Presets\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Transparency flattener presets\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/VariableWidthProfiles\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Vectorizing Presets\nLibrary/Preferences/Adobe Illustrator 23 Settings/en_US/Workspaces\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Adobe Illustrator Prefs\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Last Used Artboard Export Settings\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Last Used Asset Export Settings\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Modified Workspaces\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Perspective grid Presets\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Print Presets\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Tools/Tools Panel Presets\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Transparency flattener presets\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/VariableWidthProfiles\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Vectorizing Presets\nLibrary/Preferences/Adobe Illustrator 24 Settings/en_US/Workspaces\nLibrary/Preferences/com.adobe.illustrator.plist\n"
  },
  {
    "path": "src/mackup/applications/inkscape.cfg",
    "content": "[application]\nname = inkscape\n\n[xdg_configuration_files]\ninkscape/preferences.xml\n"
  },
  {
    "path": "src/mackup/applications/insomnia.cfg",
    "content": "[application]\nname = Insomnia\n\n[configuration_files]\nLibrary/Application Support/Insomnia\n\n[xdg_configuration_files]\nInsomnia\n"
  },
  {
    "path": "src/mackup/applications/intellijidea.cfg",
    "content": "[application]\nname = IntelliJ IDEA\n\n[configuration_files]\n.IntelliJIdea12/config\n.IntelliJIdea13/config\n.IntelliJIdea14/config\n.IntelliJIdea15/config\nIdeaIC2018.2/config\nIdeaIC2018.3/config\nIdeaIC2019.1/config\nIdeaIC2019.2/config\nIdeaIC2019.3/config\nIntelliJIdea2016.1/config\nIntelliJIdea2016.2/config\nIntelliJIdea2016.3/config\nIntelliJIdea2017.1/config\nIntelliJIdea2017.2/config\nIntelliJIdea2017.3/config\nIntelliJIdea2018.1/config\nIntelliJIdea2018.2/config\nIntelliJIdea2018.3/config\nIntelliJIdea2019.1/config\nIntelliJIdea2019.2/config\nIntelliJIdea2019.3/config\nLibrary/Application Support/IdeaIC2016.1\nLibrary/Application Support/IdeaIC2017.1\nLibrary/Application Support/IdeaIC2017.2\nLibrary/Application Support/IdeaIC2017.3\nLibrary/Application Support/IdeaIC2018.2\nLibrary/Application Support/IdeaIC2018.3\nLibrary/Application Support/IdeaIC2019.1\nLibrary/Application Support/IntelliJIdea12\nLibrary/Application Support/IntelliJIdea13\nLibrary/Application Support/IntelliJIdea14\nLibrary/Application Support/IntelliJIdea15\nLibrary/Application Support/IntelliJIdea2016.1\nLibrary/Application Support/IntelliJIdea2016.2\nLibrary/Application Support/IntelliJIdea2016.3\nLibrary/Application Support/IntelliJIdea2017.1\nLibrary/Application Support/IntelliJIdea2017.2\nLibrary/Application Support/IntelliJIdea2017.3\nLibrary/Application Support/IntelliJIdea2018.1\nLibrary/Application Support/IntelliJIdea2018.2\nLibrary/Application Support/IntelliJIdea2018.3\nLibrary/Application Support/IntelliJIdea2019.1\nLibrary/Application Support/IntelliJIdea2019.2\nLibrary/Application Support/IntelliJIdea2019.3\nLibrary/Application Support/JetBrains/IntelliJIdea2020.1\nLibrary/Application Support/JetBrains/IntelliJIdea2020.2\nLibrary/Application Support/JetBrains/IntelliJIdea2020.3\nLibrary/Application Support/JetBrains/IntelliJIdea2021.1\nLibrary/Application Support/JetBrains/IntelliJIdea2022.1\nLibrary/Application Support/JetBrains/IntelliJIdea2022.2\nLibrary/Application Support/JetBrains/IntelliJIdea2023.1\nLibrary/Application Support/JetBrains/IntelliJIdea2023.2\nLibrary/Preferences/IdeaIC2016.1\nLibrary/Preferences/IdeaIC2016.2\nLibrary/Preferences/IdeaIC2016.3\nLibrary/Preferences/IdeaIC2017.1\nLibrary/Preferences/IdeaIC2017.2\nLibrary/Preferences/IdeaIC2017.3\nLibrary/Preferences/IdeaIC2018.2\nLibrary/Preferences/IdeaIC2018.3\nLibrary/Preferences/IdeaIC2019.1\nLibrary/Preferences/IntelliJIdea12\nLibrary/Preferences/IntelliJIdea13\nLibrary/Preferences/IntelliJIdea14\nLibrary/Preferences/IntelliJIdea15\nLibrary/Preferences/IntelliJIdea2016.1\nLibrary/Preferences/IntelliJIdea2016.2\nLibrary/Preferences/IntelliJIdea2016.3\nLibrary/Preferences/IntelliJIdea2017.1\nLibrary/Preferences/IntelliJIdea2017.2\nLibrary/Preferences/IntelliJIdea2017.3\nLibrary/Preferences/IntelliJIdea2018.1\nLibrary/Preferences/IntelliJIdea2018.2\nLibrary/Preferences/IntelliJIdea2018.3\nLibrary/Preferences/IntelliJIdea2019.1\nLibrary/Preferences/IntelliJIdea2019.2\nLibrary/Preferences/IntelliJIdea2019.3\n"
  },
  {
    "path": "src/mackup/applications/ipython.cfg",
    "content": "[application]\nname = IPython\n\n[configuration_files]\n.ipython\n"
  },
  {
    "path": "src/mackup/applications/irssi.cfg",
    "content": "[application]\nname = Irssi\n\n[configuration_files]\n.irssi\n"
  },
  {
    "path": "src/mackup/applications/istat-menus.cfg",
    "content": "[application]\nname = iStat Menus\n\n[configuration_files]\nLibrary/Preferences/com.bjango.istatmenus.menubar.7.plist\nLibrary/Preferences/com.bjango.istatmenus.plist\nLibrary/Preferences/com.bjango.istatmenus.status.plist\nLibrary/Preferences/com.bjango.istatmenus5.extras.plist\nLibrary/Preferences/com.bjango.istatmenus6.extras.plist\n"
  },
  {
    "path": "src/mackup/applications/iterm2.cfg",
    "content": "[application]\nname = iTerm2\n\n[configuration_files]\nLibrary/Preferences/com.googlecode.iterm2.plist\n\n[xdg_configuration_files]\niterm2/AppSupport/DynamicProfiles\n"
  },
  {
    "path": "src/mackup/applications/itermocil.cfg",
    "content": "[application]\nname = iTermocil\n\n[configuration_files]\n.itermocil\n"
  },
  {
    "path": "src/mackup/applications/itsycal.cfg",
    "content": "[application]\nname = Itsycal\n\n[configuration_files]\nLibrary/Preferences/com.mowglii.ItsycalApp.plist\n"
  },
  {
    "path": "src/mackup/applications/itunes-scripts.cfg",
    "content": "[application]\nname = iTunes Scripts\n\n[configuration_files]\nLibrary/iTunes/Scripts\n"
  },
  {
    "path": "src/mackup/applications/jankyborders.cfg",
    "content": "[application]\nname = Janky Borders\n\n[xdg_configuration_files]\nborders/bordersrc\n"
  },
  {
    "path": "src/mackup/applications/janus.cfg",
    "content": "[application]\nname = Janus\n\n[configuration_files]\n.janus\n.vimrc.before\n.vimrc.after\n"
  },
  {
    "path": "src/mackup/applications/jitouch.cfg",
    "content": "[application]\nname = Jitouch\n\n[configuration_files]\nLibrary/Preferences/com.jitouch.Jitouch.plist\n"
  },
  {
    "path": "src/mackup/applications/joplin.cfg",
    "content": "[application]\nname = Joplin\n\n[xdg_configuration_files]\njoplin-desktop/userchrome.css\njoplin-desktop/userstyle.css\n"
  },
  {
    "path": "src/mackup/applications/jrnl.cfg",
    "content": "[application]\nname = jrnl\n\n[configuration_files]\n.jrnl_config\n\n[xdg_configuration_files]\njrnl/jrnl.yaml\n"
  },
  {
    "path": "src/mackup/applications/jsbeautifier.cfg",
    "content": "[application]\nname = JS Beautifier\n\n[configuration_files]\n.jsbeautifyrc\n"
  },
  {
    "path": "src/mackup/applications/jshint.cfg",
    "content": "[application]\nname = JSHint\n\n[configuration_files]\n.jshintrc\n"
  },
  {
    "path": "src/mackup/applications/julia.cfg",
    "content": "[application]\nname = Julia\n\n[configuration_files]\n.juliarc.jl\n.julia/config/startup.jl\n"
  },
  {
    "path": "src/mackup/applications/jumpcut.cfg",
    "content": "[application]\nname = Jumpcut\n\n[configuration_files]\nLibrary/Application Support/Jumpcut\nLibrary/Preferences/net.sf.Jumpcut.plist\n"
  },
  {
    "path": "src/mackup/applications/jupyter.cfg",
    "content": "[application]\nname = Jupyter\n\n[configuration_files]\n.jupyter\n"
  },
  {
    "path": "src/mackup/applications/k9s.cfg",
    "content": "[application]\nname = k9s\n\n[xdg_configuration_files]\nk9s/config.yml\nk9s/skin.yml\n"
  },
  {
    "path": "src/mackup/applications/kaggle.cfg",
    "content": "[application]\nname = Kaggle\n\n[configuration_files]\n.kaggle/kaggle.json\n"
  },
  {
    "path": "src/mackup/applications/kaleidoscope.cfg",
    "content": "[application]\nname = Kaleidoscope\n\n[configuration_files]\nLibrary/Preferences/com.blackpixel.kaleidoscope.plist\nLibrary/Application Support/Kaleidoscope\n"
  },
  {
    "path": "src/mackup/applications/karabiner-elements.cfg",
    "content": "[application]\nname = Karabiner Elements\n\n[xdg_configuration_files]\nkarabiner\n"
  },
  {
    "path": "src/mackup/applications/karabiner.cfg",
    "content": "[application]\nname = Karabiner\n \n[configuration_files]\nLibrary/Preferences/org.pqrs.Karabiner.plist\nLibrary/Preferences/org.pqrs.Karabiner-AXNotifier.plist\nLibrary/Preferences/org.pqrs.Karabiner.multitouchextension.plist\nLibrary/Application Support/Karabiner\n"
  },
  {
    "path": "src/mackup/applications/kdenlive.cfg",
    "content": "[application]\nname = Kdenlive\n\n[configuration_files]\nkdenlive\n\n[xdg_configuration_files]\nkdenliverc\n"
  },
  {
    "path": "src/mackup/applications/keepassx.cfg",
    "content": "[application]\nname = KeePassX\n\n[configuration_files]\n.keepassx\nLibrary/Preferences/org.keepassx.keepassx.plist\nLibrary/Application Support/keepassx/keepassx2.ini\n\n[xdg_configuration_files]\nkeepassx/keepassx2.ini\n"
  },
  {
    "path": "src/mackup/applications/keepassxc.cfg",
    "content": "[application]\nname = KeePassXC\n\n[configuration_files]\nLibrary/Preferences/org.keepassxc.keepassxc.plist\nLibrary/Application Support/keepassxc/keepassxc.ini\n"
  },
  {
    "path": "src/mackup/applications/keepingyouawake.cfg",
    "content": "[application]\nname = KeepingYouAwake\n\n[configuration_files]\nLibrary/Preferences/info.marcel-dierkes.KeepingYouAwake.plist\n"
  },
  {
    "path": "src/mackup/applications/keka.cfg",
    "content": "[application]\nname = Keka\n\n[configuration_files]\nLibrary/Preferences/com.aone.keka.plist\n"
  },
  {
    "path": "src/mackup/applications/keybase.cfg",
    "content": "[application]\nname = Keybase\n\n[configuration_files]\n.keybase/config.json\n"
  },
  {
    "path": "src/mackup/applications/keyboard-maestro.cfg",
    "content": "[application]\nname = Keyboard Maestro\n\n[configuration_files]\nLibrary/Application Support/Keyboard Maestro\n"
  },
  {
    "path": "src/mackup/applications/keymo.cfg",
    "content": "[application]\nname = Keymo\n\n[configuration_files]\nLibrary/Preferences/com.manytricks.Keymo.plist\n"
  },
  {
    "path": "src/mackup/applications/keyremap4macbook.cfg",
    "content": "[application]\nname = KeyRemap4MacBook\n\n[configuration_files]\nLibrary/Preferences/org.pqrs.KeyRemap4MacBook.plist\nLibrary/Preferences/org.pqrs.KeyRemap4MacBook.multitouchextension.plist\nLibrary/Application Support/KeyRemap4MacBook/private.xml\n"
  },
  {
    "path": "src/mackup/applications/khd.cfg",
    "content": "[application]\nname = khd\n\n[configuration_files]\n.khdrc\n"
  },
  {
    "path": "src/mackup/applications/kiro.cfg",
    "content": "[application]\nname = Kiro\n\n[configuration_files]\nLibrary/Application Support/Kiro/User/snippets\nLibrary/Application Support/Kiro/User/keybindings.json\nLibrary/Application Support/Kiro/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/kitty.cfg",
    "content": "[application]\nname = kitty\n\n[xdg_configuration_files]\nkitty/kitty.conf\n"
  },
  {
    "path": "src/mackup/applications/krew.cfg",
    "content": "[application]\nname = Krew\n\n[configuration_files]\n.krew\n"
  },
  {
    "path": "src/mackup/applications/kubectl.cfg",
    "content": "[application]\nname = kubectl\n\n[configuration_files]\n.kube/config\n"
  },
  {
    "path": "src/mackup/applications/kwm.cfg",
    "content": "[application]\nname = Kwm\n\n[configuration_files]\n.kwm\n"
  },
  {
    "path": "src/mackup/applications/latexit.cfg",
    "content": "[application]\nname = LaTeXiT\n\n[configuration_files]\nLibrary/Preferences/fr.chachatelier.pierre.LaTeXiT.plist\n"
  },
  {
    "path": "src/mackup/applications/launchbar.cfg",
    "content": "[application]\nname = LaunchBar\n\n[configuration_files]\nLibrary/Preferences/at.obdev.LaunchBar.plist\nLibrary/Application Support/LaunchBar/Actions\nLibrary/Application Support/LaunchBar/Snippets\nLibrary/Application Support/LaunchBar/Configuration.plist\nLibrary/Application Support/LaunchBar/CustomShortcuts.plist\n"
  },
  {
    "path": "src/mackup/applications/lazydocker.cfg",
    "content": "[application]\nname = lazydocker\n\n[configuration_files]\nLibrary/Application Support/jesseduffield/lazydocker/config.yml\n"
  },
  {
    "path": "src/mackup/applications/lazygit.cfg",
    "content": "[application]\nname = lazygit\n\n[configuration_files]\nLibrary/Application Support/jesseduffield/lazygit/config.yml\nLibrary/Application Support/lazygit/config.yml\n\n[xdg_configuration_files]\nlazygit/config.yml\n"
  },
  {
    "path": "src/mackup/applications/ledger.cfg",
    "content": "[application]\nname = ledger\n\n[configuration_files]\n.ledgerrc\n"
  },
  {
    "path": "src/mackup/applications/leiningen.cfg",
    "content": "[application]\nname = Leiningen\n\n[configuration_files]\n.lein\n"
  },
  {
    "path": "src/mackup/applications/lf.cfg",
    "content": "[application]\nname = lf\n\n[xdg_configuration_files]\nlf/lfrc\n"
  },
  {
    "path": "src/mackup/applications/libreoffice.cfg",
    "content": "[application]\nname = LibreOffice\n\n[xdg_configuration_files]\nlibreoffice\n"
  },
  {
    "path": "src/mackup/applications/liftoff.cfg",
    "content": "[application]\nname = liftoff\n\n[configuration_files]\n.liftoffrc\n"
  },
  {
    "path": "src/mackup/applications/light-table.cfg",
    "content": "[application]\nname = Light Table\n\n[configuration_files]\nLibrary/Application Support/LightTable/plugins\nLibrary/Application Support/LightTable/settings\nLibrary/Application Support/LightTable/User\nLibrary/Preferences/com.kodowa.LightTable.plist\n"
  },
  {
    "path": "src/mackup/applications/lightpaper.cfg",
    "content": "[application]\nname = LightPaper\n\n[configuration_files]\n.lightpaper\nLibrary/Application Support/LightPaper/customCSS.css\nLibrary/Preferences/com.42squares.LightPaper.plist\n"
  },
  {
    "path": "src/mackup/applications/lightroom-classic.cfg",
    "content": "[application]\nname = Adobe Lightroom Classic\n\n[configuration_files]\nLibrary/Preferences/com.adobe.LightroomClassicCC7.plist\n"
  },
  {
    "path": "src/mackup/applications/lightroom.cfg",
    "content": "[application]\nname = Adobe Lightroom\n\n[configuration_files]\nLibrary/Application Support/Adobe/Lightroom\nLibrary/Preferences/com.adobe.Lightroom4.plist\nLibrary/Preferences/com.adobe.Lightroom5.plist\nLibrary/Preferences/com.adobe.Lightroom6.plist\n"
  },
  {
    "path": "src/mackup/applications/limechat.cfg",
    "content": "[application]\nname = LimeChat\n\n[configuration_files]\nLibrary/Preferences/net.limechat.LimeChat-AppStore.plist\n"
  },
  {
    "path": "src/mackup/applications/liquidprompt.cfg",
    "content": "[application]\nname = Liquid Prompt\n\n[configuration_files]\n.liquidpromptrc\n\n[xdg_configuration_files]\nliquidpromptrc\n"
  },
  {
    "path": "src/mackup/applications/littlesnitch.cfg",
    "content": "[application]\nname = LittleSnitch\n\n[configuration_files]\nLibrary/Preferences/at.obdev.LittleSnitchNetworkMonitor.plist\nLibrary/Preferences/at.obdev.littlesnitch.networkmonitor.plist\nLibrary/Application Support/Little Snitch/rules.usr.xpl\nLibrary/Application Support/Little Snitch/configuration.xpl\nLibrary/Application Support/Little Snitch/configuration.user.xpl\nLibrary/Application Support/Little Snitch/configuration4.user.xpl\n"
  },
  {
    "path": "src/mackup/applications/livestreamer.cfg",
    "content": "[application]\nname = Livestreamer\n\n[configuration_files]\n.livestreamerrc\n\n[xdg_configuration_files]\nlivestreamer/config\nlivestreamer/plugins\n"
  },
  {
    "path": "src/mackup/applications/logitech-options.cfg",
    "content": "[application]\nname = Logitech Options\n\n[configuration_files]\nLibrary/Preferences/com.logitech.manager.setting.ffff.plist\n"
  },
  {
    "path": "src/mackup/applications/logseq.cfg",
    "content": "[application]\nname = Logseq\n\n[configuration_files]\n.logseq\n"
  },
  {
    "path": "src/mackup/applications/lollypop.cfg",
    "content": "[application]\nname = Lollypop\n\n[configuration_files]\n.local/share/lollypop\n"
  },
  {
    "path": "src/mackup/applications/loopback.cfg",
    "content": "[application]\nname = Loopback\n\n[configuration_files]\nLibrary/Preferences/com.rogueamoeba.Loopback.plist\nLibrary/Application Support/Loopback/Devices.plist\n"
  },
  {
    "path": "src/mackup/applications/luftrausers.cfg",
    "content": "[application]\nname = Luftrausers\n\n[configuration_files]\n.LUFTRAUSERS\n"
  },
  {
    "path": "src/mackup/applications/lunarvim.cfg",
    "content": "[application]\nname = LunarVim\n\n[xdg_configuration_files]\nlvim/config.lua\n"
  },
  {
    "path": "src/mackup/applications/macdive2.cfg",
    "content": "[application]\nname = MacDive\n\n[configuration_files]\nLibrary/Application Support/MacDive\nLibrary/Preferences/com.mintsoftware.MacDive2.plist\n"
  },
  {
    "path": "src/mackup/applications/macdown.cfg",
    "content": "[application]\nname = MacDown\n\n[configuration_files]\nLibrary/Preferences/com.uranusjr.macdown.plist\nLibrary/Application Support/MacDown/Styles\nLibrary/Application Support/MacDown/Themes\n"
  },
  {
    "path": "src/mackup/applications/mackup.cfg",
    "content": "[application]\nname = Mackup\n\n[configuration_files]\n.mackup.cfg\n.mackup\n"
  },
  {
    "path": "src/mackup/applications/macosx.cfg",
    "content": "[application]\nname = MacOSX\n\n[configuration_files]\n.MacOSX\nLibrary/KeyBindings/DefaultKeyBinding.dict\nLibrary/PDF Services\nLibrary/Preferences/com.apple.symbolichotkeys.plist\nLibrary/Preferences/.GlobalPreferences.plist\nLibrary/Scripts\nLibrary/Speech/Speakable Items\nLibrary/Workflows\n"
  },
  {
    "path": "src/mackup/applications/macvim.cfg",
    "content": "[application]\nname = MacVim\n\n[configuration_files]\nLibrary/Preferences/org.vim.MacVim.LSSharedFileList.plist\nLibrary/Preferences/org.vim.MacVim.plist\n"
  },
  {
    "path": "src/mackup/applications/magic-launch.cfg",
    "content": "[application]\nname = Magic Launch\n\n[configuration_files]\nLibrary/Preferences/com.metakine.magic-launch.agent.plist\n"
  },
  {
    "path": "src/mackup/applications/magicprefs.cfg",
    "content": "[application]\nname = MagicPrefs\n\n[configuration_files]\nLibrary/Preferences/com.vladalexa.MagicPrefs.MagicPrefsPlugins.plist\nLibrary/Preferences/com.vladalexa.MagicPrefs.plist\n"
  },
  {
    "path": "src/mackup/applications/magnet.cfg",
    "content": "[application]\nname = Magnet\n\n[configuration_files]\nLibrary/Preferences/com.crowdcafe.windowmagnet.plist\n"
  },
  {
    "path": "src/mackup/applications/maid.cfg",
    "content": "[application]\nname = Maid\n\n[configuration_files]\n.maid\n"
  },
  {
    "path": "src/mackup/applications/mail.cfg",
    "content": "[application]\nname = Mail\n\n[configuration_files]\nLibrary/Preferences/com.apple.mail.plist\n"
  },
  {
    "path": "src/mackup/applications/mailmate.cfg",
    "content": "[application]\nname = Mailmate\n\n[configuration_files]\nLibrary/Preferences/com.freron.MailMate.plist\nLibrary/Application Support/Mailmate/com.freron.MailMate.pid\nLibrary/Application Support/Mailmate/Identities.plist\nLibrary/Application Support/Mailmate/Mailboxes.plist\nLibrary/Application Support/Mailmate/Signatures.plist\nLibrary/Application Support/Mailmate/Sources.plist\nLibrary/Application Support/Mailmate/Submission.plist\nLibrary/Application Support/Mailmate/Tags.plist\nLibrary/Application Support/Mailmate/Resources/KeyBindings\n"
  },
  {
    "path": "src/mackup/applications/mailplane.cfg",
    "content": "[application]\nname = Mailplane\n\n[configuration_files]\nLibrary/Preferences/com.mailplaneapp.Mailplane.plist\nLibrary/Preferences/com.mailplaneapp.Mailplane3.plist\n"
  },
  {
    "path": "src/mackup/applications/mako.cfg",
    "content": "[application]\nname = mako\n\n[xdg_configuration_files]\nmako/config\n"
  },
  {
    "path": "src/mackup/applications/marked2.cfg",
    "content": "[application]\nname = Marked 2\n\n[configuration_files]\nLibrary/Application Support/Marked 2\nLibrary/Preferences/com.brettterpstra.marked2.plist\n"
  },
  {
    "path": "src/mackup/applications/marta.cfg",
    "content": "[application]\nname = Marta\n\n[configuration_files]\nLibrary/Application Support/org.yanex.marta/conf.json\nLibrary/Application Support/org.yanex.marta/favorites.json\nLibrary/Application Support/org.yanex.marta/conf.marco\nLibrary/Application Support/org.yanex.marta/favorites.marco\n"
  },
  {
    "path": "src/mackup/applications/matlab.cfg",
    "content": "[application]\nname = MATLAB\n\n[configuration_files]\n.matlab\n"
  },
  {
    "path": "src/mackup/applications/maven.cfg",
    "content": "[application]\nname = Maven\n\n[configuration_files]\n.m2/settings-security.xml\n.m2/settings.xml\n.m2/toolchains.xml\n"
  },
  {
    "path": "src/mackup/applications/max.cfg",
    "content": "[application]\nname = Max\n\n[configuration_files]\nLibrary/Preferences/org.sbooth.Max.plist\n"
  },
  {
    "path": "src/mackup/applications/mendeley.cfg",
    "content": "[application]\nname = Mendelay Desktop\n\n[configuration_files]\nLibrary/Application Support/Mendeley Desktop\n"
  },
  {
    "path": "src/mackup/applications/menumeters.cfg",
    "content": "[application]\nname = MenuMeters\n\n[configuration_files]\nLibrary/Preferences/com.ragingmenace.MenuMeters.plist\n"
  },
  {
    "path": "src/mackup/applications/mercurial.cfg",
    "content": "[application]\nname = Mercurial\n\n[configuration_files]\n.hgrc\n.hgignore_global\n"
  },
  {
    "path": "src/mackup/applications/mercurymover.cfg",
    "content": "[application]\nname = MercuryMover\n\n[configuration_files]\nLibrary/Preferences/com.heliumfoot.MyWiAgent.plist\n"
  },
  {
    "path": "src/mackup/applications/messages.cfg",
    "content": "[application]\nname = Messages\n\n[configuration_files]\nLibrary/Preferences/com.apple.iChat.AIM.plist\nLibrary/Preferences/com.apple.iChat.Jabber.plist\nLibrary/Preferences/com.apple.iChat.StatusMessages.plist\nLibrary/Preferences/com.apple.iChat.Yahoo.plist\n"
  },
  {
    "path": "src/mackup/applications/micro.cfg",
    "content": "[application]\nname = Micro\n\n[xdg_configuration_files]\nmicro/colorschemes\nmicro/syntax\nmicro/settings.json\nmicro/bindings.json\n"
  },
  {
    "path": "src/mackup/applications/microsoft-remote-desktop.cfg",
    "content": "[application]\nname = Microsoft Remote Desktop\n\n[configuration_files]\nLibrary/Containers/com.microsoft.rdc.macos/Data/Library/Preferences/com.microsoft.rdc.macos.plist\n"
  },
  {
    "path": "src/mackup/applications/mise.cfg",
    "content": "[application]\nname = mise-en-place\n\n[configuration_files]\nmise.toml\nmise/config.toml\n.mise.toml\n.mise/config.toml\n\n[xdg_configuration_files]\nmise.toml\nmise/config.toml\nmise/conf.d\n.mise.toml\n.mise/config.toml\n.mise/conf.d\n"
  },
  {
    "path": "src/mackup/applications/mitmproxy.cfg",
    "content": "[application]\nname = mitmproxy\n\n[configuration_files]\n.mitmproxy/config.yaml\n"
  },
  {
    "path": "src/mackup/applications/mkcert.cfg",
    "content": "[application]\nname = mkcert\n\n[configuration_files]\nLibrary/Application Support/mkcert\n"
  },
  {
    "path": "src/mackup/applications/mole.cfg",
    "content": "[application]\nname = Mole\n\n[xdg_configuration_files]\nmole/whitelist\n"
  },
  {
    "path": "src/mackup/applications/monodevelop.cfg",
    "content": "[application]\nname = MonoDevelop\n\n[configuration_files]\nLibrary/MonoDevelop-Unity-5.0/KeyBindings/Custom.mac-kb.xml\nLibrary/Preferences/MonoDevelop-Unity-5.0/MonoDevelopProperties.xml\nLibrary/MonoDevelop-Unity-5.0/Policies/Default.mdpolicy.xml\n"
  },
  {
    "path": "src/mackup/applications/moom.cfg",
    "content": "[application]\nname = Moom\n\n[configuration_files]\nLibrary/Preferences/com.manytricks.Moom.plist\nLibrary/Application Support/Many Tricks\n"
  },
  {
    "path": "src/mackup/applications/mosaic.cfg",
    "content": "[application]\nname = Mosaic\n\n[configuration_files]\nLibrary/Application Support/com.lightpillar.Mosaic/MosaicCoreData.storedata\n"
  },
  {
    "path": "src/mackup/applications/mou.cfg",
    "content": "[application]\nname = Mou\n\n[configuration_files]\nLibrary/Preferences/com.mouapp.Mou.plist\nLibrary/Preferences/com.mouapp.Mou.LSSharedFileList.plist\nLibrary/Application Support/Mou\n"
  },
  {
    "path": "src/mackup/applications/mpd.cfg",
    "content": "[application]\nname = mpd\n\n[configuration_files]\n.mpd\n.mpdconf\n"
  },
  {
    "path": "src/mackup/applications/mplayerx.cfg",
    "content": "[application]\nname = MPlayerX\n\n[configuration_files]\nLibrary/Preferences/org.niltsh.MPlayerX.plist\n"
  },
  {
    "path": "src/mackup/applications/mps-youtube.cfg",
    "content": "[application]\nname = MPS Youtube\n\n[xdg_configuration_files]\nmps-youtube/config\nmps-youtube/playlist_v2\n"
  },
  {
    "path": "src/mackup/applications/mpv.cfg",
    "content": "[application]\nname = MPV\n\n[configuration_files]\n.mpv/channels.conf\n.mpv/config\n.mpv/input.conf\n\n[xdg_configuration_files]\nmpv/config\nmpv/mpv.conf\nmpv/scripts\nmpv/script-opts\nmpv/input.conf\nmpv/watch_later\n"
  },
  {
    "path": "src/mackup/applications/mtmr.cfg",
    "content": "[application]\nname = mtmr\n\n[configuration_files]\nLibrary/Application Support/MTMR/items.json\n"
  },
  {
    "path": "src/mackup/applications/multitouch.cfg",
    "content": "[application]\nname = Multitouch\n\n[configuration_files]\nLibrary/Preferences/com.brassmonkery.Multitouch.plist\n"
  },
  {
    "path": "src/mackup/applications/mumu.cfg",
    "content": "[application]\nname = Mumu\n\n[configuration_files]\nLibrary/Application Support/Mumu\n"
  },
  {
    "path": "src/mackup/applications/musicbrainz-picard.cfg",
    "content": "[application]\nname = MusicBrainz Picard\n\n[xdg_configuration_files]\nMusicBrainz\n"
  },
  {
    "path": "src/mackup/applications/mutespotifyads.cfg",
    "content": "[application]\nname = MuteSpotifyAds\n\n[configuration_files]\nLibrary/Preferences/de.simonmeusel.MuteSpotifyAds.plist\n"
  },
  {
    "path": "src/mackup/applications/mycli.cfg",
    "content": "[application]\nname = mycli\n\n[configuration_files]\n.myclirc\n"
  },
  {
    "path": "src/mackup/applications/myrepos.cfg",
    "content": "[application]\nname = myrepos\n\n[configuration_files]\n.mrconfig\n"
  },
  {
    "path": "src/mackup/applications/mysql.cfg",
    "content": "[application]\nname = MySQL\n\n[configuration_files]\n.my.cnf\n"
  },
  {
    "path": "src/mackup/applications/mysqlworkbench.cfg",
    "content": "[application]\nname = MySQL Workbench\n\n[configuration_files]\nLibrary/Application Support/MySQL/Workbench/connections.xml\nLibrary/Application Support/MySQL/Workbench/server_instances.xml\nLibrary/Application Support/MySQL/Workbench/wb_options.xml\nLibrary/Preferences/com.oracle.mysql.workbench.plist\n"
  },
  {
    "path": "src/mackup/applications/name-mangler.cfg",
    "content": "[application]\nname = Name Mangler\n\n[configuration_files]\nLibrary/Application Support/Name Mangler\nLibrary/Preferences/com.manytricks.NameMangler.plist\n"
  },
  {
    "path": "src/mackup/applications/nano.cfg",
    "content": "[application]\nname = Nano\n\n[configuration_files]\n.nanorc\n"
  },
  {
    "path": "src/mackup/applications/navicat.cfg",
    "content": "[application]\nname = Navicat\n\n[configuration_files]\nLibrary/Application Support/PremiumSoft CyberTech\nLibrary/Application Support/Navicat for MySQL\nLibrary/Application Support/Navicat Premium\nLibrary/Preferences/com.prect.Navicat.plist\nLibrary/Preferences/com.prect.NavicatPremium.plist\nLibrary/Preferences/com.prect.NavicatPremiumEssentials.plist\n"
  },
  {
    "path": "src/mackup/applications/ncmpcpp.cfg",
    "content": "[application]\nname = ncmpcpp\n\n[configuration_files]\n.ncmpcpp\n"
  },
  {
    "path": "src/mackup/applications/neofetch.cfg",
    "content": "[application]\nname = Neofetch\n\n[xdg_configuration_files]\nneofetch/config.conf\n"
  },
  {
    "path": "src/mackup/applications/neovim.cfg",
    "content": "[application]\nname = neovim\n\n[configuration_files]\n.nvimrc\n.nvim\n\n[xdg_configuration_files]\nnvim/init.vim\nnvim/init.lua\nnvim/lua\nnvim/colors\nnvim/compiler\nnvim/ftplugin\nnvim/ftdetect\nnvim/indent\nnvim/plugin\nnvim/syntax\n"
  },
  {
    "path": "src/mackup/applications/nethack.cfg",
    "content": "[application]\nname = Nethack\n\n[configuration_files]\n.nethackrc\n"
  },
  {
    "path": "src/mackup/applications/netlify.cfg",
    "content": "[application]\nname = Netlify\n\n[configuration_files]\n.netlify/config.json\n"
  },
  {
    "path": "src/mackup/applications/newsbeuter.cfg",
    "content": "[application]\nname = newsbeuter\n\n[configuration_files]\n.newsbeuter/config\n.newsbeuter/urls\n"
  },
  {
    "path": "src/mackup/applications/ngrok.cfg",
    "content": "[application]\nname = ngrok\n\n[configuration_files]\n.ngrok\n.ngrok2\n"
  },
  {
    "path": "src/mackup/applications/ni.cfg",
    "content": "[application]\nname = ni\n\n[configuration_files]\n.nirc\n"
  },
  {
    "path": "src/mackup/applications/nomacs.cfg",
    "content": "[application]\nname = Nomacs\n\n[xdg_configuration_files]\nnomacs\n"
  },
  {
    "path": "src/mackup/applications/nosqlbooster-for-mongodb.cfg",
    "content": "[application]\nname = NoSQLBooster for MongoDB\n\n[configuration_files]\nDocuments/NoSQLBooster\n"
  },
  {
    "path": "src/mackup/applications/notion-enhancer.cfg",
    "content": "[application]\nname = notion-enhancer\n\n[configuration_files]\n.notion-enhancer\n"
  },
  {
    "path": "src/mackup/applications/nova.cfg",
    "content": "[application]\nname = Nova\n\n[configuration_files]\nLibrary/Preferences/com.panic.Nova.plist\n"
  },
  {
    "path": "src/mackup/applications/npm.cfg",
    "content": "[application]\nname = npm\n\n[configuration_files]\n.npmrc\n"
  },
  {
    "path": "src/mackup/applications/npmrc.cfg",
    "content": "[application]\nname = npmrc\n\n[configuration_files]\n.npmrcs\n"
  },
  {
    "path": "src/mackup/applications/nslogger.cfg",
    "content": "[application]\nname = NSLogger\n\n[configuration_files]\nLibrary/Preferences/com.florentpillet.NSLogger.plist\n"
  },
  {
    "path": "src/mackup/applications/nuget.cfg",
    "content": "[application]\nname = nuget\n\n[configuration_files]\n.nuget/NuGet/NuGet.Config\n"
  },
  {
    "path": "src/mackup/applications/nushell.cfg",
    "content": "[application]\nname = Nushell\n\n[configuration_files]\nLibrary/Application Support/nushell/env.nu\nLibrary/Application Support/nushell/config.nu\n\n[xdg_configuration_files]\nnushell/config.nu\nnushell/env.nu\n"
  },
  {
    "path": "src/mackup/applications/nvalt.cfg",
    "content": "[application]\nname = nvALT\n\n[configuration_files]\nLibrary/Preferences/net.elasticthreads.nv.plist\nLibrary/Application Support/Notational Velocity\n"
  },
  {
    "path": "src/mackup/applications/nvm.cfg",
    "content": "[application]\nname = nvm\n\n[configuration_files]\n.nvm/default-packages\n"
  },
  {
    "path": "src/mackup/applications/nvpy.cfg",
    "content": "[application]\nname = nvpy\n\n[configuration_files]\n.nvpy.cfg\n"
  },
  {
    "path": "src/mackup/applications/obs.cfg",
    "content": "[application]\nname = OBS\n\n[configuration_files]\nLibrary/Preferences/com.obsproject.obs-studio.plist\nLibrary/Application Support/obs-studio/global.ini\nLibrary/Application Support/obs-studio/basic\n"
  },
  {
    "path": "src/mackup/applications/oci.cfg",
    "content": "[application]\nname = Oracle Cloud Infrastructure CLI\n\n[configuration_files]\n.oci/config\n.oci/oci_cli_rc\n"
  },
  {
    "path": "src/mackup/applications/offlineimap.cfg",
    "content": "[application]\nname = OfflineIMAP\n\n[configuration_files]\n.offlineimaprc\n"
  },
  {
    "path": "src/mackup/applications/ogdesign-eagle.cfg",
    "content": "[application]\nname = Eagle (ogdesign)\n\n[configuration_files]\nLibrary/Application Support/Eagle/Settings\n"
  },
  {
    "path": "src/mackup/applications/oh-my-fish.cfg",
    "content": "[application]\nname = Oh My Fish\n\n[xdg_configuration_files]\nomf\n"
  },
  {
    "path": "src/mackup/applications/oh-my-tmux.cfg",
    "content": "[application]\nname = Oh My Tmux\n\n[configuration_files]\n.tmux.conf.local\n"
  },
  {
    "path": "src/mackup/applications/omnifocus.cfg",
    "content": "[application]\nname = OmniFocus\n\n[configuration_files]\nLibrary/Application Support/OmniFocus/Plug-Ins\nLibrary/Application Support/OmniFocus/Themes\nLibrary/Preferences/com.omnigroup.OmniFocus.plist\n"
  },
  {
    "path": "src/mackup/applications/omnigraffle.cfg",
    "content": "[application]\nname = OmniGraffle\n\n[configuration_files]\nLibrary/Application Support/The Omni Group/OmniGraffle\n"
  },
  {
    "path": "src/mackup/applications/openbox.cfg",
    "content": "[application]\nname = openbox\n\n[xdg_configuration_files]\nopenbox/menu.xml\nopenbox/rc.xml\nopenbox/environment\nopenbox/autostart\n"
  },
  {
    "path": "src/mackup/applications/opencode.cfg",
    "content": "[application]\nname = OpenCode\n\n[configuration_files]\n.local/share/opencode/auth.json\n\n[xdg_configuration_files]\nopencode/agent\nopencode/command\nopencode/themes\nopencode/tool\nopencode/settings\nopencode/opencode.json\nopencode/opencode.jsonc\nopencode/config.json\nopencode/AGENTS.md\n"
  },
  {
    "path": "src/mackup/applications/openemu.cfg",
    "content": "[application]\nname = OpenEmu\n\n[configuration_files]\nLibrary/Preferences/org.openemu.OpenEmu.plist\nLibrary/Application Support/OpenEmu\n"
  },
  {
    "path": "src/mackup/applications/opera.cfg",
    "content": "[application]\nname = Opera\n\n[configuration_files]\nLibrary/Application Support/com.operasoftware.Opera\nLibrary/Application Support/com.operasoftware.OperaDeveloper\nLibrary/Application Support/com.operasoftware.OperaNext\nLibrary/Preferences/com.operasoftware.Opera.plist\nLibrary/Preferences/com.operasoftware.OperaDeveloper.plist\nLibrary/Preferences/com.operasoftware.OperaNext.plist\n"
  },
  {
    "path": "src/mackup/applications/p10k.cfg",
    "content": "[application]\nname = Powerlevel10k\n\n[configuration_files]\n.p10k.zsh\n"
  },
  {
    "path": "src/mackup/applications/paintbrush.cfg",
    "content": "[application]\nname = Paintbrush\n\n[configuration_files]\nLibrary/Preferences/com.soggywaffles.Paintbrush.plist\n"
  },
  {
    "path": "src/mackup/applications/pandoc.cfg",
    "content": "[application]\nname = Pandoc\n\n[configuration_files]\n.pandoc\n"
  },
  {
    "path": "src/mackup/applications/pass.cfg",
    "content": "[application]\nname = pass\n\n[configuration_files]\n.password-store\n"
  },
  {
    "path": "src/mackup/applications/pastebot.cfg",
    "content": "[application]\nname = Pastebot\n\n[configuration_files]\nLibrary/Preferences/com.tapbots.PastebotSync.plist\nLibrary/Preferences/com.tapbots.PastebotSync.prefPane.plist\nLibrary/Preferences/com.tapbots.PastebotSync.stats.plist\n"
  },
  {
    "path": "src/mackup/applications/path-finder.cfg",
    "content": "[application]\nname = Path Finder\n\n[configuration_files]\nLibrary/Preferences/com.cocoatech.PathFinder.plist\nLibrary/Application Support/Path Finder\n"
  },
  {
    "path": "src/mackup/applications/pdfjam.cfg",
    "content": "[application]\nname = PDFjam\n\n[configuration_files]\n.pdfjam.conf\n"
  },
  {
    "path": "src/mackup/applications/pear.cfg",
    "content": "[application]\nname = Pear\n\n[configuration_files]\n.pearrc\n"
  },
  {
    "path": "src/mackup/applications/pentadactyl.cfg",
    "content": "[application]\nname = Pentadactyl\n\n[configuration_files]\n.pentadactyl\n.pentadactylrc\n"
  },
  {
    "path": "src/mackup/applications/perl.cfg",
    "content": "[application]\nname = Perl\n\n[configuration_files]\n.perltidyrc\n.perlcriticrc\n.proverc\n"
  },
  {
    "path": "src/mackup/applications/pgsql.cfg",
    "content": "[application]\nname = PostgreSQL\n\n[configuration_files]\n.pgpass\n.psqlrc\n"
  },
  {
    "path": "src/mackup/applications/phoenix.cfg",
    "content": "[application]\nname = Phoenix\n\n[configuration_files]\n.phoenix.js\n"
  },
  {
    "path": "src/mackup/applications/phoneview.cfg",
    "content": "[application]\nname = PhoneView\n\n[configuration_files]\nLibrary/Preferences/com.ecamm.PhoneView.plist\n"
  },
  {
    "path": "src/mackup/applications/photoshop.cfg",
    "content": "[application]\nname = Adobe Photoshop\n\n[configuration_files]\nLibrary/Application Support/Adobe/Adobe Photoshop CC 2013/Presets\nLibrary/Application Support/Adobe/Adobe Photoshop CC 2014/Presets\nLibrary/Application Support/Adobe/Adobe Photoshop CC 2015/Presets\nLibrary/Application Support/Adobe/Adobe Photoshop CC 2015.5/Presets\nLibrary/Application Support/Adobe/Adobe Photoshop CC 2019/Presets\nLibrary/Preferences/Adobe Photoshop CC 2013 Settings\nLibrary/Preferences/Adobe Photoshop CC 2014 Settings\nLibrary/Preferences/Adobe Photoshop CC 2015 Settings\nLibrary/Preferences/Adobe Photoshop CC 2015.5 Settings\nLibrary/Preferences/Adobe Photoshop CC 2019 Settings\nLibrary/Preferences/com.adobe.Photoshop.plist\n"
  },
  {
    "path": "src/mackup/applications/phpstorm.cfg",
    "content": "[application]\nname = PhpStorm\n\n[configuration_files]\nLibrary/Application Support/JetBrains/PhpStorm2020.1\nLibrary/Application Support/JetBrains/PhpStorm2020.2\nLibrary/Application Support/JetBrains/PhpStorm2020.3\nLibrary/Application Support/JetBrains/PhpStorm2021.1\nLibrary/Application Support/JetBrains/PhpStorm2021.2\nLibrary/Application Support/JetBrains/PhpStorm2021.3\nLibrary/Application Support/JetBrains/PhpStorm2022.1\nLibrary/Application Support/JetBrains/PhpStorm2022.2\nLibrary/Application Support/JetBrains/PhpStorm2022.3\nLibrary/Application Support/JetBrains/PhpStorm2023.1\nLibrary/Application Support/JetBrains/PhpStorm2023.2\nLibrary/Application Support/JetBrains/PhpStorm2023.3\nLibrary/Application Support/JetBrains/PhpStorm2024.1\nLibrary/Application Support/PhpStorm2016.1\nLibrary/Application Support/PhpStorm2016.2\nLibrary/Application Support/PhpStorm2016.3\nLibrary/Application Support/PhpStorm2017.1\nLibrary/Application Support/PhpStorm2017.2\nLibrary/Application Support/PhpStorm2017.3\nLibrary/Application Support/PhpStorm2018.1\nLibrary/Application Support/PhpStorm2018.2\nLibrary/Application Support/PhpStorm2018.3\nLibrary/Application Support/PhpStorm2019.1\nLibrary/Application Support/PhpStorm2019.2\nLibrary/Application Support/PhpStorm2019.3\nLibrary/Application Support/PhpStorm2019.4\nLibrary/Application Support/WebIde100\nLibrary/Application Support/WebIde60\nLibrary/Application Support/WebIde70\nLibrary/Application Support/WebIde80\nLibrary/Application Support/WebIde90\nLibrary/Application Support/WebIde95\nLibrary/Preferences/com.jetbrains.PhpStorm.plist\nLibrary/Preferences/PhpStorm2016.1\nLibrary/Preferences/PhpStorm2016.2\nLibrary/Preferences/PhpStorm2016.3\nLibrary/Preferences/PhpStorm2017.1\nLibrary/Preferences/PhpStorm2017.2\nLibrary/Preferences/PhpStorm2017.3\nLibrary/Preferences/PhpStorm2018.1\nLibrary/Preferences/PhpStorm2018.2\nLibrary/Preferences/PhpStorm2018.3\nLibrary/Preferences/PhpStorm2019.1\nLibrary/Preferences/PhpStorm2019.2\nLibrary/Preferences/PhpStorm2019.3\nLibrary/Preferences/PhpStorm2019.4\nLibrary/Preferences/WebIde100\nLibrary/Preferences/WebIde60\nLibrary/Preferences/WebIde70\nLibrary/Preferences/WebIde80\nLibrary/Preferences/WebIde90\nLibrary/Preferences/WebIde95\n.PhpStorm2016.1/config\n.PhpStorm2016.2/config\n.PhpStorm2016.3/config\n.PhpStorm2017.1/config\n.PhpStorm2017.2/config\n.PhpStorm2017.3/config\n.PhpStorm2018.1/config\n.PhpStorm2018.2/config\n.PhpStorm2018.3/config\n.PhpStorm2019.1/config\n.PhpStorm2019.2/config\n.PhpStorm2019.3/config\n"
  },
  {
    "path": "src/mackup/applications/picgo.cfg",
    "content": "[application]\nname = PicGo\n\n[configuration_files]\nLibrary/Application Support/picgo/data.json\n"
  },
  {
    "path": "src/mackup/applications/pidgin.cfg",
    "content": "[application]\nname = Pidgin\n\n[configuration_files]\n.purple\n"
  },
  {
    "path": "src/mackup/applications/pip.cfg",
    "content": "[application]\nname = pip\n\n[configuration_files]\nLibrary/Application Support/pip/pip.conf\n.pip/pip.conf\n\n[xdg_configuration_files]\npip/pip.conf\n"
  },
  {
    "path": "src/mackup/applications/pixelsnap.cfg",
    "content": "[application]\nname = PixelSnap\n\n[configuration_files]\nLibrary/Preferences/com.getpixelsnap.app.plist\n"
  },
  {
    "path": "src/mackup/applications/pixelsnap2.cfg",
    "content": "[application]\nname = PixelSnap 2\n\n[configuration_files]\nLibrary/Preferences/pl.maketheweb.pixelsnap2.plist\n"
  },
  {
    "path": "src/mackup/applications/planner.cfg",
    "content": "[application]\nname = Planner\n\n[configuration_files]\n.var/app/com.github.alainm23.planner/config\n.var/app/com.github.alainm23.planner/data\n"
  },
  {
    "path": "src/mackup/applications/plover.cfg",
    "content": "[application]\nname = Plover\n\n[configuration_files]\nLibrary/Application Support/Plover\n"
  },
  {
    "path": "src/mackup/applications/pnpm.cfg",
    "content": "[application]\nname = pnpm\n\n[configuration_files]\n.pnpm/global_pnpmfile.js\n.pnpm-store/2/store.json\n"
  },
  {
    "path": "src/mackup/applications/pock.cfg",
    "content": "[application]\nname = Pock\n\n[configuration_files]\nLibrary/Preferences/com.pigigaldi.pock.plist\n"
  },
  {
    "path": "src/mackup/applications/podman.cfg",
    "content": "[application]\nname = Podman\n\n[xdg_configuration_files]\ncontainers/podman-connections.json\n"
  },
  {
    "path": "src/mackup/applications/poedit.cfg",
    "content": "[application]\nname = Poedit\n\n[configuration_files]\nLibrary/Preferences/net.poedit.Poedit.cfg\nLibrary/Preferences/net.poedit.Poedit.plist\n"
  },
  {
    "path": "src/mackup/applications/poetry.cfg",
    "content": "[application]\nname = poetry\n\n[configuration_files]\nLibrary/Application Support/pypoetry/config.toml\nLibrary/Preferences/pypoetry/config.toml\n\n[xdg_configuration_files]\npypoetry/config.toml\n"
  },
  {
    "path": "src/mackup/applications/pokerstars.cfg",
    "content": "[application]\nname = PokerStars\n\n[configuration_files]\nLibrary/Application Support/PokerStars\nLibrary/Preferences/com.pokerstars.user.ini\nLibrary/Preferences/com.pokerstars.PokerStars.plist\nLibrary/Application Support/PokerStarsEU\nLibrary/Preferences/com.pokerstars.eu.user.ini\nLibrary/Preferences/com.pokerstars.PokerStarsEU.plist\n"
  },
  {
    "path": "src/mackup/applications/polybar.cfg",
    "content": "[application]\nname = polybar\n\n[configuration_files]\n.polybar/config\n.polybar/launch.sh\n\n[xdg_configuration_files]\npolybar/config\npolybar/launch.sh\npolybar/scripts\n"
  },
  {
    "path": "src/mackup/applications/popclip.cfg",
    "content": "[application]\nname = PopClip\n\n[configuration_files]\nLibrary/Preferences/com.pilotmoon.popclip.plist\nLibrary/Application Support/PopClip/Extensions\n"
  },
  {
    "path": "src/mackup/applications/popcorn-time.cfg",
    "content": "[application]\nname = Popcorn-Time\n\n[configuration_files]\nLibrary/Application Support/Popcorn-Time/data/bookmarks.db\nLibrary/Application Support/Popcorn-Time/data/settings.db\nLibrary/Application Support/Popcorn-Time/data/watched.db\nLibrary/Application Support/Popcorn-Time/data/movies.db\nLibrary/Application Support/Popcorn-Time/data/shows.db\n"
  },
  {
    "path": "src/mackup/applications/postico.cfg",
    "content": "[application]\nname = Postico\n\n[configuration_files]\nLibrary/Containers/at.eggerapps.Postico/Data/Library/Application Support/Postico\nLibrary/Containers/at.eggerapps.Postico/Data/Library/Preferences/at.eggerapps.Postico.plist\nLibrary/Preferences/at.eggerapps.Postico.plist\n"
  },
  {
    "path": "src/mackup/applications/pow.cfg",
    "content": "[application]\nname = Pow\n\n[configuration_files]\n.powconfig\n.powenv\n.powrc\n"
  },
  {
    "path": "src/mackup/applications/powerline.cfg",
    "content": "[application]\nname = Powerline\n\n[xdg_configuration_files]\npowerline/config.json\npowerline/colors.json\npowerline/themes\npowerline/colorschemes\n"
  },
  {
    "path": "src/mackup/applications/prezto.cfg",
    "content": "[application]\nname = Prezto\n\n[configuration_files]\n.zpreztorc\n"
  },
  {
    "path": "src/mackup/applications/processing.cfg",
    "content": "[application]\nname = Processing\n\n[configuration_files]\nLibrary/Processing/preferences.txt\n"
  },
  {
    "path": "src/mackup/applications/proselint.cfg",
    "content": "[application]\nname = proselint\n\n[configuration_files]\n.proselintrc\n\n[xdg_configuration_files]\nproselint/config\n"
  },
  {
    "path": "src/mackup/applications/proxychains.cfg",
    "content": "[application]\nname = ProxyChains\n\n[configuration_files]\n.proxychains\n"
  },
  {
    "path": "src/mackup/applications/proxyman.cfg",
    "content": "[application]\nname = Proxyman\n\n[configuration_files]\nLibrary/Application Support/com.proxyman.NSProxy\nLibrary/Preferences/com.proxyman.NSProxy.plist\nLibrary/Application Support/com.proxyman.NSProxy-setapp\nLibrary/Preferences/com.proxyman.NSProxy-setapp.plist\n"
  },
  {
    "path": "src/mackup/applications/prusa-slicer.cfg",
    "content": "[application]\nname = PrusaSlicer\n\n[xdg_configuration_files]\nPrusaSlicer\n"
  },
  {
    "path": "src/mackup/applications/psysh.cfg",
    "content": "[application]\nname = PsySH\n\n[xdg_configuration_files]\npsysh/config.php\n"
  },
  {
    "path": "src/mackup/applications/punto-switcher.cfg",
    "content": "[application]\nname = Punto Switcher\n\n[configuration_files]\nLibrary/Preferences/ru.yandex.punto.plist\n"
  },
  {
    "path": "src/mackup/applications/pycharm.cfg",
    "content": "[application]\nname = PyCharm\n\n[configuration_files]\n.PyCharm2016.1/config\n.PyCharm2016.2/config\n.PyCharm2016.3/config\n.PyCharm2017.1/config\n.PyCharm2017.2/config\n.PyCharm2017.3/config\n.PyCharm40/config\n.PyCharm50/config\n.PyCharmCE2016.1/config\n.PyCharmCE2016.2/config\n.PyCharmCE2016.3/config\n.PyCharmCE2017.1/config\n.PyCharmCE2017.2/config\n.PyCharmCE2017.3/config\n.PyCharmCE2019.3/config\nLibrary/Application Support/JetBrains/PyCharm2020.1\nLibrary/Application Support/JetBrains/PyCharm2020.2\nLibrary/Application Support/JetBrains/PyCharm2020.3\nLibrary/Application Support/JetBrains/PyCharm2020.4\nLibrary/Application Support/JetBrains/PyCharm2023.1\nLibrary/Application Support/JetBrains/PyCharm2023.2\nLibrary/Application Support/JetBrains/PyCharmCE2020.1\nLibrary/Application Support/PyCharm\nLibrary/Application Support/PyCharm2016.1\nLibrary/Application Support/PyCharm2016.2\nLibrary/Application Support/PyCharm2016.3\nLibrary/Application Support/PyCharm2017.1\nLibrary/Application Support/PyCharm2017.2\nLibrary/Application Support/PyCharm2017.3\nLibrary/Application Support/PyCharm2019.2\nLibrary/Application Support/PyCharm2019.3\nLibrary/Application Support/PyCharm40\nLibrary/Application Support/PyCharm50\nLibrary/Application Support/PyCharmCE2016.1\nLibrary/Application Support/PyCharmCE2016.2\nLibrary/Application Support/PyCharmCE2016.3\nLibrary/Application Support/PyCharmCE2017.1\nLibrary/Application Support/PyCharmCE2017.2\nLibrary/Application Support/PyCharmCE2017.3\nLibrary/Preferences/PyCharm2016.1\nLibrary/Preferences/PyCharm2016.2\nLibrary/Preferences/PyCharm2016.3\nLibrary/Preferences/PyCharm2017.1\nLibrary/Preferences/PyCharm2017.2\nLibrary/Preferences/PyCharm2017.3\nLibrary/Preferences/PyCharm2019.2\nLibrary/Preferences/PyCharm2019.3\nLibrary/Preferences/PyCharm40\nLibrary/Preferences/PyCharm50\nLibrary/Preferences/PyCharmCE2016.1\nLibrary/Preferences/PyCharmCE2016.2\nLibrary/Preferences/PyCharmCE2016.3\nLibrary/Preferences/PyCharmCE2017.1\nLibrary/Preferences/PyCharmCE2017.2\nLibrary/Preferences/PyCharmCE2017.3\n"
  },
  {
    "path": "src/mackup/applications/pypi.cfg",
    "content": "[application]\nname = PyPI\n\n[configuration_files]\n.pypirc\n"
  },
  {
    "path": "src/mackup/applications/pyradio.cfg",
    "content": "[application]\nname = PyRadio\n\n[configuration_files]\n.pyradio/stations.csv\n"
  },
  {
    "path": "src/mackup/applications/querious.cfg",
    "content": "[application]\nname = Querious\n\n[configuration_files]\nLibrary/Preferences/com.araeliumgroup.querious.plist\nLibrary/Application Support/Querious\n"
  },
  {
    "path": "src/mackup/applications/quicklook.cfg",
    "content": "[application]\nname = Quicklook\n\n[configuration_files]\nLibrary/Quicklook\n"
  },
  {
    "path": "src/mackup/applications/quicksilver.cfg",
    "content": "[application]\nname = Quicksilver\n\n[configuration_files]\nLibrary/Preferences/com.blacktree.Quicksilver.plist\nLibrary/Application Support/Quicksilver\n"
  },
  {
    "path": "src/mackup/applications/quitter.cfg",
    "content": "[application]\nname = Quitter\n\n[configuration_files]\nLibrary/Preferences/com.marcoarment.quitter.plist\n"
  },
  {
    "path": "src/mackup/applications/qutebrowser.cfg",
    "content": "[application]\nname = Qutebrowser\n\n[configuration_files]\n.qutebrowser/config.py\n\n[xdg_configuration_files]\nqutebrowser/config.py\nqutebrowser/qutebrowser.conf\n"
  },
  {
    "path": "src/mackup/applications/qv2ray.cfg",
    "content": "[application]\nname = Qv2ray\n\n[configuration_files]\nLibrary/Preferences/qv2ray/Qv2ray.conf\nLibrary/Preferences/qv2ray/plugin_settings\n"
  },
  {
    "path": "src/mackup/applications/r.cfg",
    "content": "[application]\nname = R\n\n[configuration_files]\n.R\n.Rhistory\n.Rprofile\n.Rprofile.d\n.Renviron\n.Renviron.d\nLibrary/Preferences/org.R-project.R.plist\n"
  },
  {
    "path": "src/mackup/applications/rails.cfg",
    "content": "[application]\nname = Rails\n\n[configuration_files]\n.railsrc\n"
  },
  {
    "path": "src/mackup/applications/ranger.cfg",
    "content": "[application]\nname = Ranger\n\n[xdg_configuration_files]\nranger/commands.py\nranger/rc.conf\nranger/rifle.conf\nranger/scope.sh\nranger/plugins\n"
  },
  {
    "path": "src/mackup/applications/rbenv.cfg",
    "content": "[application]\nname = rbenv\n\n[configuration_files]\n.rbenv/default_gems\n"
  },
  {
    "path": "src/mackup/applications/rclone.cfg",
    "content": "[application]\nname = rclone\n\n[xdg_configuration_files]\nrclone\n"
  },
  {
    "path": "src/mackup/applications/rectangle.cfg",
    "content": "[application]\nname = Rectangle\n\n[configuration_files]\nLibrary/Preferences/com.knollsoft.Rectangle.plist\n"
  },
  {
    "path": "src/mackup/applications/redshift-scheduler.cfg",
    "content": "[application]\nname = Redshift Scheduler\n\n[xdg_configuration_files]\nredshift-scheduler/rules.conf\n"
  },
  {
    "path": "src/mackup/applications/redshift.cfg",
    "content": "[application]\nname = Redshift\n\n[xdg_configuration_files]\nredshift.conf\n"
  },
  {
    "path": "src/mackup/applications/remote-desktop-manager.cfg",
    "content": "[application]\nname = Remote Desktop Manager\n\n[configuration_files]\nLibrary/Application Support/com.devolutions.remotedesktopmanager.free/Connections.db\nLibrary/Application Support/com.devolutions.remotedesktopmanager/Connections.db\n"
  },
  {
    "path": "src/mackup/applications/rhythmbox.cfg",
    "content": "[application]\nname = Rhythmbox\n\n[configuration_files]\n.gconf/apps/rhythmbox\n"
  },
  {
    "path": "src/mackup/applications/rime.cfg",
    "content": "[application]\nname = Rime\n\n[configuration_files]\nLibrary/Rime/default.custom.yaml\nLibrary/Rime/default.yaml\nLibrary/Rime/installation.yaml\nLibrary/Rime/squirrel.custom.yaml\nLibrary/Rime/squirrel.yaml\nLibrary/Rime/symbols.yaml\nLibrary/Rime/user.yaml\n\n[xdg_configuration_files]\nibus/rimedefault.custom.yaml\nibus/rimedefault.yaml\nibus/rimeinstallation.yaml\nibus/rimesquirrel.custom.yaml\nibus/rimesquirrel.yaml\nibus/rimesymbols.yaml\nibus/rimeuser.yaml\n"
  },
  {
    "path": "src/mackup/applications/ripgrep.cfg",
    "content": "[application]\nname = ripgrep\n\n[configuration_files]\n.ripgreprc\n"
  },
  {
    "path": "src/mackup/applications/robo3t.cfg",
    "content": "[application]\nname = Robo 3T\n\n[configuration_files]\nLibrary/Preferences/com.3t.Robomongo.plist\n.3T\n"
  },
  {
    "path": "src/mackup/applications/rocket.cfg",
    "content": "[application]\nname = Rocket\n\n[configuration_files]\nLibrary/Preferences/net.matthewpalmer.Rocket.plist\nLibrary/Application Support/Rocket\n"
  },
  {
    "path": "src/mackup/applications/rofi.cfg",
    "content": "[application]\nname = rofi\n\n[xdg_configuration_files]\nrofi/config.rasi\n"
  },
  {
    "path": "src/mackup/applications/royaltsx.cfg",
    "content": "[application]\nname = Royal TSX\n\n[configuration_files]\nLibrary/Application Support/Royal TSX/License.xml\nLibrary/Application Support/Royal TSX/UserPreferences.config\nLibrary/Preferences/com.lemonmojo.RoyalTSX.App.plist\n"
  },
  {
    "path": "src/mackup/applications/rstudio.cfg",
    "content": "[application]\nname = RStudio\n\n[configuration_files]\n.rstudio-desktop\nLibrary/Preferences/org.rstudio.RStudio.plist\n\n[xdg_configuration_files]\nrstudio\n"
  },
  {
    "path": "src/mackup/applications/rtorrent.cfg",
    "content": "[application]\nname = rTorrent\n\n[configuration_files]\n.rtorrent.rc\n"
  },
  {
    "path": "src/mackup/applications/rtx.cfg",
    "content": "[application]\nname = rtx\n\n[xdg_configuration_files]\nrtx\n\n[configuration_files]\n.rtx.toml\n.tool-versions\n.default-go-packages\n.default-gems\n.default-nodejs-packages\n.default-node-packages\n.default-npm-packages\n.default-python-packages\n.default-mix-commands\n"
  },
  {
    "path": "src/mackup/applications/rubitrack5.cfg",
    "content": "[application]\nname = rubiTrack 5\n\n[configuration_files]\nLibrary/Preferences/com.shiftoption.rubitrack5.pro.plist\n"
  },
  {
    "path": "src/mackup/applications/rubocop.cfg",
    "content": "[application]\nname = Rubocop\n\n[configuration_files]\n.rubocop.yml\n"
  },
  {
    "path": "src/mackup/applications/ruby-version.cfg",
    "content": "[application]\nname = Ruby Version\n\n[configuration_files]\n.ruby-version\n"
  },
  {
    "path": "src/mackup/applications/ruby.cfg",
    "content": "[application]\nname = Ruby\n\n[configuration_files]\n.gemrc\n.irbrc\n.gem/credentials\n.pryrc\n.aprc\n"
  },
  {
    "path": "src/mackup/applications/rubymine.cfg",
    "content": "[application]\nname = RubyMine\n\n[configuration_files]\nLibrary/Application Support/RubyMine40\nLibrary/Preferences/RubyMine40\nLibrary/Application Support/RubyMine50\nLibrary/Preferences/RubyMine50\nLibrary/Application Support/RubyMine60\nLibrary/Preferences/RubyMine60\nLibrary/Application Support/RubyMine70\nLibrary/Preferences/RubyMine70\nLibrary/Application Support/RubyMine80\nLibrary/Preferences/RubyMine80\nLibrary/Application Support/RubyMine2016.1\nLibrary/Preferences/RubyMine2016.1\nLibrary/Application Support/RubyMine2016.2\nLibrary/Preferences/RubyMine2016.2\nLibrary/Application Support/RubyMine2016.3\nLibrary/Preferences/RubyMine2016.3\nLibrary/Application Support/RubyMine2017.3\nLibrary/Preferences/RubyMine2017.3\nLibrary/Application Support/RubyMine2018.1\nLibrary/Preferences/RubyMine2018.1\nLibrary/Application Support/JetBrains/RubyMine2023.1\nLibrary/Application Support/JetBrains/RubyMine2023.2\n"
  },
  {
    "path": "src/mackup/applications/rust.cfg",
    "content": "[application]\nname = Rust\n\n[configuration_files]\n.cargo/config.toml\n.cargo/config\n"
  },
  {
    "path": "src/mackup/applications/rustrover.cfg",
    "content": "[application]\nname = RustRover\n\n[configuration_files]\nLibrary/Preferences/RustRover2025.2\nLibrary/Preferences/RustRover2025.3\nLibrary/Application Support/JetBrains/RustRover2025.2\nLibrary/Application Support/JetBrains/RustRover2025.3\n"
  },
  {
    "path": "src/mackup/applications/rvm.cfg",
    "content": "[application]\nname = Ruby Version Manager\n\n[configuration_files]\n.rvmrc\n"
  },
  {
    "path": "src/mackup/applications/s3cmd.cfg",
    "content": "[application]\nname = S3cmd\n\n[configuration_files]\n.s3cfg\n"
  },
  {
    "path": "src/mackup/applications/sabnzbd.cfg",
    "content": "[application]\nname = SABnzbd\n\n[configuration_files]\nLibrary/Application Support/SABnzbd/sabnzbd.ini\nLibrary/Application Support/SABnzbd/admin/rss_data.sab\n"
  },
  {
    "path": "src/mackup/applications/sbcl.cfg",
    "content": "[application]\nname = SBCL\n\n[configuration_files]\n.sbclrc\n"
  },
  {
    "path": "src/mackup/applications/sbt.cfg",
    "content": "[application]\nname = SBT\n\n[configuration_files]\n.sbtconfig\n.sbt/0.12/global.sbt\n.sbt/0.12/build.sbt\n.sbt/0.12/plugins/plugins.sbt\n.sbt/0.12/plugins/build.sbt\n.sbt/0.13/global.sbt\n.sbt/0.13/build.sbt\n.sbt/0.13/plugins/plugins.sbt\n.sbt/0.13/plugins/build.sbt\n.sbt/1.0/global.sbt\n.sbt/1.0/build.sbt\n.sbt/1.0/plugins/plugins.sbt\n.sbt/1.0/plugins/build.sbt\n"
  },
  {
    "path": "src/mackup/applications/scenario.cfg",
    "content": "[application]\nname = Scenario\n\n[configuration_files]\nLibrary/Preferences/com.lagente.scenario.plist\nLibrary/Scenario\n"
  },
  {
    "path": "src/mackup/applications/screen.cfg",
    "content": "[application]\nname = Screen\n\n[configuration_files]\n.screenrc\n"
  },
  {
    "path": "src/mackup/applications/screenhero.cfg",
    "content": "[application]\nname = Screenhero\n\n[configuration_files]\nLibrary/Preferences/com.screenhero.screenhero.plist\n"
  },
  {
    "path": "src/mackup/applications/scrivener.cfg",
    "content": "[application]\nname = Scrivener\n\n[configuration_files]\nLibrary/Preferences/com.literatureandlatte.scrivener2.plist\nLibrary/Application Support/Scrivener\n"
  },
  {
    "path": "src/mackup/applications/scroll-reverser.cfg",
    "content": "[application]\nname = Scroll Reverser\n\n[configuration_files]\nLibrary/Preferences/com.pilotmoon.scroll-reverser.plist\n"
  },
  {
    "path": "src/mackup/applications/secure-pipes.cfg",
    "content": "[application]\nname = Secure Pipes\n\n[configuration_files]\nLibrary/Preferences/net.edgeservices.connections.plist\nLibrary/Preferences/net.edgeservices.Secure-Pipes.plist\nLibrary/Preferences/net.edgeservices.sp-config.plist\nLibrary/Application Support/Secure Pipes\n"
  },
  {
    "path": "src/mackup/applications/securecrt.cfg",
    "content": "[application]\nname = SecureCRT\n\n[configuration_files]\nLibrary/Application Support/VanDyke/SecureCRT/Config\nLibrary/Preferences/com.vandyke.SecureCRT.plist\n"
  },
  {
    "path": "src/mackup/applications/seil.cfg",
    "content": "[application]\nname = Seil\n\n[configuration_files]\nLibrary/Preferences/org.pqrs.PCKeyboardHack.plist\nLibrary/Preferences/org.pqrs.Seil.plist\n"
  },
  {
    "path": "src/mackup/applications/selfcontrol.cfg",
    "content": "[application]\nname = SelfControl\n\n[configuration_files]\nLibrary/Preferences/org.eyebeam.SelfControl.plist\n"
  },
  {
    "path": "src/mackup/applications/sequel-pro.cfg",
    "content": "[application]\nname = Sequel Pro\n\n[configuration_files]\nLibrary/Application Support/Sequel Pro/Data\nLibrary/Application Support/Sequel Pro/Bundles\nLibrary/Application Support/Sequel Pro/Themes\nLibrary/Preferences/com.sequelpro.SequelPro.plist\n"
  },
  {
    "path": "src/mackup/applications/shadowsocksx-ng.cfg",
    "content": "[application]\nname = ShadowsocksX-NG\n\n[configuration_files]\n.ShadowsocksX-NG\n"
  },
  {
    "path": "src/mackup/applications/shiftit.cfg",
    "content": "[application]\nname = ShiftIt\n\n[configuration_files]\nLibrary/Preferences/org.shiftitapp.ShiftIt.plist\n"
  },
  {
    "path": "src/mackup/applications/shifty.cfg",
    "content": "[application]\nname = Shifty\n\n[configuration_files]\nLibrary/Preferences/io.natethompson.Shifty.plist\n"
  },
  {
    "path": "src/mackup/applications/shimo.cfg",
    "content": "[application]\nname = Shimo\n\n[configuration_files]\nLibrary/Application Support/Shimo/ShimoProfiles.xml\nLibrary/Preferences/com.chungwasoft.Shimo.plist\n"
  },
  {
    "path": "src/mackup/applications/showyedge.cfg",
    "content": "[application]\nname = ShowyEdge\n\n[configuration_files]\nLibrary/Preferences/org.pqrs.ShowyEdge.plist\n"
  },
  {
    "path": "src/mackup/applications/shsh-blobs.cfg",
    "content": "[application]\nname = SHSH Blobs\n\n[configuration_files]\n.shsh\n"
  },
  {
    "path": "src/mackup/applications/shuttle.cfg",
    "content": "[application]\nname = Shuttle\n\n[configuration_files]\n.shuttle.json\n"
  },
  {
    "path": "src/mackup/applications/sizeup.cfg",
    "content": "[application]\nname = SizeUp\n\n[configuration_files]\nLibrary/Preferences/com.irradiatedsoftware.SizeUp.plist\nLibrary/Application Support/SizeUp/SizeUp.sizeuplicense\n"
  },
  {
    "path": "src/mackup/applications/sizzy.cfg",
    "content": "[application]\nname = Sizzy\n\n[configuration_files]\nLibrary/Application Support/Sizzy/config.json\nLibrary/Application Support/Sizzy/window-state.json\n"
  },
  {
    "path": "src/mackup/applications/sketchybar.cfg",
    "content": "[application]\nname = SketchyBar\n\n[xdg_configuration_files]\nsketchybar/sketchybarrc\nsketchybar/plugins\n"
  },
  {
    "path": "src/mackup/applications/skhd.cfg",
    "content": "[application]\nname = skhd\n\n[configuration_files]\n.skhdrc\n\n[xdg_configuration_files]\nskhd/skhdrc\n"
  },
  {
    "path": "src/mackup/applications/skim.cfg",
    "content": "[application]\nname = Skim\n\n[configuration_files]\nLibrary/Preferences/net.sourceforge.skim-app.skim.plist\n"
  },
  {
    "path": "src/mackup/applications/skitch.cfg",
    "content": "[application]\nname = Skitch\n\n[configuration_files]\nLibrary/Preferences/com.plasq.skitch.plist\nLibrary/Preferences/com.plasq.skitch.history\n"
  },
  {
    "path": "src/mackup/applications/slate.cfg",
    "content": "[application]\nname = Slate\n\n[configuration_files]\n.slate\n.slate.js\nLibrary/Application Support/com.slate.Slate\n"
  },
  {
    "path": "src/mackup/applications/slic3r.cfg",
    "content": "[application]\nname = Slic3r\n\n[configuration_files]\nLibrary/Application Support/Slic3r\n"
  },
  {
    "path": "src/mackup/applications/slogger.cfg",
    "content": "[application]\nname = Slogger\n\n[configuration_files]\nSlogger\n"
  },
  {
    "path": "src/mackup/applications/smartgit.cfg",
    "content": "[application]\nname = SmartGit\n\n[configuration_files]\nLibrary/Preferences/SmartGit/smartgit.vmoptions\n"
  },
  {
    "path": "src/mackup/applications/smooth-mouse.cfg",
    "content": "[application]\nname = Smooth Mouse\n\n[configuration_files]\nLibrary/Preferences/com.cyberic.SmoothMouse.plist\n"
  },
  {
    "path": "src/mackup/applications/soulver.cfg",
    "content": "[application]\nname = Soulver\n\n[configuration_files]\nLibrary/Application Support/Soulver\nLibrary/Preferences/com.acqualia.soulver.plist\n# Soulver 3\nLibrary/Application Support/Soulver 3\nLibrary/Preferences/app.soulver.mac.plist\n"
  },
  {
    "path": "src/mackup/applications/sourcetree.cfg",
    "content": "[application]\nname = SourceTree\n\n[configuration_files]\nLibrary/Application Support/SourceTree/sourcetree.license\nLibrary/Application Support/SourceTree/browser.plist\nLibrary/Application Support/SourceTree/hgrc_sourcetree\nLibrary/Application Support/SourceTree/hostingservices.plist\n"
  },
  {
    "path": "src/mackup/applications/spacelauncher.cfg",
    "content": "[application]\nname = SpaceLauncher\n\n[configuration_files]\nLibrary/Preferences/name.guoc.SpaceLauncher.plist\n"
  },
  {
    "path": "src/mackup/applications/spacemacs.cfg",
    "content": "[application]\nname = Spacemacs\n\n[configuration_files]\n.spacemacs\n.spacemacs.d\n"
  },
  {
    "path": "src/mackup/applications/spacevim.cfg",
    "content": "[application]\nname = SpaceVim\n\n[configuration_files]\n# Default configuration directory\n.SpaceVim.d\n# Default configuration file; could be this specific or backup the config directory\n# .SpaceVim.d/init.toml\n"
  },
  {
    "path": "src/mackup/applications/spamsieve.cfg",
    "content": "[application]\nname = SpamSieve\n\n[configuration_files]\nLibrary/Application Support/SpamSieve\nLibrary/Preferences/com.c-command.SpamSieve.plist\n"
  },
  {
    "path": "src/mackup/applications/spark.cfg",
    "content": "[application]\nname = Spark\n\n[configuration_files]\nLibrary/Application Support/Spark\n"
  },
  {
    "path": "src/mackup/applications/spectacle.cfg",
    "content": "[application]\nname = Spectacle\n\n[configuration_files]\nLibrary/Preferences/com.divisiblebyzero.Spectacle.plist\nLibrary/Application Support/Spectacle\n"
  },
  {
    "path": "src/mackup/applications/spectrwm.cfg",
    "content": "[application]\nname = Spectrwm\n\n[configuration_files]\n.spectrwm.conf\n"
  },
  {
    "path": "src/mackup/applications/splice.cfg",
    "content": "[application]\nname = Splice\n\n[configuration_files]\nLibrary/Preferences/com.splice.Splice.plist\n"
  },
  {
    "path": "src/mackup/applications/spotify-notifications.cfg",
    "content": "[application]\nname = Spotify-Notifications \n\n[configuration_files]\nLibrary/Preferences/io.citruspi.Spotify-Notifications.plist\n"
  },
  {
    "path": "src/mackup/applications/spotify.cfg",
    "content": "[application]\nname = Spotify\n\n[configuration_files]\nLibrary/Preferences/com.spotify.client.plist\n"
  },
  {
    "path": "src/mackup/applications/sqitch.cfg",
    "content": "[application]\nname = Sqitch\n\n[configuration_files]\n.sqitch/sqitch.conf\n"
  },
  {
    "path": "src/mackup/applications/ssh.cfg",
    "content": "[application]\nname = SSH\n\n[configuration_files]\n.ssh/config\n.ssh/authorized_keys\n"
  },
  {
    "path": "src/mackup/applications/starship.cfg",
    "content": "[application]\nname = Starship\n\n[configuration_files]\n# .config is hardcoded, see https://github.com/starship/starship/blob/1eabd527252291398df5749d30f3459ea2a03823/src/config.rs#L184\n.config/starship.toml\n"
  },
  {
    "path": "src/mackup/applications/startupizer2.cfg",
    "content": "[application]\nname = Startupizer2\n\n[configuration_files]\nLibrary/Application Support/Startupizer\n"
  },
  {
    "path": "src/mackup/applications/stata.cfg",
    "content": "[application]\nname = Stata\n\n[configuration_files]\nLibrary/Application Support/Stata\nLibrary/Preferences/com.stata.stata12.plist\nLibrary/Preferences/com.stata.stata13.plist\nLibrary/Preferences/Stata 13 Preferences\n"
  },
  {
    "path": "src/mackup/applications/stats.cfg",
    "content": "[application]\nname = Stats\n\n[configuration_files]\nLibrary/Preferences/eu.exelban.Stats.plist\n"
  },
  {
    "path": "src/mackup/applications/stay.cfg",
    "content": "[application]\nname = Stay\n\n[configuration_files]\nLibrary/Preferences/com.cordlessdog.Stay.plist\nLibrary/Application Support/Stay\n"
  },
  {
    "path": "src/mackup/applications/storyist-3.cfg",
    "content": "[application]\nname = storyist-3\n\n[configuration_files]\nLibrary/Preferences/.com.storyist.storyist.plist\nLibrary/Preferences/com.storyist.storyist.plist\n"
  },
  {
    "path": "src/mackup/applications/streamdeck.cfg",
    "content": "[application]\nname = Elgato StreamDeck\n\n[configuration_files]\nLibrary/Application Support/com.elgato.StreamDeck\n"
  },
  {
    "path": "src/mackup/applications/subler.cfg",
    "content": "[application]\nname = Subler\n\n[configuration_files]\nLibrary/Application Support/Subler\n"
  },
  {
    "path": "src/mackup/applications/sublime-merge.cfg",
    "content": "[application]\nname = Sublime Merge\n\n[configuration_files]\nLibrary/Application Support/Sublime Merge/Packages/User\n"
  },
  {
    "path": "src/mackup/applications/sublime-text-2.cfg",
    "content": "[application]\nname = Sublime Text 2\n\n[configuration_files]\n# Based on https://packagecontrol.io/docs/syncing\nLibrary/Application Support/Sublime Text 2/Packages/User\n\n[xdg_configuration_files]\nsublime-text-2/Packages/User\n"
  },
  {
    "path": "src/mackup/applications/sublime-text-3.cfg",
    "content": "[application]\nname = Sublime Text 3\n\n[configuration_files]\n# Based on https://packagecontrol.io/docs/syncing\nLibrary/Application Support/Sublime Text 3/Packages/User\n\n[xdg_configuration_files]\nsublime-text-3/Packages/User\n"
  },
  {
    "path": "src/mackup/applications/sublime-text.cfg",
    "content": "[application]\nname = Sublime Text\n\n[configuration_files]\n# Based on https://packagecontrol.io/docs/syncing\nLibrary/Application Support/Sublime Text/Packages/User\n\n[xdg_configuration_files]\nsublime-text/Packages/User\n"
  },
  {
    "path": "src/mackup/applications/subversion.cfg",
    "content": "[application]\nname = Subversion\n\n[configuration_files]\n.subversion\n"
  },
  {
    "path": "src/mackup/applications/superduper.cfg",
    "content": "[application]\nname = SuperDuper!\n\n[configuration_files]\nLibrary/Application Support/SuperDuper!\n"
  },
  {
    "path": "src/mackup/applications/surge.cfg",
    "content": "[application]\nname = Surge\n\n[configuration_files]\n.surge.conf\n"
  },
  {
    "path": "src/mackup/applications/swaywm.cfg",
    "content": "[application]\nname = Swaywm\n\n[xdg_configuration_files]\nsway\n"
  },
  {
    "path": "src/mackup/applications/swinsian.cfg",
    "content": "[application]\nname = Swinsian\n\n[configuration_files]\nLibrary/Application Support/Swinsian/Library.sqlite\nLibrary/Application Support/Swinsian/License.swinsianlicense\nLibrary/Preferences/com.swinsian.Swinsian.plist\nLibrary/Preferences/com.swinsian.SwinsianDesktopWindow.plist\n"
  },
  {
    "path": "src/mackup/applications/swish.cfg",
    "content": "[application]\nname = Swish\n\n[configuration_files]\nLibrary/Preferences/co.highlyopinionated.swish.plist\n"
  },
  {
    "path": "src/mackup/applications/switchhosts.cfg",
    "content": "[application]\nname = SwitchHosts\n\n[configuration_files]\n.SwitchHosts/data/collection/hosts\n"
  },
  {
    "path": "src/mackup/applications/t.cfg",
    "content": "[application]\nname = t\n\n[configuration_files]\n.trc\n"
  },
  {
    "path": "src/mackup/applications/tableplus.cfg",
    "content": "[application]\nname = TablePlus\n\n[configuration_files]\nLibrary/Application Support/com.tinyapp.TablePlus/Cache/Favorite\nLibrary/Application Support/com.tinyapp.TablePlus/Cache/History\nLibrary/Application Support/com.tinyapp.TablePlus/Data\nLibrary/Application Support/com.tinyapp.TablePlus/Plugins\nLibrary/Preferences/com.tinyapp.TablePlus.plist\n"
  },
  {
    "path": "src/mackup/applications/taskpaper.cfg",
    "content": "[application]\nname = Task Paper\n\n[configuration_files]\nLibrary/Preferences/com.hogbaysoftware.TaskPaper.mac.plist\nLibrary/Application Support/TaskPaper\n"
  },
  {
    "path": "src/mackup/applications/taskwarrior.cfg",
    "content": "[application]\nname = Taskwarrior\n\n[configuration_files]\n.taskrc\n\n## Note: taskwarrior has it's own sync feature for actual tasks:\n## http://taskwarrior.org/docs/taskserver/setup.html\n##\n## If you'd rather want to sync using mackup, uncomment the following lines.\n\n# .task\n"
  },
  {
    "path": "src/mackup/applications/teamocil.cfg",
    "content": "[application]\nname = Teamocil\n\n[configuration_files]\n.teamocil\n"
  },
  {
    "path": "src/mackup/applications/telegram_macos.cfg",
    "content": "[application]\nname = Telegram for macOS\n\n[configuration_files]\nLibrary/Preferences/ru.keepcoder.Telegram.plist\n"
  },
  {
    "path": "src/mackup/applications/terminal.cfg",
    "content": "[application]\nname = Terminal\n\n[configuration_files]\nLibrary/Preferences/com.apple.Terminal.plist\n"
  },
  {
    "path": "src/mackup/applications/terminator.cfg",
    "content": "[application]\nname = Terminator\n\n[xdg_configuration_files]\nterminator/config\n"
  },
  {
    "path": "src/mackup/applications/termite.cfg",
    "content": "[application]\nname = termite\n\n[configuration_files]\ntermite/config\n"
  },
  {
    "path": "src/mackup/applications/termux.cfg",
    "content": "[application]\nname = Configuration for Termux\n\n[configuration_files]\n.termux/colors.properties\n.termux/termux.properties\n.termux/font.ttf\n"
  },
  {
    "path": "src/mackup/applications/terraform.cfg",
    "content": "[application]\nname = Terraform\n\n[configuration_files]\n.terraformrc\n.terraform.d\n"
  },
  {
    "path": "src/mackup/applications/textexpander.cfg",
    "content": "[application]\nname = TextExpander\n\n[configuration_files]\nLibrary/Application Support/TextExpander/Settings.textexpander\nLibrary/Preferences/com.smileonmymac.textexpander.plist\n"
  },
  {
    "path": "src/mackup/applications/textmate.cfg",
    "content": "[application]\nname = TextMate\n\n[configuration_files]\nLibrary/Application Support/TextMate/Bundles\nLibrary/Application Support/TextMate/PlugIns\nLibrary/Application Support/TextMate/Pristine Copy\nLibrary/Application Support/TextMate/Managed/Bundles\nLibrary/Preferences/com.macromates.textmate.latex_config.plist\n.tm_properties\n"
  },
  {
    "path": "src/mackup/applications/textual.cfg",
    "content": "[application]\nname = Textual\n\n[configuration_files]\nLibrary/Application Support/Textual IRC\nLibrary/Preferences/com.codeux.irc.textual.plist\nLibrary/Containers/com.codeux.irc.textual/Data/Library/Preferences/com.codeux.irc.textual.plist\n"
  },
  {
    "path": "src/mackup/applications/things.cfg",
    "content": "[application]\nname = Things\n\n[configuration_files]\nLibrary/Preferences/com.culturedcode.things.plist\nLibrary/Application Support/Cultured Code/Licenses.plist\n"
  },
  {
    "path": "src/mackup/applications/tidy.cfg",
    "content": "[application]\nname = HTML Tidy\n\n[configuration_files]\n.tidyrc\n"
  },
  {
    "path": "src/mackup/applications/tig.cfg",
    "content": "[application]\nname = Tig\n\n[configuration_files]\n.tigrc\n\n[xdg_configuration_files]\ntig/config\n"
  },
  {
    "path": "src/mackup/applications/tiles.cfg",
    "content": "[application]\nname = Tiles\n\n[configuration_files]\nLibrary/Preferences/com.sempliva.Tiles.plist\n"
  },
  {
    "path": "src/mackup/applications/tilix.cfg",
    "content": "[application]\nname = tilix\n\n[configuration_files]\ntilix.dconf\n"
  },
  {
    "path": "src/mackup/applications/timeout.cfg",
    "content": "[application]\nname = TimeOut\n\n[configuration_files]\nLibrary/Group Containers/6Z7QW53WB6.com.dejal.timeout\n"
  },
  {
    "path": "src/mackup/applications/tint2.cfg",
    "content": "[application]\nname = tint2\n\n[xdg_configuration_files]\ntint2/tint2rc\n"
  },
  {
    "path": "src/mackup/applications/tinyfugue.cfg",
    "content": "[application]\nname = TinyFugue\n\n[configuration_files]\n.tfrc\n"
  },
  {
    "path": "src/mackup/applications/tmux.cfg",
    "content": "[application]\nname = Tmux\n\n[configuration_files]\n.tmux.conf\n\n[xdg_configuration_files]\ntmux/tmux.conf\n"
  },
  {
    "path": "src/mackup/applications/tmuxinator.cfg",
    "content": "[application]\nname = Tmuxinator\n\n[xdg_configuration_files]\ntmuxinator\n"
  },
  {
    "path": "src/mackup/applications/tmuxp.cfg",
    "content": "[application]\nname = tmuxp \n\n[configuration_files]\n.tmuxp\n"
  },
  {
    "path": "src/mackup/applications/todotxt-cli.cfg",
    "content": "[application]\nname = Todo.txt CLI\n\n[configuration_files]\n.todo/config\n"
  },
  {
    "path": "src/mackup/applications/toothfairy.cfg",
    "content": "[application]\nname = ToothFairy\n\n[configuration_files]\nLibrary/Application Scripts/com.robinlu.mac.Tooth-Fairy\n"
  },
  {
    "path": "src/mackup/applications/totalspaces2.cfg",
    "content": "[application]\nname = TotalSpaces2\n\n[configuration_files]\nLibrary/Preferences/com.binaryage.TotalSpaces2.plist\n"
  },
  {
    "path": "src/mackup/applications/tower-2.cfg",
    "content": "[application]\nname = Tower 2\n\n[configuration_files]\nLibrary/Application Support/com.fournova.Tower2\nLibrary/Preferences/com.fournova.Tower2.plist\n"
  },
  {
    "path": "src/mackup/applications/tower-3.cfg",
    "content": "[application]\nname = Tower 3\n\n[configuration_files]\nLibrary/Application Support/com.fournova.Tower3\nLibrary/Preferences/com.fournova.Tower3.plist\n"
  },
  {
    "path": "src/mackup/applications/tower.cfg",
    "content": "[application]\nname = Tower\n\n[configuration_files]\nLibrary/Application Support/Tower\nLibrary/Application Support/com.fournova.Tower3\nLibrary/Preferences/com.fournova.Tower.plist\nLibrary/Preferences/com.fournova.Tower3.plist\n"
  },
  {
    "path": "src/mackup/applications/transmission.cfg",
    "content": "[application]\nname = Transmission\n\n[configuration_files]\nLibrary/Preferences/org.m0k.transmission.plist\nLibrary/Application Support/Transmission/blocklists\n\n[xdg_configuration_files]\ntransmission/blocklists\ntransmission/settings.json\ntransmission/stats.json\ntransmission-daemon/blocklists\ntransmission-daemon/settings.json\ntransmission-daemon/stats.json\n"
  },
  {
    "path": "src/mackup/applications/transmit.cfg",
    "content": "[application]\nname = Transmit\n\n[configuration_files]\nLibrary/Preferences/com.panic.Transmit.plist\nLibrary/Application Support/Transmit/Metadata\nLibrary/Application Support/Transmit/Favorites\n"
  },
  {
    "path": "src/mackup/applications/tripmode.cfg",
    "content": "[application]\nname = TripMode\n\n[configuration_files]\nLibrary/Application Support/TripMode\n"
  },
  {
    "path": "src/mackup/applications/trizen.cfg",
    "content": "[application]\nname = Trizen\n\n[xdg_configuration_files]\ntrizen/trizen.conf\n"
  },
  {
    "path": "src/mackup/applications/tunnelblick.cfg",
    "content": "[application]\nname = Tunnelblick\n\n[configuration_files]\nLibrary/Application Support/Tunnelblick/Configurations\n"
  },
  {
    "path": "src/mackup/applications/tvnamer.cfg",
    "content": "[application]\nname = tvnamer\n\n[configuration_files]\n.tvnamer.json\n"
  },
  {
    "path": "src/mackup/applications/twitterrific.cfg",
    "content": "[application]\nname = Twitterrific\n\n[configuration_files]\nLibrary/Application Support/Twitterrific\n"
  },
  {
    "path": "src/mackup/applications/typinator.cfg",
    "content": "[application]\nname = Typinator\n\n[configuration_files]\nLibrary/Application Support/Typinator\nLibrary/Preferences/com.macility.typinator2.plist\n"
  },
  {
    "path": "src/mackup/applications/typora.cfg",
    "content": "[application]\nname = Typora\n\n[configuration_files]\nLibrary/Application Support/abnerworks.Typora/themes\n\n[xdg_configuration_files]\nTypora/themes\n"
  },
  {
    "path": "src/mackup/applications/ubersicht.cfg",
    "content": "[application]\nname = Ubersicht\n\n[configuration_files]\nLibrary/Application Support/Übersicht/widgets\nLibrary/Preferences/tracesOf.Uebersicht.plist\n"
  },
  {
    "path": "src/mackup/applications/ulauncher.cfg",
    "content": "[application]\nname = Ulauncher\n\n[xdg_configuration_files]\nulauncher/extensions.json\nulauncher/settings.json\nulauncher/shortcuts.json\n"
  },
  {
    "path": "src/mackup/applications/ventrilo.cfg",
    "content": "[application]\nname = Ventrilo\n\n[configuration_files]\nLibrary/Preferences/Ventrilo\n"
  },
  {
    "path": "src/mackup/applications/verdaccio.cfg",
    "content": "[application]\nname = Verdaccio\n\n[xdg_configuration_files]\nverdaccio/config.yaml\n"
  },
  {
    "path": "src/mackup/applications/versions.cfg",
    "content": "[application]\nname = Versions\n\n[configuration_files]\nLibrary/Application Support/Versions/License\nLibrary/Preferences/com.blackpixel.versions.plist\n"
  },
  {
    "path": "src/mackup/applications/vim.cfg",
    "content": "[application]\nname = Vim\n\n[configuration_files]\n.gvimrc\n.gvimrc.after\n.gvimrc.before\n.vim/autoload\n.vim/after\n.vim/bundle\n.vim/colors\n.vim/doc\n.vim/ftdetect\n.vim/ftplugin\n.vim/indent\n.vim/pack\n.vim/plugin/settings\n.vim/spell\n.vim/syntax\n.vim/vimrc\n.vimrc\n.vimrc.after\n.vimrc.before\n"
  },
  {
    "path": "src/mackup/applications/vimperator.cfg",
    "content": "[application]\nname = Vimperator\n\n[configuration_files]\n.vimperator\n.vimperatorrc\n"
  },
  {
    "path": "src/mackup/applications/vimwiki.cfg",
    "content": "[application]\nname = Vimwiki\n\n[configuration_files]\nvimwiki\n"
  },
  {
    "path": "src/mackup/applications/viscosity.cfg",
    "content": "[application]\nname = Viscosity\n\n[configuration_files]\nLibrary/Application Support/Viscosity/OpenVPN\nLibrary/Preferences/com.viscosityvpn.Viscosity.plist\n"
  },
  {
    "path": "src/mackup/applications/vlc.cfg",
    "content": "[application]\nname = VLC\n\n[configuration_files]\nLibrary/Application Support/org.videolan.vlc\nLibrary/Preferences/org.videolan.vlc\nLibrary/Preferences/org.videolan.vlc.LSSharedFileList.plist\nLibrary/Preferences/org.videolan.vlc.plist\n"
  },
  {
    "path": "src/mackup/applications/volt.cfg",
    "content": "[application]\nname = Volt\n\n[configuration_files]\nvolt/lock.json\nvolt/plugconf\nvolt/rc\n"
  },
  {
    "path": "src/mackup/applications/vs4mac.cfg",
    "content": "[application]\nname = Visual Studio for Mac\n\n[configuration_files]\nLibrary/VisualStudio\nLibrary/Preferences/VisualStudio/7.0/EditingLayout.xml\nLibrary/Preferences/VisualStudio/7.0/MonoDevelopProperties.xml\nLibrary/Preferences/VisualStudio/8.0/EditingLayout.xml\nLibrary/Preferences/VisualStudio/8.0/MonoDevelopProperties.xml\n"
  },
  {
    "path": "src/mackup/applications/vscode-insiders.cfg",
    "content": "[application]\nname = Visual Studio Code Insiders\n\n[configuration_files]\nLibrary/Application Support/Code - Insiders/User/snippets\nLibrary/Application Support/Code - Insiders/User/prompts\nLibrary/Application Support/Code - Insiders/User/keybindings.json\nLibrary/Application Support/Code - Insiders/User/settings.json\n\n[xdg_configuration_files]\nCode - Insiders/User/snippets\nCode - Insiders/User/keybindings.json\nCode - Insiders/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/vscode-oss.cfg",
    "content": "[application]\nname = Visual Studio Code Open Source Edition\n\n[configuration_files]\nLibrary/Application Support/Code - OSS/User/snippets\nLibrary/Application Support/Code - OSS/User/keybindings.json\nLibrary/Application Support/Code - OSS/User/settings.json\n\n[xdg_configuration_files]\nCode - OSS/User/snippets\nCode - OSS/User/keybindings.json\nCode - OSS/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/vscode.cfg",
    "content": "[application]\nname = Visual Studio Code\n\n[configuration_files]\nLibrary/Application Support/Code/User/snippets\nLibrary/Application Support/Code/User/prompts\nLibrary/Application Support/Code/User/keybindings.json\nLibrary/Application Support/Code/User/settings.json\n\n[xdg_configuration_files]\nCode/User/snippets\nCode/User/keybindings.json\nCode/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/vscodium.cfg",
    "content": "[application]\nname = VSCodium\n\n[configuration_files]\nLibrary/Application Support/VSCodium/User/snippets\nLibrary/Application Support/VSCodium/User/keybindings.json\nLibrary/Application Support/VSCodium/User/settings.json\n\n[xdg_configuration_files]\nVSCodium/User/snippets\nVSCodium/User/keybindings.json\nVSCodium/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/wakatime.cfg",
    "content": "[application]\nname = Wakatime\n\n[configuration_files]\n.wakatime.cfg\n"
  },
  {
    "path": "src/mackup/applications/warp.cfg",
    "content": "[application]\nname = Warp\n\n[configuration_files]\n.warp\n"
  },
  {
    "path": "src/mackup/applications/waybar.cfg",
    "content": "[application]\nname = waybar\n\n[xdg_configuration_files]\nwaybar\n"
  },
  {
    "path": "src/mackup/applications/webstorm.cfg",
    "content": "[application]\nname = WebStorm\n\n[configuration_files]\nLibrary/Application Support/JetBrains/WebStorm2020.1\nLibrary/Application Support/JetBrains/WebStorm2020.2\nLibrary/Application Support/JetBrains/WebStorm2020.3\nLibrary/Application Support/JetBrains/WebStorm2020.4\nLibrary/Application Support/JetBrains/WebStorm2021.1\nLibrary/Application Support/JetBrains/WebStorm2021.2\nLibrary/Application Support/JetBrains/WebStorm2021.3\nLibrary/Application Support/JetBrains/WebStorm2023.1\nLibrary/Application Support/JetBrains/WebStorm2023.2\nLibrary/Application Support/WebStorm\nLibrary/Application Support/WebStorm10\nLibrary/Application Support/WebStorm11\nLibrary/Application Support/WebStorm2016.1\nLibrary/Application Support/WebStorm2016.2\nLibrary/Application Support/WebStorm2016.3\nLibrary/Application Support/WebStorm2017.1\nLibrary/Application Support/WebStorm2017.2\nLibrary/Application Support/WebStorm2017.3\nLibrary/Application Support/WebStorm2018.1\nLibrary/Application Support/WebStorm2018.2\nLibrary/Application Support/WebStorm2018.3\nLibrary/Application Support/WebStorm2019.1\nLibrary/Application Support/WebStorm2019.2\nLibrary/Application Support/WebStorm2019.3\nLibrary/Application Support/WebStorm2019.4\nLibrary/Application Support/WebStorm8\nLibrary/Application Support/WebStorm9\nLibrary/Preferences/WebStorm10\nLibrary/Preferences/WebStorm11\nLibrary/Preferences/WebStorm2016.1\nLibrary/Preferences/WebStorm2016.2\nLibrary/Preferences/WebStorm2016.3\nLibrary/Preferences/WebStorm2017.1\nLibrary/Preferences/WebStorm2017.2\nLibrary/Preferences/WebStorm2017.3\nLibrary/Preferences/WebStorm2018.1\nLibrary/Preferences/WebStorm2018.2\nLibrary/Preferences/WebStorm2018.3\nLibrary/Preferences/WebStorm2019.1\nLibrary/Preferences/WebStorm2019.2\nLibrary/Preferences/WebStorm2019.3\nLibrary/Preferences/WebStorm2019.4\nLibrary/Preferences/WebStorm8\nLibrary/Preferences/WebStorm9\n"
  },
  {
    "path": "src/mackup/applications/wezterm.cfg",
    "content": "[application]\nname = WezTerm\n\n[xdg_configuration_files]\nwezterm\n\n[configuration_files]\n.wezterm.lua\n"
  },
  {
    "path": "src/mackup/applications/wget.cfg",
    "content": "[application]\nname = Wget\n\n[configuration_files]\n.wgetrc\n.wget-hsts\n"
  },
  {
    "path": "src/mackup/applications/whatsapp.cfg",
    "content": "[application]\nname = WhatsApp Web\n\n[configuration_files]\nLibrary/Application Support/WhatsApp/Preferences\nLibrary/Application Support/WhatsApp/settings.json\nLibrary/Preferences/WhatsApp-Helper.plist\nLibrary/Preferences/WhatsApp.plist\n"
  },
  {
    "path": "src/mackup/applications/windsurf.cfg",
    "content": "[application]\nname = Windsurf\n\n[configuration_files]\nLibrary/Application Support/Windsurf/User/snippets\nLibrary/Application Support/Windsurf/User/keybindings.json\nLibrary/Application Support/Windsurf/User/settings.json\n\n[xdg_configuration_files]\nWindsurf/User/snippets\nWindsurf/User/keybindings.json\nWindsurf/User/settings.json\n"
  },
  {
    "path": "src/mackup/applications/wireshark.cfg",
    "content": "[application]\nname = Wireshark 2\n\n[xdg_configuration_files]\nwireshark\n"
  },
  {
    "path": "src/mackup/applications/witch.cfg",
    "content": "[application]\nname = Witch\n\n[configuration_files]\nLibrary/Preferences/com.manytricks.Witch.plist\n"
  },
  {
    "path": "src/mackup/applications/wordgrinder.cfg",
    "content": "[application]\nname = wordgrinder\n\n[configuration_files]\n.wordgrinder.settings\n"
  },
  {
    "path": "src/mackup/applications/workrave.cfg",
    "content": "[application]\nname = Workrave\n\n[configuration_files]\n.workrave/id\n.workrave/historystats\n"
  },
  {
    "path": "src/mackup/applications/wp-cli.cfg",
    "content": "[application]\nname = WP-CLI\n\n[configuration_files]\n.wp-cli\n"
  },
  {
    "path": "src/mackup/applications/x11.cfg",
    "content": "[application]\nname = X11\n\n[configuration_files]\n.Xmodmap\n.Xresources\n.fonts\n.xinitrc\n"
  },
  {
    "path": "src/mackup/applications/xamarinstudio-5.cfg",
    "content": "[application]\nname = Xamarin Studio 5\n\n[configuration_files]\nLibrary/Preferences/XamarinStudio-5.0\n"
  },
  {
    "path": "src/mackup/applications/xbar.cfg",
    "content": "[application]\nname = xbar\n\n[configuration_files]\nLibrary/Application Support/xbar/xbar.config.json\nLibrary/Application Support/xbar/plugins\n"
  },
  {
    "path": "src/mackup/applications/xbindkeys.cfg",
    "content": "[application]\nname = xbindkeys\n\n[configuration_files]\n.xbindkeysrc\n"
  },
  {
    "path": "src/mackup/applications/xchat.cfg",
    "content": "[application]\nname = XChat\n\n[configuration_files]\n.xchat2\n"
  },
  {
    "path": "src/mackup/applications/xcode.cfg",
    "content": "[application]\nname = Xcode\n\n[configuration_files]\nLibrary/Preferences/com.apple.dt.Xcode.plist\nLibrary/Application Support/Developer/Shared/Xcode/Plug-ins\nLibrary/Developer/Xcode/UserData/CodeSnippets\nLibrary/Developer/Xcode/UserData/FontAndColorThemes\nLibrary/Developer/Xcode/UserData/KeyBindings\nLibrary/Developer/Xcode/UserData/Debugger\nLibrary/Developer/Xcode/UserData/xcdebugger\nLibrary/Developer/Xcode/UserData/SearchScopes.xcsclist\nLibrary/Developer/Xcode/Templates\n"
  },
  {
    "path": "src/mackup/applications/xee.cfg",
    "content": "[application]\nname = Xee\n\n[configuration_files]\nLibrary/Preferences/cx.c3.Xee3.plist\n"
  },
  {
    "path": "src/mackup/applications/xemacs.cfg",
    "content": "[application]\nname = XEmacs\n\n[configuration_files]\n.xemacs\n"
  },
  {
    "path": "src/mackup/applications/xld.cfg",
    "content": "[application]\nname = XLD\n\n[configuration_files]\nLibrary/Application Support/XLD\nLibrary/Preferences/jp.tmkk.XLD.plist\n"
  },
  {
    "path": "src/mackup/applications/xonsh.cfg",
    "content": "[application]\nname = Xonsh\n\n[configuration_files]\n.xonshrc\n\n[xdg_configuration_files]\nxonsh\n"
  },
  {
    "path": "src/mackup/applications/xtrafinder.cfg",
    "content": "[application]\nname = XtraFinder\n\n[configuration_files]\nLibrary/Preferences/com.trankynam.XtraFinder.plist\n"
  },
  {
    "path": "src/mackup/applications/yabai.cfg",
    "content": "[application]\nname = yabai\n\n[configuration_files]\n.yabairc\n\n[xdg_configuration_files]\nyabai/yabairc\n"
  },
  {
    "path": "src/mackup/applications/yarn.cfg",
    "content": "[application]\nname = yarn\n\n[configuration_files]\n.yarnrc\n"
  },
  {
    "path": "src/mackup/applications/yazi.cfg",
    "content": "[application]\nname = yazi\n\n[xdg_configuration_files]\nyazi/yazi.toml\nyazi/keymap.toml\nyazi/theme.toml\n"
  },
  {
    "path": "src/mackup/applications/youtube-dl.cfg",
    "content": "[application]\nname = youtube-dl\n\n[xdg_configuration_files]\nyoutube-dl/config\n"
  },
  {
    "path": "src/mackup/applications/yummyftp.cfg",
    "content": "[application]\nname = Yummy FTP\n\n[configuration_files]\nLibrary/Preferences/com.yummysoftware.yummy-ftp.plist\nLibrary/Application Support/Yummy FTP/DefaultBookmark.bkmk\nLibrary/Application Support/Yummy FTP/Bookmarks\n"
  },
  {
    "path": "src/mackup/applications/zabbix-cli.cfg",
    "content": "[application]\nname = Zabbix CLI\n\n[configuration_files]\n.zabbix-cli/zabbix-cli.conf\n"
  },
  {
    "path": "src/mackup/applications/zathura.cfg",
    "content": "[application]\nname = zathura\n\n[xdg_configuration_files]\nzathura/zathurarc\n"
  },
  {
    "path": "src/mackup/applications/zed.cfg",
    "content": "[application]\nname = Zed\n\n[xdg_configuration_files]\nzed/keymap.json\nzed/settings.json\n"
  },
  {
    "path": "src/mackup/applications/zoom.cfg",
    "content": "[application]\nname = Zoom\n\n[configuration_files]\nLibrary/Preferences/us.zoom.Transcode.plist\nLibrary/Preferences/us.zoom.xos.Hotkey.plist\nLibrary/Preferences/us.zoom.xos.plist\nLibrary/Preferences/us.zoom.ZoomAutoUpdater.plist\nLibrary/Preferences/ZoomChat.plist\n"
  },
  {
    "path": "src/mackup/applications/zoxide.cfg",
    "content": "[application]\nname = zoxide\n\n[configuration_files]\nLibrary/Application Support/zoxide\n"
  },
  {
    "path": "src/mackup/applications/zsh.cfg",
    "content": "[application]\nname = Zsh\n\n[configuration_files]\n.zshenv\n.zprofile\n.zshrc\n.zlogin\n.zlogout\n"
  },
  {
    "path": "src/mackup/appsdb.py",
    "content": "\"\"\"\nThe applications database.\n\nThe Applications Database provides an easy to use interface to load application\ndata from the Mackup Database (files).\n\"\"\"\n\nimport configparser\nimport os\nfrom typing import Union\n\nfrom .constants import APPS_DIR, CUSTOM_APPS_DIR, CUSTOM_APPS_DIR_XDG\n\n\nclass ApplicationsDatabase:\n    \"\"\"Database containing all the configured applications.\"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Create a ApplicationsDatabase instance.\"\"\"\n        # Build the dict that will contain the properties of each application\n        self.apps: dict[str, dict[str, Union[str, set[str]]]] = {}\n\n        for config_file in ApplicationsDatabase.get_config_files():\n            config: configparser.ConfigParser = configparser.ConfigParser(\n                allow_no_value=True,\n            )\n\n            # Needed to not lowercase the configuration_files in the ini files\n            config.optionxform = str  # type: ignore\n\n            if config.read(config_file):\n                # Get the filename without the directory name\n                filename: str = os.path.basename(config_file)\n                # The app name is the cfg filename with the extension\n                app_name: str = filename[: -len(\".cfg\")]\n\n                # Start building a dict for this app\n                self.apps[app_name] = {}\n\n                # Add the fancy name for the app, for display purpose\n                app_pretty_name: str = config.get(\"application\", \"name\")\n                self.apps[app_name][\"name\"] = app_pretty_name\n\n                # Add the configuration files to sync\n                config_files: set[str] = set()\n                self.apps[app_name][\"configuration_files\"] = config_files\n                if config.has_section(\"configuration_files\"):\n                    for path in config.options(\"configuration_files\"):\n                        if path.startswith(\"/\"):\n                            raise ValueError(\n                                f\"Unsupported absolute path: {path}\",\n                            )\n                        config_files.add(path)\n\n                # Add the XDG configuration files to sync\n                home: str = os.path.expanduser(\"~/\")\n                failobj: str = f\"{home}.config\"\n                xdg_config_home: str = os.environ.get(\"XDG_CONFIG_HOME\", failobj)\n                if not xdg_config_home.startswith(home):\n                    raise ValueError(\n                        f\"$XDG_CONFIG_HOME: {xdg_config_home} must be somewhere \"\n                        f\"within your home directory: {home}\",\n                    )\n                if config.has_section(\"xdg_configuration_files\"):\n                    for path in config.options(\"xdg_configuration_files\"):\n                        if path.startswith(\"/\"):\n                            raise ValueError(\n                                f\"Unsupported absolute path: {path}\",\n                            )\n                        xdg_path = os.path.join(xdg_config_home, path)\n                        xdg_path = xdg_path.replace(home, \"\")\n                        config_files.add(xdg_path)\n\n    @staticmethod\n    def get_config_files() -> set[str]:\n        \"\"\"\n        Return the application configuration files.\n\n        Return a list of configuration files describing the apps supported by\n        Mackup. The files returned are absolute full path to those files.\n        e.g. /usr/lib/mackup/applications/bash.cfg\n\n        Only one config file per application should be returned, custom config\n        having a priority over stock config. Legacy custom apps directory\n        (~/.mackup/) takes priority over XDG location.\n\n        Returns:\n            set of strings.\n        \"\"\"\n        # Configure the config parser\n        apps_dir: str = os.path.join(\n            os.path.dirname(os.path.realpath(__file__)), APPS_DIR,\n        )\n\n        # Legacy custom apps directory: ~/.mackup/\n        legacy_custom_apps_dir: str = os.path.join(os.environ[\"HOME\"], CUSTOM_APPS_DIR)\n\n        # XDG custom apps directory: $XDG_CONFIG_HOME/mackup/applications/\n        xdg_config_home: str = os.environ.get(\n            \"XDG_CONFIG_HOME\", os.path.join(os.environ[\"HOME\"], \".config\"),\n        )\n        xdg_custom_apps_dir: str = os.path.join(xdg_config_home, CUSTOM_APPS_DIR_XDG)\n\n        # List of stock application config files\n        config_files: set[str] = set()\n\n        # Temp list of user added app config file names\n        custom_files: set[str] = set()\n\n        # Get the list of custom application config files from legacy directory first\n        # (legacy takes priority over XDG)\n        if os.path.isdir(legacy_custom_apps_dir):\n            for filename in os.listdir(legacy_custom_apps_dir):\n                if filename.endswith(\".cfg\"):\n                    config_files.add(os.path.join(legacy_custom_apps_dir, filename))\n                    custom_files.add(filename)\n\n        # Get custom application config files from XDG directory\n        # (only if not already in legacy directory)\n        if os.path.isdir(xdg_custom_apps_dir):\n            for filename in os.listdir(xdg_custom_apps_dir):\n                if filename.endswith(\".cfg\") and filename not in custom_files:\n                    config_files.add(os.path.join(xdg_custom_apps_dir, filename))\n                    custom_files.add(filename)\n\n        # Add the default provided app config files, but only if those are not\n        # customized, as we don't want to overwrite custom app config.\n        for filename in os.listdir(apps_dir):\n            if filename.endswith(\".cfg\") and filename not in custom_files:\n                config_files.add(os.path.join(apps_dir, filename))\n\n        return config_files\n\n    def get_name(self, name: str) -> str:\n        \"\"\"\n        Return the fancy name of an application.\n\n        Args:\n            name (str)\n\n        Returns:\n            str\n        \"\"\"\n        value = self.apps[name][\"name\"]\n        assert isinstance(value, str)\n        return value\n\n    def get_files(self, name: str) -> set[str]:\n        \"\"\"\n        Return the list of config files of an application.\n\n        Args:\n            name (str)\n\n        Returns:\n            set of str.\n        \"\"\"\n        value = self.apps[name][\"configuration_files\"]\n        assert isinstance(value, set)\n        return value\n\n    def get_app_names(self) -> set[str]:\n        \"\"\"\n        Return application names.\n\n        Return the list of application names that are available in the\n        database.\n\n        Returns:\n            set of str.\n        \"\"\"\n        app_names: set[str] = set()\n        for name in self.apps:\n            app_names.add(name)\n\n        return app_names\n\n    def get_pretty_app_names(self) -> set[str]:\n        \"\"\"\n        Return the list of pretty app names that are available in the database.\n\n        Returns:\n            set of str.\n        \"\"\"\n        pretty_app_names: set[str] = set()\n        for app_name in self.get_app_names():\n            pretty_app_names.add(self.get_name(app_name))\n\n        return pretty_app_names\n"
  },
  {
    "path": "src/mackup/config.py",
    "content": "\"\"\"Package used to manage the .mackup.cfg config file.\"\"\"\n\nimport configparser\nimport os\nimport os.path\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom .constants import (\n    CUSTOM_APPS_DIR,\n    CUSTOM_APPS_DIR_XDG,\n    ENGINE_DROPBOX,\n    ENGINE_FS,\n    ENGINE_GDRIVE,\n    ENGINE_ICLOUD,\n    MACKUP_BACKUP_PATH,\n    MACKUP_CONFIG_FILE,\n)\nfrom .utils import (\n    error,\n    get_dropbox_folder_location,\n    get_google_drive_folder_location,\n    get_icloud_folder_location,\n)\n\n\nclass Config:\n    \"\"\"The Mackup Config class.\"\"\"\n\n    def __init__(self, filename: Optional[str] = None) -> None:\n        \"\"\"\n        Create a Config instance.\n\n        Args:\n            filename (str): Optional filename of the config file. If empty,\n                            defaults to MACKUP_CONFIG_FILE\n        \"\"\"\n        assert isinstance(filename, str) or filename is None\n\n        # Initialize the parser\n        self._parser = self._setup_parser(filename)\n\n        # Do we have an old config file?\n        self._warn_on_old_config()\n\n        # Get the storage engine\n        self._engine = self._parse_engine()\n\n        # Get the path where the Mackup folder is\n        self._path = self._parse_path()\n\n        # Get the directory replacing 'Mackup', if any\n        self._directory = self._parse_directory()\n\n        # Get the list of apps to ignore\n        self._apps_to_ignore = self._parse_apps_to_ignore()\n\n        # Get the list of apps to allow\n        self._apps_to_sync = self._parse_apps_to_sync()\n\n    @property\n    def engine(self) -> str:\n        \"\"\"\n        The engine used by the storage.\n\n        ENGINE_DROPBOX, ENGINE_GDRIVE, ENGINE_ICLOUD or ENGINE_FS.\n\n        Returns:\n            str\n        \"\"\"\n        return str(self._engine)\n\n    @property\n    def path(self) -> str:\n        \"\"\"\n        Path to the Mackup configuration files.\n\n        The path to the directory where Mackup is gonna create and store his\n        directory.\n\n        Returns:\n            str\n        \"\"\"\n        return str(self._path)\n\n    @property\n    def directory(self) -> str:\n        \"\"\"\n        The name of the Mackup directory, named Mackup by default.\n\n        Returns:\n            str\n        \"\"\"\n        return str(self._directory)\n\n    @property\n    def fullpath(self) -> str:\n        \"\"\"\n        Full path to the Mackup configuration files.\n\n        The full path to the directory when Mackup is storing the configuration\n        files.\n\n        Returns:\n            str\n        \"\"\"\n        return str(os.path.join(self.path, self.directory))\n\n    @property\n    def apps_to_ignore(self) -> set[str]:\n        \"\"\"\n        Get the list of applications ignored in the config file.\n\n        Returns:\n            set. Set of application names to ignore, lowercase\n        \"\"\"\n        return set(self._apps_to_ignore)\n\n    @property\n    def apps_to_sync(self) -> set[str]:\n        \"\"\"\n        Get the list of applications allowed in the config file.\n\n        Returns:\n            set. Set of application names to allow, lowercase\n        \"\"\"\n        return set(self._apps_to_sync)\n\n    def _setup_parser(\n        self, filename: Optional[str] = None,\n    ) -> configparser.ConfigParser:\n        \"\"\"\n        Configure the ConfigParser instance the way we want it.\n\n        Args:\n            filename (str) or None\n\n        Returns:\n            ConfigParser\n        \"\"\"\n        assert isinstance(filename, str) or filename is None\n\n        parser = configparser.ConfigParser(\n            allow_no_value=True, inline_comment_prefixes=(\";\", \"#\"),\n        )\n        parser.read(self._best_config_path(filename))\n\n        return parser\n\n    def _best_config_path(self, filename: Optional[str] = None) -> str:\n        \"\"\"\n        If no filename is provided, we try to find one in according to the following\n        order, note that we will always check the original default of `~/.mackup.cfg`\n        first before checking the other options:\n\n        - ~/.mackup.cfg\n        - $MACKUP_CONFIG\n        - $XDG_CONFIG_HOME/mackup/mackup.cfg\n        - ~/.config/mackup/mackup.cfg\n\n        if none of these files exist, we create ~/.mackup.cfg\n\n        Args:\n            filename (str or None, optional): Optional override for the config\n                file path. Can be absolute or relative to home directory.\n                Defaults to None.\n\n        Returns:\n            str: the absolute path to the config file\n        \"\"\"\n        assert isinstance(filename, str) or filename is None\n\n        # If we are not overriding the config filename\n        config_path: Path\n        if not filename:\n            default = Path.home() / MACKUP_CONFIG_FILE\n            search_paths = [\n                # 1. the default config file is ~/.mackup.cfg\n                default,\n                # 2. check for the MACKUP_CONFIG envvar\n                Path(os.environ.get(\"MACKUP_CONFIG\", \"\")).expanduser(),\n                # 3. check for a config file in the XDG_CONFIG_HOME directory\n                (\n                    Path(os.environ.get(\"XDG_CONFIG_HOME\", \"~/.config\")).expanduser()\n                    / \"mackup\"\n                    / MACKUP_CONFIG_FILE.lstrip(\".\")\n                ),\n            ]\n            config_path = next((p for p in search_paths if p.is_file()), default)\n        else:\n            # Support both absolute and relative paths\n            config_path = Path(filename).expanduser()\n            if not config_path.is_absolute():\n                config_path = Path.home() / filename\n\n            # When explicitly specified, check that the file exists\n            if not config_path.is_file():\n                error(\n                    f\"The config file '{config_path}' does not exist. Aborting.\",\n                )\n\n        try:\n            # Make sure the config file is in the home directory\n            config_path.relative_to(Path.home())\n        except ValueError:\n            error(\n                f\"The config file '{config_path}' is not in your home \"\n                \"directory. Aborting.\",\n            )\n\n        # return the absolute path to the config file\n        return str(config_path.absolute())\n\n    def _warn_on_old_config(self) -> None:\n        \"\"\"Warn the user if an old config format is detected.\"\"\"\n        # Is an old section in the config file?\n        old_sections = [\"Allowed Applications\", \"Ignored Applications\"]\n        for old_section in old_sections:\n            if self._parser.has_section(old_section):\n                error(\n                    \"Old config file detected. Aborting.\\n\"\n                    \"\\n\"\n                    \"An old section (e.g. [Allowed Applications]\"\n                    \" or [Ignored Applications] has been detected\"\n                    f\" in your {MACKUP_CONFIG_FILE} file.\\n\"\n                    \"I'd rather do nothing than do something you\"\n                    \" do not want me to do.\\n\"\n                    \"\\n\"\n                    \"Please read the up to date documentation on\"\n                    \" <https://github.com/lra/mackup> and migrate\"\n                    \" your configuration file.\",\n                )\n\n    def _parse_engine(self) -> str:\n        \"\"\"\n        Parse the storage engine in the config.\n\n        Returns:\n            str\n        \"\"\"\n        if self._parser.has_option(\"storage\", \"engine\"):\n            engine = str(self._parser.get(\"storage\", \"engine\"))\n        else:\n            engine = ENGINE_DROPBOX\n\n        assert isinstance(engine, str)\n\n        if engine not in [\n            ENGINE_DROPBOX,\n            ENGINE_GDRIVE,\n            ENGINE_ICLOUD,\n            ENGINE_FS,\n        ]:\n            raise ConfigError(f\"Unknown storage engine: {engine}\")\n\n        return str(engine)\n\n    def _parse_path(self) -> str:\n        \"\"\"\n        Parse the storage path in the config.\n\n        Returns:\n            str\n        \"\"\"\n        if self.engine == ENGINE_DROPBOX:\n            path = get_dropbox_folder_location()\n        elif self.engine == ENGINE_GDRIVE:\n            path = get_google_drive_folder_location()\n        elif self.engine == ENGINE_ICLOUD:\n            path = get_icloud_folder_location()\n        elif self.engine == ENGINE_FS:\n            if self._parser.has_option(\"storage\", \"path\"):\n                cfg_path = self._parser.get(\"storage\", \"path\")\n                path = os.path.join(os.environ[\"HOME\"], cfg_path)\n            else:\n                raise ConfigError(\n                    \"The required 'path' can't be found while\"\n                    \" the 'file_system' engine is used.\",\n                )\n\n        return str(path)\n\n    def _parse_directory(self) -> str:\n        \"\"\"\n        Parse the storage directory in the config.\n\n        Returns:\n            str\n        \"\"\"\n        if self._parser.has_option(\"storage\", \"directory\"):\n            directory = self._parser.get(\"storage\", \"directory\")\n            # Don't allow CUSTOM_APPS_DIR or XDG custom apps dir as a storage directory\n            if directory == CUSTOM_APPS_DIR:\n                raise ConfigError(\n                    f\"{CUSTOM_APPS_DIR} cannot be used as a storage directory.\",\n                )\n            xdg_custom_apps_dir = os.path.join(\".config\", CUSTOM_APPS_DIR_XDG)\n            in_xdg_dir = directory in (CUSTOM_APPS_DIR_XDG, xdg_custom_apps_dir)\n            if in_xdg_dir or directory.endswith(\"/\" + xdg_custom_apps_dir):\n                raise ConfigError(\n                    f\"{CUSTOM_APPS_DIR_XDG} cannot be used as a storage directory.\",\n                )\n        else:\n            directory = MACKUP_BACKUP_PATH\n\n        return str(directory)\n\n    def _parse_apps_to_ignore(self) -> set[str]:\n        \"\"\"\n        Parse the applications to ignore in the config.\n\n        Returns:\n            set\n        \"\"\"\n        # We ignore nothing by default\n        apps_to_ignore = set()\n\n        # Is the \"[applications_to_ignore]\" in the cfg file?\n        section_title = \"applications_to_ignore\"\n        if self._parser.has_section(section_title):\n            apps_to_ignore = set(self._parser.options(section_title))\n\n        return apps_to_ignore\n\n    def _parse_apps_to_sync(self) -> set[str]:\n        \"\"\"\n        Parse the applications to backup in the config.\n\n        Returns:\n            set\n        \"\"\"\n        # We allow nothing by default\n        apps_to_sync = set()\n\n        # Is the \"[applications_to_sync]\" section in the cfg file?\n        section_title = \"applications_to_sync\"\n        if self._parser.has_section(section_title):\n            apps_to_sync = set(self._parser.options(section_title))\n\n        return apps_to_sync\n\n\nclass ConfigError(Exception):\n    \"\"\"Exception used for handle errors in the configuration.\"\"\"\n"
  },
  {
    "path": "src/mackup/constants.py",
    "content": "\"\"\"Constants used in Mackup.\"\"\"\n\nfrom importlib.metadata import PackageNotFoundError, version\n\n# Support platforms\nPLATFORM_DARWIN: str = \"Darwin\"\nPLATFORM_LINUX: str = \"Linux\"\n\n# Directory containing the application configs\nAPPS_DIR: str = \"applications\"\n\n# Mackup application name\nMACKUP_APP_NAME: str = \"mackup\"\n\n# Default Mackup backup path where it stores its files in Dropbox\nMACKUP_BACKUP_PATH: str = \"Mackup\"\n\n# Mackup config file\nMACKUP_CONFIG_FILE: str = \".mackup.cfg\"\n\n\ndef _get_version() -> str:\n    \"\"\"Return package version, or a safe fallback when metadata is unavailable.\"\"\"\n    try:\n        return version(MACKUP_APP_NAME)\n    except PackageNotFoundError:\n        return \"unknown\"\n\n\n# Current version\nVERSION: str = _get_version()\n\n# Directory that can contains user defined app configs\nCUSTOM_APPS_DIR: str = \".mackup\"\n\n# XDG-compliant directory for user defined app configs (relative to XDG_CONFIG_HOME)\nCUSTOM_APPS_DIR_XDG: str = \"mackup/applications\"\n\n# Supported engines\nENGINE_DROPBOX: str = \"dropbox\"\nENGINE_FS: str = \"file_system\"\nENGINE_GDRIVE: str = \"google_drive\"\nENGINE_ICLOUD: str = \"icloud\"\n\nDOCUMENTATION_URL: str = \"https://github.com/lra/mackup/blob/master/doc/README.md\"\n\n# Error message displayed when mackup can't find the storage specified\n# in the config (or the default one).\nERROR_UNABLE_TO_FIND_STORAGE: str = (\n    \"Unable to find your {provider} =(\\n\"\n    f\"If this is the first time you use {MACKUP_APP_NAME}, you may want \"\n    \"to use another provider.\\n\"\n    \"Take a look at the documentation [1] to know more about \"\n    \"how to configure mackup.\\n\\n\"\n    f\"[1]: {DOCUMENTATION_URL}\"\n)\n"
  },
  {
    "path": "src/mackup/mackup.py",
    "content": "\"\"\"\nThe Mackup Class.\n\nThe Mackup class is keeping all the state that Mackup needs to keep during its\nruntime. It also provides easy to use interface that is used by the Mackup UI.\nThe only UI for now is the command line.\n\"\"\"\n\nimport os\nimport os.path\nimport shutil\nimport tempfile\nfrom typing import Optional\n\nfrom . import appsdb, config, utils\n\n\nclass Mackup:\n    \"\"\"Main Mackup class.\"\"\"\n\n    def __init__(self, config_file: Optional[str] = None) -> None:\n        \"\"\"Mackup Constructor.\"\"\"\n        self._config: config.Config = config.Config(config_file)\n\n        self.mackup_folder: str = self._config.fullpath\n        self.temp_folder: str = tempfile.mkdtemp(prefix=\"mackup_tmp_\")\n\n    def check_for_usable_environment(self) -> None:\n        \"\"\"Check if the current env is usable and has everything's required.\"\"\"\n\n        # Allow only explicit superuser usage\n        if os.geteuid() == 0 and not utils.CAN_RUN_AS_ROOT:\n            utils.error(\n                \"Running Mackup as superuser can be dangerous.\"\n                \" Don't do it unless you know what you're doing!\"\n                \" Run mackup --help for guidance.\",\n            )\n\n        # Do we have a folder set to save Mackup content into?\n        if not os.path.isdir(self._config.path):\n            utils.error(\n                f\"Unable to find the storage folder: {self._config.path}\",\n            )\n\n        # Is Sublime Text running?\n        # if is_process_running('Sublime Text'):\n        #    error(\"Sublime Text is running. It is known to cause problems\"\n        #          \" when Sublime Text is running while I backup or restore\"\n        #          \" its configuration files. Please close Sublime Text and\"\n        #          \" run me again.\")\n\n    def check_for_usable_backup_env(self) -> None:\n        \"\"\"Check if the current env can be used to back up files.\"\"\"\n        self.check_for_usable_environment()\n        self.create_mackup_home()\n\n    def check_for_usable_restore_env(self) -> None:\n        \"\"\"Check if the current env can be used to restore files.\"\"\"\n        self.check_for_usable_environment()\n\n        if not os.path.isdir(self.mackup_folder):\n            utils.error(\n                f\"Unable to find the Mackup folder: {self.mackup_folder}\\n\"\n                \"You might want to back up some files or get your\"\n                \" storage directory synced first.\",\n            )\n\n    def clean_temp_folder(self) -> None:\n        \"\"\"Delete the temp folder and files created while running.\"\"\"\n        shutil.rmtree(self.temp_folder)\n\n    def create_mackup_home(self) -> None:\n        \"\"\"If the Mackup home folder does not exist, create it.\"\"\"\n        if not os.path.isdir(self.mackup_folder):\n            if utils.confirm(\n                \"Mackup needs a directory to store your\"\n                \" configuration files\\n\"\n                f\"Do you want to create it now? <{self.mackup_folder}>\",\n            ):\n                os.makedirs(self.mackup_folder)\n            else:\n                utils.error(\"Mackup can't do anything without a home =(\")\n\n    def get_apps_to_backup(self) -> set[str]:\n        \"\"\"\n        Get the list of applications that should be backed up by Mackup.\n\n        It's the list of allowed apps minus the list of ignored apps.\n\n        Returns:\n            (set) List of application names to back up\n        \"\"\"\n        # Instantiate the app db\n        app_db: appsdb.ApplicationsDatabase = appsdb.ApplicationsDatabase()\n\n        # If a list of apps to sync is specify, we only allow those\n        # Or we allow every supported app by default\n        apps_to_backup: set[str] = self._config.apps_to_sync or app_db.get_app_names()\n\n        # Remove the specified apps to ignore\n        for app_name in self._config.apps_to_ignore:\n            apps_to_backup.discard(app_name)\n\n        return apps_to_backup\n"
  },
  {
    "path": "src/mackup/main.py",
    "content": "\"\"\"Mackup.\n\nKeep your application settings in sync.\nCopyright (C) 2013-2025 Laurent Raufaste <http://glop.org/>\n\nUsage:\n  mackup [options] list\n  mackup [options] show <application>\n  mackup [options] backup\n  mackup [options] restore\n  mackup [options] link install\n  mackup [options] link\n  mackup [options] link uninstall\n  mackup (-h | --help)\n\nOptions:\n  -h --help                 Show this screen.\n  -f --force                Force every question asked to be answered with \"Yes\".\n  --force-no                Force every question asked to be answered with \"No\".\n  -r --root                 Allow mackup to be run as superuser.\n  -n --dry-run              Show steps without executing.\n  -v --verbose              Show additional details.\n  -c --config-file=<path>   Specify custom config file path.\n  --version                 Show version.\n\nModes of action:\n - mackup list: display a list of all supported applications.\n - mackup show: display the details for a supported application.\n - mackup backup: copy local config files in the configured remote folder.\n - mackup restore: copy config files from the configured remote folder locally.\n - mackup link install: moves local config files in remote folder, and links.\n - mackup link: links local config files from the remote folder.\n - mackup link uninstall: removes the links and copy config files locally.\n\nBy default, Mackup syncs all application data via\nDropbox, but may be configured to exclude applications or use a different\nbackend with a .mackup.cfg file.\n\nSee https://github.com/lra/mackup/tree/master/doc for more information.\n\n\"\"\"\n\nimport sys\nfrom typing import Any, Optional\n\nfrom docopt import docopt\n\nfrom . import utils\nfrom .application import ApplicationProfile\nfrom .appsdb import ApplicationsDatabase\nfrom .constants import MACKUP_APP_NAME, VERSION\nfrom .mackup import Mackup\n\n\nclass ColorFormatCodes:\n    BLUE = \"\\033[34m\"\n    BOLD = \"\\033[1m\"\n    NORMAL = \"\\033[0m\"\n\n\ndef header(text: str) -> str:\n    return ColorFormatCodes.BLUE + text + ColorFormatCodes.NORMAL\n\n\ndef bold(text: str) -> str:\n    return ColorFormatCodes.BOLD + text + ColorFormatCodes.NORMAL\n\n\ndef main() -> None:\n    \"\"\"Main function.\"\"\"\n    # Get the command line arg\n    docstring = __doc__\n    if not docstring:\n        sys.exit(\n            \"Usage information is not available because __doc__ is None. \"\n            \"This can happen when running Python with optimizations (python -OO). \"\n            \"Please run Mackup without -OO to use the command-line interface.\",\n        )\n    assert docstring is not None  # for type narrowing after sys.exit\n\n    args: dict[str, Any] = docopt(docstring, version=f\"Mackup {VERSION}\")\n\n    if args[\"--force\"] and args[\"--force-no\"]:\n        sys.exit(\"Options --force and --force-no are mutually exclusive.\")\n\n    config_file: Optional[str] = args.get(\"--config-file\")\n    mckp: Mackup = Mackup(config_file)\n    app_db: ApplicationsDatabase = ApplicationsDatabase()\n\n    def print_app_header(app_name: str) -> None:\n        if verbose:\n            header_str = header(\"---\")\n            print(f\"\\n{header_str} {bold(app_name)} {header_str}\")\n\n    # If we want to answer mackup with \"yes\" for each question\n    if args[\"--force\"]:\n        utils.FORCE_YES = True\n\n    # If we want to answer mackup with \"no\" for each question\n    if args[\"--force-no\"]:\n        utils.FORCE_NO = True\n\n    # Allow mackup to be run as root\n    if args[\"--root\"]:\n        utils.CAN_RUN_AS_ROOT = True\n\n    dry_run: bool = args[\"--dry-run\"]\n\n    verbose: bool = args[\"--verbose\"]\n\n    # mackup list\n    if args[\"list\"]:\n        # Display the list of supported applications\n        mckp.check_for_usable_environment()\n        output: str = \"Supported applications:\\n\"\n        for app_name in sorted(app_db.get_app_names()):\n            output += f\" - {app_name}\\n\"\n        output += \"\\n\"\n        output += (\n            f\"{len(app_db.get_app_names())} applications supported in \"\n            f\"Mackup v{VERSION}\"\n        )\n        print(output)\n\n    # mackup show <application>\n    elif args[\"show\"]:\n        mckp.check_for_usable_environment()\n        requested_app_name: str = args[\"<application>\"]\n\n        # Make sure the app exists\n        if requested_app_name not in app_db.get_app_names():\n            sys.exit(f\"Unsupported application: {requested_app_name}\")\n        print(f\"Name: {app_db.get_name(requested_app_name)}\")\n        print(\"Configuration files:\")\n        for file in app_db.get_files(requested_app_name):\n            print(f\" - {file}\")\n\n    # mackup backup\n    elif args[\"backup\"]:\n        mckp.check_for_usable_backup_env()\n\n        # Create a backup of the files of each application\n        for app_name in sorted(mckp.get_apps_to_backup()):\n            app: ApplicationProfile = ApplicationProfile(\n                mckp, app_db.get_files(app_name), dry_run, verbose,\n            )\n            print_app_header(app_name)\n            app.copy_files_to_mackup_folder()\n\n    # mackup restore\n    elif args[\"restore\"]:\n        mckp.check_for_usable_restore_env()\n\n        # Recover a backup of the files of each application\n        for app_name in sorted(mckp.get_apps_to_backup()):\n            app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose)\n            print_app_header(app_name)\n            app.copy_files_from_mackup_folder()\n\n    # mackup link install\n    elif args[\"link\"] and args[\"install\"]:\n        # Check the env where the command is being run\n        mckp.check_for_usable_backup_env()\n\n        # Create a link for each application\n        for app_name in sorted(mckp.get_apps_to_backup()):\n            app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose)\n            print_app_header(app_name)\n            app.link_install()\n\n    # mackup link uninstall\n    elif args[\"link\"] and args[\"uninstall\"]:\n        # Check the env where the command is being run\n        mckp.check_for_usable_restore_env()\n\n        if dry_run or (\n            utils.confirm(\n                \"You are going to uninstall Mackup.\\n\"\n                \"Every configuration file, setting and dotfile\"\n                \" managed by Mackup will be unlinked and copied back\"\n                \" to their original place, in your home folder.\\n\"\n                \"Are you sure?\",\n            )\n        ):\n            # Uninstall the apps except Mackup, which we'll uninstall last, to\n            # keep the settings as long as possible\n            app_names = mckp.get_apps_to_backup()\n            app_names.discard(MACKUP_APP_NAME)\n\n            for app_name in sorted(app_names):\n                app = ApplicationProfile(\n                    mckp, app_db.get_files(app_name), dry_run, verbose,\n                )\n                print_app_header(app_name)\n                app.link_uninstall()\n\n            # Restore the Mackup config before any other config, as we might\n            # need it to know about custom settings\n            mackup_app = ApplicationProfile(\n                mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose,\n            )\n            mackup_app.link_uninstall()\n\n            # Delete the Mackup folder in Dropbox\n            # Don't delete this as there might be other Macs that aren't\n            # uninstalled yet\n            # delete(mckp.mackup_folder)\n\n            print(\n                \"\\n\"\n                \"All your files have been put back into place. You can now\"\n                \" safely uninstall Mackup.\\n\"\n                \"\\n\"\n                \"Thanks for using Mackup!\",\n            )\n\n    # mackup link\n    elif args[\"link\"]:\n        # Check the env where the command is being run\n        mckp.check_for_usable_restore_env()\n\n        # Restore the Mackup config before any other config, as we might need\n        # it to know about custom settings\n        mackup_app = ApplicationProfile(\n            mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose,\n        )\n        print_app_header(MACKUP_APP_NAME)\n        mackup_app.link()\n\n        # Initialize again the apps db, as the Mackup config might have changed\n        # it\n        mckp = Mackup(config_file)\n        app_db = ApplicationsDatabase()\n\n        # Restore the rest of the app configs, using the restored Mackup config\n        app_names = mckp.get_apps_to_backup()\n        # Mackup has already been done\n        app_names.discard(MACKUP_APP_NAME)\n\n        for app_name in sorted(app_names):\n            app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose)\n            print_app_header(app_name)\n            app.link()\n\n    # Delete the tmp folder\n    mckp.clean_temp_folder()\n"
  },
  {
    "path": "src/mackup/utils.py",
    "content": "\"\"\"System static utilities being used by the modules.\"\"\"\n\nimport base64\nimport binascii\nimport os\nimport platform\nimport shutil\nimport sqlite3\nimport stat\nimport subprocess\nimport sys\nfrom typing import NoReturn, Optional\n\nfrom . import constants\n\n# Flag that controls how user confirmation works.\n# If True, the user wants to say \"yes\" to everything.\nFORCE_YES: bool = False\n# If True, the user wants to say \"no\" to everything.\nFORCE_NO: bool = False\n\n# Flag that control if mackup can be run as root\nCAN_RUN_AS_ROOT: bool = False\n\n\ndef confirm(question: str) -> bool:\n    \"\"\"\n    Ask the user if he really wants something to happen.\n\n    Args:\n        question(str): What can happen\n\n    Returns:\n        (boolean): Confirmed or not\n    \"\"\"\n    if FORCE_YES:\n        return True\n    if FORCE_NO:\n        return False\n\n    while True:\n        answer: str = input(question + \" <Yes|No> \").lower()\n\n        if answer in {\"yes\", \"y\"}:\n            confirmed: bool = True\n            break\n        if answer in {\"no\", \"n\"}:\n            confirmed = False\n            break\n\n    return confirmed\n\n\ndef delete(filepath: str) -> None:\n    \"\"\"\n    Delete the given file, directory or link.\n\n    It Should support undelete later on.\n\n    Args:\n        filepath (str): Absolute full path to a file. e.g. /path/to/file\n    \"\"\"\n    # Some files have ACLs, let's remove them recursively\n    remove_acl(filepath)\n\n    # Some files have immutable attributes, let's remove them recursively\n    remove_immutable_attribute(filepath)\n\n    # Finally remove the files and folders\n    if os.path.isfile(filepath) or os.path.islink(filepath):\n        os.remove(filepath)\n    elif os.path.isdir(filepath):\n        shutil.rmtree(filepath)\n\n\ndef copy(src: str, dst: str) -> None:\n    \"\"\"\n    Copy a file or a folder (recursively) from src to dst.\n\n    For the sake of simplicity, both src and dst must be absolute path and must\n    include the filename of the file or folder.\n    Also do not include any trailing slash.\n\n    e.g. copy('/path/to/src_file', '/path/to/dst_file')\n    or copy('/path/to/src_folder', '/path/to/dst_folder')\n\n    But not: copy('/path/to/src_file', 'path/to/')\n    or copy('/path/to/src_folder/', '/path/to/dst_folder')\n\n    Args:\n        src (str): Source file or folder\n        dst (str): Destination file or folder\n    \"\"\"\n    assert isinstance(src, str)\n    assert os.path.exists(src)\n    assert isinstance(dst, str)\n\n    # Create the path to the dst file if it does not exist\n    abs_path = os.path.dirname(os.path.abspath(dst))\n    if not os.path.isdir(abs_path):\n        os.makedirs(abs_path)\n\n    # We need to copy a single file\n    if os.path.isfile(src):\n        # Copy the src file to dst\n        shutil.copy(src, dst)\n\n    # We need to copy a whole folder\n    elif os.path.isdir(src):\n        shutil.copytree(src, dst, dirs_exist_ok=True, copy_function=shutil.copy)\n\n    # What the heck is this?\n    else:\n        raise ValueError(f\"Unsupported file: {src}\")\n\n    # Set the good mode to the file or folder recursively\n    chmod(dst)\n\n\ndef link(target: str, link_to: str) -> None:\n    \"\"\"\n    Create a link to a target file or a folder.\n\n    For the sake of simplicity, both target and link_to must be absolute path and must\n    include the filename of the file or folder.\n    Also do not include any trailing slash.\n\n    e.g. link('/path/to/file', '/path/to/link')\n\n    But not: link('/path/to/file', 'path/to/')\n    or link('/path/to/folder/', '/path/to/link')\n\n    Args:\n        target (str): file or folder the link will point to\n        link_to (str): Link to create\n    \"\"\"\n    assert isinstance(target, str)\n    assert os.path.exists(target)\n    assert isinstance(link_to, str)\n\n    # Create the path to the link if it does not exist\n    abs_path = os.path.dirname(os.path.abspath(link_to))\n    if not os.path.isdir(abs_path):\n        os.makedirs(abs_path)\n\n    # Make sure the file or folder recursively has the good mode\n    chmod(target)\n\n    # Create the link to target\n    os.symlink(target, link_to)\n\n\ndef chmod(target: str) -> None:\n    \"\"\"\n    Recursively set the chmod for files to 0600 and 0700 for folders.\n\n    It's ok unless we need something more specific.\n\n    Args:\n        target (str): Root file or folder\n    \"\"\"\n    assert isinstance(target, str)\n    assert os.path.exists(target)\n\n    file_mode = stat.S_IRUSR | stat.S_IWUSR\n    folder_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR\n\n    # Remove the immutable attribute recursively if there is one\n    remove_immutable_attribute(target)\n\n    if os.path.isfile(target):\n        os.chmod(target, file_mode)\n\n    elif os.path.isdir(target):\n        # chmod the root item\n        os.chmod(target, folder_mode)\n\n        # chmod recursively in the folder it it's one\n        for root, dirs, files in os.walk(target):\n            for cur_dir in dirs:\n                os.chmod(os.path.join(root, cur_dir), folder_mode)\n            for cur_file in files:\n                os.chmod(os.path.join(root, cur_file), file_mode)\n\n    else:\n        raise ValueError(f\"Unsupported file type: {target}\")\n\n\ndef error(message: str) -> NoReturn:\n    \"\"\"\n    Throw an error with the given message and immediately quit.\n\n    Args:\n        message(str): The message to display.\n    \"\"\"\n    fail: str = \"\\033[91m\"\n    end: str = \"\\033[0m\"\n    sys.exit(fail + f\"Error: {message}\" + end)\n\n\ndef get_dropbox_folder_location() -> str:\n    \"\"\"\n    Try to locate the Dropbox folder.\n\n    Returns:\n        (str) Full path to the current Dropbox folder\n    \"\"\"\n    host_db_path = os.path.join(os.environ[\"HOME\"], \".dropbox/host.db\")\n    min_host_db_fields = 2\n    try:\n        with open(host_db_path) as f_hostdb:\n            data = f_hostdb.read().split()\n        if len(data) < min_host_db_fields:\n            raise ValueError(\"Malformed Dropbox host.db\")\n        dropbox_home = base64.b64decode(data[1], validate=True).decode()\n    except (\n        OSError,\n        ValueError,\n        binascii.Error,\n        UnicodeEncodeError,\n        UnicodeDecodeError,\n    ):\n        error(constants.ERROR_UNABLE_TO_FIND_STORAGE.format(provider=\"Dropbox install\"))\n\n    return dropbox_home\n\n\ndef get_google_drive_folder_location() -> str:\n    \"\"\"\n    Try to locate the Google Drive folder.\n\n    Returns:\n        (str) Full path to the current Google Drive folder\n    \"\"\"\n    gdrive_db_path = \"Library/Application Support/Google/Drive/sync_config.db\"\n    yosemite_gdrive_db_path = (\n        \"Library/Application Support/Google/Drive/user_default/sync_config.db\"\n    )\n    yosemite_gdrive_db = os.path.join(os.environ[\"HOME\"], yosemite_gdrive_db_path)\n    if os.path.isfile(yosemite_gdrive_db):\n        gdrive_db_path = yosemite_gdrive_db\n\n    googledrive_home: Optional[str] = None\n\n    gdrive_db = (\n        gdrive_db_path\n        if os.path.isabs(gdrive_db_path)\n        else os.path.join(os.environ[\"HOME\"], gdrive_db_path)\n    )\n    if os.path.isfile(gdrive_db):\n        try:\n            with sqlite3.connect(gdrive_db) as con:\n                cur = con.cursor()\n                query = (\n                    \"SELECT data_value \"\n                    \"FROM data \"\n                    \"WHERE entry_key = 'local_sync_root_path';\"\n                )\n                cur.execute(query)\n                data = cur.fetchone()\n                if data and data[0]:\n                    googledrive_home = str(data[0])\n        except sqlite3.Error:\n            googledrive_home = None\n\n    if googledrive_home:\n        return googledrive_home\n\n    error(\n        constants.ERROR_UNABLE_TO_FIND_STORAGE.format(\n            provider=\"Google Drive install\",\n        ),\n    )\n\n\ndef get_icloud_folder_location() -> str:\n    \"\"\"\n    Try to locate the iCloud Drive folder.\n\n    Returns:\n        (str) Full path to the iCloud Drive folder.\n    \"\"\"\n    yosemite_icloud_path = \"~/Library/Mobile Documents/com~apple~CloudDocs/\"\n\n    icloud_home = os.path.expanduser(yosemite_icloud_path)\n\n    if not os.path.isdir(icloud_home):\n        error(constants.ERROR_UNABLE_TO_FIND_STORAGE.format(provider=\"iCloud Drive\"))\n\n    return str(icloud_home)\n\n\ndef is_process_running(process_name: str) -> bool:\n    \"\"\"\n    Check if a process with the given name is running.\n\n    Args:\n        (str): Process name, e.g. \"Sublime Text\"\n\n    Returns:\n        (bool): True if the process is running\n    \"\"\"\n    is_running: bool = False\n\n    # On systems with pgrep, check if the given process is running\n    if os.path.isfile(\"/usr/bin/pgrep\"):\n        with open(os.devnull, \"wb\") as dev_null:\n            returncode: int = subprocess.call(\n                [\"/usr/bin/pgrep\", process_name], stdout=dev_null,\n            )\n            is_running = bool(returncode == 0)\n\n    return is_running\n\n\ndef remove_acl(path: str) -> None:\n    \"\"\"\n    Remove the ACL of the file or folder located on the given path.\n\n    Also remove the ACL of any file and folder below the given one,\n    recursively.\n\n    Args:\n        path (str): Path to the file or folder to remove the ACL for,\n                    recursively.\n    \"\"\"\n    # Some files have ACLs, let's remove them recursively\n    if platform.system() == constants.PLATFORM_DARWIN and os.path.isfile(\"/bin/chmod\"):\n        subprocess.call([\"/bin/chmod\", \"-R\", \"-N\", path])\n    elif (platform.system() == constants.PLATFORM_LINUX) and os.path.isfile(\n        \"/bin/setfacl\",\n    ):\n        subprocess.call([\"/bin/setfacl\", \"-R\", \"-b\", path])\n\n\ndef remove_immutable_attribute(path: str) -> None:\n    \"\"\"\n    Remove the immutable attribute of the given path.\n\n    Remove the immutable attribute of the file or folder located on the given\n    path. Also remove the immutable attribute of any file and folder below the\n    given one, recursively.\n\n    Args:\n        path (str): Path to the file or folder to remove the immutable\n                    attribute for, recursively.\n    \"\"\"\n    # Some files have ACLs, let's remove them recursively\n    if (platform.system() == constants.PLATFORM_DARWIN) and os.path.isfile(\n        \"/usr/bin/chflags\",\n    ):\n        subprocess.call([\"/usr/bin/chflags\", \"-R\", \"nouchg\", path])\n    elif platform.system() == constants.PLATFORM_LINUX and os.path.isfile(\n        \"/usr/bin/chattr\",\n    ):\n        subprocess.call([\"/usr/bin/chattr\", \"-R\", \"-f\", \"-i\", path])\n\n\ndef can_file_be_synced_on_current_platform(path: str) -> bool:\n    \"\"\"\n    Check if the given path can be synced locally.\n\n    Check if it makes sense to sync the file at the given path on the current\n    platform.\n    For now we don't sync any file in the ~/Library folder on GNU/Linux.\n    There might be other exceptions in the future.\n\n    Args:\n        (str): Path to the file or folder to check. If relative, prepend it\n               with the home folder.\n               'abc' becomes '~/abc'\n               '/def' stays '/def'\n\n    Returns:\n        (bool): True if given file can be synced\n    \"\"\"\n    can_be_synced: bool = True\n\n    # If the given path is relative, prepend home\n    fullpath: str = os.path.join(os.environ[\"HOME\"], path)\n\n    # Compute the ~/Library path on macOS\n    # End it with a slash because we are looking for this specific folder and\n    # not any file/folder named LibrarySomething\n    library_path: str = os.path.join(os.environ[\"HOME\"], \"Library/\")\n\n    is_linux = platform.system() == constants.PLATFORM_LINUX\n    if is_linux and fullpath.startswith(library_path):\n        can_be_synced = False\n\n    return can_be_synced\n"
  },
  {
    "path": "tests/README.md",
    "content": "# Tests\n\nTests are put in this folder.\n\nFeel free to add more, the more the better!\n\n## How to run the tests\n\n```bash\nbrew install uv\nmake test\n```\n\nAnd you should see\n\n```text\n.\n----------------------------------------------------------------------\nRan 1 test in 0.016s\n\nOK\n```\n\nYeah, I wrote this file when there was only 1 test, I hope there will be more\nwhen you read it!\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "\"\"\"Test package for Mackup.\"\"\"\n"
  },
  {
    "path": "tests/fixtures/.mackup/legacy-test-app.cfg",
    "content": "[application]\nname = Legacy Test App\n\n[configuration_files]\n.legacy-test-app-config\n"
  },
  {
    "path": "tests/fixtures/.mackup/priority-test-app.cfg",
    "content": "[application]\nname = Priority Test App Legacy\n\n[configuration_files]\n.priority-test-legacy\n"
  },
  {
    "path": "tests/fixtures/Library/Application Support/Box/Box Sync/sync_root_folder.txt",
    "content": "/Users/whatever/Box Sync"
  },
  {
    "path": "tests/fixtures/Library/Mobile Documents/com~apple~CloudDocs/_blank_.md",
    "content": "Blank file for git sync\n"
  },
  {
    "path": "tests/fixtures/mackup-apps_to_ignore.cfg",
    "content": "[applications_to_ignore]\nsubversion ; inline comment\nsequel-pro # inline comment\nsabnzbd\n"
  },
  {
    "path": "tests/fixtures/mackup-apps_to_ignore_and_sync.cfg",
    "content": "[applications_to_sync]\nsabnzbd\nsublime-text-3\nx11\nvim\n\n[applications_to_ignore]\nsubversion\nsequel-pro\nsabnzbd\n"
  },
  {
    "path": "tests/fixtures/mackup-apps_to_sync.cfg",
    "content": "[applications_to_sync]\nsabnzbd\nsublime-text-3\nx11\n"
  },
  {
    "path": "tests/fixtures/mackup-empty.cfg",
    "content": ""
  },
  {
    "path": "tests/fixtures/mackup-engine-dropbox.cfg",
    "content": "[storage]\nengine = dropbox\ndirectory = some_weirld_name\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-file_system-absolute.cfg",
    "content": "[storage]\nengine = file_system\npath = /some/absolute/folder\ndirectory = custom_folder\n\n[applications_to_ignore]\nsubversion\nsequel-pro\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-file_system-no_path.cfg",
    "content": "[storage]\nengine = file_system\ndirectory = Mackup\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-file_system.cfg",
    "content": "[storage]\nengine = file_system\npath = some/relative/folder\n\n[applications_to_sync]\nsabnzbd\nsublime-text-3\nx11\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-google_drive.cfg",
    "content": "[storage]\nengine = google_drive\n\n[applications_to_ignore]\nsubversion\nsequel-pro\nsabnzbd\n\n[applications_to_sync]\nsabnzbd\nsublime-text-3\nx11\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-icloud.cfg",
    "content": "[storage]\nengine = icloud\n\n[applications_to_ignore]\nsubversion\nsequel-pro\nsabnzbd\n\n[applications_to_sync]\nsabnzbd\nsublime-text-3\nx11\n"
  },
  {
    "path": "tests/fixtures/mackup-engine-unknown.cfg",
    "content": "[storage]\nengine = unknown_engine\n"
  },
  {
    "path": "tests/fixtures/mackup-envarcheck.cfg",
    "content": "[test]\ntesttype = test_config_envvar\n"
  },
  {
    "path": "tests/fixtures/mackup-old-config.cfg",
    "content": "[storage]\nengine = file_system\npath = /some/absolute/folder\ndirectory = custom_folder\n\n[applications_to_ignore]\nsubversion\nsequel-pro\n\n[Allowed Applications]\nssh\ngit\n\n[Ignored Applications]\nvim\n"
  },
  {
    "path": "tests/fixtures/xdg-config-home/mackup/applications/priority-test-app.cfg",
    "content": "[application]\nname = Priority Test App XDG\n\n[configuration_files]\n.priority-test-xdg\n"
  },
  {
    "path": "tests/fixtures/xdg-config-home/mackup/applications/xdg-test-app.cfg",
    "content": "[application]\nname = XDG Test App\n\n[configuration_files]\n.xdg-test-app-config\n"
  },
  {
    "path": "tests/fixtures/xdg-config-home/mackup/mackup.cfg",
    "content": "[test]\ntesttype = test_config_xdg\n"
  },
  {
    "path": "tests/test_application.py",
    "content": "import os\nimport shutil\nimport sys\nimport tempfile\nimport unittest\nfrom io import StringIO\nfrom unittest.mock import Mock, patch\n\nfrom mackup.application import ApplicationProfile\nfrom mackup.mackup import Mackup\n\n\nclass TestApplicationProfile(unittest.TestCase):\n    def setUp(self):\n        \"\"\"Set up test fixtures.\"\"\"\n        # Create a mock Mackup instance\n        self.mock_mackup = Mock(spec=Mackup)\n        self.mock_mackup.mackup_folder = tempfile.mkdtemp()\n\n        # Create a temporary home directory\n        self.temp_home = tempfile.mkdtemp()\n\n        # Save original HOME and set it to temp directory\n        self.original_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = self.temp_home\n\n        # Define test files\n        self.test_files = {\".testfile\", \".testfolder\"}\n\n        # Create the ApplicationProfile instance\n        self.app_profile = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=False,\n            verbose=False,\n        )\n\n    def tearDown(self):\n        \"\"\"Clean up test fixtures.\"\"\"\n        # Restore original HOME\n        if self.original_home:\n            os.environ[\"HOME\"] = self.original_home\n        else:\n            del os.environ[\"HOME\"]\n\n        # Clean up temporary directories\n        if os.path.exists(self.temp_home):\n            shutil.rmtree(self.temp_home)\n        if os.path.exists(self.mock_mackup.mackup_folder):\n            shutil.rmtree(self.mock_mackup.mackup_folder)\n\n    def test_copy_files_to_mackup_folder_permission_error(self):\n        \"\"\"Test PermissionError handling in copy_files_to_mackup_folder.\"\"\"\n        # Create a test file in the home directory\n        test_file = \".testfile\"\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the actual file\n        with open(home_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the error message was printed\n            output = captured_output.getvalue()\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n            assert home_filepath in output\n\n    def test_files_are_sorted_for_deterministic_processing(self):\n        \"\"\"Application files should always be processed in sorted order.\"\"\"\n        unsorted_files = {\"z-last\", \"a-first\", \"m-middle\"}\n        app_profile = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=unsorted_files,\n            dry_run=False,\n            verbose=False,\n        )\n        assert app_profile.files == [\"a-first\", \"m-middle\", \"z-last\"]\n\n    def test_copy_files_to_mackup_folder_permission_error_verbose(self):\n        \"\"\"Test PermissionError handling in copy_files_to_mackup_folder verbose.\"\"\"\n        # Create a verbose ApplicationProfile\n        app_profile_verbose = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=False,\n            verbose=True,\n        )\n\n        # Create a test file in the home directory\n        test_file = \".testfile\"\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the actual file\n        with open(home_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            app_profile_verbose.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the verbose backing up message and error message were printed\n            output = captured_output.getvalue()\n            assert \"Backing up\" in output\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n\n    def test_copy_files_from_mackup_folder_permission_error(self):\n        \"\"\"Test PermissionError handling in copy_files_from_mackup_folder.\"\"\"\n        # Create a test file in the mackup directory\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n\n        # Create the actual file\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_from_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the error message was printed\n            output = captured_output.getvalue()\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n            assert mackup_filepath in output\n\n    def test_copy_files_from_mackup_folder_permission_error_verbose(self):\n        \"\"\"Test PermissionError handling in copy_files_from_mackup_folder verbose.\"\"\"\n        # Create a verbose ApplicationProfile\n        app_profile_verbose = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=False,\n            verbose=True,\n        )\n\n        # Create a test file in the mackup directory\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n\n        # Create the actual file\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            app_profile_verbose.copy_files_from_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the verbose recovering message and error message were printed\n            output = captured_output.getvalue()\n            assert \"Recovering\" in output\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n\n    def test_copy_files_to_mackup_folder_with_directory_permission_error(self):\n        \"\"\"Test PermissionError with a directory in copy_files_to_mackup_folder.\"\"\"\n        # Create a test directory in the home directory\n        test_dir = \".testfolder\"\n        home_dirpath = os.path.join(self.temp_home, test_dir)\n        os.makedirs(home_dirpath)\n\n        # Create a file inside the directory\n        with open(os.path.join(home_dirpath, \"testfile.txt\"), \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied for directory\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the error message was printed\n            output = captured_output.getvalue()\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n            assert home_dirpath in output\n\n    def test_copy_files_from_mackup_folder_with_directory_permission_error(self):\n        \"\"\"Test PermissionError with a directory in copy_files_from_mackup_folder.\"\"\"\n        # Create a test directory in the mackup directory\n        test_dir = \".testfolder\"\n        mackup_dirpath = os.path.join(self.mock_mackup.mackup_folder, test_dir)\n        os.makedirs(mackup_dirpath)\n\n        # Create a file inside the directory\n        with open(os.path.join(mackup_dirpath, \"testfile.txt\"), \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy to raise PermissionError\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            mock_copy.side_effect = PermissionError(\"Permission denied for directory\")\n\n            # Capture stdout to verify the error message\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_from_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was called\n            mock_copy.assert_called_once()\n\n            # Verify that the error message was printed\n            output = captured_output.getvalue()\n            assert \"Error: Unable to copy file\" in output\n            assert \"permission issue\" in output\n            assert mackup_dirpath in output\n\n    def test_copy_files_to_mackup_folder_dry_run_no_permission_error(self):\n        \"\"\"Test dry_run mode doesn't trigger PermissionError in backup.\"\"\"\n        # Create a dry_run ApplicationProfile\n        app_profile_dry = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=True,\n            verbose=False,\n        )\n\n        # Create a test file in the home directory\n        test_file = \".testfile\"\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the actual file\n        with open(home_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy - it should NOT be called in dry_run mode\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            app_profile_dry.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was NOT called (dry_run mode)\n            mock_copy.assert_not_called()\n\n            # Verify that the backing up message was printed\n            output = captured_output.getvalue()\n            assert \"Backing up\" in output\n\n    def test_copy_files_from_mackup_folder_dry_run_no_permission_error(self):\n        \"\"\"Test dry_run mode doesn't trigger PermissionError in restore.\"\"\"\n        # Create a dry_run ApplicationProfile\n        app_profile_dry = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=True,\n            verbose=False,\n        )\n\n        # Create a test file in the mackup directory\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n\n        # Create the actual file\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"test content\")\n\n        # Patch utils.copy - it should NOT be called in dry_run mode\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            app_profile_dry.copy_files_from_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy was NOT called (dry_run mode)\n            mock_copy.assert_not_called()\n\n            # Verify that the recovering message was printed\n            output = captured_output.getvalue()\n            assert \"Recovering\" in output\n\n    def test_copy_files_to_mackup_folder_decline_replace_skips_copy(self):\n        \"\"\"Test backup does not overwrite when user declines replacement.\"\"\"\n        test_file = \".testfile\"\n        home_filepath = os.path.join(self.temp_home, test_file)\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n\n        with open(home_filepath, \"w\") as f:\n            f.write(\"home content\")\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"existing backup\")\n\n        with patch(\"mackup.application.utils.confirm\", return_value=False), \\\n             patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            self.app_profile.copy_files_to_mackup_folder()\n\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n        with open(mackup_filepath) as f:\n            assert f.read() == \"existing backup\"\n\n    def test_copy_files_from_mackup_folder_decline_replace_skips_copy(self):\n        \"\"\"Test restore does not overwrite when user declines replacement.\"\"\"\n        test_file = \".testfile\"\n        home_filepath = os.path.join(self.temp_home, test_file)\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n\n        with open(home_filepath, \"w\") as f:\n            f.write(\"existing home\")\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"backup content\")\n\n        with patch(\"mackup.application.utils.confirm\", return_value=False), \\\n             patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            self.app_profile.copy_files_from_mackup_folder()\n\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n        with open(home_filepath) as f:\n            assert f.read() == \"existing home\"\n\n    def test_link_uninstall_mackup_not_a_link(self):\n        \"\"\"Test link_uninstall skips when home file is not a symbolic link.\"\"\"\n        # Create a test file in the mackup directory (regular file, not a link)\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the mackup file as a regular file\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"mackup content\")\n\n        # Create the home file as a regular file (not a link)\n        with open(home_filepath, \"w\") as f:\n            f.write(\"home content\")\n\n        # Patch utils.delete and utils.copy\n        with patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.link_uninstall()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that delete and copy were NOT called\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n            # Verify that the warning message was printed\n            output = captured_output.getvalue()\n            assert \"Warning: the file in your home\" in output\n            assert \"does not point to the original file\" in output\n            assert mackup_filepath in output\n            assert home_filepath in output\n            assert \"skipping\" in output\n\n    def test_link_uninstall_mackup_points_to_wrong_target(self):\n        \"\"\"Test link_uninstall skips when home link points to wrong target.\"\"\"\n        # Create a test file\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the mackup file\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"mackup content\")\n\n        # Create a different target file\n        wrong_target = os.path.join(self.temp_home, \".wrongtarget\")\n        with open(wrong_target, \"w\") as f:\n            f.write(\"wrong target content\")\n\n        # Create the home file as a symbolic link pointing to the wrong target\n        os.symlink(wrong_target, home_filepath)\n\n        # Patch utils.delete and utils.copy\n        with patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.link_uninstall()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that delete and copy were NOT called\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n            # Verify that the warning message was printed\n            output = captured_output.getvalue()\n            assert \"Warning: the file in your home\" in output\n            assert \"does not point to the original file\" in output\n            assert mackup_filepath in output\n            assert home_filepath in output\n            assert \"skipping\" in output\n\n    def test_link_uninstall_mackup_points_correctly(self):\n        \"\"\"Test link_uninstall proceeds when home link points to mackup file.\"\"\"\n        # Create a test file\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the mackup file first\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"mackup content\")\n\n        # Create the home file as a symbolic link pointing to the mackup file\n        os.symlink(mackup_filepath, home_filepath)\n\n        # Patch utils.delete and utils.copy\n        with patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.link_uninstall()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that delete and copy WERE called (normal operation)\n            mock_delete.assert_called_once_with(home_filepath)\n            mock_copy.assert_called_once_with(mackup_filepath, home_filepath)\n\n            # Verify that the reverting message was printed (not warning)\n            output = captured_output.getvalue()\n            assert \"Reverting\" in output\n            assert \"Warning\" not in output\n\n    def test_copy_files_to_mackup_folder_skips_already_linked_files(self):\n        \"\"\"Test that backup skips files already linked from link install.\"\"\"\n        # Create a test file\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the mackup file first (simulating link install)\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"mackup content\")\n\n        # Create the home file as a symbolic link pointing to the mackup file\n        # (simulating what link install does)\n        os.symlink(mackup_filepath, home_filepath)\n\n        # Patch utils.delete and utils.copy\n        with patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that delete and copy were NOT called (should skip)\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n            # Verify that the skipping message was NOT printed (non-verbose)\n            output = captured_output.getvalue()\n            assert \"Backing up\" not in output\n\n        # Verify the symlink still exists and points to mackup file\n        assert os.path.islink(home_filepath)\n        assert os.path.samefile(home_filepath, mackup_filepath)\n\n        # Verify the mackup file still exists with original content\n        assert os.path.exists(mackup_filepath)\n        with open(mackup_filepath) as f:\n            assert f.read() == \"mackup content\"\n\n\n    def test_copy_files_to_mackup_folder_skips_already_linked_files_verbose(self):\n        \"\"\"Test backup skips files already linked with verbose mode.\"\"\"\n        # Create a verbose ApplicationProfile\n        app_profile_verbose = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=self.test_files,\n            dry_run=False,\n            verbose=True,\n        )\n\n        # Create a test file\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create the mackup file first (simulating link install)\n        with open(mackup_filepath, \"w\") as f:\n            f.write(\"mackup content\")\n\n        # Create the home file as a symbolic link pointing to the mackup file\n        # (simulating what link install does)\n        os.symlink(mackup_filepath, home_filepath)\n\n        # Patch utils.delete and utils.copy\n        with patch(\"mackup.application.utils.delete\") as mock_delete, \\\n             patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            app_profile_verbose.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that delete and copy were NOT called (should skip)\n            mock_delete.assert_not_called()\n            mock_copy.assert_not_called()\n\n            # Verify that the skipping message WAS printed (verbose mode)\n            output = captured_output.getvalue()\n            assert \"Skipping\" in output\n            assert \"already linked to\" in output\n            assert home_filepath in output\n            assert mackup_filepath in output\n\n        # Verify the symlink still exists and points to mackup file\n        assert os.path.islink(home_filepath)\n        assert os.path.samefile(home_filepath, mackup_filepath)\n\n        # Verify the mackup file still exists with original content\n        assert os.path.exists(mackup_filepath)\n        with open(mackup_filepath) as f:\n            assert f.read() == \"mackup content\"\n\n\n    def test_copy_files_to_mackup_folder_backs_up_symlink_to_different_location(self):\n        \"\"\"Test that backup still works for symlinks pointing elsewhere (not mackup).\"\"\"\n        # Create a test file\n        test_file = \".testfile\"\n        mackup_filepath = os.path.join(self.mock_mackup.mackup_folder, test_file)\n        home_filepath = os.path.join(self.temp_home, test_file)\n\n        # Create a different target file (not in mackup folder)\n        other_target = os.path.join(self.temp_home, \".otherlocation\")\n        with open(other_target, \"w\") as f:\n            f.write(\"other content\")\n\n        # Create the home file as a symbolic link pointing to different location\n        os.symlink(other_target, home_filepath)\n\n        # Patch utils.copy (no mackup file exists, so confirm won't be called)\n        with patch(\"mackup.application.utils.copy\") as mock_copy:\n            # Capture stdout\n            captured_output = StringIO()\n            sys.stdout = captured_output\n\n            # Call the method\n            self.app_profile.copy_files_to_mackup_folder()\n\n            # Restore stdout\n            sys.stdout = sys.__stdout__\n\n            # Verify that copy WAS called (should backup symlinks to other locations)\n            mock_copy.assert_called_once_with(home_filepath, mackup_filepath)\n\n            # Verify that the backing up message was printed\n            output = captured_output.getvalue()\n            assert \"Backing up\" in output\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_appsdb_xdg.py",
    "content": "\"\"\"Tests for ApplicationsDatabase XDG support.\"\"\"\n\nimport os\nimport unittest\n\nfrom mackup.appsdb import ApplicationsDatabase\n\n\nclass TestApplicationsDatabaseXDG(unittest.TestCase):\n    \"\"\"Test XDG Base Directory support for custom applications.\"\"\"\n\n    def setUp(self):\n        \"\"\"Set up test fixtures.\"\"\"\n        realpath = os.path.dirname(os.path.realpath(__file__))\n        self.fixtures_path = os.path.join(realpath, \"fixtures\")\n        self._original_home = os.environ.get(\"HOME\")\n        self._original_xdg_config_home = os.environ.get(\"XDG_CONFIG_HOME\")\n        os.environ[\"HOME\"] = self.fixtures_path\n\n        # Clear XDG_CONFIG_HOME to ensure clean state\n        os.environ.pop(\"XDG_CONFIG_HOME\", None)\n\n    def tearDown(self):\n        \"\"\"Restore environment variables modified during tests.\"\"\"\n        if self._original_home is None:\n            os.environ.pop(\"HOME\", None)\n        else:\n            os.environ[\"HOME\"] = self._original_home\n\n        if self._original_xdg_config_home is None:\n            os.environ.pop(\"XDG_CONFIG_HOME\", None)\n        else:\n            os.environ[\"XDG_CONFIG_HOME\"] = self._original_xdg_config_home\n    def test_legacy_custom_apps_dir(self):\n        \"\"\"Test that legacy ~/.mackup/ directory is found.\"\"\"\n        # Don't set XDG_CONFIG_HOME, only legacy should be found\n        config_files = ApplicationsDatabase.get_config_files()\n        filenames = {os.path.basename(f) for f in config_files}\n\n        assert \"legacy-test-app.cfg\" in filenames\n\n    def test_xdg_custom_apps_dir(self):\n        \"\"\"Test that XDG custom apps directory is found.\"\"\"\n        xdg_config = os.path.join(self.fixtures_path, \"xdg-config-home\")\n        os.environ[\"XDG_CONFIG_HOME\"] = xdg_config\n\n        config_files = ApplicationsDatabase.get_config_files()\n        filenames = {os.path.basename(f) for f in config_files}\n\n        assert \"xdg-test-app.cfg\" in filenames\n\n    def test_legacy_takes_priority_over_xdg(self):\n        \"\"\"Test that legacy directory takes priority when same app exists.\"\"\"\n        xdg_config = os.path.join(self.fixtures_path, \"xdg-config-home\")\n        os.environ[\"XDG_CONFIG_HOME\"] = xdg_config\n\n        config_files = ApplicationsDatabase.get_config_files()\n\n        # Find the priority-test-app.cfg file\n        priority_files = [f for f in config_files if \"priority-test-app.cfg\" in f]\n\n        # Should only have one file (legacy should win)\n        assert len(priority_files) == 1\n\n        # Should be from legacy directory\n        assert \".mackup\" in priority_files[0]\n        assert \"xdg-config-home\" not in priority_files[0]\n\n    def test_both_directories_merged(self):\n        \"\"\"Test that apps from both directories are available.\"\"\"\n        xdg_config = os.path.join(self.fixtures_path, \"xdg-config-home\")\n        os.environ[\"XDG_CONFIG_HOME\"] = xdg_config\n\n        config_files = ApplicationsDatabase.get_config_files()\n        filenames = {os.path.basename(f) for f in config_files}\n\n        # Both unique apps should be present\n        assert \"legacy-test-app.cfg\" in filenames\n        assert \"xdg-test-app.cfg\" in filenames\n\n    def test_xdg_default_fallback(self):\n        \"\"\"Test that XDG falls back to ~/.config when XDG_CONFIG_HOME is not set.\"\"\"\n        # Unset XDG_CONFIG_HOME - should fall back to ~/.config\n        os.environ.pop(\"XDG_CONFIG_HOME\", None)\n\n        # This test just verifies the code doesn't crash\n        # In real scenario, ~/.config/mackup/applications/ would be checked\n        config_files = ApplicationsDatabase.get_config_files()\n\n        # Should at least contain stock apps and legacy custom apps\n        assert len(config_files) > 0\n\n    def test_applications_database_loads_xdg_apps(self):\n        \"\"\"Test that ApplicationsDatabase correctly loads apps from XDG.\"\"\"\n        xdg_config = os.path.join(self.fixtures_path, \"xdg-config-home\")\n        os.environ[\"XDG_CONFIG_HOME\"] = xdg_config\n\n        db = ApplicationsDatabase()\n\n        # XDG app should be loaded\n        assert \"xdg-test-app\" in db.get_app_names()\n        assert db.get_name(\"xdg-test-app\") == \"XDG Test App\"\n\n    def test_applications_database_priority_loads_legacy(self):\n        \"\"\"Test ApplicationsDatabase loads legacy version when app exists.\"\"\"\n        xdg_config = os.path.join(self.fixtures_path, \"xdg-config-home\")\n        os.environ[\"XDG_CONFIG_HOME\"] = xdg_config\n\n        db = ApplicationsDatabase()\n\n        # Priority app should load the legacy version\n        assert \"priority-test-app\" in db.get_app_names()\n        assert db.get_name(\"priority-test-app\") == \"Priority Test App Legacy\"\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_backup_after_link_install.py",
    "content": "\"\"\"Test the edge case: running backup after link install.\"\"\"\nimport os\nimport shutil\nimport sys\nimport tempfile\nimport unittest\nfrom io import StringIO\nfrom unittest.mock import Mock\n\nfrom mackup.application import ApplicationProfile\nfrom mackup.mackup import Mackup\n\n\nclass TestBackupAfterLinkInstall(unittest.TestCase):\n    \"\"\"Integration test for backup after link install edge case.\"\"\"\n\n    def setUp(self):\n        \"\"\"Set up test fixtures.\"\"\"\n        # Create a mock Mackup instance\n        self.mock_mackup = Mock(spec=Mackup)\n        self.mock_mackup.mackup_folder = tempfile.mkdtemp()\n\n        # Register cleanup for mackup folder\n        self.addCleanup(self._cleanup_directory, self.mock_mackup.mackup_folder)\n\n        # Create a temporary home directory\n        self.temp_home = tempfile.mkdtemp()\n\n        # Register cleanup for temp home\n        self.addCleanup(self._cleanup_directory, self.temp_home)\n\n        # Save original HOME and set it to temp directory\n        self.original_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = self.temp_home\n\n        # Register cleanup for HOME restoration\n        self.addCleanup(self._restore_home, self.original_home)\n\n    def _cleanup_directory(self, directory):\n        \"\"\"Safely clean up a directory.\"\"\"\n        if os.path.exists(directory):\n            shutil.rmtree(directory)\n\n    def _restore_home(self, original_home):\n        \"\"\"Restore the original HOME environment variable.\"\"\"\n        if original_home is not None:\n            os.environ[\"HOME\"] = original_home\n        elif \"HOME\" in os.environ:\n            del os.environ[\"HOME\"]\n\n    def tearDown(self):\n        \"\"\"Clean up test fixtures.\"\"\"\n        # Cleanup is now handled by addCleanup, which runs even if test fails\n\n    def test_backup_after_link_install_does_not_delete_mackup_files(self):\n        \"\"\"\n        Test the complete scenario:\n        1. Run link install (moves files to mackup and creates symlinks)\n        2. Run backup (should skip already linked files)\n        This prevents mackup from trying to delete files in the backup folder.\n        \"\"\"\n        # Define test files for this test\n        test_files = {\".testfile\", \".testdir\"}\n\n        # Step 1: Simulate initial state - files exist in home\n        home_file = os.path.join(self.temp_home, \".testfile\")\n        home_dir = os.path.join(self.temp_home, \".testdir\")\n\n        # Create initial files\n        with open(home_file, \"w\") as f:\n            f.write(\"original file content\")\n        os.makedirs(home_dir)\n        with open(os.path.join(home_dir, \"subfile.txt\"), \"w\") as f:\n            f.write(\"original dir content\")\n\n        # Step 2: Run link install\n        app_profile = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=test_files,\n            dry_run=False,\n            verbose=False,\n        )\n\n        captured_output = StringIO()\n        sys.stdout = captured_output\n        app_profile.link_install()\n        sys.stdout = sys.__stdout__\n\n        # Verify link install worked correctly\n        mackup_file = os.path.join(self.mock_mackup.mackup_folder, \".testfile\")\n        mackup_dir = os.path.join(self.mock_mackup.mackup_folder, \".testdir\")\n\n        # Files should exist in mackup folder\n        assert os.path.exists(mackup_file)\n        assert os.path.exists(mackup_dir)\n\n        # Home should have symlinks pointing to mackup\n        assert os.path.islink(home_file)\n        assert os.path.islink(home_dir)\n        assert os.path.samefile(home_file, mackup_file)\n        assert os.path.samefile(home_dir, mackup_dir)\n\n        # Verify content is preserved\n        with open(mackup_file) as f:\n            assert f.read() == \"original file content\"\n        with open(os.path.join(mackup_dir, \"subfile.txt\")) as f:\n            assert f.read() == \"original dir content\"\n\n        # Step 3: Run backup (this is where the edge case would occur)\n        captured_output = StringIO()\n        sys.stdout = captured_output\n        app_profile.copy_files_to_mackup_folder()\n        output = captured_output.getvalue()\n        sys.stdout = sys.__stdout__\n\n        # Verify backup skipped the already linked files\n        # Should not print \"Backing up\" for these files\n        assert \"Backing up\" not in output\n\n        # Verify the mackup files still exist and weren't deleted\n        assert os.path.exists(mackup_file)\n        assert os.path.exists(mackup_dir)\n\n        # Verify content is still intact\n        with open(mackup_file) as f:\n            assert f.read() == \"original file content\"\n        with open(os.path.join(mackup_dir, \"subfile.txt\")) as f:\n            assert f.read() == \"original dir content\"\n\n        # Verify symlinks are still in place\n        assert os.path.islink(home_file)\n        assert os.path.islink(home_dir)\n        assert os.path.samefile(home_file, mackup_file)\n        assert os.path.samefile(home_dir, mackup_dir)\n\n    def test_backup_after_link_install_verbose_shows_skip_message(self):\n        \"\"\"Test that verbose mode shows skip messages for already linked files.\"\"\"\n        # Define test file for this test\n        test_files = {\".testfile\"}\n\n        # Create initial file\n        home_file = os.path.join(self.temp_home, \".testfile\")\n\n        with open(home_file, \"w\") as f:\n            f.write(\"test content\")\n\n        # Run link install\n        app_profile = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=test_files,\n            dry_run=False,\n            verbose=False,\n        )\n        app_profile.link_install()\n\n        # Run backup in verbose mode\n        app_profile_verbose = ApplicationProfile(\n            mackup=self.mock_mackup,\n            files=test_files,\n            dry_run=False,\n            verbose=True,\n        )\n\n        captured_output = StringIO()\n        sys.stdout = captured_output\n        app_profile_verbose.copy_files_to_mackup_folder()\n        output = captured_output.getvalue()\n        sys.stdout = sys.__stdout__\n\n        # Verify skip message is shown\n        assert \"Skipping\" in output\n        assert \"already linked to\" in output\n        assert home_file in output\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom mackup import utils\nfrom mackup.main import main\n\n\nclass TestCLI(unittest.TestCase):\n    \"\"\"Test suite for CLI commands: backup, restore, and copy mode workflows.\"\"\"\n\n    def setUp(self):\n        \"\"\"Set up test environment before each test.\"\"\"\n        # Create temporary directories for testing\n        self.test_home = tempfile.mkdtemp(prefix=\"mackup_test_home_\")\n        self.test_storage = tempfile.mkdtemp(prefix=\"mackup_test_storage_\")\n        self.mackup_folder = os.path.join(self.test_storage, \"Mackup\")\n\n        # Store original HOME\n        self.original_home = os.environ.get(\"HOME\")\n        self.original_xdg = os.environ.get(\"XDG_CONFIG_HOME\")\n\n        # Set HOME to our test directory\n        os.environ[\"HOME\"] = self.test_home\n        os.environ[\"XDG_CONFIG_HOME\"] = os.path.join(self.test_home, \".config\")\n\n        # Create test config file\n        self.config_path = os.path.join(self.test_home, \".mackup.cfg\")\n        with open(self.config_path, \"w\") as f:\n            f.write(\"[storage]\\n\")\n            f.write(\"engine = file_system\\n\")\n            f.write(f\"path = {self.test_storage}\\n\")\n            f.write(\"directory = Mackup\\n\")\n            f.write(\"\\n\")\n            f.write(\"[applications_to_sync]\\n\")\n            f.write(\"test-app\\n\")\n\n        # Create a test application config in the apps database\n        self.test_app_name = \"test-app\"\n        self.test_file_name = \".testrc\"\n        self.test_file_path = os.path.join(self.test_home, self.test_file_name)\n\n        # Create test file with content\n        with open(self.test_file_path, \"w\") as f:\n            f.write(\"test_config=value\\n\")\n\n        # Create custom application config\n        self.custom_apps_dir = os.path.join(self.test_home, \".mackup\")\n        os.makedirs(self.custom_apps_dir, exist_ok=True)\n\n        self.custom_app_config = os.path.join(self.custom_apps_dir, \"test-app.cfg\")\n        with open(self.custom_app_config, \"w\") as f:\n            f.write(\"[application]\\n\")\n            f.write(f\"name = {self.test_app_name}\\n\")\n            f.write(\"\\n\")\n            f.write(\"[configuration_files]\\n\")\n            f.write(f\"{self.test_file_name}\\n\")\n\n        # Force yes to all prompts\n        utils.FORCE_YES = True\n        utils.FORCE_NO = False\n        utils.CAN_RUN_AS_ROOT = False\n\n    def tearDown(self):\n        \"\"\"Clean up test environment after each test.\"\"\"\n        # Restore original HOME\n        if self.original_home:\n            os.environ[\"HOME\"] = self.original_home\n        else:\n            os.environ.pop(\"HOME\", None)\n\n        # Restore original XDG_CONFIG_HOME\n        if self.original_xdg:\n            os.environ[\"XDG_CONFIG_HOME\"] = self.original_xdg\n        else:\n            os.environ.pop(\"XDG_CONFIG_HOME\", None)\n\n        # Clean up temporary directories\n        if os.path.exists(self.test_home):\n            shutil.rmtree(self.test_home)\n        if os.path.exists(self.test_storage):\n            shutil.rmtree(self.test_storage)\n\n        # Reset utils flags\n        utils.FORCE_YES = False\n        utils.FORCE_NO = False\n        utils.CAN_RUN_AS_ROOT = False\n\n    def test_backup_creates_mackup_folder(self):\n        \"\"\"Test that mackup backup creates the Mackup folder if it doesn't exist.\"\"\"\n        # Ensure Mackup folder doesn't exist\n        assert not os.path.exists(self.mackup_folder)\n\n        # Mock sys.argv to simulate 'mackup backup'\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Check that Mackup folder was created\n        assert os.path.exists(self.mackup_folder)\n\n    def test_backup_copies_file(self):\n        \"\"\"Test that mackup backup successfully copies a file to the backup location.\"\"\"\n        # Ensure test file exists\n        assert os.path.exists(self.test_file_path)\n\n        # Run backup\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Check that file was copied to Mackup folder\n        backed_up_file = os.path.join(self.mackup_folder, self.test_file_name)\n        assert os.path.exists(backed_up_file)\n\n        # Verify content is the same\n        with open(self.test_file_path) as f:\n            original_content = f.read()\n        with open(backed_up_file) as f:\n            backed_up_content = f.read()\n\n        assert original_content == backed_up_content\n\n    def test_restore_copies_file_back(self):\n        \"\"\"Test that mackup restore successfully copies a file back from backup.\"\"\"\n        # First, create a backup\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Verify backup exists\n        backed_up_file = os.path.join(self.mackup_folder, self.test_file_name)\n        assert os.path.exists(backed_up_file)\n\n        # Remove original file\n        os.remove(self.test_file_path)\n        assert not os.path.exists(self.test_file_path)\n\n        # Run restore\n        with patch(\"sys.argv\", [\"mackup\", \"restore\"]):\n            main()\n\n        # Check that file was restored\n        assert os.path.exists(self.test_file_path)\n\n        # Verify content is correct\n        with open(self.test_file_path) as f:\n            restored_content = f.read()\n\n        assert restored_content == \"test_config=value\\n\"\n\n    def test_backup_and_restore_full_workflow(self):\n        \"\"\"Test complete backup and restore workflow.\"\"\"\n        original_content = \"test_config=value\\n\"\n\n        # Verify original file exists and has correct content\n        assert os.path.exists(self.test_file_path)\n        with open(self.test_file_path) as f:\n            assert f.read() == original_content\n\n        # Step 1: Backup\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Verify backup was created\n        backed_up_file = os.path.join(self.mackup_folder, self.test_file_name)\n        assert os.path.exists(backed_up_file)\n\n        # Step 2: Modify original file\n        modified_content = \"test_config=modified\\n\"\n        with open(self.test_file_path, \"w\") as f:\n            f.write(modified_content)\n\n        # Verify file was modified\n        with open(self.test_file_path) as f:\n            assert f.read() == modified_content\n\n        # Step 3: Restore (should replace modified file with backup)\n        with patch(\"sys.argv\", [\"mackup\", \"restore\"]):\n            main()\n\n        # Verify file was restored to original content\n        with open(self.test_file_path) as f:\n            assert f.read() == original_content\n\n    def test_backup_preserves_file_permissions(self):\n        \"\"\"Test that mackup backup preserves file permissions.\"\"\"\n        # Set specific permissions on test file\n        os.chmod(self.test_file_path, 0o600)\n\n        # Run backup\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Check backup file permissions\n        backed_up_file = os.path.join(self.mackup_folder, self.test_file_name)\n        assert os.path.exists(backed_up_file)\n\n        # Verify permissions are preserved (mackup sets to 0600 by default)\n        backed_up_stat = os.stat(backed_up_file)\n        expected_mode = 0o600\n        assert backed_up_stat.st_mode & 0o777 == expected_mode\n\n    def test_restore_with_missing_backup(self):\n        \"\"\"Test that mackup restore handles missing backup files gracefully.\"\"\"\n        # Ensure no backup exists\n        assert not os.path.exists(self.mackup_folder)\n\n        # Create the mackup folder but don't add any files\n        os.makedirs(self.mackup_folder, exist_ok=True)\n\n        # Run restore (should not crash even though no backup exists)\n        with patch(\"sys.argv\", [\"mackup\", \"restore\"]):\n            try:\n                main()\n                # If no exception is raised, the test passes\n                # (restore should gracefully handle missing files)\n            except Exception as e:\n                self.fail(f\"Restore raised an exception with missing backup: {e}\")\n\n    def test_backup_with_folder(self):\n        \"\"\"Test that mackup backup works with folders, not just files.\"\"\"\n        # Create a test folder with a file inside\n        test_folder_name = \".test_folder\"\n        test_folder_path = os.path.join(self.test_home, test_folder_name)\n        os.makedirs(test_folder_path, exist_ok=True)\n\n        test_file_in_folder = os.path.join(test_folder_path, \"config.txt\")\n        with open(test_file_in_folder, \"w\") as f:\n            f.write(\"folder_config=value\\n\")\n\n        # Update custom app config to include the folder\n        with open(self.custom_app_config, \"w\") as f:\n            f.write(\"[application]\\n\")\n            f.write(f\"name = {self.test_app_name}\\n\")\n            f.write(\"\\n\")\n            f.write(\"[configuration_files]\\n\")\n            f.write(f\"{self.test_file_name}\\n\")\n            f.write(f\"{test_folder_name}\\n\")\n\n        # Run backup\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Check that folder was copied\n        backed_up_folder = os.path.join(self.mackup_folder, test_folder_name)\n        assert os.path.exists(backed_up_folder)\n        assert os.path.isdir(backed_up_folder)\n\n        # Check that file inside folder was copied\n        backed_up_file_in_folder = os.path.join(backed_up_folder, \"config.txt\")\n        assert os.path.exists(backed_up_file_in_folder)\n\n        # Verify content\n        with open(backed_up_file_in_folder) as f:\n            assert f.read() == \"folder_config=value\\n\"\n\n    def test_restore_with_folder(self):\n        \"\"\"Test that mackup restore works with folders.\"\"\"\n        # Create a test folder with a file inside\n        test_folder_name = \".test_folder\"\n        test_folder_path = os.path.join(self.test_home, test_folder_name)\n        os.makedirs(test_folder_path, exist_ok=True)\n\n        test_file_in_folder = os.path.join(test_folder_path, \"config.txt\")\n        with open(test_file_in_folder, \"w\") as f:\n            f.write(\"folder_config=value\\n\")\n\n        # Update custom app config to include the folder\n        with open(self.custom_app_config, \"w\") as f:\n            f.write(\"[application]\\n\")\n            f.write(f\"name = {self.test_app_name}\\n\")\n            f.write(\"\\n\")\n            f.write(\"[configuration_files]\\n\")\n            f.write(f\"{self.test_file_name}\\n\")\n            f.write(f\"{test_folder_name}\\n\")\n\n        # Run backup first\n        with patch(\"sys.argv\", [\"mackup\", \"backup\"]):\n            main()\n\n        # Delete the folder\n        shutil.rmtree(test_folder_path)\n        assert not os.path.exists(test_folder_path)\n\n        # Run restore\n        with patch(\"sys.argv\", [\"mackup\", \"restore\"]):\n            main()\n\n        # Check that folder was restored\n        assert os.path.exists(test_folder_path)\n        assert os.path.isdir(test_folder_path)\n\n        # Check that file inside folder was restored\n        assert os.path.exists(test_file_in_folder)\n\n        # Verify content\n        with open(test_file_in_folder) as f:\n            assert f.read() == \"folder_config=value\\n\"\n\n    def test_restore_fails_when_mackup_folder_missing(self):\n        \"\"\"Test that mackup restore fails when Mackup folder doesn't exist.\"\"\"\n        # Ensure Mackup folder doesn't exist\n        assert not os.path.exists(self.mackup_folder)\n\n        # Run restore - should exit with error when backup folder is missing\n        with patch(\"sys.argv\", [\"mackup\", \"restore\"]):\n            with pytest.raises(SystemExit) as context:\n                main()\n\n            # Should exit with non-zero status\n            assert context.value.code != 0\n\n    def test_force_and_force_no_are_mutually_exclusive(self):\n        \"\"\"Passing --force and --force-no together should fail fast.\"\"\"\n        with patch(\"sys.argv\", [\"mackup\", \"--force\", \"--force-no\", \"backup\"]):\n            with pytest.raises(SystemExit) as context:\n                main()\n\n            assert (\n                str(context.value)\n                == \"Options --force and --force-no are mutually exclusive.\"\n            )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "import os\nimport os.path\nimport unittest\nfrom pathlib import Path\n\nimport pytest\n\nfrom mackup.config import Config, ConfigError\nfrom mackup.constants import (\n    ENGINE_DROPBOX,\n    ENGINE_FS,\n    ENGINE_GDRIVE,\n    ENGINE_ICLOUD,\n    MACKUP_CONFIG_FILE,\n)\n\n\ndef assert_correct_config_read(testtype):\n    assert testtype == Config()._parser.get(\"test\", \"testtype\")\n\n\nclass TestConfig(unittest.TestCase):\n    def setUp(self):\n        realpath = os.path.dirname(os.path.realpath(__file__))\n        os.environ[\"HOME\"] = os.path.join(realpath, \"fixtures\")\n\n        # these may be set on some user's systems\n        os.environ.pop(\"XDG_CONFIG_HOME\", None)\n        os.environ.pop(\"MACKUP_CONFIG\", None)\n\n    def test_config_envvar(self):\n        os.environ[\"MACKUP_CONFIG\"] = \"~/mackup-envarcheck.cfg\"\n        assert_correct_config_read(\"test_config_envvar\")\n\n    def test_config_xdg(self):\n        os.environ[\"XDG_CONFIG_HOME\"] = \"~/xdg-config-home/\"\n        assert_correct_config_read(\"test_config_xdg\")\n\n    def test_config_find_correct_default(self):\n        config_path = Path.home() / MACKUP_CONFIG_FILE\n\n        try:\n            # create a default config file, this must be cleaned up after the test\n            config_path.write_text(\"[test]\\ntesttype = test_config_default\")\n\n            # nothing else set, should find the default file\n            assert_correct_config_read(\"test_config_default\")\n\n            # set MACKUP_CONFIG, but should still find the default file\n            os.environ[\"MACKUP_CONFIG\"] = \"~/mackup-envarcheck.cfg\"\n            assert_correct_config_read(\"test_config_default\")\n\n            # set XDG_CONFIG_HOME, but should still find the default file\n            os.environ[\"XDG_CONFIG_HOME\"] = \"~/xdg-config-home/\"\n            assert_correct_config_read(\"test_config_default\")\n        except Exception:\n            raise\n        finally:\n            config_path.unlink(missing_ok=True)\n\n        assert config_path.exists() is False\n\n    def test_config_finds_correct_envvar(self):\n        # set XDG_CONFIG_HOME, should find the XDG config file\n        os.environ[\"XDG_CONFIG_HOME\"] = \"~/xdg-config-home/\"\n        assert_correct_config_read(\"test_config_xdg\")\n\n        # set MACKUP_CONFIG, should find the MACKUP_CONFIG file\n        os.environ[\"MACKUP_CONFIG\"] = \"~/mackup-envarcheck.cfg\"\n        assert_correct_config_read(\"test_config_envvar\")\n\n    def test_config_no_config(self):\n        cfg = Config()\n\n        # Should should do the same as the default, empty configuration\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        print(cfg.path)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/Mackup\"\n\n        assert cfg.apps_to_ignore == set()\n        assert cfg.apps_to_sync == set()\n\n    def test_config_empty(self):\n        cfg = Config(\"mackup-empty.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/Mackup\"\n\n        assert cfg.apps_to_ignore == set()\n        assert cfg.apps_to_sync == set()\n\n    def test_config_engine_dropbox(self):\n        cfg = Config(\"mackup-engine-dropbox.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"some_weirld_name\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/some_weirld_name\"\n\n        assert cfg.apps_to_ignore == set()\n        assert cfg.apps_to_sync == set()\n\n    def test_config_engine_filesystem_absolute(self):\n        cfg = Config(\"mackup-engine-file_system-absolute.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_FS\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/some/absolute/folder\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"custom_folder\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/some/absolute/folder/custom_folder\"\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\"}\n        assert cfg.apps_to_sync == set()\n\n    def test_config_engine_filesystem(self):\n        cfg = Config(\"mackup-engine-file_system.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_FS\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path.endswith(\n            os.path.join(os.environ[\"HOME\"], \"some/relative/folder\"),\n        )\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == os.path.join(\n            os.environ[\"HOME\"], \"some/relative/folder\", \"Mackup\",\n        )\n\n        assert cfg.apps_to_ignore == set()\n        assert cfg.apps_to_sync == {\"sabnzbd\", \"sublime-text-3\", \"x11\"}\n\n    def test_config_engine_google_drive(self):\n        cfg = Config(\"mackup-engine-google_drive.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_GDRIVE\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/Users/whatever/Google Drive\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath.endswith(\"/Google Drive/Mackup\")\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\", \"sabnzbd\"}\n        assert cfg.apps_to_sync == {\"sublime-text-3\", \"x11\", \"sabnzbd\"}\n\n    def test_config_engine_icloud(self):\n        cfg = Config(\"mackup-engine-icloud.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_ICLOUD\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == os.path.expanduser(\n            \"~/Library/Mobile Documents/com~apple~CloudDocs/\",\n        )\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath.endswith(\"/com~apple~CloudDocs/Mackup\")\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\", \"sabnzbd\"}\n        assert cfg.apps_to_sync == {\"sublime-text-3\", \"x11\", \"sabnzbd\"}\n\n    def test_config_engine_filesystem_no_path(self):\n        with pytest.raises(ConfigError):\n            Config(\"mackup-engine-file_system-no_path.cfg\")\n\n    def test_config_engine_unknown(self):\n        with pytest.raises(ConfigError):\n            Config(\"mackup-engine-unknown.cfg\")\n\n    def test_config_apps_to_ignore(self):\n        cfg = Config(\"mackup-apps_to_ignore.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/Mackup\"\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\", \"sabnzbd\"}\n        assert cfg.apps_to_sync == set()\n\n    def test_config_apps_to_sync(self):\n        cfg = Config(\"mackup-apps_to_sync.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/Mackup\"\n\n        assert cfg.apps_to_ignore == set()\n        assert cfg.apps_to_sync == {\"sabnzbd\", \"sublime-text-3\", \"x11\"}\n\n    def test_config_apps_to_ignore_and_sync(self):\n        cfg = Config(\"mackup-apps_to_ignore_and_sync.cfg\")\n\n        assert isinstance(cfg.engine, str)\n        assert cfg.engine == ENGINE_DROPBOX\n\n        assert isinstance(cfg.path, str)\n        assert cfg.path == \"/home/some_user/Dropbox\"\n\n        assert isinstance(cfg.directory, str)\n        assert cfg.directory == \"Mackup\"\n\n        assert isinstance(cfg.fullpath, str)\n        assert cfg.fullpath == \"/home/some_user/Dropbox/Mackup\"\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\", \"sabnzbd\"}\n        assert cfg.apps_to_sync == {\"sabnzbd\", \"sublime-text-3\", \"x11\", \"vim\"}\n\n    def test_config_old_config(self):\n        with pytest.raises(SystemExit):\n            Config(\"mackup-old-config.cfg\")\n"
  },
  {
    "path": "tests/test_config_file_option.py",
    "content": "\"\"\"Tests for the --config-file command line option.\"\"\"\nimport os\nimport unittest\n\nimport pytest\n\nfrom mackup.config import Config\nfrom mackup.mackup import Mackup\n\n\nclass TestConfigFileOption(unittest.TestCase):\n    def setUp(self):\n        realpath = os.path.dirname(os.path.realpath(__file__))\n        os.environ[\"HOME\"] = os.path.join(realpath, \"fixtures\")\n\n        # Clear environment variables that could interfere\n        os.environ.pop(\"XDG_CONFIG_HOME\", None)\n        os.environ.pop(\"MACKUP_CONFIG\", None)\n\n    def test_config_with_relative_path(self):\n        \"\"\"Test that a relative path to config file works.\"\"\"\n        cfg = Config(\"mackup-apps_to_ignore.cfg\")\n\n        assert cfg.apps_to_ignore == {\"subversion\", \"sequel-pro\", \"sabnzbd\"}\n\n    def test_config_with_absolute_path(self):\n        \"\"\"Test that an absolute path to config file works.\"\"\"\n        abs_path = os.path.join(os.environ[\"HOME\"], \"mackup-apps_to_sync.cfg\")\n        cfg = Config(abs_path)\n\n        assert cfg.apps_to_sync == {\"sabnzbd\", \"sublime-text-3\", \"x11\"}\n\n    def test_mackup_with_config_file(self):\n        \"\"\"Test that Mackup class accepts config_file parameter.\"\"\"\n        # This should not raise any errors\n        mckp = Mackup(\"mackup-empty.cfg\")\n\n        # Verify that the config was properly initialized\n        assert mckp._config is not None\n        assert isinstance(mckp.mackup_folder, str)\n\n    def test_mackup_without_config_file(self):\n        \"\"\"Test that Mackup class works without config_file parameter.\"\"\"\n        # This should use default config file discovery\n        mckp = Mackup()\n\n        # Verify that the config was properly initialized\n        assert mckp._config is not None\n        assert isinstance(mckp.mackup_folder, str)\n\n    def test_config_file_does_not_exist(self):\n        \"\"\"Test that specifying a non-existent config file raises an error.\"\"\"\n        with pytest.raises(SystemExit):\n            Config(\"nonexistent-config-file.cfg\")\n"
  },
  {
    "path": "tests/test_constants.py",
    "content": "import unittest\nfrom unittest.mock import patch\n\nfrom mackup import constants\n\n\nclass TestConstants(unittest.TestCase):\n    def test_get_version_returns_metadata_version(self):\n        with patch(\"mackup.constants.version\", return_value=\"1.2.3\"):\n            assert constants._get_version() == \"1.2.3\"\n\n    def test_get_version_falls_back_when_metadata_missing(self):\n        with patch(\n            \"mackup.constants.version\",\n            side_effect=constants.PackageNotFoundError(\"mackup\"),\n        ):\n            assert constants._get_version() == \"unknown\"\n"
  },
  {
    "path": "tests/test_main.py",
    "content": "import unittest\n\nfrom mackup import main\n\n\nclass TestMain(unittest.TestCase):\n    def test_main_header(self):\n        assert main.header(\"blah\") == \"\\033[34mblah\\033[0m\"\n\n    def test_main_bold(self):\n        assert main.bold(\"blah\") == \"\\033[1mblah\\033[0m\"\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import os\nimport sqlite3\nimport stat\nimport tempfile\nimport unittest\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom mackup import utils\n\n\ndef convert_to_octal(file_name):\n    \"\"\"\n    Using os.stat, returns file permissions (read, write, execute) as an octal.\n    \"\"\"\n    return oct(os.stat(file_name)[stat.ST_MODE])[-3:]\n\n\nclass TestMackup(unittest.TestCase):\n    def test_confirm_yes(self):\n        # Override the input used in utils\n        with patch.object(utils, \"input\", return_value=\"Yes\", create=True):\n            assert utils.confirm(\"Answer Yes to this question\")\n\n    def test_confirm_no(self):\n        # Override the input used in utils\n        with patch.object(utils, \"input\", return_value=\"No\", create=True):\n            assert not utils.confirm(\"Answer No to this question\")\n\n    def test_confirm_typo(self):\n        # Override the input used in utils\n        with patch.object(utils, \"input\", return_value=\"No\", create=True):\n            assert not utils.confirm(\"Answer garbage to this question\")\n\n    def test_delete_file(self):\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False)\n        tfpath = tfile.name\n        tfile.close()\n\n        # Make sure the created file exists\n        assert os.path.isfile(tfpath)\n\n        # Check if mackup can really delete it\n        utils.delete(tfpath)\n        assert not os.path.exists(tfpath)\n\n    def test_delete_folder_recursively(self):\n        # Create a tmp folder\n        tfpath = tempfile.mkdtemp()\n\n        # Let's put a file in it just for fun\n        tfile = tempfile.NamedTemporaryFile(dir=tfpath, delete=False)\n        filepath = tfile.name\n        tfile.close()\n\n        # Let's put another folder in it\n        subfolder_path = tempfile.mkdtemp(dir=tfpath)\n\n        # And a file in the subfolder\n        tfile = tempfile.NamedTemporaryFile(dir=subfolder_path, delete=False)\n        subfilepath = tfile.name\n        tfile.close()\n\n        # Make sure the created files and folders exists\n        assert os.path.isdir(tfpath)\n        assert os.path.isfile(filepath)\n        assert os.path.isdir(subfolder_path)\n        assert os.path.isfile(subfilepath)\n\n        # Check if mackup can really delete it\n        utils.delete(tfpath)\n        assert not os.path.exists(tfpath)\n        assert not os.path.exists(filepath)\n        assert not os.path.exists(subfolder_path)\n        assert not os.path.exists(subfilepath)\n\n    def test_copy_file(self):\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False)\n        srcfile = tfile.name\n        tfile.close()\n\n        # Create a tmp folder\n        dstpath = tempfile.mkdtemp()\n        # Set the destination filename\n        dstfile = os.path.join(dstpath, \"subfolder\", os.path.basename(srcfile))\n\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Check if mackup can copy it\n        utils.copy(srcfile, dstfile)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert os.path.exists(dstfile)\n\n        # Let's clean up\n        utils.delete(dstpath)\n\n    def test_copy_fail(self):\n        # Create a tmp FIFO file\n        tfile = tempfile.NamedTemporaryFile()\n        srcfile = tfile.name\n        tfile.close()\n        os.mkfifo(srcfile)\n\n        # Create a tmp folder\n        dstpath = tempfile.mkdtemp()\n        # Set the destination filename\n        dstfile = os.path.join(dstpath, \"subfolder\", os.path.basename(srcfile))\n\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert not os.path.isfile(srcfile)\n        assert stat.S_ISFIFO(os.stat(srcfile).st_mode)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Check if mackup can copy it\n        with pytest.raises(ValueError, match=\"Unsupported file\"):\n            utils.copy(srcfile, dstfile)\n        assert not os.path.isfile(srcfile)\n        assert stat.S_ISFIFO(os.stat(srcfile).st_mode)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Let's clean up\n        utils.delete(srcfile)\n        utils.delete(dstpath)\n\n    def test_copy_file_to_dir(self):\n        \"\"\"Copies a file to a destination folder that already exists.\"\"\"\n        # Create a tmp folder\n        srcpath = tempfile.mkdtemp()\n\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False, dir=srcpath)\n        srcfile = tfile.name\n        tfile.close()\n\n        # Create a tmp folder\n        dstpath = tempfile.mkdtemp()\n\n        # Set the destination filename\n        srcpath_basename = os.path.basename(srcpath)\n        dstfile = os.path.join(\n            dstpath, \"subfolder\", srcpath_basename, os.path.basename(srcfile),\n        )\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert os.path.isdir(srcpath)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Check if mackup can copy it\n        utils.copy(srcfile, dstfile)\n        assert os.path.isdir(srcpath)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert os.path.exists(dstfile)\n\n        # Let's clean up\n        utils.delete(srcpath)\n        utils.delete(dstpath)\n\n    def test_copy_dir(self):\n        \"\"\"Copies a directory recursively to the destination path.\"\"\"\n        # Create a tmp folder\n        srcpath = tempfile.mkdtemp()\n\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False, dir=srcpath)\n        srcfile = tfile.name\n        tfile.close()\n\n        # Create a tmp folder\n        dstpath = tempfile.mkdtemp()\n\n        # Set the destination filename\n        srcpath_basename = os.path.basename(srcpath)\n        dstfile = os.path.join(dstpath, srcpath_basename, os.path.basename(srcfile))\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert os.path.isdir(srcpath)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Check if mackup can copy it\n        utils.copy(srcpath, dstfile)\n        assert os.path.isdir(srcpath)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert os.path.exists(dstfile)\n\n        # Let's clean up\n        utils.delete(srcpath)\n        utils.delete(dstpath)\n\n    def test_copy_dir_that_exists(self):\n        \"\"\"Copies a directory recursively to an existing destination.\"\"\"\n        # Create a source and a destination tmp folders\n        src_folder = tempfile.mkdtemp()\n        dst_folder = tempfile.mkdtemp()\n\n        # Create a folder inside the source folder\n        src_subfolder = tempfile.mkdtemp(dir=src_folder)\n\n        # Create the same subfolder inside the destination folder\n        src_subfolder_name = os.path.basename(src_subfolder)\n        dst_subfolder = os.path.join(dst_folder, src_subfolder_name)\n        os.mkdir(dst_subfolder)\n\n        # Create a tmp file in the src subfolder\n        src_file = tempfile.NamedTemporaryFile(delete=False, dir=src_subfolder)\n        src_file_name = src_file.name\n        src_file.close()\n\n        # Set the destination filename\n        dst_file = os.path.join(dst_subfolder, os.path.basename(src_file_name))\n\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert os.path.isdir(src_folder)\n        assert os.path.isdir(src_subfolder)\n        assert os.path.isfile(src_file_name)\n        assert os.path.isdir(dst_folder)\n        assert os.path.isdir(dst_subfolder)\n        assert not os.path.exists(dst_file)\n\n        # Check if mackup can copy it\n        utils.copy(src_folder, dst_folder)\n        assert os.path.isdir(src_folder)\n        assert os.path.isdir(src_subfolder)\n        assert os.path.isfile(src_file_name)\n        assert os.path.isdir(dst_folder)\n        assert os.path.isdir(dst_subfolder)\n        assert os.path.exists(dst_file)\n\n        # Let's clean up\n        utils.delete(src_folder)\n        utils.delete(dst_folder)\n\n    def test_link_file(self):\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False)\n        srcfile = tfile.name\n        tfile.close()\n\n        # Create a tmp folder\n        dstpath = tempfile.mkdtemp()\n        # Set the destination filename\n        dstfile = os.path.join(dstpath, \"subfolder\", os.path.basename(srcfile))\n\n        # Make sure the source file and destination folder exist and the\n        # destination file doesn't yet exist\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert not os.path.exists(dstfile)\n\n        # Check if mackup can link it and the link points to the correct place\n        utils.link(srcfile, dstfile)\n        assert os.path.isfile(srcfile)\n        assert os.path.isdir(dstpath)\n        assert os.path.exists(dstfile)\n        assert os.readlink(dstfile) == srcfile\n\n        # Let's clean up\n        utils.delete(dstpath)\n\n    def test_chmod_file(self):\n        # Create a tmp file\n        tfile = tempfile.NamedTemporaryFile(delete=False)\n        file_name = tfile.name\n\n        # Create a tmp directory with a sub folder\n        dir_name = tempfile.mkdtemp()\n        nested_dir = tempfile.mkdtemp(dir=dir_name)\n\n        # # File Tests\n\n        # Change the tmp file stats to S_IWRITE (200), write access only\n        os.chmod(file_name, stat.S_IWRITE)\n        assert convert_to_octal(file_name) == \"200\"\n\n        # Check to make sure that utils.chmod changes the bits to 600,\n        # which is read and write access for the owner\n        utils.chmod(file_name)\n        assert convert_to_octal(file_name) == \"600\"\n\n        # # Directory Tests\n\n        # Change the tmp folder stats to S_IREAD (400), read access only\n        os.chmod(dir_name, stat.S_IREAD)\n        assert convert_to_octal(dir_name) == \"400\"\n\n        # Check to make sure that utils.chmod changes the bits of all\n        # directories to 700, which is read, write, and execute access for the\n        # owner\n        utils.chmod(dir_name)\n        assert convert_to_octal(dir_name) == \"700\"\n        assert convert_to_octal(nested_dir) == \"700\"\n\n        # Use an \"unsupported file type\". In this case, /dev/null\n        with pytest.raises(ValueError, match=\"Unsupported file type\"):\n            utils.chmod(os.devnull)\n\n    def test_error(self):\n        test_string = \"Hello World\"\n        with pytest.raises(SystemExit):\n            utils.error(test_string)\n\n    def test_failed_backup_location(self):\n        \"\"\"\n        Tests for the error that should occur if the backup folder cannot be\n        found for Dropbox and Google\n        \"\"\"\n        # Hack to make our home folder some temporary folder\n        temp_home = tempfile.mkdtemp()\n        utils.os.environ[\"HOME\"] = temp_home\n\n        # Check for the missing Dropbox folder\n        assert not os.path.exists(os.path.join(temp_home, \".dropbox/host.db\"))\n        with pytest.raises(SystemExit):\n            utils.get_dropbox_folder_location()\n\n        # Check for the missing Google Drive folder\n        assert not os.path.exists(\n            os.path.join(\n                temp_home, \"Library/Application Support/Google/Drive/sync_config.db\",\n            ),\n        )\n        with pytest.raises(SystemExit):\n            utils.get_google_drive_folder_location()\n\n    def test_dropbox_folder_location_with_malformed_host_db(self):\n        \"\"\"Malformed Dropbox host.db should fail with a user-facing error.\"\"\"\n        with tempfile.TemporaryDirectory() as temp_home, patch.dict(\n            os.environ, {\"HOME\": temp_home},\n        ):\n            host_db_path = os.path.join(temp_home, \".dropbox\", \"host.db\")\n            os.makedirs(os.path.dirname(host_db_path), exist_ok=True)\n            with open(host_db_path, \"w\") as f:\n                f.write(\"malformed-content-without-base64-path\")\n\n            with pytest.raises(SystemExit):\n                utils.get_dropbox_folder_location()\n\n    def test_dropbox_folder_location_with_invalid_base64(self):\n        \"\"\"Invalid base64 in Dropbox host.db should fail with a user-facing error.\"\"\"\n        with tempfile.TemporaryDirectory() as temp_home, patch.dict(\n            os.environ, {\"HOME\": temp_home},\n        ):\n            host_db_path = os.path.join(temp_home, \".dropbox\", \"host.db\")\n            os.makedirs(os.path.dirname(host_db_path), exist_ok=True)\n            with open(host_db_path, \"w\") as f:\n                f.write(\"first-field invalid-base64-!@#$\")\n\n            with pytest.raises(SystemExit):\n                utils.get_dropbox_folder_location()\n\n    def test_google_drive_folder_location_with_missing_path_entry(self):\n        \"\"\"Google Drive DB without local_sync_root_path should fail cleanly.\"\"\"\n        with tempfile.TemporaryDirectory() as temp_home, patch.dict(\n            os.environ, {\"HOME\": temp_home},\n        ):\n            gdrive_db = os.path.join(\n                temp_home, \"Library/Application Support/Google/Drive/sync_config.db\",\n            )\n            os.makedirs(os.path.dirname(gdrive_db), exist_ok=True)\n\n            con = sqlite3.connect(gdrive_db)\n            cur = con.cursor()\n            cur.execute(\"CREATE TABLE data (entry_key TEXT, data_value TEXT)\")\n            cur.execute(\n                \"INSERT INTO data (entry_key, data_value) VALUES (?, ?)\",\n                (\"another_key\", \"/tmp/whatever\"),\n            )\n            con.commit()\n            con.close()\n\n            with pytest.raises(SystemExit):\n                utils.get_google_drive_folder_location()\n\n    def test_google_drive_folder_location_uses_user_default_db(self):\n        \"\"\"Read Google Drive location from user_default sync DB when present.\"\"\"\n        with tempfile.TemporaryDirectory() as temp_home, patch.dict(\n            os.environ, {\"HOME\": temp_home},\n        ):\n            gdrive_db = os.path.join(\n                temp_home,\n                \"Library/Application Support/Google/Drive/user_default/sync_config.db\",\n            )\n            os.makedirs(os.path.dirname(gdrive_db), exist_ok=True)\n\n            expected_path = os.path.join(temp_home, \"Google Drive\")\n            con = sqlite3.connect(gdrive_db)\n            cur = con.cursor()\n            cur.execute(\"CREATE TABLE data (entry_key TEXT, data_value TEXT)\")\n            cur.execute(\n                \"INSERT INTO data (entry_key, data_value) VALUES (?, ?)\",\n                (\"local_sync_root_path\", expected_path),\n            )\n            con.commit()\n            con.close()\n\n            assert utils.get_google_drive_folder_location() == expected_path\n\n    def test_is_process_running(self):\n        # A pgrep that has one letter and a wildcard will always return id 1\n        assert utils.is_process_running(\"a*\")\n        assert not utils.is_process_running(\"some imaginary process\")\n\n    def test_can_file_be_synced_on_current_platform(self):\n        # Any file path will do, even if it doesn't exist\n        path = \"some/file\"\n\n        # Force the Mac OSX Test using mock\n        with patch.object(\n            utils.platform, \"system\", return_value=utils.constants.PLATFORM_DARWIN,\n        ):\n            assert utils.can_file_be_synced_on_current_platform(path)\n\n        # Force the Linux Test using mock\n        with patch.object(\n            utils.platform, \"system\", return_value=utils.constants.PLATFORM_LINUX,\n        ):\n            assert utils.can_file_be_synced_on_current_platform(path)\n\n            # Try to use the library path on Linux, which shouldn't work\n            path = os.path.join(os.environ[\"HOME\"], \"Library/\")\n            assert not utils.can_file_be_synced_on_current_platform(path)\n"
  }
]