update get harvester results for new site
[function2gene.git] / bin / do_it_all
1 #! /usr/bin/perl
2 # do_it_all, is part of the gene search suite and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
6
7
8 use warnings;
9 use strict;
10
11 use Getopt::Long;
12 use Pod::Usage;
13
14 use Storable;
15
16 =head1 NAME
17
18   do_it_all - Call out to each of the search modules to search for
19   each of the terms
20
21 =head1 SYNOPSIS
22
23  do_it_all --keywords keywords.txt --results gene_search_results
24
25  Options:
26   --keywords newline delineated list of keywords to search for
27   --results directory to store results in
28   --database databases to search
29   --restart-at mode to start searching at
30   --debug, -d debugging level (Default 0)
31   --help, -h display this help
32   --man, -m display manual
33
34 =head1 OPTIONS
35
36 =over
37
38 =item B<--keywords>
39
40 A file which contains a newline delinated list of keywords to search
41 for. Can be specified multiple times. Lines starting with # or ; are
42 ignored.
43
44 =item B<--results>
45
46 Directory in which to store results; also stores the current state of
47 the system
48
49 =item B<--database>
50
51 Databases to search, can be specified multiple times. [Defaults to
52 NCBI, GeneCards and Harvester, the only currently supported
53 databases.]
54
55 =item B<--restart-at>
56
57 If you need to restart the process at a particular state (which has
58 already been completed) specify this option.
59
60 =item B<--debug, -d>
61
62 Debug verbosity. (Default 0)
63
64 =item B<--help, -h>
65
66 Display brief useage information.
67
68 =item B<--man, -m>
69
70 Display this manual.
71
72 =back
73
74 =head1 EXAMPLES
75
76
77 =cut
78
79
80 use vars qw($DEBUG);
81 use Cwd qw(abs_path);
82 use IO::File;
83 use Storable qw(thaw freeze);
84 use File::Basename qw(basename);
85
86 my %options = (databases       => [],
87                keywords        => [],
88                debug           => 0,
89                help            => 0,
90                man             => 0,
91                results         => '',
92                );
93
94 GetOptions(\%options,'keywords=s@','databases=s@',
95            'restart_at|restart-at=s','results=s',
96            'debug|d+','help|h|?','man|m');
97
98 pod2usage() if $options{help};
99 pod2usage({verbose=>2}) if $options{man};
100
101 my $base_dir = basename($0);
102
103 my $ERRORS='';
104
105 $ERRORS.="restart-at must be one of get, parse or combine\n" if
106      exists $options{restart_at} and $options{restart_at} !~ /^(?:get|parse|combine)$/;
107
108 $ERRORS.="unknown database(s)" if
109      @{$options{databases}} and
110      grep {$_ !~ /^(?:ncbi|genecards|harvester)$/i} @{$options{databases}};
111
112 if (not length $options{results}) {
113      $ERRORS.="results directory not specified";
114 }
115 elsif (not -d $options{results} or not -w $options{results}) {
116      $ERRORS.="results directory $options{results} does not exist or is not writeable";
117 }
118
119 pod2usage($ERRORS) if length $ERRORS;
120
121 if (not @{$options{databases}}) {
122      $options{databases} = [qw(ncbi genecards harvester)]
123 }
124
125 $DEBUG = $options{debug};
126
127 # There are three states for our engine
128 # Getting results
129 # Parsing them
130 # Combining results
131
132 # first, check to see if the state in the result directory exists
133
134 my %state;
135
136 $options{keywords} = [map {abs_path($_)} @{$options{keywords}}];
137
138 chdir $options{results} or die "Unable to chdir to $options{results}";
139
140 if (-e "do_it_all_state") {
141      ADVISE("Using existing state information");
142      my $state_fh = IO::File->new("do_it_all_state",'r') or die
143           "Unable to open state file for reading: $!";
144      local $/;
145      my $state_file = <$state_fh>;
146      %state = %{thaw($state_file)} or die "Unable to thaw state file";
147 }
148 else {
149      ADVISE("Starting new run");
150      %state = (keywords => [],
151                databases => [map {lc($_)} @{$options{databases}}],
152                done_keywords => {
153                                  get => {},
154                                  parse => {},
155                                  combine => {},
156                                 },
157               );
158 }
159
160 my @new_keywords;
161 if (@{$options{keywords}}) {
162      # uniqify keywords
163      my %old_keywords;
164      @old_keywords{@{$state{keywords}}} = (1) x @{$state{keywords}};
165      for my $keyword_file (@{$options{keywords}}) {
166           my $keyword_fh = IO::File->new($keyword_file,'r') or die
167                "Unable to open $keyword_file for reading: $!";
168           local $/;
169           while (<$keyword_fh>) {
170                next if /^\s*[#;]/;
171                chomp;
172                if (not $old_keywords{$_}) {
173                     DEBUG("Adding new keyword '$_'");
174                     push @new_keywords, $_;
175                }
176                else {
177                     DEBUG("Not adding duplicate keyword '$_'");
178                }
179           }
180      }
181 }
182
183 if (exists $options{restart_at} and length $options{restart_at}) {
184      if (lc($options{restart_at}) eq 'get') {
185           delete $state{done_keywords}{get};
186           delete $state{done_keywords}{parse};
187           delete $state{done_keywords}{combine};
188      }
189      elsif (lc($options{restart_at}) eq 'parse') {
190           delete $state{done_keywords}{parse};
191           delete $state{done_keywords}{combine};
192      }
193      elsif (lc($options{restart_at}) eq 'combine') {
194           delete $state{done_keywords}{combine};
195      }
196 }
197
198 # now we need to figure out what has to happen
199 # for each keyword, we check to see if we've got results, parsed
200 # results, and combined it. If not, we queue up those actions.
201
202 my %actions = (combine => 0,
203                get     => {},
204                parse   => {},
205               );
206
207 if (not @{$state{keywords}}) {
208      ADVISE("There are no keywords specified");
209 }
210
211 for my $keyword (@{$state{keywords}}) {
212      for my $database (@{$state{databases}}) {
213           if (not exists $state{done_keywords}{get}{$database}{$keyword}) {
214                push @{$actions{get}{$database}}, $keyword;
215                delete $state{done_keywords}{parse}{$database}{$keyword} if
216                     exists $state{done_keywords}{parse}{$database}{$keyword};
217                delete $state{done_keywords}{combine}{$database}{$keyword} if
218                     exists $state{done_keywords}{combine}{$database}{$keyword};
219           }
220           if (not exists $state{done_keywords}{parse}{$database}{$keyword}) {
221                push @{$actions{parse}{$database}},$keyword;
222                delete $state{done_keywords}{combine}{$database}{$keyword} if
223                     exists $state{done_keywords}{combine}{$database}{$keyword};
224           }
225           if (not exists $state{done_keywords}{combine}{$database}{$keyword}) {
226               $actions{combine} = 1;
227           }
228      }
229 }
230
231 use threads;
232 use Thread::Queue;
233
234 for my $state (qw(get parse)) {
235      my %databases;
236      for my $database (keys %{$actions{$state}}) {
237           next unless @{$actions{$state}{$database}};
238           $databases{$database}{queue} = Thread::Queue->new;
239           $databases{$database}{thread} = threads->new(\&handle_action($state,$database,$databases{database}{queue}));
240           $databases{$database}{queue}->enqueue(@{$actions{$state}{$database}});
241           $databases{$database}{queue}->enqueue(undef);
242      }
243      my $ERRORS=0;
244      for my $database (keys %databases) {
245           my ($actioned_keywords,$failed_keywords) = $databases{$database}{thread}->join;
246           if (@{$failed_keywords}) {
247                ADVISE("These keywords failed during '$state' of '$database':",@{$failed_keywords});
248                $ERRORS=1;
249           }
250           @{$state{done_keywords}{$state}{$database}}{@{$actioned_keywords}} = (1) x @{$actioned_keywords};
251           delete @{$state{done_keywords}{$state}{$database}}{@{$failed_keywords}};
252      }
253      save_state(\%state);
254      if ($ERRORS) {
255           WARN("Stoping, as there are errors");
256           exit 1;
257      }
258 }
259
260 if ($actions{combine}) {
261      save_state(\%state);
262      # deal with combining results
263      my @parsed_results = map { my $db = $_;
264                                 map {
265                                      "parsed_results_${db}_${_}.txt"
266                                 } keys %{$state{done_keywords}{parse}{$db}}
267                            } keys %{$state{done_keywords}{parse}};
268
269      write_command_to_file('combined_results.txt',
270                            "$base_dir/combine_results",
271                            @parsed_results,
272                           );
273      for my $result (@parsed_results) {
274           s/^parsed_results_//;
275           s/\.txt$//;
276           my ($db,$keyword) = split /_/, $_, 2;
277           $state{done_keywords}{combined}{$db}{$keyword} = 1;
278      }
279      save_state(\%state);
280      ADVISE("Finished; results in $options{results}/combined_results");
281 }
282 else {
283      ADVISE('Nothing to do. [Perhaps you wanted --restart-at?]');
284 }
285
286 sub handle_action{
287      my ($state,$database,$queue) = @_;
288      my $keyword;
289      my $actioned_keywords = ();
290      my $failed_keywords = ();
291      while ($keyword = $queue->dequeue) {
292           # handle the action, baybee
293           if ($state eq 'get') {
294                my $command_fh;
295                open($command_fh,'|-',
296                     "get_${database}_results",
297                    );
298                print {$command_fh} "$keyword\n";
299                close($command_fh);
300                if ($? != 0) {
301                     WARN("get_${database}_results with keyword $keyword failed with error code ".($?>>8));
302                     next;
303                }
304           }
305           elsif ($state eq 'parse') {
306                eval {
307                     write_command_to_file("parsed_results_${database}_${keyword}.txt",
308                                           "parse_${database}_results",
309                                           '--keywords',
310                                           $keyword,
311                                          );
312                };
313                if ($@) {
314                     WARN("parse_${database}_results failed with $@");
315                     next;
316                }
317           }
318           else {
319                die "I don't know how to handle state $state";
320           }
321           ADVISE("$state results from '$database' for '$keyword'");
322           push @{$actioned_keywords},$keyword;
323      }
324      return ($actioned_keywords,$failed_keywords);
325 }
326
327 sub save_state{
328      my ($state) = @_;
329      my $state_fh = IO::File->new("do_it_all_state",'w') or die
330           "Unable to open state file for writing: $!";
331      print {$state_fh} freeze($state) or die "Unable to freeze state file";
332      close $state_fh or die "Unable to close state file: $!";
333 }
334
335 sub write_command_to_file{
336      my ($file,@command);
337      my $fh = IO::File->new($file,'w') or
338           die "Unable to open $file for writing: $!";
339      my $command_fh;
340      open($command_fh,'-|',
341           @command,
342          ) or die "Unable to execute $command[0] $!";
343      print {$fh} <$command_fh>;
344      close $fh;
345      close $command_fh or die "$command[0] failed with ".($?>>8);
346 }
347
348
349 sub ADVISE{
350      print STDOUT map {($_,qq(\n))} @_;
351 }
352
353 sub DEBUG{
354      print STDERR map {($_,qq(\n))} @_;
355 }
356
357
358 sub WARN {
359      print STDERR map {($_,qq(\n))} @_;
360 }
361
362 __END__