refactor: make all operators generators

Except `format.counter`, of course.
This commit is contained in:
Johann Dreo 2026-04-06 16:10:05 +02:00
commit 1418055706

View file

@ -49,21 +49,22 @@ class consume:
class lines(Consume): class lines(Consume):
"""Consume line by line.""" """Consume line by line."""
def __call__(self, stream): def __call__(self, stream):
return stream.readlines() for line in stream:
yield line
class paragraphs(Consume): class paragraphs(Consume):
"""Consume paragraph by paragraph (separated by an empty line).""" """Consume paragraph by paragraph (separated by an empty line)."""
def __call__(self, stream): def __call__(self, stream):
pars = []
current = "" current = ""
for item in stream.readlines(): for item in stream:
# Not counting spaces as legit content. # Not counting spaces as legit content.
if item.strip(): if item.strip():
current += item current += item
else: else:
pars.append( current ) yield current
current = "" current = ""
return pars if current.strip():
yield current
class sections(Consume): class sections(Consume):
"""Consume section by section. A new section starts when a line matches the `mark` regexp. If `skip` is set to 'skip', the marked line is not consumed.""" """Consume section by section. A new section starts when a line matches the `mark` regexp. If `skip` is set to 'skip', the marked line is not consumed."""
@ -77,19 +78,17 @@ class consume:
self.skip = bool(skip) self.skip = bool(skip)
def __call__(self, stream): def __call__(self, stream):
sec = []
current = "" current = ""
for item in stream.readlines(): for item in stream:
if re.match(self.mark, item[0]): if re.match(self.mark, item[0]):
sec.append( current ) yield current
if self.skip: if self.skip:
current = "" current = ""
else: else:
current = item current = item
else: else:
current += item current += item
sec.append( current ) yield current
return sec
class nlines(Consume): class nlines(Consume):
"""Consume by groups of `nb` lines.""" """Consume by groups of `nb` lines."""
@ -97,19 +96,17 @@ class consume:
self.nb = int(nb) self.nb = int(nb)
def __call__(self, stream): def __call__(self, stream):
sec = []
count = 0 count = 0
current = "" current = ""
for item in stream.readlines(): for item in stream:
if count >= self.nb: if count >= self.nb:
sec.append( current ) yield current
current = "" current = ""
count = 0 count = 0
else: else:
current += item current += item
count += 1 count += 1
sec.append( current ) yield current
return sec
class format: class format:
@ -120,7 +117,8 @@ class format:
class asis(Format): class asis(Format):
"""Do not format anything.""" """Do not format anything."""
def __call__(self, items): def __call__(self, items):
return items for item in items:
yield item
class trim(Format): class trim(Format):
"""Split items if their length is longer than `max`, and create new items with the remaining parts.""" """Split items if their length is longer than `max`, and create new items with the remaining parts."""
@ -137,57 +135,47 @@ class format:
yield item yield item
def __call__(self, items): def __call__(self, items):
trimmed = []
for item in items: for item in items:
for separated in self.trim(item): for separated in self.trim(item):
trimmed.append(separated) yield separated
return trimmed
class eol(Format): class eol(Format):
"""Add an end of line after the item.""" """Add an end of line after the item."""
def __call__(self, items): def __call__(self, items):
eoled = []
for item in items: for item in items:
eoled.append( item + "\n" ) yield item + "\n"
return eoled
class strip(Format): class strip(Format):
"""Remove any space character around the item.""" """Remove any space character around the item."""
def __call__(self, items): def __call__(self, items):
stripped = []
for item in items: for item in items:
stripped.append( item.strip() ) yield item.strip()
return stripped
class skip(Format): class skip(Format):
"""Skip items containing only spaces or being empty.""" """Skip items containing only spaces or being empty."""
def __call__(self, items): def __call__(self, items):
res = []
for item in items: for item in items:
if item.strip(): if item.strip():
res.append( item ) yield item
return res
class glue(Format): class glue(Format):
"""Glue consecutive items together if they are not separated by an empty one.""" """Glue consecutive items together if they are not separated by an empty one."""
def __call__(self, items): def __call__(self, items):
glued = []
current = "" current = ""
for item in items: for item in items:
if item.strip(): if item.strip():
current += item current += item
else: else:
glued.append( current ) yield current
current = "" current = ""
return glued if current.strip():
yield current
class panel(Format): class panel(Format):
"""Surround each item by an ascii-art box. NOTE: only works when lifted on stdout.""" """Surround each item by an ascii-art box. NOTE: only works when lifted on stdout."""
def __call__(self, items): def __call__(self, items):
panel = []
for item in items: for item in items:
panel.append( Panel.fit(item.strip()) ) yield Panel.fit(item.strip())
return panel
class counter(Format): class counter(Format):
"""Add a counter at the end of each items, with the current index and the total. If `end` is given, it is added at the very last item. If `sep` is given, it is appended to the item before the counter itself.""" """Add a counter at the end of each items, with the current index and the total. If `end` is given, it is added at the very last item. If `sep` is given, it is appended to the item before the counter itself."""
@ -195,14 +183,16 @@ class format:
self.sep = sep self.sep = sep
self.end = end self.end = end
def __call__(self, items): def __call__(self, counted):
items = list(counted) # Consume everything at once.
total = len(items) total = len(items)
res = [] res = []
for i,item in enumerate(items): for i,item in enumerate(items):
res.append(f"{item}{self.sep}{i+1}/{total}") res.append(f"{item}{self.sep}{i+1}/{total}")
if res: if res:
res[-1] += self.end res[-1] += self.end
return res for r in res:
yield r
class suffix(Format): class suffix(Format):
"""Add the `content` string after each item. If `sep` is given, it is appended to the item before the content and after the item.""" """Add the `content` string after each item. If `sep` is given, it is appended to the item before the content and after the item."""
@ -211,7 +201,8 @@ class format:
self.sep = sep self.sep = sep
def __call__(self, items): def __call__(self, items):
return [i+self.sep+self.content for i in items] for i in items:
yield i+self.sep+self.content
class prefix(Format): class prefix(Format):
"""Add the `content` string before each item. If `sep` is given, it is prepended to the item after the content and before the item.""" """Add the `content` string before each item. If `sep` is given, it is prepended to the item after the content and before the item."""
@ -220,25 +211,33 @@ class format:
self.sep = sep self.sep = sep
def __call__(self, items): def __call__(self, items):
return [self.content+self.sep+i for i in items] for i in items:
yield self.content+self.sep+i
class lift: class lift:
class Lift: class Lift:
def __call__(self, items): def call(self, items):
raise NotImplementedError raise NotImplementedError
def __call__(self, items):
count = 0
for item in items:
self.call(item)
count += 1
logger.debug(f"│ │ ├ {count}th item")
logger.debug(f"│ │ └OK {count} items")
class stdout(Lift): class stdout(Lift):
"""Print the items on the standard output.""" """Print the items on the standard output."""
def __call__(self, items): def call(self, item):
for item in items: if item:
if item: if __debug__:
if __debug__: print(item, end = '', file = sys.stdout, flush = True)
print(item, end = '', file = sys.stdout, flush = True)
else:
print(item, end = '')
else: else:
logger.debug("Empty item") print(item, end = '')
else:
logger.debug("Empty item")
class Forthlifter: class Forthlifter:
@ -273,27 +272,24 @@ class Forthlifter:
logger.debug(f"│ │ ├ {type(self.consumer).__name__}({type(streamer).__name__})") logger.debug(f"│ │ ├ {type(self.consumer).__name__}({type(streamer).__name__})")
# Concatenate # Concatenate
items += self.consumer(streamer()) items += self.consumer(streamer())
logger.debug(f"│ │ ├ {len(items)} items") logger.debug(f"│ │ └OK")
logger.debug(f"│ │ └OK {len(items)} items")
return items return items
def format(self, items): def format(self, items):
logger.debug(f"│ ├ format {len(items)} items") logger.debug(f"│ ├ format")
for formatter in self.formatters: for formatter in self.formatters:
logger.debug(f"│ │ ├ {type(formatter).__name__}") logger.debug(f"│ │ ├ {type(formatter).__name__}")
# Replace # Replace
items = formatter(items) items = formatter(items)
logger.debug(f"│ │ ├ {len(items)} items") logger.debug(f"│ │ └OK")
logger.debug(f"│ │ └OK {len(items)} items")
return items return items
def lift(self, items): def lift(self, items):
logger.debug(f"│ ├ lift {len(items)} items") logger.debug(f"│ ├ lift")
for lifter in self.lifters: for lifter in self.lifters:
logger.debug(f"│ │ ├ {type(lifter).__name__}") logger.debug(f"│ │ ├ {type(lifter).__name__}")
# Call # Call
lifter(items) lifter(items)
logger.debug(f"│ │ └OK {len(items)} items")
def __call__(self): def __call__(self):
logger.debug("├ call") logger.debug("├ call")