monotone

monotone Mtn Source Tree

Root/src/ui.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10// this file contains a couple utilities to deal with the user
11// interface. the global user_interface object 'ui' owns clog, so no
12// writing to it directly!
13
14
15#include "base.hh"
16#include "platform.hh"
17#include "paths.hh"
18#include "sanity.hh"
19#include "ui.hh"
20#include "lua.hh"
21#include "charset.hh"
22#include "simplestring_xform.hh"
23#include "constants.hh"
24#include "commands.hh"
25
26#include <iostream>
27#include <fstream>
28#include <iomanip>
29#include <algorithm>
30#include <map>
31#include <set>
32#include "lexical_cast.hh"
33#include "safe_map.hh"
34
35#include <cstring>
36
37#include "current_exception.hh"
38
39using std::clog;
40using std::cout;
41using std::endl;
42using std::ios_base;
43using std::locale;
44using std::make_pair;
45using std::map;
46using std::max;
47using std::ofstream;
48using std::string;
49using std::vector;
50using std::set;
51
52using boost::lexical_cast;
53
54struct user_interface ui;
55
56struct user_interface::impl
57{
58 std::set<string> issued_warnings;
59
60 bool some_tick_is_dirty; // At least one tick needs being printed
61 bool last_write_was_a_tick;
62 map<string,ticker *> tickers;
63 tick_writer * t_writer;
64 string tick_trailer;
65
66 impl() : some_tick_is_dirty(false), last_write_was_a_tick(false),
67 t_writer(0) {}
68};
69
70ticker::ticker(string const & tickname, string const & s, size_t mod,
71 bool kilocount, bool skip_display) :
72 ticks(0),
73 mod(mod),
74 total(0),
75 previous_total(0),
76 kilocount(kilocount),
77 use_total(false),
78 may_skip_display(skip_display),
79 keyname(tickname),
80 name(_(tickname.c_str())),
81 shortname(s),
82 count_size(0)
83{
84 I(ui.imp);
85 safe_insert(ui.imp->tickers, make_pair(keyname, this));
86}
87
88ticker::~ticker()
89{
90 I(ui.imp);
91 safe_erase(ui.imp->tickers, keyname);
92
93 if (ui.imp->some_tick_is_dirty)
94 ui.write_ticks();
95 ui.finish_ticking();
96}
97
98void
99ticker::operator++()
100{
101 I(ui.imp);
102 I(ui.imp->tickers.find(keyname) != ui.imp->tickers.end());
103 ticks++;
104 ui.imp->some_tick_is_dirty = true;
105 if (ticks % mod == 0)
106 ui.write_ticks();
107}
108
109void
110ticker::operator+=(size_t t)
111{
112 I(ui.imp);
113 I(ui.imp->tickers.find(keyname) != ui.imp->tickers.end());
114 size_t old = ticks;
115
116 ticks += t;
117 if (t != 0)
118 {
119 ui.imp->some_tick_is_dirty = true;
120 if (ticks % mod == 0 || (ticks / mod) > (old / mod))
121 ui.write_ticks();
122 }
123}
124
125// We would like to put these in an anonymous namespace but we can't because
126// struct user_interface needs to make them friends.
127struct tick_writer
128{
129public:
130 tick_writer() {}
131 virtual ~tick_writer() {}
132 virtual void write_ticks() = 0;
133 virtual void clear_line() = 0;
134};
135
136struct tick_write_count : virtual public tick_writer
137{
138public:
139 tick_write_count();
140 ~tick_write_count();
141 void write_ticks();
142 void clear_line();
143private:
144 std::vector<size_t> last_tick_widths;
145 size_t last_tick_len;
146};
147
148struct tick_write_dot : virtual public tick_writer
149{
150public:
151 tick_write_dot();
152 ~tick_write_dot();
153 void write_ticks();
154 void clear_line();
155private:
156 std::map<std::string,size_t> last_ticks;
157 unsigned int chars_on_line;
158};
159
160struct tick_write_stdio : virtual public tick_writer
161{
162public:
163 tick_write_stdio();
164 ~tick_write_stdio();
165 void write_ticks();
166 void clear_line();
167private:
168 std::map<std::string,size_t> last_ticks;
169};
170
171struct tick_write_nothing : virtual public tick_writer
172{
173public:
174 void write_ticks() {}
175 void clear_line() {}
176};
177
178tick_write_count::tick_write_count() : last_tick_len(0)
179{
180}
181
182tick_write_count::~tick_write_count()
183{
184}
185
186static string compose_count(ticker *tick, size_t ticks=0)
187{
188 string count;
189
190 if (ticks == 0)
191 {
192 ticks = tick->ticks;
193 }
194
195 if (tick->kilocount && ticks)
196 {
197 // automatic unit conversion is enabled
198 float div = 1.0;
199 const char *message;
200
201 if (ticks >= 1073741824)
202 {
203 div = 1073741824;
204 // xgettext: gibibytes (2^30 bytes)
205 message = N_("%.1f G");
206 }
207 else if (ticks >= 1048576)
208 {
209 div = 1048576;
210 // xgettext: mebibytes (2^20 bytes)
211 message = N_("%.1f M");
212 }
213 else if (ticks >= 1024)
214 {
215 div = 1024;
216 // xgettext: kibibytes (2^10 bytes)
217 message = N_("%.1f k");
218 }
219 else
220 {
221 div = 1;
222 message = "%.0f";
223 }
224 // We reset the mod to the divider, to avoid spurious screen updates.
225 tick->mod = max(static_cast<int>(div / 10.0), 1);
226 count = (F(message) % (ticks / div)).str();
227 }
228 else if (tick->use_total)
229 {
230 count = (F("%d/%d") % ticks % tick->total).str();
231 }
232 else
233 {
234 // xgettext: bytes
235 count = (F("%d") % ticks).str();
236 }
237
238 return count;
239}
240
241void tick_write_count::write_ticks()
242{
243 vector<size_t> tick_widths;
244 vector<string> tick_title_strings;
245 vector<string> tick_count_strings;
246
247 I(ui.imp);
248 for (map<string,ticker *>::const_iterator i = ui.imp->tickers.begin();
249 i != ui.imp->tickers.end(); ++i)
250 {
251 ticker * tick = i->second;
252
253 // if the display of this ticker has no great importance, i.e. multiple
254 // other tickers should be displayed at the same time, skip its display
255 // to save space on terminals
256 if (tick->may_skip_display)
257 continue;
258
259 if ((tick->count_size == 0 && tick->kilocount)
260 ||
261 (tick->use_total && tick->previous_total != tick->total))
262 {
263 if (!tick->kilocount && tick->use_total)
264 {
265 // We know that we're going to eventually have 'total'
266 // displayed twice on screen, plus a slash. So we should
267 // pad out this field to that eventual size to avoid
268 // spurious re-issuing of the tick titles as we expand to
269 // the goal.
270 tick->set_count_size(display_width(utf8(compose_count(tick,
271 tick->total),
272 origin::internal)));
273 tick->previous_total = tick->total;
274 }
275 else
276 {
277 // To find out what the maximum size can be, choose one the
278 // the dividers from compose_count, subtract one and have
279 // compose_count create the count string for that. Use the
280 // size of the returned count string as an initial size for
281 // this tick.
282 tick->set_count_size(display_width(utf8(compose_count(tick,
283 1048575),
284 origin::internal)));
285 }
286 }
287
288 string count(compose_count(tick));
289
290 size_t title_width = display_width(utf8(tick->name, origin::internal));
291 size_t count_width = display_width(utf8(count, origin::internal));
292
293 if (count_width > tick->count_size)
294 {
295 tick->set_count_size(count_width);
296 }
297
298 size_t max_width = max(title_width, tick->count_size);
299
300 string name;
301 name.append(max_width - title_width, ' ');
302 name.append(tick->name);
303
304 string count2;
305 count2.append(max_width - count_width, ' ');
306 count2.append(count);
307
308 tick_title_strings.push_back(name);
309 tick_count_strings.push_back(count2);
310 tick_widths.push_back(max_width);
311 }
312
313 string tickline1;
314 bool write_tickline1 = !(ui.imp->last_write_was_a_tick
315 && (tick_widths == last_tick_widths));
316 if (write_tickline1)
317 {
318 // Reissue the titles if the widths have changed.
319 tickline1 = ui.output_prefix();
320 for (size_t i = 0; i < tick_widths.size(); ++i)
321 {
322 if (i != 0)
323 tickline1.append(" | ");
324 tickline1.append(idx(tick_title_strings, i));
325 }
326 last_tick_widths = tick_widths;
327 write_tickline1 = true;
328 }
329
330 // Always reissue the counts.
331 string tickline2 = ui.output_prefix();
332 for (size_t i = 0; i < tick_widths.size(); ++i)
333 {
334 if (i != 0)
335 tickline2.append(" | ");
336 tickline2.append(idx(tick_count_strings, i));
337 }
338
339 if (!ui.imp->tick_trailer.empty())
340 {
341 tickline2 += " ";
342 tickline2 += ui.imp->tick_trailer;
343 }
344
345 size_t curr_sz = display_width(utf8(tickline2, origin::internal));
346 if (curr_sz < last_tick_len)
347 tickline2.append(last_tick_len - curr_sz, ' ');
348 last_tick_len = curr_sz;
349
350 unsigned int tw = terminal_width();
351 if(write_tickline1)
352 {
353 if (ui.imp->last_write_was_a_tick)
354 clog << '\n';
355
356 if (tw && display_width(utf8(tickline1, origin::internal)) > tw)
357 {
358 // FIXME: may chop off more than necessary (because we chop by
359 // bytes, not by characters)
360 tickline1.resize(tw);
361 }
362 clog << tickline1 << '\n';
363 }
364 if (tw && display_width(utf8(tickline2, origin::internal)) > tw)
365 {
366 // FIXME: may chop off more than necessary (because we chop by
367 // bytes, not by characters)
368 tickline2.resize(tw);
369 }
370 clog << '\r' << tickline2;
371 clog.flush();
372}
373
374void tick_write_count::clear_line()
375{
376 clog << endl;
377}
378
379
380tick_write_dot::tick_write_dot()
381{
382}
383
384tick_write_dot::~tick_write_dot()
385{
386}
387
388void tick_write_dot::write_ticks()
389{
390 I(ui.imp);
391 static const string tickline_prefix = ui.output_prefix();
392 string tickline1, tickline2;
393 bool first_tick = true;
394
395 if (ui.imp->last_write_was_a_tick)
396 {
397 tickline1 = "";
398 tickline2 = "";
399 }
400 else
401 {
402 tickline1 = ui.output_prefix() + "ticks: ";
403 tickline2 = "\n" + tickline_prefix;
404 chars_on_line = tickline_prefix.size();
405 }
406
407 for (map<string,ticker *>::const_iterator i = ui.imp->tickers.begin();
408 i != ui.imp->tickers.end(); ++i)
409 {
410 map<string,size_t>::const_iterator old = last_ticks.find(i->first);
411
412 if (!ui.imp->last_write_was_a_tick)
413 {
414 if (!first_tick)
415 tickline1 += ", ";
416
417 tickline1 +=
418 i->second->shortname + "=\"" + i->second->name + "\""
419 + "/" + lexical_cast<string>(i->second->mod);
420 first_tick = false;
421 }
422
423 if (old == last_ticks.end()
424 || ((i->second->ticks / i->second->mod)
425 > (old->second / i->second->mod)))
426 {
427 chars_on_line += i->second->shortname.size();
428 if (chars_on_line > guess_terminal_width())
429 {
430 chars_on_line = tickline_prefix.size() + i->second->shortname.size();
431 tickline2 += "\n" + tickline_prefix;
432 }
433 tickline2 += i->second->shortname;
434
435 if (old == last_ticks.end())
436 last_ticks.insert(make_pair(i->first, i->second->ticks));
437 else
438 last_ticks[i->first] = i->second->ticks;
439 }
440 }
441
442 clog << tickline1 << tickline2;
443 clog.flush();
444}
445
446void tick_write_dot::clear_line()
447{
448 clog << endl;
449}
450
451
452tick_write_stdio::tick_write_stdio()
453{}
454
455tick_write_stdio::~tick_write_stdio()
456{}
457
458void tick_write_stdio::write_ticks()
459{
460 I(ui.imp);
461 string headers, sizes, tickline;
462
463 for (map<string,ticker *>::const_iterator i = ui.imp->tickers.begin();
464 i != ui.imp->tickers.end(); ++i)
465 {
466 std::map<std::string, size_t>::iterator it =
467 last_ticks.find(i->second->shortname);
468
469 // we output each explanation stanza just once and every time the
470 // total count has been changed
471 if (it == last_ticks.end())
472 {
473 headers += i->second->shortname + ":" + i->second->name + ";";
474 sizes += i->second->shortname + "=" + lexical_cast<string>(i->second->total) + ";";
475 last_ticks[i->second->shortname] = i->second->total;
476 }
477 else
478 if (it->second != i->second->total)
479 {
480 sizes += i->second->shortname + "=" + lexical_cast<string>(i->second->total) + ";";
481 last_ticks[i->second->shortname] = i->second->total;
482 }
483
484 tickline += i->second->shortname + "#" + lexical_cast<string>(i->second->ticks) + ";";
485 }
486
487 if (!headers.empty())
488 {
489 global_sanity.maybe_write_to_out_of_band_handler('t', headers);
490 }
491 if (!sizes.empty())
492 {
493 global_sanity.maybe_write_to_out_of_band_handler('t', sizes);
494 }
495
496 I(!tickline.empty());
497 global_sanity.maybe_write_to_out_of_band_handler('t', tickline);
498}
499
500void tick_write_stdio::clear_line()
501{
502 std::map<std::string, size_t>::iterator it;
503 std::string out;
504
505 for (it = last_ticks.begin(); it != last_ticks.end(); it++)
506 {
507 out += it->first + ";";
508 }
509
510 global_sanity.maybe_write_to_out_of_band_handler('t', out);
511 last_ticks.clear();
512}
513
514// user_interface has both constructor/destructor and initialize/
515// deinitialize because there's only one of these objects, it's
516// global, and we don't want global constructors/destructors doing
517// any real work. see monotone.cc for how this is handled.
518
519void user_interface::initialize()
520{
521 imp = new user_interface::impl;
522
523 cout.exceptions(ios_base::badbit);
524#ifdef SYNC_WITH_STDIO_WORKS
525 clog.sync_with_stdio(false);
526#endif
527 clog.unsetf(ios_base::unitbuf);
528 if (have_smart_terminal())
529 set_tick_write_count();
530 else
531 set_tick_write_dot();
532
533 timestamps_enabled = false;
534}
535
536void user_interface::deinitialize()
537{
538 I(imp);
539 delete imp->t_writer;
540 delete imp;
541}
542
543void
544user_interface::finish_ticking()
545{
546 I(imp);
547 if (imp->tickers.empty() && imp->last_write_was_a_tick)
548 {
549 imp->tick_trailer = "";
550 imp->t_writer->clear_line();
551 imp->last_write_was_a_tick = false;
552 }
553}
554
555void
556user_interface::set_tick_trailer(string const & t)
557{
558 I(imp);
559 imp->tick_trailer = t;
560}
561
562void
563user_interface::set_tick_write_dot()
564{
565 I(imp);
566 if (tick_type == dot)
567 return;
568 if (imp->t_writer != 0)
569 delete imp->t_writer;
570 imp->t_writer = new tick_write_dot;
571 tick_type = dot;
572}
573
574void
575user_interface::set_tick_write_count()
576{
577 I(imp);
578 if (tick_type == count)
579 return;
580 if (imp->t_writer != 0)
581 delete imp->t_writer;
582 imp->t_writer = new tick_write_count;
583 tick_type = count;
584}
585
586void
587user_interface::set_tick_write_stdio()
588{
589 I(imp);
590 if (tick_type == stdio)
591 return;
592 if (imp->t_writer != 0)
593 delete imp->t_writer;
594 imp->t_writer = new tick_write_stdio;
595 tick_type = stdio;
596}
597
598void
599user_interface::set_tick_write_nothing()
600{
601 I(imp);
602 if (tick_type == none)
603 return;
604 if (imp->t_writer != 0)
605 delete imp->t_writer;
606 imp->t_writer = new tick_write_nothing;
607 tick_type = none;
608}
609
610user_interface::ticker_type
611user_interface::set_ticker_type(user_interface::ticker_type type)
612{
613 ticker_type ret = tick_type;
614 switch (type)
615 {
616 case count: set_tick_write_count(); break;
617 case dot: set_tick_write_dot(); break;
618 case stdio: set_tick_write_stdio(); break;
619 case none: set_tick_write_nothing(); break;
620 }
621 return ret;
622}
623
624user_interface::ticker_type
625user_interface::get_ticker_type() const
626{
627 return tick_type;
628}
629
630
631void
632user_interface::write_ticks()
633{
634 I(imp);
635 imp->t_writer->write_ticks();
636 imp->last_write_was_a_tick = true;
637 imp->some_tick_is_dirty = false;
638}
639
640void
641user_interface::warn(string const & warning)
642{
643 I(imp);
644 if (imp->issued_warnings.find(warning) == imp->issued_warnings.end())
645 {
646 string message;
647 prefix_lines_with(_("warning: "), warning, message);
648 inform(message);
649 }
650 imp->issued_warnings.insert(warning);
651}
652
653// this message should be kept consistent with unix/main.cc and
654// win32/main.cc ::bug_report_message (it is not exactly the same)
655void
656user_interface::fatal(string const & fatal)
657{
658 inform(F("fatal: %s\n"
659 "This is almost certainly a bug in monotone.\n"
660 "Please report this error message, the output of '%s version --full',\n"
661 "and a description of what you were doing to '%s'.")
662 % fatal % prog_name % PACKAGE_BUGREPORT);
663 global_sanity.dump_buffer();
664}
665// just as above, but the error appears to have come from the database.
666// Of course, since the monotone is the only thing that should be
667// writing to the database, this still probably means there's a bug.
668void
669user_interface::fatal_db(string const & fatal)
670{
671 inform(F("fatal: %s\n"
672 "This is almost certainly a bug in monotone.\n"
673 "Please report this error message, the output of '%s version --full',\n"
674 "and a description of what you were doing to '%s'.\n"
675 "This error appears to have been triggered by something in the\n"
676 "database you were using, so please preserve it in case it can\n"
677 "help in finding the bug.")
678 % fatal % prog_name % PACKAGE_BUGREPORT);
679 global_sanity.dump_buffer();
680}
681
682// Report what we can about a fatal exception (caught in the outermost catch
683// handlers) which is from the std::exception hierarchy. In this case we
684// can access the exception object, and we can try to figure out what it
685// really is by typeinfo operations.
686int
687user_interface::fatal_exception(std::exception const & ex)
688{
689 char const * what = ex.what();
690 unrecoverable_failure const * inf;
691
692 if (dynamic_cast<option::option_error const *>(&ex)
693 || dynamic_cast<recoverable_failure const *>(&ex))
694 {
695 this->inform(what);
696 return 1;
697 }
698 else if ((inf = dynamic_cast<unrecoverable_failure const *>(&ex)))
699 {
700 if (inf->caused_by() == origin::database)
701 this->fatal_db(what);
702 else
703 this->fatal(what);
704 return 3;
705 }
706 else if (dynamic_cast<ios_base::failure const *>(&ex))
707 {
708 // an error has already been printed
709 return 1;
710 }
711 else if (dynamic_cast<std::bad_alloc const *>(&ex))
712 {
713 this->inform(_("error: memory exhausted"));
714 return 1;
715 }
716 else // we can at least produce the class name and the what()...
717 {
718 using std::strcmp;
719 using std::strncmp;
720 char const * name = typeid(ex).name();
721 char const * dem = demangle_typename(name);
722
723 if (dem == 0)
724 dem = name;
725
726 // some demanglers stick "class" at the beginning of their output,
727 // which looks dumb in this context
728 if (!strncmp(dem, "class ", 6))
729 dem += 6;
730
731 // only print what() if it's interesting, i.e. nonempty and different
732 // from the name (mangled or otherwise) of the exception type.
733 if (what == 0 || what[0] == 0
734 || !strcmp(what, name)
735 || !strcmp(what, dem))
736 this->fatal(dem);
737 else
738 this->fatal(i18n_format("%s: %s") % dem % what);
739 return 3;
740 }
741}
742
743// Report what we can about a fatal exception (caught in the outermost catch
744// handlers) which is of unknown type. If we have the <cxxabi.h> interfaces,
745// we can at least get the type_info object.
746int
747user_interface::fatal_exception()
748{
749 std::type_info *type = get_current_exception_type();
750 if (type)
751 {
752 char const * name = type->name();
753 char const * dem = demangle_typename(name);
754 if (dem == 0)
755 dem = name;
756 this->fatal(dem);
757 }
758 else
759 this->fatal(_("C++ exception of unknown type"));
760 return 3;
761}
762
763string
764user_interface::output_prefix()
765{
766 std::string prefix;
767
768 if (timestamps_enabled) {
769 try {
770 // To prevent possible infinite loops from a spurious log being
771 // made down the line from the call to .as_formatted_localtime,
772 // we temporarly turn off timestamping. Not by fiddling with
773 // timestamp_enabled, though, since that one might be looked at
774 // by some other code.
775 static int do_timestamp = 0;
776
777 if (++do_timestamp == 1) {
778 // FIXME: with no app pointer around we have no access to
779 // app.lua.get_date_format_spec() here, so we use the same format
780 // which f.e. also Apache uses for its log output
781 prefix = "[" +
782 date_t::now().as_formatted_localtime("%a %b %d %H:%M:%S %Y") +
783 "] ";
784 }
785 --do_timestamp;
786 }
787 // ensure that we do not throw an exception because we could not
788 // create the timestamp prefix above
789 catch (...) {}
790 }
791
792 if (prog_name.empty()) {
793 prefix += "?: ";
794 }
795 else
796 {
797 prefix += prog_name + ": ";
798 }
799
800 return prefix;
801}
802
803static inline string
804sanitize(string const & line)
805{
806 // FIXME: you might want to adjust this if you're using a charset
807 // which has safe values in the sub-0x20 range. ASCII, UTF-8,
808 // and most ISO8859-x sets do not.
809 string tmp;
810 tmp.reserve(line.size());
811 for (size_t i = 0; i < line.size(); ++i)
812 {
813 if ((line[i] == '\n')
814 || (static_cast<unsigned char>(line[i]) >= static_cast<unsigned char>(0x20)
815 && line[i] != static_cast<char>(0x7F)))
816 tmp += line[i];
817 else
818 tmp += ' ';
819 }
820 return tmp;
821}
822
823void
824user_interface::ensure_clean_line()
825{
826 I(imp);
827 if (imp->last_write_was_a_tick)
828 {
829 write_ticks();
830 imp->t_writer->clear_line();
831 }
832 imp->last_write_was_a_tick = false;
833}
834
835void
836user_interface::redirect_log_to(system_path const & filename)
837{
838 static ofstream filestr;
839 if (filestr.is_open())
840 filestr.close();
841 filestr.open(filename.as_external().c_str(), ofstream::out | ofstream::app);
842 E(filestr.is_open(), origin::system,
843 F("failed to open log file '%s'") % filename);
844 clog.rdbuf(filestr.rdbuf());
845}
846
847void
848user_interface::inform(string const & line)
849{
850 string prefixedLine;
851 prefix_lines_with(output_prefix(), line, prefixedLine);
852 ensure_clean_line();
853 clog << sanitize(prefixedLine) << endl; // flushes
854}
855
856unsigned int
857guess_terminal_width()
858{
859 unsigned int w = terminal_width();
860 if (!w)
861 w = constants::default_terminal_width;
862 return w;
863}
864
865LUAEXT(guess_terminal_width, )
866{
867 int w = guess_terminal_width();
868 lua_pushinteger(LS, w);
869 return 1;
870}
871
872// A very simple class that adds an operator() to a string that returns
873// the string itself. This is to make it compatible with, for example,
874// the utf8 class, allowing it to be usable in other contexts without
875// encoding conversions.
876class string_adaptor : public string
877{
878public:
879 string_adaptor(string const & str) : string(str) {}
880 string_adaptor(string const & str, origin::type) : string(str) {}
881 string const & operator()(void) const { return *this; }
882 origin::type made_from;
883};
884
885// See description for format_text below for more details.
886static vector<string>
887wrap_paragraph(string const & text, size_t const line_length,
888 size_t const first_line_length)
889{
890 I(text.find('\n') == string::npos);
891
892 vector<string> wrapped;
893 size_t line_len = 0;
894 string this_line;
895
896 vector< string_adaptor > words = split_into_words(string_adaptor(text));
897 for (vector< string_adaptor >::const_iterator iter = words.begin();
898 iter != words.end(); iter++)
899 {
900 string const & word = (*iter)();
901 size_t word_len = display_width(utf8(word, origin::no_fault));
902 size_t wanted_len = (wrapped.empty() ? first_line_length : line_length);
903 if (iter != words.begin() && line_len + word_len >= wanted_len)
904 {
905 wrapped.push_back(this_line);
906 line_len = 0;
907 this_line.clear();
908 }
909 if (!this_line.empty())
910 {
911 this_line += " ";
912 ++line_len;
913 }
914 line_len += word_len;
915 this_line += word;
916 }
917 if (!this_line.empty())
918 wrapped.push_back(this_line);
919
920 return wrapped;
921}
922
923static string
924format_paragraph(string const & text, size_t const col,
925 size_t curcol, bool indent_first_line)
926{
927 string ret;
928 size_t const maxcol = guess_terminal_width();
929 vector<string> wrapped = wrap_paragraph(text, maxcol - col, maxcol - curcol);
930 for (vector<string>::iterator w = wrapped.begin(); w != wrapped.end(); ++w)
931 {
932 if (w != wrapped.begin())
933 ret += "\n";
934 if (w != wrapped.begin() || indent_first_line)
935 ret += string(col, ' ');
936 ret += *w;
937 }
938 return ret;
939}
940
941// Reformats the given text so that it fits in the current screen with no
942// wrapping.
943//
944// The input text is a series of words and sentences. Paragraphs may be
945// separated with a '\n' character, which is taken into account to do the
946// proper formatting. The text should not finish in '\n'.
947//
948// 'col' specifies the column where the text will start and 'curcol'
949// specifies the current position of the cursor.
950string
951format_text(string const & text, size_t const col,
952 size_t curcol, bool indent_first_line)
953{
954 I(curcol <= col);
955
956 string formatted;
957
958 vector< string > lines;
959 split_into_lines(text, lines);
960 for (vector< string >::const_iterator iter = lines.begin();
961 iter != lines.end(); iter++)
962 {
963 string const & line = *iter;
964
965 formatted += format_paragraph(line, col, curcol, indent_first_line);
966 if (iter + 1 != lines.end())
967 formatted += "\n\n";
968 curcol = 0;
969 }
970
971 return formatted;
972}
973
974// See description for the other format_text above for more details.
975string
976format_text(i18n_format const & text, size_t const col,
977 size_t curcol, bool indent_first_line)
978{
979 return format_text(text.str(), col, curcol, indent_first_line);
980}
981
982namespace {
983 class option_text
984 {
985 string names;
986 string desc;
987 vector<string> formatted_names;
988 vector<string> formatted_desc;
989 public:
990 option_text(string const & n, string const & d)
991 : names(n), desc(d) { }
992 size_t format_names(size_t const width)
993 {
994 size_t const full_len = display_width(utf8(names, origin::no_fault));
995 size_t const slash = names.find('/');
996 if (slash == string::npos || full_len <= width)
997 {
998 formatted_names.push_back(names);
999 return full_len;
1000 }
1001
1002 formatted_names.push_back(names.substr(0, slash-1));
1003 formatted_names.push_back(" " + names.substr(slash-1));
1004
1005 size_t ret = 0;
1006 for (vector<string>::const_iterator i = formatted_names.begin();
1007 i != formatted_names.end(); ++i)
1008 {
1009 ret = max(ret, display_width(utf8(*i, origin::no_fault)));
1010 }
1011 return ret;
1012 }
1013 void format_desc(size_t const width)
1014 {
1015 formatted_desc = wrap_paragraph(desc, width, width);
1016 }
1017 string formatted(size_t pre_indent, size_t space, size_t namelen) const
1018 {
1019 string ret;
1020 string empty;
1021 size_t const lines = max(formatted_names.size(), formatted_desc.size());
1022 for (size_t i = 0; i < lines; ++i)
1023 {
1024 string const * left = &empty;
1025 if (i < formatted_names.size())
1026 left = &formatted_names.at(i);
1027 string const * right = &empty;
1028 if (i < formatted_desc.size())
1029 right = &formatted_desc.at(i);
1030
1031 ret += string(pre_indent, ' ')
1032 + *left + string(namelen - left->size(), ' ')
1033 + string(space, ' ')
1034 + *right
1035 + "\n";
1036 }
1037 return ret;
1038 }
1039 };
1040}
1041
1042// Format a block of options and their descriptions.
1043static string
1044format_usage_strings(vector<string> const & names,
1045 vector<string> const & descriptions)
1046{
1047 // " --long [ -s ] <arg> description goes here"
1048 // ^ ^^ ^^ ^^ ^
1049 // | | \ namelen / | | \ descwidth /| <- edge of screen
1050 // ^^^^ ^^^^
1051 // pre_indent space
1052 string result;
1053
1054 size_t const pre_indent = 2; // empty space on the left
1055 size_t const space = 2; // space after the longest option, before the description
1056 size_t const termwidth = guess_terminal_width();
1057 size_t const desired_namewidth = (termwidth - pre_indent - space) / 3;
1058 size_t namelen = 0;
1059
1060 vector<option_text> texts;
1061 I(names.size() == descriptions.size());
1062 for (vector<string>::const_iterator n = names.begin(), d = descriptions.begin();
1063 n != names.end(); ++n, ++d)
1064 {
1065 if (n->empty())
1066 continue;
1067 texts.push_back(option_text(*n, *d));
1068
1069 size_t my_name_len = texts.back().format_names(desired_namewidth);
1070 if (my_name_len > namelen)
1071 namelen = my_name_len;
1072 }
1073
1074 size_t const descindent = pre_indent + namelen + space;
1075 size_t const descwidth = termwidth - descindent;
1076
1077 for (vector<option_text>::iterator i = texts.begin();
1078 i != texts.end(); ++i)
1079 {
1080 i->format_desc(descwidth);
1081
1082 result += i->formatted(pre_indent, space, namelen);
1083 }
1084
1085 result += '\n';
1086 return result;
1087}
1088
1089static string
1090get_usage_str(options::options_type const & optset, options & opts)
1091{
1092 vector<string> names;
1093 vector<string> descriptions;
1094 unsigned int maxnamelen;
1095
1096 optset.instantiate(&opts).get_usage_strings(names, descriptions, maxnamelen,
1097 opts.show_hidden_commands);
1098 return format_usage_strings(names, descriptions);
1099}
1100
1101void
1102user_interface::inform_usage(usage const & u, options & opts)
1103{
1104 // we send --help output to stdout, so that "mtn --help | less" works
1105 // but we send error-triggered usage information to stderr, so that if
1106 // you screw up in a script, you don't just get usage information sent
1107 // merrily down your pipes.
1108 std::ostream & usage_stream = (opts.help ? cout : clog);
1109
1110 string visibleid;
1111 if (!u.which.empty())
1112 visibleid = join_words(vector< utf8 >(u.which.begin() + 1,
1113 u.which.end()))();
1114
1115 usage_stream << F("Usage: %s [OPTION...] command [ARG...]") %
1116 prog_name << "\n\n";
1117
1118 if (u.which.empty())
1119 usage_stream << get_usage_str(options::opts::globals(), opts);
1120
1121 // Make sure to hide documentation that's not part of
1122 // the current command.
1123 options::options_type cmd_options =
1124 commands::command_options(u.which);
1125 if (!cmd_options.empty())
1126 {
1127 usage_stream
1128 << F("Options specific to '%s %s' "
1129 "(run '%s help' to see global options):")
1130 % prog_name % visibleid % prog_name
1131 << "\n\n";
1132 usage_stream << get_usage_str(cmd_options, opts);
1133 }
1134
1135 commands::explain_usage(u.which, opts.show_hidden_commands, usage_stream);
1136}
1137
1138bool
1139user_interface::enable_timestamps(bool enable)
1140{
1141 bool ret = timestamps_enabled;
1142 timestamps_enabled = enable;
1143 return ret;
1144}
1145
1146// Local Variables:
1147// mode: C++
1148// fill-column: 76
1149// c-file-style: "gnu"
1150// indent-tabs-mode: nil
1151// End:
1152// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status