-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystem_utilities.lua
More file actions
257 lines (240 loc) · 10.9 KB
/
system_utilities.lua
File metadata and controls
257 lines (240 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
-- System utilities module
-- This module is intended to contain (extended) system specific utilities, including but not limited to perform file system checks
-- The purpose of pushing almost all system specific code to this module is to keep the main script and most other modules as system-agnostic as possible
local module = {}
local dkjson = require("./lib/dkjson/dkjson")
function module.remove_path_quotes(path)
-- Removes pairs of outer quotes from file and directory paths, yes, even stacked quotes
local first = string.sub(path, 1, 1)
local last = string.sub(path, #path, #path)
if not (first == "\"" and last == "\"") then
return path
end
return module.remove_path_quotes(string.sub(path, 2, #path - 1))
end
function module.get_parent_directory(path)
-- Returns the parent directory of a given path; nil on failure
-- Expects a file or directory path without a trailing slash, otherwise this will return the same path (minus the last slash)
-- The argument path is converted to a Unix path for consistency and to make processing easier; the output is a properly generated path
local work_path = module.to_unix_path(path)
local unix_dash = "/"
local last_dash_index
for i = #work_path, 1, -1 do
local character = string.sub(work_path, i, i)
if character == unix_dash then
last_dash_index = i
break
end
end
if not last_dash_index then
return
end
return module.generate_path(string.sub(work_path, 1, last_dash_index - 1))
end
function module.add_physics_directory(path)
-- This function adds the "physics" subdirectory right before the file name to the given path
-- Returns nil on failure
-- The argument path is converted to a Unix path for consistency and to make processing easier; the output is a properly generated path
local work_path = module.to_unix_path(path)
local unix_dash = "/"
local last_dash_index
local new_path
for i = #work_path, 1, -1 do
local character = string.sub(work_path, i, i)
if character == unix_dash then
last_dash_index = i
break
end
end
if not last_dash_index then
return
end
local new_path = {}
table.insert(new_path, string.sub(work_path, 1, last_dash_index - 1))
table.insert(new_path, "/physics/")
table.insert(new_path, string.sub(work_path, last_dash_index + 1, #work_path))
new_path = table.concat(new_path)
return module.generate_path(new_path)
end
function module.color_text(text, color, style)
-- Expects a text string, and optionally, a color and a style
-- Returns a string with special ANSI escape sequences for coloring and styling text (invalid color or style names are ignored)
-- (Starting from Windows 10, Windows supports ANSI escape sequences in console)
-- TODO: once it is confirmed that styled text shows up correctly on Linux hosts, remove system-specific logic
local new_text = text
local colors = {}
local styles = {}
local picked_color
local picked_style
local is_windows_host = module.is_windows_host()
local escape_character_windows = ""
local escape_character_linux = "" -- I though this would require using \e instead of the literal escape character used by Windows. This makes things easier
-- Styles
styles.reset = "[0m"
styles.bold = "[1m"
styles.underline = "[4m"
styles.inverse = "[7m"
-- Normal foreground colors
colors.black = "[30m"
colors.red = "[31m"
colors.green = "[32m"
colors.yellow = "[33m"
colors.blue = "[34m"
colors.magenta = "[35m"
colors.cyan = "[36m"
colors.white = "[37m"
-- Strong foreground colors
colors.strong_white = "[90m"
colors.strong_red = "[91m"
colors.strong_green = "[92m"
colors.strong_yellow = "[93m"
colors.strong_blue = "[94m"
colors.strong_magenta = "[95m"
colors.strong_cyan = "[96m"
colors.strong_white = "[97m"
-- (I won't bother adding support for background colors, I find them unnecessary right now)
-- To change color and style of text, print an ESC character followed by the respective color or style sequence
-- In these statements, it must be checked that the color and style are valid, or this will return an error from attempting to concatenate a "nil" value
-- On a Windows host, "and" returns a character sequence that evaluates to true and is assigned, otherwise, the Linux sequence evaluates to true and is assigned
if colors[color] then
picked_color = is_windows_host and escape_character_windows..colors[color] or escape_character_linux..colors[color]
end
if styles[style] then
picked_style = is_windows_host and escape_character_windows..styles[style] or escape_character_linux..styles[style]
end
if picked_color then
new_text = picked_color..new_text
end
if picked_style then
new_text = picked_style..new_text
end
if (picked_color or picked_style) then
-- To return console style and color to normal, print a "reset" character sequence (ESC[0m)
new_text = is_windows_host and new_text..escape_character_windows..styles.reset or new_text..escape_character_linux..styles.reset
end
return new_text
end
function module.generate_path(...)
-- Takes a variable argument list, expects the arguments to be a sequence of strings; components for a file or directory path (include separators as components)
-- Produces a valid, adapted file or directory path according to the host OS, returns "nil" on failure
local arguments = {...}
local path = ""
local is_windows_host = module.is_windows_host()
if not arguments then
return
end
if #arguments <= 0 then
return
end
for _, path_component in ipairs(arguments) do
if is_windows_host then
path = path..module.to_windows_path(path_component)
else
path = path..module.to_unix_path(path_component)
end
end
return path
end
function module.to_unix_path(windows_path)
return string.gsub(windows_path, "\\", "/")
end
function module.to_windows_path(unix_path)
return string.gsub(unix_path, "/", "\\")
end
-- TODO: add safe access checks to all file I/O operations (that require it)
function module.import_settings()
local file_path = module.generate_path("./settings.json")
if not module.is_valid_path(file_path) then
-- I disabled this because otherwise this would pop-up as an error when settings haven't been defined
-- print("error: failed to import settings from JSON file (invalid path)")
return
end
local file = io.open(file_path)
-- Now I see why this was throwing an error even after passing the "valid path" check
if not file then
return
end
local content = file:read("*a")
file:close()
local settings = dkjson.decode(content)
return settings
end
function module.export_settings_json(settings_json)
-- Expects a single-level settings table
local file = io.open(module.generate_path("./settings.json"), "w")
file:write(settings_json)
file:close()
end
function module.get_json_files_in_dir(directory)
-- Gets all file names found in the given directory, if valid
local files = {}
local path = module.generate_path(directory)
local quoted_path = module.add_quotes(path)
local is_windows_host = module.is_windows_host()
local file_list
local line
if not module.is_valid_path(path) then
return
end
if is_windows_host then
-- In Windows, DIR with option /B enables bare mode, removes all heading information and summary from the output, leaving only the file names
file_list = io.popen("DIR /B "..quoted_path)
else
-- In Linux, dir with options -a and -1 lists all files in the directory (including files starting with dot) one file by line
file_list = io.popen("dir -1 -a "..quoted_path)
end
repeat
local bare_file_name
if line and string.sub(line, -5, -1) == ".json" then
if is_windows_host then
bare_file_name = string.sub(line, 1, -6)
else
-- In Linux, spaces in file names are escaped as "\ " and require additional processing
-- For instance, in Windows a file name that looks like "human jeep", looks like "human\ jeep" in Linux
-- This line of code removes escaped backslashes from file names so they can be matched to the Type name passed by the user
bare_file_name = string.gsub(string.sub(line, 1, -6) , "\\", "")
end
table.insert(files, bare_file_name)
end
line = file_list:read("*l")
-- TODO: remove this test when the problem is solved... Spaces in file names are read as "\ " instead of just " " in Linux, apparently
-- I think the issue was caused by using pre-compiled Invader binaries for Windows, I should try testing this on a Linux build
-- if line then
-- print(bare_file_name)
-- print("LINE "..line)
-- end
until not line
file_list:close()
return files
end
function module.is_valid_path(path)
-- Tests whether the file or directory exists, regardless of whether it is accessible by the current user
local quoted_path = module.add_quotes(module.generate_path(path)) -- TODO: maybe don't generate the path here? In order to test the path as it is
local is_windows_host = module.is_windows_host()
local file_exists = false
-- In Windows, DIR is used to test if a file or directory exists; >NUL 2>&1 are file descriptor redirections to suppress command output
if is_windows_host then
file_exists = os.execute("CALL DIR "..quoted_path.." >NUL 2>&1")
else
-- In Linux, the test command ([ ]) with option "-e" can be used to test if a file exists, regardless of file type (file, directory, etc.); >/dev/null 2>&1 are file descriptors to suppress command output
file_exists = os.execute("[ -e "..quoted_path.." ] >/dev/null 2>&1")
end
-- Returns 0 on success, otherwise returns a non-zero exit code (this works the same on either OS)
if (file_exists == 0) then
return true
end
-- Do not return the "file exists" exit code, if it is 1 or anything other than "false" or "nil", it evaluates as true
return
end
function module.is_windows_host()
-- On Windows, the environment variable "OS" returns "Windows_NT", on Linux it is undefined; same goes for "WINDIR" except its value may differ
-- Attempts to get the running OS using OS-specific commands are trickier and require unnecessarily complicated processing
local is_windows = os.getenv("os") == "Windows_NT"
local is_windir_found = os.getenv("windir") ~= nil
-- Returns "true" if Windows, "false" if Linux or any unsupported system
return is_windows or is_windir_found
end
function module.add_quotes(x)
return "\""..x.."\""
end
return module