def extract_end(): """ Display a summary of warnings and errors if any. """ has_warnings = False has_errors = False summary = [] for xev in extract_results: has_errors = has_errors or bool(xev.errors) has_warnings = has_warnings or bool(xev.warnings) source = as_posixpath(xev.source) source = utils.get_relative_path(original_input, abs_input, source) for e in xev.errors: summary.append( style('ERROR extracting: %(source)s: %(e)r' % locals(), fg='red', reset=False)) for warn in xev.warnings: summary.append( style('WARNING extracting: %(source)s: %(warn)r' % locals(), fg='yellow', reset=False)) summary_color = 'green' if has_warnings: summary_color = 'yellow' if has_errors: summary_color = 'red' summary.append(style('Extracting done.', fg=summary_color, reset=True)) return '\n'.join(summary)
def extract_end(): """ Display a summary of warnings and errors if any. """ has_warnings = False has_errors = False summary = [] for xev in extract_results: has_errors = has_errors or bool(xev.errors) has_warnings = has_warnings or bool(xev.warnings) source = as_posixpath(xev.source) source = utils.get_relative_path(original_input, abs_input, source) for e in xev.errors: summary.append(style('ERROR extracting: %(source)s: %(e)r' % locals(), fg='red', reset=False)) for warn in xev.warnings: summary.append(style('WARNING extracting: %(source)s: %(warn)r' % locals(), fg='yellow', reset=False)) summary_color = 'green' if has_warnings: summary_color = 'yellow' if has_errors: summary_color = 'red' summary.append(style('Extracting done.', fg=summary_color, reset=True)) return '\n'.join(summary)
def scan_event(item): """Progress event displayed each time a file is scanned""" if quiet: return '' if item: _scan_success, _scanned_path = item _progress_line = verbose and _scanned_path or fixed_width_file_name(_scanned_path) save_logs('Scanned: '+_progress_line,output_file) return style('Scanned: ') + style(_progress_line, fg=_scan_success and 'green' or 'red')
def scan_event(item): """Progress event displayed each time a file is scanned""" if quiet or not item or not display_fn: return '' _scan_success, _scanned_path = item _scanned_path = unicode(toascii(_scanned_path)) if verbose: _progress_line = _scanned_path else: _progress_line = fixed_width_file_name(_scanned_path, max_file_name_len) return style('Scanned: ') + style(_progress_line, fg=_scan_success and 'green' or 'red')
def dl_style_word(self, word): if len(word) == 0: return word elif word[:1] == '-': return style(word, fg='white', bold=True) elif self._kngsection == 'Options': # for the options definiton list, we make non-hyphenated # words yellow; otherwise, we stick to white return style(word, fg='yellow', bold=True) else: return style(word, fg='white', bold=True)
def write_heading(self, heading): """ Writes a heading into the buffer, applying some styling if the heading matches the current section name. """ if self._kngsection is not None and heading == self._kngsection: if heading == 'Commands': heading = 'Subcommand' self.write('%*s%s%s\n' % (self.current_indent, '', style(heading, fg='cyan', bold=True), style(':', fg='white', bold=True))) else: super(KNGHelpFormatter, self).write_heading(heading)
def scan_event(item): """Progress event displayed each time a file is scanned""" if quiet or not item or not display_fn: return '' _scan_success, _scanned_path = item if verbose: _progress_line = _scanned_path else: _progress_line = fixed_width_file_name( _scanned_path, max_file_name_len) return style('Scanned: ') + style( _progress_line, fg=_scan_success and 'green' or 'red')
def echo_next_step(desc: str, cmd: str) -> None: """ Echoes a 'next step' to perform, with styling. E.g. ◦ To archive results, run $ bento archive :param desc: The step description :param cmd: The command that the user should run """ echo_styles("◦ ", style(f"{desc}, run $ ", dim=True), cmd, style(".", dim=True))
def init_pipeline(program, config, questions): """Initializes a config object by interactively asking questions.""" if config.user_data: # Some existing user settings were found, warn about overwriting them message = "{program} {note}\tThe existing {file} will be updated" segments = dict(program=program, note=style('existing', fg='yellow'), file=style(config.config_path.basename(), fg='white')) echo(message.format(**segments)) # Launch questionnaire user_defaults = questionnaire(questions) # Set the selected user defaults for dot_key, value in user_defaults.items(): config.set(dot_key, value, scope=config.user_data)
def wrap_link(text: str, extra: int, *links: Tuple[str, str], **kwargs: Any) -> str: """ Wraps text. Text may include one or more links. :param text: Unlinked text :param links: Tuples of (anchor text, target) :param extra: Any extra width to apply :param kwargs: Styling rules passed to text """ wrapped = wrap(text, extra) def find_loc(anchor: str) -> Tuple[int, str]: """ Finds the position of the anchor string in the wrapped text Note that the anchor string itself may be wrapped, so we return both the position, and the value of the (possibly wrapped) anchor string. """ pos = wrapped.find(anchor) if pos < 0: # Text was likely wrapped anchor_it = [ f"{anchor[:ix].rstrip()}\n{anchor[ix:]}" for ix in range(len(anchor)) ] pos_it = ((wrapped.find(a), a) for a in anchor_it) pos, anchor = next(((p, a) for p, a in pos_it if p > 0), (-1, anchor)) if pos < 0: raise ValueError(f"'{anchor}' does not appear in '{text}'") return pos, anchor with_locs = sorted([(find_loc(anchor), href) for anchor, href in links], key=(lambda t: t[0][0])) out = "" current = 0 for loc_anchor, href in with_locs: loc, anchor = loc_anchor out += style(wrapped[current:loc], **kwargs) out += render_link(anchor, href, print_alternative=False, pipe=sys.stderr) current = loc + len(anchor) out += style(wrapped[current:], **kwargs) return out
def render_link( text: str, href: Optional[str], print_alternative: bool = True, width: Optional[int] = None, pipe: TextIO = sys.stdout, ) -> str: """ Prints a clickable hyperlink output if in a tty; otherwise just prints a text link :param text: The link anchor text :param href: The href, if exists :param print_alternative: If true, only emits link if OSC8 links are supported, otherwise prints href after text :param width: Minimum link width :param pipe: The text IO via which this link will be emitted :return: The rendered link """ is_rendered = False if href: # Don't render if href is None or empty if pipe.isatty() and DO_PRINT_LINKS: text = f"{OSC_8}{href}{BEL}{text}{OSC_8}{BEL}" is_rendered = True if width: width += LINK_WIDTH + len(href) elif print_alternative: text = f"{text} {href}" if width: text = text.ljust(width) # Coloring has to occur after justification if is_rendered: text = style(text, fg=Colors.LINK) return text
def echo_progress(text: str, extra: int = 0, skip: bool = False) -> Callable[[], None]: """ Prints a binary in-progress / done bar Usage example: mark_done = echo_progress("Installing foo") install_foo() mark_done() :param extra: Number of unprinted characters in text (each ANSI code point is 4 characters) :param skip: If true, "Skipped" is printed instead, and callback is a no-op """ width = PRINT_WIDTH - 3 - SETUP_WIDTH + ANSI_WIDTH + extra logging.info(text) leader = style("".ljust(width - len(text), LEADER_CHAR), dim=True) if skip: secho(f"{text}{leader} {style(SKIP_TEXT, dim=True)}", err=True, dim=True) return lambda: None else: secho(f"{text}{leader} {SETUP_TEXT}", nl=False, err=True, dim=True) return lambda: secho(f"{RESET_TEXT}{DONE_TEXT}", err=True, dim=True)
def scan_end(): """Progress event displayed at end of scan""" has_warnings = False has_errors = False summary = [] summary_color = 'green' summary_color = has_warnings and 'yellow' or summary_color summary_color = has_errors and 'red' or summary_color summary.append(style('Scanning done.', fg=summary_color, reset=True)) return '\n'.join(summary)
def ask(prompt, default=None, color='cyan'): """Ask a question, waits for user input. Replacement for "input". Updates the same line by replacing "default". .. code-block:: python >>> my_name = ask('Say my name: %s ', 'Heisenberg') # Wait for user input Say my name: (Heisenberg) Walter # Updates *the same* line with 'Walter' in green Say my name: Walter >>> print(my_name) Walter Inspired by 'bower_ init' which confirms user input by replacing the default option in line (ref_). Args: prompt (str): Question to print, '%s' will be replaced by default default (str, optional): Default option unless replaced by user color (str, optional): Some common color like 'red', 'green', 'yellow' Returns: str: User input or default .. _bower: http://bower.io/ .. _ref: http://stackoverflow.com/questions/12586601 """ # helper variables MOVE_CURSOR_UP = '\x1b[1A' ERASE_LINE = '\x1b[2K' # determine if a default was submitted if default: # prepare the default-part of the prompt default_string = "(%s)" % default else: # not relevant since ``promt`` shouldn't include a '%s' default_string = '' # pass question to user and wait for response # write default option in parentheses, use it as response if nothing # was submitted by user. response = input(build_prompt(prompt, default_string)) or default if isinstance(default, list) and isinstance(response, str): sep = ',' if ',' in response else None response = [int(item) for item in response.split(sep)] # print the updated confirmation line by replacing the previous echo(MOVE_CURSOR_UP + ERASE_LINE + build_prompt(prompt, style(str(response) or '', fg=color))) return response
def path_progress_message(item, verbose=False, prefix='Scanned: '): """ Return a styled message suitable for progress display when processing a path for an `item` tuple of (location, rid, scan_errors, *other items) """ if not item: return '' location = item[0] errors = item[2] location = compat.unicode(toascii(location)) progress_line = location if not verbose: max_file_name_len = file_name_max_len() # do not display a file name in progress bar if there is no space available if max_file_name_len <= 10: return '' progress_line = fixed_width_file_name(location, max_file_name_len) color = 'red' if errors else 'green' return style(prefix) + style(progress_line, fg=color)
def log_in_terminal(message: str, *args, **kwargs) -> None: """This function logs in the terminal a message with a specific color Args: message (str): Message to log on the console foreground(str): Foreground color see click.style for options """ if args: message = message + "\n" + "\n".join(args) click.echo(style(message, **kwargs))
def link(self, ctx: Context, url: str, file: str = None, *, way: SyncWays = SyncWays.twoway): 'link a remote file to local.' try: link_id = self._api.add_link(url, file, way) self._logger.info('link id: {}'.format(style(link_id, fg='green'))) if click.confirm('sync now?', default=True, show_default=True): self._api.sync_one(link_id) except GLinkError as ge: ctx.fail(ge.message)
def push(self, ctx: Context, file: str, user: str = None, public: flag = False): 'push the file as a new gist.' if not os.path.isfile(file): self._logger.error(f'{file} is not a file.') try: link_id = self._api.push_new_gist(file, user=user, public=public) except GLinkError as ge: ctx.fail(ge.message) else: self._logger.info('link id: {}'.format(style(link_id, fg='green')))
def echo_box(text: str) -> None: """ Prints text bold, in a header box By default, the box is PRINT_WIDTH characters wide, unless the text is too long for the box, in which case the box is extended to fit. """ lines = text.split("\n") max_len = max(len(l) for l in lines) max_len = max(PRINT_WIDTH - 4, max_len) hrule = "".ljust(max_len + 2, "─") echo_newline() secho(f"╭{hrule}╮", err=True) for l in lines: p = style(f"{l:^{max_len}s}", bold=True) secho(f"│ {p} │", err=True) secho(f"╰{hrule}╯", err=True)
def build_prompt(prompt, replacement=''): """Craft a prompt to ask a question. Provides a few modifications that should make it as simple as possible to ask questions. .. code-block:: python >>> build_prompt('name', '(Heisenberg)') 'name: (Heisenberg)' >>> build_prompt('What is your name?', '(Heisenberg)') 'What is your name? (Heisenberg)' >>> build_prompt('I %sed to China', '[style(walk, fg='red')]') 'I [walk]ed to China' Args: prompt (str): Base prompt, ``%s`` will be substituted with 'replacement' replacement (str, optional): String to substitute ``%s`` with Returns: str: The full, modified prompt string """ # prefix the question-prefix prompt = ("[%s] " % style('?', fg='green')) + prompt # determine whether it seems user has tried to format the prompt if '%s' not in prompt: # automate some formatting for the sake of convenience if not prompt.endswith(' '): if not prompt.endswith('?') or prompt.endswith(':'): prompt += ':' # add the default option to the end of prompt unless specified elsewhere prompt += " %s" # make the default-substitution and ensure an empty space at the end return (prompt % replacement).rstrip() + ' '
def scan_start(): """Progress event displayed at start of scan""" return style('Scanning files...', fg='green')
def extract_start(): return style('Extracting archives...', fg='green')
def write_usage(self, prog, args='', prefix='Usage: '): prog = style(prog, fg='white', bold=True) super(KNGHelpFormatter, self).write_usage(prog, args=args, prefix=prefix)
from contextlib import contextmanager from click.core import Context, Command, Group from click.termui import style, get_terminal_size from click.formatting import HelpFormatter from click.decorators import command, option, version_option import click from .kngclicktextwrapper import KNGClickTextWrapper from .kngtextwrapper import kngterm_len, kngexpandtabs from .version import version from .output import set_verbose_level, trace KNG_OPTIONS_METAVAR = ''.join(( style('[', fg='blue'), style('OPTIONS', fg='cyan', bold=True), style(']', fg='blue'))) SUBCOMMAND_METAVAR = ''.join(( style('SUBCOMMAND', fg='cyan', bold=True), ' ', style('[', fg='blue'), style('ARGS', fg='cyan', bold=True), style(']...', fg='blue'))) SUBCOMMANDS_METAVAR = ''.join(( style('SUBCOMMAND1', fg='cyan', bold=True), ' ', style('[', fg='blue'), style('ARGS', fg='cyan', bold=True),