Skip to content

Commit

Permalink
feat(core): subdivide storage trait and apply fixes (#3)
Browse files Browse the repository at this point in the history
* feat(commands): rename report to review in commands and add completion generation

Signed-off-by: simonsan <[email protected]>

* WIP: Implement CLI tests

Signed-off-by: simonsan <[email protected]>

* test: add some basic (and still incomplete) integration testing as long, as the commands are not fully implemented

Signed-off-by: simonsan <[email protected]>

* feat(traits): subdivide traits so they are easier to implement and to compose

Signed-off-by: simonsan <[email protected]>

* refactor: use more fp

Signed-off-by: simonsan <[email protected]>

* refactor: use ActivityLog and fix some clippy lints

Signed-off-by: simonsan <[email protected]>

* docs: update status in readme

Signed-off-by: simonsan <[email protected]>

* chore: cleanup cli tests

Signed-off-by: simonsan <[email protected]>

* test: fix test to find root config files

Signed-off-by: simonsan <[email protected]>

---------

Signed-off-by: simonsan <[email protected]>
  • Loading branch information
simonsan authored Feb 12, 2024
1 parent 7a0718c commit ef24343
Show file tree
Hide file tree
Showing 39 changed files with 1,390 additions and 239 deletions.
268 changes: 259 additions & 9 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ eula = false
[dependencies]
chrono = { version = "0.4.33", features = ["serde"] }
clap = "4"
clap_complete = "4.5.0"
directories = "5.0.1"
eyre = "0.6.12"
pace_core = { workspace = true }
serde = "1"
Expand All @@ -46,7 +48,10 @@ version = "0.7.0"

[dev-dependencies]
abscissa_core = { version = "0.7.0", features = ["testing"] }
assert_cmd = "2.0.13"
once_cell = "1.19"
predicates = "3.1.0"
tempfile = "3.10.0"

# The profile that 'cargo dist' will build with
[profile.dist]
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,37 @@
Currently they are stating the intended functionality and may not be fully
implemented yet (e.g. using activities instead of tasks).

🪧 **`pace begin <task-name>`**
🪧 **`pace begin`**

- **Description:** Starts tracking time for the specified task. You can
optionally specify a category or project to help organize your tasks.
- **Usage:** `pace begin "Design Work" --category "Freelance"`
- **Usage:** `pace begin "Design Work" --category "Freelance" --time 10:00`

🪧 **`pace end <task-name>`**
🪧 **`pace end`**

- **Description:** Stops time tracking for the specified task, marking it as
completed or finished for the day.
- **Usage:** `pace end "Design Work"`
- **Usage:** `pace end --time 11:30 --only-last`

🪧 **`pace now`**

- **Description:** Displays the currently running task, showing you at a glance
what you're currently tracking.
- **Usage:** `pace now`

**`pace report --daily/--weekly/--monthly`**
⏲️ **`pace review`**

- **Description:** Generates a report for your tasks. You can specify the time
frame for daily, weekly, or monthly reports.
- **Usage:** `pace report --weekly --summary`
- **Description:** Gain insight in your activities and tasks. You can specify
the time frame for daily, weekly, or monthly insights.
- **Usage:** `pace review --weekly`

**`pace resume <task-name>`**
**`pace resume`**

- **Description:** Resumes time tracking for a previously paused task, allowing
you to continue where you left off.
- **Usage:** `pace resume "Design Work"`

**`pace hold <task-name>`**
**`pace hold`**

- **Description:** Pauses the time tracking for the specified task. This is
useful for taking breaks without ending the task.
Expand All @@ -84,22 +84,22 @@ implemented yet (e.g. using activities instead of tasks).
all projects, subprojects and their associated tasks.
- **Usage:** `pace projects`

**`pace pomo <task-name>`**
**`pace pomo`**

- **Description:** Starts a Pomodoro session for the specified task, integrating
the Pomodoro technique directly with your tasks.
- **Usage:** `pace pomo "Study Session"`

**`pace export --json/--csv`**

- **Description:** Exports your tracked data and reports in JSON or CSV format,
- **Description:** Exports your tracked data and insights in JSON or CSV format,
suitable for analysis or record-keeping.
- **Usage:** `pace export --csv --from 2021-01-01 --to 2021-01-31`

**`pace set`**

- **Description:** Sets various application configurations, including Pomodoro
lengths and preferred report formats.
lengths and preferred review formats.
- **Usage:** `pace set --work 25 --break 5`

## License
Expand Down
10 changes: 5 additions & 5 deletions config/pace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ category_separator = "::"
# Default priority for new tasks
default_priority = "Medium"

[reporting]
# Format of the reports generated by the application: "pdf", "html", "markdown", etc.
report_format = "html"
# Directory where reports will be stored
report_directory = "/path/to/your/reports/"
[reviews]
# Format of the review generated by the pace: "pdf", "html", "markdown", etc.
review_format = "html"
# Directory where the reviews will be stored
review_directory = "/path/to/your/reviews/"

[export]
export_include_tags = true
Expand Down
9 changes: 9 additions & 0 deletions config/project.toml → config/projects.pace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ id = "018d84a0-7847-7b2b-9cdb-6ba213f99a1d"
name = "Pace Project"
description = "An example project managed with Pace."
root_tasks_file = "tasks.toml" # Path to the root tasks file
filters = ["*pace*"] # Optional: Define default filters for your project

[defaults]
# Optional: Define a default category for your project
Expand Down Expand Up @@ -41,10 +42,18 @@ id = "018d84a0-a2d1-7450-98ef-8b47e0ff42b5"
name = "Pace Subproject A"
description = ""
tasks_file = "subproject-a/tasks.toml"
# Optional: Define default filters for your project
filters = [
"*pace*, *subproject-a",
]

[[subprojects]]
# Optional: Define subprojects or directories with their own tasks
id = "018d84a0-cc74-71b4-8dd4-d7d26d1f5924"
name = "Pace Subproject B"
description = ""
tasks_file = "subproject-b/tasks.toml"
# Optional: Define default filters for your project
filters = [
"*pace*, *subproject-b",
]
File renamed without changes.
4 changes: 3 additions & 1 deletion crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ include = [
[dependencies]
async-condvar-fair = "1.0.0"
chrono = { version = "0.4.33", features = ["serde"] }
derive-getters = "0.3.0"
directories = "5.0.1"
futures = "0.3.30"
getset = "0.1.2"
itertools = "0.12.1"
log = "0.4.20"
rusqlite = { version = "0.30.0", features = ["bundled", "chrono", "uuid"] }
serde = "1.0.196"
Expand Down
122 changes: 111 additions & 11 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use std::fs;
use std::path::{Path, PathBuf};

use chrono::NaiveDateTime;
use derive_getters::Getters;
use getset::{CopyGetters, Getters, MutGetters, Setters};
use serde_derive::{Deserialize, Serialize};

use directories::ProjectDirs;

use crate::{
domain::category::Category,
error::{PaceErrorKind, PaceResult},
Expand All @@ -16,18 +18,20 @@ use crate::{
#[derive(Debug, Deserialize, Default, Serialize, Getters)]
#[serde(deny_unknown_fields)]
pub struct PaceConfig {
#[getset(get = "pub")]
general: GeneralConfig,
reporting: ReportingConfig,
reviews: ReviewConfig,
export: ExportConfig,
database: Option<DatabaseConfig>, // Optional because it's only needed if log_storage is "database"
pomodoro: PomodoroConfig,
inbox: InboxConfig,
auto_archival: AutoArchivalConfig,
}

#[derive(Debug, Deserialize, Default, Serialize, Getters)]
#[derive(Debug, Deserialize, Default, Serialize, Getters, MutGetters)]
pub struct GeneralConfig {
log_storage: String,
#[getset(get = "pub", get_mut = "pub")]
activity_log_file_path: String,
log_format: String,
autogenerate_ids: bool,
Expand All @@ -36,9 +40,9 @@ pub struct GeneralConfig {
}

#[derive(Debug, Deserialize, Default, Serialize, Getters)]
pub struct ReportingConfig {
report_format: String,
report_directory: String,
pub struct ReviewConfig {
review_format: String,
review_directory: String,
}

#[derive(Debug, Deserialize, Default, Serialize, Getters)]
Expand Down Expand Up @@ -123,17 +127,113 @@ pub fn find_config_file(starting_directory: impl AsRef<Path>, file_name: &str) -
/// # Returns
///
/// The path to the file if found
pub fn find_config_file_path_from_current_dir(file_name: &str) -> PaceResult<PathBuf> {
let current_dir = env::current_dir()?;
pub fn find_root_config_file_path(
current_dir: impl AsRef<Path>,
file_name: &str,
) -> PaceResult<PathBuf> {
find_config_file(&current_dir, file_name).ok_or(
PaceErrorKind::ConfigFileNotFound {
current_dir: current_dir.clone().to_string_lossy().to_string(),
current_dir: current_dir.as_ref().to_string_lossy().to_string(),
file_name: file_name.to_string(),
}
.into(),
)
}

/// Get the paths to the config file
///
/// # Arguments
///
/// * `filename` - name of the config file
///
/// # Returns
///
/// A vector of [`PathBuf`]s to the config files
fn get_config_paths(filename: &str) -> Vec<PathBuf> {
#[allow(unused_mut)]
let mut paths = vec![
get_home_config_path(),
ProjectDirs::from("", "", "pace")
.map(|project_dirs| project_dirs.config_dir().to_path_buf()),
get_global_config_path(),
Some(PathBuf::from(".")),
];

#[cfg(target_os = "windows")]
{
if let Some(win_compatibility_paths) = get_windows_portability_config_directories() {
paths.extend(win_compatibility_paths);
};
}

paths
.into_iter()
.filter_map(|path| path.map(|p| p.join(filename)))
.collect::<Vec<_>>()
}

/// Get the path to the home config directory.
///
/// # Returns
///
/// The path to the home config directory.
/// If the environment variable `PACE_HOME` is not set, `None` is returned.
fn get_home_config_path() -> Option<PathBuf> {
std::env::var_os("PACE_HOME").map(|home_dir| PathBuf::from(home_dir).join(r"config"))
}

/// Get the paths to the user profile config directories on Windows.
///
/// # Returns
///
/// A collection of possible paths to the user profile config directory on Windows.
///
/// # Note
///
/// If the environment variable `USERPROFILE` is not set, `None` is returned.
#[cfg(target_os = "windows")]
fn get_windows_portability_config_directories() -> Option<Vec<Option<PathBuf>>> {
std::env::var_os("USERPROFILE").map(|path| {
vec![
Some(PathBuf::from(path.clone()).join(r".config\pace")),
Some(PathBuf::from(path).join(".pace")),
]
})
}

/// Get the path to the global config directory on Windows.
///
/// # Returns
///
/// The path to the global config directory on Windows.
/// If the environment variable `PROGRAMDATA` is not set, `None` is returned.
#[cfg(target_os = "windows")]
fn get_global_config_path() -> Option<PathBuf> {
std::env::var_os("PROGRAMDATA")
.map(|program_data| PathBuf::from(program_data).join(r"pace\config"))
}

/// Get the path to the global config directory on ios and wasm targets.
///
/// # Returns
///
/// `None` is returned.
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
fn get_global_config_path() -> Option<PathBuf> {
None
}

/// Get the path to the global config directory on non-Windows,
/// non-iOS, non-wasm targets.
///
/// # Returns
///
/// "/etc/pace" is returned.
#[cfg(not(any(target_os = "windows", target_os = "ios", target_arch = "wasm32")))]
fn get_global_config_path() -> Option<PathBuf> {
Some(PathBuf::from("/etc/pace"))
}

#[cfg(test)]
mod tests {

Expand All @@ -155,7 +255,7 @@ mod tests {

#[rstest]
fn test_parse_project_file_passes(
#[files("../../config/project.toml")] config_path: PathBuf,
#[files("../../config/projects.pace.toml")] config_path: PathBuf,
) -> TestResult<()> {
let toml_string = fs::read_to_string(config_path)?;
let _ = toml::from_str::<ProjectConfig>(&toml_string)?;
Expand All @@ -165,7 +265,7 @@ mod tests {

#[rstest]
fn test_parse_tasks_file_passes(
#[files("../../config/tasks.toml")] config_path: PathBuf,
#[files("../../config/tasks.pace.toml")] config_path: PathBuf,
) -> TestResult<()> {
let toml_string = fs::read_to_string(config_path)?;
let _ = toml::from_str::<TaskList>(&toml_string)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
pub mod activity;
pub mod category;
pub mod filter;
pub mod inbox;
pub mod intermission;
pub mod priority;
pub mod project;
pub mod review;
pub mod status;
pub mod tag;
pub mod task;
Expand Down
Loading

0 comments on commit ef24343

Please sign in to comment.