Skip to content

Commit

Permalink
Basic literacy generated course (#314)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinmr authored Aug 2, 2024
1 parent 905dd4d commit 0d1f7b3
Show file tree
Hide file tree
Showing 9 changed files with 560 additions and 89 deletions.
3 changes: 2 additions & 1 deletion bindings/CourseGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { KnowledgeBaseConfig } from "./KnowledgeBaseConfig";
import type { LiteracyConfig } from "./LiteracyConfig";
import type { MusicPieceConfig } from "./MusicPieceConfig";
import type { TranscriptionConfig } from "./TranscriptionConfig";

/**
* A configuration used for generating special types of courses on the fly.
*/
export type CourseGenerator = { "KnowledgeBase": KnowledgeBaseConfig } | { "MusicPiece": MusicPieceConfig } | { "Transcription": TranscriptionConfig };
export type CourseGenerator = { "KnowledgeBase": KnowledgeBaseConfig } | { "Literacy": LiteracyConfig } | { "MusicPiece": MusicPieceConfig } | { "Transcription": TranscriptionConfig };
15 changes: 14 additions & 1 deletion bindings/ExerciseAsset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BasicAsset } from "./BasicAsset";
import type { LiteracyLesson } from "./LiteracyLesson";
import type { TranscriptionLink } from "./TranscriptionLink";

