staticky-files 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +39 -0
- data/Rakefile +7 -0
- data/lib/staticky/files/adapter.rb +17 -0
- data/lib/staticky/files/error.rb +84 -0
- data/lib/staticky/files/file_system.rb +333 -0
- data/lib/staticky/files/memory_file_system/node.rb +170 -0
- data/lib/staticky/files/memory_file_system.rb +378 -0
- data/lib/staticky/files/path.rb +95 -0
- data/lib/staticky/files/version.rb +7 -0
- data/lib/staticky/files.rb +984 -0
- data/lib/staticky-files.rb +3 -0
- metadata +62 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Staticky
|
6
|
+
class Files
|
7
|
+
class MemoryFileSystem
|
8
|
+
# Memory file system node (directory or file)
|
9
|
+
#
|
10
|
+
# File modes implementation inspired by https://www.calleluks.com/flags-bitmasks-and-unix-file-system-permissions-in-ruby/
|
11
|
+
class Node
|
12
|
+
MODE_USER_READ = 0b100000000
|
13
|
+
MODE_USER_WRITE = 0b010000000
|
14
|
+
MODE_USER_EXECUTE = 0b001000000
|
15
|
+
MODE_GROUP_READ = 0b000100000
|
16
|
+
MODE_GROUP_WRITE = 0b000010000
|
17
|
+
MODE_GROUP_EXECUTE = 0b000001000
|
18
|
+
MODE_OTHERS_READ = 0b000000100
|
19
|
+
MODE_OTHERS_WRITE = 0b000000010
|
20
|
+
MODE_OTHERS_EXECUTE = 0b000000001
|
21
|
+
|
22
|
+
# Default directory mode: 0755
|
23
|
+
DEFAULT_DIRECTORY_MODE = MODE_USER_READ | MODE_USER_WRITE | MODE_USER_EXECUTE |
|
24
|
+
MODE_GROUP_READ | MODE_GROUP_EXECUTE |
|
25
|
+
MODE_OTHERS_READ | MODE_GROUP_EXECUTE
|
26
|
+
|
27
|
+
# Default file mode: 0644
|
28
|
+
DEFAULT_FILE_MODE = MODE_USER_READ | MODE_USER_WRITE | MODE_GROUP_READ | MODE_OTHERS_READ
|
29
|
+
|
30
|
+
MODE_BASE = 16
|
31
|
+
ROOT_PATH = "/"
|
32
|
+
|
33
|
+
# Instantiate a root node
|
34
|
+
#
|
35
|
+
# @return [Staticky::Files::MemoryFileSystem::Node] the root node
|
36
|
+
def self.root
|
37
|
+
new(ROOT_PATH)
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :segment, :mode, :children
|
41
|
+
|
42
|
+
# Instantiate a new node.
|
43
|
+
# It's a directory node by default.
|
44
|
+
#
|
45
|
+
# @param segment [String] the path segment of the node
|
46
|
+
# @param mode [Integer] the UNIX mode
|
47
|
+
#
|
48
|
+
# @return [Staticky::Files::MemoryFileSystem::Node] the new node
|
49
|
+
#
|
50
|
+
# @see #mode=
|
51
|
+
def initialize(segment, mode = DEFAULT_DIRECTORY_MODE)
|
52
|
+
@segment = segment
|
53
|
+
@children = nil
|
54
|
+
@content = nil
|
55
|
+
|
56
|
+
self.chmod = mode
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get a node child
|
60
|
+
#
|
61
|
+
# @param segment [String] the child path segment
|
62
|
+
#
|
63
|
+
# @return [Staticky::Files::MemoryFileSystem::Node,NilClass] the child
|
64
|
+
# node, if found
|
65
|
+
def get(segment)
|
66
|
+
@children&.fetch(segment, nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set a node child
|
70
|
+
#
|
71
|
+
# @param segment [String] the child path segment
|
72
|
+
def set(segment)
|
73
|
+
@children ||= {}
|
74
|
+
@children[segment] ||= self.class.new(segment)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Unset a node child
|
78
|
+
#
|
79
|
+
# @param segment [String] the child path segment
|
80
|
+
#
|
81
|
+
# @raise [Staticky::Files::UnknownMemoryNodeError] if the child node cannot be found
|
82
|
+
def unset(segment)
|
83
|
+
@children ||= {}
|
84
|
+
raise UnknownMemoryNodeError, segment unless @children.key?(segment)
|
85
|
+
|
86
|
+
@children.delete(segment)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check if node is a directory
|
90
|
+
#
|
91
|
+
# @return [TrueClass,FalseClass] the result of the check
|
92
|
+
def directory?
|
93
|
+
!file?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if node is a file
|
97
|
+
#
|
98
|
+
# @return [TrueClass,FalseClass] the result of the check
|
99
|
+
def file?
|
100
|
+
!@content.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Read file contents
|
104
|
+
#
|
105
|
+
# @return [String] the file contents
|
106
|
+
#
|
107
|
+
# @raise [Staticky::Files::NotMemoryFileError] if node isn't a file
|
108
|
+
def read
|
109
|
+
raise NotMemoryFileError, segment unless file?
|
110
|
+
|
111
|
+
@content.rewind
|
112
|
+
@content.read
|
113
|
+
end
|
114
|
+
|
115
|
+
# Read file content lines
|
116
|
+
#
|
117
|
+
# @return [Array<String>] the file content lines
|
118
|
+
#
|
119
|
+
# @raise [Staticky::Files::NotMemoryFileError] if node isn't a file
|
120
|
+
def readlines
|
121
|
+
raise NotMemoryFileError, segment unless file?
|
122
|
+
|
123
|
+
@content.rewind
|
124
|
+
@content.readlines
|
125
|
+
end
|
126
|
+
|
127
|
+
# Write file contents
|
128
|
+
# IMPORTANT: This operation turns a node into a file
|
129
|
+
#
|
130
|
+
# @param content [String, Array<String>] the file content
|
131
|
+
#
|
132
|
+
# @raise [Staticky::Files::NotMemoryFileError] if node isn't a file
|
133
|
+
def write(content)
|
134
|
+
content = case content
|
135
|
+
when String
|
136
|
+
content
|
137
|
+
when Array
|
138
|
+
array_to_string(content)
|
139
|
+
when NilClass
|
140
|
+
EMPTY_CONTENT
|
141
|
+
end
|
142
|
+
|
143
|
+
@content = StringIO.new(content)
|
144
|
+
@mode = DEFAULT_FILE_MODE
|
145
|
+
end
|
146
|
+
|
147
|
+
# Set UNIX mode
|
148
|
+
# It accepts base 2, 8, 10, and 16 numbers
|
149
|
+
#
|
150
|
+
# @param mode [Integer] the file mode
|
151
|
+
def chmod=(mode)
|
152
|
+
@mode = mode.to_s(MODE_BASE).hex
|
153
|
+
end
|
154
|
+
|
155
|
+
# Check if node is executable for user
|
156
|
+
#
|
157
|
+
# @return [TrueClass,FalseClass] the result of the check
|
158
|
+
def executable?
|
159
|
+
(mode & MODE_USER_EXECUTE).positive?
|
160
|
+
end
|
161
|
+
|
162
|
+
def array_to_string(content)
|
163
|
+
content
|
164
|
+
.map { |line| line.sub(NEW_LINE_MATCHER, EMPTY_CONTENT) }
|
165
|
+
.join(NEW_LINE) + NEW_LINE
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,378 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "staticky/files/path"
|
4
|
+
|
5
|
+
module Staticky
|
6
|
+
class Files
|
7
|
+
# Memory File System abstraction to support `Staticky::Files`
|
8
|
+
class MemoryFileSystem # rubocop:disable Metrics/ClassLength
|
9
|
+
EMPTY_CONTENT = ""
|
10
|
+
|
11
|
+
require_relative "memory_file_system/node"
|
12
|
+
|
13
|
+
# Creates a new instance
|
14
|
+
#
|
15
|
+
# @param root [Staticky::Files::MemoryFileSystem::Node] the root node of
|
16
|
+
# the in-memory file system
|
17
|
+
#
|
18
|
+
# @return [Staticky::Files::MemoryFileSystem]
|
19
|
+
def initialize(root: Node.root)
|
20
|
+
@root = root
|
21
|
+
end
|
22
|
+
|
23
|
+
# Opens (or creates) a new file for read/write operations.
|
24
|
+
#
|
25
|
+
# @param path [String] the target file
|
26
|
+
# @yieldparam [Staticky::Files::MemoryFileSystem::Node]
|
27
|
+
# @return [Staticky::Files::MemoryFileSystem::Node]
|
28
|
+
def open(path, *)
|
29
|
+
file = touch(path)
|
30
|
+
|
31
|
+
if block_given?
|
32
|
+
yield file
|
33
|
+
else
|
34
|
+
file
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read file contents
|
39
|
+
#
|
40
|
+
# @param path [String, Array<String>] the target path
|
41
|
+
# @return [String] the file contents
|
42
|
+
#
|
43
|
+
# @raise [Staticky::Files::IOError] in case the target path is a directory
|
44
|
+
# or if the file cannot be found
|
45
|
+
def read(path)
|
46
|
+
path = Path[path]
|
47
|
+
raise IOError, Errno::EISDIR.new(path.to_s) if directory?(path)
|
48
|
+
|
49
|
+
file = find_file(path)
|
50
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if file.nil?
|
51
|
+
|
52
|
+
file.read
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reads the entire file specified by path as individual lines,
|
56
|
+
# and returns those lines in an array
|
57
|
+
#
|
58
|
+
# @param path [String, Array<String>] the target path
|
59
|
+
# @return [Array<String>] the file contents
|
60
|
+
#
|
61
|
+
# @raise [Staticky::Files::IOError] in case the target path is a directory
|
62
|
+
# or if the file cannot be found
|
63
|
+
def readlines(path)
|
64
|
+
path = Path[path]
|
65
|
+
node = find(path)
|
66
|
+
|
67
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
68
|
+
raise IOError, Errno::EISDIR.new(path.to_s) if node.directory?
|
69
|
+
|
70
|
+
node.readlines
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates a file, if it doesn't exist, and set empty content.
|
74
|
+
#
|
75
|
+
# If the file was already existing, it's a no-op.
|
76
|
+
#
|
77
|
+
# @param path [String, Array<String>] the target path
|
78
|
+
#
|
79
|
+
# @raise [Staticky::Files::IOError] in case the target path is a directory
|
80
|
+
def touch(path)
|
81
|
+
path = Path[path]
|
82
|
+
raise IOError, Errno::EISDIR.new(path.to_s) if directory?(path)
|
83
|
+
|
84
|
+
content = read(path) if exist?(path)
|
85
|
+
write(path, content || EMPTY_CONTENT)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Creates a new file or rewrites the contents
|
89
|
+
# of an existing file for the given path and content
|
90
|
+
# All the intermediate directories are created.
|
91
|
+
#
|
92
|
+
# @param path [String, Array<String>] the target path
|
93
|
+
# @param content [String, Array<String>] the content to write
|
94
|
+
def write(path, *content)
|
95
|
+
path = Path[path]
|
96
|
+
node = @root
|
97
|
+
|
98
|
+
for_each_segment(path) do |segment|
|
99
|
+
node = node.set(segment)
|
100
|
+
end
|
101
|
+
|
102
|
+
node.write(*content)
|
103
|
+
node
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a new string formed by joining the strings using Operating
|
107
|
+
# System path separator
|
108
|
+
#
|
109
|
+
# @param path [String,Array<String>] path tokens
|
110
|
+
#
|
111
|
+
# @return [String] the joined path
|
112
|
+
def join(*path)
|
113
|
+
Path[path]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Converts a path to an absolute path.
|
117
|
+
#
|
118
|
+
# @param path [String,Array<String>] the path to the file
|
119
|
+
# @param dir [String,Array<String>] the base directory
|
120
|
+
def expand_path(path, dir)
|
121
|
+
return path if Path.absolute?(path)
|
122
|
+
|
123
|
+
join(dir, path)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the name of the current working directory.
|
127
|
+
#
|
128
|
+
# @return [String] the current working directory.
|
129
|
+
def pwd
|
130
|
+
@root.segment
|
131
|
+
end
|
132
|
+
|
133
|
+
# Temporary changes the current working directory of the process to the
|
134
|
+
# given path and yield the given block.
|
135
|
+
#
|
136
|
+
# The argument `path` is intended to be a **directory**.
|
137
|
+
#
|
138
|
+
# @param path [String] the target directory
|
139
|
+
# @param blk [Proc] the code to execute with the target directory
|
140
|
+
#
|
141
|
+
# @raise [Staticky::Files::IOError] if path cannot be found or it isn't a
|
142
|
+
# directory
|
143
|
+
def chdir(path)
|
144
|
+
path = Path[path]
|
145
|
+
directory = find(path)
|
146
|
+
|
147
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if directory.nil?
|
148
|
+
raise IOError, Errno::ENOTDIR.new(path.to_s) unless directory.directory?
|
149
|
+
|
150
|
+
current_root = @root
|
151
|
+
@root = directory
|
152
|
+
yield
|
153
|
+
ensure
|
154
|
+
@root = current_root
|
155
|
+
end
|
156
|
+
|
157
|
+
# Creates a directory and all its parent directories.
|
158
|
+
#
|
159
|
+
# The argument `path` is intended to be a **directory** that you want to
|
160
|
+
# explicitly create.
|
161
|
+
#
|
162
|
+
# @see #mkdir_p
|
163
|
+
#
|
164
|
+
# @param path [String,Array<String>] the directory to create
|
165
|
+
#
|
166
|
+
# @raise [Staticky::Files::IOError] in case path is an already existing
|
167
|
+
# file
|
168
|
+
def mkdir(path)
|
169
|
+
path = Path[path]
|
170
|
+
node = @root
|
171
|
+
|
172
|
+
for_each_segment(path) do |segment|
|
173
|
+
node = node.set(segment)
|
174
|
+
raise IOError, Errno::EEXIST.new(path.to_s) if node.file?
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Creates a directory and all its parent directories.
|
179
|
+
#
|
180
|
+
# The argument `path` is intended to be a **file**, where its
|
181
|
+
# directory ancestors will be implicitly created.
|
182
|
+
#
|
183
|
+
# @see #mkdir
|
184
|
+
#
|
185
|
+
# @param path [String,Array<String>] the file that will be in the
|
186
|
+
# directories that this method creates
|
187
|
+
#
|
188
|
+
# @raise [Staticky::Files::IOError] in case of I/O error
|
189
|
+
def mkdir_p(path)
|
190
|
+
path = Path[path]
|
191
|
+
|
192
|
+
mkdir(
|
193
|
+
Path.dirname(path)
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Copies file content from `source` to `destination`
|
198
|
+
# All the intermediate `destination` directories are created.
|
199
|
+
#
|
200
|
+
# @param source [String,Array<String>] the file(s) or directory to copy
|
201
|
+
# @param destination [String,Array<String>] the directory destination
|
202
|
+
#
|
203
|
+
# @raise [Staticky::Files::IOError] if source cannot be found
|
204
|
+
def cp(source, destination)
|
205
|
+
content = read(source)
|
206
|
+
write(destination, content)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Removes (deletes) a file
|
210
|
+
#
|
211
|
+
# @param path [String,Array<String>] the file to remove
|
212
|
+
#
|
213
|
+
# @raise [Staticky::Files::IOError] if path cannot be found or it's
|
214
|
+
# a directory
|
215
|
+
#
|
216
|
+
# @see #rm_rf
|
217
|
+
def rm(path)
|
218
|
+
path = Path[path]
|
219
|
+
file = nil
|
220
|
+
parent = @root
|
221
|
+
node = @root
|
222
|
+
|
223
|
+
for_each_segment(path) do |segment|
|
224
|
+
break unless node
|
225
|
+
|
226
|
+
file = segment
|
227
|
+
parent = node
|
228
|
+
node = node.get(segment)
|
229
|
+
end
|
230
|
+
|
231
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
232
|
+
raise IOError, Errno::EPERM.new(path.to_s) if node.directory?
|
233
|
+
|
234
|
+
parent.unset(file)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Removes (deletes) a directory
|
238
|
+
#
|
239
|
+
# @param path [String,Array<String>] the directory to remove
|
240
|
+
#
|
241
|
+
# @raise [Staticky::Files::IOError] if path cannot be found
|
242
|
+
#
|
243
|
+
# @see #rm
|
244
|
+
def rm_rf(path)
|
245
|
+
path = Path[path]
|
246
|
+
file = nil
|
247
|
+
parent = @root
|
248
|
+
node = @root
|
249
|
+
|
250
|
+
for_each_segment(path) do |segment|
|
251
|
+
break unless node
|
252
|
+
|
253
|
+
file = segment
|
254
|
+
parent = node
|
255
|
+
node = node.get(segment)
|
256
|
+
end
|
257
|
+
|
258
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
259
|
+
|
260
|
+
parent.unset(file)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Sets node UNIX mode
|
264
|
+
#
|
265
|
+
# @param path [String,Array<String>] the path to the node
|
266
|
+
# @param mode [Integer] a UNIX mode, in base 2, 8, 10, or 16
|
267
|
+
#
|
268
|
+
# @raise [Staticky::Files::IOError] if path cannot be found
|
269
|
+
def chmod(path, mode)
|
270
|
+
path = Path[path]
|
271
|
+
node = find(path)
|
272
|
+
|
273
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
274
|
+
|
275
|
+
node.chmod = mode
|
276
|
+
end
|
277
|
+
|
278
|
+
# Gets node UNIX mode
|
279
|
+
#
|
280
|
+
# @param path [String,Array<String>] the path to the node
|
281
|
+
# @return [Integer] the UNIX mode
|
282
|
+
#
|
283
|
+
# @raise [Staticky::Files::IOError] if path cannot be found
|
284
|
+
def mode(path)
|
285
|
+
path = Path[path]
|
286
|
+
node = find(path)
|
287
|
+
|
288
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
289
|
+
|
290
|
+
node.mode
|
291
|
+
end
|
292
|
+
|
293
|
+
# Check if the given path exist.
|
294
|
+
#
|
295
|
+
# @param path [String,Array<String>] the path to the node
|
296
|
+
# @return [TrueClass,FalseClass] the result of the check
|
297
|
+
def exist?(path)
|
298
|
+
path = Path[path]
|
299
|
+
|
300
|
+
!find(path).nil?
|
301
|
+
end
|
302
|
+
|
303
|
+
# Check if the given path corresponds to a directory.
|
304
|
+
#
|
305
|
+
# @param path [String,Array<String>] the path to the directory
|
306
|
+
# @return [TrueClass,FalseClass] the result of the check
|
307
|
+
def directory?(path)
|
308
|
+
path = Path[path]
|
309
|
+
!find_directory(path).nil?
|
310
|
+
end
|
311
|
+
|
312
|
+
# Check if the given path is an executable.
|
313
|
+
#
|
314
|
+
# @param path [String,Array<String>] the path to the node
|
315
|
+
# @return [TrueClass,FalseClass] the result of the check
|
316
|
+
def executable?(path)
|
317
|
+
path = Path[path]
|
318
|
+
|
319
|
+
node = find(path)
|
320
|
+
return false if node.nil?
|
321
|
+
|
322
|
+
node.executable?
|
323
|
+
end
|
324
|
+
|
325
|
+
# Reads entries from a directory
|
326
|
+
#
|
327
|
+
# @param path [String,Pathname] the path to file
|
328
|
+
# @return [Array<String>] the entries
|
329
|
+
#
|
330
|
+
# @raise [Staticky::Files::IOError] in case of I/O error
|
331
|
+
def entries(path)
|
332
|
+
path = Path[path]
|
333
|
+
node = find(path)
|
334
|
+
raise IOError, Errno::ENOENT.new(path.to_s) if node.nil?
|
335
|
+
raise IOError, Errno::ENOTDIR.new(path.to_s) unless node.directory?
|
336
|
+
|
337
|
+
[".", ".."] + Array(node.children&.keys)
|
338
|
+
end
|
339
|
+
|
340
|
+
private
|
341
|
+
|
342
|
+
def for_each_segment(path, &blk)
|
343
|
+
segments = Path.split(path)
|
344
|
+
segments.each(&blk)
|
345
|
+
end
|
346
|
+
|
347
|
+
def find_directory(path)
|
348
|
+
node = find(path)
|
349
|
+
|
350
|
+
return if node.nil?
|
351
|
+
return unless node.directory?
|
352
|
+
|
353
|
+
node
|
354
|
+
end
|
355
|
+
|
356
|
+
def find_file(path)
|
357
|
+
node = find(path)
|
358
|
+
|
359
|
+
return if node.nil?
|
360
|
+
return unless node.file?
|
361
|
+
|
362
|
+
node
|
363
|
+
end
|
364
|
+
|
365
|
+
def find(path)
|
366
|
+
node = @root
|
367
|
+
|
368
|
+
for_each_segment(path) do |segment|
|
369
|
+
break unless node
|
370
|
+
|
371
|
+
node = node.get(segment)
|
372
|
+
end
|
373
|
+
|
374
|
+
node
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Files
|
5
|
+
# Cross Operating System path
|
6
|
+
#
|
7
|
+
# It's used by the memory adapter to ensure that hardcoded string paths
|
8
|
+
# are transformed into portable paths that respect the Operating System
|
9
|
+
# directory separator.
|
10
|
+
module Path
|
11
|
+
SEPARATOR = ::File::SEPARATOR
|
12
|
+
EMPTY_TOKEN = ""
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Transform the given path into a path that respect the Operating System
|
16
|
+
# directory separator.
|
17
|
+
#
|
18
|
+
# @param path [String,Pathname,Array<String,Pathname>] the path to
|
19
|
+
# transform
|
20
|
+
#
|
21
|
+
# @return [String] the resulting path
|
22
|
+
#
|
23
|
+
# @example Portable Path
|
24
|
+
# require "staticky/files/path"
|
25
|
+
#
|
26
|
+
# path = "path/to/file"
|
27
|
+
#
|
28
|
+
# Staticky::Files::Path.call(path)
|
29
|
+
# # => "path/to/file" on UNIX based Operating System
|
30
|
+
#
|
31
|
+
# Staticky::Files::Path.call(path)
|
32
|
+
# # => "path\to\file" on Windows Operating System
|
33
|
+
#
|
34
|
+
# @example Join Nested Tokens
|
35
|
+
# require "staticky/files/path"
|
36
|
+
#
|
37
|
+
# path = ["path", ["to", ["nested", "file"]]]
|
38
|
+
#
|
39
|
+
# Staticky::Files::Path.call(path)
|
40
|
+
# # => "path/to/nested/file" on UNIX based Operating System
|
41
|
+
#
|
42
|
+
# Staticky::Files::Path.call(path)
|
43
|
+
# # => "path\to\nested\file" on Windows Operating System
|
44
|
+
#
|
45
|
+
# @example Separator path
|
46
|
+
# require "staticky/files/path"
|
47
|
+
#
|
48
|
+
# path = ::File::SEPARATOR
|
49
|
+
#
|
50
|
+
# Staticky::Files::Path.call(path)
|
51
|
+
# # => ""
|
52
|
+
def call(*path)
|
53
|
+
path = Array(path).flatten
|
54
|
+
tokens = path.map do |token|
|
55
|
+
split(token)
|
56
|
+
end
|
57
|
+
|
58
|
+
tokens
|
59
|
+
.flatten
|
60
|
+
.join(SEPARATOR)
|
61
|
+
end
|
62
|
+
alias [] call
|
63
|
+
end
|
64
|
+
|
65
|
+
# Split path according to the current Operating System directory separator
|
66
|
+
#
|
67
|
+
# @param path [String,Pathname] the path to split
|
68
|
+
#
|
69
|
+
# @return [Array<String>] the split path
|
70
|
+
def self.split(path)
|
71
|
+
return EMPTY_TOKEN if path == SEPARATOR
|
72
|
+
|
73
|
+
path.to_s.split(%r{\\|/})
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check if given path is absolute
|
77
|
+
#
|
78
|
+
# @param path [String,Pathname] the path to check
|
79
|
+
#
|
80
|
+
# @return [TrueClass,FalseClass] the result of the check
|
81
|
+
def self.absolute?(path)
|
82
|
+
path.start_with?(SEPARATOR)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns all the path, except for the last token
|
86
|
+
#
|
87
|
+
# @param path [String,Pathname] the path to extract directory name from
|
88
|
+
#
|
89
|
+
# @return [String] the directory name
|
90
|
+
def self.dirname(path)
|
91
|
+
::File.dirname(path)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|