in progress...
[jalview.git] / forester / ruby / evoruby / lib / evo / util / util.rb
1 #
2 # = lib/evo/util/util.rb - Util class
3 #
4 # Copyright::    Copyright (C) 2017 Christian M. Zmasek
5 # License::      GNU Lesser General Public License (LGPL)
6 #
7 # Last modified: 2017/04/27
8
9 require 'pathname'
10 require 'lib/evo/util/constants'
11
12 module Evoruby
13   class Util
14     def Util.canonical_path( parent, child = nil )
15       if child == nil
16         return  File.expand_path(Pathname.new(parent).cleanpath.to_s).to_s
17       end
18
19       s = nil
20       if parent.end_with?('/')
21         s = parent + child
22       else
23         s = parent + '/' + child
24       end
25       File.expand_path(Pathname.new(s).cleanpath.to_s).to_s
26     end
27
28     def Util.get_matching_files( files, prefix_pattern, suffix_pattern )
29       matching_files = Array.new
30       files.each { | file |
31         if ( !File.directory?( file ) &&
32         file !~ /^\./ &&
33         file =~ /^#{prefix_pattern}.*#{suffix_pattern}$/ )
34           matching_files << file
35         end
36       }
37       matching_files
38     end
39
40     def Util.get_matching_file( dir_name, prefix, suffix )
41       unless Dir.exist? dir_name
42         raise IOError, "directory [#{dir_name}] does not exist"
43       end
44
45       all_files = Dir.entries(dir_name)
46
47       if ( all_files.size <= 2 )
48         raise IOError, "directory [#{dir_name}] is empty"
49       end
50       matching_files = Array.new
51       all_files.each { | file |
52         if  (( !File.directory?( file )) && (file.end_with? suffix))
53           matching_files << file
54         end
55       }
56
57       if ( matching_files.size == 0 )
58         raise IOError, "no files ending with \"" + suffix + "\" found in [" + dir_name + "]"
59       end
60       my_prefix = prefix
61       done = false
62       more_than_one = false
63       the_one = nil;
64
65       loop do
66
67         matches = 0
68         matching_files.each { | file |
69           if file.start_with?( my_prefix )
70             matches += 1
71             if matches > 1
72               the_one = nil
73               break
74             end
75             the_one = file
76           end
77         }
78         if matches > 1
79           more_than_one = true
80           done = true
81         end
82         if matches == 1
83           done = true
84         else
85           if my_prefix.length <= 1
86             raise IOError, "no file matching \"" + prefix + "\" and ending with \"" + suffix + "\" found in [" + dir_name + "]"
87           end
88           my_prefix = my_prefix[ 0 ... ( my_prefix.length - 1 ) ]
89
90         end
91         break if done
92       end
93
94       if more_than_one
95         raise IOError, "multiple files matching \"" +  prefix  +
96         "\" and ending with \"" + suffix + "\" found in [" + dir_name + "]"
97       elsif the_one != nil
98       else
99         raise IOError, "no file matching \"" + prefix  + "\" and ending with \"" +
100         suffix + "\" found in [" + dir_name + "]"
101       end
102       the_one
103     end
104
105     def Util.normalize_seq_name( name, length, exception_if_too_long = false )
106       if name.length > length
107         if exception_if_too_long
108           error_msg = "sequence name \"#{name}\" is too long (>#{length})"
109           raise StandardError, error_msg
110         end
111         name = name[ 0, length ]
112       elsif name.length < length
113         t = length - name.length
114         t.times do
115           name = name + " "
116         end
117       end
118       name
119     end
120
121     # Returns true if char_code corresponds to: space * - . _
122     def Util.is_aa_gap_character?( char_code )
123       return ( char_code <= 32  || char_code == 42 || char_code == 45 || char_code == 46 ||char_code == 95  )
124     end
125
126     # Deletes *, digits, and whitespace, replaces BJOUZ? with X, and replaces non-(letters, -) with -
127     def Util.clean_seq_str( seq_str )
128       seq_str = seq_str.upcase
129       seq_str = seq_str.gsub( /\s+/, '' )
130       seq_str = seq_str.gsub( /\d+/, '' )
131       seq_str = seq_str.gsub( '*', '' )
132       seq_str = seq_str.gsub( /[BJOUZ?]/, 'X' )
133       seq_str = seq_str.gsub( /[^A-Z\-]/, '-' )
134       seq_str
135     end
136
137     # raises ArgumentError
138     def Util.check_file_for_readability( path )
139       unless ( File.exist?( path ) )
140         error_msg = "file [#{path}] does not exist"
141         raise IOError, error_msg
142       end
143       unless ( File.file?( path ) )
144         error_msg = "file [#{path}] is not a regular file"
145         raise IOError, error_msg
146       end
147       unless ( File.readable?( path ) )
148         error_msg = "file [#{path}] is not a readable file"
149         raise IOError, error_msg
150       end
151       if ( File.zero?( path ) )
152         error_msg = "file [#{path}] is empty"
153         raise IOError, error_msg
154       end
155     end
156
157     # raises ArgumentError
158     def Util.check_file_for_writability( path )
159       if File.directory?( path )
160         error_msg = "file [#{path}] is an existing directory"
161         raise IOError, error_msg
162       elsif File.exist?( path )
163         error_msg = "file [#{path}] already exists"
164         raise IOError, error_msg
165       elsif File.writable?( path )
166         error_msg = "file [#{path}] is not writeable"
167         raise IOError, error_msg
168       end
169     end
170
171     def Util.fatal_error_if_not_writable( prg_name, path )
172       begin
173         Util.check_file_for_writability( path )
174       rescue IOError => e
175         Util.fatal_error( prg_name, e.to_s )
176       end
177     end
178
179     def Util.fatal_error_if_not_readable( prg_name, path )
180       begin
181         Util.check_file_for_readability( path )
182       rescue IOError => e
183         Util.fatal_error( prg_name, e.to_s )
184       end
185     end
186
187     def Util.get_env_variable_value( env_variable )
188       value = ENV[env_variable]
189       if value == nil || value.empty?
190         error_msg = "apparently environment variable #{env_variable} has not been set"
191         raise StandardError, error_msg
192       end
193       value
194     end
195
196     # raises ArgumentError
197     def Util.file2array( path, split_by_semicolon )
198       Util.check_file_for_readability( path )
199       a = Array.new()
200       c = 0
201       File.open( path ) do | file |
202         while line = file.gets
203           if ( line =~ /^\s*(\S.*?)\s*$/ )
204             s = $1
205             if ( split_by_semicolon && s =~/;/ )
206               sa = s.split( /;/ )
207               for i in 0 ... sa.length()
208                 a[ c ] = sa[ i ].strip!
209               end
210             else
211               a[ c ] = s
212             end
213             c += 1
214           end
215         end
216       end
217       return a
218     end
219
220     def Util.print_program_information( prg_name,
221       prg_version,
222       prg_desc,
223       date,
224       www,
225       io = STDOUT )
226
227       ruby_version = RUBY_VERSION
228       l = prg_name.length + prg_version.length + date.length + ruby_version.length + 12
229       io.print( Evoruby::Constants::LINE_DELIMITER )
230       io.print( prg_name + " " + prg_version + " [" + date + "] [ruby " + ruby_version + "]")
231       io.print( Evoruby::Constants::LINE_DELIMITER )
232       l.times {
233         io.print( "_" )
234       }
235       io.print( Constants::LINE_DELIMITER )
236       io.print( Constants::LINE_DELIMITER )
237       io.print( prg_desc )
238       io.print( Constants::LINE_DELIMITER )
239       io.print( Constants::LINE_DELIMITER )
240       io.print( "Website: " + www )
241       io.print( Constants::LINE_DELIMITER )
242       io.print( Constants::LINE_DELIMITER )
243     end
244
245     def Util.fatal_error( prg_name, message, io = STDOUT )
246       io.print( Constants::LINE_DELIMITER )
247       if ( !Util.is_string_empty?( prg_name ) )
248         io.print( "[" + prg_name + "] > " + message )
249       else
250         io.print( " > " + message )
251       end
252       io.print( Constants::LINE_DELIMITER )
253       io.print( Constants::LINE_DELIMITER )
254       exit( -1 )
255     end
256
257     def Util.print_message( prg_name, message, io = STDOUT )
258       if ( !Util.is_string_empty?( prg_name ) )
259         io.print( "[" + prg_name + "] > " + message )
260       else
261         io.print( " > " + message )
262       end
263       io.print( Constants::LINE_DELIMITER )
264     end
265
266     def Util.print_warning_message( prg_name, message, io = STDOUT )
267       if ( !Util.is_string_empty?( prg_name ) )
268         io.print( "[" + prg_name + "] > WARNING: " + message )
269       else
270         io.print( " > " + message )
271       end
272       io.print( Constants::LINE_DELIMITER )
273     end
274
275     def Util.is_string_empty?( s )
276       return ( s == nil || s.length < 1 )
277     end
278
279     # From "Ruby Cookbook"
280     # counts_hash: key is a "name", value is the count (integer)
281     def Util.draw_histogram( counts_hash, char = "#" )
282       pairs = counts_hash.keys.collect { |x| [ x.to_s, counts_hash[ x ] ] }.sort
283       largest_key_size = pairs.max { |x, y| x[ 0 ].size <=> y[ 0 ].size }[ 0 ].size
284       pairs.inject( "" ) do | s, kv |
285         s << "#{ kv[ 0 ].ljust( largest_key_size ) }  | #{ char*kv[ 1 ] }" + Constants::LINE_DELIMITER
286       end
287     end
288
289     def Util.looks_like_fasta?( path )
290       Util.check_file_for_readability( path )
291       File.open( path ) do | file |
292         while line = file.gets
293           if ( line !~ /\S/ || line =~ /^\s*#/ )
294           elsif line =~ /^\s*>\s*(.+)/
295             return true
296           else
297             return false
298           end
299         end
300       end
301       error_msg = "unexpected format"
302       raise IOError, error_msg
303     end
304
305   end # class Util
306
307 end # module Evoruby