/**
Expand All @@ -16,7 +17,19 @@ front_path: string,
* open-ended, or it is referring to an external resource which contains the exercise and
* possibly the answer.
*/
back_path: string | null, } } | { "SoundSliceAsset": {
back_path: string | null, } } | { "LiteracyAsset": {
/**
* The type of the lesson.
*/
lesson_type: LiteracyLesson,
/**
* The examples to use in the lesson's exercise.
*/
examples: Array<string>,
/**
* The exceptions to the examples to use in the lesson's exercise.
*/
exceptions: Array<string>, } } | { "SoundSliceAsset": {
/**
* The link to the SoundSlice asset.
*/
Expand Down
31 changes: 31 additions & 0 deletions bindings/LiteracyConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* The configuration to create a course that teaches literacy based on the provided material.
* Material can be of two types.
*
* 1. Examples. For example, they can be words that share the same spelling and pronunciation (e.g.
* "cat", "bat", "hat"), sentences that share similar words, or sentences from the same book or
* article (for more advanced courses).
* 2. Exceptions. For example, they can be words that share the same spelling but have different
* pronunciations (e.g. "cow" and "crow").
*
* All examples and exceptions accept Markdown syntax. Examples and exceptions can be declared in
* the configuration or in separate files in the course's directory. Files that end with the
* extensions ".examples.md" and ".exceptions.md" will be considered as examples and exceptions,
* respectively.
*/
export type LiteracyConfig = {
/**
* Inlined examples to use in the course.
*/
inlined_examples: Array<string>,
/**
* Inlined exceptions to use in the course.
*/
inlined_exceptions: Array<string>,
/**
* Indicates whether to generate an optional lesson that asks the student to write the material
* based on the tutor's dictation.
*/
generate_dictation: boolean, };
6 changes: 6 additions & 0 deletions bindings/LiteracyLesson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* The types of literacy lessons that can be generated.
*/
export type LiteracyLesson = "Reading" | "Dictation";
119 changes: 39 additions & 80 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use std::{collections::BTreeMap, path::Path};
use ts_rs::TS;
use ustr::Ustr;

use self::course_generator::{
use crate::data::course_generator::{
knowledge_base::KnowledgeBaseConfig,
literacy::{LiteracyConfig, LiteracyLesson},
music_piece::MusicPieceConfig,
transcription::{TranscriptionConfig, TranscriptionLink, TranscriptionPreferences},
};
Expand Down Expand Up @@ -216,25 +217,27 @@ pub enum BasicAsset {
impl NormalizePaths for BasicAsset {
fn normalize_paths(&self, working_dir: &Path) -> Result<Self> {
match &self {
// grcov-excl-start: This is a no-op for these variants.
BasicAsset::InlinedAsset { .. } | BasicAsset::InlinedUniqueAsset { .. } => {
Ok(self.clone())
}
// grcov-excl-stop
BasicAsset::MarkdownAsset { path } => {
let abs_path = normalize_path(working_dir, path)?;
Ok(BasicAsset::MarkdownAsset { path: abs_path })
}
BasicAsset::InlinedAsset { .. } | BasicAsset::InlinedUniqueAsset { .. } => {
Ok(self.clone())
}
}
}
}

impl VerifyPaths for BasicAsset {
fn verify_paths(&self, working_dir: &Path) -> Result<bool> {
match &self {
BasicAsset::InlinedAsset { .. } | BasicAsset::InlinedUniqueAsset { .. } => Ok(true), // grcov-excl-line
BasicAsset::MarkdownAsset { path } => {
let abs_path = working_dir.join(Path::new(path));
Ok(abs_path.exists())
}
BasicAsset::InlinedAsset { .. } | BasicAsset::InlinedUniqueAsset { .. } => Ok(true),
}
}
}
Expand All @@ -249,6 +252,9 @@ pub enum CourseGenerator {
/// and for future extensibility.
KnowledgeBase(KnowledgeBaseConfig),

/// The configuration for generating a literacy course.
Literacy(LiteracyConfig),

/// The configuration for generating a music piece course.
MusicPiece(MusicPieceConfig),

Expand All @@ -258,6 +264,7 @@ pub enum CourseGenerator {
//>@course-generator

/// A struct holding the results from running a course generator.
#[derive(Debug, PartialEq)]
pub struct GeneratedCourse {
/// The lessons and exercise manifests generated for the course.
pub lessons: Vec<(LessonManifest, Vec<ExerciseManifest>)>,
Expand Down Expand Up @@ -291,6 +298,9 @@ impl GenerateManifests for CourseGenerator {
CourseGenerator::KnowledgeBase(config) => {
config.generate_manifests(course_root, course_manifest, preferences)
}
CourseGenerator::Literacy(config) => {
config.generate_manifests(course_root, course_manifest, preferences)
}
CourseGenerator::MusicPiece(config) => {
config.generate_manifests(course_root, course_manifest, preferences)
}
Expand Down Expand Up @@ -564,6 +574,20 @@ pub enum ExerciseAsset {
back_path: Option<String>,
},

/// An asset representing a literacy exercise.
LiteracyAsset {
/// The type of the lesson.
lesson_type: LiteracyLesson,

/// The examples to use in the lesson's exercise.
#[serde(default)]
examples: Vec<String>,

/// The exceptions to the examples to use in the lesson's exercise.
#[serde(default)]
exceptions: Vec<String>,
},

/// An asset which stores a link to a SoundSlice.
SoundSliceAsset {
/// The link to the SoundSlice asset.
Expand Down Expand Up @@ -617,6 +641,11 @@ impl NormalizePaths for ExerciseAsset {
back_path: abs_back_path,
})
}
// grcov-excl-start: This is a no-op for these variants.
ExerciseAsset::LiteracyAsset { .. } | ExerciseAsset::TranscriptionAsset { .. } => {
Ok(self.clone())
}
// grcov-excl-stop
ExerciseAsset::SoundSliceAsset {
link,
description,
Expand All @@ -632,7 +661,6 @@ impl NormalizePaths for ExerciseAsset {
})
}
},
ExerciseAsset::TranscriptionAsset { .. } => Ok(self.clone()),
}
}
}
Expand All @@ -655,6 +683,11 @@ impl VerifyPaths for ExerciseAsset {
Ok(front_abs_path.exists())
}
}
// grcov-excl-start: This is a no-op for these variants.
ExerciseAsset::LiteracyAsset { .. } | ExerciseAsset::TranscriptionAsset { .. } => {
Ok(true)
}
// grcov-excl-stop
ExerciseAsset::SoundSliceAsset { backup, .. } => match backup {
None => Ok(true),
Some(path) => {
Expand All @@ -663,7 +696,6 @@ impl VerifyPaths for ExerciseAsset {
Ok(abs_path.exists())
}
},
ExerciseAsset::TranscriptionAsset { .. } => Ok(true),
}
}
}
Expand Down Expand Up @@ -1140,57 +1172,6 @@ mod test {
Ok(())
}

