Skip to content

Commit 60873d3

Browse files
authored
[FEAT]: add features, ci, CODEOWNERS, etc. (#6)
* feat: add CODEOWNERS * feat: add `get` and `set` * ci: init github action ci * docs: add comment in `pre-commit` * feat: add `debounce` * feat: add `is_number` * refactor: colocate tests into folders
1 parent a797712 commit 60873d3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1006
-226
lines changed

.githooks/pre-commit

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22

33
set -eu
44

5+
# Format staged files
56
if ! cargo fmt -- --check; then
67
echo "There are some code style issues."
78
echo "Run cargo fmt first."
89
exit 1
910
fi
1011

12+
# Lint staged files
1113
if ! cargo clippy --all-targets -- -D warnings; then
1214
echo "There are some clippy issues."
1315
exit 1
1416
fi
1517

18+
# Full test
1619
if ! cargo test; then
1720
echo "There are some test issues."
1821
exit 1

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @ImBIOS

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Cargo Build & Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
env:
8+
CARGO_TERM_COLOR: always
9+
10+
jobs:
11+
pre_ci:
12+
uses: ImBIOS/.github/.github/workflows/pre_ci.yml@main
13+
14+
build_and_test:
15+
name: Rust project - latest
16+
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
toolchain:
20+
- stable
21+
- beta
22+
- nightly
23+
steps:
24+
- uses: actions/checkout@v3
25+
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
26+
- run: cargo build --verbose
27+
- run: cargo test --verbose

Cargo.toml

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "lorust"
33
authors = ["Imamuzzaki Abu Salam <[email protected]>"]
44
description = "Modern Rust utility library delivering modularity, performance & extras; or simply Rust version of Lodash"
55
version = "0.1.0"
6+
rust-version = "1.70"
67
edition = "2021"
78
readme = "README.md"
89
license = "MIT OR Apache-2.0"
@@ -22,19 +23,33 @@ categories = [
2223
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
2324

2425
[features]
25-
default = ["math", "object", "string"]
26-
math = []
27-
object = ["serde_json"]
28-
string = ["unicode-normalization", "regex"]
29-
30-
[dependencies.unicode-normalization]
31-
version = "0.1.22"
32-
optional = true
33-
34-
[dependencies.regex]
35-
version = "1.5.4"
36-
optional = true
37-
38-
[dependencies.serde_json]
39-
version = "1.0.100"
40-
optional = true
26+
default = ["function", "lang", "math", "object", "string"]
27+
28+
function = ["debounce"]
29+
debounce = ["log"]
30+
31+
lang = ["is_number"]
32+
is_number = []
33+
34+
math = ["round"]
35+
round = []
36+
37+
object = ["get", "map_values", "merge", "pick", "set"]
38+
get = []
39+
map_values = []
40+
merge = ["serde_json"]
41+
pick = []
42+
set = []
43+
44+
string = ["capitalize", "deburr", "ends_with", "kebab_case", "words"]
45+
capitalize = []
46+
deburr = ["unicode-normalization"]
47+
ends_with = []
48+
kebab_case = []
49+
words = ["regex"]
50+
51+
[dependencies]
52+
serde_json = { version = "1.0.100", optional = true }
53+
regex = { version = "1.5.4", optional = true }
54+
unicode-normalization = { version = "0.1.22", optional = true }
55+
log = { version = "0.4.14", optional = true }

rust-toolchain.toml

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/function/debounce.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use std::{
2+
pin::Pin,
3+
sync::{mpsc, Arc, Mutex},
4+
time::Duration,
5+
};
6+
7+
/// Debounce function takes a closure and a delay, and returns a `Debounce` struct.
8+
///
9+
/// # Arguments
10+
///
11+
/// * `closure` - A closure which accepts a parameter of type `T` and returns `()`.
12+
/// * `delay` - The duration after which the closure should be invoked.
13+
///
14+
/// # Returns
15+
///
16+
/// * `Debounce<T>` - An instance of Debounce struct which can be used to call the closure
17+
/// or terminate the execution.
18+
///
19+
/// The function also spawns a new thread which waits for a message to arrive on the receiver
20+
/// end of a channel. The first message that arrives sets the `current_param`.
21+
/// After the first message arrives, the thread waits with a timeout equal to `delay`.
22+
/// If another message arrives during this period, `current_param` is overwritten.
23+
/// If the timeout occurs without a new message, the closure is called with `current_param` as the argument.
24+
pub fn debounce<F, T>(closure: F, delay: Duration) -> Debounce<T>
25+
where
26+
F: Fn(T) + Send + Sync + 'static,
27+
T: Send + Sync + 'static,
28+
{
29+
let (sender, receiver) = mpsc::channel();
30+
let sender = Arc::new(Mutex::new(sender));
31+
let debounce_config = Arc::new(Mutex::new(DebounceConfig {
32+
closure: Box::pin(closure),
33+
delay,
34+
}));
35+
36+
let dup_debounce_config = debounce_config.clone();
37+
38+
Debounce {
39+
sender: Some(sender),
40+
thread: Some(std::thread::spawn(move || {
41+
let debounce_config = dup_debounce_config;
42+
let mut current_param = None; // finally saved as an argument to the execution
43+
loop {
44+
if current_param.is_none() {
45+
let message = receiver.recv();
46+
match message {
47+
Ok(param) => current_param = param,
48+
Err(_) => {
49+
break;
50+
}
51+
}
52+
} else {
53+
let message = receiver.recv_timeout(debounce_config.lock().unwrap().delay);
54+
match message {
55+
Ok(param) => current_param = param,
56+
Err(err) => match err {
57+
mpsc::RecvTimeoutError::Timeout => {
58+
if let Some(param) = current_param.take() {
59+
(*debounce_config.lock().unwrap().closure)(param);
60+
}
61+
}
62+
mpsc::RecvTimeoutError::Disconnected => {
63+
break;
64+
}
65+
},
66+
}
67+
}
68+
}
69+
})),
70+
debounce_config,
71+
}
72+
}
73+
74+
/// `DebounceConfig` struct stores a closure and a delay duration.
75+
/// This struct is used within `Debounce` struct to keep track of the configuration.
76+
///
77+
/// # Fields
78+
///
79+
/// * `closure` - A closure which accepts a parameter of type `T` and returns `()`.
80+
/// * `delay` - The duration after which the closure should be invoked.
81+
struct DebounceConfig<T> {
82+
closure: Pin<Box<dyn Fn(T) + Send + Sync + 'static>>,
83+
delay: Duration,
84+
}
85+
86+
impl<T> Drop for DebounceConfig<T> {
87+
/// The `Drop` trait implementation for `DebounceConfig`.
88+
/// Logs a message with the memory address of the `DebounceConfig` instance when it's dropped.
89+
fn drop(&mut self) {
90+
log::trace!("drop DebounceConfig {:?}", format!("{:p}", self));
91+
}
92+
}
93+
94+
/// `Debounce` struct enables calling the provided closure after a specified delay.
95+
/// The delay is reset if another call is made before the delay duration has passed.
96+
/// It holds an `Option` for a sender which is used to send messages to the worker thread.
97+
/// It also holds an `Option` for a JoinHandle for the worker thread.
98+
/// The `debounce_config` stores the closure and delay information.
99+
///
100+
/// # Fields
101+
///
102+
/// * `sender` - An `Option` for a sender which is used to send messages to the worker thread.
103+
/// * `thread` - An `Option` for a JoinHandle for the worker thread.
104+
/// * `debounce_config` - Stores the closure and delay information.
105+
#[allow(dead_code)]
106+
pub struct Debounce<T> {
107+
sender: Option<Arc<Mutex<mpsc::Sender<Option<T>>>>>,
108+
thread: Option<std::thread::JoinHandle<()>>,
109+
debounce_config: Arc<Mutex<DebounceConfig<T>>>,
110+
}
111+
112+
impl<T> Debounce<T> {
113+
/// Method `call` sends a message with a parameter to the worker thread.
114+
/// This causes the delay timer to reset in the worker thread.
115+
///
116+
/// # Arguments
117+
///
118+
/// * `param` - The parameter to send to the worker thread.
119+
/// This will be used as the argument for the closure when the delay has passed.
120+
pub fn call(&self, param: T) {
121+
self.sender
122+
.as_ref()
123+
.unwrap()
124+
.lock()
125+
.unwrap()
126+
.send(Some(param))
127+
.unwrap();
128+
}
129+
130+
/// Method `terminate` sends a `None` message to the worker thread,
131+
/// causing it to exit the loop and end the execution.
132+
pub fn terminate(&self) {
133+
self.sender
134+
.as_ref()
135+
.unwrap()
136+
.lock()
137+
.unwrap()
138+
.send(None)
139+
.unwrap();
140+
}
141+
}
142+
143+
impl<T> Drop for Debounce<T> {
144+
/// The `Drop` trait implementation for `Debounce`.
145+
/// On drop, it terminates the worker thread and logs a message with the memory address of the `Debounce` instance.
146+
fn drop(&mut self) {
147+
self.terminate();
148+
log::trace!("drop Debounce {:?}", format!("{:p}", self));
149+
}
150+
}

src/function/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod debounce;
2+
#[cfg(feature = "debounce")]
3+
pub use debounce::*;

src/lang/is_number.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// The `is_number` function takes a string and returns a boolean value.
2+
/// It checks if the input string can be parsed into a f64 number excluding the special f64 number cases
3+
/// (i.e., "inf", "-inf", "+inf", "infinity", "-infinity", "+infinity", "nan").
4+
///
5+
/// # Arguments
6+
///
7+
/// * `s` - A String to check if it can be parsed into a f64 number excluding special cases.
8+
///
9+
/// # Example
10+
///
11+
/// ```
12+
/// use lorust::is_number;
13+
///
14+
/// assert_eq!(is_number("123.456".to_string()), true);
15+
/// assert_eq!(is_number("abc".to_string()), false);
16+
/// assert_eq!(is_number("inf".to_string()), false);
17+
/// assert_eq!(is_number("NaN".to_string()), false);
18+
/// ```
19+
pub fn is_number(s: String) -> bool {
20+
match s.to_lowercase().as_str() {
21+
"inf" | "-inf" | "+inf" | "infinity" | "-infinity" | "+infinity" | "nan" => return false,
22+
_ => {}
23+
}
24+
25+
s.parse::<f64>().is_ok()
26+
}

src/lang/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod is_number;
2+
#[cfg(feature = "is_number")]
3+
pub use is_number::*;

src/lib.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,14 @@
1010
//! [dependencies]
1111
//! lorust = "0.1.0"
1212
//! ```
13-
//!
14-
//! ## Functions Categorization
15-
//!
16-
//! We follow the flat function structure of **Lodash**, but the functions can be categorized into following areas:
17-
//!
18-
//! | Category | Description |
19-
//! |--------------|--------------------------------------------------------------|
20-
//! | `string` | Utility functions to deal with Strings |
21-
//! | `array` | Utility functions to deal with Arrays (Not yet support) |
22-
//! | `math` | Utility functions to deal with Maths (Not yet support) |
23-
//! | `object` | Utility functions to deal with Objects (Not yet support) |
24-
//!
13+
14+
mod function;
15+
#[cfg(feature = "function")]
16+
pub use function::*;
17+
18+
mod lang;
19+
#[cfg(feature = "lang")]
20+
pub use lang::*;
2521

2622
mod math;
2723
#[cfg(feature = "math")]

0 commit comments

Comments
 (0)