source: trunk/project-meta/project-meta @ 208

Last change on this file since 208 was 206, checked in by g7moreau, 6 years ago
  • Mini change in doc
  • Property svn:executable set to *
File size: 15.8 KB
Line 
1#!/usr/bin/env perl
2#
3# 2018/01/17 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
4#
5# apt-get install libyaml-syck-perl libtemplate-perl libarchive-zip-perl
6# apt-get install yamllint libyaml-shell-perl # check YAML files
7
8use strict;
9use warnings;
10use version; our $VERSION = version->declare('0.0.7');
11
12use File::Copy qw(copy);   
13use YAML::Syck;
14use Getopt::Long();
15use Cwd();
16use Template;
17use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
18
19
20my ($verbose);
21Getopt::Long::GetOptions(
22   'verbose' => \$verbose,
23   );
24
25
26my %CMD_DB = (
27   'help'            => \&cmd_help,
28   'version'         => \&cmd_version,
29   'check'           => \&cmd_check,
30   'make-link'       => \&cmd_make_link,
31   'remove-link'     => \&cmd_remove_link,
32   'make-zip'        => \&cmd_make_zip,
33   'make-author'     => \&cmd_make_author,
34   'make-license'    => \&cmd_make_license,
35   'make-copyright'  => \&cmd_make_copyright,
36   'list-license'    => \&cmd_list_license,
37   );
38
39################################################################
40# main program
41################################################################
42
43my $cmd = shift @ARGV || 'help';
44if (defined $CMD_DB{$cmd}) {
45   $CMD_DB{$cmd}->(@ARGV);
46   }
47else {
48   print {*STDERR} "project-meta: command $cmd not found\n\n";
49   $CMD_DB{'help'}->();
50   exit 1;
51   }
52
53exit;
54
55################################################################
56# subroutine
57################################################################
58
59sub print_ok {
60   my ($key, $test) = @_;
61   
62   printf "%-35s : %s\n", $key, $test ? 'yes' : 'no';
63   }
64
65################################################################
66
67sub addfolder2list {
68   my ($folderdb, $folder) = @_;
69   
70   return if $folder !~ m{/};
71   
72   $folder =~ s{/[^/]+$}{};
73
74   $folderdb->{$folder}++;
75   return addfolder2list($folderdb, $folder);
76   }
77
78################################################################
79# command
80################################################################
81
82sub cmd_help {
83   print <<'END';
84project-meta - opendata project metafile manager
85
86 project-meta help
87 project-meta version
88 project-meta check
89 project-meta make-link
90 project-meta remove-link
91 project-meta make-zip
92 project-meta list-license
93 project-meta make-license
94 project-meta make-author
95 project-meta make-copyright
96END
97   }
98
99################################################################
100
101sub cmd_version {
102   print "$VERSION\n";
103   }
104
105################################################################
106
107sub cmd_check {
108   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
109
110   my $acronym     = $meta->{'project'}{'acronym'};
111   my $current_dir = Cwd::getcwd();
112   my $dap_folder  = $meta->{'public-dap'}{'dap-folder'};
113
114   print_ok 'project/acronym',                  $acronym =~ m{\d\d\w[\w\d_/]+};
115   print_ok 'public-dap/dap-folder',            $dap_folder ne '' and $dap_folder =~ m{^/};
116   print_ok 'dap-folder not match current_dir', $dap_folder !~ m{$current_dir};
117
118   #print YAML::Syck::Dump($meta);
119   }
120
121################################################################
122
123sub cmd_make_link {
124   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
125   my $current_dir = Cwd::getcwd();
126   my $acronym     = $meta->{'project'}{'acronym'};
127   my $dap_folder  = $meta->{'public-dap'}{'dap-folder'};
128   my $data_set    = $meta->{'public-dap'}{'data-set'};
129
130   push @{$data_set}, 'AUTHORS.txt', 'COPYRIGHT.txt', 'LICENSE.txt';
131   {
132      # Remove doublon
133      my %seen = ();
134      @{$data_set} = grep { ! $seen{$_}++ } @{$data_set};
135      }
136
137   # Create a list of the folder
138   my %folders;
139   for my $dataset (@{$data_set}) {
140      addfolder2list(\%folders, $dataset);
141      }
142
143   print "chmod o+rX,o-w '$current_dir'\n";
144   print "mkdir -p '$dap_folder/$acronym'\n" if not -d "$dap_folder/$acronym";
145   for my $folder (sort keys %folders) {
146      print "chmod o+rX,o-w '$current_dir/$folder'\n";
147      print "mkdir '$dap_folder/$acronym/$folder'\n" if -d "$current_dir/$folder";
148      }
149
150   for my $dataset (@{$data_set}) {
151      if ($dataset =~ m{/}) {
152         # sub-folder case
153         my $folder = $dataset =~ s{/[^/]+$}{}r;
154         print "chmod -R o+rX,o-w '$current_dir/$dataset'\n";
155         print "ln --symbolic --target-directory '$dap_folder/$acronym/$folder/' '$current_dir/$dataset'\n";
156         }
157      else {
158         # Root case
159         print "ln --symbolic --target-directory '$dap_folder/$acronym/' '$current_dir/$dataset'\n";
160         }
161
162      }
163   print "chmod -R o+rX,o-w '$dap_folder/$acronym/'\n";
164   }
165
166################################################################
167
168sub cmd_remove_link {
169   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
170   my $current_dir = Cwd::getcwd();
171   my $acronym     = $meta->{'project'}{'acronym'};
172   my $dap_folder  = $meta->{'public-dap'}{'dap-folder'};
173
174   die "Error: DAP folder match current folder" if $dap_folder =~ m{$current_dir} or $current_dir =~ m{$dap_folder};
175
176   print "find '$dap_folder/$acronym/' -type l -o -type d -exec ls -l {} \+\n";
177   print "find '$dap_folder/$acronym/' -type l -delete\n";
178   print "find '$dap_folder/$acronym/' -type d -delete\n";
179   }
180
181################################################################
182
183sub cmd_make_zip {
184   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
185   my $current_dir = Cwd::getcwd();
186   my $data_set    = $meta->{'public-dap'}{'data-set'};
187   my $acronym     = $meta->{'project'}{'acronym'};
188
189   push @{$data_set}, 'AUTHORS.txt', 'COPYRIGHT.txt', 'LICENSE.txt';
190   {
191      # Remove doublon
192      my %seen = ();
193      @{$data_set} = grep { ! $seen{$_}++ } @{$data_set};
194      }
195
196   # Create a Zip file
197   my $zip = Archive::Zip->new();
198
199   for my $dataset (@{$data_set}) {
200      if (-d $dataset) {
201         # Folder case
202         $zip->addTree($dataset, "$acronym/$dataset");
203         }
204      elsif (-f $dataset) {
205         # File case
206         $zip->addFile($dataset, "$acronym/$dataset");
207         }
208      else {
209         # Strange case
210         print "Error: entry $dataset doesn't exists\n";
211         }
212      }
213
214   my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime time;
215   $year += 1900;
216   $mon++;
217   my $date = sprintf '%04i%02i%02i-%02i%02i', $year, $mon, $mday, $hour, $min;
218
219   # Save the Zip file
220   unless ($zip->writeToFileNamed("$current_dir/$acronym--$date.zip") == AZ_OK) {
221      die 'Error: zip write error';
222      }
223   }
224
225################################################################
226
227sub cmd_make_author {
228   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
229
230   my $current_dir = Cwd::getcwd();
231
232   my $acronym    = $meta->{'project'}{'acronym'};
233   my $authors_list = $meta->{'project'}{'authors'};
234
235   if (-f "$current_dir/AUTHORS.txt") {
236      # Test for manual or automatically generated file
237      # Automatically generated file by project-meta
238      my $automatic;
239      open my $fh, '<', "$current_dir/AUTHORS.txt" or die $!;
240      for my $line (<$fh>) {
241         $line =~ m/Automatically generated .* project-meta/i and $automatic++;
242         }
243      close $fh;
244
245      if (not $automatic) {
246         print "Warning: AUTHORS.txt already exists\n";
247         return;
248         }
249
250      print "Warning: update AUTHORS.txt\n";
251      }
252
253   my $tt = Template->new(INCLUDE_PATH => '/usr/share/project-meta/template.d');
254   my $msg_format = '';
255   $tt->process('AUTHORS.tt',
256      {
257         acronym    => $acronym,
258         authorlist => $authors_list,
259      }, \$msg_format) || die $tt->error;
260
261   open my $fh,  '>', "$current_dir/AUTHORS.txt" or die $!;
262   print $fh "$msg_format\n\n";
263   close $fh;
264   }
265
266################################################################
267
268sub cmd_make_license {
269   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
270
271   my $current_dir = Cwd::getcwd();
272
273   if (-f "$current_dir/LICENSE.txt") {
274      print "Warning: LICENSE.txt already exists\n";
275      return;
276      }
277
278   my $license = $meta->{'public-dap'}{'data-license'};
279
280   if (not -f "/usr/share/project-meta/license.d/$license.txt") {
281      print "Error: license $license doesn't exists in project-meta database\n";
282      exit 1;
283      }
284
285   copy("/usr/share/project-meta/license.d/$license.txt", "$current_dir/LICENSE.txt")
286      or die "Error: license copy failed - $!";
287
288   print "Info: LICENSE.txt file create\n";
289   return;
290   }
291
292################################################################
293
294sub cmd_make_copyright {
295   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
296
297   my $current_dir = Cwd::getcwd();
298
299   if (-f "$current_dir/COPYRIGHT.txt") {
300      # Test for manual or automatically generated file
301      # Automatically generated file by project-meta
302      my $automatic;
303      open my $fh, '<', "$current_dir/COPYRIGHT.txt" or die $!;
304      for my $line (<$fh>) {
305         $line =~ m/Automatically generated .* project-meta/i and $automatic++;
306         }
307      close $fh;
308
309      if (not $automatic) {
310         print "Warning: COPYRIGHT.txt already exists\n";
311         return;
312         }
313
314      print "Warning: update COPYRIGHT.txt\n";
315      }
316   
317   my $tt = Template->new(
318      INCLUDE_PATH   => '/usr/share/project-meta/template.d',
319      POST_CHOMP     => 1, # Remove space and carriage return after %]
320      );
321   my $msg_format = '';
322   $tt->process('COPYRIGHT.tt',
323      {
324         title       => $meta->{'project'}{'title'},
325         acronym     => $meta->{'project'}{'acronym'},
326         authorlist  => $meta->{'project'}{'authors'},
327         description => $meta->{'project'}{'short-description'},
328         license     => $meta->{'public-dap'}{'data-license'},
329         doi         => $meta->{'publication'}{'doi'},
330      }, \$msg_format) || die $tt->error;
331
332   open my $fh,  '>', "$current_dir/COPYRIGHT.txt" or die $!;
333   print $fh "$msg_format\n\n";
334   close $fh;
335   }
336
337################################################################
338
339sub cmd_list_license {
340   opendir my $dh, '/usr/share/project-meta/license.d/' or die $!;
341   for my $license (readdir $dh) {
342      # Keep only file
343      next if not -f "/usr/share/project-meta/license.d/$license";
344     
345      # Keep only .txt file
346      next if not $license =~ m/\.txt$/;
347
348      $license =~ s/\.txt$//;
349      print "$license\n";
350      }
351   closedir $dh;
352   }
353
354################################################################
355# documentation
356################################################################
357
358__END__
359
360=head1 NAME
361
362project-meta - opendata project metafile manager
363
364
365=head1 USAGE
366
367 project-meta help
368 project-meta version
369 project-meta check
370 project-meta make-link
371 project-meta remove-link
372 project-meta make-zip
373 project-meta list-license
374 project-meta make-license
375 project-meta make-author
376 project-meta make-copyright
377
378
379=head1 DESCRIPTION
380
381Project-Meta is a small tool to maintain a set of open data files.
382In order to help you in this task, C<project-meta> command has a set of action
383to generated and maintain many files in your dataset.
384
385Everything is declare in the metafile F<PROJECT-META.yml>.
386This YAML file must exist in your root projet folder.
387See L</METAFILE SPECIFICATION>.
388
389
390=head1 COMMANDS
391
392Some command are defined in the source code but are not documented here.
393Theses could be not well defined, not finished, not well tested...
394You can read the source code and use them at your own risk
395(like for all the Project-Meta code).
396
397=head2 check
398
399 project-meta check
400
401Check your F<PROJECT-META.yml> has the good key.
402If your metafile is not a valid YAML file,
403you can use C<yamllint> or C<ysh> commands to check just it's format.
404
405=head2 make-link
406
407 project-meta make-link
408
409Create UNIX soft links on the OpeNDAP folder to the real data.
410Files F<AUTHORS.txt>, F<LICENSE.txt> and F<COPYRIGHT.txt> are mandatory but could be generated (see below).
411The main keys use in the F<PROJECT-META.yml> are:
412
413=over
414
415=item * C<project/acronym>: the project short acronym, add to the OpeNDAP root folder
416
417=item * C<public-dap/dap-folder>: the OpeNDAP root folder
418
419=item * C<public-dap/data-set>: a list of files or folder to push
420
421=back
422
423Because this command could be dangerous, it does nothing!
424It print on terminal shell command to be done.
425You have to verify ouput before eval it.
426
427 project-meta make-link
428 project-meta make-link | bash
429
430=head2 remove-link
431
432 project-meta remove-link
433
434Remove link in OpeNDAP folder for that projet.
435Because command C<rm> is always dangerous,
436we use here the command C<find> limited to folder and link.
437
438Please verify the returned values before excuted it with the C<-delete> option.
439
440=head2 make-zip
441
442 project-meta make-zip
443
444Create a ZIP archive with the open data set.
445Files F<AUTHORS.txt>, F<LICENSE.txt> and F<COPYRIGHT.txt> are mandatory but could be generated (see below).
446The main keys use in the F<PROJECT-META.yml> are:
447
448=over
449
450=item * C<project/acronym>: the project short acronym, use as root folder
451
452=item * C<public-dap/data-set>: a list of files or folder to push
453
454=back
455
456=head2 list-license
457
458 project-meta list-license
459
460Give the list of all the open data licenses supported by the project-meta license database.
461At this time the possible licenses are:
462
463=over
464
465=item * L<creative-common-zero-v1.0|https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt>
466        (like public domain)
467
468=item * L<licence-ouverte-v2.0|https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf>
469        (opendata french goverment)
470
471=item * L<open-database-license-v1.0|https://opendatacommons.org/files/2018/02/odbl-10.txt>
472
473=back
474
475Note that these licenses are dedicated to open data.
476Please do not use an open license that would have been thought for source code or documentation and not for open data.
477
478=head2 make-license
479
480 project-meta make-license
481
482Copy the license file from the project-meta license database at the current folder
483with the file name: F<LICENSE.txt>.
484
485The license is defined in the F<PROJECT-META.yml> specification under the key C<public-dap/data-license>.
486The list of possible license is given with the command L</list-license>.
487
488=head2 make-author
489
490 project-meta make-author
491
492Create or update the F<AUTHORS.txt> file at the current folder.
493Authors data are extracted from the C<PROJECT-META.yml> file.
494
495=head2 make-copyright
496
497 project-meta make-copyright
498
499Create or update the F<COPYRIGHT.txt> file at the current folder.
500Authors, license and copyright data are extracted from the C<PROJECT-META.yml> file.
501
502
503=head1 METAFILE SPECIFICATION
504
505Each project must have an open data metafile which describe the project : C<PROJECT-META.yml>.
506The file is in YAML format because this is a human readable style of text file.
507
508You can find in the project-meta software a
509L<PROJECT-META.sample.yml|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/project-meta/PROJECT-META.sample.yml> example.
510This one is actually the master reference specification!
511
512
513=head1 KNOWN BUGS
514
515 - not really check keys and tags before doing action!
516
517
518=head1 SEE ALSO
519
520yamllint(1), ysh(1), YAML, Archive::Zip
521
522In Debian GNU/Linux distribution, packages for C<yamllint> and C<ysh> are:
523
524=over
525
526=item * C<yamllint> - Linter for YAML files (Python)
527
528=item * C<libyaml-shell-perl> - YAML test shell (Perl)
529
530=back
531
532
533Own project ressources:
534
535=over
536
537=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/ProjectMeta>
538
539=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/project-meta/project-meta.html>
540
541=item * L<SVN repository|http://servforge.legi.grenoble-inp.fr/svn/soft-trokata/trunk/project-meta>
542
543=back
544
545
546=head1 AUTHOR
547
548Written by Gabriel Moreau, LEGI UMR5519, CNRS, Grenoble - France
549
550
551=head1 SPECIAL THANKS
552
553The list of people below did not directly contribute to project-meta's source code
554but provided me with some data, returned bugs
555or helped me in another task like having new ideas, specifications...
556Maybe I forgot your contribution in recent years,
557please forgive me in advance and send me an e-mail to correct this.
558
559Joel Sommeria, Julien Chauchat, Cyrille Bonamy, Antoine Mathieu.
560
561
562=head1 LICENSE AND COPYRIGHT
563
564License GNU GPL version 2 or later and Perl equivalent
565
566Copyright (C) 2017-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.