Subversion Repositories camp_sysinfo_client_3

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
258 rodolico 1
=head1 NAME
2
 
3
DataTransport - Efficient serialization format for nested data structures
4
 
5
=head1 SYNOPSIS
6
 
7
    use DataTransport;
8
 
9
    # Create instance with default pipe delimiter
10
    my $dt = DataTransport->new();
11
 
12
    # Or specify custom delimiter
13
    my $dt = DataTransport->new(delimiter => '::');
14
 
15
    # Encode a data structure
16
    my $data = {
17
        hostname => 'server.example.com',
18
        config => {
19
            network => {
20
                interface => 'eth0',
21
                address => '192.168.1.1'
22
            }
23
        },
24
        packages => ['perl', 'python', 'gcc']
25
    };
26
 
27
    my $encoded = $dt->encode($data);
28
    # Result:
29
    # config|network|address=192.168.1.1
30
    # config|network|interface=eth0
31
    # hostname=server.example.com
32
    # packages="perl"  "python"        "gcc"
33
 
34
    # Decode back to hash
35
    my $decoded = $dt->decode($encoded);
36
 
37
    # Write to file
38
    $dt->writeFile($data, 'output.dat');
39
 
40
    # Read from file
41
    my $data = $dt->readFile('output.dat');
42
 
43
=head1 DESCRIPTION
44
 
45
DataTransport provides a simple, efficient format for serializing nested 
46
Perl data structures. It uses a flat key=value format where nested hash 
47
keys are joined with a delimiter (default: pipe symbol '|').
48
 
49
The incentive for this is to have a human-readable, easy-to-edit format that
50
does not rely on any external modules. It is particularly useful for configuration
51
files or simple data exchange where simplicity and readability are desired.
52
 
53
Key features:
54
- Completely self contained; does not need any additional modules
55
- Flat text format, easy to read and edit
56
- Supports nested hashes, arrays, and scalar values
57
- Configurable delimiter for key paths
58
- File I/O with validation headers
59
- Keys can contain periods and special characters except for the delimiter and 
60
  equals sign
61
- Preserves empty strings and numeric zero values
62
 
63
=head1 FILE FORMAT
64
 
65
DataTransport files use the following format:
66
 
67
    # DataTransport v1.0
68
    key=value
69
    nested|key=value
70
    array|values="item1"  "item2"    "item3"
71
 
72
Lines starting with # are comments. Nested hash keys are joined with 
73
the delimiter. Array values are tab-separated and quoted.
74
 
75
=head1 PACKAGE VARIABLES
76
 
77
=head2 $VERSION
78
 
79
Module version number.
80
 
81
=head2 $delimiter
82
 
83
Global default delimiter for key paths. Default is '|' (pipe symbol).
84
Can be overridden globally or per-instance.
85
 
86
    # Override globally
87
    $DataTransport::delimiter = '::';
88
    my $dt = DataTransport->new();  # Uses '::'
89
 
90
    # Or per-instance (recommended)
91
    my $dt = DataTransport->new(delimiter => '::');
92
 
93
=head1 METHODS
94
 
95
=head2 new
96
 
97
    my $dt = DataTransport->new();
98
    my $dt = DataTransport->new(delimiter => '::');
99
 
100
Constructor. Creates a new DataTransport instance.
101
 
102
B<Parameters:>
103
 
104
=over 4
105
 
106
=item delimiter (optional)
107
 
108
Custom delimiter to use for joining nested keys. If not specified, 
109
uses the package-level $delimiter variable (default: '|').
110
 
111
=back
112
 
113
B<Returns:> DataTransport object
114
 
115
=cut
116
 
117
package DataTransport;
118
 
119
use strict;
120
use warnings;
121
 
122
our $VERSION = '1.0';
123
our $delimiter = '|';  # Default delimiter for key paths
124
 
125
sub new {
126
   my ($class, %options) = @_;
127
   my $self = bless {
128
      delimiter => $options{delimiter} || $delimiter
129
   }, $class;
130
   return $self;
131
}
132
 
133
=head2 encode
134
 
135
    my $encoded = $dt->encode($hashref);
136
    my @lines = $dt->encode($hashref);
137
 
138
Encodes a nested hash structure into DataTransport format.
139
 
