From 1b52142591bd5ee6504d9fd488712181041ecc05 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 19 Jan 2025 22:19:44 -0500 Subject: made a huge overhaul to the input validation system, it is not fully implemented across all formats yet though. file input and output support is partially implemented too. just saving for progress --- color-converter.py | 355 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 213 insertions(+), 142 deletions(-) (limited to 'color-converter.py') diff --git a/color-converter.py b/color-converter.py index 05c6dd3..f2b415d 100644 --- a/color-converter.py +++ b/color-converter.py @@ -8,68 +8,137 @@ TYPES = ['hex', 'rgb', 'cmy', 'cmyk', 'hsl', 'hsv'] HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f'] +OUTPUT = 'stdout' +APPEND = False + + def main(): # Set up arguments parser = argparse.ArgumentParser() - parser.add_argument('-hex', action='store_true', help='convert from hex (accepts hexadecimal input [ffffff] or string ["#ffffff"] (case insensitive, "#" is optional in string)') - parser.add_argument('-rgb', action='store_true', help='convert from RGB (accepts integer input [R G B], or string [\"rgb(R, G, B)\"] (case/whitespace insensitive)') - parser.add_argument('-cmy', action='store_true', help='convert from CMY (accepts integer input [C M Y], or string [\"cmy(C, M, Y)\"] (case/whitespace insensitive)') - parser.add_argument('-cmyk', action='store_true', help='convert from CMYK (accepts integer input [C M Y K], or string [\"cmyk(C, M, Y, K)\"] (case/whitespace insensitive)') + + parser.add_argument('-hex', action='store_true', help='output Hex') + parser.add_argument('-rgb', action='store_true', help='output RGB') + parser.add_argument('-cmy', action='store_true', help='output CMY') + parser.add_argument('-cmyk', action='store_true', help='output CMYK') + parser.add_argument('-hsl', action='store_true', help='output HSL') + parser.add_argument('-hsv', action='store_true', help='output HSV') + + + parser.add_argument('--input', '-i', help='name of the input file containing color codes to process') + parser.add_argument('--output', '-o', help='the name of the file to store output in (will create file if doesn\'t exist, will OVERWRITE existing file\'s contents)') + parser.add_argument('--append', '-a', help='if an output file is specified, the conversions will be appended to the file') + + ''' + parser.add_argument('-isHex', action='store_true', help='convert from hex (accepts hexadecimal input [ffffff] or string ["#ffffff"] (case insensitive, "#" is optional in string)') + parser.add_argument('-isRgb', action='store_true', help='convert from RGB (accepts integer input [R G B], or string [\"rgb(R, G, B)\"] (case/whitespace insensitive)') + parser.add_argument('-isCmy', action='store_true', help='convert from CMY (accepts integer input [C M Y], or string [\"cmy(C, M, Y)\"] (case/whitespace insensitive)') + parser.add_argument('-isCmyk', action='store_true', help='convert from CMYK (accepts integer input [C M Y K], or string [\"cmyk(C, M, Y, K)\"] (case/whitespace insensitive)') + parser.add_argument('-isHsl', action='store_true', help='convert from HSL (accepts integer input [H S L], or string [\"hsl(H, S, L)\"] (case/whitespace insensitive)') + parser.add_argument('-isHsv', action='store_true', help='convert from HSV (accepts integer input [H S V], or string [\"hsv(H, S, V)\"] (case/whitespace insensitive)') + ''' + + parser.add_argument('color', nargs='*', help='accepts a color in Hex, RGB, CMY, CMYK, or HSL and performs format conversions (does not support CMYK profiles, conversions are uncalibrated)') - parser.add_argument('-hsl', action='store_true', help='convert from HSL (accepts integer input [H S L], or string [\"hsl(H, S, L)\"] (case/whitespace insensitive)') - parser.add_argument('-hsv', action='store_true', help='convert from HSV (accepts integer input [H S V], or string [\"hsv(H, S, V)\"] (case/whitespace insensitive)') args = parser.parse_args() # debug print print(args) + print('#########') + print(args.color) - # INPUT SYNTAX VALIDATION - if not validateArguments(args) : - return; - - color = args.color - - ## HERE IS WHERE WE WOULD DETERMINE IF THIS IS MULTILINE INPUT - - # Try to autodetect format. if detected, will kick off the rest of the process - if detectColorFormat(color) : - return; - - # HANDLE HEX INPUT - if args.hex : - handleHex(color) - - # HANDLE RGB INPUT - if args.rgb : - handleRGB(color) - - if args.cmy : - handleCMY(color) - - # HANDLE CMYK INPUT - if args.cmyk : - handleCMYK(color) - - # HANDLE HSL INPUT - if args.hsl : - handleHSVorHSL(color, 'hsl') + ''' ARGS PROCESSING, determine which conversions to perform ''' + outputFormats = [] + flagsActive = 0 + for flag in vars(args) : + if flag in TYPES : + flagsActive += 1 + outputFormats.append(flag) + # if no conversion flags specified, perform every format conversion + if flagsActive == 0 : + for formats in TYPES : + outputFormats.append(format) + + # if file output is specified, set global vars for reference later + if args.output : + global OUTPUT + OUTPUT = args.output + if args.append : + global APPEND + APPEND = True + + ''' GET COLORS ''' + colorCodes = [] + # if provided a file of values + if args.input : + with open(args.input, 'r', encoding='utf-8') as file : + for line in file : + colorCodes.append(file.strip()) + else : + colors = args.color - # HANDLE HSV INPUT - if args.hsv : - handleHSVorHSL(color, 'hsv') + ''' PROCESS COLORS ''' + for color in colors : + # Try to automatically handle value + if detectColorFormat(color, outputFormats) : + continue + # if we can't auto-detect the format, check args for guidance + elif args.isHex : + handleHex(color, outputFormats) + elif args.isRgb : + handleRGB(color, outputFormats) + elif args.isCmy : + handleCMY(color, outputFormats) + elif args.isCmyk : + handleCMYK(color, outputFormats) + elif args.isHsl : + handleHSVorHSL(color, 'hsl', outputFormats) + elif args.isHsv : + handleHSVorHSL(color, 'hsv', outputFormats) ## # COLOR HANDLERS ## +# takes in a map of color values and either prints their values to STDOUT or into a specified file +def printConversions(convertedValues) : + # format converted values into better output strings + output = [] + for colorFormat in TYPES : + if convertedValues.get(colorFormat, None) is None : + continue + elif colorFormat == 'hex' : + hexCode = '#' + convertedValues['hex'] + output.append(hexCode) + else : + # format strings for xyz(a,b,c) type formats + colorCode = colorFormat + '(' + numValues = len(convertedValues[colorFormat]) + for valueIndex in range(numValues) : + value = convertedValues[colorFormat][valueIndex] + if type(value) == float : + value = f"{value:.2f}" + if valueIndex == numValues - 1 : + colorCode += str(value) + ')' + else : + colorCode += str(value) + ', ' + output.append(colorCode) + + # determine how to deliver output + if OUTPUT != 'stdout' : + with open(OUTPUT, 'w', encoding='utf-8') as file : + for color in output : + file.write(color + '\n') + else : + for color in output : + print(color) # Takes in valid RGB code and converts it to the other formats -def handleHex(color) : +def handleHex(color, outputFormats = TYPES) : hexCode = validateHex(color) if hexCode is None : return @@ -77,20 +146,24 @@ def handleHex(color) : print('convert hex: ', hexCode, '\n') rgbValues = HEXtoRGB(hexCode) - cmyValues = RGBtoCMY(rgbValues) - cmykValues = RGBtoCMYK(rgbValues) - hslValues = RGBtoHSVorHSL(rgbValues, 'hsl') - hsvValues = RGBtoHSVorHSL(rgbValues, 'hsv') - print('RGB: ', rgbValues) - print('CMY: ', cmyValues) - print('CMYK: ', cmykValues) - print('HSL: ', hslValues) - print('HSV: ', hsvValues) + outputs = {} + if 'rgb' in outputFormats : + outputs['rgb'] = rgbValues + if 'cmy' in outputFormats : + outputs['cmy'] = RGBtoCMY(rgbValues) + if 'cmyk' in outputFormats : + outputs['cmyk'] = RGBtoCMYK(rgbValues) + if 'hsl' in outputFormats : + outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl') + if 'hsv' in outputFormats : + outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv') + + printConversions(outputs) # Takes in valid RGB code and converts it to the other formats -def handleRGB(color) : +def handleRGB(color, outputFormats = TYPES) : rgbValues = validateRGB(color) if rgbValues is None : return @@ -109,7 +182,7 @@ def handleRGB(color) : print('HSL: ', hslValues) print('HSV: ', hsvValues) -def handleCMY(color) : +def handleCMY(color, outputFormats = TYPES) : cmyValues = validateCMYorCMYK(color, False) if cmyValues is None : return @@ -128,7 +201,7 @@ def handleCMY(color) : print('HSL: ', hslValues) print('HSV: ', hsvValues) -def handleCMYK(color) : +def handleCMYK(color, outputFormats = TYPES) : cmykValues = validateCMYorCMYK(color, True) if cmykValues is None : return @@ -148,8 +221,8 @@ def handleCMYK(color) : print('HSV: ', hsvValues) # isHSL determines whether the function should handle HSL or HSV -def handleHSVorHSL(color, handle) : - validated = validateHSLorHSV(color, handle) +def handleHSVorHSL(color, handle, outputFormats = TYPES) : + validated = validateHSLorHSV(color) if validated is None : return @@ -350,142 +423,139 @@ def HSLorHSVToRGB(values, convertFrom) : # INPUT VALIDATION ## -def detectColorFormat(color) : - if color.strip()[0] == '#' : - handleHex(color) +def detectColorFormat(color, outputFormats) : + if '#' in color : + handleHex(color, outputFormats) return True - elif color.strip().startswith('rgb') : - handleRGB(color) + elif 'rgb' in color : + handleRGB(color, outputFormats) return True - elif color.strip.startswith('cmyk') : - handleCMYK(color) + elif 'cmyk' in color : + handleCMYK(color, outputFormats) return True - elif color.strip.startswith('cmy') : - handleCMY(color) + elif 'cmy' in color : + handleCMY(color, outputFormats) return True - elif color.strip.startswith('hsl') : - handleHSVorHSL(color, 'hsl') + elif color.strip().startswith('hsl') : + handleHSVorHSL(color, 'hsl', outputFormats) return True - elif color.strip.startswith('hsv') : - handleHSVorHSL(color, 'hsv') + elif color.strip().startswith('hsv') : + handleHSVorHSL(color, 'hsv', outputFormats) return True else : return False -# Takes in a string and an integer. Starts at beginning of string and parses as many int/float values as defined by numValues. Returns a list of numbers in string form, will need to be cast upon return. -def extractValues(color, numValues) : + +# Extracts numerical values from a string. +# Parameters: +# color: string containing value(s) +# numValues: determines how many values to extract (e.g. 3 for RGB, 4 for CMYK) +# isHex: determines if we're looking for hex rather than decimal + +# returns: list of extracted color/number values in string form +def extractValues(color, numValues, isHex = False) : + print(color) i = 0 tempValue = '' extractedValues = [] - while i < len(color) : - if color[i].isnumeric() or color[i] == '.' : - tempValues += color[i] - elif len(tempValue) > 0 : + if isHex : + # search for hex values + while i < len(color) : + if (color[i].isnumeric()) or (color[i] in HEX_LETTERS) : + tempValue += color[i] + else : + tempValue = '' + if len(tempValue) == 6 : + extractedValues.append(tempValue) + tempValue = '' + if len(extractedValues) == numValues : + break + i = i + 1 + + if (len(extractedValues) != numValues) and (len(tempValue) == 6) : extractedValues.append(tempValue) - tempValue = '' - if len(extractedValues) == numValues : - break - if (len(extractedValues) != numValues) and (len(tempValue) > 0) : - extractedValues.append(tempValue) - - if len(extractedValues != numValues) : - print(f'Could not extract the desired number of values from input. Values requested: {numValues}, values extracted: {len(extractedValues)}, {extractedValues}') + + else : + # search for decimal values + while i < len(color) : + if color[i].isnumeric() or color[i] == '.' : + tempValue += color[i] + elif len(tempValue) > 0 : + extractedValues.append(tempValue) + tempValue = '' + if len(extractedValues) == numValues : + break + i = i + 1 + + if (len(extractedValues) != numValues) and (len(tempValue) > 0) : + extractedValues.append(tempValue) + + if len(extractedValues) != numValues : + print(f'Could not extract the correct number of values from input: {color}. numValues requested: {numValues}, isHex: {isHex}, Values successfully extracted: {len(extractedValues)}, {extractedValues}') return False return extractedValues -# Takes in a string. Returns True if valid Hex color code. +# Takes in a string. +# If string contains a valid Hex color code, it gets returned as a string. def validateHex(value) : - # cleanse hex code - value = value[0].lower().replace(' ', '').strip('#') - - if len(value) != 6 : + # attempt to extract hex code + hexcode = extractValues(value, 1, True)[0] + + if hexcode is None : print('ERROR: Improper format for hex code (see --help)') return - for i in range(len(value)): - if value[i-1].isnumeric() : - continue - elif HEX_LETTERS.count(value[i-1]) != 0 : - continue - else : - print('ERROR: Invalid character in hex code') - return + return hexcode - return value - -# Takes in a list of 3 strings. Returns same list as integers if valid RGB values. +# Takes in a string. Returns same list as integers if valid RGB values. def validateRGB(color) : - # cleanse any non-numerical stuff if entered as string - if len(color) == 1 : - #color = color[0].lower().strip('rgb(').strip(')').replace(' ', '').split(',') - color = extractValues(color, 3) + # extract 3 numbers from the provided values + rgbValues = extractValues(color, 3) - #if len(color) != 3 : - #print('ERROR: Improper number of values for RGB (should be 3)') - #return + if rgbValues is None: + return intValues = [] - for value in color : - if not value.strip().isnumeric() : - print('ERROR: Improper format for RGB value(s)') - return + for value in rgbValues : + # TODO see if i should smartround here instead of int cast value = int(value) - intValues.append(value) if (value < 0) or (value > 255) : print('ERROR: Each RBG value must be between 0-255') return + intValues.append(value) return intValues # Takes in a list of 3 or 4 strings. Returns same list as integers if valid CMYK values. def validateCMYorCMYK(color, include_K) : - # cleanse any non-numerical stuf if entered as string - if include_K and len(color) == 1 : - color = color[0].lower().strip('cmyk(').strip(')').replace('%', '').replace(' ', '').split(',') - elif not include_K and len(color) == 1 : - color = color[0].lower().strip('cmy(').strip(')').replace('%', '').replace(' ', '').split(',') - - if (include_K and len(color) != 4) : - print('ERROR: Improper number of values for CMYK (should be 4)') - return - elif (not include_K and len(color) != 3) : - print('ERROR: Improper number of values for CMY (should be 3)') + if include_K : + values = extractValues(color, 4) + else : + values = extractValues(color, 3) + if values is None : return floatValues = [] - for value in color : - if not value.replace('.', '').isnumeric() : - print('ERROR: Improper format for CMY(K) value(s). All values must be numeric and between 0.0-100.0(%)!') - return + for value in values : value = float(value) - floatValues.append(value) if (value < 0) or (value > 100) : print('ERROR: Each CMY(K) value must be between 0,0-100.0(%)') return + floatValues.append(value) return floatValues # Takes in a list of 3 strings. Returns same list as 1 integer and 2 floats -def validateHSLorHSV(color, handle) : - # cleanse color code if entered as string - if len(color) == 1 : - if handle == 'hsl' : - color = color[0].lower().strip('hsl(').strip(')').replace('%', '').replace(' ', '').split(',') - else : # is HSV - color = color[0].lower().strip('hsv(').strip(')').replace('%', '').replace(' ', '').split(',') - - if len(color) != 3 : - print('ERROR: Improper number of values for HSL/HSV (should be 3)') +def validateHSLorHSV(color) : + color = extractValues(color, 3) + if color is None : return for i in range(3) : - if not color[i].replace('.', '').isnumeric() : - print('ERROR: HSL/HSV values must be numeric') - return if i == 0 : color[i] = smartRound(color[i]) else : @@ -505,17 +575,18 @@ def validateHSLorHSV(color, handle) : # Takes in the program's arguments generated by argparse. Returns True if valid arguments def validateArguments(args) : - # ck that only one input flag is being used - flagsActive = 0 + # check that only one input flag is being used + '''flagsActive = 0 for flag in range(len(vars(args))-1) : # this uses our TYPES list to key into the args dictionary and determine how many flags are True if vars(args)[TYPES[flag]] : flagsActive += 1 if flagsActive > 1 : print('ERROR: Currently this tool only supports one input type at a time. Too many flags!') - return False + return False''' - if (flagsActive == 0) or (len(args.color) == 0) : +# if (flagsActive == 0) or (len(args.color) == 0) : + if (len(args.color) == 0) : print('ERROR: Must enter both an input flag and color code (did you forget to wrap color code in quotes?)\nFor more info, use the \'-h\' or \'--help\' flag.') return False -- cgit v1.3.1-4-g156e