{"id":18282802,"url":"https://github.com/snivilised/extendio","last_synced_at":"2026-01-30T15:31:20.316Z","repository":{"id":62900565,"uuid":"546685185","full_name":"snivilised/extendio","owner":"snivilised","description":"🐋 extentions to Go standard io library","archived":false,"fork":false,"pushed_at":"2024-08-30T09:19:14.000Z","size":861,"stargazers_count":1,"open_issues_count":20,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-01T16:18:52.879Z","etag":null,"topics":["directory-traversal","directory-tree","filesystem","go","golang","golib","i18n","walk","walker"],"latest_commit_sha":null,"homepage":"","language":"Go","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/snivilised.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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-10-06T13:32:38.000Z","updated_at":"2024-08-30T09:19:16.000Z","dependencies_parsed_at":"2023-11-15T12:24:25.536Z","dependency_job_id":"dac66316-9c37-4971-82a7-9272b03bf6bd","html_url":"https://github.com/snivilised/extendio","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/snivilised/extendio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snivilised%2Fextendio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snivilised%2Fextendio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snivilised%2Fextendio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snivilised%2Fextendio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/snivilised","download_url":"https://codeload.github.com/snivilised/extendio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snivilised%2Fextendio/sbom","scorecard":{"id":834677,"data":{"date":"2025-08-11","repo":{"name":"github.com/snivilised/extendio","commit":"50fcbd92380237e61fc26ccd00f902d0b7df09ed"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/14 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci-workflow.yml:1","Warn: topLevel 'contents' permission set to 'write': .github/workflows/release-workflow.yml:9","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci-workflow.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/ci-workflow.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release-workflow.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/release-workflow.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release-workflow.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/release-workflow.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release-workflow.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/snivilised/extendio/release-workflow.yml/main?enable=pin","Warn: goCommand not pinned by hash: .github/workflows/ci-workflow.yml:36","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned","Info:   0 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":7,"reason":"3 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T18:40:08.416Z","repository_id":62900565,"created_at":"2025-08-23T18:40:08.416Z","updated_at":"2025-08-23T18:40:08.416Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28914905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["directory-traversal","directory-tree","filesystem","go","golang","golib","i18n","walk","walker"],"created_at":"2024-11-05T13:06:17.749Z","updated_at":"2026-01-30T15:31:20.297Z","avatar_url":"https://github.com/snivilised.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🐋 extendio: ___extensions to Go standard io libraries___\n\n[![A B](https://img.shields.io/badge/branching-commonflow-informational?style=flat)](https://commonflow.org)\n[![A B](https://img.shields.io/badge/merge-rebase-informational?style=flat)](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)\n[![A B](https://img.shields.io/badge/branch%20history-linear-blue?style=flat)](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule)\n[![Go Reference](https://pkg.go.dev/badge/github.com/snivilised/extendio.svg)](https://pkg.go.dev/github.com/snivilised/extendio)\n[![Go report](https://goreportcard.com/badge/github.com/snivilised/extendio)](https://goreportcard.com/report/github.com/snivilised/extendio)\n[![Coverage Status](https://coveralls.io/repos/github/snivilised/extendio/badge.svg?branch=master)](https://coveralls.io/github/snivilised/extendio?branch=master\u0026kill_cache=1)\n[![ExtendIO Continuous Integration](https://github.com/snivilised/extendio/actions/workflows/ci-workflow.yml/badge.svg)](https://github.com/snivilised/extendio/actions/workflows/ci-workflow.yml)\n[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\u0026logoColor=white)](https://github.com/pre-commit/pre-commit)\n[![A B](https://img.shields.io/badge/commit-conventional-commits?style=flat)](https://www.conventionalcommits.org/)\n\n\u003c!-- MD013/Line Length --\u003e\n\u003c!-- MarkDownLint-disable MD013 --\u003e\n\n\u003c!-- MD033/no-inline-html: Inline HTML --\u003e\n\u003c!-- MarkDownLint-disable MD033 --\u003e\n\n\u003c!-- MD040/fenced-code-language: Fenced code blocks should have a language specified --\u003e\n\u003c!-- MarkDownLint-disable MD040 --\u003e\n\n\u003cp align=\"left\"\u003e\n  \u003ca href=\"https://go.dev\"\u003e\u003cimg src=\"resources/images/go-logo-light-blue.png\" width=\"50\" alt=\"go.dev\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n🪦 This repo will soon be retired. extendio has been superseded by other snivilised projects (traverse, li18ngo, pants) as a result of lessons learned on the journey to learning Go programming.\n\n## 🔰 Introduction\n\nThis project provides extensions/alternative implementations to Go standard libraries, typically (typically but not limited to) `io` and `filepath`. It is intended the client should be abe to use this alongside the standard libraries like `io.fs`, but to make it easier to do so, the convention within `extendio` will be to name sub-packages it contains with a prefix of ___x___, so that there is no clash with the standard version and therefore nullifies the requirement to use an alternative alias; eg the `fs` package inside `extendio` is called `xfs`.\n\n\u003ca name=\"quick-start\"\u003e\u003c/a\u003e\n\n## 🚀 Quick Start\n\n### 👣 Traversal\n\nTo invoke a traversal, create a `PrimarySession` with the ___root___ path:\n\n```go\n  import (\"github.com/snivilised/extendio/xfs/nav\")\n\n  session := nav.PrimarySession{\n    Path: \"/foo/bar/\",\n  }\n```\n\nthen configure by calling the `Configure` method on the session:\n\n```go\n  callback := nav.LabelledTraverseCallback{\n    Fn: func(item *nav.TraverseItem) error {\n      fmt.Printf(\"Current Item Path: '%v' \\n\", item.Path)\n      err := something\n      return err\n    },\n  }\n\n  result := session.Configure(func(o *nav.TraverseOptions) {\n    o.Store.Subscription = nav.SubscribeFolders\n    o.Callback = callback\n  }).Run()\n\n  noOfFoldersFound := (*result.Metrics)[nav.MetricNoFoldersEn].Count\n```\n\n📝 ___Points of Note___:\n\n- the callback here is actually an instance of `LabelledTraverseCallback`, which is a `struct` that contains the function to be invoked and a `Label`. The `Label` is optional and was defined for debugging purposes. When you have a lot of func definitions, its difficult to identify which is which without having some form of identification.\n\n- function signature of `TraverseCallback` is defined as follows:\n\n```go\nfunc(item *TraverseItem) error\n```\n\n- `Configure` requires a function to be passed in that receives an instance of `TraverseOptions`, which is already populated with default values. The function the client provides simply sets the required options (see [options reference](#options-reference)). ⚠ The `Callback` option is mandatory, if not set then traversal will fail with a panic.\n- the call to `Configure` returns an instance of `NavigationRunner`, which contains a single `Run` method that returns a `TraverseResult`\n- the `TraverseResult` contains a `Metrics` (of type `MetricCollection`) item which currently indicates the number of files and folders the callback has been invoked for during the traversal. To inspect, use the `MetricEnum` (`MetricNoFilesEn`, `MetricNoFoldersEn`) to index into `Metrics` as illustrated in the example.\n- this example traverses the file system ___rooted___ at the path indicated in the session ('/foo/bar/') and invokes the callback for all folders found in the tree.\n\n## 🎀 Features\n\n\u003ca name=\"traverse\"\u003e\u003c/a\u003e\n\n### 👣 Traverse\n\n- Provides a pre-emptive declarative paradigm, to allow the client to be notified on a wider set of criteria and to minimise callback invocation. This allows for more efficiency when navigating large directory trees.\n- More comprehensive filtering capabilities incorporating that which is already provided by `filepath.Match`. The filtering will include positive and negative matching for globs (shell patterns) and regular expressions.\n- The client is able to define custom filters\n- The callback function signature will differ from `WalkDir`. Instead of being passed just the corresponding `fs.DirEntry`, another custom type will be introduced which contains as a member `fs.DirEntry`. More properties can be attached to this new abstraction to support more features (as indicated below).\n- Add `Depth` property. This will indicate to the callback how many levels of descending has occurred relative to the root directory.\n- Add `IsLeaf` property. The client may need to know if the current directory being notified for is a leaf directory. In fact as part of the declarative paradigm, the client may if they wish request to be notified for leaf nodes only and this will be achieved using the `IsLeaf` property.\n\n\u003ca name=\"resume\"\u003e\u003c/a\u003e\n\n#### ♻️ Resume\n\n- Add `Resume` function. Typically required in recovery scenarios, particularly when a large directory tree is being traversed and has been terminated part way, possibly in response to a CTRL-C interrupt. Instead of requiring a full traversal of the directory tree, the `Resume` function can be used to only process that part of the tree not visited in the previous run. The `Resume` function would require the `Root` path parameter, and a __checkpoint path__. The term ___fractured ancestor___ is introduced which denotes those directory nodes in the tree whose contents were only partially visited. Starting at the checkpoint, `Resume` would traverse the tree beginning at the checkpoint, then get the parent and find successor sibling nodes, invoking their corresponding trees. Then ascend and repeat the process until the root is encountered. `Resume` needs to invoke `Traverse` for each sub tree individually.\n\u003ca name=\"subscription-types\"\u003e\u003c/a\u003e\n\n\u003ca name=\"i18n\"\u003e\u003c/a\u003e\n\n### 🌐 i18n\n\n- In order to support i18n, error handling will be implemented slightly differently to the standard error handling paradigm already established in Go. Simply returning an error which is just a string containing an error message, is not i18n friendly. We could just return an error code which of course would be documented, but it would be more useful to return a richer abstraction, another object which contains various properties about the error. This object will contain an error code (probably int based, or pseudo enum). It can even contain a string member which contains the error message in English, but the error code would allow for messages to be translated (possibly using Go templates). The exact implementation has not been finalised yet, but this is the direction of travel.\n\n### 📨 Message Bus\n\n- Contains an alternative version of [bus](https://github.com/mustafaturan/bus). The requirement for a bus implementation is based upon the need to create loosely coupled internal code. The original bus was designed with concurrency in mind so it uses locks to achieve thread safety. This aspect is surplus to requirements as all we need it for are synchronous scenarios, so it has been striped out.\n\n#### ☢ Error Handling\n\n## User Guide\n\n### 👣 Using Traverse\n\nThe ___Traverse___ feature comes with many options to customise the way a file system is traversed illustrated in the following table:\n\n\u003ca name=\"options-reference\"\u003e\u003c/a\u003e\n\n#### ⚙️ Options Reference\n\n| Name              | -             | - | -   | Default | Reference\n|-------------------|---------------|---|-----|---------|-----------------\n| ___Store___[🔗](#o.store) | | | | | REF\n| | ___Subscription___[🔗](#subscription-types) | | | _SubscribeAny_ | REF\n| | ___DoExtend___[🔗](#extension) | | | false\n| | __Behaviours__ | | |\n| | | ___SubPath___[🔗](#extension.sub-path) | |\n| | | | ___KeepTrailingSep___[🔗](#extension.sub-path) | _true_\n| | | __Sort__ | |\n| | | | ___IsCaseSensitive___[🔗](#o.store.behaviours.sort.is-case-sensitive) | _false_\n| | | | ___DirectoryEntryOrder___[🔗](#o.store.behaviours.sort.directory-entry-order) | _DirectoryEntryOrderFoldersFirstEn_\n| | | ___Listen___[🔗](#listening) | |\n| | | | ___InclusiveStart___[🔗](#listening) | _true_\n| | | | ___InclusiveStop___[🔗](#listening)  | _false_\n| | __Logging__[🔗](#o.store.logging) | | |\n| | | ___Path___ | | _~/snivilised.extendio.nav.log_ |\n| | | ___TimeStampFormat___ | | _2006-01-02 15:04:05_ |\n| | | ___Level___ | | _InfoLevel_ |\n| | | Rotation | | | |\n| | | | ___MaxSizeInMb___ | _50_ |\n| | | | ___MaxNoOfBackups___ | _3_ |\n| | | | ___MaxAgeInDays___ | _28_ |\n| Callback | | | | ❌ (mandatory)\n| __Notify__[🔗](#notifications)            | | |\n| | ___OnBegin___ | | | _no-op_\n| | ___OnEnd___ | | | _no-op_\n| | ___OnDescend___ | | | _no-op_\n| | ___OnAscend___ | | | _no-op_\n| | ___OnStart___ | | | _no-op_\n| | ___OnStop___ | | | _no-op_\n| __Hooks__[🔗](#hooks)             | | |\n| | ___QueryStatus___ | | | LstatHookFn\n| | ___ReadDirectory___ | | | ReadEntries\n| | ___FolderSubPath___ | | | RootParentSubPath\n| | ___FileSubPath___ | | | RootParentSubPath\n| | ___InitFilters___ | | | InitFiltersHookFn\n| | ___Sort___ | | | CaseSensitiveSortHookFn / CaseInSensitiveSortHookFn\n| | ___Extend___ | | | DefaultExtendHookFn / _no-op_\n| __Listen__[🔗](#listening)            | | |\n| | Start | | | _no-op_\n| | Stop  | | | _no-op_\n| __Persist__[🔗](#options.persist)           | | |\n| | Format  | | | PersistInJSONEn\n\n\u003ca name=\"o.store\"\u003e\u003c/a\u003e\n\n##### Options.Store\n\n\u003ca name=\"o.store.behaviours.sort.is-case-sensitive\"\u003e\u003c/a\u003e\n\n- `Sort.IsCaseSensitive`: blah\n\n\u003ca name=\"o.store.behaviours.sort.directory-entry-order\"\u003e\u003c/a\u003e\n\n- `Sort.DirectoryEntryOrder`: blah\n\n\u003ca name=\"o.store.logging\"\u003e\u003c/a\u003e\n\n- `Logging`: blah\n\n\u003ca name=\"options.hooks\"\u003e\u003c/a\u003e\n\n##### Options.Hooks\n\n\u003ca name=\"options.listen\"\u003e\u003c/a\u003e\n\n##### Options.Listen\n\n\u003ca name=\"options.persist\"\u003e\u003c/a\u003e\n\n##### Options.Persist\n\n### 🥥 Subscription Types\n\nA subscription defines which file system item type the callback gets invoked for. The client can make a subscription of one of the following types:\n\n- __files__ (`SubscribeFiles`): callback invoked for file items only\n- __folders__ (`SubscribeFolders`): callback invoked for folder items only\n- __folders with files__ (`SubscribeFoldersWithFiles`): callback invoked for folder items only, but includes all files that are contained inside the folder, as the `Children` property of `TraverseItem` that the callback is invoked with\n- __all__ (`SubscribeAny`): callback invoked for files and folders\n\n\u003ca name=\"scopes\"\u003e\u003c/a\u003e\n\n### 🍉 Scopes\n\nExtra semantics have been assigned to folders which allows for enhanced filtering. Each folder is allocated a ___scope___ depending on a combination of factors including the depth of that folder relative to the ___root___ and whether the folder contains any child folders. Available scopes are:\n\n- __root__: the ___root___ node, ie the path specified by the user to start traversing from\n- __top__: any node that is a direct descendent of the ___root___ node\n- __leaf__: any node that has no sub folders\n- __intermediate__: nodes which are neither ___root___, ___top___ or ___leaf___ nodes\n\nA node may contain multiple scope designations. The following are valid combinations:\n\n- __root__ and __leaf__\n- __top__ and __leaf__\n\n\u003ca name=\"filters\"\u003e\u003c/a\u003e\n\n### 🍒 Filters\n\nThere are 2 categories of filters, a ___node___ filter (defined in options at `Options.Store.FilterDefs.Node`) and a ___child___ filter (defined at `Options.Store.FilterDefs.Children`). The ___node___ filter is applied to a single entity (the file system item, for which the ___callback___ is being invoked for), where as the ___child___ filter is a compound filter which is applied to a collection, ie the list of the current folder's file items (for subscription type ___folders with files___).\n\n\u003ca name=\"filter-types\"\u003e\u003c/a\u003e\n\n#### Filter Types\n\nThe following filter types are available:\n\n- __regex__: ___built in___ filter by a Go regular expression\n- __glob__: ___built in___ filter by a glob pattern, characterised by use of *\n- __custom__: allows the client to perform custom filtering\n\n___built in___ filters also benefit from the following features\n\n- __negation__: a filter's logic can be reversed, by setting the `Negate` property of the `FilterDef` to `true`. Any node will now only be invoked for, if it does not match the defined pattern.\n- __scope__: a filter can be restricted to only be applied to those matching the defined ___scope___. Eg a filter may specify a scope of ___intermediate___ which means that it is only applicable to ___intermediate___ nodes. To turn off scope based filtering, use the all scope (`ScopeAllEn`) in the filter definition (`FilterDef.Scope`)\n- __ifNotApplicable__: when scope filtering is in use, we can also change the behaviour of the filter if it is not applicable to the node. By default, if the filter is not applicable, the ___callback___ will not be invoked for that node. The client can invert this behaviour so that if the filter is not applicable, then the filter should not activate and allow the callback to be invoked. To use this, the `FilterDef`'s `IfNotApplicable` property should be set to `true`.\n\n\u003ca name=\"extension\"\u003e\u003c/a\u003e\n\n### 🍓 Extension\n\nThe ___Extension___ provides extra information contained in the `TraverseItem` that is passed to the client callback. To request the ___Extension___, the client should set the `DoExtend` property in the traverse options at `Options.Store.DoExtend` to `true`.\n\n⚠ Warning: Only use the properties on the ___Extension___ (`TraverseItem.Extension`) if the `DoExtend` described above has been set to true. If ___Extension___ is not active, attempting to reference a field  on it will result in a panic.\n\n___Extension___ properties include the following:\n\n- __Depth__: traversal depth relative to the root\n- __IsLeaf__: is the item a ___leaf___ node (file items are always ___leaf___ nodes)\n- __Name__: is just the name portion of the item's path (`TraverseItem.Path`)\n- __Parent__: is the parent path of the current node\n- __SubPath__: represents the relative path between the ___root___ and the current node\n- __NodeScope__: scope designation applied to the current node\n- __Custom__: a client defined property that can be set by overriding the ___Extension___ (see next)\n\nThe Extension can be overridden using the hook function. The default ___Extension___ hook is implemented by exported function `DefaultExtendHookFn`. The client needs to set a custom extend function on the options at: `Options.Hooks.Extend`. See [hooks](#hooks) for function signature. If the client just needs to augment the default functionality rather than replace it, in the custom function implemented by the client, just needs to invoke the default function `DefaultExtendHookFn`.\n\n\u003ca name=\"extension.sub-path\"\u003e\u003c/a\u003e\n\n#### Behaviours.SubPath\n\nWhen composing the `SubPath` on the ___Extension___, 2 hooks are employed, 1 for files `FileSubPath` and the other for folders `FolderSubPath`. The ___SubPath___ created by both of these can be configured to retain a trailing path separator using option setting `Options.Store.Behaviours.SubPath.KeepTrailingSep` which defaults to `true`.\n\n\u003ca name=\"hooks\"\u003e\u003c/a\u003e\n\n### ⛏️ Hooks\n\nThe behaviour of the traversal process can be modified by use of the declared hooks. The following shows the hooks with the function type and default hook indicated inside brackets:\n\n- `QueryStatus` (`QueryStatusHookFn`, `LstatHookFn`): acquires the `fs.FileInfo` entry of the ___root___ node\n- `ReadDirectory` (`ReadDirectoryHookFn`, `ReadEntries`): reads the contents of a directory\n- `FolderSubPath` (`SubPathHookFn`, `RootParentSubPath`): used to populate the `SubPath` property of `TraverseItem.Extension` for folder nodes\n- `FileSubPath` (`SubPathHookFn`, `RootParentSubPath`): used to populate the `SubPath` property of `TraverseItem.Extension` for file nodes\n- `InitFilters` (`FilterInitHookFn`, `InitFiltersHookFn`): filter initialisation function\n- `Sort` (`SortEntriesHookFn`, set depending on value of `Options.Store.Behaviours.Sort.IsCaseSensitive`): sorting function\nWhen `Options.Store.Behaviours.Sort.IsCaseSensitive` is set to `true`, then the default function is `CaseSensitiveSortHookFn` otherwise `CaseInSensitiveSortHookFn`\n- `Extend` (`ExtendHookFn`, set depending on value of `Options.Store.DoExtend`): When `Options.Store.DoExtend` is set to `true`, then the default function is `DefaultExtendHookFn` otherwise set to an internally defined no op function.\n\n\u003ca name=\"notifications\"\u003e\u003c/a\u003e\n\n### 🔔 Notifications\n\nEnables client to be called back during specific moments of the traversal. The following notifications are available (with the function type indicated inside brackets):\n\n- `OnBegin` (`BeginHandler`): beginning of traversal\n- `OnEnd` (`EndHandler`): end of traversal\n- `OnDescend` (`AscendancyHandler`): invoked as a folder is descended\n- `OnAscend` (`AscendancyHandler`): invoked as a folder is ascended\n- `OnStart` (`ListenHandler`): start listening condition met (if listening enabled)\n- `OnStop` (`ListenHandler`): finish listening condition met (if listening enabled)\n\n\u003ca name=\"listening\"\u003e\u003c/a\u003e\n\n### 🎧 Listening\n\nThe ___Listen___ feature allows the client to define a particular condition when callback invocation is to start and when to stop. The client does this by defining predicate functions in the options at `Options.Listen.Start` and `Options.Listen.Stop`.\n\nThe client can choose to define either or both of the ___Listen___ events. If ___Start___ is defined, then once traversal begins, the callback will not be invoked until the first node is encountered that satisfies the condition. If ___Stop___ is defined, then the callback will cease to be called at the point when the ___End___ predicate fires and the traversal is ended early.\n\nThe ___Start___ and ___Stop___ conditions are defined using `ListenBy`, eg:\n\n```go\n  session.Configure(func(o *nav.TraverseOptions) {\n    o.Store.Behaviours.Listen.InclusiveStart = true\n    o.Store.Behaviours.Listen.InclusiveStop = false\n    o.Listen.Start =   nav.ListenBy{\n      Name: \"Start listening at Night Drive\",\n      Fn: func(item *nav.TraverseItem) bool {\n        return item.Extension.Name == \"Night Drive\"\n      },\n    }\n    o.Listen.Stop = nav.ListenBy{\n      Name: \"Stop listening at Electric Youth\",\n      Fn: func(item *nav.TraverseItem) bool {\n        return item.Extension.Name == \"Electric Youth\"\n      },\n    }\n  })\n```\n\n📝 ___Points of Note___:\n\n- start listening when node found whose name is \"Night Drive\"\n- stop listening when node found whose name is \"Electric Youth\"\n- `InclusiveStart` and `InclusiveStop` shown in this example are the default values so do not need to be specified, (just showed here for illustration). The ___Inclusive___ settings allows the client to adjust whether the callback is invoked at the time the predicate is fired. When ___Inclusive___ is true, the callback is invoked for the current item. When false, the callback is not invoked for the current node item. So for the default settings, the callback is invoked when the ___Start___ predicate fires, but not when the ___Stop___ predicate fires (inclusive for ___Start___ and exclusive for ___Stop___)\n- the predicates for ___Start___ and for ___Stop___ are defined by the `Listener` interface. This means that the client can use a filter to define these predicates, the previous example defined with filters is shown as follows:\n\n💥 NOT IMPLEMENTED YET see issue #125\n\n```go\n  session.Configure(func(o *nav.TraverseOptions) {\n    o.Listen.Start =   nav.RegexFilter{\n      Filter: nav.Filter{\n        Name:            \"Start listening at Night Drive\",\n        RequiredScope:   nav.ScopeAllEn,\n        Pattern:         \"^Night Drive$\",\n      },\n    }\n    o.Listen.Start =   nav.GlobFilter{\n      Filter: nav.Filter{\n        Name:            \"Stop listening at Electric Youth\",\n        RequiredScope:   nav.ScopeAllEn,\n        Pattern:         \"Electric Youth\",\n      },\n    }\n  })\n```\n\n\u003ca name=\"logging\"\u003e\u003c/a\u003e\n\n### 🎬 Logging\n\n\u003ca name=\"other-utils\"\u003e\u003c/a\u003e\n\n### 🧰 Other Utils\n\n\u003ca name=\"development\"\u003e\u003c/a\u003e\n\n## 🔨 Development\n\n___RxGo___\n\n\u003cp align=\"left\"\u003e\n  \u003ca href=\"https://rxjs.dev/guide/overview\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/6407041?s=200\u0026v=4\" width=\"50\" alt=\"rxjs\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nTo support concurrency features, Extendio uses the reactive model provided by [RxGo](https://github.com/ReactiveX/RxGo). However, since ___RxGo___ seems to be a dead project with its last release in April 2021 and its unit tests not currently running successfully, the decision has been made to re-implement this locally. One of the main reasons for the project no longer being actively maintained is the release of generics feature in Go version 1.18, and supporting generics in RxGo would require significant effort to re-write the entire library. While work on this has begun, it's unclear when this will be delivered. Despite this, the reactive model's support for concurrency is highly valued, and Extendio aims to make use of a minimal functionality set for parallel processing during directory traversal. The goal is to replace it with the next version of RxGo when it becomes available.\n\nSee:\n\n- [Github Development Workflow](./resources/doc/GITHUB-DEV.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnivilised%2Fextendio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnivilised%2Fextendio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnivilised%2Fextendio/lists"}