140
B<Parameters:>
141
 
142
=over 4
143
 
144
=item $hashref
145
 
146
Reference to a hash to encode. Can contain nested hashes, arrays, 
147
and scalar values.
148
 
149
=item $prefix (internal)
150
 
151
Internal parameter used during recursion. Do not pass this parameter.
152
 
153
=back
154
 
155
B<Returns:> 
156
 
157
In scalar context, returns a string with newline-separated key=value pairs.
158
In array context, returns an array of key=value lines.
159
 
160
B<Format Rules:>
161
 
162
=over 4
163
 
164
=item * Nested hash keys are joined with the delimiter
165
 
166
=item * Arrays are encoded as tab-separated quoted values
167
 
168
=item * Scalar values are output as-is
169
 
170
=item * Keys are sorted alphabetically at each level
171
 
172
=back
173
 
174
B<Example:>
175
 
176
    my $data = { host => 'server', config => { port => 80 } };
177
    my $encoded = $dt->encode($data);
178
    # config|port=80
179
    # host=server
180
 
181
=cut
182
 
183
sub encode {
184
   my ($self, $hashref, $prefix) = @_;
185
   $prefix //= '';
186
   my @lines;
187
   my $delim = $self->{delimiter};
188
 
189
   foreach my $key (sort keys %$hashref) {
190
      my $full_key = $prefix ? "$prefix$delim$key" : $key;
191
      my $value = $hashref->{$key};
192
 
193
      if (ref($value) eq 'HASH') {
194
         push @lines, $self->encode($value, $full_key);
195
      } elsif (ref($value) eq 'ARRAY') {
196
         my $encoded_array = join("\t", map { qq{"$_"} } @$value);
197
         push @lines, "$full_key=$encoded_array";
198
      } else {
199
         push @lines, "$full_key=$value";
200
      }
201
   }
202
 
203
   return wantarray ? @lines : join("\n", @lines);
204
}
205
 
206
=head2 decode
207
 
208
    my $hashref = $dt->decode($encoded_string);
209
    my $hashref = $dt->decode(\@lines);
210
 
211
Decodes DataTransport format back into a nested hash structure.
212
 
213
B<Parameters:>
214
 
215
=over 4
216
 
217
=item $data
218
 
219
Either a string containing newline-separated key=value pairs, or 
220
an array reference of individual lines.
221
 
222
=back
223
 
224
B<Returns:> 
225
 
226
Hash reference containing the decoded data structure.
227
 
228
B<Format Recognition:>
229
 
230
=over 4
231
 
232
=item * Lines with delimiters are split into nested hash keys
233
 
234
=item * Values starting and ending with quotes become arrays
235
 
236
=item * Tab-separated quoted values become array elements
237
 
238
=item * Other values are stored as scalars
239
 
240
=back
241
 
242
B<Example:>
243
 
244
    my $encoded = "config|port=80\nhost=server";
245
    my $data = $dt->decode($encoded);
246
    # $data = { host => 'server', config => { port => 80 } }
247
 
248
=cut
249
 