/// Verifies the `NormalizePaths` trait works for an inlined asset.
#[test]
fn normalize_inlined_assets() -> Result<()> {
let inlined_asset = BasicAsset::InlinedAsset {
content: "Test".to_string(),
};
inlined_asset.normalize_paths(Path::new("./"))?;

let inlined_asset = BasicAsset::InlinedUniqueAsset {
content: Ustr::from("Test"),
};
inlined_asset.normalize_paths(Path::new("./"))?;
Ok(())
}

/// Verifies the `VerifyPaths` trait works for an inlined asset.
#[test]
fn verify_inlined_assets() -> Result<()> {
let inlined_asset = BasicAsset::InlinedAsset {
content: "Test".to_string(),
};
assert!(inlined_asset.verify_paths(Path::new("./"))?);

let inlined_asset = BasicAsset::InlinedUniqueAsset {
content: Ustr::from("Test"),
};
assert!(inlined_asset.verify_paths(Path::new("./"))?);
Ok(())
}

/// Verifies the `NormalizePaths` trait works for a transcription asset.
#[test]
fn transcription_normalize_paths() {
let asset = ExerciseAsset::TranscriptionAsset {
content: "content".into(),
external_link: None,
};
assert!(asset.normalize_paths(Path::new("./")).is_ok());
}

/// Verifies the `VerifyPaths` trait works for a transcription asset.
#[test]
fn transcription_verify_paths() -> Result<()> {
let asset = ExerciseAsset::TranscriptionAsset {
content: "content".into(),
external_link: None,
};
assert!(asset.verify_paths(Path::new("./"))?);
Ok(())
}

/// Verifies the `VerifyPaths` trait works for a flashcard asset.
#[test]
fn verify_flashcard_assets() -> Result<()> {
Expand Down Expand Up @@ -1251,28 +1232,6 @@ mod test {
Ok(())
}

/// Verifies the `VerifyPaths` trait works for a basic exercise asset.
#[test]
fn exercise_basic_asset_verify_paths() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let basic_asset = ExerciseAsset::BasicAsset(BasicAsset::InlinedAsset {
content: "my content".to_string(),
});
assert!(basic_asset.verify_paths(temp_dir.path())?);
Ok(())
}

/// Verifies the `NormalizePaths` trait works for a basic exercise asset.
#[test]
fn exercise_basic_asset_normalize_paths() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let basic_asset = ExerciseAsset::BasicAsset(BasicAsset::InlinedAsset {
content: "my content".to_string(),
});
basic_asset.normalize_paths(temp_dir.path())?;
Ok(())
}

/// Verifies the default scheduler options are valid.
#[test]
fn valid_default_scheduler_options() {
Expand Down
1 change: 1 addition & 0 deletions src/data/course_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;

pub mod knowledge_base;
pub mod literacy;
pub mod music_piece;
pub mod transcription;

Expand Down
7 changes: 2 additions & 5 deletions src/data/course_generator/knowledge_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,9 @@ impl GenerateManifests for KnowledgeBaseConfig {
course_manifest: &CourseManifest,
_preferences: &UserPreferences,
) -> Result<GeneratedCourse> {
// Store the lessons and their exercises in a map of short lesson ID to a tuple of the
// lesson and its exercises.
// Create the lessons by iterating through all the directories in the course root,
// processing only those whose name fits the pattern `<SHORT_LESSON_ID>.lesson`.
let mut lessons = UstrMap::default();

// Iterate through all the directories in the course root, processing only those whose name
// fits the pattern `<SHORT_LESSON_ID>.lesson`.
for entry in read_dir(course_root)? {
// Ignore the entry if it's not a directory.
let entry = entry?;
Expand Down
Loading

0 comments on commit 0d1f7b3

Please sign in to comment.