{"id":15788147,"url":"https://github.com/joellefkowitz/paths","last_synced_at":"2025-03-31T18:23:40.013Z","repository":{"id":119869218,"uuid":"540552598","full_name":"JoelLefkowitz/paths","owner":"JoelLefkowitz","description":"Cross platform OS path operations and executable path retrieval.","archived":false,"fork":false,"pushed_at":"2024-05-02T18:00:40.000Z","size":294,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-04T21:41:20.077Z","etag":null,"topics":["absolute","path","relative","runtime","source"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoelLefkowitz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-23T17:39:34.000Z","updated_at":"2024-05-27T22:51:04.000Z","dependencies_parsed_at":"2024-03-17T19:29:29.217Z","dependency_job_id":"80cd55d0-6d49-4515-a0af-385a8194ca6a","html_url":"https://github.com/JoelLefkowitz/paths","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Fpaths","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Fpaths/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Fpaths/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Fpaths/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoelLefkowitz","download_url":"https://codeload.github.com/JoelLefkowitz/paths/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246515845,"owners_count":20790127,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["absolute","path","relative","runtime","source"],"created_at":"2024-10-04T21:41:14.056Z","updated_at":"2025-03-31T18:23:40.004Z","avatar_url":"https://github.com/JoelLefkowitz.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Paths\n\nCross platform OS path operations and executable path retrieval.\n\n![Review](https://img.shields.io/github/actions/workflow/status/JoelLefkowitz/paths/review.yaml)\n![Quality](https://img.shields.io/codacy/grade/61e4785a984c42bbbdf1554f025d0f7a)\n\n## Motivation\n\nThis package is inspired by [whereami](https://github.com/gpakosz/whereami) and [std::filesystem](https://en.cppreference.com/w/cpp/filesystem) but with:\n\n- Simple functions\n- Readable sources\n- C++11 compatibility\n- Sensible exception handling\n\nTo separate the need to detect the operating system at runtime [detect](https://github.com/JoelLefkowitz/detect) is dropped in.\n\nSince path manipulation is full of edge cases it is paramount to have an extensive set of unit tests. The function implementations and test fixtures are verified to be consistent with python's standard library. To provide support to multiple platforms all test suites are verified against each multiple target environments.\n\nTest environments:\n\n| Test environment | Status                                                                                                       |\n| ---------------- | ------------------------------------------------------------------------------------------------------------ |\n| Ubuntu 20.04     | ![Linux](https://img.shields.io/github/actions/workflow/status/JoelLefkowitz/paths/test_ubuntu_20.04.yaml)   |\n| MacOS 12         | ![Darwin](https://img.shields.io/github/actions/workflow/status/JoelLefkowitz/paths/test_macos_12.yaml)      |\n| Windows 2022     | ![Windows](https://img.shields.io/github/actions/workflow/status/JoelLefkowitz/paths/test_windows_2022.yaml) |\n\n## Installing\n\n```bash\nconan search paths\n```\n\nYou can also download the [sources](https://download-directory.github.io?url=https://github.com/JoelLefkowitz/paths/tree/master/src).\n\n## Documentation\n\nDocumentation and more detailed examples are hosted on [Github Pages](https://joellefkowitz.github.io/paths).\n\n## Usage\n\n```cpp\nnamespace paths {\n    // Gets the path of the current executable file\n    std::string filepath();\n\n    // Gets the name of the current executable file\n    std::string filename();\n\n    // Gets the path of the directory of the current\n    // executable file\n    std::string dirpath();\n\n    // Gets the name of the directory of the current\n    // executable file\n    std::string dirname();\n\n    // Joins and normalises path chunks\n    //\n    // Complies with its python equivalent:\n    //   os.path.normpath(os.path.join(*paths))\n    //\n    // Usage:\n    //   resolve({\"a\", \"b\", \"c\"}) -\u003e \"a/b/c\"\n    //   resolve({\"a\", \"b\", \"..\", \"c\"}) -\u003e \"a/c\"\n    std::string resolve(const std::vector\u003cstd::string\u003e \u0026paths);\n\n    // Splits a path into normalised segments\n    //\n    // Complies with its python equivalent:\n    //   [i for i in os.path.normpath(path).split(os.sep) if i != \"\"]\n    //\n    // Usage:\n    //   segments(\"\") -\u003e {\".\"}\n    //   segments(\".\") -\u003e {\".\"}\n    //   segments(\"/\") -\u003e {}\n    //   segments(\"a/b\") -\u003e {\"a\", \"b\"}\n    //   segments(\"/a/b\") -\u003e {\"a\", \"b\"}\n    //   segments(\"a/b/../c\") -\u003e {\"a\", \"c\"}\n    //\n    // [Windows]\n    //   segments(\"C:/a/b\") -\u003e {\"a\", \"b\"}\n    //   segments(\"//a/b/c\") -\u003e {\"c\"}\n    //\n    // [Otherwise]\n    //   segments(\"C:/a/b\") -\u003e {\"C:\", \"a\", \"b\"}\n    //   segments(\"//a/b/c\") -\u003e {\"a\", \"b\", \"c\"}\n    std::vector\u003cstd::string\u003e segments(const std::string \u0026path);\n\n    // Normalises path chunks\n    //\n    // Complies with its python equivalent:\n    //   os.path.normpath(os.path.join(*paths)).split(os.sep)\n    //\n    // Usage:\n    //   normalise({}) -\u003e {\".\"}\n    //   normalise({\"\"}) -\u003e {\".\"}\n    //   normalise({\"a\", \".\", \"b\"}) -\u003e {\"a\", \"b\"}\n    //   normalise({\"a\", \"..\", \"b\"}) -\u003e {\"b\"}\n    std::vector\u003cstd::string\u003e normalise(const std::vector\u003cstd::string\u003e \u0026paths);\n\n    // Normalises a path\n    //\n    // Complies with its python equivalent:\n    //   os.path.normpath(path)\n    //\n    // Usage:\n    //   normpath(\"\") -\u003e \".\"\n    //   normpath(\"/..\") -\u003e \"/\"\n    //   normpath(\"a/./b\") -\u003e \"a/b\"\n    //   normpath(\"a/../b/c\") -\u003e \"a/c\"\n    //\n    // [Windows]\n    //   normpath(\"C:/..\") -\u003e \"C:\"\n    //   normpath(\"//a/b/..\") -\u003e \"//a/b\"\n    //\n    // [Otherwise]\n    //   normpath(\"C:/..\") -\u003e \"\"\n    //   normpath(\"//a/b\") -\u003e \"//a/b\"\n    //   normpath(\"//a/b/..\") -\u003e \"//a\"\n    //   normpath(\"///a/b\") -\u003e \"/a/b\"\n    std::string normpath(const std::string \u0026path);\n\n    // Gets the absolute path of a file using the\n    // current executable's directory as a base.\n    //\n    // Complies with its python equivalent:\n    //   os.path.abspath(path)\n    //\n    // Usage:\n    //   abspath(\"a/b/c\") -\u003e \u003ccurrent executable's directory\u003e/a/b/c\n    std::string abspath(const std::string \u0026path);\n\n    // Checks if a path is absolute\n    //\n    // Complies with its python equivalent:\n    //   os.path.isabs(path)\n    //\n    // Usage:\n    //   absolute(\"/a\") -\u003e true\n    //   absolute(\"a\") -\u003e false\n    bool absolute(const std::string \u0026path);\n\n    // Finds the relative path from a source to a target\n    //\n    // Complies with its python equivalent:\n    //   os.path.relpath(target, source)\n    //\n    // Usage:\n    //   relpath(\"a/b\", \"a/c\") -\u003e \"../c\"\n    std::string relpath(const std::string \u0026source, const std::string \u0026target);\n\n    // Checks if a path is relative\n    //\n    // Complies with its python equivalent:\n    //   not os.path.isabs(path)\n    //\n    // Usage:\n    //   relative(\"a\") -\u003e true\n    //   relative(\"/a\") -\u003e false\n    bool relative(const std::string \u0026path);\n\n    // Gets a path's drive\n    //\n    // Complies with its python equivalent:\n    //   os.path.splitdrive(path)[0]\n    //\n    // Usage:\n    // [Windows]\n    //   drive(\"C:/a/b\") -\u003e \"C:\"\n    //   drive(\"//a/b/c\") -\u003e \"//a/b\"\n    //\n    // [Otherwise]\n    //   drive(\"C:/a/b\") -\u003e \"\"\n    //   drive(\"//a/b/c\") -\u003e \"\"\n    std::string drive(const std::string \u0026path);\n\n    // Gets a path's head\n    //\n    // Complies with its python equivalent:\n    //   os.path.split(os.path.normpath(path))[1]\n    //\n    // Usage:\n    //   head(\"\") -\u003e \".\"\n    //   head(\".\") -\u003e \".\"\n    //   head(\"a\") -\u003e \"a\"\n    //   head(\"a/b\") -\u003e \"b\"\n    //\n    // [Windows]\n    //   head(\"C:\") -\u003e \"\"\n    //   head(\"//a/b\") -\u003e \"b\"\n    //\n    // [Otherwise]\n    //   head(\"C:\") -\u003e \"C:\"\n    //   head(\"//a/b\") -\u003e \"b\"\n    std::string head(const std::string \u0026path);\n\n    // Gets a path's tail\n    //\n    // Complies with its python equivalent:\n    //   os.path.split(os.path.normpath(path))[0]\n    //\n    // Usage:\n    //   tail(\"\") -\u003e \"\"\n    //   tail(\".\") -\u003e \"\"\n    //   tail(\"a\") -\u003e \"\"\n    //   tail(\"/a\") -\u003e \"/\"\n    //   tail(\"a/b\") -\u003e \"a\"\n    //   tail(\"a/b/c\") -\u003e \"a/b\"\n    //\n    // [Windows]\n    //   tail(\"C:\") -\u003e \"\"\n    //   tail(\"//a/b\") -\u003e \"//a/b\"\n    //   tail(\"//a/b/c\") -\u003e \"//a/b\"\n    //\n    // [Otherwise]\n    //   tail(\"C:\") -\u003e \"\"\n    //   tail(\"//a/b\") -\u003e \"//a\"\n    //   tail(\"//a/b/c\") -\u003e \"//a/b\"\n    std::string tail(const std::string \u0026path);\n\n    // Gets a path's extension\n    //\n    // Complies with its python equivalent:\n    //   os.path.splitext(path)[1]\n    //\n    // Usage:\n    //   extension(\"a\") -\u003e \"\"\n    //   extension(\"a/b\") -\u003e \"\"\n    //   extension(\"a/b.x\") -\u003e \".x\"\n    //   extension(\"a/b.x.y\") -\u003e \".y\"\n    std::string extension(const std::string \u0026path);\n\n    constexpr char posix_sep   = '/';\n    constexpr char windows_sep = '\\\\';\n\n    // Converts a Windows path to a Posix path\n    //\n    // Usage:\n    //   posix_path(\"\") -\u003e \"\"\n    //   posix_path(\".\") -\u003e \".\"\n    //   posix_path(\"a\\b\\c\") -\u003e \"a/b/c\"\n    //   posix_path(\"C:\\a\\b\\c\") -\u003e \"/a/b/c\"\n    //   posix_path(\"\\\\a\\b\\c\") -\u003e \"//a/b/c\"\n    std::string posix_path(const std::string \u0026path);\n\n    // Converts a Posix path to a Windows path\n    //\n    // Usage:\n    //   windows_path(\"\") -\u003e \"\"\n    //   windows_path(\".\") -\u003e \".\"\n    //   windows_path(\"a/b/c\") -\u003e \"a\\b\\c\"\n    //   windows_path(\"C:/a/b/c\") -\u003e \"C:\\a\\b\\c\"\n    //   windows_path(\"//a/b/c\") -\u003e \"\\\\a\\b\\c\"\n    std::string windows_path(const std::string \u0026path);\n\n    // Converts a path to a Windows path in a Windows\n    // environment and Posix path in a Posix environment\n    //\n    // Usage:\n    // platform_path(\"\") -\u003e \"\"\n    // platform_path(\".\") -\u003e \".\"\n    //\n    // [Windows]\n    //   platform_path(\"a/b\") -\u003e \"a\\b\"\n    //   platform_path(\"a\\b\") -\u003e \"a\\b\"\n    //\n    // [Otherwise]\n    //   platform_path(\"a/b\") -\u003e \"a/b\"\n    //   platform_path(\"a\\b\") -\u003e \"a/b\"\n    std::string platform_path(const std::string \u0026path);\n\n    // Joins strings with a delimeter\n    //\n    // Usage:\n    //   join({\"a\", \"b\", \"c\"}, ',') -\u003e \"a,b,c\"\n    std::string join(const std::vector\u003cstd::string\u003e \u0026strs, char delimiter);\n\n    // Joins strings with a delimeter\n    //\n    // Usage:\n    //   join({\"a\", \"b\", \"c\"}, \",\") -\u003e \"a,b,c\"\n    std::string join(\n        const std::vector\u003cstd::string\u003e \u0026strs,\n        const std::string              \u0026delimiter\n    );\n\n    // Splits a string at each occurrence of a delimeter\n    //\n    // Usage:\n    //   split(\"abc\", '') -\u003e {\"a\", \"b\", \"c\"}\n    //   split(\"a,b,c\", ',') -\u003e {\"a\", \"b\", \"c\"}\n    std::vector\u003cstd::string\u003e split(const std::string \u0026str, char delimiter);\n\n    // Splits a string at each occurrence of a delimeter\n    //\n    // Usage:\n    //   split(\"abc\", \"\") -\u003e {\"a\", \"b\", \"c\"}\n    //   split(\"a,b,c\", \",\") -\u003e {\"a\", \"b\", \"c\"}\n    std::vector\u003cstd::string\u003e split(\n        const std::string \u0026str,\n        const std::string \u0026delimiter\n    );\n\n    // Determines if a path starts with a prefix\n    //\n    // Usage:\n    //   starts_with(\"abc\", \"a\") -\u003e true\n    bool starts_with(const std::string \u0026str, char prefix);\n\n    // Determines if a path starts with a prefix\n    //\n    // Usage:\n    //   starts_with(\"abc\", \"a\") -\u003e true\n    bool starts_with(const std::string \u0026str, const std::string \u0026prefix);\n\n    // Determines if a path ends with a prefix\n    //\n    // Usage:\n    //   ends_with(\"abc\", \"c\") -\u003e true\n    bool ends_with(const std::string \u0026str, char suffix);\n\n    // Determines if a path ends with a prefix\n    //\n    // Usage:\n    //   ends_with(\"abc\", \"c\") -\u003e true\n    bool ends_with(const std::string \u0026str, const std::string \u0026suffix);\n} // namespace paths\n```\n\n## Discussion\n\n### Terminology\n\nEffort has been made to use consistent terminology in this library's source. These terms appear frequently as variable names and in docstrings:\n\n- `segments`: Normalised path chunks\n- `components`: Parts of a path such as its head and tail\n- `chunks`: Substrings\n\n### Notes\n\nCross platform support for file discovery and iteration can be added in a future release:\n\n- `exists`\n- `lexists`\n- `isfile`\n- `islink`\n- `isdir`\n- `listdir`\n- `iterdir`\n\nTo increase this library's ease of use there are some notable differences with python's standard library:\n\nNames:\n\n- The functions to join and split path chunks are called `resolve` and `segments` whereas `join` and `split` act on strings and have a delimeter parameter.\n\nBehaviour:\n\n- `resolve`, `segments`, `head`, and `tail`, normalise paths\n- `resolve` takes a vector of paths strings rather than being variadic\n\nParameters:\n\n- `relpath` can take an empty source parameter\n- `split` can take an empty delimeter parameter\n\nInternals:\n\nThe docstring for `normalise` says:\n\n```cpp\n// Complies with its python equivalent:\n//   os.path.normpath(os.path.join(*paths)).split(os.sep)\n```\n\nIn this library the implementation of `normpath` instead calls `normalise` to do most of the heavy lifting. This design allows other functions that act on path chunks such as `resolve` and `segments` to use `normalise` where calling `normpath` would require joining the chunks beforehand and then splitting the normalised result.\n\nThe docstring for `tail` says:\n\n```cpp\n// Complies with its python equivalent:\n//   os.path.split(os.path.normpath(path))[0]\n//\n// ...\n//\n//   tail(\"//a/b/c\") -\u003e \"//a/b\"\n```\n\nHowever its python equivalent produces a different and unexpected result which this library deviates from in the interest of consistency:\n\n```py\nos.path.split(os.path.normpath(\"//a/b/c\"))[0] -\u003e \"//a/b/\"\n```\n\n## Tooling\n\n### Dependencies\n\nTo install dependencies:\n\n```bash\nyarn install\npip install .[all]\nconan install .\n```\n\n### Tests\n\nTo run tests:\n\n```bash\nscons test\n```\n\n### Documentation\n\nTo generate the documentation locally:\n\n```bash\nscons docs\n```\n\n### Linters\n\nTo run linters:\n\n```bash\nscons lint\n```\n\n### Formatters\n\nTo run formatters:\n\n```bash\nscons format\n```\n\n### Publishing\n\nThe [ConanCenter](https://conan.io/center) doesn't yet allow users to publish packages independently. Package recipes are submitted to the [conan-center-index](https://github.com/conan-io/conan-center-index). A copy of this recipe is kept in this repository in the `publish` folder. This allows us to test that the recipe is compatible with new versions and makes it easier to submit updates to the conan-center-index.\n\nTo test the recipe can build the latest published tag:\n\n```bash\nconan create publish/all/conanfile.py --version $(yq -r \".versions | keys | .[0]\" publish/config.yml)\n```\n\nThis will fetch the sources and create a locally cached version of the package. This version can also be published to a local remote for testing:\n\n```bash\nconan upload \u003cpackage\u003e/\u003cversion\u003e -r \u003cremote\u003e\n```\n\n### Toolchains\n\nScripts are defined in the `scripts` folder and can be invoked with `toolchains`:\n\nTo generate header guards:\n\n```bash\nnpx toolchains guards\n```\n\n## Contributing\n\nPlease read this repository's [Code of Conduct](CODE_OF_CONDUCT.md) which outlines our collaboration standards and the [Changelog](CHANGELOG.md) for details on breaking changes that have been made.\n\nThis repository adheres to semantic versioning standards. For more information on semantic versioning visit [SemVer](https://semver.org).\n\nBump2version is used to version and tag changes. For example:\n\n```bash\nbump2version patch\n```\n\n### Contributors\n\n- [Joel Lefkowitz](https://github.com/joellefkowitz) - Initial work\n\n## Remarks\n\nLots of love to the open source community!\n\n\u003cdiv align='center'\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/osAcIGTSyeovPq6Xph/giphy.gif' alt='Be kind to your mind' /\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/KEAAbQ5clGWJwuJuZB/giphy.gif' alt='Love each other' /\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/WRWykrFkxJA6JJuTvc/giphy.gif' alt=\"It's ok to have a bad day\" /\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoellefkowitz%2Fpaths","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoellefkowitz%2Fpaths","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoellefkowitz%2Fpaths/lists"}