| 94 } |
94 } |
| 95 } |
95 } |
| 96 return buffer; |
96 return buffer; |
| 97 } |
97 } |
| 98 |
98 |
| 99 static std::string escape_json(const std::string &raw) { |
|
| 100 using std::string_view_literals::operator ""sv; |
|
| 101 auto replace_all = [](std::string str, char chr, std::string_view repl) static { |
|
| 102 size_t pos = str.find(chr); |
|
| 103 if (pos == std::string::npos) return str; |
|
| 104 std::string result = std::move(str); |
|
| 105 do { |
|
| 106 result.replace(pos, 1, repl); |
|
| 107 pos += repl.length(); |
|
| 108 } while ((pos = result.find(chr, pos)) != std::string::npos); |
|
| 109 return result; |
|
| 110 }; |
|
| 111 return replace_all(replace_all(raw, '\\', "\\\\"), '\"', "\\\""sv); |
|
| 112 } |
|
| 113 |
|
| 114 static std::string build_tag_list(fm::tag_lists tags, bool hide_repo_names) { |
99 static std::string build_tag_list(fm::tag_lists tags, bool hide_repo_names) { |
| 115 std::string tags_json; |
100 std::string tags_json; |
| 116 if (hide_repo_names) { |
101 if (hide_repo_names) { |
| 117 for (const auto &tags_vector: tags | std::views::values) { |
102 for (const auto &tags_vector: tags | std::views::values) { |
| 118 for (const auto &tag: tags_vector) { |
103 for (const auto &tag: tags_vector) { |
| 119 tags_json += escape_json(tag.message); |
104 tags_json += encode(tag.message); |
| 120 tags_json += ' '; |
105 tags_json += ' '; |
| 121 } |
106 } |
| 122 } |
107 } |
| 123 if (!tags_json.empty()) { |
108 if (!tags_json.empty()) { |
| 124 tags_json.pop_back(); |
109 tags_json.pop_back(); |
| 125 } |
110 } |
| 126 } else { |
111 } else { |
| 127 tags_json += '{'; |
112 tags_json += '{'; |
| 128 for (const auto &[repo, tags_vector] : tags) { |
113 for (const auto &[repo, tags_vector] : tags) { |
| 129 tags_json += "\"" + escape_json(repo) + "\":\""; |
114 tags_json += "\"" + encode(repo) + "\":\""; |
| 130 for (const auto &tag: tags_vector) { |
115 for (const auto &tag: tags_vector) { |
| 131 tags_json += escape_json(tag.message); |
116 tags_json += encode(tag.message); |
| 132 tags_json += ' '; |
117 tags_json += ' '; |
| 133 } |
118 } |
| 134 if (!tags_vector.empty()) { |
119 if (!tags_vector.empty()) { |
| 135 tags_json.pop_back(); |
120 tags_json.pop_back(); |
| 136 } |
121 } |
| 149 if (hide_repo_names) { |
134 if (hide_repo_names) { |
| 150 tags_json += '['; |
135 tags_json += '['; |
| 151 for (const auto &tags_vector: tags | std::views::values) { |
136 for (const auto &tags_vector: tags | std::views::values) { |
| 152 for (const auto &tag: tags_vector) { |
137 for (const auto &tag: tags_vector) { |
| 153 tags_json += '"'; |
138 tags_json += '"'; |
| 154 tags_json += escape_json(tag); |
139 tags_json += encode(tag); |
| 155 tags_json += "\","; |
140 tags_json += "\","; |
| 156 } |
141 } |
| 157 } |
142 } |
| 158 if (!tags.empty()) { |
143 if (!tags.empty()) { |
| 159 tags_json.pop_back(); |
144 tags_json.pop_back(); |
| 160 } |
145 } |
| 161 tags_json += ']'; |
146 tags_json += ']'; |
| 162 } else { |
147 } else { |
| 163 tags_json += '{'; |
148 tags_json += '{'; |
| 164 for (const auto &[repo, tags_vector] : tags) { |
149 for (const auto &[repo, tags_vector] : tags) { |
| 165 tags_json += "\"" + escape_json(repo) + "\":["; |
150 tags_json += "\"" + encode(repo) + "\":["; |
| 166 for (const auto &tag: tags_vector) { |
151 for (const auto &tag: tags_vector) { |
| 167 tags_json += '"'; |
152 tags_json += '"'; |
| 168 tags_json += escape_json(tag); |
153 tags_json += encode(tag); |
| 169 tags_json += "\","; |
154 tags_json += "\","; |
| 170 } |
155 } |
| 171 if (!tags_vector.empty()) { |
156 if (!tags_vector.empty()) { |
| 172 tags_json.pop_back(); |
157 tags_json.pop_back(); |
| 173 } |
158 } |
| 514 std::string commit_summary; |
499 std::string commit_summary; |
| 515 if (!hide_repo_names) { |
500 if (!hide_repo_names) { |
| 516 std::string commit_summary_json; |
501 std::string commit_summary_json; |
| 517 commit_summary_json += '{'; |
502 commit_summary_json += '{'; |
| 518 for (const auto &[repo, count] : summary.commits) { |
503 for (const auto &[repo, count] : summary.commits) { |
| 519 commit_summary_json += std::format("\"{}\": {},", escape_json(repo), count); |
504 commit_summary_json += std::format("\"{}\": {},", encode(repo), count); |
| 520 } |
505 } |
| 521 if (!summary.commits.empty()) { |
506 if (!summary.commits.empty()) { |
| 522 commit_summary_json.pop_back(); |
507 commit_summary_json.pop_back(); |
| 523 } |
508 } |
| 524 commit_summary_json += '}'; |
509 commit_summary_json += '}'; |
| 580 |
565 |
| 581 // Build a JSON object of commit summaries |
566 // Build a JSON object of commit summaries |
| 582 auto add_summaries = [](std::string &json, const std::vector<fm::commit_info> &summaries) static { |
567 auto add_summaries = [](std::string &json, const std::vector<fm::commit_info> &summaries) static { |
| 583 // We have to iterate in reverse order to sort the summaries chronologically |
568 // We have to iterate in reverse order to sort the summaries chronologically |
| 584 for (const auto &summary : summaries | std::views::reverse) { |
569 for (const auto &summary : summaries | std::views::reverse) { |
| 585 json += "\"" + escape_json(summary.message) + "\","; |
570 json += "\"" + encode(summary.message) + "\","; |
| 586 } |
571 } |
| 587 json.pop_back(); |
572 json.pop_back(); |
| 588 }; |
573 }; |
| 589 std::string summaries_json; |
574 std::string summaries_json; |
| 590 if (hide_repo_names) { |
575 if (hide_repo_names) { |
| 594 } |
579 } |
| 595 summaries_json += ']'; |
580 summaries_json += ']'; |
| 596 } else { |
581 } else { |
| 597 summaries_json += '{'; |
582 summaries_json += '{'; |
| 598 for (const auto &[repo, summaries] : commits.summaries) { |
583 for (const auto &[repo, summaries] : commits.summaries) { |
| 599 summaries_json += "\"" + escape_json(repo) + "\":["; |
584 summaries_json += "\"" + encode(repo) + "\":["; |
| 600 add_summaries(summaries_json, summaries); |
585 add_summaries(summaries_json, summaries); |
| 601 summaries_json += "],"; |
586 summaries_json += "],"; |
| 602 } |
587 } |
| 603 if (!commits.summaries.empty()) { |
588 if (!commits.summaries.empty()) { |
| 604 summaries_json.pop_back(); |
589 summaries_json.pop_back(); |