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

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