@@ -276,6 +276,29 @@ void write_file(const std::filesystem::path& p, std::string_view content) {
276276 os << content;
277277}
278278
279+ std::string json_escape (std::string_view value) {
280+ std::string out;
281+ out.reserve (value.size ());
282+ for (unsigned char ch : value) {
283+ switch (ch) {
284+ case ' "' : out += " \\\" " ; break ;
285+ case ' \\ ' : out += " \\\\ " ; break ;
286+ case ' \b ' : out += " \\ b" ; break ;
287+ case ' \f ' : out += " \\ f" ; break ;
288+ case ' \n ' : out += " \\ n" ; break ;
289+ case ' \r ' : out += " \\ r" ; break ;
290+ case ' \t ' : out += " \\ t" ; break ;
291+ default :
292+ if (ch < 0x20 ) {
293+ out += std::format (" \\ u{:04x}" , static_cast <unsigned >(ch));
294+ } else {
295+ out.push_back (static_cast <char >(ch));
296+ }
297+ }
298+ }
299+ return out;
300+ }
301+
279302// LineScan: cheap field extraction for bootstrap install progress lines.
280303// Handles flat JSON; no nested array/object — the keys we extract are
281304// all leaves.
@@ -794,12 +817,13 @@ void seed_xlings_json(const Env& env,
794817 json += " \" index_repos\" : [\n " ;
795818 for (std::size_t i = 0 ; i < repos.size (); ++i) {
796819 json += std::format (" {{ \" name\" : \" {}\" , \" url\" : \" {}\" }}{}\n " ,
797- repos[i].first , repos[i].second ,
820+ json_escape (repos[i].first ),
821+ json_escape (repos[i].second ),
798822 i + 1 == repos.size () ? " " : " ," );
799823 }
800824 json += " ],\n " ;
801825 json += " \" lang\" : \" en\" ,\n " ;
802- json += std::format (" \" mirror\" : \" {}\"\n " , mirror);
826+ json += std::format (" \" mirror\" : \" {}\"\n " , json_escape ( mirror) );
803827 json += " }\n " ;
804828 write_file (path, json);
805829}
0 commit comments