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

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