Pythonでネストされた色付き文字を出力する

# はじめ

Command Prompt, PowerShell, Terminalなどで色付き文字を出力する方法(ライブラリ)は多数あります。

有名どころで言えば

**Colorama**

```python
from colorama import Fore

print(Fore.GREEN + "This text is green!")
```

https://github.com/tartley/colorama


**termcolor**

```python
from termcolor import colored
print(colored('This text is green!', 'green'))
```

https://github.com/hfeeki/termcolor

とかですかね。

## 素晴らしいライブラリ

これらは実に便利なライブラリです。簡単に色づき文字を作成したりすることができます。
ただ、実は一つ問題があります。

# ネストできない

これはつまり、たとえば

    (赤字の文章)(緑字の文章)(赤字の文章)

とか

    (赤字の文章)(赤字で下線のある文章)(赤字の文章)

などを出力しようとすると、結構めんどくさいということです。
以下の画像における各行のようなものです。
![スクリーンショット 2021-06-21 200935.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236785/83f76597-2671-c69d-bb73-8f3dfad7779d.png)
たとえばこれをtermcolorで実装しようとすると、以下のようになるでしょう。

```python
from termcolor import colored

print(colored('[RED]', 'red') + colored('[GREEN]', 'green') + colored('[RED]', 'red'))
print(colored('[RED]', 'red') + colored('[UNDERLINE]', 'red', attrs=['underline']) + colored('[RED]', 'red'))
```

ネストが深くなったり、長い文章同士を結合しようとすると毎回こういった指定をしなければならず、ちょっと大変です。そこで

# スタイリングライブラリ `iro`

iroを使ってみましょう！

    $ pip install iro

