improved winnowing stage
[zcc.git] / lib / zcc / cli_display.rb
blob6f7be4951baa44a413d27e591c3411ac29e7a9b7
1 module ZCC
2   HighLine.track_eof = false
3   def zcc_select_good_marc(results, take_how_many)
4     #puts self[0]
5     unless results.is_a? ZCC::ResultSet
6       raise ArgumentError, "This Array doesn't have a MARC::Record!" 
7     end 
8     
9     clear = %x{clear}
10     
11     # Help statements:
12     help_help = <<-EOF
13       HELP
14       For help on a particular topic:
15         help [topic]
16       topics include:
17         #   view a record
18         p   pick a record
19         u   unselect a record
20         r   remove a record
21         
22         s   sort
23         
24         c   compare two records
25         l   lint a record
26         
27         f   forwards
28         b   backwards
29         n   next        
30         d   done searching
31         q   quit
32       EOF
33     
34     help_num = "View a record by typing in the index number\n> 16." 
35     help_p = "Pick the record at that index position into the result set to work on and save.\n>  p3"
36     help_u = "Unselect a record.\nIf you have chosen to pick a record and no longer want it,\nyou can unselect it.\n> u2"
37     help_r = "Remove the record from the result set. This is a way to narrow the result set.\n> r4\nRemoves record 4 from the result set and unclutter the screen some.\n
38     Alternately, just enter 'r' and you will have the chance to enter a range to remove."
39     
40     help_s = <<-E_O_F
41       Sort.
42       Hit 's' to get a menu of sort possibilities. 
43       Possible sorts include: title, date, subfield, content standard.
44       sort by title:   s -> t
45       sort by date:    s -> d
46       sort by content: s -> c
47       Paths to sort by subfield:
48       sort -> s 260a
49       sort -> subfield -> 260a
50       sort -> subfield -> 260    which equals 260a
51       s    -> s 260a
52       s    -> s        -> 260a
53       Sorting reindexes the result set and removes nil values.
54       E_O_F
56     help_c = "Compare two records.\nCompares the records line by line.\nLines with an 'm' match each other.\nLines with a plus sign are in the first record but not the second. Lines with a minus sign '-' are from the second record but not the first.\nEven the difference of a period matters here.\n> c [ENTER] 4-2"
57     help_l = "Lint the record.\nCheck for errors such as wrong indicator values or poor punctuation.\n> l5"
59     help_f = "Forwards through the result set.\nThe number of results shown per page is configured in zcc.yaml."
60     help_b = "backwards through the result set.\nThe number of results shown per page is configured in zcc.yaml."
61     help_n = "Next Z39.50 server/group of zservers.\nIf there are no more zservers to search, you are presented with you combined picks or a new search prompt.\n>  n"
62     help_d = "Done selecting records.\nIf at least one record has been selected you continue on to final selection.\nIf no records have been selected you are presented with a search prompt again."
63     help_d = "Quit the program."
64     
65     #the following are not implemented
66     help_all = "Select all the records in the result set to work on and save."
67     help_none = "Select none of the records from the final set."
68     
69     not_implemented = "This feature is not yet implemented."
71     #take_how_many = 'multi' #'one' # 'multi'
73     
75     loop do
76       rec_copy = results.records
77       choose do |menu|
78         print $clear_code
79         
80         menu.layout = :one_line
81         menu.readline = true
82         menu.shell  = true
83         menu.prompt = "Enter "
84         menu.select_by = :name
85         menu.update_responses
86         menu.help("help", help_help)
87         
88         rec_copy.each_index do |index|
89           showings = results.index_start + results.index_pos
90           #puts showings
91           if index >= results.index_start && index <= showings
92           ZCC.display_menu(rec_copy, index)
93           end
94           #puts "\n\n"
95         end
96         puts "Highest position: " + (results.size - 1).to_s
97         say("\aYou are currently in the winnowing stage.\nEnter 'done' if you want to process all selected records.\nIf you 'unselect' all records you will return to search.  \n".bold) if take_how_many == 'winnow'
98         
99         recs_length = rec_copy.length
100         index_range = (0..recs_length - 1)
102         menu.hidden("help", help_help) do |cmd, d| 
103           if d == ''
104             say_help(help_help)
105           elsif d == '#'
106             say_help(help_num)
107           elsif d == 'p'
108             say_help(help_p)
109             elsif d == 's'
110             say_help(help_s)
111             elsif d == 'r'
112             say_help(help_r)
113             elsif d == 'c'
114             say_help(help_c)
115             elsif d == 'l'
116             say_help(help_l)
117             elsif d == 'f'
118             say_help(help_f)
119             elsif d == 'b'
120             say_help(help_b)
121             elsif d == 'n'
122             say_help(help_n)
123             elsif d == 'u'
124             say_help(help_u)
125             elsif d == 'd'
126             say_help(help_d)
127           end
128         end
129         
130         # # => view
131               menu.choice('#'.intern, help_num) do |command, details|
132           say_help(help_num)
133         end
134         for x in index_range
135                 menu.hidden("#{x}".intern, help_num) do |cmd, details|
136             if rec_copy[cmd.to_s.to_i] == nil
137               say("You removed that record!")
138             else
139               print "\a"
140               say("#{ZCC.zcc_marc_str_bold(rec_copy[cmd.to_s.to_i].to_s, 'record')}")
141             end
142             ask("Hit ENTER to continue...".headlinez)
143           end
144         end
146         # pick
147               menu.choice("p#".intern, help_p) { |cmd, d|  say_help(help_p) }
148         menu.hidden(:p, help_p){|cmd, d| say_help(help_p)}
149               menu.hidden(:pick, help_p) { |cmd, d|  say_help(help_p) }
150         for x in index_range
151                 menu.hidden("p#{x}") do |cmd, d|
152             num_picked = cmd[1,99]
153                   rec_copy[num_picked.to_i].selected = true if rec_copy[num_picked.to_i] != nil
154             if take_how_many == 'one'
155               return results
156             end
157           end
158         end
160         #unselect
161         menu.choice("u#".intern, help_u) { |cmd, d|  say_help(help_u) }
162         menu.hidden(:u, help_u) { |cmd, d|  say_help(help_u) }
163               menu.hidden(:unselect, help_u) { |cmd, d|  say_help(help_u) }
164         for x in index_range
165                 menu.hidden("u#{x}", help_u) do |cmd, d|
166             num_picked = cmd[1,99]
167             if rec_copy[num_picked.to_i] != nil
168               rec_copy[num_picked.to_i].selected = false
169               #return
170             end
171           end
172         end
173         
174         # remove
175         #menu.hidden("r#".intern, help_r) { |cmd, d|  say_help(help_r) }
176         menu.hidden(:remove, help_r) { |cmd, d|  say_help(help_r) }
177         menu.choice(:r, help_r) do |cmd, d|  
178           say(help_r)
179           range = ask("Enter range to remove like '2-5' remove records 2, 3, 4 and 5.".boldz)
180           range_a = range.split('-').collect{|i| i.to_i}
181           if range_a[1]
182             for r in range_a[0]..range_a[1]
183               rec_copy[r] = nil if rec_copy[r]
184             end
185           else
186             rec_copy[range_a[0]] = nil
187           end          
188         end
189               menu.hidden(:remove, help_r) { |cmd, d|  say_help(help_r) }
190         
191         for x in index_range
192                 menu.hidden("r#{x}", help_r) do |cmd, d|
193             num_picked = cmd[1,99]
194             rec_copy[num_picked.to_i] = nil
195             #return
196           end
197         end
198         
199         # sort
200         menu.hidden(:sort, help_s){|cmd, d| say(help_s)}
201               menu.choice(:s, help_s) do |command, details|
202           say(help_s)
203           choose do |sort_menu|
204             
205             sort_menu.layout = :one_line
206             sort_menu.readline = true
207             sort_menu.shell  = true
208             sort_menu.prompt = "Enter "
209             sort_menu.select_by = :name
210             
211             sort_menu.choice(:title, help_s){|cmd, d| results.sort_by_title!}
212             sort_menu.choice(:date, help_s){|cmd, d| results.sort_by_date!}
213             sort_menu.choice(:content, help_s){|cmd, d| results.sort_by_standard!}
214             sort_menu.choice(:subfield, help_s){|cmd, d|
215               puts "|" + d + "|"
216               if d == ''
217                 field_subfield = ask("Enter field and subfield like so: 245c > ")
218                 results.sort_by_subfield!(field_subfield)                
219               else
220                 results.sort_by_subfield!(d)
221               end
222             }
223           end
224           #sort_menu(rec_copy)
225         end
226               
227         # compare
228         menu.hidden(:compare, help_c){|c,d| say_help(help_c)}
229         menu.choice(:c, help_c) do |c,d| 
230           say(help_c)
231           comparitors = ask("Enter the two to compare. 1-2 compares record 1 to record 2.".boldz)
232           compare_nums = comparitors.split('-').collect{|i| i.to_i}
233           if rec_copy[compare_nums[0].to_i] == nil || rec_copy[compare_nums[1].to_i] == nil
234               say_help("One of the records has been removed!")
235             else
236               say("comparison:".headline)
237               compare_marc(rec_copy[compare_nums[0].to_i], rec_copy[compare_nums[1].to_i])
238               ask("Hit ENTER to continue...".headlinez)            
239               next
240             end
241         end
242         #this always gave an Ambiguous choice error in many cases for instance c1-2 would throw the Highline error. Would like to get this to work as originally intended.
243               #menu.choice('c#-#', help_c) do |command, details|
244         #  say_help(help_c)
245         #end
246         # comparison = []
247         # for x in (0..recs_length-1)
248           # for y in (0..recs_length-1)
249             # unless x == y
250               # comparison << 'c' + x.to_s + '-' + y.to_s
251             # end
252           # end
253         # end
254         # comparison.each do |compare|
255           # menu.hidden(compare, help_c) do |cmd, details|
256             # cmd = cmd[1,99]
257             # compare_nums = cmd.split('-')
258             # if rec_copy[compare_nums[0].to_i] == nil || rec_copy[compare_nums[1].to_i] == nil
259               # say_help("One of the records has been removed!")
260             # else
261               # say("comparison:".headline)
262               # compare_marc(rec_copy[compare_nums[0].to_i], rec_copy[compare_nums[1].to_i])
263               # ask("Hit ENTER to continue...".headlinez)            
264               # next
265             # end
266           # end
267         # end
269         # lint
270         menu.hidden(:l, help_l){|c,d| say_help(help_l)}
271               menu.choice('l#', help_l) do |cmd, d|
272           say_help(help_l)
273           #ask("Which record do you want to lint? ")
274         end
275               menu.hidden(:lint) {|cmd, d| say(help_l)}
276         for x in (0..recs_length - 1)
277                 menu.hidden("l#{x}") do |cmd, d| 
278             if rec_copy[cmd[1,99].to_i] != nil
279               rec_copy[cmd[1,99].to_i].linter
280               ask("Hit ENTER to continue...".headlinez)
281             end
282           end
283         end
285         # forwards
286         menu.choice(:f, help_f) do |cmd, d| 
287           unless results.index_start + results.index_pos + 1 >= results.size
288             results.index_start += results.index_pos + 1
289                 else
290             puts "\a"
291           end
292         end
293   
294         # backwards
295         menu.choice(:b, help_b) do |cmd, d| 
296           unless results.index_start == 0
297             results.index_start -= (results.index_pos + 1)
298           else
299             puts "\a"
300           end
301         end
303         # next
304               if take_how_many == 'multi'
305           menu.choice(:n, help_n) do |command, details|
306             return 'next'
307           end
308         end
309         
310         # done => selected as many 
311               unless take_how_many == 'one'
312           menu.choice(:d, help_d) do |cmd, d|
313             return 'done'
314           end
315         end
317         # quit
318               menu.choice(:quit, "Exit program.") { |cmd, d| exit}
319         
320         # none -- only for final record taking. not currently used
321               if take_how_many == 'one'    
322                 menu.choice(:none, help_none) do |cmd, d| 
323             say("Since you cannot decide on a good record, please refer the book to a cataloger.".headline)
324             return "none"       
325           end
326         end        
327         
328       end
329     end
330   end
332   def display_menu(rec_copy, index)    
333     field_width = $term_width - 8
334     if rec_copy[index].nil?
335       say(index.to_s + " Removed from set.")      
336     else
337       if rec_copy[index].selected
338         say(index.to_s.headlinez)
339       else
340         say(index.to_s.red.boldz)
341       end
342       
343       # print rank
344       if rec_copy[index].rank >= 10
345         print " **".boldz
346       elsif  rec_copy[index].rank >= 5
347         print "  *".boldz
348       else 
349         #nothing
350       end
351       say("\t\t" + rec_copy[index].zserver.to_s)
352       #['245', '260', '300'].each do |field|
353       FIELDS_TO_SHOW.each do |field|
354         field = field.to_s
355         string = rec_copy[index].marc[field].to_s
356         string.rstrip!
357         string.lstrip!
358         begin
359           if string.length < field_width
360             say("\t" + ZCC.zcc_marc_str_bold(string, field))
361           else
362             better_string = ZCC.wrap_field(string, field_width)
363             say("\t" + ZCC.zcc_marc_str_bold(better_string, field))
364           end
365         rescue
366           #The dp here stands for display problem.
367           puts "  dp\t#{rec_copy[index].marc[field].to_s}"
368           next
369         end
370       end
371       puts "\n"
372     end        
373   end
374   
375   #currently this goes word by word. how difficult to go field by subfield?
376   def wrap_field(s, width)
377     lines = []
378     line = ""
379     smaller_width = width - 7
380     s.split(/\s+/).each do |word|
381             if (line.size + word.size) >= (width - 3)
382         lines << line 
383         line = word
384         width = smaller_width
385             elsif line.empty?
386         line = word
387             else
388         line << " " << word
389       end
390           end
391           lines << line if line
392     return lines.join("\n\t\t")
393   end
394   
395   
396   def zcc_marc_str_bold(string, field)
397     #puts field
398     #string.gsub!("'", "\'")
399     #unless field == 'record'
400     #  string.gsub!('"', '\"')
401     #end
402     #string.gsub!("(", "\(")
403     #string.gsub!(")", "\)")
404     if field == '245'
405       string.gsub!(/(\$a\s)(.*?)(\$.|$)/, "\\1" + "\\2".blue + "\\3")
406     elsif field == '260'
407       #puts 'gets here'
408       string.gsub!(/(\$c\s)([\[0-9A-Za-z\]]*)(.|$)/,  "\\1" + "\\2".blue + "\\3")
409       #string.gsub!(/(\$c)(.*)(\$\s)/,  "\\1<%= color(\"\\2\", :field_hilite) %>\\3")
410       #string.gsub!(/(\$c)(.*)(\.|$)/,  "\\1<%= color(\"\\2\", :field_hilite) %>\\3")
411     elsif field == '300'
412       string.gsub!(/(\$a)(.*?)(\$|$)/, "\\1" + "\\2".blue + "\\3")
413     elsif field == 'record'
414       string.sub!(/(LEADER.{19})(.{1})/, "\\1" + "\\2".bold.blue) #colorizes the value of the standard (AACR2, ISBD, or none)
415       string.sub!(/(LEADER.{10})(.{1})/, "\\1" + "\\2".bold.blue) #colorizes the
416     end
417     string.gsub!( /(\$.)/, "\\1".bold )
418     string.gsub!( /^(\d\d\d)\s/, "\\1 ".bold)
419     string
420   end
421   
422   def not_valid_value
423     print $clear_code
424     say("\aThat's not a valid value. Try again.".headline)
425     sleep 1
426     print "\a"
427   end
430   def say_help h
431     say(h)
432     ask("Hit enter to continue.".boldz)
433    
434   end
435   
436   def sort_menu records
437     loop do
438       choose do |menu|
439         menu.layout = :menu_only
440         menu.readline = true
441         menu.shell  = true
442         #menu.prompt = "> "
443         menu.select_by = :index_or_name
444         menu.choice(:author){|cmd, d| say("not implemented yet") }
445         menu.choice(:date){|cmd, d| say("not implemented yet")}
446         menu.choice(:subfield, "in the form of 245a"){|cmd, d| say("not implemented yet")}
447         menu.choice(:relevancy){|cmd, d| say("not implemented yet")}
448       end
449     end
450   end
452   
453   
454   
458 # Override HighLine's own defaults so that our large menu options do not display.
459 # This needs work to have better help for these error messages.
461  class Highline
462    class Menu
463      def update_responses(  )
464        append_default unless default.nil?
465        @responses = { 
466          :ambiguous_completion =>    "Ambiguous choice.  ",                     
467          :ask_on_error         =>    "?  ",                     
468          :invalid_type         =>    "You must enter a valid option.",                     :no_completion        =>    "You must pick a valid option.",                     :not_in_range         =>    "You must input a valid option." ,                     
469          :not_valid            =>    "You must have a valid option." 
470        }.merge(@responses)
471      end    
472    end
473  end