import { AppInfo } from "../appInfo.js"
import { Maybe } from "../common/types.js"
export enum RootBranch {
Public = "public",
Pretty = "p",
Private = "private",
PrivateLog = "privateLog",
Shared = "shared",
SharedCounter = "sharedCounter",
Version = "version"
}
export enum Kind {
Directory = "directory",
File = "file"
}
export type Segment = string
export type Segments = Segment[]
export type SegmentsNonEmpty = [ Segment, ...Segments ]
export type Partitioned
= [ P, ...Segments ]
export type PartitionedNonEmpty
= [ P, Segment, ...Segments ]
/**
* Private partition
*/
export type Private = "private" | RootBranch.Private
/**
* Public partition
*/
export type Public = "public" | RootBranch.Public
/**
* `RootBranch`es that are accessible through the POSIX file system interface.
*/
export type Partition = Private | Public
/**
* A directory path.
*/
export type DirectoryPath
= { directory: P }
/**
* A file path.
*/
export type FilePath
= { file: P }
/**
* A file or directory path.
*/
export type DistinctivePath
= DirectoryPath
| FilePath
/**
* Alias for `DirectoryPath`
*/
export type Directory
= DirectoryPath
/**
* Alias for `FilePath`
*/
export type File
= FilePath
/**
* Alias for `DistinctivePath`
*/
export type Distinctive
= DistinctivePath
// CREATION
/**
* Utility function to create a `DirectoryPath`
*/
export function directory
(...args: PartitionedNonEmpty
): DirectoryPath>
export function directory(...args: Partitioned
): DirectoryPath>
export function directory(...args: SegmentsNonEmpty): DirectoryPath
export function directory(...args: Segments): DirectoryPath
export function directory(...args: Segments): DirectoryPath {
if (args.some(p => p.includes("/"))) {
throw new Error("Forward slashes `/` are not allowed")
}
return { directory: args }
}
/**
* Utility function to create a `FilePath`
*/
export function file(...args: PartitionedNonEmpty
): FilePath>
export function file(...args: SegmentsNonEmpty): FilePath
export function file(...args: Segments): FilePath
export function file(...args: Segments): FilePath {
if (args.some(p => p.includes("/"))) {
throw new Error("Forward slashes `/` are not allowed")
}
return { file: args }
}
/**
* Utility function to create a root `DirectoryPath`
*/
export function root(): DirectoryPath {
return { directory: [] }
}
/**
* Utility function create an app data path.
*/
export function appData(app: AppInfo): DirectoryPath>
export function appData(app: AppInfo, suffix: FilePath): FilePath>
export function appData(app: AppInfo, suffix: DirectoryPath): DirectoryPath>
export function appData(app: AppInfo, suffix: DistinctivePath): DistinctivePath>
export function appData(app: AppInfo, suffix?: DistinctivePath): DistinctivePath> {
const appDir = directory(RootBranch.Private, "Apps", app.creator, app.name)
return suffix ? combine(appDir, suffix) : appDir
}
// POSIX
/**
* Transform a string into a `DistinctivePath`.
*
* Directories should have the format `path/to/dir/` and
* files should have the format `path/to/file`.
*
* Leading forward slashes are removed too, so you can pass absolute paths.
*/
export function fromPosix(path: string): DistinctivePath {
const split = path.replace(/^\/+/, "").split("/")
if (path.endsWith("/")) return { directory: split.slice(0, -1) }
else if (path === "") return root()
return { file: split }
}
/**
* Transform a `DistinctivePath` into a string.
*
* Directories will have the format `path/to/dir/` and
* files will have the format `path/to/file`.
*/
export function toPosix(
path: DistinctivePath,
{ absolute }: { absolute: boolean } = { absolute: false }
): string {
const prefix = absolute ? "/" : ""
const joinedPath = unwrap(path).join("/")
if (isDirectory(path)) return prefix + joinedPath + (joinedPath.length ? "/" : "")
return prefix + joinedPath
}
// 🛠
/**
* Combine two `DistinctivePath`s.
*/
export function combine(a: DirectoryPath>, b: FilePath): FilePath>
export function combine(a: DirectoryPath>, b: FilePath): FilePath>
export function combine(a: DirectoryPath>, b: FilePath): FilePath>
export function combine(a: DirectoryPath, b: FilePath): FilePath
export function combine(a: DirectoryPath>, b: DirectoryPath): DirectoryPath>
export function combine(a: DirectoryPath>, b: DirectoryPath): DirectoryPath>
export function combine(a: DirectoryPath>, b: DirectoryPath): DirectoryPath>
export function combine(a: DirectoryPath, b: DirectoryPath): DirectoryPath
export function combine(a: DirectoryPath>, b: DistinctivePath): DistinctivePath>
export function combine(a: DirectoryPath>, b: DistinctivePath): DistinctivePath>
export function combine(a: DirectoryPath>, b: DistinctivePath): DistinctivePath>
export function combine(a: DirectoryPath, b: DistinctivePath): DistinctivePath
export function combine(a: DirectoryPath, b: DistinctivePath): DistinctivePath {
return map(p => unwrap(a).concat(p), b)
}
/**
* Is this `DistinctivePath` a directory?
*/
export function isDirectory(path: DistinctivePath
): path is DirectoryPath
{
return !!(path as DirectoryPath
).directory
}
/**
* Is this `DistinctivePath` a file?
*/
export function isFile
(path: DistinctivePath
): path is FilePath
{
return !!(path as FilePath
).file
}
/**
* Is this `DistinctivePath` of the given `Partition`?
*/
export function isPartition(partition: Partition, path: DistinctivePath): boolean {
return unwrap(path)[ 0 ] === partition
}
/**
* Is this `DistinctivePath` on the given `RootBranch`?
*/
export function isOnRootBranch(rootBranch: RootBranch, path: DistinctivePath): boolean {
return unwrap(path)[ 0 ] === rootBranch
}
/**
* Is this `DirectoryPath` a root directory?
*/
export function isRootDirectory(path: DirectoryPath): boolean {
return path.directory.length === 0
}
/**
* Check if two `DistinctivePath` have the same `Partition`.
*/
export function isSamePartition(a: DistinctivePath, b: DistinctivePath): boolean {
return unwrap(a)[ 0 ] === unwrap(b)[ 0 ]
}
/**
* Check if two `DistinctivePath` are of the same kind.
*/
export function isSameKind(a: DistinctivePath, b: DistinctivePath): boolean {
if (isDirectory(a) && isDirectory(b)) return true
else if (isFile(a) && isFile(b)) return true
else return false
}
/**
* What `Kind` of path are we dealing with?
*/
export function kind(path: DistinctivePath): Kind {
if (isDirectory(path)) return Kind.Directory
return Kind.File
}
/**
* Map a `DistinctivePath`.
*/
export function map(fn: (p: A) => B, path: DistinctivePath): DistinctivePath {
if (isDirectory(path)) return { directory: fn(path.directory) }
else if (isFile(path)) return { file: fn(path.file) }
return path
}
/**
* Get the parent directory of a `DistinctivePath`.
*/
export function parent(path: DistinctivePath<[ Partition, Segment, Segment, ...Segments ]>): DirectoryPath>
export function parent(path: DistinctivePath<[ Segment, Segment, Segment, ...Segments ]>): DirectoryPath
export function parent(path: DistinctivePath>): DirectoryPath>
export function parent(path: DistinctivePath<[ Partition, Segment ]>): DirectoryPath>
export function parent(path: DistinctivePath>): DirectoryPath
export function parent(path: DistinctivePath): DirectoryPath
export function parent(path: DistinctivePath<[ Segment ]>): DirectoryPath<[]>
export function parent(path: DistinctivePath<[]>): null
export function parent(path: DistinctivePath): Maybe>
export function parent(path: DistinctivePath): Maybe> {
return isDirectory(path) && isRootDirectory(path as DirectoryPath)
? null
: directory(...unwrap(path).slice(0, -1))
}
/**
* Remove the `Partition` of a `DistinctivePath` (ie. the top-level directory)
*/
export function removePartition(path: DistinctivePath): DistinctivePath {
return map(
p => isDirectory(path) || p.length > 1 ? p.slice(1) : p,
path
)
}
/**
* Get the last part of the path.
*/
export function terminus(path: DistinctivePath): Maybe {
const u = unwrap(path)
if (u.length < 1) return null
return u[ u.length - 1 ]
}
/**
* Unwrap a `DistinctivePath`.
*/
export function unwrap(path: DistinctivePath
): P {
if (isDirectory(path)) {
return path.directory
} else if (isFile(path)) {
return path.file
}
throw new Error("Path is neither a directory or a file")
}
/**
* Utility function to prefix a path with a `Partition`.
*/
export function withPartition
(partition: P, path: DirectoryPath): DirectoryPath>
export function withPartition(partition: P, path: DirectoryPath): DirectoryPath>
export function withPartition(partition: P, path: FilePath): FilePath>
export function withPartition(partition: P, path: FilePath): FilePath>
export function withPartition(partition: P, path: DistinctivePath): DistinctivePath>
export function withPartition(partition: P, path: DistinctivePath): DistinctivePath>
export function withPartition(partition: P, path: DistinctivePath): DistinctivePath> {
return combine(
directory(partition),
path
)
}
// ⚗️
/**
* Render a raw `Path` to a string for logging purposes.
*/
export function log(path: Segments): string {
return `[ ${path.join(", ")} ]`
}