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