28
28
DEFAULT_F = "nj"
29
29
30
30
31
+ def shorten_output (s ):
32
+ """
33
+ Shortens the output string if it exceeds 40 characters.
34
+
35
+ Args:
36
+ s (str): The input string.
37
+
38
+ Returns:
39
+ str: The shortened string.
40
+ """
41
+ if len (s ) > 40 :
42
+ s = s [:40 ] + "..."
43
+ return s
44
+
45
+
31
46
def error (* msg , error_code = 1 ):
32
47
"""
33
48
Prints an error message to stderr and exits the program with the specified error code.
@@ -59,7 +74,7 @@ def message(*msg):
59
74
print (log_line , file = sys .stderr )
60
75
61
76
62
- def run_safe (command , output_fn = None , output_fo = None , err_msg = None , thr_exc = True , silent = False ):
77
+ def run_safe (command , output_fn = None , output_fo = None , err_msg = None , thr_exc = True , silent = False , verbose = True ):
63
78
"""
64
79
Executes a shell command safely.
65
80
@@ -91,9 +106,13 @@ def run_safe(command, output_fn=None, output_fo=None, err_msg=None, thr_exc=True
91
106
command_safe .append (part )
92
107
93
108
command_str = " " .join (command_safe )
109
+ if verbose :
110
+ command_str_nice = command_str
111
+ else :
112
+ command_str_nice = shorten_output (command_str )
94
113
95
114
if not silent :
96
- message ("Shell command:" , command_str )
115
+ message (f "Shell command: ' { command_str_nice } '" )
97
116
98
117
if output_fn is None :
99
118
if output_fo is None :
@@ -120,9 +139,9 @@ def run_safe(command, output_fn=None, output_fo=None, err_msg=None, thr_exc=True
120
139
121
140
if error_code == 0 or error_code == 141 :
122
141
if not silent :
123
- message ("Finished: {}" . format ( command_str ) )
142
+ message (f "Finished: ' { command_str_nice } '" )
124
143
else :
125
- message ("Unfinished, an error occurred (error code {}): {}" . format ( error_code , command_str ) )
144
+ message (f "Unfinished, an error occurred (error code { error_code } ): ' { command_str } '" )
126
145
127
146
if err_msg is not None :
128
147
print ('Error: {}' .format (err_msg ), file = sys .stderr )
@@ -133,7 +152,7 @@ def run_safe(command, output_fn=None, output_fo=None, err_msg=None, thr_exc=True
133
152
sys .exit (1 )
134
153
135
154
136
- def mash_triangle (inp_fns , phylip_fn , k , s , t , fof ):
155
+ def mash_triangle (inp_fns , phylip_fn , k , s , t , fof , verbose ):
137
156
"""
138
157
Runs the 'mash triangle' command with the given parameters.
139
158
@@ -151,12 +170,12 @@ def mash_triangle(inp_fns, phylip_fn, k, s, t, fof):
151
170
Raises:
152
171
None
153
172
"""
154
- message ("Running mash " )
173
+ message ("Running Mash " )
155
174
cmd = f"mash triangle -s { s } -k { k } -p { t } " .split ()
156
175
if fof :
157
176
cmd += ["-l" ]
158
177
cmd += inp_fns
159
- run_safe (cmd , output_fn = phylip_fn )
178
+ run_safe (cmd , output_fn = phylip_fn , verbose = verbose )
160
179
161
180
162
181
def fn_to_node_name (fn ):
@@ -177,7 +196,7 @@ def fn_to_node_name(fn):
177
196
return nname
178
197
179
198
180
- def postprocess_mash_phylip (phylip_in_fn , phylip_out_fn ):
199
+ def postprocess_mash_phylip (phylip_in_fn , phylip_out_fn , verbose ):
181
200
"""
182
201
Postprocesses a PHYLIP file by copying its contents from the input file to the output file.
183
202
@@ -193,20 +212,13 @@ def postprocess_mash_phylip(phylip_in_fn, phylip_out_fn):
193
212
for i , x in enumerate (f ):
194
213
x = x .strip ()
195
214
if i != 0 :
196
- print (x , file = sys .stderr )
197
215
l , sep , r = x .partition ("\t " )
198
216
l = fn_to_node_name (l )
199
217
x = l + sep + r
200
- message (x )
201
218
print (x , file = g )
202
- #basename_components = os.path.basename(p[0]).split(".")
203
- #if len(basename_components) == 1:
204
- # basename_components.append("")
205
- ## remove suffix
206
- #p[0] = ".".join(basename_components[:-1])
207
219
208
220
209
- def quicktree (phylip_fn , newick_fn , algorithm ):
221
+ def quicktree (phylip_fn , newick_fn , algorithm , verbose ):
210
222
"""
211
223
Runs the quicktree algorithm to generate a phylogenetic tree.
212
224
@@ -224,20 +236,24 @@ def quicktree(phylip_fn, newick_fn, algorithm):
224
236
if algorithm == "upgma" :
225
237
cmd += ["-upgma" ]
226
238
cmd += [phylip_fn ]
227
- run_safe (cmd , output_fn = newick_fn )
239
+ run_safe (cmd , output_fn = newick_fn , verbose = verbose )
228
240
229
241
230
- def postprocess_quicktree_nw (nw_in_fn , nw_out_fo ):
242
+ def postprocess_quicktree_nw (nw_in_fn , nw_out_fo , verbose ):
231
243
"""
232
244
Reformat newick.
233
245
246
+ This function reads an input newick file, removes any leading or trailing whitespace from each line,
247
+ and writes the postprocessed newick file to the specified file object.
248
+
234
249
Notes:
235
250
- assumption: node names already don't contain paths and prefixes
236
251
- expects fo to allow both a filename or stdout
237
252
238
253
Args:
239
254
nw_in_fn (str): Path to the input newick file.
240
255
nw_out_fo (file object): File object to write the postprocessed newick file.
256
+ verbose (bool): If True, print additional information during the postprocessing.
241
257
242
258
Returns:
243
259
None
@@ -251,7 +267,7 @@ def postprocess_quicktree_nw(nw_in_fn, nw_out_fo):
251
267
print ("" .join (buffer ), file = nw_out_fo )
252
268
253
269
254
- def attotree (fns , newick_fo , k , s , t , phylogeny_algorithm , fof ):
270
+ def attotree (fns , newick_fo , k , s , t , phylogeny_algorithm , fof , verbose , debug ):
255
271
"""
256
272
Generate a phylogenetic tree using the given parameters.
257
273
@@ -263,27 +279,68 @@ def attotree(fns, newick_fo, k, s, t, phylogeny_algorithm, fof):
263
279
t (int): Value for parameter t.
264
280
phylogeny_algorithm (str): Name of the phylogeny algorithm to use.
265
281
fof (bool): Flag indicating whether to use the fof parameter.
282
+ verbose (bool): Flag indicating whether to enable verbose output.
283
+ debug (bool): Flag indicating whether to retain auxiliary files.
266
284
267
285
Returns:
268
286
None
269
287
"""
270
- with tempfile .TemporaryDirectory () as d :
271
- message ('created a temporary directory' , d )
288
+ features = []
289
+ if verbose :
290
+ features .append ("verbose" )
291
+ if debug :
292
+ features .append ("debuging" )
293
+ if len (features ) > 0 :
294
+ fmsg = f" ({ ', ' .join (features )} )"
295
+ else :
296
+ fmsg = ""
297
+ message (f"Attotree starting{ fmsg } " )
298
+ with tempfile .TemporaryDirectory (delete = not debug ) as d :
299
+ message ('Created a temporary directory' , d )
272
300
phylip1_fn = os .path .join (d , "distances.phylip0" )
273
301
phylip2_fn = os .path .join (d , "distances.phylip" )
274
302
newick1_fn = os .path .join (d , "tree.nw" )
275
303
newick2_fo = newick_fo
276
- mash_triangle (fns , phylip1_fn , k = k , s = s , t = t , fof = fof )
277
- postprocess_mash_phylip (phylip1_fn , phylip2_fn )
278
- quicktree (phylip2_fn , newick1_fn , algorithm = phylogeny_algorithm )
279
- postprocess_quicktree_nw (newick1_fn , newick2_fo )
304
+ if fof :
305
+ #This is to make the list of file pass to Mash even with
306
+ #process substitutions
307
+ old_fof_fn = fns [0 ]
308
+ new_fof_fn = os .path .join (d , "fof.txt" )
309
+ with open (old_fof_fn ) as f , open (new_fof_fn , 'w' ) as g :
310
+ g .write (f .read ())
311
+ fns = [new_fof_fn ]
312
+ mash_triangle (fns , phylip1_fn , k = k , s = s , t = t , fof = fof , verbose = verbose )
313
+ postprocess_mash_phylip (phylip1_fn , phylip2_fn , verbose = verbose )
314
+ quicktree (phylip2_fn , newick1_fn , algorithm = phylogeny_algorithm , verbose = verbose )
315
+ postprocess_quicktree_nw (newick1_fn , newick2_fo , verbose = verbose )
316
+
317
+ if debug :
318
+ emsg = f" (auxiliary files retained in '{ d } ')"
319
+ else :
320
+ emsg = ""
321
+ message (f"Attotree finished{ emsg } " )
280
322
281
323
282
324
def main ():
325
+ """
326
+ The main function that is executed when the script is run.
327
+
328
+ Returns:
329
+ None
330
+ """
283
331
284
332
class CustomArgumentParser (argparse .ArgumentParser ):
285
333
334
+ def __init__ (self , prog = None , ** kwargs ):
335
+ super ().__init__ (prog = "attotree" , ** kwargs )
336
+
286
337
def print_help (self ):
338
+ """
339
+ Prints the help message.
340
+
341
+ Returns:
342
+ None
343
+ """
287
344
msg = self .format_help ()
288
345
repl = re .compile (r'\]\s+\[' )
289
346
msg = repl .sub ("] [" , msg )
@@ -369,6 +426,20 @@ def format_help(self):
369
426
help = f'input files are list of files' ,
370
427
)
371
428
429
+ parser .add_argument (
430
+ '-D' ,
431
+ action = 'store_true' ,
432
+ dest = 'D' ,
433
+ help = f'debugging (don\' t remove tmp dir)' ,
434
+ )
435
+
436
+ parser .add_argument (
437
+ '-V' ,
438
+ action = 'store_true' ,
439
+ dest = 'V' ,
440
+ help = f'verbose output' ,
441
+ )
442
+
372
443
parser .add_argument (
373
444
'genomes' ,
374
445
nargs = "+" ,
@@ -378,7 +449,10 @@ def format_help(self):
378
449
args = parser .parse_args ()
379
450
380
451
#print(args)
381
- attotree (fns = args .genomes , k = args .k , s = args .s , t = args .t , newick_fo = args .o , phylogeny_algorithm = args .f , fof = args .L )
452
+ attotree (
453
+ fns = args .genomes , k = args .k , s = args .s , t = args .t , newick_fo = args .o , phylogeny_algorithm = args .f , fof = args .L ,
454
+ verbose = args .V , debug = args .D
455
+ )
382
456
383
457
args = parser .parse_args ()
384
458
0 commit comments