Repository navigation
Expand file tree
/
Copy pathdatalinter
More file actions
executable file
·193 lines (167 loc) · 5.99 KB
/
Copy pathdatalinter
File metadata and controls
executable file
·193 lines (167 loc) · 5.99 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
#!/usr/bin/env -S julia --startup-file=no
#=
datalinter
Portable cross-platform Julia-based DataLinter Docker wrapper.
Features:
- Directly supports --data-path (or positional first argument as data file(s)), --config-path, --code-path.
- Mounts directories of these paths as **separate volumes**:
- Data directory → /_data in container
- Config directory → /_config in container
- Code directory → /_code in container
- Full forwarding of extra arguments to the inner datalinter binary
- Translates host paths to corresponding container paths when calling the inner binary.
- Support for additional Docker arguments (via --docker-* prefix or -- separator)
- Comprehensive error handling and validation
- Minimal dependencies (Base Julia only).
- Works on Windows, Linux, macOS
Usage
─────
• Unix-like:
./datalinter path/to/data.csv [--config-path /host/config/dir/custom.toml] [--code-path /host/code/script.R] [extra flags...]
• Or with explicit --data-path:
./datalinter --data-path path/to/data.csv --config-path ... --code-path ...
• Windows (PowerShell/cmd):
julia --startup-file=no datalinter "C:\path\to\data.csv" [flags...]
=#
using Base: abspath, dirname, basename, isdir, isfile, Cmd
# ------------------------------------------------------------------
# Argument Parsing
# ------------------------------------------------------------------
function parse_args(args::Vector{String})
data_paths = String[]
config_path = ""
code_path = ""
docker_extra = String[] # e.g. --docker--memory=2g, --docker--env=KEY=val
inner_extra = String[]
use_separator = false
i = 1
while i ≤ length(args)
arg = args[i]
if arg == "--"
use_separator = true
i += 1
continue
end
if use_separator
push!(inner_extra, arg)
i += 1
continue
end
if arg == "--data-path" && i < length(args)
push!(data_paths, args[i+1])
i += 2
elseif arg == "--config-path" && i < length(args)
config_path = args[i+1]
i += 2
elseif arg == "--code-path" && i < length(args)
code_path = args[i+1]
i += 2
# Docker-specific arguments (strip --docker- prefix)
elseif startswith(arg, "--docker-")
docker_arg = "--" * arg[9:end] # --docker-memory=2g → --memory=2g
push!(docker_extra, docker_arg)
if i < length(args) && !startswith(args[i+1], "-")
push!(docker_extra, args[i+1])
i += 1
end
i += 1
# Other flags go to inner binary (or could be Docker if user wants)
elseif startswith(arg, "--") || startswith(arg, "-")
push!(inner_extra, arg)
if i < length(args) && !startswith(args[i+1], "-")
push!(inner_extra, args[i+1])
i += 1
end
i += 1
else
# Positional → data file
push!(data_paths, arg)
i += 1
end
end
if isempty(data_paths)
println(stderr, "Error: No data file(s) provided. Use positional argument or --data-path.")
exit(1)
end
return (data_paths, config_path, code_path, docker_extra, inner_extra)
end
# ------------------------------------------------------------------
# Path utilities
# ------------------------------------------------------------------
function safe_dir(path::String)::String
if isempty(path)
return ""
end
p = abspath(path)
d = isdir(p) ? p : dirname(p)
if !isdir(d)
println(stderr, "Warning: Directory does not exist for path '$path': $d")
end
return d
end
# ------------------------------------------------------------------
# Main
# ------------------------------------------------------------------
function main()
data_paths, config_path, code_path, docker_extra, inner_extra = parse_args(ARGS)
# Resolve host directories
data_dirs = unique(safe_dir(p) for p in data_paths)
config_dir = isempty(config_path) ? "" : safe_dir(config_path)
code_dir = isempty(code_path) ? "" : safe_dir(code_path)
# Build volume mounts (separate volumes as requested)
volumes = String[]
for d in data_dirs
push!(volumes, "--volume=$d:/_data")
end
if !isempty(config_dir)
push!(volumes, "--volume=$config_dir:/_config")
end
if !isempty(code_dir)
push!(volumes, "--volume=$code_dir:/_code")
end
# Translate paths to container
container_data = [joinpath("/_data", basename(p)) for p in data_paths]
container_config = isempty(config_path) ? "/datalinter/config/default.toml" : joinpath("/_config", basename(config_path))
container_code = isempty(code_path) ? "" : joinpath("/_code", basename(code_path))
# Build inner datalinter command
inner_cmd = ["/datalinter/bin/datalinter"]
append!(inner_cmd, container_data)
push!(inner_cmd, "--config-path", container_config)
if !isempty(code_path)
push!(inner_cmd, "--code-path", container_code)
end
# Default sensible flags + user-provided inner flags
append!(inner_cmd, [
"--progress",
"--timed",
"--print-exceptions",
"--log-level", "error",
])
append!(inner_cmd, inner_extra)
# Build full Docker command
docker_cmd_parts = vcat([
"docker", "run",
"-it",
"--rm",
volumes...,
docker_extra...,
"ghcr.io/zgornel/datalinter-compiled:latest",
inner_cmd...
])
#println(stderr, "→ Running: ", join(docker_cmd_parts, " "))
# Execute with error handling
try
run(Cmd(docker_cmd_parts))
catch e
if isa(e, ProcessFailedException)
println(stderr, "\nError: Docker command failed:\n$e")
exit(1)
else
println(stderr, "\nUnexpected error running Docker: $e")
exit(1)
end
end
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end