You've already forked comprehensive-rust
							
							
				mirror of
				https://github.com/google/comprehensive-rust.git
				synced 2025-10-31 08:37:45 +02:00 
			
		
		
		
	Use tables to summarize course content (#2005)
This is more friendly to translation (as it can share the translation of the title). This fixes #1982.
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							45aa43f406
						
					
				
				
					commit
					bb44b1d7a8
				
			| @@ -36,10 +36,10 @@ | ||||
| //! top-level item are treated as further slides in the same segment. | ||||
|  | ||||
| use crate::frontmatter::{split_frontmatter, Frontmatter}; | ||||
| use crate::markdown::{duration, relative_link}; | ||||
| use crate::markdown::{duration, Table}; | ||||
| use mdbook::book::{Book, BookItem, Chapter}; | ||||
| use std::fmt::Write; | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::path::PathBuf; | ||||
|  | ||||
| /// Duration, in minutes, of breaks between segments in the course. | ||||
| const BREAK_DURATION: u64 = 10; | ||||
| @@ -245,32 +245,26 @@ impl Course { | ||||
|  | ||||
|     /// Generate a Markdown schedule for this course, for placement at the given | ||||
|     /// path. | ||||
|     pub fn schedule(&self, at_source_path: impl AsRef<Path>) -> String { | ||||
|     pub fn schedule(&self) -> String { | ||||
|         let mut outline = String::from("Course schedule:\n"); | ||||
|         for session in self { | ||||
|             writeln!( | ||||
|                 &mut outline, | ||||
|                 " * {} ({}, including breaks)", | ||||
|                 " * {} ({}, including breaks)\n", | ||||
|                 session.name, | ||||
|                 duration(session.minutes()) | ||||
|             ) | ||||
|             .unwrap(); | ||||
|             let mut segments = Table::new(["Segment".into(), "Duration".into()]); | ||||
|             for segment in session { | ||||
|                 // Skip short segments (welcomes, wrap-up, etc.) | ||||
|                 if segment.minutes() == 0 { | ||||
|                     continue; | ||||
|                 } | ||||
|                 writeln!( | ||||
|                     &mut outline, | ||||
|                     "   * [{}]({}) ({})", | ||||
|                     segment.name, | ||||
|                     relative_link( | ||||
|                         &at_source_path, | ||||
|                         &segment.slides[0].source_paths[0] | ||||
|                     ), | ||||
|                     duration(segment.minutes()) | ||||
|                 ) | ||||
|                 .unwrap(); | ||||
|                 segments | ||||
|                     .add_row([segment.name.clone(), duration(segment.minutes())]); | ||||
|             } | ||||
|             writeln!(&mut outline, "{}\n", segments).unwrap(); | ||||
|         } | ||||
|         outline | ||||
|     } | ||||
| @@ -313,24 +307,18 @@ impl Session { | ||||
|  | ||||
|     /// Generate a Markdown outline for this session, for placement at the given | ||||
|     /// path. | ||||
|     pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String { | ||||
|         let mut outline = String::from("In this session:\n"); | ||||
|     pub fn outline(&self) -> String { | ||||
|         let mut segments = Table::new(["Segment".into(), "Duration".into()]); | ||||
|         for segment in self { | ||||
|             // Skip short segments (welcomes, wrap-up, etc.) | ||||
|             if segment.minutes() == 0 { | ||||
|                 continue; | ||||
|             } | ||||
|             writeln!( | ||||
|                 &mut outline, | ||||
|                 " * [{}]({}) ({})", | ||||
|                 segment.name, | ||||
|                 relative_link(&at_source_path, &segment.slides[0].source_paths[0]), | ||||
|                 duration(segment.minutes()) | ||||
|             ) | ||||
|             .unwrap(); | ||||
|             segments.add_row([segment.name.clone(), duration(segment.minutes())]); | ||||
|         } | ||||
|         writeln!(&mut outline,"\nIncluding {BREAK_DURATION} minute breaks, this session should take about {}", duration(self.minutes())).unwrap(); | ||||
|         outline | ||||
|         format!( | ||||
|             "Including {BREAK_DURATION} minute breaks, this session should take about {}. It contains:\n\n{}", | ||||
|             duration(self.minutes()), segments) | ||||
|     } | ||||
|  | ||||
|     /// Return the total duration of this session. | ||||
| @@ -394,28 +382,19 @@ impl Segment { | ||||
|         self.into_iter().map(|s| s.minutes()).sum() | ||||
|     } | ||||
|  | ||||
|     pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String { | ||||
|         let mut outline = String::from("In this segment:\n"); | ||||
|     pub fn outline(&self) -> String { | ||||
|         let mut slides = Table::new(["Slide".into(), "Duration".into()]); | ||||
|         for slide in self { | ||||
|             if slide.minutes() == 0 { | ||||
|                 continue; | ||||
|             } | ||||
|             writeln!( | ||||
|                 &mut outline, | ||||
|                 " * [{}]({}) ({})", | ||||
|                 slide.name, | ||||
|                 relative_link(&at_source_path, &slide.source_paths[0]), | ||||
|                 duration(slide.minutes()) | ||||
|             ) | ||||
|             .unwrap(); | ||||
|             slides.add_row([slide.name.clone(), duration(slide.minutes())]); | ||||
|         } | ||||
|         writeln!( | ||||
|             &mut outline, | ||||
|             "\nThis segment should take about {}", | ||||
|             duration(self.minutes()) | ||||
|         format!( | ||||
|             "This segment should take about {}. It contains:\n\n{}", | ||||
|             duration(self.minutes()), | ||||
|             slides, | ||||
|         ) | ||||
|         .unwrap(); | ||||
|         outline | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| use std::fmt; | ||||
| use std::path::Path; | ||||
|  | ||||
| /// Given a source_path for the markdown file being rendered and a source_path | ||||
| @@ -57,6 +58,46 @@ pub fn duration(mut minutes: u64) -> String { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Table implements Display to format a two-dimensional table as markdown, | ||||
| /// following https://github.github.com/gfm/#tables-extension-. | ||||
| pub struct Table<const N: usize> { | ||||
|     header: [String; N], | ||||
|     rows: Vec<[String; N]>, | ||||
| } | ||||
|  | ||||
| impl<const N: usize> Table<N> { | ||||
|     pub fn new(header: [String; N]) -> Self { | ||||
|         Self { header, rows: Vec::new() } | ||||
|     } | ||||
|  | ||||
|     pub fn add_row(&mut self, row: [String; N]) { | ||||
|         self.rows.push(row); | ||||
|     } | ||||
|  | ||||
|     fn write_row<'a, I: Iterator<Item = &'a str>>( | ||||
|         &self, | ||||
|         f: &mut fmt::Formatter<'_>, | ||||
|         iter: I, | ||||
|     ) -> fmt::Result { | ||||
|         write!(f, "|")?; | ||||
|         for cell in iter { | ||||
|             write!(f, " {} |", cell)?; | ||||
|         } | ||||
|         write!(f, "\n") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const N: usize> fmt::Display for Table<N> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         self.write_row(f, self.header.iter().map(|s| s.as_str()))?; | ||||
|         self.write_row(f, self.header.iter().map(|_| "-"))?; | ||||
|         for row in &self.rows { | ||||
|             self.write_row(f, row.iter().map(|s| s.as_str()))? | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use super::*; | ||||
| @@ -152,4 +193,15 @@ mod test { | ||||
|     fn duration_hours_mins() { | ||||
|         assert_eq!(duration(130), "2 hours and 10 minutes") | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn table() { | ||||
|         let mut table = Table::new(["a".into(), "b".into()]); | ||||
|         table.add_row(["a1".into(), "b1".into()]); | ||||
|         table.add_row(["a2".into(), "b2".into()]); | ||||
|         assert_eq!( | ||||
|             format!("{}", table), | ||||
|             "| a | b |\n| - | - |\n| a1 | b1 |\n| a2 | b2 |\n" | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -40,19 +40,19 @@ pub fn replace( | ||||
|             let directive: Vec<_> = directive_str.split_whitespace().collect(); | ||||
|             match directive.as_slice() { | ||||
|                 ["session", "outline"] if session.is_some() => { | ||||
|                     session.unwrap().outline(source_path) | ||||
|                     session.unwrap().outline() | ||||
|                 } | ||||
|                 ["segment", "outline"] if segment.is_some() => { | ||||
|                     segment.unwrap().outline(source_path) | ||||
|                     segment.unwrap().outline() | ||||
|                 } | ||||
|                 ["course", "outline"] if course.is_some() => { | ||||
|                     course.unwrap().schedule(source_path) | ||||
|                     course.unwrap().schedule() | ||||
|                 } | ||||
|                 ["course", "outline", course_name] => { | ||||
|                     let Some(course) = courses.find_course(course_name) else { | ||||
|                         return captures[0].to_string(); | ||||
|                     }; | ||||
|                     course.schedule(source_path) | ||||
|                     course.schedule() | ||||
|                 } | ||||
|                 _ => directive_str.to_owned(), | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user