summaryrefslogblamecommitdiff
path: root/attoconf/help.py
blob: a2379a6cdc988b5665432fe7983c651c3bb65729 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                        
 




























































































                                                                           









                                                                             













                                                                    
                                     





                                                                        
                                                
 
                                             





                                                                      
                                                        











                                                                 
#   Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
#
#   This file is part of attoconf.
#
#   attoconf is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   attoconf is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with attoconf.  If not, see <http://www.gnu.org/licenses/>.



def put_line_in_width(file, line, width, indent):
    ''' Print a line with wrapping.
    '''
    line = line.rstrip(' ')
    if len(line) <= width or ' ' not in line.lstrip(' '):
        # this is not just an optimization
        file.writelines([line, '\n'])
        return
    line += ' '
    indents = indent * ' '

    initial_spaces = len(line) - len(line.lstrip(' '))
    while line.lstrip():
        space = line.rfind(' ', initial_spaces, width + 1)
        if space == -1:
            space = line.find(' ', initial_spaces)
        if space == -1:
            space = len(line)
        file.writelines([line[:space], '\n'])
        line = indents + line[space+1:].lstrip(' ')
        initial_spaces = indent


class HelpSection(object):
    ''' A help section contains some header lines and some related options.
    '''
    __slots__ = (
            'headers',
            'options',
    )
    def __init__(self):
        ''' Create an empty help section.
        '''
        self.headers = []
        self.options = []

    def add_text(self, text, hidden):
        ''' Add a header line.
        '''
        self.headers.append((hidden, text))

    def add_option(self, name, text, hidden):
        ''' Add an option with its description.
        '''
        self.options.append((hidden, name, text))

    def print(self, file, hidden, width):
        ''' Format (some of) the help text prettily.
        '''
        if self.options:
            for oh, name, ht in self.options:
                if oh <= hidden:
                    break
            else:
                return False

        # options longer than this will be split into multiple lines
        split_width = width / 4 - 2
        # longest option
        longest_opt = 0
        for oh, name, ht in self.options:
            if oh > hidden:
                continue
            l = len(name) + 2
            if l > split_width:
                continue
            if l > longest_opt:
                longest_opt = l
        if longest_opt == 0:
            longest_opt = int(split_width)

        for oh, ht in self.headers:
            if oh > hidden:
                continue
            put_line_in_width(file, ht, width, 0)

        for oh, name, ht in self.options:
            if oh > hidden:
                continue
            # no, it's not really that simple
            if len(name) > longest_opt:
                file.writelines(['  ', name, '\n'])
                name = ''
            line = '  %-*s%s' % (longest_opt, name, ht)
            put_line_in_width(file, line, width, longest_opt + 2)

        return True


def detect_terminal_width(fd, DEFAULT_WIDTH=float('inf')):
    ''' Detect the width of a terminal.
    '''
    import os
    import shutil

    if isinstance(fd, int):
        try:
            return os.get_terminal_size(fd)
        except OSError(...):
            return DEFAULT_WIDTH
    else:
        return shutil.get_terminal_size(fallback=(DEFAULT_WIDTH, 24)).columns


class Help(object):
    ''' Help collects the set of flavor text and option descriptions
        related to arguments.
    '''
    __slots__ = (
            'sections',
    )
    def __init__(self):
        ''' Help does not take any arguments during construction.
        '''
        self.sections = []

    def add_text(self, help, hidden):
        ''' Add a header line.

            This creates a new section if the last line wasn't a header.
        '''
        if not self.sections or self.sections[-1].options:
            self.sections.append(HelpSection())
        self.sections[-1].add_text(help, hidden)

    def add_option(self, name, help, hidden):
        ''' Add an option with its description to the current section.

            This creates a new section only if it is first.
        '''
        if not self.sections:
            self.sections.append(HelpSection())
        self.sections[-1].add_option(name, help, hidden)

    def print(self, file, hidden, width=0):
        ''' Print all the help at the given level of hidden-ness.
        '''
        if width == 0:
            width = detect_terminal_width(file)
        if width < 20:
            width = 80

        for s in self.sections:
            if s.print(file, hidden, width):
                file.write('\n')