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

Last change on this file since 219 was 219, checked in by g7moreau, 6 years ago
  • links on metadat schema
  • Property svn:executable set to *
File size: 18.6 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.11');
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   'dap-publish'           => \&cmd_dap_publish,
31   'dap-unpublish'         => \&cmd_dap_unpublish,
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,
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
60sub print_ok {
61   my ($key, $test) = @_;
62   
63   printf "%-35s : %s\n", $key, $test ? 'yes' : 'no';
64   }
65
66################################################################
67
68sub addfolder2list {
69   my ($folderdb, $folder) = @_;
70   
71   return if $folder !~ m{/};
72   
73   $folder =~ s{/[^/]+$}{};
74
75   $folderdb->{$folder}++;
76   return addfolder2list($folderdb, $folder);
77   }
78
79################################################################
80# command
81################################################################
82
83sub cmd_help {
84   print <<'END';
85project-meta - opendata project metafile manager
86
87 project-meta help
88 project-meta version
89 project-meta check
90 project-meta dap-publish
91 project-meta dap-unpublish
92 project-meta make-zip
93 project-meta make-allfiles
94 project-meta list-license
95 project-meta make-file-license
96 project-meta make-file-author
97 project-meta make-file-copyright
98END
99   }
100
101################################################################
102
103sub cmd_version {
104   print "$VERSION\n";
105   }
106
107################################################################
108
109sub cmd_check {
110   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
111
112   my $acronym     = $meta->{'project'}{'acronym'};
113   my $current_dir = Cwd::getcwd();
114   my $dap_folder  = $meta->{'public-dap'}{'dap-folder'};
115
116   print_ok 'project/acronym',                  $acronym =~ m{\d\d\w[\w\d_/]+};
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
125sub cmd_dap_publish {
126   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
127   my $current_dir = Cwd::getcwd();
128   my $acronym     = $meta->{'project'}{'acronym'};
129   my $dap_folder  = $meta->{'public-dap'}{'dap-folder'};
130   my $data_set    = $meta->{'public-dap'}{'data-set'};
131
132   push @{$data_set}, 'AUTHORS.txt', 'COPYRIGHT.txt', 'LICENSE.txt';
133   {
134      # Remove doublon
135      my %seen = ();
136      @{$data_set} = grep { ! $seen{$_}++ } @{$data_set};
137      }
138
139   # Create a list of the folder
140   my %folders;
141   for my $dataset (@{$data_set}) {
142      addfolder2list(\%folders, $dataset);
143      }
144
145   print "chmod o+rX,o-w '$current_dir'\n";
146   print "mkdir -p '$dap_folder/$acronym'\n" if not -d "$dap_folder/$acronym";
147   for my $folder (sort keys %folders) {
148      print "chmod o+rX,o-w '$current_dir/$folder'\n";
149      print "mkdir '$dap_folder/$acronym/$folder'\n" if -d "$current_dir/$folder";
150      }
151
152   for my $dataset (@{$data_set}) {
153      if ($dataset =~ m{/}) {
154         # sub-folder case
155         my $folder = $dataset =~ s{/[^/]+$}{}r;
156         print "chmod -R o+rX,o-w '$current_dir/$dataset'\n";
157         print "ln --symbolic --target-directory '$dap_folder/$acronym/$folder/' '$current_dir/$dataset'\n";
158         }
159      else {
160         # Root case
161         print "ln --symbolic --target-directory '$dap_folder/$acronym/' '$current_dir/$dataset'\n";
162         }
163
164      }
165   print "chmod -R o+rX,o-w '$dap_folder/$acronym/'\n";
166   }
167
168################################################################
169
170sub cmd_dap_unpublish {
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
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";
181   }
182
183################################################################
184
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
191   push @{$data_set}, 'AUTHORS.txt', 'COPYRIGHT.txt', 'LICENSE.txt';
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
204         $zip->addTree($dataset, "$acronym/$dataset");
205         }
206      elsif (-f $dataset) {
207         # File case
208         $zip->addFile($dataset, "$acronym/$dataset");
209         }
210      else {
211         # Strange case
212         print "Error: entry $dataset doesn't exists\n";
213         }
214      }
215
216   my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime time;
217   $year += 1900;
218   $mon++;
219   my $date = sprintf '%04i%02i%02i-%02i%02i', $year, $mon, $mday, $hour, $min;
220
221   # Save the Zip file
222   unless ($zip->writeToFileNamed("$current_dir/$acronym--$date.zip") == AZ_OK) {
223      die 'Error: zip write error';
224      }
225   }
226
227################################################################
228
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 {
238   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
239
240   my $current_dir = Cwd::getcwd();
241
242   my $acronym    = $meta->{'project'}{'acronym'};
243   my $authors_list = $meta->{'project'}{'authors'};
244
245   if (-f "$current_dir/AUTHORS.txt") {
246      # Test for manual or automatically generated file
247      # Automatically generated file by project-meta
248      my $automatic;
249      open my $fh, '<', "$current_dir/AUTHORS.txt" or die $!;
250      for my $line (<$fh>) {
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";
261      }
262
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
271   open my $fh,  '>', "$current_dir/AUTHORS.txt" or die $!;
272   print $fh "$msg_format\n\n";
273   close $fh;
274   }
275
276################################################################
277
278sub cmd_make_file_license {
279   my $meta = YAML::Syck::LoadFile("PROJECT-META.yml");
280
281   my $current_dir = Cwd::getcwd();
282
283   if (-f "$current_dir/LICENSE.txt") {
284      print "Warning: LICENSE.txt already exists\n";
285      return;
286      }
287
288   my $license = $meta->{'public-dap'}{'data-license'};
289
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";
292      exit 1;
293      }
294
295   copy("/usr/share/project-meta/license.d/$license.txt", "$current_dir/LICENSE.txt")
296      or die "Error: license copy failed - $!";
297
298   print "Info: LICENSE.txt file create\n";
299   return;
300   }
301
302################################################################
303
304sub cmd_make_file_copyright {
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      }
326   
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      );
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'},
338         license     => $meta->{'public-dap'}{'data-license'},
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################################################################
348
349sub cmd_list_license {
350   opendir my $dh, '/usr/share/project-meta/license.d/' or die $!;
351   for my $license (readdir $dh) {
352      # Keep only file
353      next if not -f "/usr/share/project-meta/license.d/$license";
354     
355      # Keep only .txt file
356      next if not $license =~ m/\.txt$/;
357
358      $license =~ s/\.txt$//;
359      print "$license\n";
360      }
361   closedir $dh;
362   }
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
377 project-meta help
378 project-meta version
379 project-meta check
380 project-meta dap-publish
381 project-meta dap-unpublish
382 project-meta make-zip
383 project-meta list-license
384 project-meta make-file-license
385 project-meta make-file-author
386 project-meta make-file-copyright
387
388
389=head1 DESCRIPTION
390
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.
394
395Everything is declare in the metafile F<PROJECT-META.yml>.
396This YAML file must exist in your root projet folder.
397See L</METAFILE SPECIFICATION>.
398
399
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
405(like for all the Project-Meta code).
406
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,
413you can use C<yamllint> or C<ysh> commands to check just it's format.
414
415=head2 dap-publish
416
417 project-meta dap-publish
418
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.
423Files F<AUTHORS.txt>, F<LICENSE.txt> and F<COPYRIGHT.txt> are mandatory but could be generated (see below).
424The main keys use in the F<PROJECT-META.yml> are:
425
426=over
427
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
432=item * C<public-dap/data-set>: a list of files or folder to push
433
434=back
435
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
440 project-meta dap-publish
441 project-meta dap-publish | bash
442
443=head2 dap-unpublish
444
445 project-meta dap-unpublish
446
447Unpublish data from the OpeNDAP server.
448In practice, it remove links in OpeNDAP folder for that projet.
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
454=head2 make-zip
455
456 project-meta make-zip
457
458Create a ZIP archive with the open data set.
459Files F<AUTHORS.txt>, F<LICENSE.txt> and F<COPYRIGHT.txt> are mandatory but could be generated (see below).
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
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
478=head2 list-license
479
480 project-meta list-license
481
482Give the list of all the open data licenses supported by the project-meta license database.
483At this time the possible licenses are:
484
485=over
486
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)
489
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
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
496=item * L<creative-common-zero-v1.0|https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt>
497        (like public domain)
498
499=item * L<licence-ouverte-v2.0|https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf>
500        (copyleft - opendata french goverment)
501
502=item * L<open-database-license-v1.0|https://opendatacommons.org/files/2018/02/odbl-10.txt>
503        (copyleft - allow users to freely share, modify, and use the database while maintaining this same freedom for others)
504
505=back
506
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.
509Here are some links about open data licence context:
510
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
521=head2 make-file-license
522
523 project-meta make-file-license
524
525Copy the license file from the project-meta license database at the current folder
526with the file name: F<LICENSE.txt>.
527
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>.
530
531=head2 make-file-author
532
533 project-meta make-file-author
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
538=head2 make-file-copyright
539
540 project-meta make-file-copyright
541
542Create or update the F<COPYRIGHT.txt> file at the current folder.
543Authors, license and copyright data are extracted from the C<PROJECT-META.yml> file.
544
545
546=head1 METAFILE SPECIFICATION
547
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.
551
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.
554This one is actually the master reference specification!
555
556Some interresting papers or links about Open Meta Data Schema:
557
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
575=head1 KNOWN BUGS
576
577 - not really check keys and tags before doing action!
578
579
580=head1 SEE ALSO
581
582yamllint(1), ysh(1), YAML, Archive::Zip
583
584In Debian GNU/Linux distribution, packages for C<yamllint> and C<ysh> are:
585
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
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
608=head1 AUTHOR
609
610Written by Gabriel Moreau, LEGI UMR5519, CNRS, Grenoble - France
611
612
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
617or helped me in another task like having new ideas, specifications...
618Maybe I forgot your contribution in recent years,
619please forgive me in advance and send me an e-mail to correct this.
620
621Joel Sommeria, Julien Chauchat, Cyrille Bonamy, Antoine Mathieu.
622
623
624=head1 LICENSE AND COPYRIGHT
625
626License GNU GPL version 2 or later and Perl equivalent
627
628Copyright (C) 2017-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.