1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-04-25 08:53:01 +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:
Dustin J. Mitchell 2024-04-19 09:38:26 -04:00 committed by GitHub
parent 45aa43f406
commit bb44b1d7a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 78 additions and 47 deletions

View File

@ -36,10 +36,10 @@
//! top-level item are treated as further slides in the same segment. //! top-level item are treated as further slides in the same segment.
use crate::frontmatter::{split_frontmatter, Frontmatter}; use crate::frontmatter::{split_frontmatter, Frontmatter};
use crate::markdown::{duration, relative_link}; use crate::markdown::{duration, Table};
use mdbook::book::{Book, BookItem, Chapter}; use mdbook::book::{Book, BookItem, Chapter};
use std::fmt::Write; use std::fmt::Write;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
/// Duration, in minutes, of breaks between segments in the course. /// Duration, in minutes, of breaks between segments in the course.
const BREAK_DURATION: u64 = 10; const BREAK_DURATION: u64 = 10;
@ -245,32 +245,26 @@ impl Course {
/// Generate a Markdown schedule for this course, for placement at the given /// Generate a Markdown schedule for this course, for placement at the given
/// path. /// 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"); let mut outline = String::from("Course schedule:\n");
for session in self { for session in self {
writeln!( writeln!(
&mut outline, &mut outline,
" * {} ({}, including breaks)", " * {} ({}, including breaks)\n",
session.name, session.name,
duration(session.minutes()) duration(session.minutes())
) )
.unwrap(); .unwrap();
let mut segments = Table::new(["Segment".into(), "Duration".into()]);
for segment in session { for segment in session {
// Skip short segments (welcomes, wrap-up, etc.)
if segment.minutes() == 0 { if segment.minutes() == 0 {
continue; continue;
} }
writeln!( segments
&mut outline, .add_row([segment.name.clone(), duration(segment.minutes())]);
" * [{}]({}) ({})",
segment.name,
relative_link(
&at_source_path,
&segment.slides[0].source_paths[0]
),
duration(segment.minutes())
)
.unwrap();
} }
writeln!(&mut outline, "{}\n", segments).unwrap();
} }
outline outline
} }
@ -313,24 +307,18 @@ impl Session {
/// Generate a Markdown outline for this session, for placement at the given /// Generate a Markdown outline for this session, for placement at the given
/// path. /// path.
pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String { pub fn outline(&self) -> String {
let mut outline = String::from("In this session:\n"); let mut segments = Table::new(["Segment".into(), "Duration".into()]);
for segment in self { for segment in self {
// Skip short segments (welcomes, wrap-up, etc.) // Skip short segments (welcomes, wrap-up, etc.)
if segment.minutes() == 0 { if segment.minutes() == 0 {
continue; continue;
} }
writeln!( segments.add_row([segment.name.clone(), duration(segment.minutes())]);
&mut outline,
" * [{}]({}) ({})",
segment.name,
relative_link(&at_source_path, &segment.slides[0].source_paths[0]),
duration(segment.minutes())
)
.unwrap();
} }
writeln!(&mut outline,"\nIncluding {BREAK_DURATION} minute breaks, this session should take about {}", duration(self.minutes())).unwrap(); format!(
outline "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. /// Return the total duration of this session.
@ -394,28 +382,19 @@ impl Segment {
self.into_iter().map(|s| s.minutes()).sum() self.into_iter().map(|s| s.minutes()).sum()
} }
pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String { pub fn outline(&self) -> String {
let mut outline = String::from("In this segment:\n"); let mut slides = Table::new(["Slide".into(), "Duration".into()]);
for slide in self { for slide in self {
if slide.minutes() == 0 { if slide.minutes() == 0 {
continue; continue;
} }
writeln!( slides.add_row([slide.name.clone(), duration(slide.minutes())]);
&mut outline,
" * [{}]({}) ({})",
slide.name,
relative_link(&at_source_path, &slide.source_paths[0]),
duration(slide.minutes())
)
.unwrap();
} }
writeln!( format!(
&mut outline, "This segment should take about {}. It contains:\n\n{}",
"\nThis segment should take about {}", duration(self.minutes()),
duration(self.minutes()) slides,
) )
.unwrap();
outline
} }
} }

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::fmt;
use std::path::Path; use std::path::Path;
/// Given a source_path for the markdown file being rendered and a source_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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -152,4 +193,15 @@ mod test {
fn duration_hours_mins() { fn duration_hours_mins() {
assert_eq!(duration(130), "2 hours and 10 minutes") 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"
);
}
} }

View File

@ -40,19 +40,19 @@ pub fn replace(
let directive: Vec<_> = directive_str.split_whitespace().collect(); let directive: Vec<_> = directive_str.split_whitespace().collect();
match directive.as_slice() { match directive.as_slice() {
["session", "outline"] if session.is_some() => { ["session", "outline"] if session.is_some() => {
session.unwrap().outline(source_path) session.unwrap().outline()
} }
["segment", "outline"] if segment.is_some() => { ["segment", "outline"] if segment.is_some() => {
segment.unwrap().outline(source_path) segment.unwrap().outline()
} }
["course", "outline"] if course.is_some() => { ["course", "outline"] if course.is_some() => {
course.unwrap().schedule(source_path) course.unwrap().schedule()
} }
["course", "outline", course_name] => { ["course", "outline", course_name] => {
let Some(course) = courses.find_course(course_name) else { let Some(course) = courses.find_course(course_name) else {
return captures[0].to_string(); return captures[0].to_string();
}; };
course.schedule(source_path) course.schedule()
} }
_ => directive_str.to_owned(), _ => directive_str.to_owned(),
} }