diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index ed013a490..6b273d580 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -53,12 +53,12 @@ pub fn format(hurl_file: HurlFile, standalone: bool) -> String { impl Htmlable for HurlFile { fn to_html(&self) -> String { let mut buffer = String::from(""); - buffer.push_str("
"); + buffer.push_str("
");
         for entry in self.clone().entries {
             buffer.push_str(entry.to_html().as_str());
         }
         add_line_terminators(&mut buffer, self.line_terminators.clone());
-        buffer.push_str("
"); + buffer.push_str(""); buffer } } @@ -66,12 +66,12 @@ impl Htmlable for HurlFile { impl Htmlable for Entry { fn to_html(&self) -> String { let mut buffer = String::from(""); - buffer.push_str("
"); + buffer.push_str(""); buffer.push_str(self.request.to_html().as_str()); if let Some(response) = self.clone().response { buffer.push_str(response.to_html().as_str()); } - buffer.push_str("
"); + buffer.push_str(""); buffer } } @@ -79,16 +79,17 @@ impl Htmlable for Entry { impl Htmlable for Request { fn to_html(&self) -> String { let mut buffer = String::from(""); - buffer.push_str("
"); + buffer.push_str(""); add_line_terminators(&mut buffer, self.line_terminators.clone()); + buffer.push_str(""); buffer.push_str(self.space0.to_html().as_str()); buffer.push_str(self.method.to_html().as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push_str(format!("{}", self.url.to_html()).as_str()); - buffer.push_str(self.line_terminator0.to_html().as_str()); buffer.push_str(""); - buffer.push_str("
"); + buffer.push_str(self.line_terminator0.to_html().as_str()); + for header in self.headers.clone() { buffer.push_str(header.to_html().as_str()); } @@ -98,6 +99,7 @@ impl Htmlable for Request { if let Some(body) = self.body.clone() { buffer.push_str(body.to_html().as_str()); } + buffer.push_str(""); buffer } } @@ -105,7 +107,7 @@ impl Htmlable for Request { impl Htmlable for Response { fn to_html(&self) -> String { let mut buffer = String::from(""); - buffer.push_str("
"); + buffer.push_str(""); add_line_terminators(&mut buffer, self.line_terminators.clone()); buffer.push_str(""); buffer.push_str(self.space0.to_html().as_str()); @@ -113,6 +115,7 @@ impl Htmlable for Response { buffer.push_str(self.space1.to_html().as_str()); buffer.push_str(self.status.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); for header in self.headers.clone() { buffer.push_str(header.to_html().as_str()); } @@ -122,7 +125,7 @@ impl Htmlable for Response { if let Some(body) = self.body.clone() { buffer.push_str(body.to_html().as_str()); } - buffer.push_str("
"); + buffer.push_str(""); buffer } } @@ -144,7 +147,7 @@ impl Htmlable for Version { impl Htmlable for Status { fn to_html(&self) -> String { - format!("{}", self.value.to_string()) + format!("{}", self.value.to_string()) } } @@ -160,6 +163,7 @@ impl Htmlable for Section { ) .as_str(), ); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer.push_str(self.value.to_html().as_str()); buffer } @@ -215,8 +219,8 @@ impl Htmlable for KeyValue { buffer.push_str(":"); buffer.push_str(self.space2.to_html().as_str()); buffer.push_str(format!("{}", self.value.to_html()).as_str()); - buffer.push_str(self.line_terminator0.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } @@ -241,8 +245,8 @@ impl Htmlable for FileParam { buffer.push(':'); buffer.push_str(self.space2.to_html().as_str()); buffer.push_str(self.value.to_html().as_str()); - buffer.push_str(self.line_terminator0.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } @@ -267,8 +271,9 @@ impl Htmlable for FileValue { impl Htmlable for Filename { fn to_html(&self) -> String { - let mut buffer = String::from(""); + let mut buffer = String::from(""); buffer.push_str(self.value.as_str()); + buffer.push_str(""); buffer } } @@ -279,21 +284,24 @@ impl Htmlable for Cookie { add_line_terminators(&mut buffer, self.line_terminators.clone()); buffer.push_str(""); buffer.push_str(self.space0.to_html().as_str()); - buffer.push_str(self.name.value.as_str()); + buffer + .push_str(format!("{}", self.name.value.as_str()).as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push_str(":"); buffer.push_str(self.space2.to_html().as_str()); buffer.push_str(self.value.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } impl Htmlable for CookieValue { fn to_html(&self) -> String { - let mut buffer = String::from(""); - buffer.push_str(self.value.as_str()); - buffer + format!( + "{}", + self.value.as_str() + ) } } @@ -303,12 +311,14 @@ impl Htmlable for Capture { add_line_terminators(&mut buffer, self.line_terminators.clone()); buffer.push_str(""); buffer.push_str(self.space0.to_html().as_str()); - buffer.push_str(self.name.value.as_str()); + buffer + .push_str(format!("{}", self.name.value.as_str()).as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push_str(":"); buffer.push_str(self.space2.to_html().as_str()); buffer.push_str(self.query.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } @@ -346,7 +356,7 @@ impl Htmlable for QueryValue { ); } QueryValue::Body {} => { - buffer.push_str("status"); + buffer.push_str("body"); } QueryValue::Xpath { space0, expr } => { buffer.push_str("xpath"); @@ -398,13 +408,15 @@ impl Htmlable for Subquery { let mut buffer = String::from(""); match self.value.clone() { SubqueryValue::Regex { expr, space0 } => { - buffer.push_str("regex"); + buffer.push_str("regex"); buffer.push_str(space0.to_html().as_str()); buffer.push_str( format!("\"{}\"", expr.to_html()).as_str(), ); } - SubqueryValue::Count {} => buffer.push_str("count"), + SubqueryValue::Count {} => { + buffer.push_str("count") + } } buffer } @@ -443,6 +455,7 @@ impl Htmlable for Assert { buffer.push_str(self.space1.to_html().as_str()); buffer.push_str(self.predicate.to_html().as_str()); buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } @@ -451,7 +464,7 @@ impl Htmlable for Predicate { fn to_html(&self) -> String { let mut buffer = String::from(""); if self.not { - buffer.push_str("not"); + buffer.push_str("not"); buffer.push_str(self.space0.to_html().as_str()); } buffer.push_str(self.predicate_func.to_html().as_str()); @@ -617,9 +630,7 @@ impl Htmlable for PredicateValue { format!("{}", value.to_string()) } PredicateValue::Bool(value) => format!("{}", value), - PredicateValue::Hex(value) => { - format!("{}", value.to_string()) - } + PredicateValue::Hex(value) => value.to_html(), PredicateValue::Base64(value) => value.to_html(), PredicateValue::Expression(value) => value.to_html(), PredicateValue::Null {} => "null".to_string(), @@ -629,28 +640,43 @@ impl Htmlable for PredicateValue { impl Htmlable for RawString { fn to_html(&self) -> String { - let mut buffer = String::from("```"); - if !self.newline.to_html().as_str().is_empty() { - buffer.push_str(""); + let mut buffer = "".to_string(); + buffer.push_str(""); + buffer.push_str("```"); + + if !self.newline.value.as_str().is_empty() { + buffer.push_str( + format!( + "{}", + self.newline.value.as_str() + ) + .as_str(), + ); } - let end_newline = self.value.to_string().ends_with('\n'); let mut lines: Vec = regex::Regex::new(r"\n|\r\n") .unwrap() .split(self.value.to_string().trim()) - .map(|l| l.to_string()) + .map(|l| xml_escape(l.to_string())) + .filter(|l| !l.is_empty()) .collect(); - buffer.push_str(xml_escape(lines.remove(0)).as_str()); - - for line in lines { - buffer.push_str(""); - buffer.push_str(xml_escape(line).as_str()); + if lines.is_empty() { + buffer.push_str("```"); + } else if lines.len() == 1 { + buffer.push_str(encode_html(lines.get(0).unwrap().to_string()).as_str()); + buffer.push_str("```"); + } else { + buffer.push_str(encode_html(lines.remove(0)).as_str()); + buffer.push_str("\n"); + for line in lines { + buffer.push_str(""); + buffer.push_str(encode_html(line).as_str()); + buffer.push_str("\n"); + } + buffer.push_str("```"); } - if end_newline { - buffer.push_str(""); - } - buffer.push_str("```"); + buffer.push_str(""); buffer } } @@ -659,70 +685,46 @@ impl Htmlable for Body { fn to_html(&self) -> String { let mut buffer = String::from(""); add_line_terminators(&mut buffer, self.line_terminators.clone()); - buffer.push_str(""); buffer.push_str(self.space0.to_html().as_str()); buffer.push_str(self.value.to_html().as_str()); - buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); buffer } } impl Htmlable for Bytes { fn to_html(&self) -> String { - let mut buffer = String::from(""); match self { - Bytes::Base64(value) => { - buffer.push_str(value.to_html().as_str()); - } - Bytes::Hex(value) => { - buffer.push_str(value.to_html().as_str()); - } - Bytes::File(value) => { - buffer.push_str(value.to_html().as_str()); - } - Bytes::RawString(value) => { - buffer.push_str(value.to_html().as_str()); - } - Bytes::Json { value } => buffer.push_str(value.to_html().as_str()), - Bytes::Xml { value } => { - let mut lines: Vec = regex::Regex::new(r"\n|\r\n") - .unwrap() - .split(value.as_str()) - .map(|l| l.to_string()) - .collect(); - buffer.push_str(xml_escape(lines.remove(0)).as_str()); - for line in lines { - buffer.push_str(""); - buffer.push_str(xml_escape(line).as_str()); - buffer.push_str(""); - } - } + Bytes::Base64(value) => format!("{}", value.to_html()), + Bytes::File(value) => format!("{}", value.to_html()), + Bytes::Hex(value) => format!("{}", value.to_html()), + Bytes::Json { value } => value.to_html(), + Bytes::RawString(value) => value.to_html(), + Bytes::Xml { value } => xml_html(value), } - buffer } } -fn xml_escape(s: String) -> String { - s.replace('<', "<") - .replace('>', ">") - .replace('&', "&") +// you should probably define for XML value to be consistent with the other types +fn xml_html(value: &str) -> String { + let mut buffer = String::from(""); + buffer.push_str(multilines(value.to_string()).as_str()); + buffer.push_str(""); + buffer } +fn xml_escape(s: String) -> String { + s.replace('&', "&") + .replace('<', "<") + .replace('>', ">") +} + +// Improvement: break into spans within the json value impl Htmlable for JsonValue { fn to_html(&self) -> String { - let s = self.to_string(); - let mut lines: Vec = regex::Regex::new(r"\n|\r\n") - .unwrap() - .split(s.as_str()) - .map(|l| l.to_string()) - .collect(); - let mut buffer = String::from(""); - buffer.push_str(lines.remove(0).as_str()); - for line in lines { - buffer.push_str(""); - buffer.push_str(line.as_str()); - buffer.push_str(""); - } + let mut buffer = String::from(""); + buffer.push_str(multilines(self.encoded()).as_str()); + buffer.push_str(""); buffer } } @@ -743,48 +745,52 @@ impl Htmlable for LineTerminator { let mut buffer = String::from(""); buffer.push_str(self.space0.to_html().as_str()); if let Some(v) = self.clone().comment { - buffer.push_str(""); - buffer.push_str(format!("#{}", v.value.as_str()).as_str()); - buffer.push_str(""); + buffer.push_str(v.to_html().as_str()); } + buffer.push_str(self.newline.value.as_str()); + buffer + } +} + +impl Htmlable for Comment { + fn to_html(&self) -> String { + let mut buffer = String::from(""); + buffer.push_str(format!("#{}", xml_escape(self.value.clone())).as_str()); + buffer.push_str(""); buffer } } impl Htmlable for File { fn to_html(&self) -> String { - let mut buffer = String::from(""); - buffer.push_str("file,"); + let mut buffer = String::from("file,"); buffer.push_str(self.space0.to_html().as_str()); buffer.push_str(self.filename.to_html().as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push(';'); - buffer.push_str(""); buffer } } impl Htmlable for Base64 { fn to_html(&self) -> String { - let mut buffer = String::from(""); - buffer.push_str("base64,"); + let mut buffer = String::from("base64,"); buffer.push_str(self.space0.to_html().as_str()); - buffer.push_str(self.encoded.as_str()); + buffer + .push_str(format!("{}", self.encoded.as_str()).as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push(';'); - buffer.push_str(""); buffer } } + impl Htmlable for Hex { fn to_html(&self) -> String { - let mut buffer = String::from(""); - buffer.push_str("hex,"); + let mut buffer = String::from("hex,"); buffer.push_str(self.space0.to_html().as_str()); - buffer.push_str(self.encoded.as_str()); + buffer.push_str(format!("{}", self.encoded.as_str()).as_str()); buffer.push_str(self.space1.to_html().as_str()); buffer.push(';'); - buffer.push_str(""); buffer } } @@ -797,7 +803,15 @@ impl Htmlable for EncodedString { impl Htmlable for Template { fn to_html(&self) -> String { - xml_escape(self.to_string().replace("\n", "\\n")) + let mut s = "".to_string(); + for element in self.elements.clone() { + let elem_str = match element { + TemplateElement::String { encoded, .. } => encoded, + TemplateElement::Expression(expr) => format!("{{{{{}}}}}", expr.to_string()), + }; + s.push_str(elem_str.as_str()) + } + xml_escape(s) } } @@ -814,11 +828,11 @@ impl Htmlable for Expr { fn add_line_terminators(buffer: &mut String, line_terminators: Vec) { for line_terminator in line_terminators.clone() { buffer.push_str(""); - buffer.push_str(line_terminator.to_html().as_str()); if line_terminator.newline.value.is_empty() { buffer.push_str("
"); } buffer.push_str("
"); + buffer.push_str(line_terminator.to_html().as_str()); } } @@ -826,6 +840,15 @@ fn encode_html(s: String) -> String { s.replace(">", ">").replace("<", "<") } +fn multilines(s: String) -> String { + regex::Regex::new(r"\n|\r\n") + .unwrap() + .split(s.as_str()) + .map(|l| format!("{}", xml_escape(l.to_string()))) + .collect::>() + .join("\n") +} + #[cfg(test)] mod tests { use super::*; @@ -847,7 +870,10 @@ mod tests { source_info: SourceInfo::init(0, 0, 0, 0), }, }; - assert_eq!(raw_string.to_html(), "``````".to_string()); + assert_eq!( + raw_string.to_html(), + "``````".to_string() + ); // ```hello``` let raw_string = RawString { @@ -864,7 +890,10 @@ mod tests { source_info: SourceInfo::init(0, 0, 0, 0), }, }; - assert_eq!(raw_string.to_html(), "```hello```".to_string()); + assert_eq!( + raw_string.to_html(), + "```hello```".to_string() + ); // ``` // line1 @@ -884,6 +913,81 @@ mod tests { source_info: SourceInfo::init(0, 0, 0, 0), }, }; - assert_eq!(raw_string.to_html(), "```line1line2```".to_string()); + assert_eq!( + raw_string.to_html(), + "```\nline1\nline2\n```".to_string() + ); + } + + #[test] + fn test_multilines() { + assert_eq!( + multilines("{\n \"id\": 1\n}".to_string()), + "{\n \"id\": 1\n}" + ); + assert_eq!( + multilines("\ncafé".to_string()), + "<?xml version=\"1.0\"?>\n<drink>café</drink>" + ); + } + + #[test] + fn test_json() { + let value = JsonValue::Object { + space0: "".to_string(), + elements: vec![JsonObjectElement { + space0: "\n ".to_string(), + name: Template { + quotes: true, + elements: vec![TemplateElement::String { + value: "id".to_string(), + encoded: "id".to_string(), + }], + source_info: SourceInfo::init(0, 0, 0, 0), + }, + space1: "".to_string(), + space2: " ".to_string(), + value: JsonValue::Number("1".to_string()), + space3: "\n".to_string(), + }], + }; + assert_eq!( + value.to_html(), + "{\n \"id\": 1\n}" + ); + } + + #[test] + fn test_json_encoded_newline() { + let value = JsonValue::String(Template { + quotes: true, + elements: vec![TemplateElement::String { + value: "\n".to_string(), + encoded: "\\n".to_string(), + }], + source_info: SourceInfo::init(0, 0, 0, 0), + }); + assert_eq!( + value.to_html(), + "\"\\n\"" + ) + } + + #[test] + fn test_xml() { + let value = "\ncafé"; + assert_eq!( + xml_html(value), + "<?xml version=\"1.0\"?>\n<drink>café</drink>" + ) + } + + #[test] + fn test_xml_escape() { + assert_eq!(xml_escape("hello".to_string()), "hello"); + assert_eq!( + xml_escape("".to_string()), + "<?xml version=\"1.0\"?>" + ); } } diff --git a/packages/hurl_core/src/format/hurl.css b/packages/hurl_core/src/format/hurl.css index 10511c324..ee73f190c 100644 --- a/packages/hurl_core/src/format/hurl.css +++ b/packages/hurl_core/src/format/hurl.css @@ -4,7 +4,6 @@ body { } span.line { - display: block; line-height: 1.2rem; } @@ -31,7 +30,7 @@ span.line:before { .version { color: black; } -.status { +.number { color: blue; } .section-header { @@ -42,6 +41,14 @@ span.line:before { color: teal; } +.subquery-type { + color: darkblue; +} + +.not { + color: darkblue; +} + .predicate-type { color: darkblue; } @@ -49,7 +56,30 @@ span.line:before { .string { color: darkgreen; } - +.raw { + color: darkgreen; +} .comment { color: dimgray; } +.name { + color: darkgreen; +} +.json { + color: darkgreen; +} +.xml { + color: darkgreen; +} +.base64 { + color: darkgreen; +} +.hex { + color: darkgreen; +} +.filename { + color: darkgreen; +} +.cookie-value { + color: darkgreen; +} \ No newline at end of file