-
Notifications
You must be signed in to change notification settings - Fork 1
/
inputvalidator.pro
260 lines (233 loc) · 8.28 KB
/
inputvalidator.pro
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
258
259
260
;h+
; (c) 2018 Harris Geospatial Solutions, Inc.
;
; Licensed under MIT, see LICENSE.txt for more details.
;h-
;+
; :Examples:
;
; # Validate that an argument is present and defined:
;
; inputValidator, hash('nameOfArg', 'required')
;
; Note that this means the variable is not undefined (i.e. defined in
; the IDL code or passed in as a parameter). A variable
; defined as `!NULL` will still pass.
;
; # Validate that an argument is a string array and present
;
; inputValidator, hash('nameOfArg', ['string', 'array', 'required'])
;
; # Validate that an argument, if present is a double array
;
; inputValidator, hash('nameOfArg', ['double', 'array'])
;
;
;
; :Description:
; Simple routine that can be used to perform input validation
; for any routine to ensure that parameters are present or a
; certain type. You can specify each possible type that the
; data value can represent and optionally require that the value have
; all of the types. Here are the generic types that can be used:
;
; - required : If set, the value must be present and defined.
;
; - array : If set, value supplied must be an array.
;
; - number : If set, value supplied must be a number.
;
; - file : If set, value must be a file on disk.
;
; - directory: If set, value must be a folder on disk.
;
; This routine uses IDL's `isa` function to make the comparison so,
; in addition to the types above, you can specify anything else that
; can pass as an argument. Some exampled are: byte, int, long, float,
; hash, orderedhash, enviraster. They can be any IDL-specific data
; type and it can also be the type of object such as idlgrwindow or
; any named, custom object type.
;
; :Params:
; requirements: in, optional/required, type=hash/orderedhash
; Hash type object with key/value pair representing the variable
; name and a string/atring array representing the different types
; that must be present.
;
; This argument is optional only if the `CALLED_FROM` keyword is set.
;
; :Keywords:
; CALLED_FROM: in, optional, type=string
; If present, the validator makes sure that the routine was called from
; the specified function or procedure. The string comparison is case insensitive.
; LEVEL: in, optional, type=uint
; Specify the scope level (with reference to where this was called, and not
; in this scope) for which you want the prefix of the error message to appear.
; For example, if you specify -1, then the prefix will be from the parent of the
; routine that calls this procedure.
; PRINT_NAME: in, optional, type=string
; If specified, then this value will be printed in place of the variable names
; in the requirements hash. This is meant for use with one variable at a time.
; PRINT_PREFIX: in, optional, type=string, default='Variable'
; Set this to a string that you will want printed before any error messages. The
; default value is "Variable". This is provided if you are trying to validate
; an existing variable from something like a hash so you can print the term
; "Key" which is the correct term.
;
; :Author: Zachary Norman - GitHub : [znorman-harris](https://github.com/znorman-harris)
;-
pro inputValidator, requirements,$
CALLED_FROM = called_from,$
LEVEL = level,$
PRINT_NAME = print_name,$
PRINT_PREFIX = print_prefix
compile_opt idl2, hidden
on_error, 2
;check if we have a scope level
if (level ne !NULL) then begin
scopeLevel = -1 + level
endif else begin
scopeLevel = -1
endelse
;check our callback
if keyword_set(called_from) then begin
if ~isa(called_from, /STRING) then begin
message, 'CALLED_FROM specified, but supplied value is not a string, required!'
endif
trace = scope_traceback()
from = strlowcase(trace[-3])
if (strpos(from, strlowcase(called_from)) ne 0) then begin
message, 'Routine not called from expected source!', LEVEL = scopeLevel
endif
;return if nothing else to do
if (n_elements(requriements) eq 0) then begin
return
endif
endif
;make sure we have something to check
if (n_elements(requirements) eq 0) then begin
message, 'requirements argument not provided or has no key/value pairs, required!'
endif
if ~isa(requirements, 'hash') then begin
message, 'requirements argument provided, but it is not a hash or orderedhash, required!'
endif
;get the prefix for printing
if keyword_set(print_prefix) then begin
pName = strtrim(print_prefix,2)
endif else begin
pName = 'Variable'
endelse
;get our scope level
level = scope_level()
;loop over each element
foreach initReqs, requirements, varName do begin
;trim extra strings
reqs = strtrim(initReqs,2)
;init all of our basic flags
required = 0
array = 0
number = 0
file = 0
directory = 0
;flag for if we need all data types specified to be present
;otherwise just need one
all = 0
;init array to hold data types
;use array bc overhead will be small
dTypes = []
typeTotal = 0
;get the name that we want to print
if keyword_set(print_name) then begin
varPrint = strtrim(print_name,2)
endif else begin
varPrint = varName
endelse
;loop over our requirements and make the proper flags
foreach req, strlowcase(reqs) do begin
case (req) of
;basic variable information
'required' : required = 1
'array' : array = 1
'number' : number = 1
'file' : file = 1
'directory' : directory = 1
'all' : all = 1
else:begin
dTypes = [dTypes, req]
end
endcase
endforeach
;check if there are other types to check
types = n_elements(dTypes) gt 0
;check to see if we have a null variable
catch, err
if (err ne 0) then begin
isNull = 1
endif else begin
value = scope_varfetch(varName, LEVEL = level - 1)
isNull = isa(value, /NULL) ; TODO: add info for hash keys
case (1) of
;isa(value, 'hash'): isNull = n_elements(value) eq 0
else:;do nothing
endcase
endelse
catch, /CANCEL
;check if present and required
if (required AND isNull) then begin
message, pName + ' "' + varPrint + '" has not been defined, required!', LEVEL = scopeLevel
endif else begin
if (isNull) then begin
continue
endif
endelse
;check for array
if (array) then begin
if ~isa(value, /ARRAY) then begin
message, pName + ' "' + varPrint + '" is not an array, required!', LEVEL = scopeLevel
endif
endif
;check for file
if (file) then begin
if ~file_test(value) then begin
message, pName + ' "' + varPrint + '" is not a file on disk, required!', LEVEL = scopeLevel
endif
endif
;check for directory
if (directory) then begin
if ~file_test(value, /DIRECTORY) then begin
message, pName + ' "' + varPrint + '" is not a directory on disk, required!', LEVEL = scopeLevel
endif
endif
;check for number
if (number) then begin
nflag = isa(value, /NUMBER)
if (~nFlag AND ~types) then begin
message, pName + ' "' + varPrint + '" is not a number, required!', LEVEL = scopeLevel
endif
typeTotal += nFlag
endif
;check all of the data types
if (n_elements(dTypes) gt 0) then begin
foreach type, dTypes do begin
flag = isa(value, type)
if (all AND ~flag) then begin
message, pName + ' "' + varPrint + '" is not a "' + type + '", required!', LEVEL = scopeLevel
endif else begin
typeTotal += flag
endelse
endforeach
endif
;validate input
if ((n_elements(dTypes) gt 0) OR number) then begin
case (1) of
(all) : nReq = n_elements(dtypes) + number
else : nReq = 1
endcase
;check if we were none of the potential data types
if ~(typeTotal ge nReq) then begin
message, pName + ' "' + varPrint + '" does not match any optional data types. Optional types are: ' + $
string(10b) + strjoin(dTypes, string(10b)), LEVEL = scopeLevel
endif
endif
endforeach
end