1use std::collections::HashMap;
18
19use crate::common::ParsingResult;
20use crate::doctree::tree_node_types::TreeNodeType;
21use crate::doctree::DocTree;
22use crate::parser::line_cursor::LineCursor;
23use crate::parser::state_machine::State;
24use crate::parser::types_and_aliases::{
25 InlineParsingResult, LineAdvance, PushOrPop, TransitionResult,
26};
27use crate::parser::Parser;
28use crate::parser::converters;
29use crate::parser::types_and_aliases::IndentedBlockResult;
30use crate::parser::types_and_aliases::TextBlockResult;
31
32pub fn parse_standard_admonition(
33 src_lines: &Vec<String>,
34 body_indent: usize,
35 section_level: usize,
36 first_indent: usize,
37 mut doctree: DocTree,
38 line_cursor: &mut LineCursor,
39 admonition_type: &str,
40 empty_after_marker: bool,
41) -> TransitionResult {
42 use crate::doctree::directives::AdmonitionType;
43
44 let variant: AdmonitionType = match admonition_type {
45 "attention" => AdmonitionType::Attention,
46 "caution" => AdmonitionType::Caution,
47 "danger" => AdmonitionType::Danger,
48 "error" => AdmonitionType::Error,
49 "hint" => AdmonitionType::Hint,
50 "important" => AdmonitionType::Important,
51 "note" => AdmonitionType::Note,
52 "tip" => AdmonitionType::Tip,
53 "warning" => AdmonitionType::Warning,
54 _ => unreachable!(
55 "No standard admonition type \"{}\" on line {}. Computer says no...",
56 admonition_type,
57 line_cursor.sum_total()
58 ),
59 };
60
61 let mut arg_lines = if let Some(arg) = scan_directive_arguments(
62 src_lines,
63 line_cursor,
64 body_indent,
65 Some(first_indent),
66 empty_after_marker,
67 ) {
68 arg
69 } else {
70 Vec::new()
71 };
72
73 let (classes, name) = if let Some(mut options) =
75 scan_directive_options(src_lines, line_cursor, body_indent)
76 {
77 (
78 options.remove("class"),
79 options.remove("name"),
80 )
81 } else {
82 (None, None)
83 };
84
85 let offset = match Parser::read_indented_block(
87 src_lines,
88 line_cursor.relative_offset(),
89 false,
90 true,
91 Some(body_indent),
92 Some(body_indent),
93 false) {
94 IndentedBlockResult::Ok {mut lines, minimum_indent, offset, blank_finish } => {
95 arg_lines.append(&mut lines);
96 offset
97 },
98 _ => return TransitionResult::Failure {
99 message: format!("Error when reading in the contents of \"{}\" around line {}. Computer says no...", variant.to_string(), line_cursor.sum_total()),
100 doctree: doctree
101 }
102 };
103
104 let admonition_data = TreeNodeType::Admonition {
106 content_indent: body_indent,
107 classes: classes,
108 name: name,
109 variant: variant.clone(),
110 };
111
112 doctree = match doctree.push_data_and_focus(admonition_data) {
114 Ok(tree) => tree,
115 Err(tree) => {
116 return TransitionResult::Failure {
117 message: format!(
118 "Node insertion error on line {}. Computer says no...",
119 line_cursor.sum_total()
120 ),
121 doctree: tree,
122 }
123 }
124 };
125
126 let (doctree, nested_state_stack) = match Parser::new(
128 arg_lines,
129 doctree,
130 body_indent,
131 line_cursor.sum_total(),
132 State::Admonition,
133 section_level,
134 ).parse() {
135 ParsingResult::EOF {
136 doctree,
137 state_stack,
138 } => (doctree, state_stack),
139 ParsingResult::EmptyStateStack {
140 doctree,
141 state_stack,
142 } => (doctree, state_stack),
143 ParsingResult::Failure { message, doctree } => {
144 return TransitionResult::Failure {
145 message: format!(
146 "Error when parsing a \"{}\" on line {}: {}",
147 variant,
148 line_cursor.sum_total(),
149 message
150 ),
151 doctree: doctree,
152 }
153 }
154 };
155
156 TransitionResult::Success {
157 doctree: doctree,
158 push_or_pop: PushOrPop::Push(nested_state_stack),
159 line_advance: LineAdvance::Some(offset),
160 }
161}
162
163pub fn parse_generic_admonition(
168 src_lines: &Vec<String>,
169 mut doctree: DocTree,
170 line_cursor: &mut LineCursor,
171 empty_after_marker: bool,
172 body_indent: usize,
173 first_indent: Option<usize>,
174) -> TransitionResult {
175
176 let argument = if let Some(arg) =
177 scan_directive_arguments(src_lines, line_cursor, body_indent, first_indent, empty_after_marker)
178 {
179 arg
180 } else {
181 return TransitionResult::Failure {
182 message: format!("General admonition on line {} does not contain a compulsory title argument. Computer says no...", line_cursor.sum_total()),
183 doctree: doctree
184 };
185 };
186
187 let directive_options =
188 scan_directive_options(src_lines, line_cursor, body_indent);
189
190 let (classes, name) = if let Some(mut options) = directive_options {
191 (
192 options.remove("class"),
193 options.remove("name")
194 )
195 } else {
196 (None, None)
197 };
198
199 let admonition_data = TreeNodeType::Admonition {
200 content_indent: body_indent,
201 classes: classes,
202 name: name,
203 variant: crate::doctree::directives::AdmonitionType::Admonition {
204 title: argument.join(" "),
205 },
206 };
207
208 doctree = match doctree.push_data_and_focus(admonition_data) {
209 Ok(tree) => tree,
210 Err(tree) => {
211 return TransitionResult::Failure {
212 message: format!(
213 "Node insertion error on line {}. Computer says no...",
214 line_cursor.sum_total()
215 ),
216 doctree: tree,
217 }
218 }
219 };
220
221 TransitionResult::Success {
222 doctree: doctree,
223 push_or_pop: PushOrPop::Push(vec![State::Admonition]),
224 line_advance: LineAdvance::None,
225 }
226}
227
228pub fn parse_image(
229 src_lines: &Vec<String>,
230 mut doctree: DocTree,
231 line_cursor: &mut LineCursor,
232 empty_after_marker: bool,
233 body_indent: usize,
234 first_indent: Option<usize>,
235) -> TransitionResult {
236
237 let argument = if let Some(arg) =
238 scan_directive_arguments(src_lines, line_cursor, body_indent, first_indent, empty_after_marker)
239 {
240 arg
241 } else {
242 return TransitionResult::Failure {
243 message: format!(
244 "Image on line {} does not contain a compulsory image URI. Computer says no...",
245 line_cursor.sum_total()
246 ),
247 doctree: doctree,
248 };
249 };
250
251 let (alt, height, width, scale, align, target, classes, name) =
252 if let Some(mut options) = scan_directive_options(src_lines, line_cursor, body_indent) {
253 (
254 options.remove("alt"),
255 options.remove("height"),
256 options.remove("width"),
257 options.remove("scale"),
258 options.remove("align"),
259 options.remove("target"),
260 options.remove("class"),
261 options.remove("name")
262 )
263 } else {
264 (None, None, None, None, None, None, None, None)
265 };
266
267 let image_data = TreeNodeType::Image {
268 uri: argument.join(""),
269 alt: alt,
270 height: if let Some(h) = &height {
271 converters::str_to_length(h)
272 } else {
273 None
274 },
275 width: if let Some(w) = &width {
276 converters::str_to_length(w)
277 } else {
278 None
279 },
280 scale: if let Some(scale) = &scale {
281 converters::str_to_percentage(scale)
282 } else {
283 None
284 },
285 align: if let Some(a) = &align {
286 converters::str_to_html_alignment(a)
287 } else {
288 None
289 },
290 target: target,
291 name: name,
292 class: classes,
293 inline: false
294 };
295
296 doctree = match doctree.push_data(image_data) {
297 Ok(tree) => tree,
298 Err(tree) => {
299 return TransitionResult::Failure {
300 message: format!(
301 "Node insertion error on line {}. Computer says no...",
302 line_cursor.sum_total()
303 ),
304 doctree: tree,
305 }
306 }
307 };
308
309 TransitionResult::Success {
310 doctree: doctree,
311 push_or_pop: PushOrPop::Neither,
312 line_advance: LineAdvance::None,
313 }
314}
315
316pub fn parse_figure(
317 src_lines: &Vec<String>,
318 mut doctree: DocTree,
319 line_cursor: &mut LineCursor,
320 base_indent: usize,
321 empty_after_marker: bool,
322 body_indent: usize,
323 first_indent: Option<usize>,
324 section_level: usize,
325) -> TransitionResult {
326 let argument = if let Some(arg) =
327 scan_directive_arguments(src_lines, line_cursor, body_indent, first_indent, empty_after_marker)
328 {
329 arg
330 } else {
331 return TransitionResult::Failure {
332 message: format!("Figure on line {} does not contain a compulsory image URI. Computer says no...", line_cursor.sum_total()),
333 doctree: doctree
334 };
335 };
336
337 let (alt, height, width, scale, align, target, classes, name, figwidth, figclass) =
338 if let Some(mut options) =
339 scan_directive_options(src_lines, line_cursor, body_indent)
340 {
341 (
342 options.remove("alt"),
343 options.remove("height"),
344 options.remove("width"),
345 options.remove("scale"),
346 options.remove("align"),
347 options.remove("target"),
348 options.remove("class"),
349 options.remove("name"),
350 options.remove("figwidth"),
351 options.remove("figclass")
352 )
353 } else {
354 (None, None, None, None, None, None, None, None, None, None)
355 };
356
357 let image = TreeNodeType::Image {
359 uri: argument.join(""),
360
361 alt: alt,
362 height: if let Some(h) = height {
363 converters::str_to_length(&h)
364 } else {
365 None
366 },
367 width: if let Some(w) = width {
368 converters::str_to_length(&w)
369 } else {
370 None
371 },
372 scale: if let Some(scale) = &scale {
373 converters::str_to_percentage(scale)
374 } else {
375 None
376 },
377 align: None, target: target,
379 class: classes,
380 name: None, inline: false
382 };
383
384 let figure = TreeNodeType::Figure {
385 body_indent: body_indent,
386 align: if let Some(a) = &align {
387 converters::str_to_horizontal_alignment(a)
388 } else {
389 None
390 },
391 figclass: figclass,
392 figwidth: if let Some(w) = &figwidth {
393 converters::str_to_length(w)
394 } else {
395 None
396 },
397 name: if let Some(refname) = &name {
398 Some(crate::common::normalize_refname(refname))
399 } else {
400 None
401 }
402 };
403
404 doctree = match doctree.push_data_and_focus(figure) {
406 Ok(tree) => tree,
407 Err(tree) => {
408 return TransitionResult::Failure {
409 message: format!(
410 "Node insertion error on line {}. Computer says no...",
411 line_cursor.sum_total()
412 ),
413 doctree: tree,
414 }
415 }
416 };
417
418 doctree = match doctree.push_data(image) {
420 Ok(tree) => tree,
421 Err(tree) => {
422 return TransitionResult::Failure {
423 message: format!(
424 "Node insertion error on line {}. Computer says no...",
425 line_cursor.sum_total()
426 ),
427 doctree: tree,
428 }
429 }
430 };
431
432 TransitionResult::Success {
433 doctree: doctree,
434 push_or_pop: PushOrPop::Push(vec![State::Figure]),
435 line_advance: LineAdvance::None,
436 }
437}
438
439pub fn parse_topic() {
440 todo!()
441}
442
443pub fn parse_sidebar() {
444 todo!()
445}
446
447pub fn parse_line_block() {
448 todo!()
449}
450
451pub fn parse_parsed_literal() {
452 todo!()
453}
454
455pub fn parse_code(
457 src_lines: &Vec<String>,
458 mut doctree: DocTree,
459 line_cursor: &mut LineCursor,
460 base_indent: usize,
461 empty_after_marker: bool,
462 body_indent: usize,
463 first_indent: Option<usize>,
464 section_level: usize,
465) -> TransitionResult {
466
467 let language = if let Some(arg) =
468 scan_directive_arguments(src_lines, line_cursor, body_indent, first_indent, empty_after_marker)
469 {
470 Some(arg.join(""))
471 } else {
472 None
473 };
474
475 let directive_options =
476 scan_directive_options(src_lines, line_cursor, body_indent);
477
478 let (classes, name, number_lines) = if let Some(mut options) = directive_options {
479 (
480 options.remove("class"),
481 options.remove("name"),
482 options.remove("number-lines"),
483 )
484 } else {
485 (None, None, None)
486 };
487
488 let (lines, offset) = if let IndentedBlockResult::Ok {lines, minimum_indent, offset, blank_finish } = Parser::read_indented_block(
489 src_lines,
490 line_cursor.relative_offset(),
491 false,
492 true,
493 Some(body_indent),
494 None,
495 false,
496 ) {
497 (lines, offset)
498 } else {
499 return TransitionResult::Failure {
500 message: format!(
501 "Could not read the code block on line {}. Computer says no...",
502 line_cursor.sum_total()
503 ),
504 doctree: doctree,
505 };
506 };
507
508 let code_block = TreeNodeType::Code {
509 text: lines.join("\n"),
510 language: language,
511 number_lines: number_lines,
512 class: classes,
513 name: name,
514 };
515
516 doctree = match doctree.push_data(code_block) {
517 Ok(tree) => tree,
518 Err(tree) => {
519 return TransitionResult::Failure {
520 message: format!(
521 "Node insertion error on line {}. Computer says no...",
522 line_cursor.sum_total()
523 ),
524 doctree: tree,
525 }
526 }
527 };
528
529 TransitionResult::Success {
530 doctree: doctree,
531 push_or_pop: PushOrPop::Neither,
532 line_advance: LineAdvance::Some(offset),
533 }
534}
535
536pub fn parse_math_block(
538 src_lines: &Vec<String>,
539 mut doctree: DocTree,
540 line_cursor: &mut LineCursor,
541 body_indent: usize,
542 empty_after_marker: bool,
543 first_indent: usize,
544) -> TransitionResult {
545
546 let math_after_marker = scan_directive_arguments(
547 src_lines,
548 line_cursor,
549 body_indent,
550 Some(first_indent),
551 empty_after_marker
552 );
553
554 let (classes, name, nowrap, label) = if let Some(mut options)
555 = scan_directive_options(src_lines, line_cursor, body_indent)
556 {
557 (
558 options.remove("class"),
559 options.remove("name"),
560 options.remove("nowrap"),
562 options.remove("label"),
563 )
564 } else {
565 (None, None, None, None)
566 };
567
568 if let Some(math) = math_after_marker {
570 doctree = match doctree.push_data(
571 TreeNodeType::MathBlock {
572 math_block: math.join("\n"),
573 class: classes,
574 name: name,
575 }
576 ) {
577 Ok(tree) => tree,
578 Err(tree) => {
579 return TransitionResult::Failure {
580 message: format!(
581 "Node insertion error on line {}. Computer says no...",
582 line_cursor.sum_total()
583 ),
584 doctree: tree,
585 }
586 }
587 };
588 return TransitionResult::Success {
589 doctree: doctree,
590 push_or_pop: PushOrPop::Neither,
591 line_advance: LineAdvance::None,
592 };
593 }
594
595 let (lines, offset) = match Parser::read_indented_block(
597 src_lines,
598 line_cursor.relative_offset(),
599 false,
600 true,
601 Some(body_indent),
602 Some(body_indent),
603 false
604 ) {
605 IndentedBlockResult::Ok {lines, minimum_indent, offset, blank_finish } => (lines, offset),
606 _ => return TransitionResult::Failure {
607 message: format!(
608 "Could not read the math block on line {}. Computer says no...",
609 line_cursor.sum_total()
610 ),
611 doctree: doctree,
612 }
613 };
614
615 let blocks = {
617 let mut blocks = Vec::new();
618 let mut block = String::new();
619
620 for line in lines.iter() {
621 if ! line.trim().is_empty() {
622 block = block + "\n" + line;
623 } else if line.trim().is_empty() && !block.trim().is_empty() {
624 blocks.push(block);
625 block = String::new();
626 } else {
627 continue
628 }
629 }
630
631 if ! block.trim().is_empty() {
632 blocks.push(block)
633 }
634
635 blocks
636 };
637
638 if blocks.is_empty() {
639 return TransitionResult::Failure {
640 message: format!("Tried reading a math block on line {} but didn't find any actual content. Computer says no...", line_cursor.sum_total()),
641 doctree: doctree
642 };
643 }
644
645 let mut refname_counter = if blocks.len() == 1 {
647 None
648 } else {
649 Some(0)
650 };
651 for block in blocks {
652 doctree = match doctree.push_data(
653 TreeNodeType::MathBlock {
654 math_block: block.trim().to_string(),
655 name: if let Some(name) = &name {
656 match &mut refname_counter {
657 Some(counter) => {
658 *counter += 1;
659 Some(name.clone() + &(counter.to_string()))
660 },
661 None => Some(name.clone())
662 }
663 } else {
664 None
665 },
666 class: classes.clone(),
667 }
668 ) {
669 Ok(tree) => tree,
670 Err(tree) => {
671 return TransitionResult::Failure {
672 message: format!(
673 "Node insertion error on line {}. Computer says no...",
674 line_cursor.sum_total()
675 ),
676 doctree: tree,
677 }
678 }
679 };
680 }
681
682 TransitionResult::Success {
683 doctree: doctree,
684 push_or_pop: PushOrPop::Neither,
685 line_advance: LineAdvance::Some(offset),
686 }
687}
688
689pub fn parse_rubric() {
690 todo!()
691}
692
693pub fn parse_epigraph() {
694 todo!()
695}
696
697pub fn parse_highlights() {
698 todo!()
699}
700
701pub fn parse_pull_quote() {
702 todo!()
703}
704
705pub fn parse_compound() {
706 todo!()
707}
708
709pub fn parse_container() {}
710
711pub fn parse_table() {
712 todo!()
713}
714
715pub fn parse_csv_table() {
716 todo!()
717}
718
719pub fn parse_list_table(
720 src_lines: &Vec<String>,
721 mut doctree: DocTree,
722 line_cursor: &mut LineCursor,
723 base_indent: usize,
724 empty_after_marker: bool,
725 first_indent: Option<usize>,
726 body_indent: usize,
727 section_level: usize,
728) -> TransitionResult {
729
730 let table_title = if let Some(title) = scan_directive_arguments(
731 src_lines,
732 line_cursor,
733 body_indent,
734 first_indent,
735 empty_after_marker,
736 ) {
737 title.join(" ")
738 } else {
739 String::new()
740 };
741
742 let (header_rows, stub_columns, width, widths, class, name, align) =
743 if let Some(mut options) = scan_directive_options
744 (src_lines, line_cursor, body_indent) {
745 (
746 options.remove("header-rows"),
747 options.remove("stub-columns"),
748 options.remove("width"),
749 options.remove("widths"),
750 options.remove("class"),
751 options.remove("name"),
752 options.remove("align"),
753 )
754 } else {
755 (None, None, None, None, None, None, None)
756 };
757
758 use crate::common::{HorizontalAlignment, MetricType, TableColWidths};
759
760 let list_table_node = TreeNodeType::ListTable {
761 body_indent: body_indent,
762
763 title: if !table_title.is_empty() {
764 Some(table_title)
765 } else {
766 None
767 },
768 widths: if let Some(widths) = widths {
769 if widths.as_str().trim() == "auto" {
770 Some(TableColWidths::Auto)
771 } else {
772 let widths = widths
773 .split_whitespace()
774 .filter(
775 |s| ! s.is_empty()
776 )
777 .map(
778 |int|
779 if let Ok(result) = int.parse::<f64>() {
780 result
781 } else {
782 panic!(
783 "Tried converting a list table column width \"{}\" into a integer on line {} but failed. Computer says no...",
784 int,
785 line_cursor.sum_total()
786 );
787 }
788 )
789 .collect::<Vec<f64>>();
790 if widths.len() == 0 {
791 None
792 } else {
793 Some(TableColWidths::Columns(widths))
794 }
795 }
796 } else {
797 None
798 },
799 width: if let Some(width) = width {
800 if let Some(length) = converters::str_to_length(&width) {
801 Some(MetricType::Lenght(length))
802 } else if let Some(percentage) = converters::str_to_percentage(&width) {
803 Some(crate::common::MetricType::Percentage(percentage))
804 } else {
805 None
806 }
807 } else {
808 None
809 },
810 header_rows: if let Some(num) = header_rows {
811 if let Ok(result) = num.parse::<u32>() {
812 Some(result)
813 } else {
814 eprintln!(
815 "Could not parse list-table header-rows setting to integer on line {}...",
816 line_cursor.sum_total()
817 );
818 None
819 }
820 } else {
821 None
822 },
823 stub_columns: if let Some(num) = stub_columns {
824 if let Ok(result) = num.parse::<u32>() {
825 Some(result)
826 } else {
827 eprintln!(
828 "Could not parse list-table stub-columns setting to integer on line {}...",
829 line_cursor.sum_total()
830 );
831 None
832 }
833 } else {
834 None
835 },
836 align: if let Some(alignment) = align {
837 match alignment.as_str() {
838 "left" => Some(HorizontalAlignment::Left),
839 "center" => Some(HorizontalAlignment::Center),
840 "right" => Some(HorizontalAlignment::Right),
841 _ => {
842 eprintln!("Found an alignment setting for list table on line {}, but setting not valid...", line_cursor.sum_total());
843 None
844 }
845 }
846 } else {
847 None
848 },
849 };
850
851 Parser::skip_empty_lines(src_lines, line_cursor);
852
853 doctree = match doctree.push_data_and_focus(list_table_node) {
854 Ok(tree) => tree,
855 Err(tree) => {
856 return TransitionResult::Failure {
857 message: format!(
858 "Node insertion error on line {}. Computer says no...",
859 line_cursor.sum_total()
860 ),
861 doctree: tree,
862 }
863 }
864 };
865
866 let (lines, offset) = if let IndentedBlockResult::Ok {lines, minimum_indent, offset, blank_finish } = Parser::read_indented_block(
867 src_lines,
868 line_cursor.relative_offset(),
869 false,
870 true,
871 Some(body_indent),
872 None,
873 false,
874 ) {
875 (lines, offset)
876 } else {
877 return TransitionResult::Failure {
878 message: format!("Could not read the legend contents of the figure on line {}. Computer says no...", line_cursor.sum_total()),
879 doctree: doctree
880 };
881 };
882
883 let (mut doctree, mut nested_state_stack) = match Parser::new(
884 lines,
885 doctree,
886 body_indent,
887 line_cursor.sum_total(),
888 State::ListTable,
889 section_level,
890 ).parse() {
891 ParsingResult::EOF {
892 doctree,
893 state_stack,
894 } => (doctree, state_stack),
895 ParsingResult::EmptyStateStack {
896 doctree,
897 state_stack,
898 } => (doctree, state_stack),
899 ParsingResult::Failure { message, doctree } => {
900 return TransitionResult::Failure {
901 message: format!(
902 "Error when parsing a list-table on line {}: {}",
903 line_cursor.sum_total(),
904 message
905 ),
906 doctree: doctree,
907 };
908 }
909 };
910
911 while nested_state_stack.len() > 1 {
913 nested_state_stack.pop();
914 doctree = doctree.focus_on_parent()
915 }
916
917 if let TreeNodeType::ListTable { .. } = doctree.shared_data() {
918 } else {
920 return TransitionResult::Failure {
921 message: format!("Not focused on list-table after parsing its contents starting on line {}. Computer says no...", line_cursor.sum_total()),
922 doctree: doctree
923 };
924 };
925
926 let n_of_columns = {
929 let mut max_cols: u32 = 0;
930
931 if let Some(children) = doctree.shared_children() {
932 if let Some(child_list) = children.get(0) {
933 if let TreeNodeType::BulletList { .. } = child_list.shared_data() {
934 if let Some(list_items) = child_list.shared_children() {
935 for list_item in list_items {
937 if let Some(children) = list_item.shared_children() {
938 if let Some(nested_list) = children.get(0) {
939 if let TreeNodeType::BulletList { .. } = nested_list.shared_data() {
940 if let Some(items) = nested_list.shared_children() {
941 let row_entry_count = items
942 .iter()
943 .filter(
944 |item| {
945 if let TreeNodeType::BulletListItem { .. } = item.shared_data() {
946 true
947 } else {
948 false
949 }
950 }
951 )
952 .count() as u32;
953 max_cols = std::cmp::max(max_cols, row_entry_count);
954 } else {
955 return TransitionResult::Failure {
956 message: format!("Second level list has no children inside list-table before line {}. Computer says no...", line_cursor.sum_total()),
957 doctree: doctree
958 };
959 }
960 } else {
961 return TransitionResult::Failure {
962 message: format!("No second level bullet list inside list-table before line {}. Computer says no...", line_cursor.sum_total()),
963 doctree: doctree
964 };
965 }
966 } else {
967 return TransitionResult::Failure {
968 message: format!("List item in list-table on line {} does not contain children. Computer says no...", line_cursor.sum_total()),
969 doctree: doctree
970 };
971 }
972 } else {
973 return TransitionResult::Failure {
974 message: format!("First level list item inside list-table on line {} has no children. Computer says no...", line_cursor.sum_total()),
975 doctree: doctree
976 };
977 }
978 }
979 } else {
980 return TransitionResult::Failure {
981 message: format!("Bullet list in list-table on line {} cannot have children? Computer says no...", line_cursor.sum_total()),
982 doctree: doctree
983 };
984 }
985 } else {
986 return TransitionResult::Failure {
987 message: format!("First child if list-table on line {} is not a bullet list. Computer says no...", line_cursor.sum_total()),
988 doctree: doctree
989 };
990 }
991 } else {
992 return TransitionResult::Failure {
993 message: format!(
994 "List-table on line {} has no children. Computer says no...",
995 line_cursor.sum_total()
996 ),
997 doctree: doctree,
998 };
999 }
1000 } else {
1001 return TransitionResult::Failure {
1002 message: format!(
1003 "List-table before line {} cannot have children? Computer says no...",
1004 line_cursor.sum_total()
1005 ),
1006 doctree: doctree,
1007 };
1008 }
1009 max_cols
1010 };
1011
1012 if let TreeNodeType::ListTable { widths, .. } = doctree.mut_node_data() {
1015 if widths.is_none() {
1016 *widths = Some(
1017 TableColWidths::Columns(
1018 std::iter::repeat(1.0 / n_of_columns as f64)
1019 .take(n_of_columns as usize)
1020 .collect(),
1021 )
1022 )
1023 }
1024 } else {
1025 return TransitionResult::Failure {
1026 message: format!("Not focused on list-table before line {}, after validating said table. Computer says no...", line_cursor.sum_total()),
1027 doctree: doctree
1028 };
1029 }
1030
1031 TransitionResult::Success {
1032 doctree: doctree,
1033 push_or_pop: PushOrPop::Push(vec![State::ListTable]),
1034 line_advance: LineAdvance::Some(offset),
1035 }
1036}
1037
1038pub fn parse_contents() {
1039 todo!()
1040}
1041
1042pub fn parse_section_numbering() {
1043 todo!()
1044}
1045
1046pub fn parse_header_or_footer() {
1047 todo!()
1048}
1049
1050pub fn parse_target_notes() {
1051 todo!()
1052}
1053
1054pub fn parse_footnotes() {
1055 todo!()
1056}
1057
1058pub fn parse_citations() {
1059 todo!()
1060}
1061
1062pub fn parse_meta() {
1063 todo!()
1064}
1065
1066pub fn parse_include(
1069 src_lines: &Vec<String>,
1070 mut doctree: DocTree,
1071 line_cursor: &mut LineCursor,
1072 first_indent: usize,
1073 body_indent: usize,
1074 empty_after_marker: bool,
1075 section_level: usize,
1076) -> TransitionResult {
1077
1078 let uri = match scan_directive_arguments(src_lines, line_cursor, body_indent, Some(first_indent), empty_after_marker) {
1079 Some(arg) => arg.join(""),
1080 None => return TransitionResult::Failure {
1081 message: format!("Include directive on line {} did not have a file URI as an argument.", line_cursor.sum_total()),
1082 doctree: doctree
1083 }
1084 };
1085
1086 let (
1087 start_line,
1088 end_line,
1089 start_after,
1090 end_before,
1091 literal,
1092 code,
1093 number_lines,
1094 encoding,
1095 tab_width,
1096 name,
1097 class
1098 ) = if let Some(mut options) = scan_directive_options
1099 (src_lines, line_cursor, body_indent) {
1100 let start_line = if let Some(option) = options.remove("start-line") {
1101 match option.parse::<u32>() {
1102 Ok(num) => Some(num),
1103 Err(_) => None
1104 }
1105 } else {
1106 None
1107 };
1108 let end_line = if let Some(option) = options.remove("end-line") {
1109 match option.parse::<u32>() {
1110 Ok(num) => Some(num),
1111 Err(_) => None
1112 }
1113 } else {
1114 None
1115 };
1116 let start_after = if let Some(option) = options.remove("start-after") {
1117 match option.parse::<u32>() {
1118 Ok(num) => Some(num),
1119 Err(_) => None
1120 }
1121 } else {
1122 None
1123 };
1124 let end_before = if let Some(option) = options.remove("end-before") {
1125 match option.parse::<u32>() {
1126 Ok(num) => Some(num),
1127 Err(_) => None
1128 }
1129 } else {
1130 None
1131 };
1132 let literal = if let Some(option) = options.remove("literal") {
1133 true
1134 } else {
1135 false
1136 };
1137 let code = if let Some(language) = options.remove("code") {
1138 if language.trim().is_empty() {
1139 Some(None)
1140 } else {
1141 Some(Some(language))
1142 }
1143 } else {
1144 None
1145 };
1146 let number_lines = if let Some(option) = options.remove("code") {
1147 if option.trim().is_empty() {
1148 Some(None)
1149 } else {
1150 match option.parse::<u32>() {
1151 Ok(number) => Some(Some(number)),
1152 Err(_) => Some(None)
1153 }
1154 }
1155 } else {
1156 None
1157 };
1158 let encoding = if let Some(encoding) = options.remove("encoding") {
1159 Some(encoding)
1160 } else {
1161 None
1162 };
1163 let tab_width = if let Some(option) = options.remove("tab-width") {
1164 match option.parse::<u32>() {
1165 Ok(number) => Some(number),
1166 Err(_) => None
1167 }
1168 } else {
1169 None
1170 };
1171 let name = if let Some(option) = options.remove("name") {
1172 Some(option)
1173 } else {
1174 None
1175 };
1176 let class = if let Some(option) = options.remove("class") {
1177 Some(option)
1178 } else {
1179 None
1180 };
1181
1182 (start_line, end_line, start_after, end_before, literal, code, number_lines, encoding, tab_width, name, class)
1183
1184 } else {
1185 (None, None, None, None, false, None, None, None, None, None, None)
1186 };
1187
1188 let include_node_data = TreeNodeType::Include {
1189 uri: uri,
1190 start_line: start_line,
1191 end_line: end_line,
1192 start_after: start_after,
1193 end_before: end_before,
1194 literal: literal,
1195 code: code,
1196 number_lines: number_lines,
1197 encoding: encoding,
1198 tab_width: tab_width,
1199 name: name,
1200 class: class
1201 };
1202
1203 doctree = match doctree.push_data(include_node_data) {
1204 Ok(tree) => tree,
1205 Err(tree) => return TransitionResult::Failure {
1206 message: format!("Node insertion error on line {}.", line_cursor.sum_total()),
1207 doctree: tree
1208 }
1209 };
1210
1211 TransitionResult::Success {
1212 doctree: doctree,
1213 push_or_pop: PushOrPop::Neither,
1214 line_advance: LineAdvance::None
1215 }
1216}
1217
1218pub fn parse_raw() {
1219 todo!()
1220}
1221
1222pub fn parse_class(
1223 src_lines: &Vec<String>,
1224 mut doctree: DocTree,
1225 line_cursor: &mut LineCursor,
1226 first_indent: usize,
1227 body_indent: usize,
1228 empty_after_marker: bool,
1229 section_level: usize,
1230) -> TransitionResult {
1231 let classes = if let Some(classes) = scan_directive_arguments(
1232 src_lines,
1233 line_cursor,
1234 body_indent,
1235 Some(first_indent),
1236 empty_after_marker,
1237 ) {
1238 classes
1239 .iter()
1240 .filter(|s| !s.is_empty())
1241 .map(|s| s.to_string())
1242 .collect::<Vec<String>>()
1243 } else {
1244 return TransitionResult::Failure {
1245 message: format!(
1246 "Class directive on line {} doesn't provide any classes. Computer says no...",
1247 line_cursor.sum_total()
1248 ),
1249 doctree: doctree,
1250 };
1251 };
1252
1253 let class_node = TreeNodeType::Class {
1254 body_indent: body_indent,
1255 classes: classes,
1256 };
1257
1258 doctree = match doctree.push_data_and_focus(class_node) {
1259 Ok(tree) => tree,
1260 Err(tree) => {
1261 return TransitionResult::Failure {
1262 message: format!(
1263 "Failed to push class node to tree on line {}...",
1264 line_cursor.sum_total()
1265 ),
1266 doctree: tree,
1267 }
1268 }
1269 };
1270
1271 let (lines, offset) = if let IndentedBlockResult::Ok {lines, minimum_indent, offset, blank_finish } = Parser::read_indented_block(
1272 src_lines,
1273 line_cursor.relative_offset(),
1274 false,
1275 true,
1276 Some(body_indent),
1277 None,
1278 false,
1279 ) {
1280 (lines, offset)
1281 } else {
1282 return TransitionResult::Failure {
1283 message: format!(
1284 "Could not parse class contents starting from line {}. Computer says no...",
1285 line_cursor.sum_total()
1286 ),
1287 doctree: doctree,
1288 };
1289 };
1290
1291 let (doctree, nested_state_stack) = match Parser::new(
1292 lines,
1293 doctree,
1294 body_indent,
1295 line_cursor.sum_total(),
1296 State::Body,
1297 section_level,
1298 ).parse() {
1299 ParsingResult::EOF {
1300 doctree,
1301 state_stack,
1302 } => (doctree, state_stack),
1303 ParsingResult::EmptyStateStack {
1304 doctree,
1305 state_stack,
1306 } => (doctree, state_stack),
1307 ParsingResult::Failure { message, doctree } => {
1308 return TransitionResult::Failure {
1309 message: format!(
1310 "Error when parsing a class on line {}: {}",
1311 line_cursor.sum_total(),
1312 message
1313 ),
1314 doctree: doctree,
1315 }
1316 }
1317 };
1318
1319 TransitionResult::Success {
1320 doctree: doctree,
1321 push_or_pop: PushOrPop::Push(nested_state_stack),
1322 line_advance: LineAdvance::None,
1323 }
1324}
1325
1326pub fn parse_role() {
1327 todo!()
1328}
1329
1330pub fn parse_default_role() {
1331 todo!()
1332}
1333
1334pub fn parse_title() {
1335 todo!()
1336}
1337
1338pub fn restucturetext_test_directive() {
1339 todo!()
1340}
1341
1342pub fn parse_sphinx_toctree() {
1347 todo!()
1348}
1349
1350pub fn parse_sphinx_versionadded() {
1351 todo!()
1352}
1353
1354pub fn parse_sphinx_versionchanged() {
1355 todo!()
1356}
1357
1358pub fn parse_sphinx_deprecated() {
1359 todo!()
1360}
1361
1362pub fn parse_sphinx_seealso() {
1363 todo!()
1364}
1365
1366pub fn parse_sphinx_centered() {
1367 todo!()
1368}
1369
1370pub fn parse_sphinx_hlist() {
1371 todo!()
1372}
1373
1374pub fn parse_sphinx_highlight() {
1375 todo!()
1376}
1377
1378pub fn parse_sphinx_code_block(
1381 src_lines: &Vec<String>,
1382 mut doctree: DocTree,
1383 line_cursor: &mut LineCursor,
1384 base_indent: usize,
1385 empty_after_marker: bool,
1386 body_indent: usize,
1387 first_indent: Option<usize>,
1388) -> TransitionResult {
1389 let formal_language = if let Some(arg) = scan_directive_arguments(
1391 src_lines,
1392 line_cursor,
1393 body_indent,
1394 first_indent,
1395 empty_after_marker,
1396 ) {
1397 arg.join("")
1398 } else {
1399 String::from("python") };
1401
1402 let (linenos, lineno_start, emphasize_lines, caption, name, dedent, force) =
1404 if let Some(mut settings) = scan_directive_options(
1405 src_lines, line_cursor, body_indent
1406 ){
1407 let mut linenos = if let Some(linenos) = settings.remove("linenos") {
1408 true
1409 } else {
1410 false
1411 };
1412 let lineno_start = if let Some(start_line) = settings.remove("lineno-start") {
1413 if let Ok(number) = start_line.parse::<usize>() {
1414 linenos = true;
1415 Some(number)
1416 } else {
1417 None
1418 }
1419 } else {
1420 None
1421 };
1422 let emphasize_lines = if let Some(line_numbers) = settings.remove("emphasize-lines")
1423 {
1424 let emph_lines = line_numbers
1425 .split(",")
1426 .filter(|s| !s.trim().is_empty())
1427 .map(|s| s.trim())
1428 .filter_map(|s| s.parse::<usize>().ok())
1429 .collect::<Vec<usize>>();
1430
1431 Some(emph_lines)
1432 } else {
1433 None
1434 };
1435 let caption = settings.remove("caption");
1436 let name = if let Some(refname) = settings.remove("name") {
1437 Some(crate::common::normalize_refname(&refname))
1438 } else {
1439 None
1440 };
1441 let dedent = if let Some(dedent) = settings.remove("dedent") {
1442 if let Ok(dedent) = dedent.parse::<usize>() {
1443 Some(dedent)
1444 } else {
1445 None
1446 }
1447 } else {
1448 None
1449 };
1450 let force = if let Some(force) = settings.remove("force") {
1451 true
1452 } else {
1453 false
1454 };
1455
1456 (
1457 linenos,
1458 lineno_start,
1459 emphasize_lines,
1460 caption,
1461 name,
1462 dedent,
1463 force,
1464 )
1465 } else {
1466 (false, None, None, None, None, None, false)
1467 };
1468
1469 let (code_text, offset) = match Parser::read_indented_block(
1472 src_lines,
1473 line_cursor.relative_offset(),
1474 false,
1475 true,
1476 Some(body_indent),
1477 None,
1478 false,
1479 ) {
1480 IndentedBlockResult::Ok {mut lines, minimum_indent, offset, blank_finish } => {
1481 lines = lines
1483 .iter()
1484 .skip_while(|line| line.is_empty())
1485 .map(|s| s.to_string())
1486 .collect();
1487
1488 while let Some(line) = lines.last_mut() {
1490 if line.is_empty() {
1491 lines.pop();
1492 } else {
1493 break;
1494 }
1495 }
1496
1497 (lines.join("\n") + "\n", offset)
1498 }
1499 _ => {
1500 return TransitionResult::Failure {
1501 message: format!(
1502 "Error when parsing a Sphinx code block on line {}.",
1503 line_cursor.sum_total(),
1504 ),
1505 doctree: doctree,
1506 }
1507 }
1508 };
1509
1510 let code_block_data = TreeNodeType::SphinxCodeBlock {
1511 language: formal_language,
1512 linenos: linenos,
1513 lineno_start: lineno_start,
1514 emphasize_lines: emphasize_lines,
1515 caption: caption,
1516 name: name,
1517 dedent: dedent,
1518 force: force,
1519 code_text: code_text,
1520 };
1521
1522 doctree = match doctree.push_data(code_block_data) {
1523 Ok(tree) => tree,
1524 Err(tree) => {
1525 return TransitionResult::Failure {
1526 message: format!(
1527 "Erro when parsing Sphinx code block on line {}. Computer says no...",
1528 line_cursor.sum_total()
1529 ),
1530 doctree: tree,
1531 }
1532 }
1533 };
1534
1535 TransitionResult::Success {
1536 doctree: doctree,
1537 push_or_pop: PushOrPop::Neither,
1538 line_advance: LineAdvance::Some(offset),
1539 }
1540}
1541
1542pub fn parse_sphinx_literalinclude() {
1543 todo!()
1544}
1545
1546pub fn parse_sphinx_glossary() {
1547 todo!()
1548}
1549
1550pub fn parse_sphinx_sectionauthor() {
1551 todo!()
1552}
1553
1554pub fn parse_sphinx_codeauthor() {
1555 todo!()
1556}
1557
1558pub fn parse_sphinx_index() {
1559 todo!()
1560}
1561
1562pub fn parse_sphinx_only(
1563 src_lines: &Vec<String>,
1564 mut doctree: DocTree,
1565 line_cursor: &mut LineCursor,
1566 empty_after_marker: bool,
1567 first_indent: usize,
1568 body_indent: usize,
1569) -> TransitionResult {
1570
1571
1572 const ALWAYS_DEFINED_TAGS: &[&str] = &["html", "latex", "text"];
1578
1579 let expression = match scan_directive_arguments(src_lines, line_cursor, body_indent, Some(first_indent), empty_after_marker) {
1580 Some(lines) => lines.join(" "),
1581 None => String::new()
1582 };
1583
1584 if expression.is_empty() {
1585 return TransitionResult::Failure {
1586 message: format!(
1587 r#"The expression of an "only" Sphinx directive on line {} should not be empty. Computer says no..."#,
1588 line_cursor.sum_total()
1589 ),
1590 doctree: doctree,
1591 };
1592 }
1593
1594 let only_node = TreeNodeType::SphinxOnly {
1595 expression: expression,
1596 body_indent: body_indent,
1597 };
1598
1599 doctree = match doctree.push_data_and_focus(only_node) {
1600 Ok(tree) => tree,
1601 Err(tree) => {
1602 return TransitionResult::Failure {
1603 message: format!(
1604 "Node insertion error on line {}. Computer says no...",
1605 line_cursor.sum_total()
1606 ),
1607 doctree: tree,
1608 }
1609 }
1610 };
1611
1612 TransitionResult::Success {
1613 doctree: doctree,
1614 push_or_pop: PushOrPop::Push(vec![State::Body]),
1615 line_advance: LineAdvance::Some(1),
1616 }
1617}
1618
1619pub fn parse_sphinx_tabularcolumns() {
1620 todo!()
1621}
1622
1623pub fn parse_sphinx_math_block() {
1624 todo!()
1625}
1626
1627pub fn parse_sphinx_productionlist() {
1628 todo!()
1629}
1630
1631pub fn parse_aplus_questionnaire(
1636 src_lines: &Vec<String>,
1637 mut doctree: DocTree,
1638 line_cursor: &mut LineCursor,
1639 base_indent: usize,
1640 empty_after_marker: bool,
1641 first_indent: usize,
1642 body_indent: usize,
1643) -> TransitionResult {
1644 let (key, difficulty, max_points): (String, String, String) = match scan_directive_arguments(
1645 src_lines,
1646 line_cursor,
1647 body_indent,
1648 Some(first_indent),
1649 empty_after_marker,
1650 ) {
1651 Some(lines) => aplus_key_difficulty_and_max_points(lines.join(" ").as_str(), line_cursor),
1652 None => return TransitionResult::Failure {
1653 message: format!(
1654 "A+ questionnaire on line {} was not given arguments. Computer says no...",
1655 line_cursor.sum_total()
1656 ),
1657 doctree: doctree,
1658 }
1659 };
1660
1661 let (
1662 submissions,
1663 points_to_pass,
1664 feedback,
1665 title,
1666 no_override,
1667 pick_randomly,
1668 preserve_questions_between_attempts,
1669 category,
1670 status,
1671 reveal_model_at_max_submissions,
1672 show_model,
1673 allow_assistant_viewing,
1674 allow_assistant_grading,
1675 ) = if let Some(mut options) = scan_directive_options(src_lines, line_cursor, body_indent) {
1676 (
1677 options.remove("submissions"),
1678 options.remove("points-to-pass"),
1679 options.remove("feedback"),
1680 options.remove("title"),
1681 options.remove("no_override"),
1682 options.remove("pick_randomly"),
1683 options.remove("preserve-questions-between-attempts"),
1684 options.remove("category"),
1685 options.remove("status"),
1686 options.remove("reveal-model-at-max-submissions"),
1687 options.remove("show-model"),
1688 options.remove("allow-assistant-viewing"),
1689 options.remove("allow-assistant-grading"),
1690 )
1691 } else {
1692 (
1693 None, None, None, None, None, None, None, None, None, None, None, None, None,
1694 )
1695 };
1696
1697 use crate::common::QuizPoints;
1698
1699 let questionnaire_node = TreeNodeType::AplusQuestionnaire {
1700 body_indent: body_indent,
1701 key: key,
1702 difficulty: if difficulty.is_empty() {
1703 None
1704 } else {
1705 Some(difficulty)
1706 },
1707 max_points: if let Ok(result) = max_points.parse::<QuizPoints>() {
1708 Some(result)
1709 } else {
1710 None
1711 },
1712 points_from_children: 0,
1713 submissions: submissions,
1714 points_to_pass: points_to_pass,
1715 feedback: feedback,
1716 title: title,
1717 no_override: no_override,
1718 pick_randomly: pick_randomly,
1719 preserve_questions_between_attempts: preserve_questions_between_attempts,
1720 category: category,
1721 status: status,
1722 reveal_model_at_max_submissions: reveal_model_at_max_submissions,
1723 show_model: show_model,
1724 allow_assistant_viewing: allow_assistant_viewing,
1725 allow_assistant_grading: allow_assistant_grading,
1726 };
1727
1728 doctree = match doctree.push_data_and_focus(questionnaire_node) {
1729 Ok(tree) => tree,
1730 Err(tree) => {
1731 return TransitionResult::Failure {
1732 message: format!(
1733 "Node insertion error on line {}. Computer says no...",
1734 line_cursor.sum_total()
1735 ),
1736 doctree: tree,
1737 }
1738 }
1739 };
1740
1741 TransitionResult::Success {
1742 doctree: doctree,
1743 push_or_pop: PushOrPop::Push(vec![State::AplusQuestionnaire]),
1744 line_advance: LineAdvance::None,
1745 }
1746}
1747
1748pub fn parse_aplus_pick_one(
1750 src_lines: &Vec<String>,
1751 mut doctree: DocTree,
1752 line_cursor: &mut LineCursor,
1753 first_indent: usize,
1754 body_indent: usize,
1755 empty_after_marker: bool,
1756) -> TransitionResult {
1757
1758
1759 const APLUS_PICK_ONE_CHOICE_PATTERN: &'static str =
1766 r"^(\s*)(?P<pre_selected>\+)?(?P<correct>\*)?(?P<label>\S+)\.[ ]+(?P<answer>.+)";
1767 const APLUS_PICK_HINT_PATTERN: &'static str =
1768 r"^(\s*)(?P<show_not_answered>!)?(?P<label>\S+)[ ]*§[ ]*(?P<hint>.+)";
1769
1770 use regex::{Captures, Regex};
1771
1772 lazy_static::lazy_static! {
1773 static ref CHOICE_RE: Regex = Regex::new(APLUS_PICK_ONE_CHOICE_PATTERN).unwrap();
1774 static ref HINT_RE: Regex = Regex::new(APLUS_PICK_HINT_PATTERN).unwrap();
1775 }
1776
1777 use crate::common::QuizPoints;
1780
1781 let points: QuizPoints = match scan_directive_arguments(
1782 src_lines,
1783 line_cursor,
1784 body_indent,
1785 Some(first_indent),
1786 empty_after_marker,
1787 ) {
1788 Some(lines) => match lines.join(" ").as_str().parse() {
1789 Ok(points) => points,
1790 Err(e) => return TransitionResult::Failure {
1791 message: format!("Quiz question points preceding line {} could not be parsed into an integer. Computer says no...", line_cursor.sum_total()),
1792 doctree: doctree
1793 }
1794 }
1795 None => return TransitionResult::Failure {
1796 message: format!(
1797 "No points provided for pick-one question on line {}. Computer says no...",
1798 line_cursor.sum_total()
1799 ),
1800 doctree: doctree,
1801 }
1802 };
1803
1804 if let TreeNodeType::AplusQuestionnaire {
1805 points_from_children,
1806 ..
1807 } = doctree.mut_node_data() {
1808 *points_from_children += points;
1809 }
1810
1811 let (class, required, key, dropdown) = if let Some(mut options) =
1814 scan_directive_options
1815 (src_lines, line_cursor, body_indent)
1816 {
1817 (
1818 options.remove("class"),
1819 options.remove("required"),
1820 options.remove("key"),
1821 options.remove("dropdown"),
1822 )
1823 } else {
1824 (None, None, None, None)
1825 };
1826
1827 Parser::skip_empty_lines(src_lines, line_cursor);
1828
1829 let pick_one_node = TreeNodeType::AplusPickOne {
1832 body_indent: body_indent,
1833 class: class,
1834 points: points,
1835 required: if required.is_some() { true } else { false },
1836 key: key,
1837 dropdown: if dropdown.is_some() { true } else { false },
1838 };
1839
1840 doctree = match doctree.push_data_and_focus(pick_one_node) {
1841 Ok(tree) => tree,
1842 Err(tree) => {
1843 return TransitionResult::Failure {
1844 message: format!(
1845 "Node insertion error on line {}. Computer says no...",
1846 line_cursor.sum_total()
1847 ),
1848 doctree: tree,
1849 }
1850 }
1851 };
1852
1853 Parser::skip_empty_lines(src_lines, line_cursor);
1856
1857 let start_line = match src_lines.get(line_cursor.relative_offset()) {
1858 Some(line) => line,
1859 None => return TransitionResult::Failure {
1860 message: format!(
1861 "Input overflow on line {} when parsing pick-one assignment. Computer says no...",
1862 line_cursor.sum_total()
1863 ),
1864 doctree: doctree
1865 }
1866 };
1867
1868 let assignment_inline_nodes: Vec<TreeNodeType> = if !CHOICE_RE.is_match(start_line) {
1869 let (block_lines, offset) = match Parser::read_text_block(src_lines, line_cursor.relative_offset(), true, true, Some(body_indent),true) {
1870 TextBlockResult::Ok {lines, offset } => (lines, offset),
1871 TextBlockResult::Err { lines, offset } => return TransitionResult::Failure {
1872 message: format!("Could not read pick-one assignment lines starting on line {}. Computer says no...", line_cursor.sum_total()),
1873 doctree: doctree
1874 }
1875 };
1876 let inline_nodes = match Parser::inline_parse(block_lines.join("\n"), None, line_cursor) {
1877 InlineParsingResult::Nodes(nodes) => nodes,
1878 _ => return TransitionResult::Failure {
1879 message: format!("Could not parse pick-one assignment for inline nodes on line {}. Computer says no...", line_cursor.sum_total()),
1880 doctree: doctree
1881 }
1882 };
1883
1884 line_cursor.increment_by(1);
1885
1886 inline_nodes
1887 } else {
1888 Vec::new()
1889 };
1890
1891 Parser::skip_empty_lines(src_lines, line_cursor);
1894
1895 if !assignment_inline_nodes.is_empty() {
1896 let assignment_node = TreeNodeType::Paragraph {
1897 indent: body_indent,
1898 };
1899 doctree = match doctree.push_data_and_focus(assignment_node) {
1900 Ok(tree) => tree,
1901 Err(tree) => {
1902 return TransitionResult::Failure {
1903 message: format!(
1904 "Node insertion error on line {}. Computer says no...",
1905 line_cursor.sum_total()
1906 ),
1907 doctree: tree,
1908 }
1909 }
1910 };
1911 for node in assignment_inline_nodes {
1912 doctree = match doctree.push_data(node) {
1913 Ok(tree) => tree,
1914 Err(tree) => {
1915 return TransitionResult::Failure {
1916 message: format!(
1917 "Node insertion error on line {}. Computer says no...",
1918 line_cursor.sum_total()
1919 ),
1920 doctree: tree,
1921 }
1922 }
1923 };
1924 }
1925 doctree = doctree.focus_on_parent()
1926 }
1927
1928 Parser::skip_empty_lines(src_lines, line_cursor);
1929
1930 doctree = match doctree.push_data_and_focus(TreeNodeType::AplusPickChoices {
1933 body_indent: body_indent,
1934 }) {
1935 Ok(tree) => tree,
1936 Err(tree) => {
1937 return TransitionResult::Failure {
1938 message: format!(
1939 "Node insertion error on line {}. Computer says no...",
1940 line_cursor.sum_total()
1941 ),
1942 doctree: tree,
1943 }
1944 }
1945 };
1946
1947 while let Some(current_line) = src_lines.get(line_cursor.relative_offset()) {
1948 let indent = current_line
1949 .chars()
1950 .take_while(|c| c.is_whitespace())
1951 .count();
1952
1953 if indent != body_indent {
1954 break;
1955 }
1956
1957 let captures: Captures = if let Some(capts) = CHOICE_RE.captures(current_line) {
1958 capts
1959 } else {
1960 break;
1961 };
1962
1963 let label = captures.name("label").unwrap().as_str().to_string();
1964 let pre_selected = captures.name("pre_selected");
1965 let correct = captures.name("correct");
1966 let answer = if let Some(capture) = captures.name("answer") {
1967 capture.as_str()
1968 } else {
1969 ""
1970 };
1971
1972 if answer.trim().is_empty() {
1973 return TransitionResult::Failure {
1974 message: format!("Discovered a pick-one answer without content on line {}. Computer says no...", line_cursor.sum_total()),
1975 doctree: doctree
1976 };
1977 }
1978
1979 let answer_nodes: Vec<TreeNodeType> = match Parser::inline_parse(answer.to_string(), None, line_cursor) {
1980 InlineParsingResult::Nodes(nodes) => nodes,
1981 _ => return TransitionResult::Failure {
1982 message: format!("Could not parse pick-one answer on line {} for inline nodes. Computer says no...", line_cursor.sum_total()),
1983 doctree: doctree
1984 }
1985 };
1986
1987 let choice_node = TreeNodeType::AplusPickChoice {
1988 label: label,
1989 is_pre_selected: pre_selected.is_some(),
1990 is_correct: correct.is_some(),
1991 is_neutral: false, };
1993
1994 doctree = match doctree.push_data_and_focus(choice_node) {
1995 Ok(tree) => tree,
1996 Err(tree) => {
1997 return TransitionResult::Failure {
1998 message: format!(
1999 "Node insertion error on line {}. Computer says no...",
2000 line_cursor.sum_total()
2001 ),
2002 doctree: tree,
2003 }
2004 }
2005 };
2006 for node in answer_nodes {
2007 doctree = match doctree.push_data(node) {
2008 Ok(tree) => tree,
2009 Err(tree) => {
2010 return TransitionResult::Failure {
2011 message: format!(
2012 "Node insertion error on line {}. Computer says no...",
2013 line_cursor.sum_total()
2014 ),
2015 doctree: tree,
2016 }
2017 }
2018 };
2019 }
2020 doctree = doctree.focus_on_parent();
2021
2022 line_cursor.increment_by(1);
2023 }
2024
2025 if doctree.n_of_children() == 0 {
2026 return TransitionResult::Failure {
2027 message: format!(
2028 "Found no choices for pick-one question on line {}. Computer says no...",
2029 line_cursor.sum_total()
2030 ),
2031 doctree: doctree,
2032 };
2033 }
2034
2035 doctree = doctree.focus_on_parent();
2036
2037 Parser::skip_empty_lines(src_lines, line_cursor);
2040
2041 doctree = match doctree.push_data_and_focus(TreeNodeType::AplusQuestionnaireHints {
2042 body_indent: body_indent,
2043 }) {
2044 Ok(tree) => tree,
2045 Err(tree) => {
2046 return TransitionResult::Failure {
2047 message: format!(
2048 "Node insertion error on line {}. Computer says no...",
2049 line_cursor.sum_total()
2050 ),
2051 doctree: tree,
2052 }
2053 }
2054 };
2055
2056 while let Some(current_line) = src_lines.get(line_cursor.relative_offset()) {
2057 let indent = if !current_line.is_empty() {
2058 current_line
2059 .chars()
2060 .take_while(|c| c.is_whitespace())
2061 .count()
2062 } else {
2063 body_indent
2064 };
2065
2066 if indent != body_indent {
2067 break;
2068 }
2069
2070 let captures = if let Some(capts) = HINT_RE.captures(current_line) {
2071 capts
2072 } else {
2073 break;
2074 };
2075
2076 let show_not_answered = captures.name("show_not_answered");
2077 let label = match captures.name("label") {
2078 Some(label) => label.as_str().to_string(),
2079 None => {
2080 return TransitionResult::Failure {
2081 message: format!(
2082 "No enumerator for pick-one hint on line {}. Computer says no...",
2083 line_cursor.sum_total()
2084 ),
2085 doctree: doctree,
2086 }
2087 }
2088 };
2089 let hint: &str = if let Some(hint) = captures.name("hint") {
2090 hint.as_str().trim()
2091 } else {
2092 return TransitionResult::Failure {
2093 message: format!(
2094 "No hint text for pick-one hint on line {}. Computer says no...",
2095 line_cursor.sum_total()
2096 ),
2097 doctree: doctree,
2098 };
2099 };
2100
2101 if hint.is_empty() {
2102 return TransitionResult::Failure {
2103 message: format!(
2104 "Empty hint text for hint on line {}. Computer says no...",
2105 line_cursor.sum_total()
2106 ),
2107 doctree: doctree,
2108 };
2109 }
2110
2111 let hint_nodes: Vec<TreeNodeType> = match Parser::inline_parse(hint.to_string(), None, line_cursor) {
2112 InlineParsingResult::Nodes(nodes) => nodes,
2113 _ => return TransitionResult::Failure {
2114 message: format!("Could not parse pick-one answer on line {} for inline nodes. Computer says no...", line_cursor.sum_total()),
2115 doctree: doctree
2116 }
2117 };
2118
2119 if hint_nodes.is_empty() {
2120 return TransitionResult::Failure {
2121 message: format!(
2122 "No inline nodes found for pick-one hint on line {}. Computer says no...",
2123 line_cursor.sum_total()
2124 ),
2125 doctree: doctree,
2126 };
2127 }
2128
2129 let hint_node = TreeNodeType::AplusQuestionnaireHint {
2130 label: label,
2131 show_when_not_selected: show_not_answered.is_some(),
2132 question_type: crate::common::AplusQuestionnaireType::PickOne,
2133 };
2134
2135 doctree = match doctree.push_data_and_focus(hint_node) {
2136 Ok(tree) => tree,
2137 Err(tree) => {
2138 return TransitionResult::Failure {
2139 message: format!(
2140 "Node insertion error on line {}. Computer says no...",
2141 line_cursor.sum_total()
2142 ),
2143 doctree: tree,
2144 }
2145 }
2146 };
2147 for node in hint_nodes {
2148 doctree = match doctree.push_data(node) {
2149 Ok(tree) => tree,
2150 Err(tree) => {
2151 return TransitionResult::Failure {
2152 message: format!(
2153 "Node insertion error on line {}. Computer says no...",
2154 line_cursor.sum_total()
2155 ),
2156 doctree: tree,
2157 }
2158 }
2159 };
2160 }
2161 doctree = doctree.focus_on_parent();
2162
2163 line_cursor.increment_by(1);
2164 }
2165
2166 Parser::skip_empty_lines(src_lines, line_cursor);
2167
2168 doctree = doctree.focus_on_parent(); doctree = doctree.focus_on_parent(); TransitionResult::Success {
2174 doctree: doctree,
2175 push_or_pop: PushOrPop::Neither,
2176 line_advance: LineAdvance::None,
2177 }
2178}
2179
2180pub fn parse_aplus_pick_any(
2182 src_lines: &Vec<String>,
2183 mut doctree: DocTree,
2184 line_cursor: &mut LineCursor,
2185 first_indent: usize,
2186 body_indent: usize,
2187 empty_after_marker: bool,
2188) -> TransitionResult {
2189
2190 const APLUS_PICK_ANY_CHOICE_PATTERN: &'static str = r"^(\s*)(?P<pre_selected>\+)?(?:(?P<neutral>\?)|(?P<correct>\*))?(?P<label>\S+)\.[ ]+(?P<answer>.+)";
2191 const APLUS_PICK_HINT_PATTERN: &'static str =
2192 r"^(\s*)(?P<show_not_answered>!)?(?P<label>\S+)[ ]*§[ ]*(?P<hint>.+)";
2193
2194 lazy_static::lazy_static! {
2195 static ref CHOICE_RE: regex::Regex = regex::Regex::new(APLUS_PICK_ANY_CHOICE_PATTERN).unwrap();
2196 static ref HINT_RE: regex::Regex = regex::Regex::new(APLUS_PICK_HINT_PATTERN).unwrap();
2197 }
2198
2199 let points: crate::common::QuizPoints = match scan_directive_arguments(
2200 src_lines,
2201 line_cursor,
2202 body_indent,
2203 Some(first_indent),
2204 empty_after_marker,
2205 ) {
2206 Some(lines) => match lines.join(" ").as_str().parse() {
2207 Ok(points) => points,
2208 Err(e) => return TransitionResult::Failure {
2209 message: format!(
2210 "Quiz question points preceding line {} could not be parsed into an integer. Computer says no...",
2211 line_cursor.sum_total()
2212 ),
2213 doctree: doctree
2214 }
2215 },
2216 None => return TransitionResult::Failure {
2217 message: format!(
2218 "No points provided for pick-any question on line {}. Computer says no...",
2219 line_cursor.sum_total()
2220 ),
2221 doctree: doctree,
2222 }
2223 };
2224
2225 if let TreeNodeType::AplusQuestionnaire {
2226 points_from_children,
2227 ..
2228 } = doctree.mut_node_data() {
2229 *points_from_children += points;
2230 }
2231
2232 let (
2233 class,
2234 required,
2235 key,
2236 partial_points,
2237 randomized,
2238 correct_count,
2239 preserve_questions_between_attempts,
2240 ) = if let Some(mut options) = scan_directive_options
2241 (src_lines, line_cursor, body_indent) {
2242 (
2243 options.remove("class"),
2244 options.remove("required"),
2245 options.remove("key"),
2246 options.remove("partial-points"),
2247 options.remove("randomized"),
2248 options.remove("correct-count"),
2249 options.remove("preserve-questions-between-attempts"),
2250 )
2251 } else {
2252 (None, None, None, None, None, None, None)
2253 };
2254
2255 let pick_any_node = TreeNodeType::AplusPickAny {
2256 body_indent: body_indent,
2257 points: points,
2258 class: class,
2259 required: if required.is_some() { true } else { false },
2260 key: key,
2261 partial_points: if partial_points.is_some() {
2262 true
2263 } else {
2264 false
2265 },
2266 randomized: if randomized.is_some() && correct_count.is_some() {
2267 true
2268 } else {
2269 false
2270 },
2271 correct_count: if randomized.is_some() && correct_count.is_some() {
2272 if let Ok(result) = correct_count.unwrap().parse() {
2273 Some(result)
2274 } else {
2275 return TransitionResult::Failure {
2276 message: format!("No correct count provided for pick-any on line {} with randomization activated. Computer says no...", line_cursor.sum_total()),
2277 doctree: doctree
2278 };
2279 }
2280 } else {
2281 None
2282 },
2283 preserve_questions_between_attempts: if preserve_questions_between_attempts.is_some() {
2284 true
2285 } else {
2286 false
2287 },
2288 };
2289
2290 doctree = match doctree.push_data_and_focus(pick_any_node) {
2291 Ok(tree) => tree,
2292 Err(tree) => {
2293 return TransitionResult::Failure {
2294 message: format!(
2295 "Node insertion error on line {}. Computer says no...",
2296 line_cursor.sum_total()
2297 ),
2298 doctree: tree,
2299 }
2300 }
2301 };
2302
2303 Parser::skip_empty_lines(src_lines, line_cursor);
2306
2307 let start_line = match src_lines.get(line_cursor.relative_offset()) {
2308 Some(line) => line,
2309 None => return TransitionResult::Failure {
2310 message: format!(
2311 "Input overflow on line {} when parsing pick-any assignment. Computer says no...",
2312 line_cursor.sum_total()
2313 ),
2314 doctree: doctree
2315 }
2316 };
2317
2318 let assignment_inline_nodes: Vec<TreeNodeType> = if !CHOICE_RE.is_match(start_line) {
2319 let (block_lines, offset) = match Parser::read_text_block(src_lines, line_cursor.relative_offset(), true, true, Some(body_indent), true) {
2320 TextBlockResult::Ok {lines, offset } => (lines, offset),
2321 TextBlockResult::Err {lines, offset } => return TransitionResult::Failure {
2322 message: format!("Could not read pick-any assignment lines starting on line {}. Computer says no...", line_cursor.sum_total()),
2323 doctree: doctree
2324 }
2325 };
2326
2327 let inline_nodes = match Parser::inline_parse(block_lines.join("\n"), None, line_cursor) {
2328 InlineParsingResult::Nodes(nodes) => nodes,
2329 _ => return TransitionResult::Failure {
2330 message: format!("Could not parse pick-any assignment for inline nodes on line {}. Computer says no...", line_cursor.sum_total()),
2331 doctree: doctree
2332 }
2333 };
2334
2335 line_cursor.increment_by(1);
2336
2337 inline_nodes
2338 } else {
2339 Vec::new()
2340 };
2341
2342 if !assignment_inline_nodes.is_empty() {
2345 let assignment_node = TreeNodeType::Paragraph {
2346 indent: body_indent,
2347 };
2348 doctree = match doctree.push_data_and_focus(assignment_node) {
2349 Ok(tree) => tree,
2350 Err(tree) => {
2351 return TransitionResult::Failure {
2352 message: format!(
2353 "Node insertion error on line {}. Computer says no...",
2354 line_cursor.sum_total()
2355 ),
2356 doctree: tree,
2357 }
2358 }
2359 };
2360 for node in assignment_inline_nodes {
2361 doctree = match doctree.push_data(node) {
2362 Ok(tree) => tree,
2363 Err(tree) => {
2364 return TransitionResult::Failure {
2365 message: format!(
2366 "Node insertion error on line {}. Computer says no...",
2367 line_cursor.sum_total()
2368 ),
2369 doctree: tree,
2370 }
2371 }
2372 };
2373 }
2374 doctree = doctree.focus_on_parent()
2375 }
2376
2377 Parser::skip_empty_lines(src_lines, line_cursor);
2378
2379 doctree = match doctree.push_data_and_focus(TreeNodeType::AplusPickChoices {
2382 body_indent: body_indent,
2383 }) {
2384 Ok(tree) => tree,
2385 Err(tree) => {
2386 return TransitionResult::Failure {
2387 message: format!(
2388 "Node insertion error on line {}. Computer says no...",
2389 line_cursor.sum_total()
2390 ),
2391 doctree: tree,
2392 }
2393 }
2394 };
2395
2396 while let Some(current_line) = src_lines.get(line_cursor.relative_offset()) {
2397 let indent = current_line
2398 .chars()
2399 .take_while(|c| c.is_whitespace())
2400 .count();
2401
2402 if indent != body_indent {
2403 break;
2404 }
2405
2406 let captures: regex::Captures = if let Some(capts) = CHOICE_RE.captures(current_line) {
2407 capts
2408 } else {
2409 break;
2410 };
2411
2412 let pre_selected = captures.name("pre_selected");
2413 let correct = captures.name("correct");
2414 let neutral = captures.name("neutral");
2415
2416 let label = captures.name("label").unwrap().as_str().to_string();
2417 let answer = if let Some(capture) = captures.name("answer") {
2418 capture.as_str()
2419 } else {
2420 ""
2421 };
2422
2423 if answer.trim().is_empty() {
2424 return TransitionResult::Failure {
2425 message: format!("Discovered a pick-any answer without content on line {}. Computer says no...", line_cursor.sum_total()),
2426 doctree: doctree
2427 };
2428 }
2429
2430 let answer_nodes: Vec<TreeNodeType> = match Parser::inline_parse(answer.to_string(), None, line_cursor) {
2431 InlineParsingResult::Nodes(nodes) => nodes,
2432 _ => return TransitionResult::Failure {
2433 message: format!("Could not parse pick-any answer on line {} for inline nodes. Computer says no...", line_cursor.sum_total()),
2434 doctree: doctree
2435 }
2436 };
2437
2438 let choice_node = TreeNodeType::AplusPickChoice {
2439 label: label,
2440 is_pre_selected: pre_selected.is_some(),
2441 is_correct: correct.is_some(),
2442 is_neutral: neutral.is_some(),
2443 };
2444
2445 doctree = match doctree.push_data_and_focus(choice_node) {
2446 Ok(tree) => tree,
2447 Err(tree) => {
2448 return TransitionResult::Failure {
2449 message: format!(
2450 "Node insertion error on line {}. Computer says no...",
2451 line_cursor.sum_total()
2452 ),
2453 doctree: tree,
2454 }
2455 }
2456 };
2457 for node in answer_nodes {
2458 doctree = match doctree.push_data(node) {
2459 Ok(tree) => tree,
2460 Err(tree) => {
2461 return TransitionResult::Failure {
2462 message: format!(
2463 "Node insertion error on line {}. Computer says no...",
2464 line_cursor.sum_total()
2465 ),
2466 doctree: tree,
2467 }
2468 }
2469 };
2470 }
2471 doctree = doctree.focus_on_parent();
2472
2473 line_cursor.increment_by(1);
2474 }
2475
2476 if doctree.n_of_children() == 0 {
2477 return TransitionResult::Failure {
2478 message: format!(
2479 "Found no choices for pick-any question on line {}. Computer says no...",
2480 line_cursor.sum_total()
2481 ),
2482 doctree: doctree,
2483 };
2484 }
2485
2486 doctree = doctree.focus_on_parent();
2487
2488 Parser::skip_empty_lines(src_lines, line_cursor);
2491
2492 doctree = match doctree.push_data_and_focus(TreeNodeType::AplusQuestionnaireHints {
2493 body_indent: body_indent,
2494 }) {
2495 Ok(tree) => tree,
2496 Err(tree) => {
2497 return TransitionResult::Failure {
2498 message: format!(
2499 "Node insertion error on line {}. Computer says no...",
2500 line_cursor.sum_total()
2501 ),
2502 doctree: tree,
2503 }
2504 }
2505 };
2506
2507 while let Some(current_line) = src_lines.get(line_cursor.relative_offset()) {
2508 let indent = current_line
2509 .chars()
2510 .take_while(|c| c.is_whitespace())
2511 .count();
2512
2513 if indent != body_indent {
2514 break;
2515 }
2516
2517 let captures = if let Some(capts) = HINT_RE.captures(current_line) {
2518 capts
2519 } else {
2520 break;
2521 };
2522
2523 let show_not_answered = captures.name("show_not_answered");
2524 let label = match captures.name("label") {
2525 Some(enumerator) => enumerator.as_str().to_string(),
2526 None => {
2527 return TransitionResult::Failure {
2528 message: format!(
2529 "No label for pick-any hint on line {}. Computer says no...",
2530 line_cursor.sum_total()
2531 ),
2532 doctree: doctree,
2533 }
2534 }
2535 };
2536 let hint: &str = if let Some(hint) = captures.name("hint") {
2537 hint.as_str().trim()
2538 } else {
2539 return TransitionResult::Failure {
2540 message: format!(
2541 "No hint text for pick-any hint on line {}. Computer says no...",
2542 line_cursor.sum_total()
2543 ),
2544 doctree: doctree,
2545 };
2546 };
2547
2548 if hint.is_empty() {
2549 return TransitionResult::Failure {
2550 message: format!(
2551 "Empty hint text for hint on line {}. Computer says no...",
2552 line_cursor.sum_total()
2553 ),
2554 doctree: doctree,
2555 };
2556 }
2557
2558 let hint_nodes: Vec<TreeNodeType> = match Parser::inline_parse(hint.to_string(), None, line_cursor) {
2559 InlineParsingResult::Nodes(nodes) => nodes,
2560 _ => return TransitionResult::Failure {
2561 message: format!("Could not parse pick-any answer on line {} for inline nodes. Computer says no...", line_cursor.sum_total()),
2562 doctree: doctree
2563 }
2564 };
2565
2566 if hint_nodes.is_empty() {
2567 return TransitionResult::Failure {
2568 message: format!(
2569 "No inline nodes found for pick-any hint on line {}. Computer says no...",
2570 line_cursor.sum_total()
2571 ),
2572 doctree: doctree,
2573 };
2574 }
2575
2576 let hint_node = TreeNodeType::AplusQuestionnaireHint {
2577 label: label,
2578 show_when_not_selected: show_not_answered.is_some(),
2579 question_type: crate::common::AplusQuestionnaireType::PickAny,
2580 };
2581
2582 doctree = match doctree.push_data_and_focus(hint_node) {
2583 Ok(tree) => tree,
2584 Err(tree) => {
2585 return TransitionResult::Failure {
2586 message: format!(
2587 "Node insertion error on line {}. Computer says no...",
2588 line_cursor.sum_total()
2589 ),
2590 doctree: tree,
2591 }
2592 }
2593 };
2594 for node in hint_nodes {
2595 doctree = match doctree.push_data(node) {
2596 Ok(tree) => tree,
2597 Err(tree) => {
2598 return TransitionResult::Failure {
2599 message: format!(
2600 "Node insertion error on line {}. Computer says no...",
2601 line_cursor.sum_total()
2602 ),
2603 doctree: tree,
2604 }
2605 }
2606 };
2607 }
2608 doctree = doctree.focus_on_parent();
2609
2610 line_cursor.increment_by(1);
2611 }
2612
2613 Parser::skip_empty_lines(src_lines, line_cursor);
2614
2615 doctree = doctree.focus_on_parent(); doctree = doctree.focus_on_parent(); TransitionResult::Success {
2621 doctree: doctree,
2622 push_or_pop: PushOrPop::Neither,
2623 line_advance: LineAdvance::None,
2624 }
2625}
2626
2627pub fn parse_aplus_freetext(
2629 src_lines: &Vec<String>,
2630 mut doctree: DocTree,
2631 line_cursor: &mut LineCursor,
2632 first_indent: usize,
2633 body_indent: usize,
2634 empty_after_marker: bool,
2635) -> TransitionResult {
2636
2637 use crate::common::QuizPoints;
2638
2639 let (points, method_string) = if let Some(arg) = scan_directive_arguments(
2640 src_lines,
2641 line_cursor,
2642 body_indent,
2643 Some(first_indent),
2644 empty_after_marker,
2645 ) {
2646 let arg_string = arg.join(" ");
2647
2648 let mut arg_iter = arg_string.split_whitespace();
2649 let points_string: Option<&str> = arg_iter.next();
2650 let method_str = arg_iter.next();
2651
2652 let points: QuizPoints = if let Some(string) = points_string {
2653 if let Ok(result) = string.parse() {
2654 result
2655 } else {
2656 return TransitionResult::Failure {
2657 message: format!("Quiz freetext question points preceding line {} could not be parsed into an integer. Computer says no...", line_cursor.sum_total()),
2658 doctree: doctree
2659 };
2660 }
2661 } else {
2662 return TransitionResult::Failure {
2663 message: format!(
2664 "No points found for freetext on line {}. Computer says no...",
2665 line_cursor.sum_total()
2666 ),
2667 doctree: doctree,
2668 };
2669 };
2670 let method_string = if let Some(string) = method_str {
2671 string.to_string()
2672 } else {
2673 String::new()
2674 };
2675
2676 (points, method_string)
2677 } else {
2678 return TransitionResult::Failure {
2679 message: format!(
2680 "No points provided for freetext question on line {}. Computer says no...",
2681 line_cursor.sum_total()
2682 ),
2683 doctree: doctree,
2684 };
2685 };
2686
2687 if let TreeNodeType::AplusQuestionnaire {
2688 points_from_children,
2689 ..
2690 } = doctree.mut_node_data()
2691 {
2692 *points_from_children += points;
2693 }
2694
2695 let (class, required, key, length, height) = if let Some(mut options) = scan_directive_options
2696 (src_lines, line_cursor, body_indent) {
2697 (
2698 options.remove("class"),
2699 options.remove("required"),
2700 options.remove("key"),
2701 options.remove("length"),
2702 options.remove("height"),
2703 )
2704 } else {
2705 (None, None, None, None, None)
2706 };
2707
2708 let freetext_node = TreeNodeType::AplusFreeText {
2709 body_indent: body_indent,
2710 points: points,
2711 compare_method: method_string,
2712 model_answer: String::new(),
2713 class: class,
2714 required: required,
2715 key: key,
2716 length: length,
2717 height: height,
2718 };
2719
2720 doctree = match doctree.push_data_and_focus(freetext_node) {
2721 Ok(tree) => tree,
2722 Err(tree) => {
2723 return TransitionResult::Failure {
2724 message: format!(
2725 "Node insertion error on line {}. Computer says no...",
2726 line_cursor.sum_total()
2727 ),
2728 doctree: tree,
2729 }
2730 }
2731 };
2732
2733 Parser::skip_empty_lines(src_lines, line_cursor);
2734
2735 let assignment_inline_nodes: Vec<TreeNodeType> = {
2738 let (block_lines, offset) = match Parser::read_text_block(src_lines, line_cursor.relative_offset(), true, true, Some(body_indent), true) {
2739 TextBlockResult::Ok { lines, offset } => (lines, offset),
2740 TextBlockResult::Err { lines, offset } => return TransitionResult::Failure {
2741 message: format!("Could not read pick-any assignment lines starting on line {}. Computer says no...", line_cursor.sum_total()),
2742 doctree: doctree
2743 }
2744 };
2745
2746 let inline_nodes = match Parser::inline_parse(block_lines.join("\n"), None, line_cursor) {
2747 InlineParsingResult::Nodes(nodes) => nodes,
2748 _ => return TransitionResult::Failure {
2749 message: format!("Could not parse pick-any assignment for inline nodes on line {}. Computer says no...", line_cursor.sum_total()),
2750 doctree: doctree
2751 }
2752 };
2753
2754 line_cursor.increment_by(1);
2755
2756 inline_nodes
2757 };
2758
2759 let assignment_node = TreeNodeType::Paragraph {
2762 indent: body_indent,
2763 };
2764 doctree = match doctree.push_data_and_focus(assignment_node) {
2765 Ok(tree) => tree,
2766 Err(tree) => {
2767 return TransitionResult::Failure {
2768 message: format!(
2769 "Node insertion error on line {}. Computer says no...",
2770 line_cursor.sum_total()
2771 ),
2772 doctree: tree,
2773 }
2774 }
2775 };
2776 for node in assignment_inline_nodes {
2777 doctree = match doctree.push_data(node) {
2778 Ok(tree) => tree,
2779 Err(tree) => {
2780 return TransitionResult::Failure {
2781 message: format!(
2782 "Node insertion error on line {}. Computer says no...",
2783 line_cursor.sum_total()
2784 ),
2785 doctree: tree,
2786 }
2787 }
2788 };
2789 }
2790 doctree = doctree.focus_on_parent();
2791
2792 Parser::skip_empty_lines(src_lines, line_cursor);
2793
2794 if let Some(answer) = src_lines.get(line_cursor.relative_offset()) {
2797 let indent = answer.chars().take_while(|c| c.is_whitespace()).count();
2798 if indent != body_indent {
2799 return TransitionResult::Failure {
2800 message: format!("A+ freetext answer has incorrect indentation on line {}. Computer says no...", line_cursor.sum_total()),
2801 doctree: doctree
2802 };
2803 }
2804
2805 if let TreeNodeType::AplusFreeText { model_answer, .. } = doctree.mut_node_data() {
2806 model_answer.push_str(answer.trim());
2807 } else {
2808 return TransitionResult::Failure {
2809 message: format!("Not focused on A+ freetext node when reading its model answer on line {}? Computer says no...", line_cursor.sum_total()),
2810 doctree: doctree
2811 };
2812 }
2813
2814 line_cursor.increment_by(1);
2815 } else {
2816 return TransitionResult::Failure {
2817 message: format!("Tried scanning freetext question for correct answer but encountered end of input on line {}. Computer says no...", line_cursor.sum_total()),
2818 doctree: doctree
2819 };
2820 };
2821
2822 const APLUS_PICK_HINT_PATTERN: &'static str =
2824 r"^(\s*)(?P<show_not_answered>!)?(?P<label>.+)[ ]*§[ ]*(?P<hint>.+)";
2825 lazy_static::lazy_static! {
2826 static ref HINT_RE: regex::Regex = regex::Regex::new(APLUS_PICK_HINT_PATTERN).unwrap();
2827 }
2828
2829 Parser::skip_empty_lines(src_lines, line_cursor);
2830
2831 doctree = match doctree.push_data_and_focus(
2832 TreeNodeType::AplusQuestionnaireHints {
2833 body_indent: body_indent,
2834 }
2835 ) {
2836 Ok(tree) => tree,
2837 Err(tree) => {
2838 return TransitionResult::Failure {
2839 message: format!(
2840 "Node insertion error on line {}. Computer says no...",
2841 line_cursor.sum_total()
2842 ),
2843 doctree: tree,
2844 }
2845 }
2846 };
2847
2848 while let Some(current_line) = src_lines.get(line_cursor.relative_offset()) {
2849 let indent = current_line
2850 .chars()
2851 .take_while(|c| c.is_whitespace())
2852 .count();
2853
2854 if indent != body_indent {
2855 break;
2856 }
2857
2858 let captures = if let Some(capts) = HINT_RE.captures(current_line) {
2859 capts
2860 } else {
2861 break;
2862 };
2863
2864 let show_not_answered = captures.name("show_not_answered");
2865 let label = match captures.name("label") {
2866 Some(label) => label.as_str().trim().to_string(),
2867 None => {
2868 return TransitionResult::Failure {
2869 message: format!(
2870 "No text for freetext hint on line {}. Computer says no...",
2871 line_cursor.sum_total()
2872 ),
2873 doctree: doctree,
2874 }
2875 }
2876 };
2877 let hint: &str = if let Some(hint) = captures.name("hint") {
2878 hint.as_str().trim()
2879 } else {
2880 return TransitionResult::Failure {
2881 message: format!(
2882 "No hint text for freetext hint on line {}. Computer says no...",
2883 line_cursor.sum_total()
2884 ),
2885 doctree: doctree,
2886 };
2887 };
2888
2889 if hint.is_empty() {
2890 return TransitionResult::Failure {
2891 message: format!(
2892 "Empty hint text for hint on line {}. Computer says no...",
2893 line_cursor.sum_total()
2894 ),
2895 doctree: doctree,
2896 };
2897 }
2898
2899 let hint_nodes: Vec<TreeNodeType> = match Parser::inline_parse(hint.to_string(), None, line_cursor) {
2900 InlineParsingResult::Nodes(nodes) => nodes,
2901 _ => return TransitionResult::Failure {
2902 message: format!("Could not parse freetext hint on line {} for inline nodes. Computer says no...", line_cursor.sum_total()),
2903 doctree: doctree
2904 }
2905 };
2906
2907 if hint_nodes.is_empty() {
2908 return TransitionResult::Failure {
2909 message: format!(
2910 "No inline nodes found for freetext hint on line {}. Computer says no...",
2911 line_cursor.sum_total()
2912 ),
2913 doctree: doctree,
2914 };
2915 }
2916
2917 let hint_node = TreeNodeType::AplusQuestionnaireHint {
2918 label: label,
2919 show_when_not_selected: show_not_answered.is_some(),
2920 question_type: crate::common::AplusQuestionnaireType::FreeText,
2921 };
2922
2923 doctree = match doctree.push_data_and_focus(hint_node) {
2924 Ok(tree) => tree,
2925 Err(tree) => {
2926 return TransitionResult::Failure {
2927 message: format!(
2928 "Node insertion error on line {}. Computer says no...",
2929 line_cursor.sum_total()
2930 ),
2931 doctree: tree,
2932 }
2933 }
2934 };
2935 for node in hint_nodes {
2936 doctree = match doctree.push_data(node) {
2937 Ok(tree) => tree,
2938 Err(tree) => {
2939 return TransitionResult::Failure {
2940 message: format!(
2941 "Node insertion error on line {}. Computer says no...",
2942 line_cursor.sum_total()
2943 ),
2944 doctree: tree,
2945 }
2946 }
2947 };
2948 }
2949 doctree = doctree.focus_on_parent();
2950
2951 line_cursor.increment_by(1);
2952 }
2953
2954 Parser::skip_empty_lines(src_lines, line_cursor);
2955
2956 doctree = doctree.focus_on_parent(); doctree = doctree.focus_on_parent(); TransitionResult::Success {
2960 doctree: doctree,
2961 push_or_pop: PushOrPop::Neither,
2962 line_advance: LineAdvance::None,
2963 }
2964}
2965
2966pub fn parse_aplus_submit(
2967 src_lines: &Vec<String>,
2968 mut doctree: DocTree,
2969 line_cursor: &mut LineCursor,
2970 first_indent: usize,
2971 body_indent: usize,
2972 empty_after_marker: bool,
2973) -> TransitionResult {
2974
2975 let (key, difficulty, max_points): (String, String, String) = if let Some(arg) =
2976 scan_directive_arguments(
2977 src_lines,
2978 line_cursor,
2979 body_indent,
2980 Some(first_indent),
2981 empty_after_marker,
2982 ) {
2983 aplus_key_difficulty_and_max_points(arg.join(" ").as_str(), line_cursor)
2984 } else {
2985 return TransitionResult::Failure {
2986 message: format!(
2987 "A+ submit exercise on line {} was not given arguments. Computer says no...",
2988 line_cursor.sum_total()
2989 ),
2990 doctree: doctree,
2991 };
2992 };
2993
2994 Parser::skip_empty_lines(src_lines, line_cursor);
2995
2996 let (
2997 config,
2998 submissions,
2999 points_to_pass,
3000 class,
3001 title,
3002 category,
3003 status,
3004 ajax,
3005 allow_assistant_viewing,
3006 allow_assistant_grading,
3007 quiz,
3008 url,
3009 radar_tokenizer,
3010 radar_minimum_match_tokens,
3011 lti,
3012 lti_resource_link_id,
3013 lti_open_in_iframe,
3014 lti_aplus_get_and_post,
3015 ) = if let Some(mut options) = scan_directive_options(
3016 src_lines, line_cursor, body_indent
3017 ) {
3018 (
3019 options.remove("config"),
3020 options.remove("submissions"),
3021 options.remove("points-to-pass"),
3022 options.remove("class"),
3023 options.remove("title"),
3024 options.remove("category"),
3025 options.remove("status"),
3026 options.remove("ajax"),
3027 options.remove("allow-assistant-viewing"),
3028 options.remove("allow-assistant-grading"),
3029 options.remove("quiz"),
3030 options.remove("url"),
3031 options.remove("radar-tokenizer"),
3032 options.remove("radar_minimum_match_tokens"),
3033 options.remove("lti"),
3034 options.remove("lti_resource_link_id"),
3035 options.remove("lti_open_in_iframe"),
3036 options.remove("lti_aplus_get_and_post"),
3037 )
3038 } else {
3039 (
3040 None, None, None, None, None, None, None, None, None, None, None, None, None, None,
3041 None, None, None, None,
3042 )
3043 };
3044
3045 if config.is_none() {
3046 return TransitionResult::Failure {
3047 message: format!("A+ submit exercise on line {} has to specify a configuration file location via the :config: option. Computer says no...", line_cursor.sum_total()),
3048 doctree: doctree
3049 };
3050 }
3051
3052 let max_points = if let Ok(result) = max_points.parse() {
3054 result
3055 } else {
3056 10
3057 };
3058 let points_to_pass = if let Some(ptp) = points_to_pass {
3059 if let Ok(result) = ptp.parse() {
3060 result
3061 } else {
3062 0
3063 }
3064 } else {
3065 0
3066 };
3067
3068 use crate::common::AplusExerciseStatus;
3069 let status = if let Some(status) = status {
3070 match status.as_str().trim() {
3071 "ready" => AplusExerciseStatus::Ready,
3072 "unlisted" => AplusExerciseStatus::Unlisted,
3073 "hidden" => AplusExerciseStatus::Hidden,
3074 "enrollment" => AplusExerciseStatus::Enrollment,
3075 "enrollment_ext" => AplusExerciseStatus::EnrollmentExt,
3076 "maintenance" => AplusExerciseStatus::Maintenance,
3077 _ => AplusExerciseStatus::Unlisted,
3078 }
3079 } else {
3080 AplusExerciseStatus::Unlisted };
3082
3083 use crate::common::AplusRadarTokenizer;
3084 let tokenizer =
3085 if let Some(tokenizer) = radar_tokenizer {
3086 match tokenizer.as_str().trim() {
3087 "python" => AplusRadarTokenizer::Python3,
3088 "scala" => AplusRadarTokenizer::Scala,
3089 "javascript" => AplusRadarTokenizer::JavaScript,
3090 "css" => AplusRadarTokenizer::CSS,
3091 "html" => AplusRadarTokenizer::HTML,
3092 _ => return TransitionResult::Failure {
3093 message: format!(
3094 "No such tokenizer A+ submit exerciose on line {}. Computer says no...",
3095 line_cursor.sum_total()
3096 ),
3097 doctree: doctree,
3098 },
3099 }
3100 } else {
3101 AplusRadarTokenizer::None };
3103
3104 let lti = if let Some(lti) = lti {
3105 lti
3106 } else {
3107 String::new()
3108 };
3109
3110 let submit_node = TreeNodeType::AplusSubmit {
3113 body_indent: body_indent,
3114 key: key,
3115 difficulty: difficulty,
3116 max_points: max_points,
3117 config: config.unwrap(),
3118 submissions: if let Some(submissions) = submissions {
3119 if let Ok(result) = submissions.parse() {
3120 result
3121 } else {
3122 10
3123 }
3124 } else {
3125 10
3126 },
3127 points_to_pass: points_to_pass,
3128 class: if let Some(class) = class {
3129 class
3130 } else {
3131 String::new()
3132 },
3133 title: if let Some(title) = title {
3134 title
3135 } else {
3136 String::new()
3137 },
3138 category: if let Some(category) = category {
3139 category
3140 } else {
3141 String::from("submit")
3142 },
3143 status: status,
3144 ajax: ajax.is_some(),
3145 allow_assistant_viewing: allow_assistant_viewing.is_some(),
3146 allow_assistant_grading: allow_assistant_grading.is_some(),
3147 quiz: quiz.is_some(),
3148 url: if let Some(url) = url {
3149 url
3150 } else {
3151 String::new()
3152 },
3153 radar_tokenizer: tokenizer, radar_minimum_match_tokens: if let Some(min) = radar_minimum_match_tokens {
3155 if let AplusRadarTokenizer::None = tokenizer {
3156 None
3157 } else {
3158 if let Ok(result) = min.parse() {
3159 Some(result)
3160 } else {
3161 None
3162 }
3163 }
3164 } else {
3165 None
3166 },
3167 lti: lti,
3168 lti_resource_link_id: if let Some(id) = lti_resource_link_id {
3169 id
3170 } else {
3171 String::new()
3172 },
3173 lti_open_in_iframe: lti_open_in_iframe.is_some(),
3174 lti_aplus_get_and_post: lti_aplus_get_and_post.is_some(),
3175 };
3176
3177 doctree = match doctree.push_data_and_focus(submit_node) {
3178 Ok(tree) => tree,
3179 Err(tree) => {
3180 return TransitionResult::Failure {
3181 message: format!(
3182 "Node insertion error on line {}. Computer says no...",
3183 line_cursor.sum_total()
3184 ),
3185 doctree: tree,
3186 }
3187 }
3188 };
3189
3190 TransitionResult::Success {
3191 doctree: doctree,
3192 push_or_pop: PushOrPop::Push(vec![State::Body]),
3193 line_advance: LineAdvance::None,
3194 }
3195}
3196
3197pub fn parse_aplus_toctree() {
3198 todo!()
3199}
3200
3201pub fn parse_aplus_active_element_input(
3202 src_lines: &Vec<String>,
3203 mut doctree: DocTree,
3204 line_cursor: &mut LineCursor,
3205 base_indent: usize,
3206 empty_after_marker: bool,
3207 first_indent: usize,
3208 body_indent: usize,
3209) -> TransitionResult {
3210
3211 let key_for_input = if let Some(args) = scan_directive_arguments(
3212 src_lines,
3213 line_cursor,
3214 body_indent,
3215 Some(first_indent),
3216 empty_after_marker,
3217 ) {
3218 args.join(" ")
3219 } else {
3220 return TransitionResult::Failure {
3221 message: format!("A+ active element input before line {} has no key for output. Computer says no...", line_cursor.sum_total()),
3222 doctree: doctree
3223 };
3224 };
3225
3226 let (title, default, class, width, height, clear, input_type, file) =
3227 if let Some(mut options) = scan_directive_options
3228 (src_lines, line_cursor, body_indent) {
3229 (
3230 options.remove("title"),
3231 options.remove("default"),
3232 options.remove("class"),
3233 options.remove("width"),
3234 options.remove("height"),
3235 options.remove("clear"),
3236 options.remove("type"),
3237 options.remove("file"),
3238 )
3239 } else {
3240 (None, None, None, None, None, None, None, None)
3241 };
3242
3243 use crate::common::{AplusActiveElementClear, AplusActiveElementInputType};
3244
3245 let ae_input_node = TreeNodeType::AplusActiveElementInput {
3246 key_for_input: key_for_input,
3247 title: title,
3248 default: default,
3249 class: class,
3250 width: if let Some(w) = &width {
3251 converters::str_to_length(w)
3252 } else {
3253 None
3254 },
3255 height: if let Some(h) = &height {
3256 converters::str_to_length(h)
3257 } else {
3258 None
3259 },
3260 clear: if let Some(clear) = clear {
3261 match clear.as_str() {
3262 "both" => Some(AplusActiveElementClear::Both),
3263 "left" => Some(AplusActiveElementClear::Left),
3264 "right" => Some(AplusActiveElementClear::Right),
3265 _ => return TransitionResult::Failure {
3266 message: format!("No such clear type for A+ active element input before line {}. Computer says no...", line_cursor.sum_total()),
3267 doctree: doctree
3268 }
3269 }
3270 } else {
3271 None
3272 },
3273 input_type: if let Some(input_type) = &input_type {
3274 if input_type == "file" {
3275 Some(AplusActiveElementInputType::File)
3276 } else if input_type == "clickable" {
3277 Some(AplusActiveElementInputType::Clickable)
3278 } else if input_type.starts_with("dropdown:") {
3279 let options = if let Some(options) = input_type.split(":").last() {
3280 options
3281 } else {
3282 return TransitionResult::Failure {
3283 message: format!("No options for dropdown input for A+ activ element input before line {}. Computer says no...", line_cursor.sum_total()),
3284 doctree: doctree
3285 };
3286 };
3287 Some(crate::common::AplusActiveElementInputType::Dropdown(
3288 options.to_string(),
3289 ))
3290 } else {
3291 return TransitionResult::Failure {
3292 message: format!("No such input type for A+ active element input before line {}. Ignoring...", line_cursor.sum_total()),
3293 doctree: doctree
3294 };
3295 }
3296 } else {
3297 None
3298 },
3299 file: if let (Some(input_type), Some(file)) = (input_type, file) {
3300 if input_type == "clickable" {
3301 Some(file)
3302 } else {
3303 None
3304 }
3305 } else {
3306 None
3307 },
3308 };
3309
3310 doctree = match doctree.push_data(ae_input_node) {
3311 Ok(tree) => tree,
3312 Err(tree) => {
3313 return TransitionResult::Failure {
3314 message: format!(
3315 "Node insertion error on line {}. Computer says no...",
3316 line_cursor.sum_total()
3317 ),
3318 doctree: tree,
3319 }
3320 }
3321 };
3322
3323 TransitionResult::Success {
3324 doctree: doctree,
3325 push_or_pop: PushOrPop::Neither,
3326 line_advance: LineAdvance::None,
3327 }
3328}
3329
3330pub fn parse_aplus_active_element_output(
3332 src_lines: &Vec<String>,
3333 mut doctree: DocTree,
3334 line_cursor: &mut LineCursor,
3335 base_indent: usize,
3336 empty_after_marker: bool,
3337 first_indent: usize,
3338 body_indent: usize,
3339) -> TransitionResult {
3340
3341 let key_for_output = if let Some(args) = scan_directive_arguments(
3342 src_lines,
3343 line_cursor,
3344 body_indent,
3345 Some(first_indent),
3346 empty_after_marker,
3347 ) {
3348 args.join(" ")
3349 } else {
3350 return TransitionResult::Failure {
3351 message: format!("A+ active element output before line {} has no key for output. Computer says no...", line_cursor.sum_total()),
3352 doctree: doctree
3353 };
3354 };
3355
3356 let (
3357 config,
3358 inputs,
3359 title,
3360 class,
3361 width,
3362 height,
3363 clear,
3364 output_type,
3365 file,
3366 submissions,
3367 scale_size,
3368 status,
3369 ) = if let Some(mut options) = scan_directive_options(
3370 src_lines, line_cursor, body_indent
3371 ) {
3372 (
3373 options.remove("config"),
3374 options.remove("inputs"),
3375 options.remove("title"),
3376 options.remove("class"),
3377 options.remove("width"),
3378 options.remove("height"),
3379 options.remove("clear"),
3380 options.remove("type"),
3381 options.remove("file"),
3382 options.remove("submissions"),
3383 options.remove("scale-size"),
3384 options.remove("status")
3385 )
3386 } else {
3387 (
3388 None, None, None, None, None, None, None, None, None, None, None, None
3389 )
3390 };
3391
3392 use crate::common::{
3393 AplusActiveElementClear, AplusActiveElementOutputType, AplusExerciseStatus,
3394 };
3395
3396 let ae_output_node = TreeNodeType::AplusActiveElementOutput {
3397 key_for_output: key_for_output,
3398 config: if let Some(config) = config {
3399 config
3400 } else {
3401 return TransitionResult::Failure {
3402 message: format!("A+ active element output before line {} must have a set config file via the \"config\" option. Computer says no...", line_cursor.sum_total()),
3403 doctree: doctree
3404 };
3405 },
3406 inputs: if let Some(inputs) = inputs {
3407 inputs
3408 } else {
3409 return TransitionResult::Failure {
3410 message: format!("A+ active element output before line {} must have a set of inputs set via the \"inputs\" setting. Computer says no...", line_cursor.sum_total()),
3411 doctree: doctree
3412 };
3413 },
3414 title: title,
3415 class: class,
3416 width: if let Some(w) = &width {
3417 converters::str_to_length(w)
3418 } else {
3419 None
3420 },
3421 height: if let Some(h) = &height {
3422 converters::str_to_length(h)
3423 } else {
3424 None
3425 },
3426 clear: if let Some(clear) = clear {
3427 match clear.as_str() {
3428 "both" => Some(AplusActiveElementClear::Both),
3429 "left" => Some(AplusActiveElementClear::Left),
3430 "right" => Some(AplusActiveElementClear::Right),
3431 _ => None
3432 }
3433 } else {
3434 None
3435 },
3436 output_type: if let Some(output_type) = output_type {
3437 match output_type.as_str() {
3438 "text" => AplusActiveElementOutputType::Text,
3439 "image" => AplusActiveElementOutputType::Image,
3440 _ => AplusActiveElementOutputType::Text
3441 }
3442 } else {
3443 AplusActiveElementOutputType::Text
3444 },
3445 submissions: if let Some(submissions) = submissions {
3446 if let Ok(result) = submissions.parse::<u32>() {
3447 Some(result)
3448 } else {
3449 None
3450 }
3451 } else {
3452 None
3453 },
3454 scale_size: if let Some(_) = scale_size {
3455 true
3456 } else {
3457 false
3458 },
3459 status: if let Some(status) = status {
3460 match status.as_str().trim() {
3461 "ready" => AplusExerciseStatus::Ready,
3462 "unlisted" => AplusExerciseStatus::Unlisted,
3463 "hidden" => AplusExerciseStatus::Hidden,
3464 "enrollment" => AplusExerciseStatus::Enrollment,
3465 "enrollment_ext" => AplusExerciseStatus::EnrollmentExt,
3466 "maintenance" => AplusExerciseStatus::Maintenance,
3467 _ => AplusExerciseStatus::Unlisted
3468 }
3469 } else {
3470 AplusExerciseStatus::Unlisted
3471 },
3472 };
3473
3474 doctree = match doctree.push_data(ae_output_node) {
3475 Ok(tree) => tree,
3476 Err(tree) => {
3477 return TransitionResult::Failure {
3478 message: format!(
3479 "Node insertion error on line {}. Computer says no...",
3480 line_cursor.sum_total()
3481 ),
3482 doctree: tree,
3483 }
3484 }
3485 };
3486
3487 TransitionResult::Success {
3488 doctree: doctree,
3489 push_or_pop: PushOrPop::Neither,
3490 line_advance: LineAdvance::None,
3491 }
3492}
3493
3494pub fn parse_aplus_hidden_block() {
3495 todo!()
3496}
3497
3498pub fn parse_aplus_point_of_interest(
3503 src_lines: &Vec<String>,
3504 mut doctree: DocTree,
3505 line_cursor: &mut LineCursor,
3506 base_indent: usize,
3507 empty_after_marker: bool,
3508 first_indent: usize,
3509 body_indent: usize,
3510 section_level: usize,
3511) -> TransitionResult {
3512
3513 let title = scan_directive_arguments(
3514 src_lines,
3515 line_cursor,
3516 body_indent,
3517 Some(first_indent),
3518 empty_after_marker,
3519 );
3520
3521 let (
3523 id,
3524 previous,
3525 next,
3526 hidden,
3527 class,
3528 height,
3529 columns,
3530 bgimg,
3531 not_in_slides,
3532 not_in_book,
3533 no_poi_box,
3534 ) = if let Some(mut options) = scan_directive_options(
3535 src_lines, line_cursor, body_indent
3536 ) {
3537
3538 (
3539 options.remove("id"),
3540 options.remove("previous"),
3541 options.remove("next"),
3542 options.remove("hidden"),
3543 options.remove("class"),
3544 options.remove("height"),
3545 options.remove("columns"),
3546 options.remove("bgimg"),
3547 options.remove("not_in_slides"),
3548 options.remove("not_in_book"),
3549 options.remove("no_poi_box"),
3550 )
3551 } else {
3552 (
3553 None, None, None, None, None, None, None, None, None, None, None,
3554 )
3555 };
3556
3557 let poi_node = TreeNodeType::AplusPOI {
3558 title: if let Some(title) = title {
3559 title.join(" ")
3560 } else {
3561 "".to_string()
3562 },
3563 body_indent: body_indent,
3564
3565 id: id,
3566 previous: previous,
3567 next: next,
3568 hidden: hidden,
3569 class: class,
3570 height: if let Some(h) = &height {
3571 converters::str_to_length(h)
3572 } else {
3573 None
3574 },
3575 columns: columns,
3576 bgimg: bgimg,
3577 not_in_slides: not_in_slides,
3578 not_in_book: not_in_book,
3579 no_poi_box: no_poi_box,
3580 };
3581
3582 doctree = match doctree.push_data_and_focus(poi_node) {
3583 Ok(tree) => tree,
3584 Err(tree) => {
3585 return TransitionResult::Failure {
3586 message: format!(
3587 "Node insertion error on line {}. Computer says no...",
3588 line_cursor.sum_total()
3589 ),
3590 doctree: tree,
3591 }
3592 }
3593 };
3594
3595 TransitionResult::Success {
3596 doctree: doctree,
3597 push_or_pop: PushOrPop::Push(vec![State::AplusMultiCol]), line_advance: LineAdvance::None,
3599 }
3600}
3601
3602pub fn parse_aplus_annotated() {
3603 todo!()
3604}
3605
3606pub fn parse_aplus_lineref_codeblock() {
3607 todo!()
3608}
3609
3610pub fn parse_aplus_repl_res_count_reset() {
3611 todo!()
3612}
3613
3614pub fn parse_aplus_acos_submit() {
3615 todo!()
3616}
3617
3618pub fn parse_aplus_div() {
3619 todo!()
3620}
3621
3622pub fn parse_aplus_styled_topic() {
3623 todo!()
3624}
3625
3626pub fn parse_aplus_story() {
3627 todo!()
3628}
3629
3630pub fn parse_aplus_jsvee() {
3631 todo!()
3632}
3633
3634pub fn parse_aplus_youtube() {
3635 todo!()
3636}
3637
3638pub fn parse_aplus_local_video() {
3639 todo!()
3640}
3641
3642pub fn parse_aplus_embedded_page() {
3643 todo!()
3644}
3645
3646pub fn parse_unknown_directive(
3648 mut doctree: DocTree,
3649 src_lines: &Vec<String>,
3650 line_cursor: &mut LineCursor,
3651 directive_name: &str,
3652 first_line_indent: usize,
3653 body_indent: usize,
3654 empty_after_marker: bool,
3655) -> TransitionResult {
3656 let argument = if let Some(arg) = scan_directive_arguments(
3657 src_lines,
3658 line_cursor,
3659 body_indent,
3660 Some(first_line_indent),
3661 empty_after_marker,
3662 ) {
3663 arg.join(" ").trim().to_string()
3664 } else {
3665 String::new()
3666 };
3667
3668 let options = if let Some(options) =
3669 scan_directive_options
3670 (src_lines, line_cursor, body_indent)
3671 {
3672 options
3673 } else {
3674 HashMap::new()
3675 };
3676
3677 let unknown_directive_data = TreeNodeType::UnknownDirective {
3678 directive_name: String::from(directive_name),
3679 argument: argument,
3680 options: options,
3681 body_indent: body_indent,
3682 };
3683
3684 doctree = match doctree.push_data_and_focus(unknown_directive_data) {
3685 Ok(tree) => tree,
3686 Err(tree) => {
3687 return TransitionResult::Failure {
3688 message: format!(
3689 "Could not add unknown directive data to doctree on line {}",
3690 line_cursor.sum_total()
3691 ),
3692 doctree: tree,
3693 }
3694 }
3695 };
3696
3697 TransitionResult::Success {
3698 doctree: doctree,
3699 push_or_pop: PushOrPop::Push(vec![State::Body]),
3700 line_advance: LineAdvance::None,
3701 }
3702
3703 }
3730
3731fn scan_directive_arguments(
3745 src_lines: &Vec<String>,
3746 line_cursor: &mut LineCursor,
3747 body_indent: usize,
3748 first_indent: Option<usize>,
3749 empty_after_marker: bool,
3750) -> Option<Vec<String>> {
3751 use crate::parser::automata::FIELD_MARKER_AUTOMATON;
3752
3753 let mut argument_lines: Vec<String> = Vec::new();
3755 let mut on_marker_line = true;
3756
3757 if empty_after_marker {
3759 line_cursor.increment_by(1);
3760 on_marker_line = false;
3761 }
3762
3763 while let Some(line) = src_lines.get(line_cursor.relative_offset()) {
3764
3765
3766 let line_without_indent: String = if on_marker_line {
3768 match first_indent {
3769 Some(indent) => {
3770 on_marker_line = false;
3771 line.chars().skip(indent).collect()
3772 }
3773 None => panic!("On directive marker line {} but couldn't skip the marker to parse line contents. Computer says no...", line_cursor.sum_total())
3774 }
3775 } else {
3776 if ! (line.chars().take_while(|c| c.is_whitespace()).count() == body_indent) {
3778 break
3779 };
3780 line.chars()
3781 .skip_while(|c| c.is_whitespace())
3782 .collect::<String>()
3783 .as_str()
3784 .trim()
3785 .to_string()
3786 };
3787
3788 if line_without_indent.as_str().trim().is_empty()
3789 || FIELD_MARKER_AUTOMATON.is_match(line_without_indent.as_str())
3790 {
3791 break;
3792 }
3793
3794 argument_lines.push(line_without_indent);
3795 line_cursor.increment_by(1);
3796 }
3797
3798 if argument_lines.is_empty() {
3799 None
3800 } else {
3801 Some(argument_lines)
3802 }
3803}
3804
3805
3806fn scan_directive_options(
3815 src_lines: &Vec<String>,
3816 line_cursor: &mut LineCursor,
3817 body_indent: usize,
3818) -> Option<HashMap<String, String>> {
3819 use crate::parser::automata::FIELD_MARKER_AUTOMATON;
3820
3821 let mut option_map: HashMap<String, String> = HashMap::new();
3822
3823 let mut ended_with_blank: bool = false;
3824
3825 while let Some(line) = src_lines.get(line_cursor.relative_offset()) {
3826 if line.trim().is_empty() {
3827 ended_with_blank = true;
3828 break;
3829 } if let Some(captures) = FIELD_MARKER_AUTOMATON.captures(line) {
3832 let line_indent = captures.get(1).unwrap().as_str().chars().count();
3833 if line_indent != body_indent {
3834 break;
3835 } let option_key = captures.get(2).unwrap().as_str().trim();
3837
3838 let option_val_indent = captures.get(0).unwrap().as_str().chars().count();
3839 let option_val = match line.char_indices().nth(option_val_indent) {
3840 Some((index, _)) => line[index..].trim(),
3841 None => "",
3842 };
3843
3844 if let Some(val) = option_map.insert(option_key.to_string(), option_val.to_string())
3845 {
3846 }
3848 } else {
3849 ended_with_blank = false;
3850 break; }
3852 line_cursor.increment_by(1);
3853 }
3854
3855 if option_map.is_empty() {
3856 None
3857 } else {
3858 if ended_with_blank {
3859 line_cursor.increment_by(1)
3860 }
3861 Some(option_map)
3862 }
3863}
3864
3865fn aplus_key_difficulty_and_max_points(
3869 arg_str: &str,
3870 line_cursor: &mut LineCursor,
3871) -> (String, String, String) {
3872 use regex::Regex;
3873
3874 lazy_static::lazy_static! {
3875 static ref EXERCISE_ARGS_RE: Regex = Regex::new(r"^(?P<key>[a-zA-Z0-9]+)?[ ]*(?P<difficulty>[A-Z])?(?P<max_points>[0-9]+)?").unwrap();
3876 }
3877
3878 if let Some(captures) = EXERCISE_ARGS_RE.captures(arg_str) {
3879 let key = if let Some(key) = captures.name("key") {
3880 String::from(key.as_str())
3881 } else {
3882 String::new()
3883 };
3884 let difficulty = if let Some(difficulty) = captures.name("difficulty") {
3885 String::from(difficulty.as_str())
3886 } else {
3887 String::new()
3888 };
3889 let max_points = if let Some(points) = captures.name("max_points") {
3890 String::from(points.as_str())
3891 } else {
3892 String::new()
3893 };
3894
3895 (key, difficulty, max_points)
3896 } else {
3897 eprintln!("Either no arguments or invalid argument format for questionnaire preceding line {}...", line_cursor.sum_total());
3899 (String::new(), String::new(), String::new())
3900 }
3901}