...
This commit is contained in:
		
							
								
								
									
										0
									
								
								cargo_instructions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								cargo_instructions.md
									
									
									
									
									
										Normal file
									
								
							| @@ -12,23 +12,18 @@ readme = "README.md" | ||||
|  | ||||
| [workspace] | ||||
| members = [ | ||||
|     ".", | ||||
|     "vault", | ||||
|     "git", | ||||
|     "redisclient", | ||||
|     "mycelium", | ||||
|     "text", | ||||
|     "os", | ||||
|     "net", | ||||
|     "zinit_client", | ||||
|     "process", | ||||
|     "virt", | ||||
|     "zos", | ||||
|     "postgresclient", | ||||
|     "kubernetes", | ||||
|     "rhai", | ||||
|     "herodo", | ||||
|     "service_manager", | ||||
|     "clients/myceliumclient", | ||||
|     "clients/postgresclient", | ||||
|     "clients/redisclient", | ||||
|     "clients/zinitclient", | ||||
|     "core/net", | ||||
|     "core/text", | ||||
|     "crypt/vault", | ||||
|     "system/git", | ||||
|     "system/kubernetes", | ||||
|     "system/os", | ||||
|     "system/process", | ||||
|     "system/virt", | ||||
| ] | ||||
| resolver = "2" | ||||
|  | ||||
| @@ -89,25 +84,22 @@ urlencoding = "2.1.3" | ||||
| tokio-test = "0.4.4" | ||||
|  | ||||
| [dependencies] | ||||
| thiserror = "2.0.12"         # For error handling in the main Error enum | ||||
| tokio = { workspace = true } # For async examples | ||||
| thiserror = { workspace = true } | ||||
| tokio = { workspace = true } | ||||
|  | ||||
| # Optional dependencies - users can choose which modules to include | ||||
| sal-git = { path = "git", optional = true } | ||||
| sal-kubernetes = { path = "kubernetes", optional = true } | ||||
| sal-redisclient = { path = "redisclient", optional = true } | ||||
| sal-mycelium = { path = "mycelium", optional = true } | ||||
| sal-text = { path = "text", optional = true } | ||||
| sal-os = { path = "os", optional = true } | ||||
| sal-net = { path = "net", optional = true } | ||||
| sal-zinit-client = { path = "zinit_client", optional = true } | ||||
| sal-process = { path = "process", optional = true } | ||||
| sal-virt = { path = "virt", optional = true } | ||||
| sal-postgresclient = { path = "postgresclient", optional = true } | ||||
| sal-vault = { path = "vault", optional = true } | ||||
| sal-rhai = { path = "rhai", optional = true } | ||||
| sal-service-manager = { path = "service_manager", optional = true } | ||||
| zinit-client.workspace = true | ||||
| sal-git = { path = "../system/git", optional = true } | ||||
| sal-kubernetes = { path = "../system/kubernetes", optional = true } | ||||
| sal-redisclient = { path = "../clients/redisclient", optional = true } | ||||
| sal-mycelium = { path = "../clients/myceliumclient", optional = true } | ||||
| sal-text = { path = "../core/text", optional = true } | ||||
| sal-os = { path = "../system/os", optional = true } | ||||
| sal-net = { path = "../core/net", optional = true } | ||||
| sal-zinit-client = { path = "../clients/zinitclient", optional = true } | ||||
| sal-process = { path = "../system/process", optional = true } | ||||
| sal-virt = { path = "../system/virt", optional = true } | ||||
| sal-postgresclient = { path = "../clients/postgresclient", optional = true } | ||||
| sal-vault = { path = "../crypt/vault", optional = true } | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
| @@ -125,14 +117,11 @@ process = ["dep:sal-process"] | ||||
| virt = ["dep:sal-virt"] | ||||
| postgresclient = ["dep:sal-postgresclient"] | ||||
| vault = ["dep:sal-vault"] | ||||
| rhai = ["dep:sal-rhai"] | ||||
| service_manager = ["dep:sal-service-manager"] | ||||
|  | ||||
| # Convenience feature groups | ||||
| core = ["os", "process", "text", "net"] | ||||
| clients = ["redisclient", "postgresclient", "zinit_client", "mycelium"] | ||||
| infrastructure = ["git", "vault", "kubernetes", "virt", "service_manager"] | ||||
| scripting = ["rhai"] | ||||
| infrastructure = ["git", "vault", "kubernetes", "virt"] | ||||
| all = [ | ||||
|     "git", | ||||
|     "kubernetes", | ||||
| @@ -146,22 +135,4 @@ all = [ | ||||
|     "virt", | ||||
|     "postgresclient", | ||||
|     "vault", | ||||
|     "rhai", | ||||
|     "service_manager", | ||||
| ] | ||||
|  | ||||
| # Examples | ||||
| [[example]] | ||||
| name = "postgres_cluster" | ||||
| path = "examples/kubernetes/clusters/postgres.rs" | ||||
| required-features = ["kubernetes"] | ||||
|  | ||||
| [[example]] | ||||
| name = "redis_cluster" | ||||
| path = "examples/kubernetes/clusters/redis.rs" | ||||
| required-features = ["kubernetes"] | ||||
|  | ||||
| [[example]] | ||||
| name = "generic_cluster" | ||||
| path = "examples/kubernetes/clusters/generic.rs" | ||||
| required-features = ["kubernetes"] | ||||
|   | ||||
							
								
								
									
										825
									
								
								packages/core/logger/instructions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										825
									
								
								packages/core/logger/instructions.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,825 @@ | ||||
| <file_map> | ||||
| /Users/despiegk/code/github/freeflowuniverse/herolib | ||||
| ├── aiprompts | ||||
| │   └── herolib_core | ||||
| │       ├── core_ourtime.md | ||||
| │       ├── core_paths.md | ||||
| │       └── core_text.md | ||||
| └── lib | ||||
|     └── core | ||||
|         └── logger | ||||
|             ├── factory.v | ||||
|             ├── log_test.v | ||||
|             ├── log.v | ||||
|             ├── model.v | ||||
|             ├── readme.md | ||||
|             └── search.v | ||||
|  | ||||
| </file_map> | ||||
|  | ||||
| <file_contents> | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/factory.v | ||||
| ```v | ||||
| module logger | ||||
|  | ||||
| import freeflowuniverse.herolib.core.pathlib | ||||
|  | ||||
| pub fn new(path string) !Logger { | ||||
| 	mut p := pathlib.get_dir(path: path, create: true)! | ||||
| 	return Logger{ | ||||
| 		path:         p | ||||
| 		lastlog_time: 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/log_test.v | ||||
| ```v | ||||
| module logger | ||||
|  | ||||
| import os | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
| import freeflowuniverse.herolib.core.pathlib | ||||
|  | ||||
| fn testsuite_begin() { | ||||
| 	if os.exists('/tmp/testlogs') { | ||||
| 		os.rmdir_all('/tmp/testlogs')! | ||||
| 	} | ||||
| } | ||||
|  | ||||
| fn test_logger() { | ||||
| 	mut logger := new('/tmp/testlogs')! | ||||
|  | ||||
| 	// Test stdout logging | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'test-app' | ||||
| 		log:       'This is a test message\nWith a second line\nAnd a third line' | ||||
| 		logtype:   .stdout | ||||
| 		timestamp: ourtime.new('2022-12-05 20:14:35')! | ||||
| 	})! | ||||
|  | ||||
| 	// Test error logging | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'error-test' | ||||
| 		log:       'This is an error\nWith details' | ||||
| 		logtype:   .error | ||||
| 		timestamp: ourtime.new('2022-12-05 20:14:35')! | ||||
| 	})! | ||||
|  | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'test-app' | ||||
| 		log:       'This is a test message\nWith a second line\nAnd a third line' | ||||
| 		logtype:   .stdout | ||||
| 		timestamp: ourtime.new('2022-12-05 20:14:36')! | ||||
| 	})! | ||||
|  | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'error-test' | ||||
| 		log:       ' | ||||
| 				This is an error | ||||
| 				 | ||||
| 				With details | ||||
| 			' | ||||
| 		logtype:   .error | ||||
| 		timestamp: ourtime.new('2022-12-05 20:14:36')! | ||||
| 	})! | ||||
|  | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'error-test' | ||||
| 		log:       ' | ||||
| 				aaa | ||||
|  | ||||
| 				bbb | ||||
| 			' | ||||
| 		logtype:   .error | ||||
| 		timestamp: ourtime.new('2022-12-05 22:14:36')! | ||||
| 	})! | ||||
|  | ||||
| 	logger.log(LogItemArgs{ | ||||
| 		cat:       'error-test' | ||||
| 		log:       ' | ||||
| 				aaa2 | ||||
|  | ||||
| 				bbb2 | ||||
| 			' | ||||
| 		logtype:   .error | ||||
| 		timestamp: ourtime.new('2022-12-05 22:14:36')! | ||||
| 	})! | ||||
|  | ||||
| 	// Verify log directory exists | ||||
| 	assert os.exists('/tmp/testlogs'), 'Log directory should exist' | ||||
|  | ||||
| 	// Get log file | ||||
| 	files := os.ls('/tmp/testlogs')! | ||||
| 	assert files.len == 2 | ||||
|  | ||||
| 	mut file := pathlib.get_file( | ||||
| 		path:   '/tmp/testlogs/${files[0]}' | ||||
| 		create: false | ||||
| 	)! | ||||
|  | ||||
| 	content := file.read()!.trim_space() | ||||
|  | ||||
| 	items_stdout := logger.search( | ||||
| 		timestamp_from: ourtime.new('2022-11-1 20:14:35')! | ||||
| 		timestamp_to:   ourtime.new('2025-11-1 20:14:35')! | ||||
| 		logtype:        .stdout | ||||
| 	)! | ||||
| 	assert items_stdout.len == 2 | ||||
|  | ||||
| 	items_error := logger.search( | ||||
| 		timestamp_from: ourtime.new('2022-11-1 20:14:35')! | ||||
| 		timestamp_to:   ourtime.new('2025-11-1 20:14:35')! | ||||
| 		logtype:        .error | ||||
| 	)! | ||||
| 	assert items_error.len == 4 | ||||
| } | ||||
|  | ||||
| fn testsuite_end() { | ||||
| 	// if os.exists('/tmp/testlogs') { | ||||
| 	// 	os.rmdir_all('/tmp/testlogs')! | ||||
| 	// } | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/log.v | ||||
| ```v | ||||
| module logger | ||||
|  | ||||
| import os | ||||
| import freeflowuniverse.herolib.core.texttools | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
|  | ||||
| @[params] | ||||
| pub struct LogItemArgs { | ||||
| pub mut: | ||||
| 	timestamp ?ourtime.OurTime | ||||
| 	cat       string | ||||
| 	log       string | ||||
| 	logtype   LogType | ||||
| } | ||||
|  | ||||
| pub fn (mut l Logger) log(args_ LogItemArgs) ! { | ||||
| 	mut args := args_ | ||||
|  | ||||
| 	t := args.timestamp or { | ||||
| 		t2 := ourtime.now() | ||||
| 		t2 | ||||
| 	} | ||||
|  | ||||
| 	// Format category (max 10 chars, ascii only) | ||||
| 	args.cat = texttools.name_fix(args.cat) | ||||
| 	if args.cat.len > 10 { | ||||
| 		return error('category cannot be longer than 10 chars') | ||||
| 	} | ||||
| 	args.cat = texttools.expand(args.cat, 10, ' ') | ||||
|  | ||||
| 	args.log = texttools.dedent(args.log).trim_space() | ||||
|  | ||||
| 	mut logfile_path := '${l.path.path}/${t.dayhour()}.log' | ||||
|  | ||||
| 	// Create log file if it doesn't exist | ||||
| 	if !os.exists(logfile_path) { | ||||
| 		os.write_file(logfile_path, '')! | ||||
| 		l.lastlog_time = 0 // make sure we put time again | ||||
| 	} | ||||
|  | ||||
| 	mut f := os.open_append(logfile_path)! | ||||
|  | ||||
| 	mut content := '' | ||||
|  | ||||
| 	// Add timestamp if we're in a new second | ||||
| 	if t.unix() > l.lastlog_time { | ||||
| 		content += '\n${t.time().format_ss()}\n' | ||||
| 		l.lastlog_time = t.unix() | ||||
| 	} | ||||
|  | ||||
| 	// Format log lines | ||||
| 	error_prefix := if args.logtype == .error { 'E' } else { ' ' } | ||||
| 	lines := args.log.split('\n') | ||||
|  | ||||
| 	for i, line in lines { | ||||
| 		if i == 0 { | ||||
| 			content += '${error_prefix} ${args.cat} - ${line}\n' | ||||
| 		} else { | ||||
| 			content += '${error_prefix}              ${line}\n' | ||||
| 		} | ||||
| 	} | ||||
| 	f.writeln(content.trim_space_right())! | ||||
| 	f.close() | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/model.v | ||||
| ```v | ||||
| module logger | ||||
|  | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
| import freeflowuniverse.herolib.core.pathlib | ||||
|  | ||||
| @[heap] | ||||
| pub struct Logger { | ||||
| pub mut: | ||||
| 	path         pathlib.Path | ||||
| 	lastlog_time i64 // to see in log format, every second we put a time down, we need to know if we are in a new second (logs can come in much faster) | ||||
| } | ||||
|  | ||||
| pub struct LogItem { | ||||
| pub mut: | ||||
| 	timestamp ourtime.OurTime | ||||
| 	cat       string | ||||
| 	log       string | ||||
| 	logtype   LogType | ||||
| } | ||||
|  | ||||
| pub enum LogType { | ||||
| 	stdout | ||||
| 	error | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/readme.md | ||||
| ```md | ||||
| # Logger Module | ||||
|  | ||||
| A simple logging system that provides structured logging with search capabilities.  | ||||
|  | ||||
| Logs are stored in hourly files with a consistent format that makes them both human-readable and machine-parseable. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Structured logging with categories and error types | ||||
| - Automatic timestamp management | ||||
| - Multi-line message support | ||||
| - Search functionality with filtering options | ||||
| - Human-readable log format | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ```v | ||||
| import freeflowuniverse.herolib.core.logger | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
|  | ||||
| // Create a new logger | ||||
| mut l := logger.new(path: '/var/logs')! | ||||
|  | ||||
| // Log a message | ||||
| l.log( | ||||
|     cat: 'system', | ||||
|     log: 'System started successfully', | ||||
|     logtype: .stdout | ||||
| )! | ||||
|  | ||||
| // Log an error | ||||
| l.log( | ||||
|     cat: 'system', | ||||
|     log: 'Failed to connect\nRetrying in 5 seconds...', | ||||
|     logtype: .error | ||||
| )! | ||||
|  | ||||
| // Search logs | ||||
| results := l.search( | ||||
|     timestamp_from: ourtime.now().warp("-24h"), // Last 24 hours | ||||
|     cat: 'system',                               // Filter by category | ||||
|     log: 'failed',                              // Search in message content | ||||
|     logtype: .error,                            // Only error messages | ||||
|     maxitems: 100                               // Limit results | ||||
| )! | ||||
| ``` | ||||
|  | ||||
| ## Log Format | ||||
|  | ||||
| Each log file is named using the format `YYYY-MM-DD-HH.log` and contains entries in the following format: | ||||
|  | ||||
| ``` | ||||
| 21:23:42 | ||||
|  system     - This is a normal log message | ||||
|  system     - This is a multi-line message | ||||
|               second line with proper indentation | ||||
|               third line maintaining alignment | ||||
| E error_cat - This is an error message | ||||
| E             second line of error | ||||
| E             third line of error | ||||
| ``` | ||||
|  | ||||
| ### Format Rules | ||||
|  | ||||
| - Time stamps (HH:MM:SS) are written once per second when the log time changes | ||||
| - Categories are: | ||||
|   - Limited to 10 characters maximum | ||||
|   - Padded with spaces to exactly 10 characters | ||||
|   - Any `-` in category names are converted to `_` | ||||
| - Each line starts with either: | ||||
|   - ` ` (space) for normal logs (LogType.stdout) | ||||
|   - `E` for error logs (LogType.error) | ||||
| - Multi-line messages maintain consistent indentation (14 spaces after the prefix) | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/search.v | ||||
| ```v | ||||
| module logger | ||||
|  | ||||
| import os | ||||
| import freeflowuniverse.herolib.core.texttools | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
|  | ||||
| @[params] | ||||
| pub struct SearchArgs { | ||||
| pub mut: | ||||
| 	timestamp_from ?ourtime.OurTime | ||||
| 	timestamp_to   ?ourtime.OurTime | ||||
| 	cat            string // can be empty | ||||
| 	log            string // any content in here will be looked for | ||||
| 	logtype        LogType | ||||
| 	maxitems       int = 10000 | ||||
| } | ||||
|  | ||||
| pub fn (mut l Logger) search(args_ SearchArgs) ![]LogItem { | ||||
| 	mut args := args_ | ||||
|  | ||||
| 	// Format category (max 10 chars, ascii only) | ||||
| 	args.cat = texttools.name_fix(args.cat) | ||||
| 	if args.cat.len > 10 { | ||||
| 		return error('category cannot be longer than 10 chars') | ||||
| 	} | ||||
|  | ||||
| 	mut timestamp_from := args.timestamp_from or { ourtime.OurTime{} } | ||||
| 	mut timestamp_to := args.timestamp_to or { ourtime.OurTime{} } | ||||
|  | ||||
| 	// Get time range | ||||
| 	from_time := timestamp_from.unix() | ||||
| 	to_time := timestamp_to.unix() | ||||
| 	if from_time > to_time { | ||||
| 		return error('from_time cannot be after to_time: ${from_time} <  ${to_time}') | ||||
| 	} | ||||
|  | ||||
| 	mut result := []LogItem{} | ||||
|  | ||||
| 	// Find log files in time range | ||||
| 	mut files := os.ls(l.path.path)! | ||||
| 	files.sort() | ||||
|  | ||||
| 	for file in files { | ||||
| 		if !file.ends_with('.log') { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Parse dayhour from filename | ||||
| 		dayhour := file[..file.len - 4] // remove .log | ||||
| 		file_time := ourtime.new(dayhour)! | ||||
| 		mut current_time := ourtime.OurTime{} | ||||
| 		mut current_item := LogItem{} | ||||
| 		mut collecting := false | ||||
|  | ||||
| 		// Skip if file is outside time range | ||||
| 		if file_time.unix() < from_time || file_time.unix() > to_time { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Read and parse log file | ||||
| 		content := os.read_file('${l.path.path}/${file}')! | ||||
| 		lines := content.split('\n') | ||||
|  | ||||
| 		for line in lines { | ||||
| 			if result.len >= args.maxitems { | ||||
| 				return result | ||||
| 			} | ||||
|  | ||||
| 			line_trim := line.trim_space() | ||||
| 			if line_trim == '' { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// Check if this is a timestamp line | ||||
| 			if !(line.starts_with(' ') || line.starts_with('E')) { | ||||
| 				current_time = ourtime.new(line_trim)! | ||||
| 				if collecting { | ||||
| 					process(mut result, current_item, current_time, args, from_time, to_time)! | ||||
| 				} | ||||
| 				collecting = false | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if collecting && line.len > 14 && line[13] == `-` { | ||||
| 				process(mut result, current_item, current_time, args, from_time, to_time)! | ||||
| 				collecting = false | ||||
| 			} | ||||
|  | ||||
| 			// Parse log line | ||||
| 			is_error := line.starts_with('E') | ||||
| 			if !collecting { | ||||
| 				// Start new item | ||||
| 				current_item = LogItem{ | ||||
| 					timestamp: current_time | ||||
| 					cat:       line[2..12].trim_space() | ||||
| 					log:       line[15..].trim_space() | ||||
| 					logtype:   if is_error { .error } else { .stdout } | ||||
| 				} | ||||
| 				// println('new current item: ${current_item}') | ||||
| 				collecting = true | ||||
| 			} else { | ||||
| 				// Continuation line | ||||
| 				if line_trim.len < 16 { | ||||
| 					current_item.log += '\n' | ||||
| 				} else { | ||||
| 					current_item.log += '\n' + line[15..] | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Add last item if collecting | ||||
| 		if collecting { | ||||
| 			process(mut result, current_item, current_time, args, from_time, to_time)! | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| fn process(mut result []LogItem, current_item LogItem, current_time ourtime.OurTime, args SearchArgs, from_time i64, to_time i64) ! { | ||||
| 	// Add previous item if it matches filters | ||||
| 	log_epoch := current_item.timestamp.unix() | ||||
| 	if log_epoch < from_time || log_epoch > to_time { | ||||
| 		return | ||||
| 	} | ||||
| 	if (args.cat == '' || current_item.cat.trim_space() == args.cat) | ||||
| 		&& (args.log == '' || current_item.log.contains(args.log)) | ||||
| 		&& args.logtype == current_item.logtype { | ||||
| 		result << current_item | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_ourtime.md | ||||
| ```md | ||||
| # OurTime Module | ||||
|  | ||||
| The `OurTime` module in V provides flexible time handling, supporting relative and absolute time formats, Unix timestamps, and formatting utilities. | ||||
|  | ||||
| ## Key Features | ||||
| - Create time objects from strings or current time | ||||
| - Relative time expressions (e.g., `+1h`, `-2d`) | ||||
| - Absolute time formats (e.g., `YYYY-MM-DD HH:mm:ss`) | ||||
| - Unix timestamp conversion | ||||
| - Time formatting and warping | ||||
|  | ||||
| ## Basic Usage | ||||
|  | ||||
| ```v | ||||
| import freeflowuniverse.herolib.data.ourtime | ||||
|  | ||||
| // Current time | ||||
| mut t := ourtime.now() | ||||
|  | ||||
| // From string | ||||
| t2 := ourtime.new('2022-12-05 20:14:35')! | ||||
|  | ||||
| // Get formatted string | ||||
| println(t2.str()) // e.g., 2022-12-05 20:14 | ||||
|  | ||||
| // Get Unix timestamp | ||||
| println(t2.unix()) // e.g., 1670271275 | ||||
| ``` | ||||
|  | ||||
| ## Time Formats | ||||
|  | ||||
| ### Relative Time | ||||
|  | ||||
| Use `s` (seconds), `h` (hours), `d` (days), `w` (weeks), `M` (months), `Q` (quarters), `Y` (years). | ||||
|  | ||||
| ```v | ||||
| // Create with relative time | ||||
| mut t := ourtime.new('+1w +2d -4h')! | ||||
|  | ||||
| // Warp existing time | ||||
| mut t2 := ourtime.now() | ||||
| t2.warp('+1h')! | ||||
| ``` | ||||
|  | ||||
| ### Absolute Time | ||||
|  | ||||
| Supports `YYYY-MM-DD HH:mm:ss`, `YYYY-MM-DD HH:mm`, `YYYY-MM-DD HH`, `YYYY-MM-DD`, `DD-MM-YYYY`. | ||||
|  | ||||
| ```v | ||||
| t1 := ourtime.new('2022-12-05 20:14:35')! | ||||
| t2 := ourtime.new('2022-12-05')! // Time defaults to 00:00:00 | ||||
| ``` | ||||
|  | ||||
| ## Methods Overview | ||||
|  | ||||
| ### Creation | ||||
|  | ||||
| ```v | ||||
| now_time := ourtime.now() | ||||
| from_string := ourtime.new('2023-01-15')! | ||||
| from_epoch := ourtime.new_from_epoch(1673788800) | ||||
| ``` | ||||
|  | ||||
| ### Formatting | ||||
|  | ||||
| ```v | ||||
| mut t := ourtime.now() | ||||
| println(t.str()) // YYYY-MM-DD HH:mm | ||||
| println(t.day()) // YYYY-MM-DD | ||||
| println(t.key()) // YYYY_MM_DD_HH_mm_ss | ||||
| println(t.md())  // Markdown format | ||||
| ``` | ||||
|  | ||||
| ### Operations | ||||
|  | ||||
| ```v | ||||
| mut t := ourtime.now() | ||||
| t.warp('+1h')! // Move 1 hour forward | ||||
| unix_ts := t.unix() | ||||
| is_empty := t.empty() | ||||
| ``` | ||||
|  | ||||
| ## Error Handling | ||||
|  | ||||
| Time parsing methods return a `Result` type and should be handled with `!` or `or` blocks. | ||||
|  | ||||
| ```v | ||||
| t_valid := ourtime.new('2023-01-01')! | ||||
| t_invalid := ourtime.new('bad-date') or { | ||||
|     println('Error: ${err}') | ||||
|     ourtime.now() // Fallback | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_paths.md | ||||
| ```md | ||||
| # Pathlib Usage Guide | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The pathlib module provides a comprehensive interface for handling file system operations. Key features include: | ||||
|  | ||||
| - Robust path handling for files, directories, and symlinks | ||||
| - Support for both absolute and relative paths | ||||
| - Automatic home directory expansion (~) | ||||
| - Recursive directory operations | ||||
| - Path filtering and listing | ||||
| - File and directory metadata access | ||||
|  | ||||
| ## Basic Usage | ||||
|  | ||||
| ### Importing pathlib | ||||
| ```v | ||||
| import freeflowuniverse.herolib.core.pathlib | ||||
| ``` | ||||
|  | ||||
| ### Creating Path Objects | ||||
| ```v | ||||
| // Create a Path object for a file | ||||
| mut file_path := pathlib.get("path/to/file.txt") | ||||
|  | ||||
| // Create a Path object for a directory | ||||
| mut dir_path := pathlib.get("path/to/directory") | ||||
| ``` | ||||
|  | ||||
| ### Basic Path Operations | ||||
| ```v | ||||
| // Get absolute path | ||||
| abs_path := file_path.absolute() | ||||
|  | ||||
| // Get real path (resolves symlinks) | ||||
| real_path := file_path.realpath() | ||||
|  | ||||
| // Check if path exists | ||||
| if file_path.exists() { | ||||
|     // Path exists | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Path Properties and Methods | ||||
|  | ||||
| ### Path Types | ||||
| ```v | ||||
| // Check if path is a file | ||||
| if file_path.is_file() { | ||||
|     // Handle as file | ||||
| } | ||||
|  | ||||
| // Check if path is a directory | ||||
| if dir_path.is_dir() { | ||||
|     // Handle as directory | ||||
| } | ||||
|  | ||||
| // Check if path is a symlink | ||||
| if file_path.is_link() { | ||||
|     // Handle as symlink | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Path Normalization | ||||
| ```v | ||||
| // Normalize path (remove extra slashes, resolve . and ..) | ||||
| normalized_path := file_path.path_normalize() | ||||
|  | ||||
| // Get path directory | ||||
| dir_path := file_path.path_dir() | ||||
|  | ||||
| // Get path name without extension | ||||
| name_no_ext := file_path.name_no_ext() | ||||
| ``` | ||||
|  | ||||
| ## File and Directory Operations | ||||
|  | ||||
| ### File Operations | ||||
| ```v | ||||
| // Write to file | ||||
| file_path.write("Content to write")! | ||||
|  | ||||
| // Read from file | ||||
| content := file_path.read()! | ||||
|  | ||||
| // Delete file | ||||
| file_path.delete()! | ||||
| ``` | ||||
|  | ||||
| ### Directory Operations | ||||
| ```v | ||||
| // Create directory | ||||
| mut dir := pathlib.get_dir( | ||||
|     path: "path/to/new/dir" | ||||
|     create: true | ||||
| )! | ||||
|  | ||||
| // List directory contents | ||||
| mut dir_list := dir.list()! | ||||
|  | ||||
| // Delete directory | ||||
| dir.delete()! | ||||
| ``` | ||||
|  | ||||
| ### Symlink Operations | ||||
| ```v | ||||
| // Create symlink | ||||
| file_path.link("path/to/symlink", delete_exists: true)! | ||||
|  | ||||
| // Resolve symlink | ||||
| real_path := file_path.realpath() | ||||
| ``` | ||||
|  | ||||
| ## Advanced Operations | ||||
|  | ||||
| ### Path Copying | ||||
| ```v | ||||
| // Copy file to destination | ||||
| file_path.copy(dest: "path/to/destination")! | ||||
| ``` | ||||
|  | ||||
| ### Recursive Operations | ||||
| ```v | ||||
| // List directory recursively | ||||
| mut recursive_list := dir.list(recursive: true)! | ||||
|  | ||||
| // Delete directory recursively | ||||
| dir.delete()! | ||||
| ``` | ||||
|  | ||||
| ### Path Filtering | ||||
| ```v | ||||
| // List files matching pattern | ||||
| mut filtered_list := dir.list( | ||||
|     regex: [r".*\.txt$"], | ||||
|     recursive: true | ||||
| )! | ||||
| ``` | ||||
|  | ||||
| ## Best Practices | ||||
|  | ||||
| ### Error Handling | ||||
| ```v | ||||
| if file_path.exists() { | ||||
|     // Safe to operate | ||||
| } else { | ||||
|     // Handle missing file | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ``` | ||||
|  | ||||
| File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_text.md | ||||
| ```md | ||||
| # TextTools Module | ||||
|  | ||||
| The `texttools` module provides a comprehensive set of utilities for text manipulation and processing. | ||||
|  | ||||
| ## Functions and Examples: | ||||
|  | ||||
| ```v | ||||
| import freeflowuniverse.herolib.core.texttools | ||||
|  | ||||
| assert hello_world == texttools.name_fix("Hello World!") | ||||
|  | ||||
| ``` | ||||
| ### Name/Path Processing | ||||
| *   `name_fix(name string) string`: Normalizes filenames and paths. | ||||
| *   `name_fix_keepspace(name string) !string`: Like name_fix but preserves spaces. | ||||
| *   `name_fix_no_ext(name_ string) string`: Removes file extension. | ||||
| *   `name_fix_snake_to_pascal(name string) string`: Converts snake_case to PascalCase. | ||||
|     ```v | ||||
|     name := texttools.name_fix_snake_to_pascal("hello_world") // Result: "HelloWorld" | ||||
|     ``` | ||||
| *   `snake_case(name string) string`: Converts PascalCase to snake_case. | ||||
|     ```v | ||||
|     name := texttools.snake_case("HelloWorld") // Result: "hello_world" | ||||
|     ``` | ||||
| *   `name_split(name string) !(string, string)`: Splits name into site and page components. | ||||
|  | ||||
|  | ||||
| ### Text Cleaning | ||||
| *   `name_clean(r string) string`: Normalizes names by removing special characters. | ||||
|     ```v | ||||
|     name := texttools.name_clean("Hello@World!") // Result: "HelloWorld" | ||||
|     ``` | ||||
| *   `ascii_clean(r string) string`: Removes all non-ASCII characters. | ||||
| *   `remove_empty_lines(text string) string`: Removes empty lines from text. | ||||
|     ```v | ||||
|     text := texttools.remove_empty_lines("line1\n\nline2\n\n\nline3") // Result: "line1\nline2\nline3" | ||||
|     ``` | ||||
| *   `remove_double_lines(text string) string`: Removes consecutive empty lines. | ||||
| *   `remove_empty_js_blocks(text string) string`: Removes empty code blocks (```...```). | ||||
|  | ||||
| ### Command Line Parsing | ||||
| *   `cmd_line_args_parser(text string) ![]string`: Parses command line arguments with support for quotes and escaping. | ||||
|     ```v | ||||
|     args := texttools.cmd_line_args_parser("'arg with spaces' --flag=value") // Result: ['arg with spaces', '--flag=value'] | ||||
|     ``` | ||||
| *   `text_remove_quotes(text string) string`: Removes quoted sections from text. | ||||
| *   `check_exists_outside_quotes(text string, items []string) bool`: Checks if items exist in text outside of quotes. | ||||
|  | ||||
| ### Text Expansion | ||||
| *   `expand(txt_ string, l int, expand_with string) string`: Expands text to a specified length with a given character. | ||||
|  | ||||
| ### Indentation | ||||
| *   `indent(text string, prefix string) string`: Adds indentation prefix to each line. | ||||
|     ```v | ||||
|     text := texttools.indent("line1\nline2", "  ") // Result: "  line1\n  line2\n" | ||||
|     ``` | ||||
| *   `dedent(text string) string`: Removes common leading whitespace from every line. | ||||
|     ```v | ||||
|     text := texttools.dedent("    line1\n    line2") // Result: "line1\nline2" | ||||
|     ``` | ||||
|  | ||||
| ### String Validation | ||||
| *   `is_int(text string) bool`: Checks if text contains only digits. | ||||
| *   `is_upper_text(text string) bool`: Checks if text contains only uppercase letters. | ||||
|  | ||||
| ### Multiline Processing | ||||
| *   `multiline_to_single(text string) !string`: Converts multiline text to a single line with proper escaping. | ||||
|  | ||||
| ### Text Splitting | ||||
| *   `split_smart(t string, delimiter_ string) []string`: Intelligent string splitting that respects quotes. | ||||
|  | ||||
| ### Tokenization | ||||
| *   `tokenize(text_ string) TokenizerResult`: Tokenizes text into meaningful parts. | ||||
| *   `text_token_replace(text string, tofind string, replacewith string) !string`: Replaces tokens in text. | ||||
|  | ||||
| ### Version Parsing | ||||
| *   `version(text_ string) int`: Converts version strings to comparable integers. | ||||
|     ```v | ||||
|     ver := texttools.version("v0.4.36") // Result: 4036 | ||||
|     ver = texttools.version("v1.4.36") // Result: 1004036 | ||||
|     ``` | ||||
|  | ||||
| ### Formatting | ||||
| *   `format_rfc1123(t time.Time) string`: Formats a time.Time object into RFC 1123 format. | ||||
|    | ||||
|  | ||||
| ### Array Operations | ||||
| *   `to_array(r string) []string`: Converts a comma or newline separated list to an array of strings. | ||||
|     ```v | ||||
|     text := "item1,item2,item3" | ||||
|     array := texttools.to_array(text) // Result: ['item1', 'item2', 'item3'] | ||||
|     ``` | ||||
| *   `to_array_int(r string) []int`: Converts a text list to an array of integers. | ||||
| *   `to_map(mapstring string, line string, delimiter_ string) map[string]string`: Intelligent mapping of a line to a map based on a template. | ||||
|     ```v | ||||
|     r := texttools.to_map("name,-,-,-,-,pid,-,-,-,-,path", | ||||
|         "root   304   0.0  0.0 408185328   1360   ??  S    16Dec23   0:34.06 /usr/sbin/distnoted") | ||||
|     // Result: {'name': 'root', 'pid': '1360', 'path': '/usr/sbin/distnoted'} | ||||
|     ``` | ||||
|  | ||||
| ``` | ||||
| </file_contents> | ||||
| <user_instructions> | ||||
| create a module in rust in location packages/core/logger | ||||
|  | ||||
| which reimplements herolib/lib/core/logger  | ||||
| all features need to be reimplemented | ||||
|  | ||||
|  | ||||
| write me an implementation plan for my coding agent | ||||
|  | ||||
|  | ||||
| </user_instructions> | ||||
		Reference in New Issue
	
	Block a user