1use std::io::Write;
10
11use super::*;
12use crate::common::AplusExerciseStatus;
13use crate::rustla_options::ruSTLaOptions;
14use crate::rustla_options::OutputStream;
15
16const LATEX_OPTION_DELIM: &str = ",";
17
18impl DocTree {
19
20 pub fn write_to_larst(self, rustla_options: &ruSTLaOptions) {
22
23 let mut output_stream: Box<dyn Write> = match rustla_options.shared_out_stream() {
25
26 OutputStream::StdOut => {
27 let stdout = std::io::stdout();
29 Box::new(stdout)
30 }
31
32 OutputStream::File => {
33
34 if self.file_folder.is_empty() || self.filename_stem.is_empty() {
36 panic!("Cannot write to file without knowing the location. Computer says no...")
37 }
38
39 const TEX_FILE_SUFFIX: &str = ".tex";
41
42 let folder = &self.file_folder;
43 let mut object_file_path = std::path::PathBuf::from(folder);
44 object_file_path.push(self.filename_stem + TEX_FILE_SUFFIX);
45
46 eprintln!("Writing output to {:#?}...", object_file_path);
47
48 let object_file: std::fs::File = match std::fs::OpenOptions::new()
50 .write(true)
51 .truncate(true)
52 .create(true)
53 .open(object_file_path)
54 {
55 Ok(file) => file,
56 Err(e) => panic!("Could not open LarST file for writing purposes: {}", e),
57 };
58
59 Box::new(object_file)
60 }
61 };
62
63 self.tree.write_to_larst(&mut output_stream, rustla_options);
64
65 match rustla_options.shared_out_stream() {
68
69 OutputStream::File if rustla_options.create_class_file() => {
70
71 const APLUS_CLASS_FILE_NAME: &str = "aplus.cls";
73
74 let folder = &self.file_folder;
75 let mut aplus_class_file_path = std::path::PathBuf::from(folder);
76 aplus_class_file_path.push(APLUS_CLASS_FILE_NAME);
77 let mut aplus_class_file: std::fs::File = match std::fs::OpenOptions::new()
78 .write(true)
79 .truncate(true)
80 .create(true)
81 .open(aplus_class_file_path)
82 {
83 Ok(file) => file,
84 Err(e) => panic!("Could not open A+ class file for writing purposes: {}", e),
85 };
86
87 match aplus_class_file.write(aplus_cls_contents().as_bytes()){
88 Ok(_) => {},
89 Err(_) => panic!("Could not write to A+ class file after generating object code. Computer says no...")
90 };
91 }
92 _ => {}
93 }
94 }
95}
96
97impl TreeZipper {
98
99 fn write_to_larst(mut self, output_stream: &mut Box<dyn Write>, rustla_options: &ruSTLaOptions) {
105
106 self = self.walk_to_root(); self.shared_node().larst_pre_order_write(output_stream, rustla_options);
109
110 if let Some(children) = self.shared_node().shared_children() {
111 for child in children {
112 child.write_to_larst(output_stream, rustla_options);
113 }
114 }
115
116 self.shared_node().larst_post_order_write(output_stream, rustla_options);
117 }
118}
119
120impl TreeNode {
121
122 fn write_to_larst(&self, output_stream: &mut Box<dyn Write>, rustla_options: &ruSTLaOptions) {
124
125 self.larst_pre_order_write(output_stream, rustla_options);
126
127 if let Some(children) = self.shared_children() {
128 for child in children {
129 child.write_to_larst(output_stream, rustla_options);
130 }
131 }
132
133 self.larst_post_order_write(output_stream, rustla_options);
134 }
135
136 fn larst_pre_order_write(&self, output_stream: &mut Box<dyn Write>, rustla_options: &ruSTLaOptions) {
139
140 let refnames = self.shared_target_labels().as_ref();
141
142 let pre_string = self.shared_data().larst_pre_order_string(refnames, rustla_options);
143 match output_stream.write(pre_string.as_bytes()) {
144 Ok(_) => {}
145 Err(_) => panic!(
146 "Could not write the prefix string \"{}\" to file. Computer says no...",
147 pre_string
148 ),
149 };
150 }
151
152 fn larst_post_order_write(&self, output_stream: &mut Box<dyn Write>, rustla_options: &ruSTLaOptions) {
155
156 let refnames = self.shared_target_labels().as_ref();
157
158 let post_string = self.shared_data().larst_post_order_string(refnames, rustla_options);
159 match output_stream.write(post_string.as_bytes()) {
160 Ok(_) => {}
161 Err(_) => panic!(
162 "Could not write the prefix string \"{}\" to file. Computer says no...",
163 post_string
164 ),
165 };
166 }
167
168
169 fn ref_names_into_larst_labels(&self) -> String {
171 if let Some(refnames) = self.shared_target_label() {
172 let mut targets = String::new();
173 for refname in refnames.iter() {
174 targets += &format!("\\label{{{}}}\n", refname);
175 }
176 targets
177 } else {
178 String::new()
179 }
180 }
181}
182
183impl TreeNodeType {
184
185 fn larst_pre_order_string(&self, ref_names: Option<&Vec<String>>, rustla_options: &ruSTLaOptions) -> String {
187 let pre_string = match self {
188 Self::Abbreviation { .. } => todo!(),
189 Self::AbsoluteURI { text } => {
190 format!(r"\url{{{}}}", text)
191 }
192 Self::Acronym { .. } => todo!(),
193 Self::Address => todo!(),
194 Self::Admonition {
195 content_indent,
196 classes,
197 name,
198 variant,
199 } => {
200 use crate::doctree::directives::AdmonitionType;
201
202 match variant {
203 AdmonitionType::Admonition { title } => {
204 format!("\\begin{{admonition}}{{{}}}\n", title)
205 }
206 _ => format!("\\begin{{{}}}\n", variant.to_string()),
207 }
208 }
209 Self::Attribution { raw_text } => {
210 format!("-- {}", raw_text)
211 }
212 Self::Author { .. } => todo!(),
213 Self::Authors { .. } => todo!(),
214 Self::AutomaticSectionNumbering { .. } => todo!(),
215 Self::BlockQuote { body_indent } => {
216 format!("\\begin{{quotation}}\n")
217 }
218 Self::BulletList {
219 bullet,
220 bullet_indent,
221 text_indent,
222 } => {
223 format!("\\begin{{itemize}}\n")
224 }
225 Self::BulletListItem {
226 bullet,
227 bullet_indent,
228 text_indent,
229 } => {
230 format!("\\item ")
231 }
232 Self::Caption { indent } => {
233 format!(r"\captionof{{figure}}{{")
234 }
235 Self::Citation { body_indent, label } => {
236 format!("\\bibitem{{{}}}\n", label)
237 }
238 Self::CitationReference {
239 displayed_text,
240 target_label,
241 } => {
242 format!("\\cite{{{}}}", displayed_text)
243 }
244 Self::Class { .. } => "".to_string(),
245 Self::Classifier { .. } => todo!(),
246 Self::Code {
247 text,
248 language,
249 name,
250 class,
251 number_lines,
252 } => {
253 let lang = if let Some(lang) = language {
254 format!("[{}]", lang)
255 } else {
256 "".to_string()
257 };
258 format!("\\begin{{codeblock}}{}\n", lang)
259 }
260 Self::ColSpec { .. } => todo!(),
261 Self::Comment { text } => {
262 if let Some(comment) = text {
263 comment
264 .lines()
265 .fold(String::new(), |a, b| a + "% " + b + "\n")
266 + "\n"
267 } else {
268 String::new()
269 }
270 }
271 Self::CompoundParagraph { .. } => todo!(),
272 Self::Contact { .. } => todo!(),
273 Self::Container { .. } => todo!(),
274 Self::Copyright { .. } => todo!(),
275 Self::CSVTable { .. } => todo!(),
276 Self::Date => todo!(),
277 Self::Decoration => todo!(),
278 Self::Definition => todo!(),
279 Self::DefinitionList { term_indent } => {
280 format!("\\begin{{itemize}}\n")
281 }
282 Self::DefinitionListItem {
283 term,
284 classifiers,
285 body_indent,
286 } => {
287 let classifiers = if classifiers.is_empty() {
288 "".to_string()
289 } else {
290 format!(": {}", classifiers.join(", "))
291 };
292
293 format!("\\item \\textbf{{{}}}{}\n\n", term, classifiers)
294 }
295 Self::Description => todo!(),
296 Self::DocInfo => todo!(),
297 Self::DoctestBlock { .. } => todo!(),
298 Self::Document { .. } => if rustla_options.is_full_document() {
299 format!("\\documentclass{{aplus}}\n\\begin{{document}}\n\n")
300 } else {
301 String::new()
302 },
303 Self::Emphasis { text } => {
304 format!("\\textit{{{}}}", text)
305 }
306 Self::EmptyLine => {
307 format!("")
308 }
309 Self::Entry { .. } => {
310 format!("")
311 }
312 Self::EnumeratedList {
313 delims,
314 kind,
315 start_index,
316 n_of_items,
317 enumerator_indent,
318 } => {
319 format!("\\begin{{enumerate}}\n")
320 }
321 Self::EnumeratedListItem {
322 delims,
323 kind,
324 index_in_list,
325 enumerator_indent,
326 text_indent,
327 } => {
328 format!("\\item ")
329 }
330 Self::ExternalHyperlinkTarget { .. } => todo!(),
331 Self::Field => unimplemented!(),
332 Self::FieldBody { .. } => unimplemented!(),
333 Self::FieldList { marker_indent } => {
334 format!("\\begin{{itemize}}\n")
335 }
336 Self::FieldListItem {
337 raw_marker_name,
338 marker_name_as_inline_nodes,
339 ..
340 } => {
341 format!("\\item \\textbf{{{}}}\n\n", raw_marker_name)
342 }
343 Self::Figure {
344 body_indent,
345 align,
346 figwidth,
347 figclass,
348 ..
349 } => {
350 let mut options = Vec::<String>::new();
351 if let Some(alignment) = align {
352 options.push(alignment.to_string())
353 }
354 if let Some(width) = figwidth {
355 options.push(format!("figwidth={}", width.to_string()))
356 }
357 if let Some(class) = figclass {
358 options.push(format!("figclass={}", class.to_string()))
359 }
360
361 let options_string = if !options.is_empty() {
362 format!("[{}]", options.join(","))
363 } else {
364 String::new()
365 };
366
367 format!("\\begin{{center}}\n")
368 }
369 Self::Footer { .. } => todo!(),
370 Self::Footnote { kind, label, target, .. } => {
371 format!("\\footnote{{\n")
372 },
373 Self::FootnoteReference { .. } => {
374 format!("\\footnotemark")
375 },
376 Self::Header { .. } => todo!(),
377 Self::Generated => todo!(),
378 Self::Image {
379 uri,
380 alt,
381 height,
382 width,
383 scale,
384 align,
385 target,
386 name,
387 class,
388 ..
389 } => {
390 let mut options = Vec::<String>::new();
391
392 if let Some(val) = alt {
393 options.push(format!("{}", val))
394 };
395 if let Some(h) = height {
396 options.push(format!("height={}", h.to_string()))
397 }
398 if let Some(w) = width {
399 options.push(format!("width={}", w.to_string()))
400 }
401 if let Some(val) = scale {
402 options.push(format!("scale={0:.2}%", val))
403 }
404 if let Some(val) = align {
405 options.push(format!("align={}", val.to_string()))
406 }
407
408 let options = if options.is_empty() {
409 String::new()
410 } else {
411 format!("[{}]", options.join(LATEX_OPTION_DELIM))
412 };
413
414 format!("\\includegraphics{}{{{}}}", options, uri)
415 }
416 Self::Include { uri, .. } => {
417 format!("\\input{{{}}}\n", uri)
419 }
420 Self::IndirectHyperlinkTarget { .. } => todo!(),
421 Self::Inline { .. } => todo!(),
422 Self::InlineTarget { .. } => todo!(),
423 Self::InterpretedText { role, content } => {
424 format!("\\{}{{{}}}", role, content)
425 },
426 Self::Label { .. } => todo!(),
427 Self::Legend { .. } => todo!(),
428 Self::Line { .. } => todo!(),
429 Self::LineBlock { .. } => todo!(),
430 Self::ListTable {
431 title,
432 widths,
433 width,
434 header_rows,
435 stub_columns,
436 align,
437 ..
438 } => {
439 let widths = if let Some(widths) = widths {
440 match widths {
441 TableColWidths::Auto => String::new(),
442 TableColWidths::Columns(vals) => {
443 let mut col_widths = Vec::<String>::with_capacity(vals.len());
444 for val in vals {
445 col_widths.push(format!("p{{{0:.2}\\textwidth}}", *val));
446 }
447 col_widths.join("")
448 }
449 }
450 } else {
451 panic!("Columns widths need to be set for all list tables. Computer says no...")
452 };
453
454 let mut options = Vec::<String>::new();
456 if let Some(title) = title { options.push(format!("title={}", title)) }
457 let options_string = if ! options.is_empty() {
458 let options = options.join(LATEX_OPTION_DELIM);
459 format!("[{}]", options)
460 } else {
461 String::new()
462 };
463
464 format!("\\begin{{tabular}}{}{{{}}}\n", options_string, widths)
465 }
466 Self::Literal { text } => format!("\\texttt{{{}}}", text),
467 Self::LiteralBlock { text } => {
468 use crate::utf8_to_latex::unicode_text_to_latex;
469 format!("\\begin{{codeblock}}\n{}", unicode_text_to_latex(text))
470 }
471 Self::Math { text, class, name } => {
472 format!(r"\({}\)", crate::utf8_to_latex::unicode_math_to_latex(text))
473 }
474 Self::MathBlock {
475 math_block,
476 name,
477 class,
478 } => {
479 let ref_labels = self.anchor_string(ref_names);
480 format!(
481 "{}\\begin{{equation}}\n{}\n",
482 ref_labels, crate::utf8_to_latex::unicode_math_to_latex(math_block)
483 )
484 }
485 Self::OptionList { .. } => todo!(),
486 Self::OptionListItem { .. } => todo!(),
487 Self::OptionString { .. } => todo!(),
488 Self::Organization { .. } => todo!(),
489 Self::Paragraph { .. } => "".to_string(),
490 Self::ParsedLiteralBlock { .. } => todo!(),
491 Self::Pending { .. } => todo!(),
492 Self::Problematic { .. } => todo!(),
493 Self::Raw { .. } => "\\begin{codeblock}\n".to_string(),
494 Self::Reference {
495 displayed_text,
496 reference,
497 } => {
498
499 use crate::common::Reference;
500
501 match reference {
502 Reference::Internal(ref_str) => {
503 if let Some(text) = displayed_text {
504 format!("\\hyperref[{}]{{{}}}", ref_str, text)
505 } else {
506 format!("\\ref{{{}}}", ref_str)
507 }
508 }
509 Reference::URI(ref_str) => {
510 if let Some(text) = displayed_text {
511 format!("\\href{{{}}}{{{}}}", ref_str, text)
512 } else {
513 format!("\\url{{{}}}", ref_str)
514 }
515 }
516 Reference::EMail(ref_str) => {
517 if let Some(text) = displayed_text {
518 format!("\\href{{{}}}{{{}}}", ref_str, text)
519 } else {
520 format!("\\href{{{}}}{{{}}}", ref_str, ref_str)
521 }
522 }
523 }
524 }
525 Self::Revision { .. } => todo!(),
526 Self::Row { .. } => todo!(),
527 Self::Rubric { .. } => todo!(),
528 Self::Section {
529 title_text,
530 level,
531 line_style,
532 } => {
533 let (command, subs) = if *level == 1 {
534 ("chapter", "")
535 } else if *level == 2 {
536 ("section", "")
537 } else if *level == 3 {
538 ("section", "sub")
539 } else {
540 ("section", "subsub")
541 };
542
543 let anchors = self.anchor_string(ref_names);
544
545 format!("{}\\{}{}{{{}}}\n\n", anchors, subs, command, title_text)
546 }
547 Self::Sidebar { .. } => todo!(),
548 Self::Status { .. } => todo!(),
549 Self::StrongEmphasis { text } => {
550 format!("\\textbf{{{}}}", text)
551 }
552 Self::Subscript { text } => {
553 format!(r"\textsubscript{{{}}}", text)
554 }
555 Self::SubstitutionDefinition { .. } => todo!(),
556 Self::SubstitutionReference {
557 substitution_label,
558 target_label,
559 } => {
560 todo!()
561 }
562 Self::Subtitle { .. } => todo!(),
563 Self::Superscript { text } => {
564 format!(r"\textsuperscript{{{}}}", text)
565 }
566 Self::SystemMessage { .. } => todo!(),
567 Self::Table { .. } => todo!(),
568 Self::Target { .. } => todo!(),
569 Self::TBody { .. } => "".to_string(),
570 Self::Term { .. } => todo!(),
571 Self::Text { text } => {
572 format!("{}", text)
573 }
574 Self::TGroup { .. } => todo!(),
575 Self::THead { .. } => todo!(),
576 Self::TRow { .. } => "".to_string(),
577 Self::Title { .. } => todo!(),
578 Self::TitleReference {
579 displayed_text,
580 target_label,
581 } => {
582 format!("\\hyperref[{}]{{{}}}", target_label, displayed_text)
583 }
584 Self::Topic { .. } => todo!(),
585 Self::Transition {} => {
586 format!("\\hrulefill\n")
587 }
588 Self::UnknownDirective {
589 directive_name,
590 argument,
591 options,
592 ..
593 } => {
594 let arg_str: String = if argument.trim().is_empty() {
595 String::new()
596 } else {
597 format!("{{{}}}", argument)
598 };
599 let mut option_vec = Vec::new();
600 for (key, val) in options.keys().zip(options.values()) {
601 option_vec.push(format!("{}={}", key, val))
602 }
603 let option_str = if option_vec.is_empty() {
604 String::new()
605 } else {
606 format!("[{}]", option_vec.join(LATEX_OPTION_DELIM))
607 };
608
609 format!(
610 "\\begin{{{}}}{}{}\n",
611 directive_name.to_lowercase(),
612 option_str,
613 arg_str
614 )
615 }
616 Self::Version { .. } => todo!(),
617 Self::WhiteSpace { text } => {
618 format!("{}", text)
619 }
620
621 Self::SphinxOnly {
625 expression,
626 body_indent,
627 } => {
628 format!("\\begin{{only}}[{}]\n", expression)
629 }
630
631 Self::SphinxCodeBlock {
632 language,
633 linenos,
634 lineno_start,
635 emphasize_lines,
636 caption,
637 name,
638 dedent,
639 force,
640 code_text,
641 } => {
642 let mut options = Vec::<String>::new();
643
644 if *linenos {
645 options.push(String::from("linenos"))
646 }
647 if let Some(start_line) = lineno_start {
648 options.push(format!("lineno-start={}", start_line.to_string()))
649 }
650 if let Some(line_numbers) = emphasize_lines {
651 let line_number_strings = line_numbers
652 .iter()
653 .map(|line| line.to_string())
654 .collect::<Vec<String>>()
655 .join(",");
656 options.push(format!("emphasize_lines={}", line_number_strings))
657 }
658 if let Some(caption) = caption {
659 let latex_caption = crate::utf8_to_latex::unicode_text_to_latex(caption);
660 options.push(format!("caption={}", latex_caption))
661 }
662 if let Some(name) = name {
663 let normalized_refname = crate::common::normalize_refname(name);
664 options.push(format!("name"))
665 }
666 if let Some(dedent) = dedent {
667 }
669
670 if *force {
671 options.push(format!("force"))
672 }
673
674 let option_string = options.join(",");
675
676 format!("\\begin{{codeblock}}[{}]\n{}", language, code_text)
678 }
679
680 Self::AplusPOI {
684 id,
685 title,
686 previous,
687 next,
688 hidden,
689 class,
690 height,
691 columns,
692 bgimg,
693 not_in_slides,
694 not_in_book,
695 no_poi_box,
696 ..
697 } => {
698 let mut options = String::new();
699
700 if let Some(option) = id {
701 options = options + "id=" + option + LATEX_OPTION_DELIM
702 };
703 if let Some(option) = previous {
704 options = options + "previous=" + option + LATEX_OPTION_DELIM
705 };
706 if let Some(option) = next {
707 options = options + "next=" + option + LATEX_OPTION_DELIM
708 };
709 if let Some(option) = hidden {
710 options = options + "hidden" + LATEX_OPTION_DELIM
711 };
712 if let Some(option) = class {
713 options = options + "class=" + option + LATEX_OPTION_DELIM
714 };
715 if let Some(option) = height {
716 let height = rst_length_to_string(option);
717 options = options + "height=" + &height + LATEX_OPTION_DELIM
718 };
719 if let Some(option) = columns {
720 options = options + "columns=" + option + LATEX_OPTION_DELIM
721 };
722 if let Some(option) = bgimg {
723 options = options + "bgimg=" + option + LATEX_OPTION_DELIM
724 };
725 if let Some(option) = not_in_slides {
726 options = options + "not_in_slides" + LATEX_OPTION_DELIM
727 };
728 if let Some(option) = not_in_book {
729 options = options + "not_in_book" + LATEX_OPTION_DELIM
730 };
731 if let Some(option) = no_poi_box {
732 options = options + "no_poi_box" + LATEX_OPTION_DELIM
733 };
734
735 if !options.is_empty() {
736 options = format!("[{}]", options.as_str())
737 };
738 format!("\\begin{{poi}}{}{{{}}}\n\n", options, title)
739 }
740 Self::AplusColBreak => "\\newcol\n\n".to_string(),
741 Self::AplusQuestionnaire {
742 max_points,
743 key,
744 points_from_children,
745 difficulty,
746 submissions,
747 points_to_pass,
748 feedback,
749 title,
750 no_override,
751 pick_randomly,
752 preserve_questions_between_attempts,
753 category,
754 status,
755 reveal_model_at_max_submissions,
756 show_model,
757 allow_assistant_viewing,
758 allow_assistant_grading,
759 ..
760 } => {
761 let max_points = if let Some(points) = max_points {
762 points
763 } else {
764 points_from_children
765 };
766
767 format!("\\begin{{quiz}}{{{}}}{{{}}}\n", key, *max_points)
768 }
769 Self::AplusPickOne {
770 points,
771 class,
772 required,
773 key,
774 dropdown,
775 ..
776 } => {
777 let mut options = String::new();
778 if let Some(option) = class {
779 options = options + "id=" + option + LATEX_OPTION_DELIM
780 };
781 if *required {
782 options = options + "required" + LATEX_OPTION_DELIM
783 };
784 if let Some(option) = key {
785 options = options + "key=" + option + LATEX_OPTION_DELIM
786 };
787 let options = if !options.is_empty() {
790 format!("[{}]", options)
791 } else {
792 options
793 };
794
795 format!("\\begin{{pick}}{}{{one}}{{{}}}\n", options, points)
796 }
797 Self::AplusPickAny {
798 points,
799 class,
800 required,
801 key,
802 partial_points,
803 randomized,
804 correct_count,
805 preserve_questions_between_attempts,
806 ..
807 } => {
808 let mut options = String::new();
809 if let Some(option) = class {
810 options = options + "id=" + option + LATEX_OPTION_DELIM
811 };
812 if *required {
813 options = options + "required" + LATEX_OPTION_DELIM
814 };
815 if let Some(option) = key {
816 options = options + "key=" + option + LATEX_OPTION_DELIM
817 };
818 if *partial_points {
819 options = options + "partial-points" + LATEX_OPTION_DELIM
820 };
821 if *randomized {
822 options = options + "randomized" + LATEX_OPTION_DELIM
823 };
824 if let Some(correct_count) = correct_count {
825 options = options
826 + "correct-count="
827 + correct_count.to_string().as_str()
828 + LATEX_OPTION_DELIM
829 };
830 if *preserve_questions_between_attempts {
831 options = options + "preserve-questions-between-attempts" + LATEX_OPTION_DELIM
832 };
833
834 if !options.is_empty() {
835 options = format!("[{}]", options)
836 }
837
838 format!("\\begin{{pick}}{}{{any}}{{{}}}\n", options, points)
839 }
840 Self::AplusFreeText {
841 points,
842 compare_method,
843 model_answer,
844 required,
845 class,
846 key,
847 length,
848 height,
849 ..
850 } => {
851 format!(
852 "\\begin{{freetext}}{{{}}}{{{}}}{{{}}}\n",
853 compare_method, points, model_answer
854 )
855 }
856 Self::AplusPickChoices { .. } => "\\begin{answers}\n".to_string(),
857 Self::AplusPickChoice {
858 label,
859 is_correct,
860 is_pre_selected,
861 is_neutral,
862 } => {
863 let is_correct = if *is_neutral {
864 "\\undet"
865 } else if *is_correct {
866 "\\right"
867 } else {
868 "\\wrong"
869 };
870 format!("{} ", is_correct)
871 }
872 Self::AplusQuestionnaireHints { .. } => "".to_string(),
873 Self::AplusQuestionnaireHint {
874 label,
875 show_when_not_selected,
876 question_type,
877 } => {
878 let show_when_not_selected = if *show_when_not_selected { "" } else { "!" };
879
880 use crate::common::AplusQuestionnaireType;
881
882 let reference = match question_type {
883 AplusQuestionnaireType::PickOne | AplusQuestionnaireType::PickAny => {
884 format!("\\ref{{{}}}", label)
885 }
886 AplusQuestionnaireType::FreeText => String::new(),
887 };
888
889 format!("\\feedback{{{}{}}}{{", show_when_not_selected, reference)
890 }
891 Self::AplusSubmit {
892 body_indent,
893 key,
894 difficulty,
895 max_points,
896 config,
897 submissions,
898 points_to_pass,
899 class,
900 title,
901 category,
902 status,
903 ajax,
904 allow_assistant_viewing,
905 allow_assistant_grading,
906 quiz,
907 url,
908 radar_tokenizer,
909 radar_minimum_match_tokens,
910 lti,
911 lti_resource_link_id,
912 lti_open_in_iframe,
913 lti_aplus_get_and_post,
914 } => {
915 let mut options = Vec::<String>::new();
918 if !config.is_empty() { options.push(format!("config={}", config)) }
919 options.push(format!("submissions={}", *submissions));
920 options.push(format!("points-to-pass={}", *points_to_pass));
921 if !class.is_empty() { options.push(format!("class={}", class)) }
922 if !title.is_empty() { options.push(format!("title={}", title)) };
923 if !category.is_empty() { options.push(format!("category={}", category)) }
924 match status {
925 AplusExerciseStatus::Ready => {
926 options.push(String::from("status=ready"))
927 }
928 AplusExerciseStatus::Unlisted => {
929 options.push(String::from("status=unlisted"))
930 }
931 AplusExerciseStatus::Hidden => {
932 options.push(String::from("status=hidden"))
933 }
934 AplusExerciseStatus::Enrollment => {
935 options.push(String::from("status=enrollment"))
936 }
937 AplusExerciseStatus::EnrollmentExt => {
938 options.push(String::from("status=enrollment_ext"))
939 }
940 AplusExerciseStatus::Maintenance => {
941 options.push(String::from("status=maintenance"))
942 }
943 };
944 if *ajax { options.push(String::from("ajax")) };
945 if *allow_assistant_viewing { options.push(String::from("allow-assistant-viewing")) };
946 if *allow_assistant_grading { options.push(String::from("allow-assistant-grading")) };
947 if *quiz { options.push(String::from("quiz")) };
948 if !lti.is_empty() { options.push(String::from("lti")) };
949 if !lti_resource_link_id.is_empty() { options.push(format!("resource_link_id={}", lti_resource_link_id))};
950 if *lti_open_in_iframe { options.push(String::from("lti_open_in_iframe")) };
951 if *lti_aplus_get_and_post { options.push(String::from("lti_aplus_get_and_post")) };
952
953 let option_string = format!("[{}]", options.join(LATEX_OPTION_DELIM));
954
955 format!(
956 "\\begin{{submit}}{}{{{}}}{{{}}}\n",
957 option_string.trim(), key, max_points
958 )
959 }
960
961 Self::AplusActiveElementInput {
962 key_for_input,
963 title,
964 default,
965 class,
966 width,
967 height,
968 clear,
969 input_type,
970 file,
971 } => {
972 use crate::common::{AplusActiveElementClear, AplusActiveElementInputType};
973
974 let mut options = String::new();
975
976 let title = if let Some(title) = title { title } else { "" };
977 if let Some(option) = default {
978 options = options + "default=" + option + LATEX_OPTION_DELIM
979 }
980 if let Some(option) = class {
981 options = options + "class=" + option + LATEX_OPTION_DELIM
982 }
983 if let Some(option) = width {
984 let width = rst_length_to_string(option);
985 options = options + "width=" + &width + LATEX_OPTION_DELIM
986 }
987 if let Some(option) = height {
988 let height = rst_length_to_string(option);
989 options = options + "height=" + &height + LATEX_OPTION_DELIM
990 }
991 if let Some(option) = clear {
992 match option {
993 AplusActiveElementClear::Both => {
994 options = options + "clear=both" + LATEX_OPTION_DELIM
995 }
996 AplusActiveElementClear::Left => {
997 options = options + "clear=left" + LATEX_OPTION_DELIM
998 }
999 AplusActiveElementClear::Right => {
1000 options = options + "clear=right" + LATEX_OPTION_DELIM
1001 }
1002 }
1003 }
1004 if let Some(option) = input_type {
1005 match option {
1006 AplusActiveElementInputType::File => {
1007 options = options + "type=file" + LATEX_OPTION_DELIM
1008 }
1009 AplusActiveElementInputType::Clickable => {
1010 options = options + "type=clickable" + LATEX_OPTION_DELIM
1011 }
1012 AplusActiveElementInputType::Dropdown(option_string) => {
1013 options =
1014 options + "type=dropdown:" + option_string + LATEX_OPTION_DELIM
1015 }
1016 }
1017 }
1018 if let (Some(input_type), Some(file)) = (input_type, file) {
1019 if let AplusActiveElementInputType::Clickable = input_type {
1020 options = options + "file=" + file + LATEX_OPTION_DELIM;
1021 } else {
1022 eprintln!("LarST writer found an alleged file path but active element input type not \"clickable\". Ignoring...")
1023 }
1024 }
1025
1026 if !options.is_empty() {
1027 options = format!("[{}]", options)
1028 }
1029
1030 format!("\\aeinput{}{{{}}}{{{}}}", options, key_for_input, title)
1031 }
1032
1033 Self::AplusActiveElementOutput {
1034 key_for_output,
1035 config,
1036 inputs,
1037 title,
1038 class,
1039 width,
1040 height,
1041 clear,
1042 output_type,
1043 submissions,
1044 scale_size,
1045 status,
1046 } => {
1047 let mut options = String::new();
1048 let title = if let Some(title) = title { title } else { "" };
1049
1050 use crate::common::{AplusActiveElementClear, AplusActiveElementOutputType};
1051
1052 options = options + "config=" + config + LATEX_OPTION_DELIM;
1053 if let Some(option) = class {
1055 options = options + "class=" + option + LATEX_OPTION_DELIM
1056 }
1057 if let Some(option) = width {
1058 let width = rst_length_to_string(option);
1059 options = options + "width=" + &width + LATEX_OPTION_DELIM
1060 }
1061 if let Some(option) = height {
1062 let height = rst_length_to_string(option);
1063 options = options + "height=" + &height + LATEX_OPTION_DELIM
1064 }
1065 if let Some(option) = clear {
1066 match option {
1067 AplusActiveElementClear::Both => {
1068 options = options + "clear=both" + LATEX_OPTION_DELIM
1069 }
1070 AplusActiveElementClear::Left => {
1071 options = options + "clear=left" + LATEX_OPTION_DELIM
1072 }
1073 AplusActiveElementClear::Right => {
1074 options = options + "clear=right" + LATEX_OPTION_DELIM
1075 }
1076 }
1077 }
1078 match output_type {
1079 AplusActiveElementOutputType::Text => {
1080 options = options + "type=text" + LATEX_OPTION_DELIM
1081 }
1082 AplusActiveElementOutputType::Image => {
1083 options = options + "type=image" + LATEX_OPTION_DELIM
1084 }
1085 }
1086 if let Some(option) = submissions {
1087 options = options + "submissions=" + &option.to_string() + LATEX_OPTION_DELIM
1088 }
1089 if *scale_size {
1090 options = options + "scale-size" + LATEX_OPTION_DELIM
1091 }
1092 match status {
1093 AplusExerciseStatus::Ready => {
1094 options = options + "status=ready" + LATEX_OPTION_DELIM
1095 }
1096 AplusExerciseStatus::Unlisted => {
1097 options = options + "status=unlisted" + LATEX_OPTION_DELIM
1098 }
1099 AplusExerciseStatus::Hidden => {
1100 options = options + "status=hidden" + LATEX_OPTION_DELIM
1101 }
1102 AplusExerciseStatus::Enrollment => {
1103 options = options + "status=enrollment" + LATEX_OPTION_DELIM
1104 }
1105 AplusExerciseStatus::EnrollmentExt => {
1106 options = options + "status=enrollment-ext" + LATEX_OPTION_DELIM
1107 }
1108 AplusExerciseStatus::Maintenance => {
1109 options = options + "status=maintenance" + LATEX_OPTION_DELIM
1110 }
1111 }
1112
1113 if !options.is_empty() {
1114 options = format!("[{}]", options)
1115 }
1116
1117 format!(
1118 "\\aeoutput{}{{{}}}{{{}}}{{{}}}",
1119 options, key_for_output, inputs, title
1120 )
1121 }
1122 };
1123
1124 pre_string
1125 }
1126
1127 fn larst_post_order_string(&self, ref_names: Option<&Vec<String>>, rustla_options: &ruSTLaOptions) -> String {
1129 let post_string = match self {
1130 Self::Abbreviation { .. } => todo!(),
1131 Self::AbsoluteURI { .. } => "".to_string(),
1132 Self::Acronym { .. } => todo!(),
1133 Self::Address => todo!(),
1134 Self::Admonition { variant, .. } => {
1135 use crate::doctree::directives::AdmonitionType;
1136 match variant {
1137 AdmonitionType::Admonition { title } => format!("\\end{{admonition}}\n\n"),
1138 _ => format!("\\end{{{}}}\n\n", variant.to_string()),
1139 }
1140 }
1141 Self::Attribution { .. } => "\n".to_string(),
1142 Self::Author { .. } => todo!(),
1143 Self::Authors { .. } => todo!(),
1144 Self::AutomaticSectionNumbering { .. } => todo!(),
1145 Self::BlockQuote { .. } => "\\end{quotation}\n\n".to_string(),
1146 Self::BulletList { .. } => format!("\\end{{itemize}}\n\n"),
1147 Self::BulletListItem { .. } => "".to_string(),
1148 Self::Caption { .. } => "}\n".to_string(),
1149 Self::Citation { .. } => "\n".to_string(),
1150 Self::CitationReference { .. } => "".to_string(),
1151 Self::Class { .. } => "".to_string(),
1152 Self::Classifier { .. } => todo!(),
1153 Self::Code { .. } => "\\end{codeblock}\n\n".to_string(),
1154 Self::ColSpec { .. } => todo!(),
1155 Self::Comment { .. } => "".to_string(),
1156 Self::CompoundParagraph { .. } => "\n".to_string(),
1157 Self::Contact { .. } => todo!(),
1158 Self::Container { .. } => todo!(),
1159 Self::Copyright { .. } => todo!(),
1160 Self::CSVTable { .. } => todo!(),
1161 Self::Date => todo!(),
1162 Self::Decoration => todo!(),
1163 Self::Definition => todo!(),
1164 Self::DefinitionList { .. } => "\\end{itemize}\n\n".to_string(),
1165 Self::DefinitionListItem { .. } => "\n".to_string(),
1166 Self::Description => todo!(),
1167 Self::DocInfo => todo!(),
1168 Self::DoctestBlock { .. } => todo!(),
1169 Self::Document { .. } => if rustla_options.is_full_document() {
1170 "\\end{document}\n".to_string()
1171 } else {
1172 String::new()
1173 },
1174 Self::Emphasis { .. } => "".to_string(),
1175 Self::EmptyLine => "".to_string(),
1176 Self::Entry { is_last } => {
1177 let suffix = if *is_last { "" } else { "&\n" };
1178 format!("{}", suffix)
1179 }
1180 Self::EnumeratedList { .. } => "\\end{enumerate}\n\n".to_string(),
1181 Self::EnumeratedListItem { .. } => "".to_string(),
1182 Self::ExternalHyperlinkTarget { .. } => "\n".to_string(),
1183 Self::Field => todo!(),
1184 Self::FieldBody { .. } => todo!(),
1185 Self::FieldList { .. } => "\\end{itemize}\n\n".to_string(),
1186 Self::FieldListItem { .. } => "\n".to_string(),
1187 Self::Figure { .. } => {
1188 let anchors = self.anchor_string(ref_names);
1189 format!("{}\\end{{center}}\n\n", anchors)
1190 },
1191 Self::Footer { .. } => todo!(),
1192 Self::Footnote { .. } => String::from("}\n\n"),
1193 Self::FootnoteReference { .. } => String::new(),
1194 Self::Header { .. } => todo!(),
1195 Self::Generated => todo!(),
1196 Self::Image { inline, .. } => {
1197 if *inline {
1198 String::new()
1199 } else {
1200 String::from("\n")
1201 }
1202 },
1203 Self::Include { .. } => "".to_string(),
1204 Self::IndirectHyperlinkTarget { .. } => todo!(),
1205 Self::Inline { .. } => todo!(),
1206 Self::InlineTarget { .. } => todo!(),
1207 Self::InterpretedText { .. } => String::new(),
1208 Self::Label { .. } => todo!(),
1209 Self::Legend { .. } => "\n".to_string(),
1210 Self::Line { .. } => "\n".to_string(),
1211 Self::LineBlock { .. } => "\n".to_string(),
1212 Self::ListTable { .. } => "\\end{tabular}\n\n".to_string(),
1213 Self::Literal { .. } => "".to_string(),
1214 Self::LiteralBlock { .. } => "\n\\end{codeblock}\n\n".to_string(),
1215 Self::Math { .. } => "".to_string(),
1216 Self::MathBlock { .. } => "\\end{equation}\n\n".to_string(),
1217 Self::OptionList { .. } => "\n".to_string(),
1218 Self::OptionListItem { .. } => "\n".to_string(),
1219 Self::OptionString { .. } => todo!(),
1220 Self::Organization { .. } => todo!(),
1221 Self::Paragraph { .. } => "\n\n".to_string(),
1222 Self::ParsedLiteralBlock { .. } => "\n\n".to_string(),
1223 Self::Pending { .. } => todo!(),
1224 Self::Problematic { .. } => todo!(),
1225 Self::Raw { .. } => "\\end{raw}\n\n".to_string(),
1226 Self::Reference { .. } => "".to_string(),
1227 Self::Revision { .. } => todo!(),
1228 Self::Row { .. } => todo!(),
1229 Self::Rubric { .. } => "\n".to_string(),
1230 Self::Section { .. } => "".to_string(),
1231 Self::Sidebar { .. } => "\n".to_string(),
1232 Self::Status { .. } => todo!(),
1233 Self::StrongEmphasis { .. } => "".to_string(),
1234 Self::Subscript { .. } => "".to_string(),
1235 Self::SubstitutionDefinition { .. } => "\n".to_string(),
1236 Self::SubstitutionReference { .. } => "".to_string(),
1237 Self::Subtitle { .. } => "".to_string(),
1238 Self::Superscript { .. } => "".to_string(),
1239 Self::SystemMessage { .. } => todo!(),
1240 Self::Table { .. } => "\n".to_string(),
1241 Self::Target { .. } => "\n".to_string(),
1242 Self::TBody { .. } => "\n".to_string(),
1243 Self::Term { .. } => todo!(),
1244 Self::Text { .. } => "".to_string(),
1245 Self::TGroup { .. } => todo!(),
1246 Self::THead { .. } => "\n".to_string(),
1247 Self::TRow => "\\\\\n".to_string(),
1248 Self::Title { .. } => todo!(),
1249 Self::TitleReference { .. } => "".to_string(),
1250 Self::Topic { .. } => todo!(),
1251 Self::Transition { .. } => "\n".to_string(),
1252 Self::UnknownDirective { directive_name, .. } => {
1253 format!("\\end{{{}}}\n\n", directive_name.to_lowercase())
1254 }
1255 Self::Version { .. } => todo!(),
1256 Self::WhiteSpace { .. } => "".to_string(),
1257
1258 Self::SphinxOnly {
1262 expression,
1263 body_indent,
1264 } => "\\end{only}\n\n".to_string(),
1265 Self::SphinxCodeBlock { .. } => String::from("\\end{codeblock}\n\n"),
1266
1267 Self::AplusPOI { .. } => "\\end{poi}\n\n".to_string(),
1271 Self::AplusColBreak => "".to_string(),
1272 Self::AplusQuestionnaire { .. } => "\\end{quiz}\n\n".to_string(),
1273 Self::AplusPickOne { .. } => "\\end{pick}\n\n".to_string(),
1274 Self::AplusPickAny { .. } => "\\end{pick}\n\n".to_string(),
1275 Self::AplusFreeText { .. } => "\\end{freetext}\n\n".to_string(),
1276 Self::AplusPickChoices { .. } => "\\end{answers}\n\n".to_string(),
1277 Self::AplusPickChoice { label, .. } => {
1278 let label = format!(" \\label{{{}}}", label);
1279 format!("{}\n", label)
1280 }
1281 Self::AplusQuestionnaireHints { .. } => "\n".to_string(),
1282 Self::AplusQuestionnaireHint { .. } => "}\n".to_string(),
1283 Self::AplusSubmit { .. } => "\\end{submit}\n\n".to_string(),
1284 Self::AplusActiveElementInput { .. } => "\n\n".to_string(),
1285 Self::AplusActiveElementOutput { .. } => "\n\n".to_string(),
1286 };
1287
1288 post_string
1289 }
1290
1291 fn anchor_string (
1293 &self,
1294 refnames_from_node: Option<&Vec<String>>,
1295 ) -> String {
1296
1297 let mut anchor_string = String::new();
1298
1299 let (refname, anchor_type_str): (Option<&String>, &str) = match self {
1300 Self::Abbreviation { .. } => (None, ""),
1301 Self::AbsoluteURI { .. } => (None, ""),
1302 Self::Acronym { .. } => (None, ""),
1303 Self::Address => (None, ""),
1304 Self::Admonition { variant, .. } => (None, ""),
1305 Self::Attribution { .. } => (None, ""),
1306 Self::Author { .. } => (None, ""),
1307 Self::Authors { .. } => (None, ""),
1308 Self::AutomaticSectionNumbering { .. } => (None, ""),
1309 Self::BlockQuote { .. } => (None, ""),
1310 Self::BulletList { .. } => (None, ""),
1311 Self::BulletListItem { .. } => (None, ""),
1312 Self::Caption { .. } => (None, ""),
1313 Self::Citation { .. } => (None, ""),
1314 Self::CitationReference { .. } => (None, ""),
1315 Self::Class { .. } => (None, ""),
1316 Self::Classifier { .. } => (None, ""),
1317 Self::Code { .. } => (None, ""),
1318 Self::ColSpec { .. } => (None, ""),
1319 Self::Comment { .. } => (None, ""),
1320 Self::CompoundParagraph { .. } => (None, ""),
1321 Self::Contact { .. } => (None, ""),
1322 Self::Container { .. } => (None, ""),
1323 Self::Copyright { .. } => (None, ""),
1324 Self::CSVTable { .. } => (None, ""),
1325 Self::Date => (None, ""),
1326 Self::Decoration => (None, ""),
1327 Self::Definition => (None, ""),
1328 Self::DefinitionList { .. } => (None, ""),
1329 Self::DefinitionListItem { .. } => (None, ""),
1330 Self::Description => (None, ""),
1331 Self::DocInfo => (None, ""),
1332 Self::DoctestBlock { .. } => (None, ""),
1333 Self::Document { .. } => (None, ""),
1334 Self::Emphasis { .. } => (None, ""),
1335 Self::EmptyLine => (None, ""),
1336 Self::Entry { is_last } => (None, ""),
1337 Self::EnumeratedList { .. } => (None, ""),
1338 Self::EnumeratedListItem { .. } => (None, ""),
1339 Self::ExternalHyperlinkTarget { .. } => (None, ""),
1340 Self::Field => (None, ""),
1341 Self::FieldBody { .. } => (None, ""),
1342 Self::FieldList { .. } => (None, ""),
1343 Self::FieldListItem { .. } => (None, ""),
1344 Self::Figure { name, .. } => (name.as_ref(), "label"),
1345 Self::Footer { .. } => (None, ""),
1346 Self::Footnote { .. } => (None, ""),
1347 Self::FootnoteReference { .. } => (None, ""),
1348 Self::Header { .. } => (None, ""),
1349 Self::Generated => (None, ""),
1350 Self::Image { .. } => (None, ""),
1351 Self::Include { .. } => (None, ""),
1352 Self::IndirectHyperlinkTarget { .. } => (None, ""),
1353 Self::Inline { .. } => (None, ""),
1354 Self::InlineTarget { .. } => (None, ""),
1355 Self::InterpretedText { .. } => (None, ""),
1356 Self::Label { .. } => (None, ""),
1357 Self::Legend { .. } => (None, ""),
1358 Self::Line { .. } => (None, ""),
1359 Self::LineBlock { .. } => (None, ""),
1360 Self::ListTable { .. } => (None, ""),
1361 Self::Literal { .. } => (None, ""),
1362 Self::LiteralBlock { .. } => (None, ""),
1363 Self::Math { .. } => (None, ""),
1364 Self::MathBlock { name, .. } => (name.as_ref(), "rstlabel"),
1365 Self::OptionList { .. } => (None, ""),
1366 Self::OptionListItem { .. } => (None, ""),
1367 Self::OptionString { .. } => (None, ""),
1368 Self::Organization { .. } => (None, ""),
1369 Self::Paragraph { .. } => (None, ""),
1370 Self::ParsedLiteralBlock { .. } => (None, ""),
1371 Self::Pending { .. } => (None, ""),
1372 Self::Problematic { .. } => (None, ""),
1373 Self::Raw { .. } => (None, ""),
1374 Self::Reference { .. } => (None, ""),
1375 Self::Revision { .. } => (None, ""),
1376 Self::Row { .. } => (None, ""),
1377 Self::Rubric { .. } => (None, ""),
1378 Self::Section { .. } => (None, "rstlabel"),
1379 Self::Sidebar { .. } => (None, ""),
1380 Self::Status { .. } => (None, ""),
1381 Self::StrongEmphasis { .. } => (None, ""),
1382 Self::Subscript { .. } => (None, ""),
1383 Self::SubstitutionDefinition { .. } => (None, ""),
1384 Self::SubstitutionReference { .. } => (None, ""),
1385 Self::Subtitle { .. } => (None, ""),
1386 Self::Superscript { .. } => (None, ""),
1387 Self::SystemMessage { .. } => (None, ""),
1388 Self::Table { .. } => (None, ""),
1389 Self::Target { .. } => (None, ""),
1390 Self::TBody { .. } => (None, ""),
1391 Self::Term { .. } => (None, ""),
1392 Self::Text { .. } => (None, ""),
1393 Self::TGroup { .. } => (None, ""),
1394 Self::THead { .. } => (None, ""),
1395 Self::TRow => (None, ""),
1396 Self::Title { .. } => (None, ""),
1397 Self::TitleReference { .. } => (None, ""),
1398 Self::Topic { .. } => (None, ""),
1399 Self::Transition { .. } => (None, ""),
1400 Self::UnknownDirective { directive_name, .. } => (None, ""),
1401 Self::Version { .. } => (None, ""),
1402 Self::WhiteSpace { .. } => (None, ""),
1403
1404 Self::SphinxOnly { .. } => (None, ""),
1408 Self::SphinxCodeBlock { .. } => (None, ""),
1409
1410 Self::AplusPOI { .. } => (None, ""),
1414 Self::AplusColBreak => (None, ""),
1415 Self::AplusQuestionnaire { .. } => (None, ""),
1416 Self::AplusPickOne { .. } => (None, ""),
1417 Self::AplusPickAny { .. } => (None, ""),
1418 Self::AplusFreeText { .. } => (None, ""),
1419 Self::AplusPickChoices { .. } => (None, ""),
1420 Self::AplusPickChoice { label, .. } => (None, ""),
1421 Self::AplusQuestionnaireHints { .. } => (None, ""),
1422 Self::AplusQuestionnaireHint { .. } => (None, ""),
1423 Self::AplusSubmit { .. } => (None, ""),
1424 Self::AplusActiveElementInput { .. } => (None, ""),
1425 Self::AplusActiveElementOutput { .. } => (None, ""),
1426 };
1427
1428 if let Some(name) = refname {
1432 match anchor_type_str {
1433 "rstlabel" => {
1434 anchor_string += &format!("\\{}{{{}}}\n", anchor_type_str, name);
1435 }
1436 "label" => {
1437 anchor_string += &format!("\\{}{{{}}}\n", anchor_type_str, name);
1438 }
1439 "hypertarget" => {
1440 anchor_string += &format!("\\{}{{{}}}{{{}}}\n", anchor_type_str, name, name);
1441 }
1442 "" => {
1443 }
1445 _ => unreachable!("No anchor of type {}. Computer says no...", anchor_type_str)
1446 }
1447 }
1448 if let Some(names) = refnames_from_node {
1449 for name in names {
1450 match anchor_type_str {
1451 "rstlabel" => {
1452 anchor_string += &format!("\\{}{{{}}}\n", anchor_type_str, name);
1453 }
1454 "label" => {
1455 anchor_string += &format!("\\{}{{{}}}\n", anchor_type_str, name);
1456 }
1457 "hypertarget" => {
1458 anchor_string += &format!("\\{}{{{}}}{{{}}}\n", anchor_type_str, name, name);
1459 }
1460 "" => {
1461 }
1463 _ => unreachable!("No anchor of type {}. Computer says no...", anchor_type_str)
1464 }
1465 }
1466 }
1467
1468 anchor_string
1469 }
1470}
1471
1472fn aplus_cls_contents() -> &'static str {
1483 r#"%
1484% The LaRST Project
1485%
1486% alpus -- Documentclass for the direct LaTeX compilation of A+ materials
1487%
1488% (c) 2019-2020 Tomi Janhunen
1489
1490\NeedsTeXFormat{LaTeX2e}
1491\ProvidesClass{aplus}
1492
1493\LoadClass{book}
1494\RequirePackage{url}
1495\RequirePackage{graphicx}
1496\RequirePackage[breakable,most]{tcolorbox}
1497\RequirePackage{amsmath}
1498\RequirePackage{amssymb}
1499\RequirePackage{pifont}
1500\RequirePackage{keyval}
1501\RequirePackage{ifthen}
1502\RequirePackage{xstring}
1503\RequirePackage{comment}
1504\RequirePackage{environ}
1505\RequirePackage{fancyvrb}
1506\RequirePackage{hyperref}
1507
1508% Font issues
1509\RequirePackage[T1]{fontenc}
1510
1511% Reset page dimensions
1512\usepackage[nohead,nofoot,top=1in,margin=1in]{geometry}
1513\pagestyle{empty}
1514
1515% \newcommand{\chapter}[1]{{\Huge\textbf{#1}}}
1516
1517% Set fonts toward ``Read the Docs''
1518\usepackage[scaled]{helvet}
1519\renewcommand\familydefault{\sfdefault}
1520
1521% No indentation
1522\setlength{\parindent}{0pt}
1523\setlength{\parskip}{0.5\baselineskip}
1524
1525% Remove (sub)section numbering
1526% \makeatletter
1527% \renewcommand{\@seccntformat}[1]{}
1528% \makeatother
1529
1530% Unification of labels
1531\global\def\labelhere{}
1532\newcommand{\rstlabel}[1]{\global\def\labelhere{\hypertarget{#1}{}\label{#1}}}
1533
1534% RST Simulations in LaTeX
1535
1536\newcommand{\aplus}[2]{}
1537
1538\makeatletter
1539\long\def\notext#1{}
1540\newenvironment{only}[1][foo]{%
1541 \ifthenelse{\equal{#1}{latex}}%
1542 {}{\Collect@Body\notext}
1543 }{}
1544\makeatother
1545
1546\newenvironment{raw}{}{}
1547\RenewEnviron{raw}{}{}
1548
1549\newcommand{\code}[1]{\texttt{#1}}
1550
1551% Blocks of code
1552
1553\makeatletter
1554\define@key{codeblock}{python}[]{}
1555\makeatother
1556
1557\newcommand\innercodeblock[1][]{#1}
1558\newenvironment{codeblock}{ \bgroup\verbatim\innercodeblock }{ \endverbatim\egroup }
1559% \newenvironment{codeblock}[1][]{\begin{BVerbatim}}{\end{BVerbatim}}
1560
1561% File download
1562
1563\newcommand{\download}[2]{\par\texttt{#1}\footnote{\url{#2}}}
1564\newcommand{\rstclass}[1]{}
1565\newcommand{\feedback}[2]{\par\textbf{#1}. #2 \\}
1566
1567\newenvironment{toggle}[1]{\textbf{#1}. }{}
1568
1569
1570% Points of interest (slide-type objects within material)
1571
1572\makeatletter
1573\define@key{poi}{hidden}[]{}
1574\define@key{poi}{columns}[]{\def\poi@colums{#1}}
1575\define@key{poi}{id}[]{\def\poi@id{#1}}
1576\define@key{poi}{next}[]{\def\poi@next{#1}}
1577\define@key{poi}{prev}[]{\def\poi@prev{#1}}
1578\define@key{poi}{bgimg}[]{\def\poi@bgimg{#1}}
1579\makeatother
1580
1581\newcommand{\newcol}{\newpage} % Semantic mismatch
1582\newenvironment{poi}[2][]{%
1583\setkeys{poi}{#1}
1584\par\noindent\begin{large}\begin{tcolorbox}[width=\textwidth,adjusted title=#2]%
1585}{%
1586\end{tcolorbox}\end{large}}
1587
1588% Active elements
1589
1590\makeatletter
1591\newlength{\ae@width}
1592\newlength{\ae@height}
1593\define@key{aelement}{width}[]{\def\ae@width{#1}}
1594\define@key{aelement}{height}[]{\def\ae@height{#1}}
1595\define@key{aelement}{class}[]{\def\ae@class{#1}}
1596\define@key{aelement}{type}[]{\def\ae@type{#1}}
1597\setkeys{aelement}{width=\textwidth,height=\baselineskip,type=pdf,class=left}%
1598\newcommand{\aeinput}[2][]{\setkeys{aelement}{#1}}
1599\newcommand{\aeoutput}[3][]{\setkeys{aelement}{#1}}
1600\makeatother
1601
1602% Submission fields
1603
1604\makeatletter
1605\define@key{submit}{config}[]{\def\sbm@config{#1}}
1606\define@key{submit}{submissions}[]{\def\sbm@submissions{#1}}
1607\define@key{submit}{points-to-pass}[]{\def\sbm@ptp{#1}}
1608\define@key{submit}{class}[]{\def\sbm@class{#1}}
1609\define@key{submit}{title}[]{\def\sbm@title{#1}}
1610\define@key{submit}{category}[]{\def\sbm@category{#1}}
1611\define@key{submit}{status}[]{\def\sbm@status{#1}}
1612\define@key{submit}{allow-assistant-viewing}[]{\def\sbm@viewing{#1}}
1613\define@key{submit}{allow-assistant-grading}[]{\def\sbm@grading{#1}}
1614\define@key{submit}{url}[]{\def\sbm@url{#1}}
1615\define@key{submit}{lti}[]{\def\sbm@lti{#1}}
1616\define@key{submit}{ajax}[]{\def\sbm@ajax{true}}
1617\define@key{submit}{quiz}[]{\def\sbm@quiz{true}}
1618\makeatother
1619
1620\newenvironment{submit}[2][]{%
1621\setkeys{submit}{#1}%
1622\par\noindent\begin{tcolorbox}[width=\textwidth,adjusted title=#2]%
1623}{%
1624\end{tcolorbox}}
1625
1626% Quizzes
1627
1628\newcommand{\wrong}{\item[\fbox{\phantom{\large x}}]}
1629\renewcommand{\right}{\item[\fbox{\large x}]}
1630\newcommand{\undet}{\item[\fbox{\large *}]}
1631
1632\newcounter{question}\stepcounter{question}
1633\newenvironment{answers}{\begin{enumerate}}{\end{enumerate}}
1634
1635\makeatletter
1636\define@key{quiz}{submissions}[]{\def\qz@submissions{#1}}
1637\define@key{quiz}{points-to-pass}[]{\def\qz@points{#1}}
1638\define@key{quiz}{title}[]{\def\qz@title{#1}}
1639\define@key{quiz}{pick-randomly}[]{\def\qz@randomly{#1}}
1640\define@key{quiz}{category}[]{\def\qz@category{#1}}
1641\define@key{quiz}{status}[]{\def\qz@status{#1}}
1642\define@key{quiz}{reveal-model-at-max-submissions}[]{\def\qz@reveal{#1}}
1643\define@key{quiz}{show-model}[]{\def\qz@show{#1}}
1644\define@key{quiz}{allow-assistant-viewing}[]{\def\qz@viewing{#1}}
1645\define@key{quiz}{allow-assistant-grading}[]{\def\qz@grading{#1}}
1646\define@key{quiz}{feedback}[]{\def\qz@feedback{true}}
1647\define@key{quiz}{no-override}[]{\def\qz@noover{true}}
1648\define@key{quiz}{preserve-questions-between-attempts}[]{\def\qz@preserve{true}}
1649\setkeys{quiz}{}%
1650\newenvironment{quiz}[3][]{%
1651\setkeys{quiz}{#1}{}%
1652\section*{Quiz #2}}{\setcounter{question}{1}}
1653\makeatother
1654
1655% Pick
1656
1657\makeatletter
1658\define@key{pick}{class}[]{\def\pick@class{#1}}
1659\define@key{pick}{key}[]{\def\pick@key{#1}}
1660\define@key{pick}{randomized}[]{\def\pick@randomized{#1}}
1661\define@key{pick}{correct-count}[]{\def\pick@correct{#1}}
1662\define@key{pick}{required}[]{\def\pick@required{true}}
1663\define@key{pick}{partial-points}[]{\def\pick@partial{true}}
1664\setkeys{pick}{}%
1665\newenvironment{pick}[3][]{%
1666\setkeys{pick}{#1}{}%
1667\par\textbf{Q\thequestion:}~}{\stepcounter{question}}
1668\makeatother
1669
1670% Freetext
1671
1672\makeatletter
1673\newlength{\ft@height}
1674\newlength{\ft@length}
1675\define@key{freetext}{required}[]{\def\ft@required{true}}
1676\define@key{freetext}{length}[]{\def\ft@length{#1}}
1677\define@key{freetext}{height}[]{\def\ft@height{#1}}
1678\define@key{freetext}{class}[]{\def\ft@class{#1}}
1679\define@key{freetext}{key}[]{\def\ft@key{#1}}
1680\setkeys{freetext}{length=100em,height=5\baselineskip,class=left}%
1681\newenvironment{freetext}[4][]{%
1682\setkeys{freetext}{#1}{}
1683\par\textbf{Q\thequestion:}~}{\stepcounter{question}}
1684\makeatother
1685
1686% LaTeX environments (assumed by default, some used in limited ways)
1687
1688% \begin{document} ... \end{document}
1689% \begin{itemize} ... \item ... \end{itemize}
1690% \begin{enumerate} ... \item ... \end{enumerate}
1691% \begin{tabular}[...] ... & ... & ... \\ ... \end{tabular}
1692% \begin{thebibliography}{...} ... \end{thebibligraphy}
1693% \begin{equation} ... \end{equation}
1694% \begin{center} ... \end{center}
1695
1696% LaTeX commands (assumed by default)
1697
1698% \documentclass{}
1699% \bibliographystyle{...}
1700% \tableofcontents
1701% \contentsline{...}{...}{...}
1702% \chapter{...}
1703% \section{...}
1704% \subsection{...}
1705% \emph{...} or {\em ...}
1706% \textit{...}
1707% \textbf{...} or {\bf ...}
1708% \texttt{...}
1709% \captionof{...}{...}
1710% \newcounter{...}
1711% \the...
1712% \stepcounter{...}
1713% \refstepcounter{...}
1714% \addtocounter{...}{...}
1715% \setcounter{...}{...}
1716% \numberwithin{...}{...}
1717% \include{...}
1718% \input{...}
1719% \includegraphics[...]{...}
1720% \cite{...}
1721% \ref{...}
1722% \label{...}
1723% \url{...}
1724% \href{...}{...}
1725% \hyperref[...]{...}
1726% \hypertarget{...}{...}
1727% \hyperlink{...}{...}
1728% \textbackslash
1729% \textasciicircum
1730% \textunderscore
1731% \textasciitilde
1732% \nbspc
1733% \aa
1734% \AA
1735% \hrulefill
1736
1737"#
1738}
1739
1740fn rst_length_to_string(length: &Length) -> String {
1744 match length {
1745 Length::Em(val) => val.to_string() + "em",
1746 Length::Ex(val) => val.to_string() + "ex",
1747 Length::Mm(val) => val.to_string() + "mm",
1748 Length::Cm(val) => val.to_string() + "cm",
1749 Length::In(val) => val.to_string() + "in",
1750 Length::Px(val) => val.to_string() + "px",
1751 Length::Pt(val) => val.to_string() + "pt",
1752 Length::Pc(val) => val.to_string() + "pc",
1753 }
1754}
1755
1756fn html_alignment_to_string(alignment: &HTMLAlignment) -> String {
1760 match alignment {
1761 HTMLAlignment::Top => String::from("top"),
1762 HTMLAlignment::Middle => String::from("middle"),
1763 HTMLAlignment::Bottom => String::from("bottom"),
1764 HTMLAlignment::Left => String::from("left"),
1765 HTMLAlignment::Center => String::from("center"),
1766 HTMLAlignment::Right => String::from("right"),
1767 }
1768}
1769
1770fn horizontal_alignment_to_string(alignment: &HorizontalAlignment) -> String {
1774 match alignment {
1775 HorizontalAlignment::Left => String::from("left"),
1776 HorizontalAlignment::Center => String::from("center"),
1777 HorizontalAlignment::Right => String::from("right"),
1778 }
1779}
1780
1781fn rstlabel_string_from_labels (labels: Option<&Vec<String>>) -> String {
1784
1785 if let Some(labels) = labels {
1786 let mut label_string = String::new();
1787 for label in labels {
1788 let rst_label = format!("\\rstlabel{{{}}}\n", label);
1789 label_string.push_str(&rst_label)
1790 }
1791 label_string
1792 } else {
1793 String::new()
1794 }
1795}
1796
1797fn latex_label_string_from_labels (labels: Option<&Vec<String>>) -> String {
1800
1801 if let Some(labels) = labels {
1802 let mut label_string = String::new();
1803 for label in labels {
1804 let rst_label = format!("\\label{{{}}}\n", label);
1805 label_string.push_str(&rst_label)
1806 }
1807 label_string
1808 } else {
1809 String::new()
1810 }
1811}