250
sub decode {
251
   my ($self, $data) = @_;
252
   my $hashref = {};
253
   my @lines = ref($data) eq 'ARRAY' ? @$data : split(/\n/, $data);
254
   my $delim = quotemeta($self->{delimiter});
255
 
256
   foreach my $line (@lines) {
257
      next unless $line =~ /^(.+?)=(.*)$/;
258
      my ($key_path, $value) = ($1, $2);
259
      my @keys = split(/$delim/, $key_path);
260
 
261
      # Check if value is an array (contains quotes at start and end, possibly with tabs)
262
      # Single element: "value" or multiple: "val1"\t"val2"\t"val3"
263
      if ($value =~ /^".*"$/) {
264
         if ($value =~ /\t/) {
265
            # Multiple elements
266
            my @elements = split(/\t/, $value);
267
            @elements = map { s/^"//; s/"$//; s/^\s+//; s/\s+$//; $_ } @elements;
268
            $value = \@elements;
269
         } else {
270
            # Single element array
271
            $value =~ s/^"//;
272
            $value =~ s/"$//;
273
            $value = [$value];
274
         }
275
      }
276
 
277
      # Build nested hash structure
278
      my $current = $hashref;
279
      for (my $i = 0; $i < @keys - 1; $i++) {
280
         $current->{$keys[$i]} //= {};
281
         $current = $current->{$keys[$i]};
282
      }
283
      $current->{$keys[-1]} = $value;
284
   }
285
 
286
   return $hashref;
287
}
288
 
289
=head2 writeFile
290
 
291
    $dt->writeFile($hashref, $filename);
292
 
293
Encodes a hash structure and writes it to a file with a validation header.
294
 
295
B<Parameters:>
296
 
297
=over 4
298
 
299
=item $hashref
300
 
301
Reference to hash to encode and write.
302
 
303
=item $filename
304
 
305
Path to output file. Will be created or overwritten.
306
 
307
=back
308
 
309
B<Returns:> 
310
 
311
1 on success, undef on failure.
312
 
313
B<File Format:>
314
 
315
The file includes a comment header for validation:
316
 
317
    # DataTransport v1.0
318
    key1=value1
319
    key2=value2
320
 
321
B<Example:>
322
 
323
    my $data = { hostname => 'server', port => 80 };
324
    $dt->writeFile($data, '/tmp/config.dat') or die "Write failed";
325
 
326
=cut
327
 
328
#######################################################
329
#
330
# writeFile( $hashref, $filename )
331
#
332
# Write encoded data structure to a file with DataTransport header
333
#
334
# Parameters:
335
#   $hashref  - reference to hash to encode and write
336
#   $filename - path to output file
337
#
338
# Returns:
339
#   1 on success, undef on failure
340
#
341
# File format includes a comment header identifying it as
342
# a DataTransport file for validation during readFile
343
#
344
#######################################################
345
sub writeFile {
346
   my ($self, $hashref, $filename) = @_;
347
 
348
   return unless defined $hashref && defined $filename;
349
 
350
   # Open file for writing
351
   open(my $fh, '>', $filename) or return;
352
 
353
   # Write header comment
354
   print $fh "# DataTransport v$VERSION\n";
355
 
356
   # Encode and write data
357
   my $encoded = $self->encode($hashref);
358
   print $fh $encoded;
359
   print $fh "\n" unless $encoded =~ /\n$/;
360
 
361
   close($fh);
362
   return 1;
363
}
364
 
365
=head2 readFile
366
 
367
    my $hashref = $dt->readFile($filename);
368
 
369
Reads and decodes a DataTransport file.
370
 
371
B<Parameters:>
372
 
373
=over 4
374
 
375
=item $filename
376
 
377
Path to DataTransport file to read.
378
 
379
=back
380
 
381
B<Returns:> 
382
 
383
Hash reference on success, undef on failure.
384
 
385
B<Validation:>
386
 
387
Checks for the DataTransport header comment. If not found, 
388
prints a warning and returns undef.
389
 
390
B<Comment Handling:>
391
 
392
Lines starting with # are treated as comments and ignored during 
393
parsing (except for the header validation).
394
 
395
B<Example:>
396
 
397
    my $data = $dt->readFile('/tmp/config.dat');
398
    die "Failed to read file" unless defined $data;
399
    print "Hostname: $data->{hostname}\n";
400
 
401
=cut
402
 
403
#######################################################
404
#
405
# readFile( $filename )
406
#
407
# Read and decode a DataTransport file
408
#
409
# Parameters:
410
#   $filename - path to input file
411
#
412
# Returns:
413
#   hashref on success, undef on failure
414
#
415
# Validates that the file contains the DataTransport header
416
# before attempting to decode the contents
417
#
418
#######################################################
419
sub readFile {
420
   my ($self, $filename) = @_;
421
 
422
   return unless defined $filename && -f $filename;
423
 
424
   # Open file for reading
425
   open(my $fh, '<', $filename) or return;
426
 
427
   # Read all lines
428
   my @lines = <$fh>;
429
   close($fh);
430
 
431
   return unless @lines;
432
 
433
   # Check for DataTransport header
434
   if ($lines[0] !~ /^# DataTransport v/) {
435
      warn "File $filename does not appear to be a DataTransport file\n";
436
      return;
437
   }
438
 
439
   # Remove header line
440
   shift @lines;
441
 
442
   # Filter out any other comment lines and empty lines
443
   @lines = grep { !/^#/ && /\S/ } @lines;
444
 
445
   # Decode the data
446
   return $self->decode(\@lines);
447
}
448
 
449
1;
450
=head1 EXAMPLES
451
 
452
=head2 Basic Usage
453
 
454
    use DataTransport;
455
 
456
    my $dt = DataTransport->new();
457
    my $data = {
458
        name => 'John Doe',
459
        email => 'john@example.com'
460
    };
461
 
462
    my $encoded = $dt->encode($data);
463
    my $decoded = $dt->decode($encoded);
464
 
465
=head2 Nested Structures
466
 
467
    my $config = {
468
        server => {
469
            hostname => 'web.example.com',
470
            port => 443,
471
            ssl => {
472
                enabled => 1,
473
                cert => '/etc/ssl/cert.pem'
474
            }
475
        }
476
    };
477
 
478
    my $encoded = $dt->encode($config);
479
    # server|hostname=web.example.com
480
    # server|port=443
481
    # server|ssl|cert=/etc/ssl/cert.pem
482
    # server|ssl|enabled=1
483
 
484
=head2 Arrays
485
 
486
    my $data = {
487
        packages => ['perl', 'python', 'ruby'],
488
        versions => ['5.32', '3.9', '2.7']
489
    };
490
 
491
    my $encoded = $dt->encode($data);
492
    # packages="perl"  "python"        "ruby"
493
    # versions="5.32"  "3.9"   "2.7"
494
 
495
=head2 Custom Delimiter
496
 
497
    # Keys with periods using pipe delimiter
498
    my $dt = DataTransport->new();
499
    my $data = {
500
        'host.example.com' => 'server1',
501
        'net.interface' => 'eth0'
502
    };
503
 
504
    my $encoded = $dt->encode($data);
505
    # host.example.com=server1
506
    # net.interface=eth0
507
 
508
    # Using :: delimiter for compatibility
509
    my $dt2 = DataTransport->new(delimiter => '::');
510
    my $config = {
511
        database => {
512
            host => 'db.example.com'
513
        }
514
    };
515
 
516
    my $encoded2 = $dt2->encode($config);
517
    # database::host=db.example.com
518
 
519
=head2 File Operations
520
 
521
    my $dt = DataTransport->new();
522
 
523
    # Write data
524
    my $system_info = {
525
        hostname => 'server01',
526
        kernel => '5.10.0',
527
        memory => '16GB'
528
    };
529
 
530
    $dt->writeFile($system_info, '/tmp/sysinfo.dat');
531
 
532
    # Read data
533
    my $loaded = $dt->readFile('/tmp/sysinfo.dat');
534
    print "Hostname: $loaded->{hostname}\n";
535
 
536
=head1 NOTES
537
 
538
=over 4
539
 
540
=item * Keys are always sorted alphabetically during encoding
541
 
542
=item * The delimiter should not appear in key names (use quotes in keys if needed)
543
 
544
=item * Empty strings and zeros are preserved correctly
545
 
546
=item * Tab characters in values may be lost during array decoding
547
 
548
=item * The module uses simple regex parsing - complex nested quotes may not work
549
 
550
=back
551
 
552
=head1 SEE ALSO
553
 
554
L<YAML::Tiny>, L<JSON>, L<Data::Dumper>
555
 
556
=head1 AUTHOR
557
 
558
R. W. Rodolico
559
 
560
=head1 COPYRIGHT AND LICENSE
561
 
562
Copyright (c) 2025 R. W. Rodolico
563
 
564
Redistribution and use in source and binary forms, with or without
565
modification, are permitted provided that the following conditions are met:
566
 
567
1. Redistributions of source code must retain the above copyright notice,
568
   this list of conditions and the following disclaimer.
569
 
570
2. Redistributions in binary form must reproduce the above copyright notice,
571
   this list of conditions and the following disclaimer in the documentation
572
   and/or other materials provided with the distribution.
573
 
574
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
575
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
576
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
577
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
578
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
579
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
580
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
581
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
582
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
583
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
584
POSSIBILITY OF SUCH DAMAGE.
585
 
586
=cut
587
 
588
1;