ucn 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ucn might be problematic. Click here for more details.
- package/.claude/skills/ucn/SKILL.md +77 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/cli/index.js +2437 -0
- package/core/discovery.js +513 -0
- package/core/imports.js +558 -0
- package/core/output.js +1274 -0
- package/core/parser.js +279 -0
- package/core/project.js +3261 -0
- package/index.js +52 -0
- package/languages/go.js +653 -0
- package/languages/index.js +267 -0
- package/languages/java.js +826 -0
- package/languages/javascript.js +1346 -0
- package/languages/python.js +667 -0
- package/languages/rust.js +950 -0
- package/languages/utils.js +457 -0
- package/package.json +42 -0
- package/test/fixtures/go/go.mod +3 -0
- package/test/fixtures/go/main.go +257 -0
- package/test/fixtures/go/service.go +187 -0
- package/test/fixtures/java/DataService.java +279 -0
- package/test/fixtures/java/Main.java +287 -0
- package/test/fixtures/java/Utils.java +199 -0
- package/test/fixtures/java/pom.xml +6 -0
- package/test/fixtures/javascript/main.js +109 -0
- package/test/fixtures/javascript/package.json +1 -0
- package/test/fixtures/javascript/service.js +88 -0
- package/test/fixtures/javascript/utils.js +67 -0
- package/test/fixtures/python/main.py +198 -0
- package/test/fixtures/python/pyproject.toml +3 -0
- package/test/fixtures/python/service.py +166 -0
- package/test/fixtures/python/utils.py +118 -0
- package/test/fixtures/rust/Cargo.toml +3 -0
- package/test/fixtures/rust/main.rs +253 -0
- package/test/fixtures/rust/service.rs +210 -0
- package/test/fixtures/rust/utils.rs +154 -0
- package/test/fixtures/typescript/main.ts +154 -0
- package/test/fixtures/typescript/package.json +1 -0
- package/test/fixtures/typescript/repository.ts +149 -0
- package/test/fixtures/typescript/types.ts +114 -0
- package/test/parser.test.js +3661 -0
- package/test/public-repos-test.js +477 -0
- package/test/systematic-test.js +619 -0
- package/ucn.js +8 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
//! Main Rust test fixtures.
|
|
2
|
+
//! Tests structs, traits, enums, and async functions.
|
|
3
|
+
|
|
4
|
+
mod service;
|
|
5
|
+
mod utils;
|
|
6
|
+
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
use std::sync::{Arc, Mutex};
|
|
9
|
+
|
|
10
|
+
/// Status enum representing task states.
|
|
11
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
12
|
+
pub enum Status {
|
|
13
|
+
Pending,
|
|
14
|
+
Active,
|
|
15
|
+
Completed,
|
|
16
|
+
Failed,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Task struct representing a task entity.
|
|
20
|
+
#[derive(Debug, Clone)]
|
|
21
|
+
pub struct Task {
|
|
22
|
+
pub id: String,
|
|
23
|
+
pub name: String,
|
|
24
|
+
pub status: Status,
|
|
25
|
+
pub priority: i32,
|
|
26
|
+
pub metadata: HashMap<String, String>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl Task {
|
|
30
|
+
/// Create a new task.
|
|
31
|
+
pub fn new(id: String, name: String) -> Self {
|
|
32
|
+
Task {
|
|
33
|
+
id,
|
|
34
|
+
name,
|
|
35
|
+
status: Status::Pending,
|
|
36
|
+
priority: 1,
|
|
37
|
+
metadata: HashMap::new(),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Set the task priority.
|
|
42
|
+
pub fn with_priority(mut self, priority: i32) -> Self {
|
|
43
|
+
self.priority = priority;
|
|
44
|
+
self
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Set the task status.
|
|
48
|
+
pub fn with_status(mut self, status: Status) -> Self {
|
|
49
|
+
self.status = status;
|
|
50
|
+
self
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Check if the task is complete.
|
|
54
|
+
pub fn is_complete(&self) -> bool {
|
|
55
|
+
self.status == Status::Completed
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Trait for entities with an ID.
|
|
60
|
+
pub trait Entity {
|
|
61
|
+
fn get_id(&self) -> &str;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl Entity for Task {
|
|
65
|
+
fn get_id(&self) -> &str {
|
|
66
|
+
&self.id
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Task manager that manages a collection of tasks.
|
|
71
|
+
pub struct TaskManager {
|
|
72
|
+
tasks: Arc<Mutex<Vec<Task>>>,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl TaskManager {
|
|
76
|
+
/// Create a new task manager.
|
|
77
|
+
pub fn new() -> Self {
|
|
78
|
+
TaskManager {
|
|
79
|
+
tasks: Arc::new(Mutex::new(Vec::new())),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Add a task to the manager.
|
|
84
|
+
pub fn add_task(&self, task: Task) -> Result<(), String> {
|
|
85
|
+
validate_task(&task)?;
|
|
86
|
+
let mut tasks = self.tasks.lock().unwrap();
|
|
87
|
+
tasks.push(task);
|
|
88
|
+
Ok(())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Get a task by ID.
|
|
92
|
+
pub fn get_task(&self, id: &str) -> Option<Task> {
|
|
93
|
+
let tasks = self.tasks.lock().unwrap();
|
|
94
|
+
tasks.iter().find(|t| t.id == id).cloned()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Get all tasks, optionally filtered.
|
|
98
|
+
pub fn get_tasks<F>(&self, filter: Option<F>) -> Vec<Task>
|
|
99
|
+
where
|
|
100
|
+
F: Fn(&Task) -> bool,
|
|
101
|
+
{
|
|
102
|
+
let tasks = self.tasks.lock().unwrap();
|
|
103
|
+
match filter {
|
|
104
|
+
Some(f) => tasks.iter().filter(|t| f(t)).cloned().collect(),
|
|
105
|
+
None => tasks.clone(),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Update a task by ID.
|
|
110
|
+
pub fn update_task(&self, id: &str, name: Option<String>, status: Option<Status>) -> Option<Task> {
|
|
111
|
+
let mut tasks = self.tasks.lock().unwrap();
|
|
112
|
+
for task in tasks.iter_mut() {
|
|
113
|
+
if task.id == id {
|
|
114
|
+
if let Some(n) = name {
|
|
115
|
+
task.name = n;
|
|
116
|
+
}
|
|
117
|
+
if let Some(s) = status {
|
|
118
|
+
task.status = s;
|
|
119
|
+
}
|
|
120
|
+
return Some(task.clone());
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
None
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Delete a task by ID.
|
|
127
|
+
pub fn delete_task(&self, id: &str) -> bool {
|
|
128
|
+
let mut tasks = self.tasks.lock().unwrap();
|
|
129
|
+
let len_before = tasks.len();
|
|
130
|
+
tasks.retain(|t| t.id != id);
|
|
131
|
+
tasks.len() < len_before
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Get the count of tasks.
|
|
135
|
+
pub fn count(&self) -> usize {
|
|
136
|
+
let tasks = self.tasks.lock().unwrap();
|
|
137
|
+
tasks.len()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
impl Default for TaskManager {
|
|
142
|
+
fn default() -> Self {
|
|
143
|
+
Self::new()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Validate a task.
|
|
148
|
+
pub fn validate_task(task: &Task) -> Result<(), String> {
|
|
149
|
+
if task.id.is_empty() {
|
|
150
|
+
return Err("Task ID is required".to_string());
|
|
151
|
+
}
|
|
152
|
+
if task.name.is_empty() {
|
|
153
|
+
return Err("Task name is required".to_string());
|
|
154
|
+
}
|
|
155
|
+
Ok(())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Create a new task with a generated ID.
|
|
159
|
+
pub fn create_task(name: &str, priority: i32) -> Task {
|
|
160
|
+
let id = generate_id();
|
|
161
|
+
Task::new(id, name.to_string()).with_priority(priority)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Generate a unique ID.
|
|
165
|
+
fn generate_id() -> String {
|
|
166
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
167
|
+
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
168
|
+
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
|
|
169
|
+
format!("task-{}", id)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Filter tasks by status.
|
|
173
|
+
pub fn filter_by_status(tasks: &[Task], status: Status) -> Vec<Task> {
|
|
174
|
+
tasks.iter().filter(|t| t.status == status).cloned().collect()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Filter tasks by minimum priority.
|
|
178
|
+
pub fn filter_by_priority(tasks: &[Task], min_priority: i32) -> Vec<Task> {
|
|
179
|
+
tasks.iter().filter(|t| t.priority >= min_priority).cloned().collect()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Task processor for processing tasks.
|
|
183
|
+
pub struct TaskProcessor {
|
|
184
|
+
manager: Arc<TaskManager>,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
impl TaskProcessor {
|
|
188
|
+
/// Create a new task processor.
|
|
189
|
+
pub fn new(manager: Arc<TaskManager>) -> Self {
|
|
190
|
+
TaskProcessor { manager }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Process all tasks.
|
|
194
|
+
pub fn process_all(&self) -> Vec<HashMap<String, String>> {
|
|
195
|
+
let tasks = self.manager.get_tasks::<fn(&Task) -> bool>(None);
|
|
196
|
+
tasks.iter().map(|t| self.process_task(t)).collect()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Process only pending tasks.
|
|
200
|
+
pub fn process_pending(&self) -> Vec<HashMap<String, String>> {
|
|
201
|
+
let tasks = self.manager.get_tasks(Some(|t: &Task| t.status == Status::Pending));
|
|
202
|
+
tasks.iter().map(|t| self.process_task(t)).collect()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Process a single task.
|
|
206
|
+
fn process_task(&self, task: &Task) -> HashMap<String, String> {
|
|
207
|
+
format_task(task)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Format a task as a map.
|
|
212
|
+
pub fn format_task(task: &Task) -> HashMap<String, String> {
|
|
213
|
+
let mut map = HashMap::new();
|
|
214
|
+
map.insert("id".to_string(), task.id.clone());
|
|
215
|
+
map.insert("name".to_string(), task.name.clone());
|
|
216
|
+
map.insert("status".to_string(), format!("{:?}", task.status));
|
|
217
|
+
map.insert("priority".to_string(), task.priority.to_string());
|
|
218
|
+
map
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Unused function for deadcode detection.
|
|
222
|
+
#[allow(dead_code)]
|
|
223
|
+
fn unused_function() -> &'static str {
|
|
224
|
+
"never used"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fn main() {
|
|
228
|
+
let manager = TaskManager::new();
|
|
229
|
+
let task = create_task("Test Task", 1);
|
|
230
|
+
manager.add_task(task).unwrap();
|
|
231
|
+
println!("Created {} tasks", manager.count());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[cfg(test)]
|
|
235
|
+
mod tests {
|
|
236
|
+
use super::*;
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn test_create_task() {
|
|
240
|
+
let task = create_task("Test", 1);
|
|
241
|
+
assert!(!task.id.is_empty());
|
|
242
|
+
assert_eq!(task.name, "Test");
|
|
243
|
+
assert_eq!(task.priority, 1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn test_task_manager() {
|
|
248
|
+
let manager = TaskManager::new();
|
|
249
|
+
let task = create_task("Test", 1);
|
|
250
|
+
manager.add_task(task).unwrap();
|
|
251
|
+
assert_eq!(manager.count(), 1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//! Service module for data operations.
|
|
2
|
+
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use std::sync::{Arc, Mutex};
|
|
5
|
+
use std::time::{Duration, Instant};
|
|
6
|
+
|
|
7
|
+
/// Configuration for services.
|
|
8
|
+
#[derive(Debug, Clone)]
|
|
9
|
+
pub struct Config {
|
|
10
|
+
pub api_url: String,
|
|
11
|
+
pub timeout: Duration,
|
|
12
|
+
pub retries: u32,
|
|
13
|
+
pub debug: bool,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl Default for Config {
|
|
17
|
+
fn default() -> Self {
|
|
18
|
+
Config {
|
|
19
|
+
api_url: "https://api.example.com".to_string(),
|
|
20
|
+
timeout: Duration::from_secs(5),
|
|
21
|
+
retries: 3,
|
|
22
|
+
debug: false,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Repository trait for data access.
|
|
28
|
+
pub trait Repository<T> {
|
|
29
|
+
fn save(&self, entity: T) -> Result<(), String>;
|
|
30
|
+
fn find(&self, id: &str) -> Option<T>;
|
|
31
|
+
fn find_all(&self) -> Vec<T>;
|
|
32
|
+
fn delete(&self, id: &str) -> bool;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Generic data service.
|
|
36
|
+
pub struct DataService<T: Clone> {
|
|
37
|
+
config: Config,
|
|
38
|
+
storage: Arc<Mutex<HashMap<String, T>>>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl<T: Clone> DataService<T> {
|
|
42
|
+
/// Create a new data service.
|
|
43
|
+
pub fn new(config: Config) -> Self {
|
|
44
|
+
DataService {
|
|
45
|
+
config,
|
|
46
|
+
storage: Arc::new(Mutex::new(HashMap::new())),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Create with default config.
|
|
51
|
+
pub fn with_defaults() -> Self {
|
|
52
|
+
Self::new(Config::default())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Get the config.
|
|
56
|
+
pub fn config(&self) -> &Config {
|
|
57
|
+
&self.config
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Clear all stored entities.
|
|
61
|
+
pub fn clear(&self) {
|
|
62
|
+
let mut storage = self.storage.lock().unwrap();
|
|
63
|
+
storage.clear();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Cache entry with timestamp.
|
|
68
|
+
struct CacheEntry<T> {
|
|
69
|
+
value: T,
|
|
70
|
+
timestamp: Instant,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Caching service with TTL.
|
|
74
|
+
pub struct CacheService<T: Clone> {
|
|
75
|
+
ttl: Duration,
|
|
76
|
+
cache: Arc<Mutex<HashMap<String, CacheEntry<T>>>>,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl<T: Clone> CacheService<T> {
|
|
80
|
+
/// Create a new cache service.
|
|
81
|
+
pub fn new(ttl: Duration) -> Self {
|
|
82
|
+
CacheService {
|
|
83
|
+
ttl,
|
|
84
|
+
cache: Arc::new(Mutex::new(HashMap::new())),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Get a value from cache.
|
|
89
|
+
pub fn get(&self, key: &str) -> Option<T> {
|
|
90
|
+
let cache = self.cache.lock().unwrap();
|
|
91
|
+
if let Some(entry) = cache.get(key) {
|
|
92
|
+
if entry.timestamp.elapsed() < self.ttl {
|
|
93
|
+
return Some(entry.value.clone());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
None
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Set a value in cache.
|
|
100
|
+
pub fn set(&self, key: String, value: T) {
|
|
101
|
+
let mut cache = self.cache.lock().unwrap();
|
|
102
|
+
cache.insert(key, CacheEntry {
|
|
103
|
+
value,
|
|
104
|
+
timestamp: Instant::now(),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Delete a value from cache.
|
|
109
|
+
pub fn delete(&self, key: &str) -> bool {
|
|
110
|
+
let mut cache = self.cache.lock().unwrap();
|
|
111
|
+
cache.remove(key).is_some()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Clear all values from cache.
|
|
115
|
+
pub fn clear(&self) {
|
|
116
|
+
let mut cache = self.cache.lock().unwrap();
|
|
117
|
+
cache.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// Remove expired entries.
|
|
121
|
+
pub fn cleanup_expired(&self) -> usize {
|
|
122
|
+
let mut cache = self.cache.lock().unwrap();
|
|
123
|
+
let expired: Vec<String> = cache
|
|
124
|
+
.iter()
|
|
125
|
+
.filter(|(_, entry)| entry.timestamp.elapsed() >= self.ttl)
|
|
126
|
+
.map(|(key, _)| key.clone())
|
|
127
|
+
.collect();
|
|
128
|
+
let count = expired.len();
|
|
129
|
+
for key in expired {
|
|
130
|
+
cache.remove(&key);
|
|
131
|
+
}
|
|
132
|
+
count
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// HTTP client for API requests.
|
|
137
|
+
pub struct ApiClient {
|
|
138
|
+
config: Config,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
impl ApiClient {
|
|
142
|
+
/// Create a new API client.
|
|
143
|
+
pub fn new(config: Config) -> Self {
|
|
144
|
+
ApiClient { config }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Make a GET request.
|
|
148
|
+
pub async fn get(&self, path: &str) -> Result<HashMap<String, String>, String> {
|
|
149
|
+
let url = self.build_url(path);
|
|
150
|
+
self.request("GET", &url, None).await
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Make a POST request.
|
|
154
|
+
pub async fn post(&self, path: &str, data: HashMap<String, String>) -> Result<HashMap<String, String>, String> {
|
|
155
|
+
let url = self.build_url(path);
|
|
156
|
+
self.request("POST", &url, Some(data)).await
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Make a DELETE request.
|
|
160
|
+
pub async fn delete(&self, path: &str) -> Result<HashMap<String, String>, String> {
|
|
161
|
+
let url = self.build_url(path);
|
|
162
|
+
self.request("DELETE", &url, None).await
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Build the full URL.
|
|
166
|
+
fn build_url(&self, path: &str) -> String {
|
|
167
|
+
if path.starts_with("http") {
|
|
168
|
+
path.to_string()
|
|
169
|
+
} else {
|
|
170
|
+
format!("{}{}", self.config.api_url, path)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Make an HTTP request.
|
|
175
|
+
async fn request(
|
|
176
|
+
&self,
|
|
177
|
+
method: &str,
|
|
178
|
+
url: &str,
|
|
179
|
+
_data: Option<HashMap<String, String>>,
|
|
180
|
+
) -> Result<HashMap<String, String>, String> {
|
|
181
|
+
// Simulated request
|
|
182
|
+
let mut result = HashMap::new();
|
|
183
|
+
result.insert("status".to_string(), "200".to_string());
|
|
184
|
+
result.insert("method".to_string(), method.to_string());
|
|
185
|
+
result.insert("url".to_string(), url.to_string());
|
|
186
|
+
Ok(result)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Create a data service with defaults.
|
|
191
|
+
pub fn create_service<T: Clone>() -> DataService<T> {
|
|
192
|
+
DataService::with_defaults()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Create an API client with defaults.
|
|
196
|
+
pub fn create_api_client() -> ApiClient {
|
|
197
|
+
ApiClient::new(Config::default())
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[cfg(test)]
|
|
201
|
+
mod tests {
|
|
202
|
+
use super::*;
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn test_cache_service() {
|
|
206
|
+
let cache: CacheService<String> = CacheService::new(Duration::from_secs(60));
|
|
207
|
+
cache.set("key".to_string(), "value".to_string());
|
|
208
|
+
assert_eq!(cache.get("key"), Some("value".to_string()));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//! Utility functions for data manipulation.
|
|
2
|
+
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
|
|
5
|
+
/// Format data as a string.
|
|
6
|
+
pub fn format_data<T: std::fmt::Debug>(data: &T) -> String {
|
|
7
|
+
format!("{:?}", data)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// Validate that a value is not empty.
|
|
11
|
+
pub fn validate_not_empty(value: &str) -> Result<(), String> {
|
|
12
|
+
if value.is_empty() {
|
|
13
|
+
Err("Value cannot be empty".to_string())
|
|
14
|
+
} else {
|
|
15
|
+
Ok(())
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Deep merge two HashMaps.
|
|
20
|
+
pub fn deep_merge(
|
|
21
|
+
base: HashMap<String, String>,
|
|
22
|
+
updates: HashMap<String, String>,
|
|
23
|
+
) -> HashMap<String, String> {
|
|
24
|
+
let mut result = base;
|
|
25
|
+
for (key, value) in updates {
|
|
26
|
+
result.insert(key, value);
|
|
27
|
+
}
|
|
28
|
+
result
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Flatten a nested vector.
|
|
32
|
+
pub fn flatten<T: Clone>(nested: Vec<Vec<T>>) -> Vec<T> {
|
|
33
|
+
nested.into_iter().flatten().collect()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Split a vector into chunks.
|
|
37
|
+
pub fn chunk<T: Clone>(items: Vec<T>, size: usize) -> Vec<Vec<T>> {
|
|
38
|
+
items.chunks(size).map(|c| c.to_vec()).collect()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Safely get a value from a HashMap.
|
|
42
|
+
pub fn safe_get<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a String> {
|
|
43
|
+
map.get(key)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Transform all keys in a HashMap.
|
|
47
|
+
pub fn transform_keys<F>(map: HashMap<String, String>, transformer: F) -> HashMap<String, String>
|
|
48
|
+
where
|
|
49
|
+
F: Fn(&str) -> String,
|
|
50
|
+
{
|
|
51
|
+
map.into_iter().map(|(k, v)| (transformer(&k), v)).collect()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Convert snake_case to camelCase.
|
|
55
|
+
pub fn snake_to_camel(name: &str) -> String {
|
|
56
|
+
let mut result = String::new();
|
|
57
|
+
let mut capitalize_next = false;
|
|
58
|
+
|
|
59
|
+
for ch in name.chars() {
|
|
60
|
+
if ch == '_' {
|
|
61
|
+
capitalize_next = true;
|
|
62
|
+
} else if capitalize_next {
|
|
63
|
+
result.push(ch.to_ascii_uppercase());
|
|
64
|
+
capitalize_next = false;
|
|
65
|
+
} else {
|
|
66
|
+
result.push(ch);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
result
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Convert camelCase to snake_case.
|
|
74
|
+
pub fn camel_to_snake(name: &str) -> String {
|
|
75
|
+
let mut result = String::new();
|
|
76
|
+
|
|
77
|
+
for ch in name.chars() {
|
|
78
|
+
if ch.is_uppercase() {
|
|
79
|
+
if !result.is_empty() {
|
|
80
|
+
result.push('_');
|
|
81
|
+
}
|
|
82
|
+
result.push(ch.to_ascii_lowercase());
|
|
83
|
+
} else {
|
|
84
|
+
result.push(ch);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Data transformer struct.
|
|
92
|
+
pub struct DataTransformer {
|
|
93
|
+
transformations: Vec<Box<dyn Fn(String) -> String>>,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl DataTransformer {
|
|
97
|
+
/// Create a new data transformer.
|
|
98
|
+
pub fn new() -> Self {
|
|
99
|
+
DataTransformer {
|
|
100
|
+
transformations: Vec::new(),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Add a transformation.
|
|
105
|
+
pub fn add_transformation<F>(&mut self, f: F) -> &mut Self
|
|
106
|
+
where
|
|
107
|
+
F: Fn(String) -> String + 'static,
|
|
108
|
+
{
|
|
109
|
+
self.transformations.push(Box::new(f));
|
|
110
|
+
self
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Apply all transformations.
|
|
114
|
+
pub fn transform(&self, data: String) -> String {
|
|
115
|
+
let mut result = data;
|
|
116
|
+
for f in &self.transformations {
|
|
117
|
+
result = f(result);
|
|
118
|
+
}
|
|
119
|
+
result
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Clear all transformations.
|
|
123
|
+
pub fn clear(&mut self) {
|
|
124
|
+
self.transformations.clear();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
impl Default for DataTransformer {
|
|
129
|
+
fn default() -> Self {
|
|
130
|
+
Self::new()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Process and format data.
|
|
135
|
+
pub fn process_and_format<T: std::fmt::Debug>(data: &T) -> String {
|
|
136
|
+
format_data(data)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#[cfg(test)]
|
|
140
|
+
mod tests {
|
|
141
|
+
use super::*;
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_snake_to_camel() {
|
|
145
|
+
assert_eq!(snake_to_camel("hello_world"), "helloWorld");
|
|
146
|
+
assert_eq!(snake_to_camel("some_long_name"), "someLongName");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[test]
|
|
150
|
+
fn test_camel_to_snake() {
|
|
151
|
+
assert_eq!(camel_to_snake("helloWorld"), "hello_world");
|
|
152
|
+
assert_eq!(camel_to_snake("someLongName"), "some_long_name");
|
|
153
|
+
}
|
|
154
|
+
}
|