Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Signed-off-by: simonsan <[email protected]>
  • Loading branch information
simonsan committed Feb 19, 2024
1 parent 658dae1 commit fafd173
Show file tree
Hide file tree
Showing 26 changed files with 323 additions and 164 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dialoguer = { version = "0.11.0", features = ["history", "fuzzy-select"] }
directories = "5.0.1"
eyre = { workspace = true }
human-panic = "1.2.3"
insta = { version = "1.34.0", features = ["toml"] }
pace_cli = { workspace = true }
pace_core = { workspace = true }
serde = "1"
Expand Down
19 changes: 19 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Testing strategy

## Unit tests

- TODO

## Snapshot tests

- Use toml snapshots for checking if the formatting is right and not the weird
double table thing

## Property-based tests

- Implement Arbitrary for all serializable, storage-related types

## Integration tests

- CLI
- API
4 changes: 2 additions & 2 deletions crates/cli/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ pub(crate) fn confirmation_or_break(prompt: &str) -> Result<()> {
///
/// Returns `Ok(())` if the setup assistant succeeds
pub fn craft_setup(term: &Term) -> Result<()> {
let default_config_content = PaceConfig::default();
let mut config = PaceConfig::default();

let config_paths = get_config_paths("pace.toml")
.into_iter()
Expand All @@ -328,7 +328,7 @@ pub fn craft_setup(term: &Term) -> Result<()> {

let final_paths = prompt_activity_log_path(&activity_log_paths)?;

let config = default_config_content.with_activity_log(final_paths.activity_log_path());
config.add_activity_log_path(final_paths.activity_log_path());

let final_paths = prompt_config_file_path(final_paths, config_paths.as_slice())?;

Expand Down
78 changes: 42 additions & 36 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use serde_derive::{Deserialize, Serialize};

use directories::ProjectDirs;

use crate::error::{PaceErrorKind, PaceResult};
use crate::{
domain::priority::ItemPriority,
error::{PaceErrorKind, PaceResult},
};

/// The pace configuration file
///
Expand Down Expand Up @@ -45,60 +48,63 @@ impl PaceConfig {
/// # Arguments
///
/// `activity_log` - The path to the activity log file
#[must_use]
pub fn with_activity_log(self, activity_log: impl AsRef<Path>) -> Self {
let mut new_config = self;
new_config.general.activity_log_file_path =
activity_log.as_ref().to_string_lossy().to_string();
new_config
pub fn add_activity_log_path(&mut self, activity_log: impl AsRef<Path>) {
self.general.activity_log_file_path = activity_log.as_ref().to_string_lossy().to_string();
}
}

/// The general configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, MutGetters, Clone)]
#[getset(get = "pub")]
pub struct GeneralConfig {
/// The storage type for the activity log
log_storage: String,

/// The path to the activity log file
/// Default is operating system dependent
/// Use `pace craft setup` to set this value initially
#[getset(get = "pub", get_mut = "pub")]
activity_log_file_path: String,

/// The format for the activity log
log_format: String,

/// If IDs should be autogenerated for activities
/// If IDs should be autogenerated for activities, otherwise it's a hard error
/// Default: `true`
autogenerate_ids: bool,

/// The default category separator
/// Default: `::`
category_separator: String,

/// The default category
default_priority: String,
/// The default priority
/// Default: `medium`
default_priority: ItemPriority,

/// The format for the activity log
/// Default: `toml`
log_format: String,

/// The storage type for the activity log
/// Default: `file`
log_storage: String,
}

/// The review configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)]
#[getset(get = "pub")]
pub struct ReviewConfig {
/// The format for the review
review_format: String,

/// The directory to store the review files
review_directory: String,

/// The format for the review
review_format: String,
}

/// The export configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)]
#[getset(get = "pub")]
pub struct ExportConfig {
/// If the export should include tags
export_include_tags: bool,

/// If the export should include descriptions
export_include_descriptions: bool,

/// If the export should include tags
export_include_tags: bool,

/// The time format within the export
export_time_format: String,
}
Expand All @@ -107,21 +113,18 @@ pub struct ExportConfig {
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)]
#[getset(get = "pub")]
pub struct DatabaseConfig {
/// The connection string for the database
connection_string: String, // `type` is a reserved keyword in Rust

/// The type of database
#[serde(rename = "type")]
db_type: String, // `type` is a reserved keyword in Rust

/// The connection string for the database
connection_string: String,
db_type: String,
}

/// The pomodoro configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone, Copy)]
#[getset(get = "pub")]
pub struct PomodoroConfig {
/// The duration of a work session in minutes
work_duration_minutes: u32,

/// The duration of a short break in minutes
break_duration_minutes: u32,

Expand All @@ -130,34 +133,37 @@ pub struct PomodoroConfig {

/// The number of work sessions before a long break
sessions_before_long_break: u32,

/// The duration of a work session in minutes
work_duration_minutes: u32,
}

/// The inbox configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)]
#[getset(get = "pub")]
pub struct InboxConfig {
/// The maximum items the inbox should hold
max_size: u32,
/// The default time to auto-archive items in the inbox (in days)
auto_archive_after_days: u32,

/// The default category for items in the inbox
default_priority: String,

/// The default time to auto-archive items in the inbox (in days)
auto_archive_after_days: u32,
/// The maximum items the inbox should hold
max_size: u32,
}

/// The auto-archival configuration for the pace application
#[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)]
#[getset(get = "pub")]
pub struct AutoArchivalConfig {
/// If auto-archival is enabled
enabled: bool,

/// The default auto-archival time after which items should be archived (in days)
archive_after_days: u32,

/// The path to the archive file
archive_path: String,

/// If auto-archival is enabled
enabled: bool,
}

/// Get the current directory and then search upwards in the directory hierarchy for a file name
Expand Down
23 changes: 15 additions & 8 deletions crates/core/src/domain/activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,30 @@ enum PomodoroCycle {
#[derive(Merge)]
pub struct Activity {
/// The activity's unique identifier
#[builder(default = Some(ActivityId::default()), setter(strip_option))]
#[builder(default = Some(ActivityGuid::default()), setter(strip_option))]
#[getset(get_copy, get_mut = "pub")]
id: Option<ActivityId>,
#[serde(rename = "id", skip_serializing_if = "Option::is_none")]
guid: Option<ActivityGuid>,

/// The category of the activity
// TODO: We had it as a struct before with an ID, but it's questionable if we should go for this
// TODO: Reconsider when we implement the project management part
// category: Category,
#[builder(default)]
#[serde(skip_serializing_if = "Option::is_none")]
category: Option<String>,

/// The description of the activity
// This needs to be an Optional, because we use the whole activity struct
// as well for intermissions, which don't have a description
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,

/// The end date and time of the activity
#[builder(default, setter(strip_option))]
#[getset(get = "pub", get_mut = "pub")]
#[serde(skip_serializing_if = "Option::is_none")]
end: Option<NaiveDateTime>,

/// The start date and time of the activity
Expand All @@ -91,6 +95,7 @@ pub struct Activity {
/// The duration of the activity
#[builder(default, setter(strip_option))]
#[getset(get = "pub", get_mut = "pub")]
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<PaceDuration>,

/// The kind of activity
Expand All @@ -110,18 +115,20 @@ pub struct Activity {
// Pomodoro-specific attributes
/// The pomodoro cycle of the activity
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pomodoro_cycle: Option<PomodoroCycle>,

// Intermission-specific attributes
/// The intermission periods of the activity
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
intermission_periods: Option<Vec<IntermissionPeriod>>,
}

impl Default for Activity {
fn default() -> Self {
Self {
id: Some(ActivityId::default()),
guid: Some(ActivityGuid::default()),
category: Some("Uncategorized".to_string()),
description: Some("This is an example activity".to_string()),
end: None,
Expand All @@ -136,15 +143,15 @@ impl Default for Activity {

/// The unique identifier of an activity
#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)]
pub struct ActivityId(Ulid);
pub struct ActivityGuid(Ulid);

impl Display for ActivityId {
impl Display for ActivityGuid {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl Default for ActivityId {
impl Default for ActivityGuid {
fn default() -> Self {
Self(Ulid::new())
}
Expand All @@ -168,14 +175,14 @@ impl Display for Activity {
}
}

impl rusqlite::types::FromSql for ActivityId {
impl rusqlite::types::FromSql for ActivityGuid {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
let bytes = <[u8; 16]>::column_result(value)?;
Ok(Self(Ulid::from(u128::from_be_bytes(bytes))))
}
}

impl rusqlite::types::ToSql for ActivityId {
impl rusqlite::types::ToSql for ActivityGuid {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(self.0.to_string()))
}
Expand Down
Loading

0 comments on commit fafd173

Please sign in to comment.