@@ -88,27 +88,32 @@ def validate(self):
8888 raise ValueError (attr + " is not set" )
8989
9090 # Validate name
91- invalid_chars = set ( string . punctuation . replace ( "_" , "" ). replace ( "." , "" ) )
91+ invalid_chars = GPTaskSpec . invalid_chars ( )
9292 if any (char in invalid_chars for char in self .name ):
9393 raise ValueError ("module name includes invalid characters: " + self .name )
9494
9595 # Validate LSID
9696 self ._valid_lsid ()
9797
9898 # Validate categories
99- if not self ._all_strings (self .categories ):
99+ if not self .all_strings (self .categories ):
100100 raise TypeError ("categories contains non-string value: " + str (self .categories ))
101101
102102 # Validate file formats
103- if not self ._all_strings (self .file_format ):
103+ if not self .all_strings (self .file_format ):
104104 raise TypeError ("file_format contains non-string value: " + str (self .file_format ))
105105
106106 # Validate support files
107- if not self ._all_strings (self .support_files ):
107+ if not self .all_strings (self .support_files ):
108108 raise TypeError ("support_files contains non-string value: " + str (self .support_files ))
109109
110- # Validate parameters
111- # TODO: Implement
110+ # Validate parameter list
111+ if not self ._all_params (self .parameters ):
112+ raise TypeError ("parameters contains non-GPParamSpec value: " + str (self .parameters ))
113+
114+ # Validate individual parameters
115+ for param in self .parameters :
116+ param .validate ()
112117
113118 # Return that everything validates
114119 return True
@@ -164,7 +169,7 @@ def write_manifest(self, module_directory=""):
164169
165170 # Write initial attributes
166171 manifest_file .write ("JVMLevel=\n " )
167- manifest_file .write ("LSID=" + self ._escape_colons (self .lsid ) + "\n " )
172+ manifest_file .write ("LSID=" + self .manifest_escape (self .lsid ) + "\n " )
168173 manifest_file .write ("author=" + self ._author_line () + "\n " )
169174 manifest_file .write ("categories=" + ';' .join (self .categories ) + "\n " )
170175 manifest_file .write ("commandLine=" + self .command_line + "\n " )
@@ -177,7 +182,8 @@ def write_manifest(self, module_directory=""):
177182 manifest_file .write ("os=" + str (self .os .value ) + "\n " )
178183
179184 # Write parameter attributes
180- # TODO: Implement
185+ for index , param in enumerate (self .parameters ):
186+ manifest_file .write (param .manifest_repr (index + 1 ))
181187
182188 # Write footer attributes
183189 manifest_file .write ("privacy=" + str (self .privacy .value ) + "\n " )
@@ -222,20 +228,26 @@ def _task_type(self):
222228 return ""
223229
224230 @staticmethod
225- def _escape_colons (string ):
231+ def manifest_escape (string ):
226232 """
227- Escape colon characters for inclusion in manifest file
233+ Escape colon and equals characters for inclusion in manifest file
228234 :param string:
229235 :return: string
230236 """
231- return string .replace (':' , '\:' )
237+ return string .replace (':' , '\:' ). replace ( '=' , '\=' )
232238
233239 @staticmethod
234- def _all_strings (arr ):
240+ def all_strings (arr ):
235241 if not isinstance ([], list ):
236242 raise TypeError ("non-list value found where list is expected" )
237243 return all (isinstance (x , str ) for x in arr )
238244
245+ @staticmethod
246+ def _all_params (arr ):
247+ if not isinstance ([], list ):
248+ raise TypeError ("non-list value found for parameters" )
249+ return all (isinstance (x , GPParamSpec ) for x in arr )
250+
239251 def _valid_lsid (self ):
240252 if not isinstance (self .lsid , str ):
241253 raise TypeError ("lsid is not a string, string expected: " + str (self .lsid ))
@@ -245,3 +257,156 @@ def _valid_lsid(self):
245257
246258 if self .lsid .split (':' )[0 ].lower () != 'urn' :
247259 raise ValueError ("lsid does not begin with urn: " + str (self .lsid ))
260+
261+ @staticmethod
262+ def invalid_chars ():
263+ """
264+ Returns a set of characters which are not valid in module or parameter names
265+ :return:
266+ """
267+ return set (string .punctuation .replace ("_" , "" ).replace ("." , "" ) + string .whitespace )
268+
269+
270+ class Type (Enum ):
271+ FILE = "FILE"
272+ TEXT = "TEXT"
273+ INTEGER = "Integer"
274+ FLOATING_POINT = "Floating Point"
275+ DIRECTORY = "DIRECTORY"
276+ PASSWORD = "PASSWORD"
277+
278+
279+ class JavaType (Enum ):
280+ FILE = "java.io.File"
281+ TEXT = "java.lang.String"
282+ INTEGER = "java.lang.Integer"
283+ FLOATING_POINT = "java.lang.Float"
284+ DIRECTORY = "DIRECTORY"
285+ PASSWORD = "PASSWORD"
286+
287+
288+ class Optional (Enum ):
289+ REQUIRED = ""
290+ OPTIONAL = "on"
291+
292+
293+ class GPParamSpec :
294+ """
295+ Specification needed to create a parameter for a new GenePattern module
296+ """
297+ def __init__ (self , name = None , description = "" , optional = Optional .REQUIRED ,
298+ type = Type .TEXT , choices = {}, value = "" , default_value = "" ,
299+ file_format = [], min_values = 0 , max_values = 1 ,
300+ flag = "" , prefix_when_specified = False ):
301+
302+ self .name = name
303+ self .description = description
304+ self .optional = optional
305+
306+ self .type = type
307+ self .choices = choices
308+ self .value = value
309+ self .default_value = default_value
310+
311+ self .file_format = file_format
312+ self .min_values = min_values
313+ self .max_values = max_values
314+
315+ self .flag = flag
316+ self .prefix_when_specified = prefix_when_specified
317+
318+ def validate (self ):
319+ # Check all values for None, only max_values is allowed to be None
320+ for attr in self .__dict__ :
321+ if self .__dict__ [attr ] is None and attr != "max_values" :
322+ raise ValueError (attr + " is not set" )
323+
324+ # Validate name
325+ invalid_chars = GPTaskSpec .invalid_chars ()
326+ if any (char in invalid_chars for char in self .name ):
327+ raise ValueError ("parameter name includes invalid characters: " + self .name )
328+
329+ # Validate min_values
330+ if not isinstance (self .min_values , int ):
331+ raise ValueError ("min_values not an int in: " + self .name )
332+
333+ # Validate max_values
334+ if not isinstance (self .max_values , int ) and self .max_values is not None and self .max_values != float ("inf" ):
335+ raise ValueError ("max_values not an int, None or infinity in: " + self .name )
336+
337+ # Validate file formats
338+ if not GPTaskSpec .all_strings (self .file_format ):
339+ raise TypeError ("file_format contains non-string value in parameter: " + self .name )
340+
341+ # Validate choices dict
342+ if not isinstance (self .choices , dict ):
343+ raise TypeError ("choices is not dict in parameter: " + self .name )
344+
345+ # Return that everything validates
346+ return True
347+
348+ def manifest_repr (self , p_num ):
349+ """
350+ Builds a manifest string representation of the parameters and returns it
351+ :param p_num: int
352+ :return: string
353+ """
354+ # Build the parameter prefix
355+ prefix = "p" + str (p_num ) + "_"
356+
357+ # Generate the manifest string
358+ manifest = prefix + "MODE=" + ("IN" if self .type == Type .FILE else "" ) + "\n "
359+ manifest += prefix + "TYPE=" + str (self .type .value ) + "\n "
360+ if self .type == Type .FILE and len (self .choices ) > 0 :
361+ manifest += prefix + "choices=" + self ._choices () + "\n "
362+ manifest += prefix + "default_value=" + self .default_value + "\n "
363+ manifest += prefix + "description=" + GPTaskSpec .manifest_escape (self .description ) + "\n "
364+ manifest += prefix + "fileFormat=" + ';' .join (self .file_format ) + "\n "
365+ manifest += prefix + "flag=" + self .flag + "\n "
366+ manifest += prefix + "name=" + self .name + "\n "
367+ manifest += prefix + "numValues=" + self ._num_values () + "\n "
368+ manifest += prefix + "optional=" + str (self .optional .value ) + "\n "
369+ manifest += prefix + "prefix=" + (self .flag if self .prefix_when_specified else "" ) + "\n "
370+ manifest += prefix + "prefix_when_specified=" + (self .flag if self .prefix_when_specified else "" ) + "\n "
371+ manifest += prefix + "type=" + self ._java_type () + "\n "
372+ manifest += prefix + "value=" + (self ._choices () if self .type != Type .FILE and len (self .choices ) > 0 else "" ) + "\n "
373+
374+ # Return the manifest string
375+ return manifest
376+
377+ def _choices (self ):
378+ """
379+ Generate a string of choices as key/value pairs
380+ :return: string
381+ """
382+ # Generate key/value strings
383+ pairs = []
384+ for key , value in self .choices .items ():
385+ pairs .append (str (value ) + "=" + str (key ))
386+
387+ # Assemble into overall string and escape
388+ return GPTaskSpec .manifest_escape (";" .join (pairs ))
389+
390+ def _num_values (self ):
391+ """
392+ Generate a valid num_values string based off min_values and max_values
393+ :return: string
394+ """
395+ # Add min_values to string
396+ num_values = str (self .min_values ) if self .min_values else "0"
397+
398+ # Handle infinite max_values or finite max_values
399+ if self .max_values is None or self .max_values == float ("inf" ):
400+ num_values += "+"
401+ else :
402+ num_values += ".." + str (self .max_values )
403+
404+ # Return the num_values string
405+ return num_values
406+
407+ def _java_type (self ):
408+ """
409+ Translates GenePattern type string to Java type string
410+ :return: string
411+ """
412+ return JavaType [self .type .name ].value
0 commit comments