TidyKit streamlines your Pascal development experience.
Warning
Tired of writing the same code over and over? TidyKit helps you focus on your application logic instead of reinventing the wheel.
// JSON has never been easier
var
Json: TJSONValue;
begin
Json := TJSON.Parse('{"name":"Pascal","age":50}');
WriteLn('Name: ', Json['name'].AsString);
// Easy file operations
var
Files: TStringArray;
FileName: string;
begin
Files := TFileKit.ListFiles('./data', '*.csv', True); // Recursively find all CSVs
for FileName in Files do
TFileKit.CopyFile(FileName, './backup/' + TFileKit.GetFileName(FileName));
end;
// Easy-to-use collections with interface-based memory management
var
NameList: IList<string>;
begin
NameList := CreateList<string>; // no need to worry about freeing!
NameList.Add('Alice');
NameList.Add('Bob');
NameList.Sort;
end;
end;
- Everything You Need: One library with everything from JSON to file handling to collections
- Modern Pascal: Clean, consistent APIs with FPC 3.2.2 compatibility
- Rock-Solid Reliability: Extensive test suite ensures things just work
- No Dependency Nightmares: Works on Windows and Linux with minimal external requirements
- Smart Memory Management: Forget manual Destroy calls with interface-based reference counting
- Ready-to-Use Collections: Lists, dictionaries, sets, and more with generic type safety
- π§ TidyKit: Make Pascal Development Fun Again!
- β¨ Say Goodbye to Boilerplate
- π Why TidyKit?
- π Table of Contents
- ποΈ Architectural Patterns
- β¨ Features
- π» Installation (Lazarus IDE)
- π» Installation (General)
- π Library Usage
- π Quick Start
- π System Requirements
- π Documentation
- π Real-World Examples
- π¬ Community & Support
β οΈ Known Limitations- β Testing
- π€ Contributing
- βοΈ License
- π Acknowledgments
- πΊοΈ Roadmap
We've designed TidyKit with one goal in mind: making your development experience smoother. Here's how we've organized things:
1. Just Call It - For simple operations like file handling or string manipulation:
// No objects to create, just call and go!
TFileKit.WriteTextFile('log.txt', 'Hello World');
NewStr := TStringKit.ToUpperCase('make me shout');
2. Smart Objects - For complex operations like JSON handling, with automatic cleanup:
// No manual memory management needed!
var JsonObj := TJSON.Parse(JsonText);
JsonObj['config']['enabled'] := True;
// When JsonObj goes out of scope, memory is automatically freed
3. Simple Records - For operations that need state without complexity:
// Easy to use with method chaining
var Request := TRequest.Create('https://api.example.com')
.AddHeader('Content-Type', 'application/json')
.SetTimeout(5000);
var Response := Request.Get;
Note
In our next version, we're streamlining things even further to make the API even more consistent and intuitive.
TidyKit is packed with tools that make common programming tasks simple. Here's a taste of what you can do:
Transform, validate, and manipulate text with ease
// Convert case styles seamlessly
var
snake, kebab: string;
begin
snake := TStringKit.ToSnakeCase('HelloWorld');
kebab := TStringKit.ToKebabCase('HelloWorld');
// Validate email addresses
if TStringKit.IsValidEmail(email) then ...
end;
Work with files without the headaches
// Find and filter files
var
PascalFiles: TStringArray;
Dir, FileName: string;
begin
PascalFiles := TFileKit.ListFiles('.', '*.pas', True, fsDateDesc);
// Easy path operations
Dir := TFileKit.GetDirectory(Path);
FileName := TFileKit.GetFileName(Path);
end;
Create and extract ZIP/TAR archives
// Extract ZIP files
TArchiveKit.ExtractZipFile('archive.zip', 'output_dir');
// Create TAR archives
TArchiveKit.CreateTarFile('src_dir', 'output.tar', '*.txt');
Modern encryption and hashing
// Modern SHA3 hashing
var
hash, encrypted: string;
begin
hash := TCrypto.SHA3.ComputeHash(Data);
// Simple AES-256 encryption
encrypted := TCrypto.AES.Encrypt(plaintext, password);
end;
Simplified web requests without the complexity
// GET request with just one line
var
Response: TResponse;
begin
Response := TRequest.Create(url).Get();
// POST JSON data easily
Response := TRequest.Create(url)
.SetJSON(jsonObj).Post();
end;
Parse, create and modify JSON with ease
// Parse existing JSON
var
config, user: TJSONValue;
begin
config := TJSON.Parse(jsonText);
// Build JSON programmatically
user := TJSON.Obj
.Add('name', 'Alice')
.Add('age', 30);
end;
Flexible logging with multiple outputs
// Quick console logging (correct usage)
TLogger.CreateConsoleLogger();
Logger.Info('Starting up...');
// Structured file logging (correct usage)
TLogger.CreateFileLogger('app.log');
Logger.LogStructured(llInfo, 'UserLogin', [NameValuePair('user', username)]);
Modern generic collections with memory management
// Create a dictionary with string keys
var
Dict: IDictionary<string, Integer>;
begin
Dict := CreateDictionary<string, Integer>(@FNV1aHash, @TidyKitStringEquals);
// Add and retrieve values
Dict.Add('Alice', 30);
WriteLn(Dict['Alice']); // Displays: 30
end;
Powerful date and time operations
// Date calculations made simple
var
Today, Tomorrow, NextWeek: TDateTime;
FormattedDate: string;
begin
Today := TDateTimeKit.Today;
Tomorrow := TDateTimeKit.AddDays(Today, 1);
NextWeek := TDateTimeKit.AddWeeks(Today, 1);
// Format dates easily
FormattedDate := TDateTimeKit.Format(Today, 'yyyy-mm-dd');
end;
Parse command-line arguments with ease
// Define and parse arguments
var
Args: TParseArgs;
begin
Args := TParseArgs.Create
.Add('v|verbose', 'Enable verbose output')
.Add('f|file=', 'Input file path')
.Parse;
end;
Extensive documentation for all features
// Documentation for all units
// See our detailed docs for more information
- π String Operations: Case conversion, validation, pattern matching, Unicode support
- π File System: File manipulation, path operations, searching and filtering
- π¦ Archive: ZIP/TAR compression with pattern-based filtering
- π Cryptography: SHA3, SHA2, AES-256 encryption, secure hashing
- π Network: HTTP client with request/response handling
- π JSON: Memory-managed JSON with full Unicode support
- π Logging: Multi-destination logging with structured data support
- π οΈ Command Line Parsing: Easy-to-use argument parser with support for various data types
TidyKit.ParseArgs
- For parsing command-line arguments with support for:- String, integer, float, and boolean options
- Required and optional parameters
- Automatic help generation
- Callback support for custom argument handling
- Array parameters for handling multiple values
- π Collections: Type-safe lists, dictionaries, sets, and queues
- When to use each collection:
TidyKit.Collections.List
- For sequential access and general-purpose ordered collectionsTidyKit.Collections.Dictionary
- For key-value storage with O(1) average time complexity (uses separate chaining for collision resolution)TidyKit.Collections.Deque
- For double-ended queues and efficient operations at both endsTidyKit.Collections.HashSet
- For unique unordered collections with fast lookups
- Documentation: See our detailed docs for Dictionary and HashSet
- All collections support automatic memory management via interfaces (no manual Destroy calls needed)
- Note:
for..in..do
enumeration is not supported; use indexed access orToArray
for traversal
- When to use each collection:
- π οΈ Command Line Parsing: Easy-to-use argument parser with support for various data types
- Clone the repository:
git clone https://github.com/ikelaiah/tidykit-fp
-
Open / start a new project in Lazarus IDE
-
Go to
Package
βOpen Package File (.lpk)...
-
Navigate to the TidyKit packages in the
packages/lazarus/
folder and selectTidyKit.lpk
-
In the package window that opens, click
Compile
-
Click
Use β Add to Project
to install the package
The TidyKit package is now ready to use in your Lazarus project.
- Clone the repository:
git clone https://github.com/ikelaiah/tidykit-fp
- Add the source directory to your project's search path.
uses
// String manipulation unit
TidyKit.Strings, // String operations
// File system operations
TidyKit.FS, // File system operations
// Date Time manipulation unit
TidyKit.DateTime, // Date Time operations
// JSON functionality
TidyKit.JSON, // All JSON functionality
// Logging functionality
TidyKit.Logger, // Easy to use logging system
// Network units
TidyKit.Request, // HTTP client with simple and advanced features
// Cryptography units
TidyKit.Crypto, // Base crypto operations
TidyKit.Crypto.SHA2, // SHA2 implementation
TidyKit.Crypto.SHA3, // SHA3 implementation
TidyKit.Crypto.AES256, // AES-256 implementation
// Archive operations
TidyKit.Archive, // Archive operations
// Command-line argument parsing
TidyKit.ParseArgs, // Simple argument parser
// Collections
TidyKit.Collections.List, // Generic List implementation
TidyKit.Collections.Deque, // Generic Deque implementation
TidyKit.Collections.HashSet; // Generic HashSet implementation
var
Text: string;
begin
// Email validation
if TStringKit.MatchesPattern('[email protected]', '^[\w\.-]+@[\w\.-]+\.\w+$') then
WriteLn('Valid email');
// Format phone number
Text := TStringKit.PadLeft('5551234', 10, '0'); // Returns '0005551234'
// Clean user input
Text := TStringKit.CollapseWhitespace(' Hello World '); // Returns 'Hello World'
// Format product code
Text := TStringKit.PadCenter('A123', 8, '-'); // Returns '--A123---'
end;
var
Files: TFilePathArray;
Attrs: TFileAttributes;
Content: string;
begin
// Basic file operations
TFileKit.WriteFile('example.txt', 'Hello World');
Content := TFileKit.ReadFile('example.txt');
// Directory operations
TFileKit.CreateDirectory('new_folder');
TFileKit.EnsureDirectory('path/to/folder');
// List files with pattern matching
Files := TFileKit.ListFiles('src', '*.pas', True, fsName);
// Get file attributes
Attrs := TFileKit.GetAttributes('example.txt');
WriteLn(Format('Read-only: %s', [BoolToStr(Attrs.ReadOnly, True)]));
// File manipulation
TFileKit.CopyFile('source.txt', 'dest.txt');
TFileKit.MoveFile('old.txt', 'new.txt');
TFileKit.DeleteFile('temp.txt');
// Path operations
WriteLn(TFileKit.GetFileName('path/to/file.txt')); // Returns 'file.txt'
WriteLn(TFileKit.GetExtension('script.pas')); // Returns '.pas'
WriteLn(TFileKit.ChangeExtension('test.txt', '.bak')); // Returns 'test.bak'
end;
var
CurrentTime: TDateTime;
NextWorkday: TDateTime;
TZInfo: TTimeZoneInfo;
OriginalTZ: string;
DSTTransitionDate: TDateTime;
begin
// Get next business day for delivery date
CurrentTime := TDateTimeKit.GetNow;
NextWorkday := TDateTimeKit.NextBusinessDay(CurrentTime);
// Format for display
WriteLn(TDateTimeKit.GetAsString(NextWorkday, 'yyyy-mm-dd'));
// Check if within business hours (9 AM - 5 PM)
if TDateTimeKit.IsWithinInterval(CurrentTime,
TDateTimeKit.CreateInterval(
TDateTimeKit.StartOfDay(CurrentTime) + EncodeTime(9, 0, 0, 0),
TDateTimeKit.StartOfDay(CurrentTime) + EncodeTime(17, 0, 0, 0)
)) then
WriteLn('Within business hours');
// Cross-platform timezone handling
TZInfo := TDateTimeKit.GetTimeZone(CurrentTime);
WriteLn('Current timezone: ', TZInfo.Name);
WriteLn('Offset from UTC: ', TZInfo.Offset, ' minutes');
WriteLn('DST active: ', BoolToStr(TZInfo.IsDST, True));
// Convert between timezones
WriteLn('UTC time: ', TDateTimeKit.GetAsString(
TDateTimeKit.WithTimeZone(CurrentTime, 'UTC'),
'yyyy-mm-dd hh:nn:ss'));
// Cross-platform environment variables for testing
OriginalTZ := GetEnvVar('TZ');
try
// Change timezone for testing
SetEnvVar('TZ', 'America/New_York');
// Create a datetime value near the US DST transition
// (2:00 AM on second Sunday in March 2024)
DSTTransitionDate := EncodeDateTime(2024, 3, 10, 2, 0, 0, 0);
// Check if this time is in DST using GetTimeZone
TZInfo := TDateTimeKit.GetTimeZone(DSTTransitionDate);
WriteLn('DST active: ', BoolToStr(TZInfo.IsDST, True));
WriteLn('Timezone offset: ', TZInfo.Offset, ' minutes');
finally
// Restore original timezone
SetEnvVar('TZ', OriginalTZ);
end;
end;
var
Person: IJSONObject;
Address: IJSONObject;
Hobbies: IJSONArray;
JSON: string;
Value: IJSONValue;
begin
// Create a person object
Person := TJSON.Obj;
Person.Add('name', 'John Smith');
Person.Add('age', 30);
Person.Add('isActive', True);
// Create and add an address object
Address := TJSON.Obj;
Address.Add('street', '123 Main St');
Address.Add('city', 'Springfield');
Address.Add('zipCode', '12345');
Person.Add('address', Address);
// Create and add a hobbies array
Hobbies := TJSON.Arr;
Hobbies.Add('reading');
Hobbies.Add('cycling');
Hobbies.Add('swimming');
Person.Add('hobbies', Hobbies);
// Convert to JSON string with pretty printing
JSON := Person.ToString(True);
WriteLn(JSON);
// Parse JSON string
JSON := '{"name":"Jane Doe","age":25,"skills":["Pascal","Python"]}';
Value := TJSON.Parse(JSON);
Person := Value.AsObject;
// Access values
WriteLn('Name: ', Person['name'].AsString);
WriteLn('Age: ', Person['age'].AsInteger);
WriteLn('First Skill: ', Person['skills'].AsArray[0].AsString);
// Error handling
try
Value := TJSON.Parse('{invalid json}');
except
on E: EJSONException do
WriteLn('Error: ', E.Message);
end;
end;
// Simple one-line setup for console and file logging
TLogger.CreateConsoleAndFileLogger('application.log', llInfo);
// Log messages with different levels
Logger.Debug('Processing started'); // Only shown if minimum level is Debug
Logger.Info('User %s logged in', ['JohnDoe']);
Logger.Warning('Disk space is low: %d%% remaining', [5]);
Logger.Error('Failed to save file: %s', ['Access denied']);
Logger.Fatal('Application crashed: %s', ['Segmentation fault']);
// Create category-based loggers for better organization
var
UILogger, DBLogger: ILogContext;
begin
UILogger := Logger.CreateContext('UI');
DBLogger := Logger.CreateContext('DB');
UILogger.Info('Window created');
DBLogger.Warning('Slow query detected: %s', ['SELECT * FROM large_table']);
end;
// Time operations and log their duration
var
Timer: ITimedOperation;
begin
Timer := Logger.TimedBlock('Data processing');
// ... perform long operation ...
// Timer automatically logs completion with duration when it goes out of scope
end;
// IMPORTANT: Always close log files when shutting down
try
// Your application logic with logging...
finally
Logger.CloseLogFiles; // Ensures all data is written to disk
end;
var
Response: TResponse;
UserData: IJSONObject;
ApiResponse: IJSONObject;
Result: TRequestResult;
begin
// Simple GET request with JSON response
Response := Http.Get('https://api.example.com/data');
if Response.StatusCode = 200 then
begin
ApiResponse := Response.JSON.AsObject;
WriteLn('User ID: ', ApiResponse['id'].AsInteger);
WriteLn('Username: ', ApiResponse['username'].AsString);
end;
// POST with JSON data
UserData := TJSON.Obj;
UserData.Add('name', 'John Smith');
UserData.Add('email', '[email protected]');
UserData.Add('age', 30);
Response := Http.PostJSON('https://api.example.com/users',
UserData.ToString);
if Response.StatusCode = 201 then
WriteLn('User created with ID: ', Response.JSON.AsObject['id'].AsString);
// Error handling with TryGet pattern
Result := Http.TryGet('https://api.example.com/users');
if Result.Success then
WriteLn('Found ', Result.Response.JSON.AsObject['count'].AsInteger, ' users')
else
WriteLn('Error: ', Result.Error);
// Download file with custom headers
Response := Http.Get('https://example.com/large-file.zip');
TFileKit.WriteFile('download.zip', Response.Text);
end;
var
Hash: string;
Encrypted: string;
begin
// Hash password for storage
Hash := TCryptoKit.SHA512Hash('user_password');
// Secure configuration data
Encrypted := TCryptoKit.BlowfishCrypt(
'{"api_key": "secret123"}',
'encryption_key',
bmEncrypt
);
// Verify file integrity
Hash := TCryptoKit.SHA256Hash(TFileKit.ReadFile('important.dat'));
end;
var
SourceDir, DestDir: string;
begin
// Create ZIP archive
SourceDir := 'path/to/source';
TArchiveKit.CompressToZip(SourceDir, 'archive.zip', True); // Recursive
// Extract specific files
DestDir := 'path/to/extract';
TArchiveKit.DecompressFromZip('archive.zip', DestDir, '*.txt'); // Only .txt files
// Create TAR archive with specific files
TArchiveKit.CompressToTar(SourceDir, 'backup.tar', True, '*.pas'); // Only .pas files
// Extract entire TAR archive
TArchiveKit.DecompressFromTar('backup.tar', DestDir);
end;
uses
TidyKit.Collections.List, TidyKit.Collections.Deque,
TidyKit.Collections.HashSet, TidyKit.Collections.HashFunction,
TidyKit.Collections.EqualityFunction;
var
// Example for TList<T>
MyIntList: IList<Integer>;
I: Integer;
// Example for TDeque<T>
MyStringDeque: IDeque<string>;
// Example for THashSet<T>
MyHashSet: IHashSet<string>;
begin
// --- TList<T> Example ---
MyIntList := CreateList<Integer>; // Using helper for interface-based management
MyIntList.Add(10);
MyIntList.Add(20);
MyIntList.Insert(1, 15); // List is now: 10, 15, 20
Write('List items: ');
for I := 0 to MyIntList.Count - 1 do
Write(MyIntList[I], ' ');
WriteLn; // Output: 10 15 20
// --- TDeque<T> Example ---
MyStringDeque := CreateDeque<string>; // Using helper for interface-based management
MyStringDeque.PushBack('apples');
MyStringDeque.PushFront('bananas'); // Deque is now: 'bananas', 'apples'
MyStringDeque.PushBack('cherries'); // Deque is now: 'bananas', 'apples', 'cherries'
Write('Deque items (popping from front): ');
while MyStringDeque.Count > 0 do
Write(MyStringDeque.PopFront, ' ');
WriteLn; // Output: bananas apples cherries
// --- THashSet<T> Example with specialized hash functions ---
MyHashSet := CreateHashSet<string>(@XXHash32, @TidyKitStringEquals);
MyHashSet.Add('apple');
MyHashSet.Add('banana');
MyHashSet.Add('cherry');
MyHashSet.Add('apple'); // Duplicate, won't be added
Write('HashSet items: ');
for I := 0 to MyHashSet.ToArray.Count - 1 do
Write(MyHashSet.ToArray[I], ' ');
WriteLn; // Output: apple banana cherry
// No explicit Free needed as these are interface variables
// and will be automatically managed.
end;
Module | Windows 11 | Ubuntu 24.04.2 |
---|---|---|
TidyKit.Strings | β | β |
TidyKit.FS | β | β |
TidyKit.DateTime | β | β |
TidyKit.JSON | β | β |
TidyKit.JSON.Factory | β | β |
TidyKit.JSON.Parser | β | β |
TidyKit.JSON.Scanner | β | β |
TidyKit.JSON.Types | β | β |
TidyKit.JSON.Writer | β | β |
TidyKit.Logger | β | β |
TidyKit.Request | β | β |
TidyKit.Crypto | β | β |
TidyKit.Crypto.AES256 | β | β |
TidyKit.Crypto.SHA2 | β | β |
TidyKit.Crypto.SHA3 | β | β |
TidyKit.Archive | β | β |
TidyKit.Collections | β | β |
TidyKit.Collections.Deque | β | β |
TidyKit.Collections.Dictionary | β | β |
TidyKit.Collections.EqualityFunction | β | β |
TidyKit.Collections.HashFunction | β | β |
TidyKit.Collections.HashSet | β | β |
TidyKit.Collections.List | β | β |
TidyKit.ParseArgs | β | β |
- Windows
- No external dependencies required
- Linux
- Ubuntu/Debian:
sudo apt-get install libssl-dev
(needed for HTTPS inTidyKit.Request
) - Fedora/RHEL:
sudo dnf install openssl-devel
(needed for HTTPS inTidyKit.Request
)
- Ubuntu/Debian:
- Uses only standard Free Pascal RTL units
- Free Pascal Compiler (FPC) 3.2.2+
- Lazarus 3.6+
- Basic development tools (git, terminal, etc)
For detailed documentation, see:
- π Cheat Sheet
- β FAQ - Common questions about design decisions and patterns
- π Strings
- π File System
- π DateTime
- π JSON
- π Logger
- π Network
- π Crypto
- π¦ Archive
- π Collections:
TidyKit can be used to build a wide variety of applications quickly:
Example Project | Description | Source Code |
---|---|---|
File System Utility | Batch operations on files with pattern matching | View Example |
Simple File Explorer | Navigate directories, view files, and perform file operations with an interactive console UI | View Example |
File Analyzer | Analyze text files, count word frequencies, and gather directory statistics | View Example |
File Backup | Backup files and folders, supports simulate run | View Example |
Simple Data Logger | Record sensor readings with timestamp and export as CSV | View Example |
Configuration Manager | Load, parse, and validate JSON configuration files | View Example |
Secure Password Storage | Hash and verify passwords with SHA-256 | View Example |
Date Calculator | Business day calculator with timezone handling | View Example |
- Questions? Open a discussion
- Found a bug? Report an issue
- Platform Support: Currently only tested on Windows 11 and Ubuntu 24.04. MacOS and FreeBSD are not officially supported.
- Threading: Batch logging mode is not thread-safe. Use in single-threaded contexts or implement your own synchronization.
- HTTP SSL: HTTPS requests require OpenSSL libraries on Linux systems (see Dependencies section).
- Timezone Support: Limited timezone support on Unix-like systems.
- Language Features: Designed for FPC 3.2.2, so no use of inline var declarations or anonymous functions.
- Library Integration: Currently no integration with package managers.
- Open the
TestRunner.lpi
using Lazarus IDE - Compile the project
- Run the Test Runner:
$ cd tests
$ ./TestRunner.exe -a --format=plain
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the Project
- Create your Feature Branch (git checkout -b feature/AmazingFeature)
- Commit your Changes (git commit -m 'Add some AmazingFeature')
- Push to the Branch (git push origin feature/AmazingFeature)
- Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- FPC Team for Free Pascal
- Contributors and maintainers
TidyKit is under active development. Here's what's planned:
- Provide a simpler API.
- Add more real-world examples and tutorials demonstrating common use cases.
- Performance optimizations based on profiling.
- Expand unit test coverage, especially for edge cases identified during API simplification.
- Command line argument parser module.
- Thread-safe collections and data structures module.
- Concurrency module.
- Integration with package managers.
Feedback and suggestions are welcome! See the issues page to contribute ideas or track progress.