OpenCode 实践课:4 分钟用 Python 写出一个命令行颜色格式互转工具

项目介绍

前端切图的时候,你是不是经常在 HEX、RGB、HSL 之间来回换算?打开浏览器控制台、点开 ColorZilla,或者搜个在线工具——其实完全没必要。

今天用 OpenCode 花 4 分钟写一个命令行颜色格式互转工具。输入任意一种颜色格式,立刻输出全部三种格式,外加终端里的 ANSI 色块预览。纯 Python 实现,代码不到 90 行。

技术栈:Python 3 + argparse + colorsys(标准库,零依赖)。

准备工作

  • Python 3.6+
  • 安装 OpenCode(安装命令见官网)
  • 终端支持 True Color(Windows Terminal、iTerm2、kitty 等均支持)

实践过程

步骤一:一句话起项目

打开终端,进入工作目录,对 OpenCode 说:

我的提示词:

> 帮我写一个 Python 命令行颜色格式互转工具,支持 HEX(如 #ff5733)、RGB(如 255 87 51)和 HSL(如 12 85 60)三种颜色格式。用 argparse 解析参数,如果是 HEX 输入就转成 RGB 和 HSL,如果是 RGB 输入就转成 HEX 和 HSL,以此类推。

OpenCode 做了什么:

OpenCode 自动创建了 color.py,引入了 argparse 和标准库的 colorsys 模块,用 add_mutually_exclusive_group 确保了用户一次只输入一种格式。核心转换逻辑不到 30 行就搞定了。

它甚至还写了输入校验——RGB 三个值如果超出 0-255 范围会报错。

关键要点:

  • colorsys.rgb_to_hls 是 Python 标准库自带的,不需要 pip 装任何东西。注意它的返回值顺序是 HLS 不是 HSL,S 和 L 刚好反了,这是个常见坑。
  • argparse 的互斥组处理 --hex--rgb--hsl 三个互斥参数是最干净的方案。

步骤二:补上短 HEX 格式

运行 python color.py --hex ff0 发现报错了,因为工具只支持 6 位 HEX。但 CSS 里 #RGB 是合法的简写形式(#ff0 等于 #ffff00)。

我的提示词:

> #RGB 这种 3 位短格式的 HEX 现在不能识别,帮我加上对短格式的支持。#f0f 应该等价于 #ff00ff。

OpenCode 的响应:

OpenCode 在 hex_to_rgb 函数里加了三行代码:检测输入长度为 3 时,把每个字符复制一份变成 6 位。比如 f0fff00ff,然后再走原来的 6 位解析逻辑。

改动很小但很关键——这就是用 OpenCode 的体验:你发现一个边界情况,一句话告诉它,它立刻修,不需要自己翻文档查正则。

关键要点:

  • 短 HEX 格式是前端工程师最常用的写法,这个功能让工具的使用门槛降了很多。
  • OpenCode 的补丁式修改非常精准,只改动了 hex_to_rgb 函数里的 3 行,没有动其他逻辑。

步骤三:支持 CSS 颜色名

跑了几次感觉不错,但每次都得查 teal 的 HEX 是多少,不如直接支持颜色名。

我的提示词:

> 帮我加上对 CSS 颜色名的支持,比如 --css orange 就能直接转换。内置十几个常用颜色就行:red、green、blue、yellow、cyan、magenta、white、black、gray、orange、purple、pink、lime、navy、teal、maroon、olive、silver。

OpenCode 做了什么:

OpenCode 在文件顶部加了一个 CSS_COLORS 字典,把颜色名映射到 HEX 值。然后在 argparse 里加了一个 --css 参数,choices 直接用字典的 keys。这样 argparse 会自动校验输入是否在列表里,不在就直接报错并提示可用值。

$ python color.py --css orange

  HEX: #ffa500
  RGB: rgb(255, 165, 0)
  HSL: hsl(39°, 100%, 50%)

       ████████

关键要点:

  • choices 参数让 argparse 自带了输入校验和帮助信息,不需要手写 if-else。
  • 这个功能本质是把 18 个字符串映射到它们对应的 HEX 值——逻辑极其简单,但极大提升了便利性。

步骤四:加 ANSI 颜色预览

现在转换结果已经很完整了,但还缺一个杀手级体验——在终端里直接看到颜色长什么样。

我的提示词:

> 输出结果里能不能加上一个 ANSI True Color 的色块预览?就是在终端里用彩色背景块展示转换后的颜色。

OpenCode 做了什么:

OpenCode 加了一个 ansi_preview 函数,用 ANSI 转义码 \033[48;2;R;G;Bm 设置 24 位背景色,后面跟 7 个空格作为色块,最后 \033[0m 重置。

然后在 print_result 里调用,在转换结果下方打印色块。

效果非常直观:

$ python color.py --css teal

  HEX: #008080
  RGB: rgb(0, 128, 128)
  HSL: hsl(180°, 100%, 25%)

       ████████       ← 这是青色!

关键要点:

  • \033[48;2;R;G;Bm 是现代终端都支持的 True Color ANSI 序列,用 24 位色值直接设置背景色,和 HTML 的 background-color 一样精确。
  • 从"能用"到"好用",这一步只加了 4 行代码,但用户体验提升了一个档次。

完整代码

#!/usr/bin/env python3
"""颜色格式互转工具 — 支持 HEX / RGB / HSL 互相转换"""

import argparse
import sys
import colorsys

CSS_COLORS = {
    "red": "#ff0000", "green": "#008000", "blue": "#0000ff",
    "yellow": "#ffff00", "cyan": "#00ffff", "magenta": "#ff00ff",
    "white": "#ffffff", "black": "#000000", "gray": "#808080",
    "orange": "#ffa500", "purple": "#800080", "pink": "#ffc0cb",
    "lime": "#00ff00", "navy": "#000080", "teal": "#008080",
    "maroon": "#800000", "olive": "#808000", "silver": "#c0c0c0",
}


def hex_to_rgb(h: str):
    h = h.lstrip("#")
    if len(h) == 3:
        h = "".join(c * 2 for c in h)
    if len(h) != 6:
        raise ValueError(f"无效的 HEX 值: #{h}")
    return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))


def rgb_to_hex(r: int, g: int, b: int):
    return f"#{r:02x}{g:02x}{b:02x}"


def rgb_to_hsl(r: int, g: int, b: int):
    h, l, s = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
    return round(h * 360), round(s * 100), round(l * 100)


def hsl_to_rgb(h: int, s: int, l: int):
    r, g, b = colorsys.hls_to_rgb(h / 360, l / 100, s / 100)
    return round(r * 255), round(g * 255), round(b * 255)


def ansi_preview(r: int, g: int, b: int):
    return f"\033[48;2;{r};{g};{b}m       \033[0m"


def print_result(r: int, g: int, b: int):
    h, s, l = rgb_to_hsl(r, g, b)
    hex_val = rgb_to_hex(r, g, b)
    preview = ansi_preview(r, g, b)

    print(f"\n  HEX: {hex_val}")
    print(f"  RGB: rgb({r}, {g}, {b})")
    print(f"  HSL: hsl({h}°, {s}%, {l}%)")
    print(f"\n  {preview}\n")


def main():
    parser = argparse.ArgumentParser(
        description="颜色格式互转工具 — 输入任意格式,输出全部三种格式",
    )
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--hex", help="HEX 颜色值(支持 #RGB 和 #RRGGBB)")
    group.add_argument(
        "--rgb", nargs=3, type=int, metavar=("R", "G", "B"), help="RGB 值"
    )
    group.add_argument(
        "--hsl", nargs=3, type=int, metavar=("H", "S", "L"), help="HSL 值"
    )
    group.add_argument(
        "--css", choices=list(CSS_COLORS.keys()), help="CSS 颜色名称"
    )

    args = parser.parse_args()

    if not any([args.hex, args.rgb, args.hsl, args.css]):
        parser.print_help()
        return

    try:
        if args.hex:
            r, g, b = hex_to_rgb(args.hex)
        elif args.rgb:
            r, g, b = args.rgb
            if not all(0 <= v <= 255 for v in (r, g, b)):
                raise ValueError("RGB 值必须在 [0, 255] 范围内")
        elif args.hsl:
            h, s, l = args.hsl
            if not (0 <= h <= 360 and 0 <= s <= 100 and 0 <= l <= 100):
                raise ValueError("HSL: H∈[0,360], S∈[0,100], L∈[0,100]")
            r, g, b = hsl_to_rgb(h, s, l)
        elif args.css:
            r, g, b = hex_to_rgb(CSS_COLORS[args.css])

        print_result(r, g, b)
    except ValueError as e:
        print(f"错误: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

小结

整个工具从无到有,用了 4 次对话、大约 4 分钟。最终代码 85 行,功能上覆盖了日常开发中 90% 的颜色转换需求。

对比传统开发方式:

  • 写 argparse 参数定义、互斥组——5 分钟
  • colorsys 文档、确认 HLS 参数顺序——3 分钟
  • 写转换函数、处理边界情况——8 分钟
  • 测试、修 bug——5 分钟

手动写大概 20 分钟,用 OpenCode 省了 80% 的时间。而且中间加短 HEX 支持、CSS 颜色名、ANSI 预览这三个需求,在传统开发中都是"要不再开个 issue 下次再搞"的特性,但在 OpenCode 里就是一句话的事。

工具虽然小,但这个过程说明了 OpenCode 真正擅长的事:不是帮你写复杂的架构设计,而是把那些"想法一秒钟、实现十分钟"的小需求,变成真正的生产力工具。