diff --git a/colout/colout.py b/colout/colout.py index 8ac771b..77b4f2d 100755 --- a/colout/colout.py +++ b/colout/colout.py @@ -258,6 +258,166 @@ def load_resources( themes_dir, palettes_dir ): # Library ############################################################################### +def mode( color ): + if color in colors: + return 8 + elif color in colormaps.keys(): + if color[0].islower(): + return 8 + elif color[0].isupper(): + return 256 + elif color.lower() in ("scale","hash","random") or color.lower() in lexers: + if color[0].islower(): + return 8 + elif color[0].isupper(): + return 256 + elif color[0] == "#": + return 256 + elif color.isdigit() and (ansi_min < int(color) and int(color) < ansi_max) : + return 256 + else: + raise UnknownColor(color) + + +def next_in_map( color ): + # loop over indices in colormap + return (colormap_idx+1) % len(colormaps[color]) + + +def color_random( color ): + m = mode(color) + if m == 8: + color_code = random.choice(list(colors.values())) + color_code = str(30 + color_code) + + elif m == 256: + color_nb = random.randint(0, 255) + color_code = str(color_nb) + + return color_code + + +def color_in_colormaps( color ): + m = mode(color) + if m == 8: + c = colormaps[color][colormap_idx] + if c.isdigit(): + color_code = str(30 + c) + else: + color_code = str(30 + colors[c]) + + else: + color_nb = colormaps[color][colormap_idx] + color_code = str( color_nb ) + + colormap_idx = next_in_map(color) + + return color_code + + +def color_scale( color, text ): + # filter out everything that does not seem to be necessary to interpret the string as a number + # this permits to transform "[ 95%]" to "95" before number conversion, + # and thus allows to color a group larger than the matched number + chars_in_numbers = "-+.,e/*" + allowed = string.digits + chars_in_numbers + nb = "".join([i for i in filter(allowed.__contains__, text)]) + + # interpret as decimal + # First, try with the babel module, if available + # if not, use python itself, + # if thoses fails, try to `eval` the string + # (this allow strings like "1/2+0.9*2") + try: + # babel is a specialized module + import babel.numbers as bn + try: + f = float(bn.parse_decimal(nb)) + except NumberFormatError: + f = eval(nb) # Note: in python2, `eval(2/3)` would produce `0`, in python3 `0.666` + except ImportError: + try: + f = float(nb) + except ValueError: + f = eval(nb) + + # if out of scale, do not color + if f < scale[0] or f > scale[1]: + return text + + # normalize and scale over the nb of colors in cmap + i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(colormap)-1) ) ) + + m = mode(color) + color = colormap[i] + + if m == 8: + color_code = str(30 + colors[color]) + else: + color_code = str(color) + + return color_code + + +def color_hash( color, text ): + hasher = hashlib.md5() + hasher.update(text.encode('utf-8')) + hash = hasher.hexdigest() + + f = float(functools.reduce(lambda x, y: x+ord(y), hash, 0) % 101) + + # normalize and scale over the nb of colors in cmap + i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(colormap)-1) ) ) + + m = mode(color) + color = colormap[i] + if m == 8: + color_code = str(30 + colors[color]) + else: + color_code = str(color) + + return color_code + + +def color_map(): + # current color + color = colormap[colormap_idx] + + m = mode(color) + if m == 8: + color_code = str(30 + colors[color]) + + else: + color_nb = int(color) + assert( ansi_min <= color_nb <= ansi_max ) + color_code = str(color_nb) + + colormap_idx = next_in_map(color) + + return color_code + + +def color_lexer( color, style, text ): + lexer = get_lexer_by_name(color.lower()) + # Python => 256 colors, python => 8 colors + m = mode(color) + if m == 256: + try: + formatter = Terminal256Formatter(style=style) + except: # style not found + formatter = Terminal256Formatter() + else: + if style not in ("light","dark"): + style = "dark" # dark color scheme by default + formatter = TerminalFormatter(bg=style) + # We should return all but the last character, + # because Pygments adds a newline char. + if not debug: + return highlight(text, lexer, formatter)[:-1] + else: + return "<"+color+">"+ highlight(text, lexer, formatter)[:-1] + "" + + def colorin(text, color="red", style="normal"): """ Return the given text, surrounded by the given color ASCII markers. @@ -293,6 +453,8 @@ def colorin(text, color="red", style="normal"): style_code = str(styles[style]) color = color.strip() + m = mode(color) + if color == "none": # if no color, style cannot be applied if not debug: @@ -300,180 +462,55 @@ def colorin(text, color="red", style="normal"): else: return ""+text+"" - - elif color == "random": - mode = 8 - color_code = random.choice(list(colors.values())) - color_code = str(30 + color_code) - - elif color == "Random": - mode = 256 - color_nb = random.randint(0, 255) - color_code = str(color_nb) - + elif color.lower() == "random": + color_code = color_random( color ) elif color in colormaps.keys(): - if color[0].islower(): # lower case first letter - mode = 8 - c = colormaps[color][colormap_idx] - if c.isdigit(): - color_code = str(30 + c) - else: - color_code = str(30 + colors[c]) - - else: # upper case - mode = 256 - color_nb = colormaps[color][colormap_idx] - color_code = str( color_nb ) - - if colormap_idx < len(colormaps[color])-1: - colormap_idx += 1 - else: - colormap_idx = 0 - + color_code = color_in_colormaps( color ) elif color.lower() == "scale": # "scale" or "Scale" - - # filter out everything that does not seem to be necessary to interpret the string as a number - # this permits to transform "[ 95%]" to "95" before number conversion, - # and thus allows to color a group larger than the matched number - chars_in_numbers = "-+.,e/*" - allowed = string.digits + chars_in_numbers - nb = "".join([i for i in filter(allowed.__contains__, text)]) - - # interpret as decimal - # First, try with the babel module, if available - # if not, use python itself, - # if thoses fails, try to `eval` the string - # (this allow strings like "1/2+0.9*2") - try: - # babel is a specialized module - import babel.numbers as bn - try: - f = float(bn.parse_decimal(nb)) - except NumberFormatError: - f = eval(nb) # Note: in python2, `eval(2/3)` would produce `0`, in python3 `0.666` - except ImportError: - try: - f = float(nb) - except ValueError: - f = eval(nb) - - # if out of scale, do not color - if f < scale[0] or f > scale[1]: - return text - - if color[0].islower(): - mode = 8 - # Use the default colormap in lower case = 8-colors mode - cmap = colormap - - # normalize and scale over the nb of colors in cmap - i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) ) - - color = cmap[i] - color_code = str(30 + colors[color]) - - else: - mode = 256 - cmap = colormap - i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) ) - color = cmap[i] - color_code = str(color) - + color_code = color_scale( color, text ) # "hash" or "Hash"; useful to randomly but consistently color strings elif color.lower() == "hash": - hasher = hashlib.md5() - hasher.update(text.encode('utf-8')) - hash = hasher.hexdigest() - - f = float(functools.reduce(lambda x, y: x+ord(y), hash, 0) % 101) - - if color[0].islower(): - mode = 8 - cmap = colormap - - # normalize and scale over the nb of colors in cmap - i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) ) - - color = cmap[i] - color_code = str(30 + colors[color]) - - else: - mode = 256 - cmap = colormap - i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) ) - color = cmap[i] - color_code = str(color) - + color_code = color_hash( color, text ) # Really useful only when using colout as a library # thus you can change the "colormap" variable to your favorite one before calling colorin elif color == "colormap": - color = colormap[colormap_idx] - if color in colors: - mode = 8 - color_code = str(30 + colors[color]) - else: - mode = 256 - color_nb = int(color) - assert(0 <= color_nb <= 255) - color_code = str(color_nb) - - if colormap_idx < len(colormap)-1: - colormap_idx += 1 - else: - colormap_idx = 0 + color_code = color_map() # 8 colors modes elif color in colors: - mode = 8 color_code = str(30 + colors[color]) # hexadecimal color elif color[0] == "#": - mode = 256 color_nb = rgb_to_ansi(*hex_to_rgb(color)) assert(0 <= color_nb <= 255) color_code = str(color_nb) # 256 colors mode elif color.isdigit(): - mode = 256 color_nb = int(color) assert(0 <= color_nb <= 255) color_code = str(color_nb) # programming language elif color.lower() in lexers: - lexer = get_lexer_by_name(color.lower()) - # Python => 256 colors, python => 8 colors - ask_256 = color[0].isupper() - if ask_256: - try: - formatter = Terminal256Formatter(style=style) - except: # style not found - formatter = Terminal256Formatter() - else: - if style not in ("light","dark"): - style = "dark" # dark color scheme by default - formatter = TerminalFormatter(bg=style) - # We should return all but the last character, - # because Pygments adds a newline char. - if not debug: - return highlight(text, lexer, formatter)[:-1] - else: - return "<"+color+">"+ highlight(text, lexer, formatter)[:-1] + "" + # bypass color encoding and return text colored by the lexer + return color_lexer(color,style,text) # unrecognized else: raise UnknownColor(color) if not debug: - return start + style_code + endmarks[mode] + color_code + "m" + text + stop + return start + style_code + endmarks[m] + color_code + "m" + text + stop else: - return start + style_code + endmarks[mode] + color_code + "m<" + str(color) + ">" + text + "" + stop + return start + style_code + endmarks[m] + color_code + "m" \ + + "" \ + + text + "" + stop def colorout(text, match, prev_end, color="red", style="normal", group=0):