@@ -446,7 +446,117 @@ def build_lua_char_class(chars):
446446 ordered = ordered .replace ("-" , "" ) + "-"
447447 return f"[{ ordered } ]"
448448
449+ def _stage_to_lua_fragment (stage ):
450+ """Convert a single pipeline stage to Lua code that transforms 'v'."""
451+ cut = parse_cut (stage )
452+ if cut :
453+ mode , start , end , delim = cut
454+ if mode == "bytes" :
455+ return None
456+ elif mode == "field" :
457+ esc = lua_string_escape (lua_pattern_escape (delim ))
458+ return (
459+ f"do local t={{}} "
460+ f'for f in v:gmatch("[^{ esc } ]+") do t[#t+1]=f end '
461+ f'v=t[{ start } ] or "" end'
462+ )
463+ elif mode == "range" :
464+ esc = lua_string_escape (lua_pattern_escape (delim ))
465+ return (
466+ f"do local t={{}} "
467+ f'for f in v:gmatch("[^{ esc } ]+") do t[#t+1]=f end '
468+ f'v=table.concat(t,"{ lua_string_escape (delim )} ",'
469+ f"{ start } ,math.min(#t,{ end } )) end"
470+ )
471+ if _RE_TR_LOWER .match (stage ):
472+ return "v=v:lower()"
473+ if _RE_TR_UPPER .match (stage ):
474+ return "v=v:upper()"
475+ m = _RE_TR_DELETE .match (stage )
476+ if m :
477+ chars = m .group (1 )
478+ if len (chars ) == 1 :
479+ pat = lua_pattern_escape (chars )
480+ else :
481+ pat = build_lua_char_class (chars )
482+ return f'v=(v:gsub("{ lua_string_escape (pat )} ", ""))'
483+ m = _RE_TR_DELETE_BARE .match (stage )
484+ if m :
485+ pat = lua_string_escape (lua_pattern_escape (m .group (1 )))
486+ return f'v=(v:gsub("{ pat } ", ""))'
487+ m = _RE_TR_REPLACE .match (stage )
488+ if m :
489+ pat = lua_string_escape (lua_pattern_escape (m .group (1 )))
490+ repl = lua_string_escape (lua_gsub_repl_escape (m .group (2 )))
491+ return f'v=(v:gsub("{ pat } ", "{ repl } "))'
492+ m = _RE_AWK_F .match (stage )
493+ if m :
494+ delim = m .group (1 )
495+ print_args = m .group (2 )
496+ parts = _RE_AWK_FIELDS .findall (print_args )
497+ if parts :
498+ if not all (is_safe_for_expand (sep ) for _ , sep in parts if sep ):
499+ return None
500+ lua_parts = []
501+ for field_num , separator in parts :
502+ if field_num :
503+ lua_parts .append (f'(t[{ field_num } ] or "")' )
504+ elif separator is not None :
505+ lua_parts .append (f'"{ lua_string_escape (separator )} "' )
506+ if lua_parts :
507+ lua_expr = " .. " .join (lua_parts )
508+ esc = lua_string_escape (lua_pattern_escape (delim ))
509+ return (
510+ f"do local t={{}} "
511+ f'for f in v:gmatch("[^{ esc } ]+") do t[#t+1]=f end '
512+ f"v={ lua_expr } end"
513+ )
514+ substs = parse_sed_substs (stage )
515+ if substs :
516+ if all (is_safe_for_expand (repl ) for _ , repl , _ in substs ):
517+ parts = []
518+ for pattern , repl , is_global in substs :
519+ esc_pat = lua_string_escape (sed_pattern_to_lua (pattern ))
520+ esc_repl = lua_string_escape (lua_gsub_repl_escape (repl ))
521+ count_arg = "" if is_global else ", 1"
522+ parts .append (
523+ f'v=(v:gsub("{ esc_pat } ", "{ esc_repl } "{ count_arg } ))'
524+ )
525+ return " " .join (parts )
526+ return None
527+
449528 def convert_string_op (expr , cmd ):
529+ # Handle pipelines: split cmd into stages and compose Lua
530+ try :
531+ tokens = shlex .split (cmd , posix = False )
532+ except ValueError :
533+ tokens = None
534+ if tokens :
535+ stages = []
536+ current = []
537+ for token in tokens :
538+ if token == "|" :
539+ if current :
540+ stages .append (" " .join (current ))
541+ current = []
542+ else :
543+ current .append (token )
544+ if current :
545+ stages .append (" " .join (current ))
546+ if len (stages ) > 1 :
547+ esc_expr = lua_string_escape (expr )
548+ fragments = []
549+ for stage in stages :
550+ fragment = _stage_to_lua_fragment (stage )
551+ if fragment is None :
552+ return None
553+ fragments .append (fragment )
554+ lua_code = f'local v=rpm.expand("{ esc_expr } ")'
555+ for fragment in fragments :
556+ lua_code += f" { fragment } "
557+ lua_code += " print(v)"
558+ return f"%{{lua:{ lua_code } }}"
559+
450560 # -- cut --
451561 cut = parse_cut (cmd )
452562 if cut :
0 commit comments