a811899a09daa6f2e46e1b6475406393ddface37
[jalview.git] / forester / ruby / evoruby / lib / evo / tool / phylogenies_decorator.rb
1 #!/usr/local/bin/ruby -w
2 #
3 # = lib/evo/apps/phylogenies_decorator
4 #
5 # Copyright::    Copyright (C) 2017 Christian M. Zmasek
6 # License::      GNU Lesser General Public License (LGPL)
7 #
8 # decoration of phylogenies with sequence/species names and domain architectures
9 #
10 # Environment variable FORESTER_HOME needs to point to the appropriate
11 # directory (e.g. setenv FORESTER_HOME $HOME/SOFTWARE_DEV/ECLIPSE_WORKSPACE/forester/)
12
13 require 'lib/evo/util/constants'
14 require 'lib/evo/util/util'
15 require 'lib/evo/util/command_line_arguments'
16 require 'date'
17 require 'fileutils'
18
19 module Evoruby
20   class PhylogeniesDecorator
21
22     DECORATOR_OPTIONS_SEQ_NAMES = '-p -t -mp -or'
23     DECORATOR_OPTIONS_DOMAINS   = '-p -t'
24     DOMAINS_MAPFILE_SUFFIX      = '.dff'
25     SLEEP_TIME                  = 0.01
26     REMOVE_NI                   = true
27     TMP_FILE_1                  = '___PD1___'
28     TMP_FILE_2                  = '___PD2___'
29     LOG_FILE                    = '00_phylogenies_decorator.log'
30     FORESTER_HOME               = ENV[Constants::FORESTER_HOME_ENV_VARIABLE]
31     JAVA_HOME                   = ENV[Constants::JAVA_HOME_ENV_VARIABLE]
32
33     PRG_NAME       = "phylogenies_decorator"
34     PRG_DATE       = "170427"
35     PRG_DESC       = "decoration of phylogenies with sequence/species names and domain architectures"
36     PRG_VERSION    = "1.04"
37     WWW            = "https://sites.google.com/site/cmzmasek/home/software/forester"
38
39     HELP_OPTION_1                           = "help"
40     HELP_OPTION_2                           = "h"
41     NO_DOMAINS_OPTION                       = 'nd'
42     NO_SEQS_OPTION                          = 'ns'
43     VERBOSE_OPTION                          = 'v'
44     EXTRACT_BRACKETED_TAXONOMIC_CODE_OPTION = 'tc'
45
46     NL = Constants::LINE_DELIMITER
47     def run
48
49       Util.print_program_information( PRG_NAME,
50       PRG_VERSION,
51       PRG_DESC,
52       PRG_DATE,
53       WWW,
54       STDOUT )
55
56       if ( ARGV == nil || ARGV.length < 2  )
57         print_help
58         exit( -1 )
59       end
60
61       if FORESTER_HOME == nil || FORESTER_HOME.length < 1
62         Util.fatal_error( PRG_NAME, "apparently environment variable #{Constants::FORESTER_HOME_ENV_VARIABLE} has not been set" )
63       end
64       if JAVA_HOME == nil ||  JAVA_HOME.length < 1
65         Util.fatal_error( PRG_NAME, "apparently environment variable #{Constants::JAVA_HOME_ENV_VARIABLE} has not been set" )
66       end
67
68       if !File.exist?( FORESTER_HOME )
69         Util.fatal_error( PRG_NAME, '[' + FORESTER_HOME + '] does not exist' )
70       end
71       if !File.exist?( JAVA_HOME )
72         Util.fatal_error( PRG_NAME, '[' + JAVA_HOME + '] does not exist' )
73       end
74
75       decorator = JAVA_HOME + '/bin/java -cp ' + FORESTER_HOME + '/java/forester.jar org.forester.application.decorator'
76
77       begin
78         cla = CommandLineArguments.new( ARGV )
79       rescue ArgumentError => e
80         Util.fatal_error( PRG_NAME, "error: " + e.to_s )
81       end
82
83       if ( cla.is_option_set?( HELP_OPTION_1 ) ||
84       cla.is_option_set?( HELP_OPTION_2 ) )
85         print_help
86         exit( 0 )
87       end
88
89       if ( cla.get_number_of_files != 2 )
90         print_help
91         exit( -1 )
92       end
93
94       allowed_opts = Array.new
95       allowed_opts.push(NO_DOMAINS_OPTION)
96       allowed_opts.push(NO_SEQS_OPTION)
97       allowed_opts.push(EXTRACT_BRACKETED_TAXONOMIC_CODE_OPTION)
98       allowed_opts.push(VERBOSE_OPTION)
99
100       disallowed = cla.validate_allowed_options_as_str( allowed_opts )
101       if ( disallowed.length > 0 )
102         Util.fatal_error( PRG_NAME,
103         "unknown option(s): " + disallowed,
104         STDOUT )
105       end
106
107       no_domains = false
108       if cla.is_option_set?(NO_DOMAINS_OPTION)
109         no_domains = true
110       end
111
112       no_seqs_files = false
113       if cla.is_option_set?(NO_SEQS_OPTION)
114         no_seqs_files = true
115       end
116
117       extr_bracketed_tc = false
118       if cla.is_option_set?(EXTRACT_BRACKETED_TAXONOMIC_CODE_OPTION)
119         extr_bracketed_tc = true
120       end
121
122       verbose = false
123       if cla.is_option_set?(VERBOSE_OPTION)
124         verbose = true
125       end
126
127       if File.exist?( LOG_FILE )
128         Util.fatal_error( PRG_NAME, 'logfile [' + LOG_FILE + '] already exists' )
129       end
130
131       in_suffix = cla.get_file_name( 0 )
132       out_suffix = cla.get_file_name( 1 )
133
134       log = String.new
135
136       now = DateTime.now
137       log << "Program              : " + PRG_NAME + NL
138       log << "Version              : " + PRG_VERSION + NL
139       log << "Program date         : " + PRG_DATE + NL
140       log << "No domains data      : " + no_domains.to_s + NL
141       log << "No mol seq data      : " + no_seqs_files.to_s + NL
142       log << "Extract tax codes    : " + extr_bracketed_tc.to_s + NL
143       log << "Date/time: " + now.to_s + NL
144       log << "Directory: " + Dir.getwd  + NL + NL
145
146       Util.print_message( PRG_NAME, 'input suffix     : ' + in_suffix )
147       Util.print_message( PRG_NAME, 'output suffix    : ' + out_suffix )
148
149       log << 'input suffix     : ' + in_suffix + NL
150       log << 'output suffix    : ' + out_suffix + NL
151
152       if ( File.exist?( TMP_FILE_1 ) )
153         File.delete( TMP_FILE_1 )
154       end
155       if ( File.exist?( TMP_FILE_2 ) )
156         File.delete( TMP_FILE_2 )
157       end
158
159       files = Dir.entries( "." )
160
161       counter = 0
162
163       files.each { | phylogeny_file |
164         if ( !File.directory?( phylogeny_file ) &&
165         phylogeny_file !~ /^\./ &&
166         phylogeny_file !~ /^00/ &&
167         phylogeny_file !~ /#{out_suffix}$/ &&
168         phylogeny_file =~ /#{in_suffix}$/ )
169           begin
170             Util.check_file_for_readability( phylogeny_file )
171           rescue ArgumentError
172             Util.fatal_error( PRG_NAME, 'can not read from: ' + phylogeny_file + ': '+ $! )
173           end
174
175           counter += 1
176
177           outfile = phylogeny_file.sub( /#{in_suffix}$/, out_suffix )
178
179           if REMOVE_NI
180             outfile = outfile.sub( /_ni_/, '_' )
181           end
182
183           if File.exist?( outfile )
184             msg = counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile +
185             ' : already exists, skipping'
186             Util.print_message( PRG_NAME, msg  )
187             log << msg + NL
188             next
189           end
190
191           if verbose
192             puts
193           end
194           Util.print_message( PRG_NAME, counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile )
195           log << counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile + NL
196
197           phylogeny_id = get_id( phylogeny_file )
198           if  phylogeny_id == nil || phylogeny_id.size < 1
199             Util.fatal_error( PRG_NAME, 'could not get id from ' + phylogeny_file.to_s )
200           end
201           if verbose
202             Util.print_message( PRG_NAME, "Id: " + phylogeny_id )
203           end
204           log << "Id: " + phylogeny_id + NL
205
206           ids_mapfile_name = nil
207           domains_mapfile_name = nil
208           seqs_file_name = nil
209
210           ids_mapfile_name = get_file( ".", phylogeny_id, Constants::ID_MAP_FILE_SUFFIX )
211
212           begin
213             Util.check_file_for_readability( ids_mapfile_name )
214           rescue IOError
215             Util.fatal_error( PRG_NAME, 'failed to read from [#{ids_mapfile_name}]: ' + $! )
216           end
217           if verbose
218             Util.print_message( PRG_NAME, "Ids mapfile: " + ids_mapfile_name )
219           end
220           log << "Ids mapfile: " + ids_mapfile_name + NL
221
222           unless no_seqs_files
223             seqs_file_name = get_file( ".", phylogeny_id, Constants::ID_NORMALIZED_FASTA_FILE_SUFFIX )
224             begin
225               Util.check_file_for_readability( seqs_file_name  )
226             rescue IOError
227               Util.fatal_error( PRG_NAME, 'failed to read from [#{seqs_file_name }]: ' + $! )
228             end
229             if verbose
230               Util.print_message( PRG_NAME, "Seq file: " + seqs_file_name )
231             end
232             log << "Seq file: " + seqs_file_name + NL
233           end
234
235           unless no_domains
236             domains_mapfile_name = get_file( ".", phylogeny_id, Constants::DOMAINS_TO_FORESTER_OUTFILE_SUFFIX  )
237             begin
238               Util.check_file_for_readability( domains_mapfile_name )
239             rescue IOError
240               Util.fatal_error( PRG_NAME, 'failed to read from [#{domains_mapfile_name}]: ' + $! )
241             end
242             if verbose
243               Util.print_message( PRG_NAME, "Domains file: " + domains_mapfile_name )
244             end
245             log << "Domains file: " + domains_mapfile_name + NL
246           end
247
248           if no_seqs_files
249             FileUtils.cp(phylogeny_file, TMP_FILE_1)
250           else
251             cmd = decorator +
252             ' -t -p -f=m ' + phylogeny_file + ' ' +
253             seqs_file_name  + ' ' + TMP_FILE_1
254             if verbose
255               puts cmd
256             end
257             begin
258               execute_cmd( cmd, log )
259             rescue Error
260               Util.fatal_error( PRG_NAME, 'error: ' + $! )
261             end
262           end
263
264           unless no_domains
265             cmd = decorator + ' ' + DECORATOR_OPTIONS_DOMAINS + ' ' +
266             '-f=d ' + TMP_FILE_1 + ' ' +
267             domains_mapfile_name + ' ' + TMP_FILE_2
268             if verbose
269               puts cmd
270             end
271             begin
272               execute_cmd( cmd, log )
273             rescue Error
274               Util.fatal_error( PRG_NAME, 'error: ' + $! )
275             end
276           end
277
278           opts = DECORATOR_OPTIONS_SEQ_NAMES
279           if extr_bracketed_tc
280             opts += ' -tc'
281           end
282
283           if no_domains
284             cmd = decorator + ' ' + opts + ' -f=n ' + TMP_FILE_1 + ' ' +
285             ids_mapfile_name + ' ' + outfile
286             if verbose
287               puts cmd
288             end
289             begin
290               execute_cmd( cmd, log )
291             rescue Error
292               Util.fatal_error( PRG_NAME, 'error: ' + $! )
293             end
294             File.delete( TMP_FILE_1 )
295           else
296             cmd = decorator + ' ' + opts + ' -f=n ' + TMP_FILE_2 + ' ' +
297             ids_mapfile_name + ' ' + outfile
298             if verbose
299               puts cmd
300             end
301             begin
302               execute_cmd( cmd, log )
303             rescue Error
304               Util.fatal_error( PRG_NAME, 'error: ' + $! )
305             end
306             File.delete( TMP_FILE_1 )
307             File.delete( TMP_FILE_2 )
308           end
309         end
310       }
311       open( LOG_FILE, 'w' ) do | f |
312         f.write( log )
313       end
314       if verbose
315         puts
316       end
317       Util.print_message( PRG_NAME, 'OK' )
318       puts
319     end # def run
320
321     def execute_cmd( cmd, log )
322       log << 'executing ' + cmd + NL
323       IO.popen( cmd , 'r+' ) do | pipe |
324         pipe.close_write
325         log << pipe.read + NL + NL
326       end
327       sleep( SLEEP_TIME )
328     end
329
330     def get_id( phylogeny_file_name )
331       return phylogeny_file_name
332       #if phylogeny_file_name =~ /^(.+?_DA)_/
333       #  return $1
334       #elsif phylogeny_file_name =~ /^(.+?)_/
335       #  return $1
336       #end
337       #nil
338     end
339
340     def get_file( files_in_dir, phylogeny_id, suffix_pattern )
341       Util.get_matching_file( files_in_dir, phylogeny_id, suffix_pattern )
342       #      matching_files = Util.get_matching_files( files_in_dir, phylogeny_id, suffix_pattern )
343       #      if matching_files.length < 1
344       #        Util.fatal_error( PRG_NAME, 'no file matching [' + phylogeny_id +
345       #        '...' + suffix_pattern + '] present in current directory' )
346       #      end
347       #      if matching_files.length > 1
348       #        Util.fatal_error( PRG_NAME, 'more than one file matching [' +
349       #        phylogeny_id  + '...' + suffix_pattern + '] present in current directory' )
350       #      end
351       #      matching_files[ 0 ]
352     end
353
354     def get_seq_file( files_in_dir, phylogeny_id )
355       matching_files = Array.new
356
357       files_in_dir.each { | file |
358
359         if ( !File.directory?( file ) &&
360         file !~ /^\./ &&
361         file !~ /^00/ &&
362         ( file =~ /^#{phylogeny_id}__.+\d$/ || file =~ /^#{phylogeny_id}_.*\.fasta$/ ) )
363           matching_files << file
364         end
365       }
366
367       if matching_files.length < 1
368         Util.fatal_error( PRG_NAME, 'no seq file matching [' +
369         phylogeny_id + '_] present in current directory' )
370       end
371       if matching_files.length > 1
372         Util.fatal_error( PRG_NAME, 'more than one seq file matching [' +
373         phylogeny_id + '_] present in current directory' )
374       end
375       matching_files[ 0 ]
376     end
377
378     def print_help()
379       puts "Usage:"
380       puts
381       puts "   " + PRG_NAME + ".rb <suffix of in-trees to be decorated> <suffix for decorated out-trees> "
382       puts
383       puts "   required files (in this dir): " + "name mappings       : .nim"
384       puts "                                 " + "sequences           : _ni.fasta"
385       puts "                                 " + "domain architectures: .dff"
386       puts
387       puts "   options: -" + NO_DOMAINS_OPTION  + ": to not add domain architecture information (.dff file)"
388       puts "            -" + NO_SEQS_OPTION   + ": to not add molecular sequence information (_ni.fasta file)"
389       puts "            -" + EXTRACT_BRACKETED_TAXONOMIC_CODE_OPTION  + ": to extract bracketed taxonomic codes, e.g. [NEMVE]"
390       puts "            -" + VERBOSE_OPTION  + ":  verbose"
391       puts
392       puts "Examples: " + PRG_NAME + ".rb .xml _d.xml"
393       puts "          " + PRG_NAME + ".rb -#{NO_DOMAINS_OPTION} -#{NO_SEQS_OPTION} .xml _d.xml"
394       puts
395     end
396   end # class PhylogenyiesDecorator
397
398 end # module Evoruby