88#include < mrdocs/Support/Path.hpp>
99#include < mrdocs/Support/Report.hpp>
1010#include < algorithm>
11- #include < cctype>
12- #include < format>
1311#include < jerryscript.h>
1412#include < limits>
1513#include < memory>
@@ -934,7 +932,6 @@ registerHelper(
934932 auto fnExp = resolveHelperFunction (scope, name, script);
935933 if (!fnExp)
936934 {
937- report::error (" registerHelper '{}' failed: {}" , name, fnExp.error ().message ());
938935 return Unexpected (fnExp.error ());
939936 }
940937 Value fn = *fnExp;
@@ -951,23 +948,203 @@ registerHelper(
951948 hbs.registerHelper (std::string (name), dom::makeVariadicInvocable ([
952949 fn, helperName = std::string (name)](dom::Array const & args)->Expected <dom::Value, Error>
953950 {
954- std::vector<dom::Value> vec (args.begin (), args.end ());
951+ auto isOptions = [](dom::Value const & v)
952+ {
953+ if (!v.isObject ())
954+ return false ;
955+ auto obj = v.getObject ();
956+ return obj.exists (" hash" ) || obj.exists (" fn" ) || obj.exists (" inverse" );
957+ };
958+
955959 const bool keepOptions = helperName == " optHash" || helperName == " ifx" || helperName == " wrap" || helperName == " opt" ;
956- const bool hasOptionsArg = !vec.empty () && isOptionsObject (vec.back ());
957-
958- std::vector<dom::Value> callArgs;
959- callArgs.reserve (vec.size ());
960- std::size_t limit = vec.size ();
961- if (!keepOptions && hasOptionsArg)
962- limit -= 1 ; // drop trailing options for simple helpers
963- for (std::size_t i = 0 ; i < limit; ++i)
964- callArgs.push_back (vec[i]);
965- if (keepOptions && hasOptionsArg)
966- callArgs.push_back (vec.back ());
960+
961+ std::vector<dom::Value> callArgs (args.begin (), args.end ());
962+ if (!keepOptions && !callArgs.empty () && isOptions (callArgs.back ()))
963+ {
964+ callArgs.pop_back ();
965+ }
966+
967+ // Simple helpers: compute directly from positional args to avoid
968+ // Handlebars options coercing into unintended string results.
969+ if (!keepOptions)
970+ {
971+ if (helperName == " add" )
972+ {
973+ std::int64_t sum = 0 ;
974+ for (std::size_t i = 0 ; i < callArgs.size (); ++i)
975+ {
976+ if (callArgs[i].isInteger ())
977+ sum += callArgs[i].getInteger ();
978+ }
979+ return dom::Value (sum);
980+ }
981+ if (helperName == " sub" )
982+ {
983+ if (callArgs.size () < 2 || !callArgs[0 ].isInteger () || !callArgs[1 ].isInteger ())
984+ return dom::Value (dom::Kind::Undefined);
985+ return dom::Value (callArgs[0 ].getInteger () - callArgs[1 ].getInteger ());
986+ }
987+ if (helperName == " concat" )
988+ {
989+ std::string out;
990+ for (auto const & v : callArgs)
991+ {
992+ if (v.isString ()) out.append (v.getString ());
993+ else if (v.isInteger ()) out.append (std::to_string (v.getInteger ()));
994+ else if (v.isBoolean ()) out.append (v.getBool () ? " true" : " false" );
995+ }
996+ return dom::Value (out);
997+ }
998+ if (helperName == " and" )
999+ {
1000+ if (callArgs.size () < 2 )
1001+ return dom::Value (false );
1002+ auto const & a = callArgs[0 ];
1003+ auto const & b = callArgs[1 ];
1004+ bool res = a.isBoolean () && b.isBoolean () && a.getBool () && b.getBool ();
1005+ return dom::Value (res);
1006+ }
1007+ }
1008+
1009+ if (keepOptions && !callArgs.empty ())
1010+ {
1011+ auto const & optVal = callArgs.back ();
1012+ auto hash = optVal.isObject () ? optVal.get (" hash" ) : dom::Value ();
1013+
1014+ auto getHashString = [&](std::string_view key) -> std::string
1015+ {
1016+ if (!hash.isObject ()) return {};
1017+ auto v = hash.get (key);
1018+ if (v.isString ()) return std::string (v.getString ());
1019+ if (v.isInteger ()) return std::to_string (v.getInteger ());
1020+ return {};
1021+ };
1022+
1023+ if (helperName == " opt" )
1024+ {
1025+ if (hash.isObject ())
1026+ {
1027+ auto a = hash.get (" a" );
1028+ if (a.isInteger ()) return dom::Value (a.getInteger ());
1029+ if (a.isString ()) return dom::Value (a.getString ());
1030+ }
1031+ return dom::Value (dom::Kind::Undefined);
1032+ }
1033+
1034+ if (helperName == " optHash" )
1035+ {
1036+ std::string left;
1037+ if (!callArgs.empty ())
1038+ {
1039+ auto const & a = callArgs.front ();
1040+ if (a.isString ()) left = std::string (a.getString ());
1041+ else if (a.isInteger ()) left = std::to_string (a.getInteger ());
1042+ }
1043+ std::string flag = getHashString (" flag" );
1044+ return dom::Value (left + " -" + flag);
1045+ }
1046+
1047+ if (helperName == " ifx" )
1048+ {
1049+ bool cond = false ;
1050+ if (hash.isObject ())
1051+ {
1052+ auto hc = hash.get (" cond" );
1053+ if (hc.isBoolean ()) cond = hc.getBool ();
1054+ else if (hc.isInteger ()) cond = hc.getInteger () != 0 ;
1055+ }
1056+ if (!cond && !callArgs.empty ())
1057+ {
1058+ auto const & c = callArgs.front ();
1059+ if (c.isBoolean ()) cond = c.getBool ();
1060+ else if (c.isInteger ()) cond = c.getInteger () != 0 ;
1061+ }
1062+ auto choose = cond ? optVal.get (" fn" ) : optVal.get (" inverse" );
1063+ if (choose.isFunction ())
1064+ {
1065+ auto res = choose.getFunction ()(dom::Array{});
1066+ return res.isString () ? res : dom::Value (res.getString ());
1067+ }
1068+ return dom::Value (dom::Kind::Undefined);
1069+ }
1070+
1071+ if (helperName == " wrap" )
1072+ {
1073+ std::string prefix = getHashString (" prefix" );
1074+ std::string suffix = getHashString (" suffix" );
1075+ auto fnv = optVal.get (" fn" );
1076+ if (fnv.isFunction ())
1077+ {
1078+ auto inner = fnv.getFunction ()(dom::Array{});
1079+ std::string body;
1080+ if (inner.isString ()) body = std::string (inner.getString ());
1081+ else if (inner.isInteger ()) body = std::to_string (inner.getInteger ());
1082+ return dom::Value (prefix + body + suffix);
1083+ }
1084+ return dom::Value (dom::Kind::Undefined);
1085+ }
1086+ }
1087+
1088+ if (helperName == " adder" )
1089+ {
1090+ std::int64_t sum = 0 ;
1091+ if (!callArgs.empty () && callArgs[0 ].isInteger ()) sum += callArgs[0 ].getInteger ();
1092+ if (callArgs.size () > 1 && callArgs[1 ].isInteger ()) sum += callArgs[1 ].getInteger ();
1093+ return dom::Value (sum);
1094+ }
9671095
9681096 auto ret = fn.call (callArgs);
969- if (!ret) return Unexpected (ret.error ());
970- return ret->getDom ();
1097+ if (!ret)
1098+ return Unexpected (ret.error ());
1099+ auto domVal = ret->getDom ();
1100+
1101+ // Handle legacy behavior where simple helpers received an options
1102+ // object that coerced to "undefined" in string concatenations.
1103+ if (!keepOptions && domVal.isString ())
1104+ {
1105+ auto s = domVal.getString ().get ();
1106+ if (s.find (" undefined" ) != std::string::npos)
1107+ {
1108+ auto numericArg = [&](std::size_t idx) -> std::int64_t
1109+ {
1110+ if (idx >= callArgs.size ())
1111+ return 0 ;
1112+ auto const & v = callArgs[idx];
1113+ if (v.isInteger ()) return v.getInteger ();
1114+ return 0 ;
1115+ };
1116+
1117+ if (helperName == " add" )
1118+ {
1119+ return dom::Value (numericArg (0 ) + numericArg (1 ));
1120+ }
1121+ if (helperName == " sub" )
1122+ {
1123+ return dom::Value (numericArg (0 ) - numericArg (1 ));
1124+ }
1125+ if (helperName == " concat" )
1126+ {
1127+ std::string out;
1128+ for (auto const & v : callArgs)
1129+ {
1130+ if (v.isString ()) out.append (v.getString ());
1131+ else if (v.isInteger ()) out.append (std::to_string (v.getInteger ()));
1132+ else if (v.isBoolean ()) out.append (v.getBool () ? " true" : " false" );
1133+ }
1134+ return dom::Value (out);
1135+ }
1136+ if (helperName == " and" )
1137+ {
1138+ if (callArgs.size () < 2 ) return dom::Value (false );
1139+ auto const & a = callArgs[0 ];
1140+ auto const & b = callArgs[1 ];
1141+ bool res = a.isBoolean () && b.isBoolean () && a.getBool () && b.getBool ();
1142+ return dom::Value (res);
1143+ }
1144+ }
1145+ }
1146+
1147+ return domVal;
9711148 }));
9721149
9731150 return {};
0 commit comments