UNPKG

4.66 kBPlain TextView Raw
1import { Maybe } from './common/types'
2
3
4export enum Branch {
5 Public = 'public',
6 Pretty = 'p',
7 Private = 'private',
8 PrivateLog = 'privateLog',
9 Version = 'version'
10}
11
12export enum Kind {
13 Directory = "directory",
14 File = "file"
15}
16
17export type Path = string[]
18
19export type DirectoryPath = { directory: Path }
20export type FilePath = { file: Path }
21
22/**
23 * The primarily used type for paths.
24 */
25export type DistinctivePath = DirectoryPath | FilePath
26
27
28
29// CREATION
30
31
32/**
33 * Utility function to create a `DirectoryPath`
34 */
35export function directory(...args: Path): DirectoryPath {
36 if (args.some(p => p.includes("/"))) {
37 throw new Error("Forward slashes `/` are not allowed")
38 }
39 return { directory: args }
40}
41
42/**
43 * Utility function to create a `FilePath`
44 */
45export function file(...args: Path): FilePath {
46 if (args.some(p => p.includes("/"))) {
47 throw new Error("Forward slashes `/` are not allowed")
48 }
49 return { file: args }
50}
51
52/**
53 * Utility function to create a root `DirectoryPath`
54 */
55export function root(): DirectoryPath {
56 return { directory: [] }
57}
58
59
60
61// POSIX
62
63
64/**
65 * Transform a string into a `DistinctivePath`.
66 *
67 * Directories should have the format `path/to/dir/` and
68 * files should have the format `path/to/file`.
69 *
70 * Leading forward slashes are removed too, so you can pass absolute paths.
71 */
72export function fromPosix(path: string): DistinctivePath {
73 const split = path.replace(/^\/+/, "").split("/")
74 if (path.endsWith("/")) return { directory: split.slice(0, -1) }
75 else if (path === "") return root()
76 return { file: split }
77}
78
79/**
80 * Transform a `DistinctivePath` into a string.
81 *
82 * Directories will have the format `path/to/dir/` and
83 * files will have the format `path/to/file`.
84 */
85export function toPosix(
86 path: DistinctivePath,
87 { absolute }: { absolute: boolean } = { absolute: false }
88): string {
89 const prefix = absolute ? "/" : ""
90 const joinedPath = unwrap(path).join("/")
91 if (isDirectory(path)) return prefix + joinedPath + (joinedPath.length ? "/" : "")
92 return prefix + joinedPath
93}
94
95
96
97// 🛠
98
99
100/**
101 * Combine two `DistinctivePath`s.
102 */
103export function combine(a: DirectoryPath, b: DistinctivePath): DistinctivePath {
104 return map(p => unwrap(a).concat(p), b)
105}
106
107/**
108 * Is this `DistinctivePath` of the given `Branch`?
109 */
110export function isBranch(branch: Branch, path: DistinctivePath): boolean {
111 return unwrap(path)[0] === branch
112}
113
114/**
115 * Is this `DistinctivePath` a directory?
116 */
117export function isDirectory(path: DistinctivePath): path is DirectoryPath {
118 return !!(path as DirectoryPath).directory
119}
120
121/**
122 * Is this `DistinctivePath` a file?
123 */
124export function isFile(path: DistinctivePath): path is FilePath {
125 return !!(path as FilePath).file
126}
127
128/**
129 * Is this `DirectoryPath` a root directory?
130 */
131export function isRootDirectory(path: DirectoryPath): boolean {
132 return path.directory.length === 0
133}
134
135/**
136 * Check if two `DistinctivePath` have the same `Branch`.
137 */
138export function isSameBranch(a: DistinctivePath, b: DistinctivePath): boolean {
139 return unwrap(a)[0] === unwrap(b)[0]
140}
141
142/**
143 * Check if two `DistinctivePath` are of the same kind.
144 */
145export function isSameKind(a: DistinctivePath, b: DistinctivePath): boolean {
146 if (isDirectory(a) && isDirectory(b)) return true
147 else if (isFile(a) && isFile(b)) return true
148 else return false
149}
150
151/**
152 * What `Kind` of path are we dealing with?
153 */
154export function kind(path: DistinctivePath): Kind {
155 if (isDirectory(path)) return Kind.Directory
156 return Kind.File
157}
158
159/**
160 * Map a `DistinctivePath`.
161 */
162export function map(fn: (p: Path) => Path, path: DistinctivePath): DistinctivePath {
163 if (isDirectory(path)) return { directory: fn(path.directory) }
164 else if (isFile(path)) return { file: fn(path.file) }
165 return path
166}
167
168/**
169 * Get the parent directory of a `DistinctivePath`.
170 */
171export function parent(path: DistinctivePath): Maybe<DirectoryPath> {
172 return isDirectory(path) && isRootDirectory(path as DirectoryPath)
173 ? null
174 : directory(...unwrap(path).slice(0, -1))
175}
176
177/**
178 * Remove the `Branch` of a `DistinctivePath` (ie. the top-level directory)
179 */
180export function removeBranch(path: DistinctivePath): DistinctivePath {
181 return map(
182 p => isDirectory(path) || p.length > 1 ? p.slice(1) : p,
183 path
184 )
185}
186
187/**
188 * Unwrap a `DistinctivePath`.
189 */
190export function unwrap(path: DistinctivePath): Path {
191 if (isDirectory(path)) {
192 return path.directory
193 } else if (isFile(path)) {
194 return path.file
195 }
196
197 return []
198}
199
200
201
202// ⚗️
203
204
205/**
206 * Render a raw `Path` to a string for logging purposes.
207 */
208export function log(path: Path): string {
209 return `[ ${path.join(", ")} ]`
210}