5bc5bac15aa5eb32ce3eec331fe68f7b7a60fd73
[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     IDS_ONLY                  = false #TODO this should be a command line option
36     FIXED_NIM_FILE            = nil #'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     WWW            = "https://sites.google.com/site/cmzmasek/home/software/forester"
48
49     HELP_OPTION_1       = "help"
50     HELP_OPTION_2       = "h"
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 > 3 || 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 File.exist?( LOG_FILE )
96         Util.fatal_error( PRG_NAME, 'logfile [' + LOG_FILE + '] already exists' )
97       end
98
99       in_suffix = cla.get_file_name( 0 )
100       out_suffix = cla.get_file_name( 1 )
101
102       log = String.new
103
104       now = DateTime.now
105       log << "Program              : " + PRG_NAME + NL
106       log << "Version              : " + PRG_VERSION + NL
107       log << "Program date         : " + PRG_DATE + NL
108       log << "Options for seq names: " + DECORATOR_OPTIONS_SEQ_NAMES + NL
109       log << "Options for domains  : " + DECORATOR_OPTIONS_DOMAINS + NL
110       log << "FORESTER_HOME        : " + FORESTER_HOME + NL
111       log << "JAVA_HOME            : " + JAVA_HOME + NL + NL
112       log << "Date/time: " + now.to_s + NL
113       log << "Directory: " + Dir.getwd  + NL + NL
114
115       Util.print_message( PRG_NAME, 'input suffix     : ' + in_suffix )
116       Util.print_message( PRG_NAME, 'output suffix    : ' + out_suffix )
117
118       log << 'input suffix     : ' + in_suffix + NL
119       log << 'output suffix    : ' + out_suffix + NL
120
121       if ( File.exist?( TMP_FILE_1 ) )
122         File.delete( TMP_FILE_1 )
123       end
124       if ( File.exist?( TMP_FILE_2 ) )
125         File.delete( TMP_FILE_2 )
126       end
127
128       files = Dir.entries( "." )
129
130       counter = 0
131
132       files.each { | phylogeny_file |
133         if ( !File.directory?( phylogeny_file ) &&
134         phylogeny_file !~ /^\./ &&
135         phylogeny_file !~ /^00/ &&
136         phylogeny_file !~ /#{out_suffix}$/ &&
137         phylogeny_file =~ /#{in_suffix}$/ )
138           begin
139             Util.check_file_for_readability( phylogeny_file )
140           rescue ArgumentError
141             Util.fatal_error( PRG_NAME, 'can not read from: ' + phylogeny_file + ': '+ $! )
142           end
143
144           counter += 1
145
146           outfile = phylogeny_file.sub( /#{in_suffix}$/, out_suffix )
147
148           if REMOVE_NI
149             outfile = outfile.sub( /_ni_/, '_' )
150           end
151
152           if File.exist?( outfile )
153             msg = counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile +
154             ' : already exists, skipping'
155             Util.print_message( PRG_NAME, msg  )
156             log << msg + NL
157             next
158           end
159
160           Util.print_message( PRG_NAME, counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile )
161           log << counter.to_s + ': ' + phylogeny_file + ' -> ' +  outfile + NL
162
163           phylogeny_id = get_id( phylogeny_file )
164           if  phylogeny_id == nil || phylogeny_id.size < 1
165             Util.fatal_error( PRG_NAME, 'could not get id from ' + phylogeny_file.to_s )
166           end
167           puts
168           Util.print_message( PRG_NAME, "Id: " + phylogeny_id )
169           log << "Id: " + phylogeny_id + NL
170
171           ids_mapfile_name = nil
172           domains_mapfile_name = nil
173           seqs_file_name = nil
174
175           if ( FIXED_NIM_FILE == nil )
176             ids_mapfile_name = get_file( files, phylogeny_id, IDS_MAPFILE_SUFFIX )
177           else
178             ids_mapfile_name = FIXED_NIM_FILE
179           end
180
181           Util.print_message( PRG_NAME, "Ids mapfile: " + ids_mapfile_name )
182           log << "Ids mapfile: " + ids_mapfile_name + NL
183
184           unless IDS_ONLY
185             domains_mapfile_name = get_file( files, phylogeny_id, DOMAINS_MAPFILE_SUFFIX )
186             seqs_file_name = get_seq_file( files, phylogeny_id )
187             Util.print_message( PRG_NAME, "Domains file: " + domains_mapfile_name )
188             log << "Domains file: " + domains_mapfile_name + NL
189             Util.print_message( PRG_NAME, "Seq file: " + seqs_file_name )
190             log << "Seq file: " + seqs_file_name + NL
191           end
192
193           unless IDS_ONLY
194             begin
195               Util.check_file_for_readability( domains_mapfile_name )
196             rescue IOError
197               Util.fatal_error( PRG_NAME, 'failed to read from [#{domains_mapfile_name}]: ' + $! )
198             end
199             begin
200               Util.check_file_for_readability( seqs_file_name  )
201             rescue IOError
202               Util.fatal_error( PRG_NAME, 'failed to read from [#{seqs_file_name }]: ' + $! )
203             end
204           end
205
206           begin
207             Util.check_file_for_readability( ids_mapfile_name )
208           rescue IOError
209             Util.fatal_error( PRG_NAME, 'failed to read from [#{ids_mapfile_name}]: ' + $! )
210           end
211
212           unless IDS_ONLY
213             cmd = decorator +
214             ' -t -p -f=m ' + phylogeny_file + ' ' +
215             seqs_file_name  + ' ' + TMP_FILE_1
216             puts cmd
217             begin
218               execute_cmd( cmd, log )
219             rescue Error
220               Util.fatal_error( PRG_NAME, 'error: ' + $! )
221             end
222
223             cmd = decorator + ' ' + DECORATOR_OPTIONS_DOMAINS + ' ' +
224             '-f=d ' + TMP_FILE_1 + ' ' +
225             domains_mapfile_name + ' ' + TMP_FILE_2
226             puts cmd
227             begin
228               execute_cmd( cmd, log )
229             rescue Error
230               Util.fatal_error( PRG_NAME, 'error: ' + $! )
231             end
232           end
233
234           if IDS_ONLY
235             cmd = decorator + ' ' +  DECORATOR_OPTIONS_SEQ_NAMES + ' ' +
236             '-f=n ' + phylogeny_file + ' ' +
237             ids_mapfile_name + ' ' + outfile
238             puts cmd
239             begin
240               execute_cmd( cmd, log )
241             rescue Error
242               Util.fatal_error( PRG_NAME, 'error: ' + $! )
243             end
244           else
245             cmd = decorator + ' ' +  DECORATOR_OPTIONS_SEQ_NAMES + ' ' +
246             '-f=n ' + TMP_FILE_2 + ' ' +
247             ids_mapfile_name + ' ' + outfile
248             puts cmd
249             begin
250               execute_cmd( cmd, log )
251             rescue Error
252               Util.fatal_error( PRG_NAME, 'error: ' + $! )
253             end
254             File.delete( TMP_FILE_1 )
255             File.delete( TMP_FILE_2 )
256           end
257         end
258       }
259       open( LOG_FILE, 'w' ) do | f |
260         f.write( log )
261       end
262       puts
263       Util.print_message( PRG_NAME, 'OK' )
264       puts
265     end # def run
266
267     def execute_cmd( cmd, log )
268       log << 'executing ' + cmd + NL
269       IO.popen( cmd , 'r+' ) do | pipe |
270         pipe.close_write
271         log << pipe.read + NL + NL
272       end
273       sleep( SLEEP_TIME )
274     end
275
276     def get_id( phylogeny_file_name )
277       if phylogeny_file_name =~ /^(.+?)_/
278         return $1
279       end
280       nil
281     end
282
283     def get_file( files_in_dir, phylogeny_id, suffix_pattern )
284       matching_files = Util.get_matching_files( files_in_dir, phylogeny_id, suffix_pattern )
285       if matching_files.length < 1
286         Util.fatal_error( PRG_NAME, 'no file matching [' + phylogeny_id +
287         '...' + suffix_pattern + '] present in current directory' )
288       end
289       if matching_files.length > 1
290         Util.fatal_error( PRG_NAME, 'more than one file matching [' +
291         phylogeny_id  + '...' + suffix_pattern + '] present in current directory' )
292       end
293       matching_files[ 0 ]
294     end
295
296     def get_seq_file( files_in_dir, phylogeny_id )
297       matching_files = Array.new
298
299       files_in_dir.each { | file |
300
301         if ( !File.directory?( file ) &&
302         file !~ /^\./ &&
303         file !~ /^00/ &&
304         ( file =~ /^#{phylogeny_id}__.+\d$/ || file =~ /^#{phylogeny_id}_.*\.fasta$/ ) )
305           matching_files << file
306         end
307       }
308
309       if matching_files.length < 1
310         Util.fatal_error( PRG_NAME, 'no seq file matching [' +
311         phylogeny_id + '_] present in current directory' )
312       end
313       if matching_files.length > 1
314         Util.fatal_error( PRG_NAME, 'more than one seq file matching [' +
315         phylogeny_id + '_] present in current directory' )
316       end
317       matching_files[ 0 ]
318     end
319
320     def print_help()
321       puts( "Usage:" )
322       puts()
323       puts( "  " + PRG_NAME + ".rb <suffix of intrees to be decorated> <suffix for decorated outtrees> " )
324       puts()
325       puts()
326     end
327   end # class PhylogenyiesDecorator
328
329 end # module Evoruby