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