timofurrer / colorful

Terminal string styling done right, in Python :snake: :tada:
MIT License
525 stars 23 forks source link

Support indexing and slicing #35

Open rsalmei opened 5 years ago

rsalmei commented 5 years ago

Hello,

You say in the readme that:

It correctly supports all str() methods including len().

But it seems it does not support basic indexing or slicing:

image

Could you please fix or implement it? I need this kind of functionality to be able to support colors in my progress bar project. I was wondering if I'd implement colorizing and len and indexing and slicing myself, but thankfully found this project of yours, it is missing little to fit my need perfectly. Thanks.

My project is in: https://github.com/rsalmei/alive-progress alive-progress

timofurrer commented 5 years ago

Good catch! Give me a day or two to find some spare time! If you'd want to implement it by yourself - I'm happy to review PRs :tada:

rsalmei commented 5 years ago

Great, thanks!

Do you realize it's not that easy to implement? Imagine in my example, a red Hello and an orange World. The char at index 1 should be a red e. And a slice between 4:7 should be a red o, a space and an orange W, and etc. All slices should maintain the styling...

I'm currently busy with my progress bar, but I will contribute in another time, thank you!

timofurrer commented 5 years ago

Yeah, that's what I figured ... let's see if that's possible some how ;)

timofurrer commented 5 years ago

I've just released an alpha release v0.6.0a1 which supports the basics of indexing and slicing. There are some limitations though: see the README changes

Could you please verify if that implementation if enough for your project?

rsalmei commented 5 years ago

Wow, that was fast! Thank you. I'll verify and report back to you!

rsalmei commented 5 years ago

Hey, it seems to be working real nice! It just broke with unicode characters, but should be easy to fix:

In [30]: colorful.red('♫♬')
Out[30]: <colorful.core.ColorfulString at 0x10e403a10>

In [31]: print(_)
♫♬

In [32]: _[1]
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-32-ac1337140e92> in <module>()
----> 1 _[1]

/Users/rogerio/.pyenv/versions/2.7.15/lib/python2.7/site-packages/colorful/core.pyc in __getitem__(self, item)
    357                 else:
    358                     if start <= current_orig_string_idx < stop:
--> 359                         sliced_styled_string += self.orig_string[current_orig_string_idx]
    360                         step_counter = step - 1
    361

UnicodeDecodeError: 'ascii' codec can't decode byte 0x99 in position 0: ordinal not in range(128)
rsalmei commented 5 years ago

I got another of the same error with disable:

In [34]: colorful.disable()

In [35]: print(_30)
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-35-16e467c1c773> in <module>()
----> 1 print(_30)

/Users/rogerio/.pyenv/versions/2.7.15/lib/python2.7/site-packages/colorful/core.pyc in __str__(self)
    238         def __str__(self):
    239             if self.colorful_ctx.colormode == terminal.NO_COLORS:
--> 240                 return self.orig_string.encode(DEFAULT_ENCODING)
    241             else:
    242                 return self.styled_string.encode(DEFAULT_ENCODING)

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

ps.: testing with python 2.7 to be more restrictive, it probably works in 3.7.

timofurrer commented 5 years ago

Are you sure that's a bug in colorful? The string with the unicode char is that really a Python 2 unicode string? Try using the u"" string literal of import unicode_literals from __future__.

image

timofurrer commented 5 years ago

image

rsalmei commented 5 years ago

Oh yes, my mistake! I'm sorry for that. It does work for these cases!

Unfortunately it is still missing something, but I'm not sure what. I'm my progress bar, I need to generate some cool lines like these:

In [17]: blank = ' '; gap = 2; block_size = 10; content = 'abc'

In [18]: ''.join(chain.from_iterable(zip(repeat(blank * gap),
    ...:                                 map(lambda c: c * block_size, content))))
Out[18]: '  aaaaaaaaaa  bbbbbbbbbb  cccccccccc'

So, I've generated blocks of each character, with a certain gap between them.

But with a colorful string, I get:

In [19]: content = colorful.red('♫♬')

In [20]: ''.join(chain.from_iterable(zip(repeat(blank * gap),
    ...:                                 map(lambda c: c * block_size, content))))
Out[20]: '  \x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b  [[[[[[[[[[  3333333333  8888888888  ;;;;;;;;;;  2222222222  ;;;;;;;;;;  2222222222  5555555555  5555555555  ;;;;;;;;;;  0000000000  ;;;;;;;;;;  0000000000  mmmmmmmmmm  ♫♫♫♫♫♫♫♫♫♫  ♬♬♬♬♬♬♬♬♬♬  \x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b\x1b  [[[[[[[[[[  3333333333  9999999999  mmmmmmmmmm'

So maybe the __iter__ would have to return colored chars too.

rsalmei commented 5 years ago

Also, I think I need object.reversed, as I use reversed() in some computations. Look:

In [24]: content = reversed(colorful.red('♫♬'))

In [25]: ''.join(chain.from_iterable(zip(repeat(blank * gap),
    ...:                                 map(lambda c: c * block_size, content))))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-8facd9412722> in <module>
      1 ''.join(chain.from_iterable(zip(repeat(blank * gap),
----> 2                                 map(lambda c: c * block_size, content))))

TypeError: sequence item 1: expected str instance, ColorfulString found

~I don't really get where or why this error was triggered...~ Actually I do! The reverse protocol knows how to handle strings, any other objects has to implement the support via the reversed method...

timofurrer commented 5 years ago

I'll have a look at the first problem.

Also, I think I need object.reversed, as I use reversed() in some computations.

Reversing the string could be a little harder ... Do you need the coloring there? If not you could just access the original string of the ColorfulString object and reverse that - as a workaround at least.

rsalmei commented 5 years ago

Hey, yes I do need color in reverse strings... I have a plethora of factory methods to generate the various animations, and if one calls for a different scrolling direction, I need to reverse the original string, including colors. So if we have: red a yellow b reset, I'd expect the reverse to return yellow b red a reset.

Regarding the first issue, I think it's just the iter missing, which should return red a reset, and yellow b reset. Actually, it should respect the len protocol, if the object has len=2, it should return 2 iterations in iter.

Do you think it's doable?

github-actions[bot] commented 5 years ago

This Issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days

rsalmei commented 4 years ago

Hello @timofurrer, how are you?

Hey, there's someone interested in helping me with this coloring, maybe he could help you here too! I'm going to link this issue there, thank you for all the help you already did!

rsalmei commented 4 years ago

As I explained in my issue there:

The problem lies on having to have string-like objects that support: len, indexing, slicing, concatenation and reverse without losing color information!! 😨