[GitHub](https://github.com/nagataaaas/Iro)
さて、このライブラリを使うと、ネストされたスタイルをこのように表現することができます。
出力は上記の画像と全く同じです。

```python
from iro import Iro, Color, Style

print(Iro([
    Color.RED, "[RED]",
    [ Color.GREEN, "[GREEN]" ],
    "[RED]"
]))

print(Iro([
    Color.RED, "[RED]",
    [ Style.UNDERLINE, "[UNDERLINE]" ],
    "[RED]"
]))
```

ある深さにある`str`には、その深さまでの色・スタイルがすべて適用されます。深さが深い方が優先度が高くなるので、このように簡単に表すことができます。

また、これはネストがより深くなればなるほどわかりやすくなります。

以下の二つのコードは同等の出力をします。

```python
print(Iro([
    Color.RED, "[RED]",
    [
        Style.UNDERLINE, "[RED/UNDERLINE]",
        [
            Style.BOLD, Color.GREEN, "[GREEN/UNDERLINE/BOLD]",
            [
                Style.INVERT, "[GREEN/UNDERLINE/BOLD/INVERT]",
                [
                    Style.OFF_BOLD, "[GREEN/UNDERLINE/INVERT]"
                ]
            ],
            "[GREEN/UNDERLINE/BOLD]"
        ],
        "[RED/UNDERLINE]"
    ]
]))
```

```python
print(colored("[RED]", 'red'),
      colored("[RED/UNDERLINE]", 'red', attrs=['underline']),
      colored("[GREEN/UNDERLINE/BOLD]", 'green', attrs=['underline', 'bold']),
      colored("[GREEN/UNDERLINE/BOLD/ITALIC]", 'green', attrs=['underline', 'bold', 'reverse']),
      colored("[GREEN/UNDERLINE/ITALIC]", 'green', attrs=['underline', 'reverse']),
      colored("[GREEN/UNDERLINE/BOLD]", 'green', attrs=['underline', 'bold']),
      colored("[RED/UNDERLINE]", 'red', attrs=['underline']), sep=''
      )
```

`Iro`のほうはあえて見やすさのために大量に改行などしてますが、非常に分かりやすくなっているのではないでしょうか？

これだけでなく、`Iro`は8-bitカラーや、RGBカラーを使用することもできます。

```python
print(Iro([
    [ Color256(135), Color256(217, bg=True), " SEXY COLOR " ],
    [ ColorRGB.from_color_code("EEA47F"), ColorRGB(0, 83, 156, bg=True), " AND THIS IS SICK " ],
    [ Color.BRIGHT_YELLOW, Color.BG_YELLOW, " BRIGHT!! " ]
], disable_rgb=False))
```
![スクリーンショット 2021-06-21 211606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236785/c9b49ca6-0ca6-2393-0fd7-bb9871cf3375.png)

デフォルトでは`disable_rgb`は`True`で、`ColorRGB`をそれに最も近しい`Color256`に変換して出力します。

RGBカラーに対応していないコンソールも時々あるので、その時は`disable_rgb`を`True`のままにしておきましょう。`True`にするとこのようになります。
![スクリーンショット 2021-06-21 211646.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236785/eae75b42-904d-bac5-9e07-1be10d131af4.png)
確かによく見てみると、上の写真と下の写真、中央の色が違いますね！(その程度の差なので、結構精度は高い)

Styleも豊富で

    RESET
    BOLD 
    DIM 
    ITALIC 
    UNDERLINE 
    SLOW_BLINK 
    RAPID_BLINK 
    INVERT 
    HIDE 
    STRIKE 
    BLACKLETTER_FONT 
    DOUBLY_UNDERLINE 

    OFF_INTENSITY
    OFF_BOLD
    OFF_DIM
    OFF_ITALIC
    OFF_UNDERLINE
    OFF_BLINK
    OFF_INVERT
    OFF_HIDE
    OFF_STRIKE

    OFF_COLOR
    OFF_BG_COLOR
    
    OFF_OVERLINE

などが使えます。

```python
Iro([text...], optimize_level=1)
```
とすることで最適化が行われ描画が高速になる可能性がありますが、端末によっては最適化によって動作が不安定になる場合があります。ただ、この効果は大きく、GitHubのQ&Aを見ると

```python
value = [
    Color.RED, "[RED]",
    [
        Style.UNDERLINE, "[RED/UNDERLINE]",
        [
            Style.BOLD, Color.GREEN, "[GREEN/UNDERLINE/BOLD]",
            [
                Style.INVERT, "[GREEN/UNDERLINE/BOLD/INVERT]",
                [
                    Style.OFF_BOLD, "[GREEN/UNDERLINE/INVERT]"
                ]
            ],
            "[GREEN/UNDERLINE/BOLD]"
        ],
        "[RED/UNDERLINE]"
    ]
]
print(repr(Iro(value, optimize_level=1).text))
# '\x1b[31m[RED]\x1b[4m[RED/UNDERLINE]\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]\x1b[7m[GREEN/UNDERLINE/BOLD/INVERT]
# \x1b[22m[GREEN/UNDERLINE/INVERT]\x1b[1m\x1b[27m[GREEN/UNDERLINE/BOLD]\x1b[22m\x1b[31m[RED/UNDERLINE]
# \x1b[24m\x1b[39m\x1b[0m'

print(repr(Iro(value, optimize_level=0).text))
# '\x1b[31m[RED]\x1b[31m\x1b[4m[RED/UNDERLINE]\x1b[31m\x1b[4m\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]
# \x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m[GREEN/UNDERLINE/BOLD/INVERT]
# \x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m\x1b[22m[GREEN/UNDERLINE/INVERT]
# \x1b[39m\x1b[24m\x1b[22m\x1b[39m\x1b[27m\x1b[22m\x1b[31m\x1b[4m\x1b[1m\x1b[32m\x1b[7m\x1b[39m\x1b
# [24m\x1b[22m\x1b[39m\x1b[27m\x1b[31m\x1b[4m\x1b[1m\x1b[32m[GREEN/UNDERLINE/BOLD]
# \x1b[39m\x1b[24m\x1b[22m\x1b[39m\x1b[31m\x1b[4m[RED/UNDERLINE]
# \x1b[39m\x1b[24m\x1b[31m\x1b[39m\x1b[0m'
```
という程の変化があるようです。

# おわり
是非便利にお使いください!(作者)