monotone

monotone Mtn Source Tree

Root/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#include "config.h"
15
16#include "platform.hh"
17#include "sanity.hh"
18#include "ui.hh"
19#include "charset.hh"
20#include "simplestring_xform.hh"
21#include "constants.hh"
22
23#include <iostream>
24#include <fstream>
25#include <iomanip>
26#include <algorithm>
27#include <boost/lexical_cast.hpp>
28
29#include <typeinfo>
30#include <cstring>
31
32// Add #ifdeffage here as appropriate for other compiler-specific ways to
33// get this information. Windows note: as best I can determine from poking
34// around on MSDN, MSVC type_info.name() is already demangled, and there is
35// no documented equivalent of __cxa_current_exception_type().
36#ifdef HAVE_CXXABI_H
37 #include <cxxabi.h>
38 #ifdef HAVE___CXA_DEMANGLE
39 inline char const * demangle_typename(char const * name)
40 {
41 int status = -1;
42 char * dem = abi::__cxa_demangle(name, 0, 0, &status);
43 if (status == 0)
44 return dem;
45 else
46 return 0;
47 }
48 #else
49 #define demangle_typename(x) 0
50 #endif
51 #ifdef HAVE___CXA_CURRENT_EXCEPTION_TYPE
52 #define get_current_exception_type() abi::__cxa_current_exception_type()
53 #else
54 #define get_current_exception_type() 0
55 #endif
56#else
57 #define demangle_typename(x) 0
58 #define get_current_exception_type() 0
59#endif
60
61using std::clog;
62using std::cout;
63using std::endl;
64using std::ios_base;
65using std::locale;
66using std::make_pair;
67using std::map;
68using std::max;
69using std::ofstream;
70using std::string;
71using std::vector;
72
73using boost::lexical_cast;
74
75struct user_interface ui;
76
77ticker::ticker(string const & tickname, string const & s, size_t mod,
78 bool kilocount) :
79 ticks(0),
80 mod(mod),
81 total(0),
82 previous_total(0),
83 kilocount(kilocount),
84 use_total(false),
85 keyname(tickname),
86 name(_(tickname.c_str())),
87 shortname(s),
88 count_size(0)
89{
90 I(ui.tickers.find(keyname) == ui.tickers.end());
91 ui.tickers.insert(make_pair(keyname, this));
92}
93
94ticker::~ticker()
95{
96 I(ui.tickers.find(keyname) != ui.tickers.end());
97 if (ui.some_tick_is_dirty)
98 {
99 ui.write_ticks();
100 }
101 ui.tickers.erase(keyname);
102 ui.finish_ticking();
103}
104
105void
106ticker::operator++()
107{
108 I(ui.tickers.find(keyname) != ui.tickers.end());
109 ticks++;
110 ui.some_tick_is_dirty = true;
111 if (ticks % mod == 0)
112 ui.write_ticks();
113}
114
115void
116ticker::operator+=(size_t t)
117{
118 I(ui.tickers.find(keyname) != ui.tickers.end());
119 size_t old = ticks;
120
121 ticks += t;
122 if (t != 0)
123 {
124 ui.some_tick_is_dirty = true;
125 if (ticks % mod == 0 || (ticks / mod) > (old / mod))
126 ui.write_ticks();
127 }
128}
129
130
131tick_write_count::tick_write_count() : last_tick_len(0)
132{
133}
134
135tick_write_count::~tick_write_count()
136{
137}
138
139static string compose_count(ticker *tick, size_t ticks=0)
140{
141 string count;
142
143 if (ticks == 0)
144 {
145 ticks = tick->ticks;
146 }
147
148 if (tick->kilocount && ticks)
149 {
150 // automatic unit conversion is enabled
151 float div = 1.0;
152 const char *message;
153
154 if (ticks >= 1073741824)
155 {
156 div = 1073741824;
157 // xgettext: gibibytes (2^30 bytes)
158 message = N_("%.1f G");
159 }
160 else if (ticks >= 1048576)
161 {
162 div = 1048576;
163 // xgettext: mebibytes (2^20 bytes)
164 message = N_("%.1f M");
165 }
166 else if (ticks >= 1024)
167 {
168 div = 1024;
169 // xgettext: kibibytes (2^10 bytes)
170 message = N_("%.1f k");
171 }
172 else
173 {
174 div = 1;
175 message = "%.0f";
176 }
177 // We reset the mod to the divider, to avoid spurious screen updates.
178 tick->mod = max(static_cast<int>(div / 10.0), 1);
179 count = (F(message) % (ticks / div)).str();
180 }
181 else if (tick->use_total)
182 {
183 count = (F("%d/%d") % ticks % tick->total).str();
184 }
185 else
186 {
187 // xgettext: bytes
188 count = (F("%d") % ticks).str();
189 }
190
191 return count;
192}
193
194void tick_write_count::write_ticks()
195{
196 vector<size_t> tick_widths;
197 vector<string> tick_title_strings;
198 vector<string> tick_count_strings;
199
200 for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
201 i != ui.tickers.end(); ++i)
202 {
203 ticker * tick = i->second;
204
205 if ((tick->count_size == 0 && tick->kilocount)
206 ||
207 (tick->use_total && tick->previous_total != tick->total))
208 {
209 if (!tick->kilocount && tick->use_total)
210 {
211 // We know that we're going to eventually have 'total'
212 // displayed twice on screen, plus a slash. So we should
213 // pad out this field to that eventual size to avoid
214 // spurious re-issuing of the tick titles as we expand to
215 // the goal.
216 tick->set_count_size(display_width(utf8(compose_count(tick,
217 tick->total))));
218 tick->previous_total = tick->total;
219 }
220 else
221 {
222 // To find out what the maximum size can be, choose one the
223 // the dividers from compose_count, subtract one and have
224 // compose_count create the count string for that. Use the
225 // size of the returned count string as an initial size for
226 // this tick.
227 tick->set_count_size(display_width(utf8(compose_count(tick,
228 1048575))));
229 }
230 }
231
232 string count(compose_count(tick));
233
234 size_t title_width = display_width(utf8(tick->name));
235 size_t count_width = display_width(utf8(count));
236
237 if (count_width > tick->count_size)
238 {
239 tick->set_count_size(count_width);
240 }
241
242 size_t max_width = max(title_width, tick->count_size);
243
244 string name;
245 name.append(max_width - title_width, ' ');
246 name.append(tick->name);
247
248 string count2;
249 count2.append(max_width - count_width, ' ');
250 count2.append(count);
251
252 tick_title_strings.push_back(name);
253 tick_count_strings.push_back(count2);
254 tick_widths.push_back(max_width);
255 }
256
257 string tickline1;
258 bool write_tickline1 = !(ui.last_write_was_a_tick
259 && (tick_widths == last_tick_widths));
260 if (write_tickline1)
261 {
262 // Reissue the titles if the widths have changed.
263 tickline1 = ui.output_prefix();
264 for (size_t i = 0; i < tick_widths.size(); ++i)
265 {
266 if (i != 0)
267 tickline1.append(" | ");
268 tickline1.append(idx(tick_title_strings, i));
269 }
270 last_tick_widths = tick_widths;
271 write_tickline1 = true;
272 }
273
274 // Always reissue the counts.
275 string tickline2 = ui.output_prefix();
276 for (size_t i = 0; i < tick_widths.size(); ++i)
277 {
278 if (i != 0)
279 tickline2.append(" | ");
280 tickline2.append(idx(tick_count_strings, i));
281 }
282
283 if (!ui.tick_trailer.empty())
284 {
285 tickline2 += " ";
286 tickline2 += ui.tick_trailer;
287 }
288
289 size_t curr_sz = display_width(utf8(tickline2));
290 if (curr_sz < last_tick_len)
291 tickline2.append(last_tick_len - curr_sz, ' ');
292 last_tick_len = curr_sz;
293
294 unsigned int tw = terminal_width();
295 if(write_tickline1)
296 {
297 if (ui.last_write_was_a_tick)
298 clog << '\n';
299
300 if (tw && display_width(utf8(tickline1)) > tw)
301 {
302 // FIXME: may chop off more than necessary (because we chop by
303 // bytes, not by characters)
304 tickline1.resize(tw);
305 }
306 clog << tickline1 << '\n';
307 }
308 if (tw && display_width(utf8(tickline2)) > tw)
309 {
310 // FIXME: may chop off more than necessary (because we chop by
311 // bytes, not by characters)
312 tickline2.resize(tw);
313 }
314 clog << '\r' << tickline2;
315 clog.flush();
316}
317
318void tick_write_count::clear_line()
319{
320 clog << endl;
321}
322
323
324tick_write_dot::tick_write_dot()
325{
326}
327
328tick_write_dot::~tick_write_dot()
329{
330}
331
332void tick_write_dot::write_ticks()
333{
334 static const string tickline_prefix = ui.output_prefix();
335 string tickline1, tickline2;
336 bool first_tick = true;
337
338 if (ui.last_write_was_a_tick)
339 {
340 tickline1 = "";
341 tickline2 = "";
342 }
343 else
344 {
345 tickline1 = ui.output_prefix() + "ticks: ";
346 tickline2 = "\n" + tickline_prefix;
347 chars_on_line = tickline_prefix.size();
348 }
349
350 for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
351 i != ui.tickers.end(); ++i)
352 {
353 map<string,size_t>::const_iterator old = last_ticks.find(i->first);
354
355 if (!ui.last_write_was_a_tick)
356 {
357 if (!first_tick)
358 tickline1 += ", ";
359
360 tickline1 +=
361 i->second->shortname + "=\"" + i->second->name + "\""
362 + "/" + lexical_cast<string>(i->second->mod);
363 first_tick = false;
364 }
365
366 if (old == last_ticks.end()
367 || ((i->second->ticks / i->second->mod)
368 > (old->second / i->second->mod)))
369 {
370 chars_on_line += i->second->shortname.size();
371 if (chars_on_line > guess_terminal_width())
372 {
373 chars_on_line = tickline_prefix.size() + i->second->shortname.size();
374 tickline2 += "\n" + tickline_prefix;
375 }
376 tickline2 += i->second->shortname;
377
378 if (old == last_ticks.end())
379 last_ticks.insert(make_pair(i->first, i->second->ticks));
380 else
381 last_ticks[i->first] = i->second->ticks;
382 }
383 }
384
385 clog << tickline1 << tickline2;
386 clog.flush();
387}
388
389void tick_write_dot::clear_line()
390{
391 clog << endl;
392}
393
394// user_interface has both constructor/destructor and initialize/
395// deinitialize because there's only one of these objects, it's
396// global, and we don't want global constructors/destructors doing
397// any real work. see monotone.cc for how this is handled.
398
399user_interface::user_interface() :
400 prog_name("?"),
401 last_write_was_a_tick(false),
402 t_writer(0)
403{}
404
405void user_interface::initialize()
406{
407 cout.exceptions(ios_base::badbit);
408#ifdef SYNC_WITH_STDIO_WORKS
409 clog.sync_with_stdio(false);
410#endif
411 clog.unsetf(ios_base::unitbuf);
412 if (have_smart_terminal())
413 set_tick_writer(new tick_write_count);
414 else
415 set_tick_writer(new tick_write_dot);
416}
417
418user_interface::~user_interface()
419{}
420
421void user_interface::deinitialize()
422{
423 delete t_writer;
424}
425
426void
427user_interface::finish_ticking()
428{
429 if (tickers.size() == 0 &&
430 last_write_was_a_tick)
431 {
432 tick_trailer = "";
433 t_writer->clear_line();
434 last_write_was_a_tick = false;
435 }
436}
437
438void
439user_interface::set_tick_trailer(string const & t)
440{
441 tick_trailer = t;
442}
443
444void
445user_interface::set_tick_writer(tick_writer * t)
446{
447 if (t_writer != 0)
448 delete t_writer;
449 t_writer = t;
450}
451
452void
453user_interface::write_ticks()
454{
455 t_writer->write_ticks();
456 last_write_was_a_tick = true;
457 some_tick_is_dirty = false;
458}
459
460void
461user_interface::warn(string const & warning)
462{
463 if (issued_warnings.find(warning) == issued_warnings.end())
464 {
465 string message;
466 prefix_lines_with(_("warning: "), warning, message);
467 inform(message);
468 }
469 issued_warnings.insert(warning);
470}
471
472// this message should be kept consistent with unix/main.cc and
473// win32/main.cc ::bug_report_message (it is not exactly the same)
474void
475user_interface::fatal(string const & fatal)
476{
477 inform(F("fatal: %s\n"
478 "this is almost certainly a bug in monotone.\n"
479 "please send this error message, the output of '%s --full-version',\n"
480 "and a description of what you were doing to %s.")
481 % fatal % prog_name % PACKAGE_BUGREPORT);
482 global_sanity.dump_buffer();
483}
484
485// Report what we can about a fatal exception (caught in the outermost catch
486// handlers) which is from the std::exception hierarchy. In this case we
487// can access the exception object.
488void
489user_interface::fatal_exception(std::exception const & ex)
490{
491 using std::strcmp;
492 using std::strncmp;
493 char const * ex_name = typeid(ex).name();
494 char const * ex_dem = demangle_typename(ex_name);
495 char const * ex_what = ex.what();
496
497 if (ex_dem == 0)
498 ex_dem = ex_name;
499
500 // some demanglers stick "class" at the beginning of their output,
501 // which looks dumb in this context
502 if (!strncmp(ex_dem, "class ", 6))
503 ex_dem += 6;
504
505 // only print what() if it's interesting, i.e. nonempty and different
506 // from the name (mangled or otherwise) of the exception type.
507 if (ex_what == 0 || ex_what[0] == 0
508 || !strcmp(ex_what, ex_name)
509 || !strcmp(ex_what, ex_dem))
510 this->fatal(ex_dem);
511 else
512 this->fatal(i18n_format("%s: %s") % ex_dem % ex_what);
513}
514
515// Report what we can about a fatal exception (caught in the outermost catch
516// handlers) which is of unknown type. If we have the <cxxabi.h> interfaces,
517// we can at least get the type_info object.
518void
519user_interface::fatal_exception()
520{
521 std::type_info *ex_type = get_current_exception_type();
522 if (ex_type)
523 {
524 char const * ex_name = ex_type->name();
525 char const * ex_dem = demangle_typename(ex_name);
526 if (ex_dem == 0)
527 ex_dem = ex_name;
528 this->fatal(ex_dem);
529 }
530 else
531 this->fatal("exception of unknown type");
532}
533
534string
535user_interface::output_prefix()
536{
537 if (prog_name.empty()) {
538 return "?: ";
539 }
540 return prog_name + ": ";
541}
542
543static inline string
544sanitize(string const & line)
545{
546 // FIXME: you might want to adjust this if you're using a charset
547 // which has safe values in the sub-0x20 range. ASCII, UTF-8,
548 // and most ISO8859-x sets do not.
549 string tmp;
550 tmp.reserve(line.size());
551 for (size_t i = 0; i < line.size(); ++i)
552 {
553 if ((line[i] == '\n')
554 || (static_cast<unsigned char>(line[i]) >= static_cast<unsigned char>(0x20)
555 && line[i] != static_cast<char>(0x7F)))
556 tmp += line[i];
557 else
558 tmp += ' ';
559 }
560 return tmp;
561}
562
563void
564user_interface::ensure_clean_line()
565{
566 if (last_write_was_a_tick)
567 {
568 write_ticks();
569 t_writer->clear_line();
570 }
571 last_write_was_a_tick = false;
572}
573
574void
575user_interface::redirect_log_to(system_path const & filename)
576{
577 static ofstream filestr;
578 if (filestr.is_open())
579 filestr.close();
580 filestr.open(filename.as_external().c_str(), ofstream::out | ofstream::app);
581 E(filestr.is_open(), F("failed to open log file '%s'") % filename);
582 clog.rdbuf(filestr.rdbuf());
583}
584
585void
586user_interface::inform(string const & line)
587{
588 string prefixedLine;
589 prefix_lines_with(output_prefix(), line, prefixedLine);
590 ensure_clean_line();
591 clog << sanitize(prefixedLine) << endl; // flushes
592}
593
594unsigned int
595guess_terminal_width()
596{
597 unsigned int w = terminal_width();
598 if (!w)
599 w = constants::default_terminal_width;
600 return w;
601}
602
603// Local Variables:
604// mode: C++
605// fill-column: 76
606// c-file-style: "gnu"
607// indent-tabs-mode: nil
608// End:
609// 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