File Coverage

blib/lib/GitHub/Crud.pm
Criterion Covered Total %
statement 52 659 7.8
branch 3 298 1.0
condition 0 95 0.0
subroutine 16 76 21.0
pod 37 52 71.1
total 108 1180 9.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib
2             #-------------------------------------------------------------------------------
3             # Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
4             # Per: https://developer.github.com/v3/
5             # Philip R Brenan at gmail dot com, Appa Apps Ltd, 2017-2020
6             #-------------------------------------------------------------------------------
7             #podDocumentation
8             package GitHub::Crud;
9 1     1   3049 use v5.16;
  1         11  
10             our $VERSION = 20230303;
11 1     1   6 use warnings FATAL => qw(all);
  1         2  
  1         32  
12 1     1   5 use strict;
  1         2  
  1         43  
13 1     1   6 use Carp qw(confess);
  1         2  
  1         108  
14 1     1   2270 use Data::Dump qw(dump);
  1         8329  
  1         65  
15 1     1   7538 use Data::Table::Text qw(:all !fileList);
  1         149245  
  1         1962  
16 1     1   1385 use Digest::SHA1 qw(sha1_hex);
  1         2014  
  1         67  
17             #use Date::Manip;
18 1     1   11 use Scalar::Util qw(blessed reftype looks_like_number);
  1         2  
  1         71  
19 1     1   6 use Time::HiRes qw(time);
  1         4  
  1         11  
20 1     1   856 use Encode qw(encode decode);
  1         10810  
  1         66  
21 1     1   8 use utf8; # To allow utf8 constants for testing
  1         2  
  1         7  
22              
23 0     0 0 0 sub url { "https://api.github.com/repos" } # Github repository api url
24 0     0 0 0 sub api { "https://api.github.com/" } # Github api url
25 0     0 0 0 sub accessFolder { q(/etc/GitHubCrudPersonalAccessToken) }; # Personal access tokens are stored in a file in this folder with the name of the userid of the L repository
26              
27             my %shas; # L digests already seen - used to optimize write and delete
28              
29             sub GitHub::Crud::Response::new($$) #P Execute a request against L and decode the response
30 0     0   0 {my ($gitHub, $request) = @_; # Github, request string
31              
32 0         0 my $R = bless {command=>$request}, "GitHub::Crud::Response"; # Construct the response
33              
34 0         0 my $r = xxx $request, qr(HTTP);
35              
36 0         0 $r =~ s/\r//gs; # Internet line ends
37 0         0 my ($http, @r) = split /\n/, $r;
38 0   0     0 while(@r > 2 and $http =~ "HTTP" and $http =~ /100/) # Continue messages
      0        
39 0         0 {shift @r; $http = shift @r;
  0         0  
40             }
41              
42 0 0 0     0 if ($http and $http =~ "HTTP" and $http =~ /200|201|404|409|422/)
      0        
43 0         0 {my $ps = 0; # Parse the response
44 0         0 my @data;
45             my %can;
46              
47 0         0 for(@r)
48 0 0       0 {if ($ps == 0)
49 0 0       0 {if (length == 0)
50 0         0 {$ps = 1;
51             }
52             else
53 0         0 {my ($name, $content) = split /\s*:\s*/, $_, 2; # Parse each header
54 0         0 $name =~ s/-/_/gs; # Translate - in names to _
55 0 0       0 if ($R->can($name))
56 0         0 {$R->$name = $content;
57             }
58 0         0 else {$can{$name}++} # Update list of new methods required
59             }
60             }
61             else
62 0         0 {push @data, $_;
63             }
64             }
65              
66 0 0       0 if (keys %can) # List of new methods required
67 0         0 {lll "Add the following fields to package GitHub::Crud::Response, continuing ...";
68 0         0 say STDERR " $_=> undef," for(sort keys %can);
69             }
70              
71 0 0       0 if (@data) # Save any data
72 0         0 {my $j = join ' ', @data;
73 0         0 my $p = $R->data = bless decodeJson($j), "GitHub::Crud::Response::Data";
74 0 0 0     0 if (ref($p) =~ m/hash/is and my $c = $p->content)
75 0         0 {$R->content = decodeBase64($c); # Decode the data
76             }
77             }
78              
79 0   0     0 ($R->status) = split / /, $R->Status || $R->status || 200; # Save response status - github returns status == 0 when running as an action so we make it 200
80              
81 0         0 return $gitHub->response = $R; # Return successful response
82             }
83             else
84 0         0 {confess "Unexpected response from GitHub:\n$r\n$request\n"; # Confess to failure
85             }
86             }
87              
88             genHash(q(GitHub::Crud::Response), # Attributes describing a response from L.
89             Accept_Ranges => undef,
90             access_control_allow_origin => undef,
91             Access_Control_Allow_Origin => undef,
92             access_control_expose_headers => undef,
93             Access_Control_Expose_Headers => undef,
94             cache_control => undef,
95             Cache_Control => undef,
96             Connection => undef,
97             content_length => undef,
98             Content_Length => undef,
99             content_security_policy => undef,
100             Content_Security_Policy => undef,
101             content_type => undef,
102             Content_Type => undef,
103             content => undef, # The actual content of the file from L.
104             data => undef, # The data received from L, normally in L format.
105             date => undef,
106             Date => undef,
107             etag => undef,
108             ETag => undef,
109             Expires => undef,
110             last_modified => undef,
111             Last_Modified => undef,
112             Link => undef,
113             Location => undef,
114             referrer_policy => undef,
115             Referrer_Policy => undef,
116             server => undef,
117             Server => undef,
118             Source_Age => undef,
119             Status => undef,
120             status => undef, # Our version of Status.
121             strict_transport_security => undef,
122             Strict_Transport_Security => undef,
123             vary => undef,
124             Vary => undef,
125             Via => undef,
126             x_accepted_oauth_scopes => undef,
127             X_Accepted_OAuth_Scopes => undef,
128             X_Cache_Hits => undef,
129             X_Cache => undef,
130             x_content_type_options => undef,
131             X_Content_Type_Options => undef,
132             X_Content_Type => undef,
133             X_Fastly_Request_ID => undef,
134             x_frame_options => undef,
135             X_Frame_Options => undef,
136             X_Geo_Block_List => undef,
137             x_github_media_type => undef,
138             X_GitHub_Media_Type => undef,
139             x_github_request_id => undef,
140             X_GitHub_Request_Id => undef,
141             x_oauth_scopes => undef,
142             X_OAuth_Scopes => undef,
143             x_ratelimit_limit => undef,
144             X_RateLimit_Limit => undef,
145             x_ratelimit_remaining => undef,
146             X_RateLimit_Remaining => undef,
147             x_ratelimit_reset => undef,
148             X_RateLimit_Reset => undef,
149             x_ratelimit_used => undef,
150             X_RateLimit_Used => undef,
151             X_Runtime_rack => undef,
152             X_Served_By => undef,
153             X_Timer => undef,
154             x_xss_protection => undef,
155             X_XSS_Protection => undef,
156             x_ratelimit_resource => undef,
157             x_github_api_version_selected => undef,
158             );
159              
160             genHash(q(GitHub::Crud::Response::Data), # Response from a request made to L.
161             command => undef,
162             content => undef,
163             documentation_url => undef,
164             download_url => undef,
165             encoding => undef,
166             git => undef,
167             git_url => undef,
168             html => undef,
169             html_url => undef,
170             _links => undef,
171             message => undef,
172             name => undef,
173             path => undef,
174             self => undef,
175             sha => undef,
176             size => undef,
177             type => undef,
178             url => undef,
179             );
180              
181             sub getSha($) #P Compute L for data after encoding any unicode characters as utf8.
182 0     0 0 0 {my ($data) = @_; # String possibly containing non ascii code points
183              
184 0         0 my $length = length($data);
185 0         0 my $blob = 'blob' . " $length\0" . $data;
186 0         0 utf8::encode($blob);
187 0         0 my $r = eval{sha1_hex($blob)};
  0         0  
188 0 0       0 confess $@ if $@;
189 0         0 $r
190             }
191              
192             if (0) # Test L
193             {my $sha = getSha("

Hello World

\n");
194             my $Sha = "f3e333e80d224c631f2ff51b9b9f7189ad349c15";
195             unless($sha eq $Sha)
196             {confess "Wrong SHA: $sha".
197             "Should be: $Sha";
198             }
199             confess "getSha success";
200             }
201              
202             sub shaKey($;$) #P Add a L key to a L
203 0     0 0 0 {my ($gitHub, $fileData) = @_; # Github, optional fileData to specify the file to use if it is not gitFile
204 0 0       0 filePath($gitHub->repository,
205             $fileData ? ($fileData->path, $fileData->name) : $gitHub->gitFile)
206             }
207              
208             sub saveSha($$) #P Save the L of a file
209 0     0 0 0 {my ($gitHub, $fileData) = @_; # Github, file details returned by list or exists
210 0         0 $shas{$gitHub->shaKey($fileData)} = $fileData->sha;
211             }
212              
213             sub copySha($) #P Save the L of a file just read to a file just about to be written
214 0     0 0 0 {my ($gitHub) = @_; # Github
215 0         0 $shas{$gitHub->shaKey} = $gitHub->response->data->sha;
216             }
217              
218             sub getExistingSha($) #P Get the L of a file that already exists
219 0     0 0 0 {my ($gitHub) = @_; # Github
220 0         0 my $s = $shas{$gitHub->shaKey}; # Get the L from the cache
221 0 0       0 return $s if defined $s; # A special L of 0 means the file was deleted
222 0         0 my $r = $gitHub->exists; # Get the L of the file via exists if the file exists
223 0 0       0 return $r->sha if $r; # L of existing file
224             undef # Undef if no such file
225 0         0 }
226              
227             sub deleteSha($) #P Delete a L that is no longer valid
228 0     0 0 0 {my ($gitHub) = @_; # Github
229 0         0 $shas{$gitHub->shaKey} = undef # Mark the L as deleted
230             }
231              
232             sub qm($) #P Quotemeta extended to include undef
233 0     0 0 0 {my ($s) = @_; # String to quote
234 0 0       0 return '' unless $s;
235 0         0 $s =~ s((\'|\"|\\)) (\\$1)gs;
236 0         0 $s =~ s(\s) (%20)gsr; # Url encode blanks
237             }
238              
239             sub patKey($) #P Create an authorization header by locating an appropriate personal access token
240 0     0 0 0 {my ($gitHub) = @_; # GitHub
241              
242 0 0       0 $gitHub->loadPersonalAccessToken unless $gitHub->personalAccessToken; # Load a personal access token if none has been supplied
243              
244 0 0       0 if (my $pat = $gitHub->personalAccessToken) # User supplied personal access token explicitly
245 0         0 {return "-H \"Authorization: token $pat\""
246             }
247              
248 0         0 confess "Personal access token required with scope \"public_repo\"". # We must have a personal access token to do anything useful!
249             " as generated on page:\nhttps://github.com/settings/tokens";
250             }
251              
252             sub refOrBranch($$) #P Add a ref or branch keyword
253 0     0 0 0 {my ($gitHub, $ref) = @_; # Github, whether to use ref rather than branch
254 0         0 my $b = $gitHub->branch;
255 0 0 0     0 return "?ref=$b" if $ref and $b;
256 0 0 0     0 return "?branch=$b" if !$ref and $b;
257 0         0 ''
258             }
259              
260             sub gitHub(%) #P Create a test L object
261 0     0 0 0 {my (%options) = @_; # Options
262 0         0 GitHub::Crud::new
263             (userid => q(philiprbrenan),
264             repository => q(aaa),
265             confessOnFailure => 1,
266             @_);
267             }
268              
269             #D1 Constructor # Create a L object with the specified attributes describing the interface with L.
270              
271             sub new(@) # Create a new L object with attributes as described at: L.
272 0     0 1 0 {my (%attributes) = @_; # Attribute values
273              
274 0         0 my $curl = qx(curl -V); # Check Curl
275 0 0       0 if ($curl =~ /command not found/)
276 0         0 {confess "Command curl not found"
277             }
278              
279 0         0 my $g = genHash(__PACKAGE__, # Attributes describing the interface with L.
280             body => undef, #I The body of an issue.
281             branch => undef, #I Branch name (you should create this branch first) or omit it for the default branch which is usually 'master'.
282             confessOnFailure => undef, #I Confess to any failures
283             failed => undef, # Defined if the last request to L failed else B.
284             fileList => undef, # Reference to an array of files produced by L.
285             gitFile => undef, #I File name on L - this name can contain '/'. This is the file to be read from, written to, copied from, checked for existence or deleted.
286             gitFolder => undef, #I Folder name on L - this name can contain '/'.
287             message => undef, #I Optional commit message
288             nonRecursive => undef, #I Fetch only one level of files with L.
289             personalAccessToken => undef, #I A personal access token with scope "public_repo" as generated on page: https://github.com/settings/tokens.
290             personalAccessTokenFolder => accessFolder, #I The folder into which to save personal access tokens. Set to q(/etc/GitHubCrudPersonalAccessToken) by default.
291             private => undef, #I Whether the repository being created should be private or not.
292             readData => undef, # Data produced by L.
293             repository => undef, #I The name of the repository to be worked on minus the userid - you should create this repository first manually.
294             response => undef, # A reference to L's response to the latest request.
295             secret => undef, #I The secret for a web hook - this is created by the creator of the web hook and remembered by L,
296             title => undef, #I The title of an issue.
297             webHookUrl => undef, #I The url for a web hook.
298             userid => undef, #I Userid on L of the repository to be worked on.
299             );
300              
301 0         0 $g->$_ = $attributes{$_} for sort keys %attributes;
302              
303 0         0 $g
304             }
305              
306             #D1 Files # File actions on the contents of L repositories.
307              
308             sub list($) # List all the files contained in a L repository or all the files below a specified folder in the repository.\mRequired attributes: L, L.\mOptional attributes: L, L, L, L.\mUse the L parameter to specify the folder to start the list from, by default, the listing will start at the root folder of your repository.\mUse the L option if you require only the files in the start folder as otherwise all the folders in the start folder will be listed as well which might take some time.\mIf the list operation is successful, L is set to false and L is set to refer to an array of the file names found.\mIf the list operation fails then L is set to true and L is set to refer to an empty array.\mReturns the list of file names found or empty list if no files were found.
309 0     0 1 0 {my ($gitHub) = @_; # GitHub
310             my $r = sub # Get contents
311 0 0   0   0 {my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
312 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
313 0   0     0 my $path = qm $gitHub->gitFolder || '';
314 0         0 my $bran = qm $gitHub->refOrBranch(1);
315 0         0 my $pat = $gitHub->patKey(0);
316 0         0 my $url = url;
317 0         0 my $s = filePath
318             ("curl -si $pat $url", $user, $repo, qq(contents), $path.$bran);
319 0         0 GitHub::Crud::Response::new($gitHub, $s);
320 0         0 }->();
321              
322 0         0 my $failed = $gitHub->failed = $r->status != 200; # Check response code
323 0 0 0     0 $failed and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
324              
325 0         0 $gitHub->fileList = [];
326 0 0 0     0 if (!$failed and reftype($r->data) =~ m(array)i) # Array of file details
327 0         0 {for(@{$r->data}) # Objectify and save L digests from file descriptions retrieved by this call
  0         0  
328 0         0 {bless $_, "GitHub::Crud::Response::Data";
329 0         0 saveSha($gitHub, $_);
330             }
331              
332 0   0     0 my $path = $gitHub->gitFolder || '';
333 0         0 my @d = map{filePath $path, $_->name} grep {$_->type eq "dir"} @{$r->data};# Folders
  0         0  
  0         0  
  0         0  
334 0         0 my @f = map{filePath $path, $_->name} grep {$_->type eq "file"} @{$r->data};# Files
  0         0  
  0         0  
  0         0  
335              
336 0 0       0 unless($gitHub->nonRecursive) # Get the contents of sub folders unless otherwise specified
337 0         0 {for my $d(@d)
338 0         0 {my $p = $gitHub->gitFolder = $d;
339 0         0 push @f, $gitHub->list;
340             }
341             }
342 0         0 $gitHub->gitFolder = $path; # Restore path supplied by the user
343 0         0 $gitHub->fileList = [@f]; # List of files not directories
344             }
345 0         0 @{$gitHub->fileList}
  0         0  
346             }
347              
348             sub specialFileData($) #P Do not encode or decode data with a known file signature
349 0     0 1 0 {my ($d) = @_; # String to check
350 0         0 my $h = '';
351 0 0 0     0 if ($d and length($d) > 8) # Read file magic number
352 0         0 {for my $e(0..7)
353 0         0 {$h .= sprintf("%x", ord(substr($d, $e, 1)));
354             }
355 0 0       0 return 1 if $h =~ m(\A504b)i; # PK Zip
356 0 0       0 return 1 if $h =~ m(\Ad0cf11e0)i; # OLE files
357 0 0       0 return 1 if $h =~ m(\Affd8ff)i; # Jpg
358 0 0       0 return 1 if $h =~ m(\A89504e470d0a1a0a)i; # Png
359 0 0       0 return 1 if $h =~ m(\A4D546864)i; # Midi
360 0 0       0 return 1 if $h =~ m(\A49443340)i; # Mp3
361             }
362             0 # Not a special file
363 0         0 }
364              
365             sub read($;$) # Read data from a file on L.\mRequired attributes: L, L.\mOptional attributes: L = the file to read, L, L.\mIf the read operation is successful, L is set to false and L is set to the data read from the file.\mIf the read operation fails then L is set to true and L is set to B.\mReturns the data read or B if no file was found.
366 0     0 1 0 {my ($gitHub, $File) = @_; # GitHub, file to read if not specified in gitFile
367              
368 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
369 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
370 0 0 0     0 my $file = qm($File//$gitHub->gitFile); $file or confess "gitFile required";
  0         0  
371 0         0 my $bran = qm $gitHub->refOrBranch(1);
372 0         0 my $pat = $gitHub->patKey(0);
373              
374 0         0 my $url = url;
375 0         0 my $s = filePath(qq(curl -si $pat $url),
376             $user, $repo, qq(contents), $file.$bran);
377 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s); # Get response from GitHub
378 0         0 my $failed = $gitHub->failed = $r->status != 200; # Check response code
379 0 0 0     0 $failed and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
380              
381 0 0       0 if ($failed) # Decode data unless read failed
382 0         0 {$gitHub->readData = undef;
383             }
384             else # Decode data
385 0         0 {my $d = decodeBase64($r->data->content);
386 0 0       0 $gitHub->readData = specialFileData($d) ? $d : decode "UTF8", $d; # Convert to utf unless a known file format
387             }
388              
389 0         0 $gitHub->readData
390             }
391              
392             sub write($$;$) # Write utf8 data into a L file.\mRequired attributes: L, L, L. Either specify the target file on: using the L attribute or supply it as the third parameter. Returns B on success else L.
393 0     0 1 0 {my ($gitHub, $data, $File) = @_; # GitHub object, data to be written, optionally the name of the file on github
394              
395 0 0       0 unless($data) # No data supplied so delete the file
396 0 0       0 {if ($File)
397 0         0 {my $file = $gitHub->file;
398 0         0 $gitHub->file = $File;
399 0         0 $gitHub->delete;
400 0         0 $gitHub->file = $file;
401             }
402             else
403 0         0 {$gitHub->delete;
404             }
405 0         0 return 'empty'; # Success
406             }
407              
408 0         0 my $pat = $gitHub->patKey(1);
409 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
410 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
411 0 0 0     0 my $file = qm($File//$gitHub->gitFile); $file or confess "gitFile required";
  0         0  
412 0   0     0 my $bran = qm $gitHub->refOrBranch(0) || '?';
413 0         0 my $mess = qm $gitHub->message;
414              
415 0 0       0 if (!specialFileData($data)) # Send the data as utf8 unless it is a special file
416 1     1   3062 {use Encode 'encode';
  1         9  
  1         6172  
417 0         0 $data = encode('UTF-8', $data);
418             }
419              
420 0         0 my $url = url;
421 0         0 my $save = $gitHub->gitFile; # Save any existing file name as we might need to update it to get the sha if the target file was supplied as a parameter to this sub
422 0 0       0 $gitHub->gitFile = $File if $File; # Set target file name so we can get its sha
423 0   0     0 my $s = $gitHub->getExistingSha || getSha($data); # Get the L of the file if the file exists
424 0         0 $gitHub->gitFile = $save; # Restore file name
425 0 0       0 my $sha = $s ? ', "sha": "'. $s .'"' : ''; # L of existing file or blank string if no existing file
426              
427             # if ($s and my $S = getSha($data)) # L of new data
428             # {if ($s eq $S) # Duplicate if the Ls match
429             # {$gitHub->failed = undef;
430             # return 1;
431             # }
432             # }
433              
434 0         0 my $denc = encodeBase64($data) =~ s/\n//gsr;
435              
436             my $branch = sub # It seems we must put the branch in the json file though the documentation seems to imply it can go in the url or the json
437 0     0   0 {my $b = $gitHub->branch;
438 0 0       0 return qq(, "branch" : "$b") if $b;
439 0         0 q()
440 0         0 }->();
441              
442 0         0 my $j = qq({"message": "$mess", "content": "$denc" $sha $branch});
443 0         0 my $t = writeFile(undef, $j); # Write encoded content to temporary file
444 0         0 my $d = qq(-d @).$t;
445 0         0 my $u = filePath($url, $user, $repo, qw(contents), $file.$bran);
446 0         0 my $c = qq(curl -si -X PUT $pat $u $d); # Curl command
447 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
448 0         0 unlink $t; # Cleanup
449              
450 0         0 my $status = $r->status; # Check response code
451 0 0       0 my $success = $status == 200 ? 'updated' : $status == 201 ? 'created' : undef;# Updated, created
    0          
452 0 0       0 $gitHub->failed = $success ? undef : 1;
453 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
454              
455 0         0 $success # Return true on success
456             }
457              
458             sub readBlob($$) # Read a L from L.\mRequired attributes: L, L, L. Returns the content of the L identified by the specified L.
459 0     0 1 0 {my ($gitHub, $sha) = @_; # GitHub object, data to be written
460 0 0       0 defined($sha) or confess "sha required";
461              
462 0         0 my $pat = $gitHub->patKey(1);
463 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
464 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
465 0         0 my $url = url;
466              
467 0         0 my $u = filePath($url, $user, $repo, qw(git blobs), $sha); # Url
468 0         0 my $c = qq(curl -si $pat $u); # Curl command
469 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
470              
471 0         0 my $status = $r->status; # Check response code
472 0         0 my $success = $status == 200;
473 0 0       0 $gitHub->failed = $success ? undef : 1;
474 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
475              
476 0 0       0 $success ? decodeBase64($gitHub->response->data->content) : undef # Return content on success else undef
477             }
478              
479             sub writeBlob($$) # Write data into a L as a L that can be referenced by future commits.\mRequired attributes: L, L, L. Returns the L of the created L or L in a failure occurred.
480 0     0 1 0 {my ($gitHub, $data) = @_; # GitHub object, data to be written
481 0 0       0 defined($data) or confess "binary data required";
482              
483 0         0 my $pat = $gitHub->patKey(1);
484 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
485 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
486 0         0 my $url = url;
487              
488 0         0 my $denc = encodeBase64($data) =~ s/\n//gsr;
489 0         0 my $t = writeTempFile(qq({"content": "$denc", "encoding" : "base64"})); # Write encoded content to temporary file
490 0         0 my $d = qq(-d @).$t;
491 0         0 my $u = filePath($url, $user, $repo, qw(git blobs));
492 0         0 my $c = qq(curl -si -X POST $pat $u $d); # Curl command
493 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
494 0         0 unlink $t; # Cleanup
495              
496 0         0 my $status = $r->status; # Check response code
497 0 0       0 my $success = $status == 200 ? 'updated' : $status == 201 ? 'created' : undef;# Updated, created
    0          
498 0 0       0 $gitHub->failed = $success ? undef : 1;
499 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
500              
501 0 0       0 $success ? $gitHub->response->data->sha : undef # Return sha of blob on success
502             }
503              
504             sub copy($$) # Copy a source file from one location to another target location in your L repository, overwriting the target file if it already exists.\mRequired attributes: L, L, L, L = the file to be copied.\mOptional attributes: L.\mIf the write operation is successful, L is set to false otherwise it is set to true.\mReturns B if the write updated the file, B if the write created the file else B if the write failed.
505 0     0 1 0 {my ($gitHub, $target) = @_; # GitHub object, the name of the file to be created
506 0 0       0 defined($target) or confess "Specify the name of the file to be copied to";
507 0         0 my $r = $gitHub->read; # Read the content of the source file
508 0 0       0 if (defined $r)
509 0         0 {my $file = $gitHub->gitFile; # Save current source file
510 0         0 my $sha = $gitHub->response->data->sha; # L of last file read
511 0         0 $gitHub->gitFile = $target; # Set target file as current file
512 0         0 my $R = $gitHub->write($r); # Write content to target file
513 0         0 $gitHub->copySha; # Copy the L from the file just read
514 0         0 $gitHub->gitFile = $file; # Restore source file
515 0         0 return $R; # Return response from write
516             }
517             undef # Failed
518 0         0 }
519              
520             sub exists($) # Test whether a file exists on L or not and returns an object including the B and B fields if it does else L.\mRequired attributes: L, L, L file to test.\mOptional attributes: L, L.
521 0     0 1 0 {my ($gitHub) = @_; # GitHub object
522 0         0 my @file = split /\//, $gitHub->gitFile;
523 0 0       0 confess "gitFile required to name the file to be checked" unless @file;
524 0         0 pop @file;
525 0         0 my $folder = $gitHub->gitFolder;
526 0         0 my $nonRecursive = $gitHub->nonRecursive;
527 0         0 $gitHub->gitFolder = filePath(@file);
528 0         0 $gitHub->nonRecursive = 1;
529 0         0 my $r = $gitHub->list; # Get a file listing
530 0         0 $gitHub->gitFolder = $folder;
531 0         0 $gitHub->nonRecursive = $nonRecursive;
532              
533 0 0 0     0 if (!$gitHub->failed and reftype($gitHub->response->data) =~ m(array)i) # Look for requested file in file listing
534 0         0 {for(@{$gitHub->response->data})
  0         0  
535 0 0       0 {return $_ if $_->path eq $gitHub->gitFile;
536             }
537             }
538             undef
539 0         0 }
540              
541             sub rename($$) # Rename a source file on L if the target file name is not already in use.\mRequired attributes: L, L, L, L = the file to be renamed.\mOptional attributes: L.\mReturns the new name of the file B if the rename was successful else B if the rename failed.
542 0     0 1 0 {my ($gitHub, $target) = @_; # GitHub object, the new name of the file
543 0         0 my $file = $gitHub->gitFile;
544 0         0 $gitHub->gitFile = $target;
545 0 0       0 return undef if $gitHub->exists;
546 0         0 $gitHub->gitFile = $file;
547 0         0 $gitHub->copy($target);
548 0         0 $gitHub->gitFile = $target;
549 0 0       0 if ($gitHub->exists)
550 0         0 {$gitHub->gitFile = $file;
551 0 0       0 return $target if $gitHub->delete;
552 0         0 confess "Failed to delete source file $file";
553             }
554             undef
555 0         0 }
556              
557             sub delete($) # Delete a file from L.\mRequired attributes: L, L, L, L = the file to be deleted.\mOptional attributes: L.\mIf the delete operation is successful, L is set to false otherwise it is set to true.\mReturns true if the delete was successful else false.
558 0     0 1 0 {my ($gitHub) = @_; # GitHub object
559              
560 0         0 my $pat = $gitHub->patKey(1);
561 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
562 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
563 0 0       0 my $file = qm $gitHub->gitFile; $file or confess "file to delete required";
  0         0  
564 0         0 my $bran = qm $gitHub->refOrBranch(0);
565 0         0 my $url = url;
566              
567 0         0 my $s = $gitHub->getExistingSha; # L of existing file or undef
568 0 0       0 return 2 unless $s; # File already deleted
569 0         0 my $sha = ' -d \'{"message": "", "sha": "'. $s .'"}\'';
570 0         0 my $u = filePath($url, $user, $repo, qw(contents), $file.$bran.$sha);
571 0         0 my $d = "curl -si -X DELETE $pat $u";
572 0         0 my $r = GitHub::Crud::Response::new($gitHub, $d);
573 0         0 my $success = $r->status == 200; # Check response code
574 0 0       0 $gitHub->deleteSha if $success; # The L is no longer valid
575 0 0       0 $gitHub->failed = $success ? undef : 1;
576 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
577 0 0       0 $success ? 1 : undef # Return true on success
578             }
579              
580             #D1 Repositories # Perform actions on L repositories.
581              
582             sub getRepository($) # Get the overall details of a repository
583 0     0 1 0 {my ($gitHub) = @_; # GitHub object
584              
585 0         0 my $pat = $gitHub->patKey(1);
586 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
587 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
588 0         0 my $url = url;
589              
590 0         0 my $c = qq(curl -si $pat $url/$user/$repo);
591 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
592 0         0 my $success = $r->status == 200; # Check response code
593 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump([$gitHub, $c]); # Confess to any failure if so requested
594              
595 0         0 $r
596             }
597              
598             sub listCommits($) # List all the commits in a L repository.\mRequired attributes: L, L.
599 0     0 1 0 {my ($gitHub) = @_; # GitHub object
600              
601 0         0 my $pat = $gitHub->patKey(1);
602 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
603 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
604 0         0 my $url = url;
605              
606 0         0 my $c = qq(curl -si $pat $url/$user/$repo/branches);
607              
608 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
609 0         0 my $success = $r->status == 200; # Check response code
610 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
611              
612 0         0 $r
613             }
614              
615             sub listCommitShas($) # Create {commit name => sha} from the results of L.
616 0     0 1 0 {my ($commits) = @_; # Commits from L
617              
618 0 0       0 return undef unless my $data = $commits->data; # Commits array
619 0         0 {map {$$_{name} => $$_{commit}{sha}} @$data} # Commits hash
  0         0  
  0         0  
620             }
621              
622             sub writeCommit($$@) # Write all the files in a B<$folder> (or just the the named files) into a L repository in parallel as a commit on the specified branch.\mRequired attributes: L, L, L.
623 0     0 1 0 {my ($gitHub, $folder, @files) = @_; # GitHub object, file prefix to remove, files to write
624              
625 0 0       0 -d $folder or confess "No such folder"; # Folder does not exist
626              
627 0         0 my $pat = $gitHub->patKey(1);
628 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
629 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
630 0 0       0 my $bran = $gitHub->branch; $bran or confess "branch required";
  0         0  
631 0         0 my $url = url;
632              
633             my @sha = processFilesInParallel sub # Create blobs for each file
634 0     0   0 {my ($source) = @_;
635 0         0 my $target = $gitHub->gitFile = swapFilePrefix($source, $folder);
636 0         0 [$source, $target, $gitHub->writeBlob(readBinaryFile($source))]
637 0 0       0 }, undef, @files ? @files : searchDirectoryTreesForMatchingFiles($folder);
638              
639             my $tree = sub # Create the tree
640 0     0   0 {my @t;
641 0         0 for my $f(@sha) # Load files into a tree
642 0         0 {my ($s, $t, $b) = @$f;
643 0         0 push @t, <
644             {"path" : "$t",
645             "mode" : "100644",
646             "type" : "blob",
647             "sha" : "$b"
648             }
649             END
650             }
651              
652 0         0 my $t = join ",\n", @t; # Assemble tree
653 0         0 my $j = qq({"tree" : [$t]}); # Json describing tree
654 0         0 my $f = writeTempFile($j); # Write Json
655 0         0 my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/trees);
656              
657 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
658 0         0 my $success = $r->status == 201; # Check response code
659 0         0 unlink $f; # Cleanup
660              
661 0 0       0 $success or confess "Unable to create tree: ".dump($r);
662              
663 0         0 $r
664 0         0 }->();
665              
666             my $parents = sub # Prior commits
667 0     0   0 {my %c = listCommitShas $gitHub->listCommits;
668 0         0 my $b = $gitHub->branch;
669 0 0       0 return '' unless my $s = $c{$b};
670 0         0 qq(, "parents" : ["$s"])
671 0         0 }->();
672              
673             my $commit = sub # Create a commit to hold the tree
674 0     0   0 {my $s = $tree->data->sha;
675 0         0 my $d = dateTimeStamp;
676 0         0 my $j = <
677             { "message" : "Committed by GitHub::Crud on: $d"
678             , "tree" : "$s"
679             $parents
680             }
681             END
682 0         0 my $f = writeFile(undef, $j); # Write json
683              
684 0         0 my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/commits); # Execute json
685              
686 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
687 0         0 my $success = $r->status == 201; # Check response code
688 0         0 unlink $f; # Cleanup
689              
690 0 0       0 $success or confess "Unable to create commit: ".dump($r);
691              
692 0         0 $r
693 0         0 }->();
694              
695             my $branch = sub # Update branch - if this fails we will try a force as the next step
696 0     0   0 {my $s = $commit->data->sha;
697 0         0 my $f = writeFile(undef, <
698             {
699             "ref": "refs/heads/$bran",
700             "sha": "$s"
701             }
702             END
703 0         0 my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/refs);
704 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
705 0         0 my $success = $r->status == 201; # Check response code
706 0         0 unlink $f; # Cleanup
707              
708 0         0 $r
709 0         0 }->();
710              
711 0         0 my $status = $branch->status; # Creation status
712 0 0       0 if ($branch->status == 201) {return $branch} # Branch created
  0 0       0  
713             elsif ($branch->status == 422) # Update existing branch
714             {my $branchUpdate = sub
715 0     0   0 {my $s = $commit->data->sha;
716 0         0 my $f = writeFile(undef, <
717             { "sha": "$s",
718             "force": true
719             }
720             END
721 0         0 my $c = qq(curl -si -X PATCH $pat -d \@$f $url/$user/$repo/git/refs/heads/$bran);
722 0         0 my $r = GitHub::Crud::Response::new($gitHub, $c);
723 0         0 my $success = $r->status == 200; # Check response code
724 0         0 unlink $f; # Cleanup
725              
726 0 0       0 $success or confess "Unable to update branch: ".dump($r);
727              
728 0         0 $r
729 0         0 }->();
730 0         0 return $branchUpdate;
731             }
732              
733 0         0 confess "Unable to create/update branch: $bran";
734             }
735              
736             sub listWebHooks($) # List web hooks associated with your L repository.\mRequired: L, L, L. \mIf the list operation is successful, L is set to false otherwise it is set to true.\mReturns true if the list operation was successful else false.
737 0     0 1 0 {my ($gitHub) = @_; # GitHub object
738 0         0 my $pat = $gitHub->patKey(1);
739 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
740 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
741 0         0 my $bran = qm $gitHub->refOrBranch(0);
742 0         0 my $url = url;
743              
744 0         0 my $u = filePath($url, $user, $repo, qw(hooks));
745 0         0 my $s = "curl -si $pat $u";
746 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s);
747 0         0 my $success = $r->status =~ m(200|201); # Present or not present
748 0 0       0 $gitHub->failed = $success ? undef : 1;
749 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
750 0 0       0 $success ? $gitHub->response->data : undef # Return reference to array of web hooks on success. If there are no web hooks set then the referenced array will be empty.
751             }
752              
753             sub createPushWebHook($) # Create a web hook for your L userid.\mRequired: L, L, L, L.\mOptional: L.\mIf the create operation is successful, L is set to false otherwise it is set to true.\mReturns true if the web hook was created successfully else false.
754 0     0 1 0 {my ($gitHub) = @_; # GitHub object
755 0         0 my $pat = $gitHub->patKey(1);
756 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
757 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
758 0 0       0 my $webUrl = qm $gitHub->webHookUrl; $webUrl or confess "url required";
  0         0  
759 0         0 my $bran = qm $gitHub->refOrBranch(0);
760 0         0 my $secret = $gitHub->secret;
761 0 0       0 my $sj = $secret ? qq(, "secret": "$secret") : ''; # Secret for Json
762 0         0 my $url = url;
763              
764 0 0       0 $webUrl =~ m(\Ahttps?://) or confess # Check that we are using a url like thing for the web hook or complain
765             "Web hook has no scheme, should start with https?:// not:\n$webUrl";
766              
767 0         0 owf(my $tmpFile = temporaryFile(), my $json = <
768             {"name": "web", "active": true, "events": ["push"],
769             "config": {"url": "$webUrl", "content_type": "json" $sj}
770             }
771             END
772 0         0 my $d = q( -d @).$tmpFile;
773 0         0 my $u = filePath($url, $user, $repo, qw(hooks));
774 0         0 my $s = "curl -si -X POST $pat $u $d"; # Create url
775 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s);
776              
777 0         0 my $success = $r->status == 201; # Check response code
778 0         0 unlink $tmpFile; # Cleanup
779 0 0       0 $gitHub->failed = $success ? undef : 1;
780 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
781 0 0       0 $success ? 1 : undef # Return true on success
782             }
783              
784             sub listRepositories($) # List the repositories accessible to a user on L.\mRequired: L.\mReturns details of the repositories.
785 0     0 1 0 {my ($gitHub) = @_; # GitHub object
786              
787 0         0 my $pat = $gitHub->patKey(1);
788 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
789 0         0 my $url = api;
790              
791 0         0 my $u = filePath($url, qw(user repos)); # Request url
792 0         0 my $s = "curl -si $pat $u"; # Create url
793 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s);
794 0         0 my $success = $r->status == 200; # Check response code
795 0 0       0 $gitHub->failed = $success ? undef : 1;
796 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
797 0 0       0 $success ? $r->data->@* : undef # Return a list of repositories on success
798             }
799              
800             sub createRepository($) # Create a repository on L.\mRequired: L, L.\mReturns true if the issue was created successfully else false.
801 0     0 1 0 {my ($gitHub) = @_; # GitHub object
802 0         0 my $pat = $gitHub->patKey(1);
803 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
804 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
805 0 0       0 my $private= $gitHub->private ? q(, "private":true) : q(); # Private or not
806 0         0 my $url = api;
807              
808 0         0 my $json = qq({"name":"$repo", "auto_init":true $private}); # Issue in json
809 0         0 my $tmpFile = writeFile(undef, $json); # Write repo definition
810 0         0 my $d = q( -d @).$tmpFile;
811 0         0 my $u = filePath($url, qw(user repos)); # Request url
812 0         0 my $s = "curl -si -X POST $pat $u $d"; # Create url
813 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s);
814 0         0 my $success = $r->status == 201; # Check response code
815 0         0 unlink $tmpFile; # Cleanup
816 0 0       0 $gitHub->failed = $success ? undef : 1;
817 0 0 0     0 !$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
818 0 0       0 $success ? 1 : undef # Return true on success
819             }
820              
821             sub createRepositoryFromSavedToken($$;$$) # Create a repository on L using an access token either as supplied or saved in a file using L.\mReturns true if the issue was created successfully else false.
822 0     0 1 0 {my ($userid, $repository, $private, $accessFolderOrToken) = @_; # Userid on GitHub, the repository name, true if the repo is private, location of access token.
823 0         0 my $g = GitHub::Crud::new;
824 0         0 $g->userid = $userid;
825 0         0 $g->repository = $repository;
826 0         0 $g->private = $private;
827 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
828 0         0 $g->loadPersonalAccessToken;
829 0         0 $g->confessOnFailure = 0;
830 0         0 $g->createRepository;
831             }
832              
833             #D1 Issues # Create issues on L.
834              
835             sub createIssue($) # Create an issue on L.\mRequired: L, L, L, L.\mIf the operation is successful, L is set to false otherwise it is set to true.\mReturns true if the issue was created successfully else false.
836 0     0 1 0 {my ($gitHub) = @_; # GitHub object
837 0         0 my $pat = $gitHub->patKey(1);
838 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
839 0 0       0 my $repo = qm $gitHub->repository; $repo or confess "repository required";
  0         0  
840 0 0       0 my $body = $gitHub->body; $body or confess "body required";
  0         0  
841 0 0       0 my $title = $gitHub->title; $title or confess "title required";
  0         0  
842 0         0 my $bran = qm $gitHub->refOrBranch(0);
843 0         0 my $url = url;
844              
845 0         0 my $json = encodeJson({body=>$body, title=>$title}); # Issue in json
846 0         0 owf(my $tmpFile = temporaryFile(), $json); # Write issue definition
847 0         0 my $d = q( -d @).$tmpFile;
848 0         0 my $u = filePath($url, $user, $repo, qw(issues));
849 0         0 my $s = "curl -si -X POST $pat $u $d"; # Create url
850 0         0 my $r = GitHub::Crud::Response::new($gitHub, $s);
851 0         0 my $success = $r->status == 201; # Check response code
852 0         0 unlink $tmpFile; # Cleanup
853 0 0       0 $gitHub->failed = $success ? undef : 1;
854 0 0 0     0 !$success and $gitHub->confessOnFailure and # Confess to any failure if so requested
855             confess join "\n", dump($gitHub), $json, $s;
856 0 0       0 $success ? 1 : undef # Return true on success
857             }
858              
859             #D1 Using saved access tokens # Call methods directly using a saved access token rather than first creating a L description object and then calling methods using it. This is often more convenient if you just want to perform one or two actions.
860              
861             sub createIssueFromSavedToken($$$$;$) # Create an issue on L using an access token as supplied or saved in a file using L.\mReturns true if the issue was created successfully else false.
862 0     0 1 0 {my ($userid, $repository, $title, $body, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, issue title, issue body, location of access token.
863 0         0 my $g = GitHub::Crud::new;
864 0         0 $g->userid = $userid;
865 0         0 $g->repository = $repository;
866 0         0 $g->title = $title;
867 0         0 $g->body = $body;
868 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
869 0         0 $g->loadPersonalAccessToken;
870 0         0 $g->confessOnFailure = 1;
871 0         0 $g->createIssue;
872             }
873              
874             sub writeFileUsingSavedToken($$$$;$) # Write to a file on L using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
875 0     0 1 0 {my ($userid, $repository, $file, $content, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on github, file content, location of access token.
876 0         0 my $g = GitHub::Crud::new;
877 0 0       0 $g->userid = $userid; $userid or confess "Userid required";
  0         0  
878 0 0       0 $g->repository = $repository; $repository or confess "Repository required";
  0         0  
879 0 0       0 $g->gitFile = $file; $file or confess "File required";
  0         0  
880 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
881 0         0 $g->loadPersonalAccessToken;
882 0         0 $g->write($content);
883             }
884              
885             sub writeFileFromFileUsingSavedToken($$$$;$) # Copy a file to L using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
886 0     0 1 0 {my ($userid, $repository, $file, $localFile, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on github, file content, location of access token.
887 0         0 writeFileUsingSavedToken($userid, $repository, $file,
888             readBinaryFile($localFile), $accessFolderOrToken);
889             }
890              
891             sub readFileUsingSavedToken($$$;$) # Read from a file on L using a personal access token as supplied or saved in a file. Return the content of the file on success or confess to any failure.
892 0     0 1 0 {my ($userid, $repository, $file, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on GitHub, location of access token.
893 0         0 my $g = GitHub::Crud::new;
894 0         0 $g->userid = $userid;
895 0         0 $g->repository = $repository;
896 0         0 $g->gitFile = $file;
897 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
898 0         0 $g->loadPersonalAccessToken;
899 0         0 $g->read;
900             }
901              
902             sub writeFolderUsingSavedToken($$$$;$) # Write all the files in a local folder to a target folder on a named L repository using a personal access token as supplied or saved in a file.
903 0     0 1 0 {my ($userid,$repository,$targetFolder,$localFolder,$accessFolderOrToken) = @_;# Userid on GitHub, repository name, target folder on GitHub, local folder name, location of access token.
904 0         0 my $g = GitHub::Crud::new;
905 0         0 $g->userid = $userid;
906 0         0 $g->repository = $repository;
907 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
908 0         0 $g->loadPersonalAccessToken;
909              
910 0         0 for my $file(searchDirectoryTreesForMatchingFiles($localFolder))
911 0         0 {$g->gitFile = swapFilePrefix($file, $localFolder, $targetFolder);
912 0         0 $g->write(readBinaryFile($file));
913             }
914             }
915              
916             sub writeCommitUsingSavedToken($$$;$) # Write all the files in a local folder to a named L repository using a personal access token as supplied or saved in a file.
917 0     0 1 0 {my ($userid, $repository, $source, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, local folder on GitHub, optionally: location of access token.
918 0         0 my $g = GitHub::Crud::new;
919 0         0 $g->userid = $userid;
920 0         0 $g->repository = $repository;
921 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
922 0         0 $g->loadPersonalAccessToken;
923 0         0 $g->branch = 'master';
924              
925 0         0 $g->writeCommit($source);
926             }
927              
928             sub deleteFileUsingSavedToken($$$;$) # Delete a file on L using a saved token
929 0     0 1 0 {my ($userid, $repository, $target, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file on GitHub, optional: the folder containing saved access tokens.
930 0         0 my $g = GitHub::Crud::new;
931 0         0 $g->userid = $userid;
932 0         0 $g->repository = $repository;
933 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
934 0         0 $g->loadPersonalAccessToken;
935              
936 0         0 $g->gitFile = $target;
937 0         0 $g->delete;
938             }
939              
940             sub getRepositoryUsingSavedToken($$;$) # Get repository details from L using a saved token
941 0     0 1 0 {my ($userid, $repository, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, optionally: location of access token.
942 0         0 my $g = GitHub::Crud::new;
943 0 0       0 $g->userid = $userid; $userid or confess "Userid required";
  0         0  
944 0 0       0 $g->repository = $repository; $repository or confess "Repository required";
  0         0  
945 0         0 $g->personalAccessTokenFolder = $accessFolderOrToken;
946 0         0 $g->loadPersonalAccessToken;
947 0         0 $g->getRepository;
948             }
949              
950             sub getRepositoryUpdatedAtUsingSavedToken($$;$) # Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
951 0     0 1 0 {my ($userid, $repository, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, optionally: location of access token.
952 0         0 my $r = &getRepositoryUsingSavedToken(@_); # Get repository details using a saved token
953 0         0 my $u = $r->data->{updated_at};
954 0         0 return Date::Manip::UnixDate($u,'%s');
955             }
956              
957             #D1 Actions # Perform an action against the current repository while running as a L action. If such an action requires a security token please supply the token as shown in at the end of: L.
958              
959             sub currentRepo() #P Create a L object for the current repo if we are on L actions
960 0 0   0 1 0 {if (my $r = $ENV{GITHUB_REPOSITORY}) # We are on GitHub
961 0         0 {my ($user, $repo) = split m(/), $r, 2;
962 0         0 my $g = GitHub::Crud::new;
963 0         0 $g->userid = $user;
964 0         0 $g->repository = $repo;
965 0         0 $g->personalAccessToken = $ENV{GITHUB_TOKEN};
966 0         0 $g->confessOnFailure = 1;
967              
968 0 0       0 if (!$g->personalAccessToken)
969 0         0 {confess "Unable to load github token for repository $r from environment variable: GITHUB_TOKEN\nSee: https://github.com/philiprbrenan/postgres/blob/main/.github/workflows/main.yml";
970             }
971              
972 0         0 return $g;
973             }
974             undef
975 0         0 }
976              
977             sub createIssueInCurrentRepo($$) # Create an issue in the current L repository if we are running on L.
978 0     0 1 0 {my ($title, $body) = @_; # Title of issue, body of issue
979 0 0       0 if (my $g = currentRepo) # We are on GitHub
980 0         0 {$g->title = $title;
981 0         0 $g->body = $body;
982 0         0 $g->createIssue;
983             }
984             }
985              
986             sub writeFileFromCurrentRun($$) # Write text into a file in the current L repository if we are running on L.
987 0     0 1 0 {my ($target, $text) = @_; # The target file name in the repo, the text to write into this file
988 0 0       0 if (my $g = currentRepo) # We are on GitHub
989 0         0 {$g->gitFile = $target;
990 0         0 $g->write($text);
991             }
992             }
993              
994             sub writeFileFromFileFromCurrentRun($) # Write to a file in the current L repository by copying a local file if we are running on L.
995 0     0 1 0 {my ($target) = @_; # File name both locally and in the repo
996 0 0       0 -e $target or confess "File to upload does not exist:\n$target";
997 0 0       0 if (my $g = currentRepo) # We are on GitHub
998 0         0 {$g->gitFile = $target;
999 0         0 $g->write(scalar(readFile($target)));
1000             }
1001             }
1002              
1003             sub writeBinaryFileFromFileInCurrentRun($$) # Write to a file in the current L repository by copying a local binary file if we are running on L.
1004 0     0 1 0 {my ($target, $source) = @_; # The target file name in the repo, the current file name in the run
1005 0 0       0 if (my $g = currentRepo) # We are on GitHub
1006 0         0 {$g->gitFile = $target;
1007 0         0 $g->write(readBinaryFile($source));
1008             }
1009             }
1010              
1011             #D1 Access tokens # Load and save access tokens. Some L requests must be signed with an L access token. These methods help you store and reuse such tokens. Access tokens can be created at: L.
1012              
1013             sub savePersonalAccessToken($) # Save a L personal access token by userid in folder L.
1014 0     0 1 0 {my ($gitHub) = @_; # GitHub object
1015 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
1016 0 0       0 my $pat = $gitHub->personalAccessToken; $pat or confess "personal access token required";
  0         0  
1017 0   0     0 my $dir = $gitHub->personalAccessTokenFolder // accessFolder;
1018 0         0 my $file = filePathExt($dir, $user, q(data));
1019 0         0 makePath($file);
1020 0         0 storeFile($file, {pat=>$pat}); # Store personal access token
1021 0 0       0 -e $file or confess "Unable to store personal access token in file:\n$file"; # Complain if store fails
1022 0         0 my $p = retrieveFile $file; # Retrieve access token to check that we wrote it successfully
1023             $pat eq $p->{pat} or # Check file format
1024 0 0       0 confess "File contains the wrong personal access token:\n$file";
1025             }
1026              
1027             sub loadPersonalAccessToken($) # Load a personal access token by userid from folder L.
1028 0     0 1 0 {my ($gitHub) = @_; # GitHub object
1029 0 0       0 my $user = qm $gitHub->userid; $user or confess "userid required";
  0         0  
1030              
1031 0 0 0     0 if (length($gitHub->personalAccessTokenFolder//accessFolder) == 43) # Access token supplied directly
1032 0         0 {return $gitHub->personalAccessToken = $gitHub->personalAccessTokenFolder;
1033             }
1034              
1035 0 0       0 if ($ENV{GITHUB_TOKEN}) # Access token supplied through environment
1036 0         0 {return $gitHub->personalAccessToken = $ENV{GITHUB_TOKEN};
1037             }
1038              
1039 0   0     0 my $dir = $gitHub->personalAccessTokenFolder // accessFolder;
1040 0         0 my $file = filePathExt($dir, $user, q(data));
1041 0         0 my $p = retrieveFile $file; # Load personal access token
1042             my $a = $p->{pat} or # Check file format
1043 0 0       0 confess "File does not contain a personal access token:\n$file";
1044 0         0 $gitHub->personalAccessToken = $a; # Retrieve token
1045             }
1046              
1047             #D0
1048             #-------------------------------------------------------------------------------
1049             # Export - eeee
1050             #-------------------------------------------------------------------------------
1051              
1052 1     1   10 use Exporter qw(import);
  1         2  
  1         33  
1053              
1054 1     1   6 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         482  
1055              
1056             # containingFolder
1057              
1058             @ISA = qw(Exporter);
1059             @EXPORT_OK = qw(
1060             createIssueFromSavedToken
1061             createIssueInCurrentRepo
1062             createRepositoryFromSavedToken
1063             deleteFileUsingSavedToken
1064             getRepository
1065             getRepositoryUsingSavedToken
1066             getRepositoryUpdatedAtUsingSavedToken
1067             readFileUsingSavedToken
1068             writeBinaryFileFromFileInCurrentRun
1069             writeCommitUsingSavedToken
1070             writeFileFromCurrentRun
1071             writeFileFromFileUsingSavedToken
1072             writeFileUsingSavedToken
1073             writeFolderUsingSavedToken
1074             );
1075             %EXPORT_TAGS = (all=>[@EXPORT_OK]);
1076              
1077             #podDocumentation
1078              
1079             =pod
1080              
1081             =encoding utf-8
1082              
1083             =head1 Name
1084              
1085             GitHub::Crud - Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
1086              
1087             =head1 Synopsis
1088              
1089             Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub as
1090             described at:
1091              
1092             https://developer.github.com/v3/repos/contents/#update-a-file
1093              
1094             =head2 Upload a file from an action
1095              
1096             Upload a file created during a github action to the repository for that action:
1097              
1098             GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
1099             perl -M"GitHub::Crud" -e"GitHub::Crud::writeFileFromFileFromCurrentRun q(output.txt);"
1100              
1101             =head2 Upload a folder
1102              
1103             Commit a folder to GitHub then read and check some of the uploaded content:
1104              
1105             use GitHub::Crud;
1106             use Data::Table::Text qw(:all);
1107              
1108             my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
1109             my $c = dateTimeStamp; # Create some content
1110             my $if = q(/home/phil/.face); # Image file
1111              
1112             writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
1113             copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
1114              
1115             my $g = GitHub::Crud::new # Create GitHub
1116             (userid => q(philiprbrenan),
1117             repository => q(aaa),
1118             branch => q(test),
1119             confessOnFailure => 1);
1120              
1121             $g->loadPersonalAccessToken; # Load a personal access token
1122             $g->writeCommit($f); # Upload commit - confess to any errors
1123              
1124             my $C = $g->read(q(data/1.txt)); # Read data written in commit
1125             my $I = $g->read(q(face.jpg));
1126             my $i = readBinaryFile $if;
1127              
1128             confess "Date stamp failed" unless $C eq $c; # Check text
1129             confess "Image failed" unless $i eq $I; # Check image
1130             confess "Write commit succeeded";
1131              
1132             =head2 Prerequisites
1133              
1134             Please install B if it is not already present on your computer.
1135              
1136             sudo apt-get install curl
1137              
1138             =head2 Personal Access Token
1139              
1140             You will need to create a personal access token if you wish to gain write
1141             access to your respositories : L.
1142             Depending on your security requirements you can either install this token at
1143             the well known location:
1144              
1145             /etc/GitHubCrudPersonalAccessToken/
1146              
1147             or at a location of your choice. If you use a well known location then the
1148             personal access token will be loaded automatically for you, else you will need
1149             to supply it to each call via the L attribute.
1150              
1151             =head1 Description
1152              
1153             Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
1154              
1155              
1156             Version 20210615.
1157              
1158              
1159             The following sections describe the methods in each functional area of this
1160             module. For an alphabetic listing of all methods by name see L.
1161              
1162              
1163              
1164             =head1 Constructor
1165              
1166             Create a L object with the specified attributes describing the interface with L.
1167              
1168             =head2 new(%attributes)
1169              
1170             Create a new L object with attributes as described at: L.
1171              
1172             Parameter Description
1173             1 %attributes Attribute values
1174              
1175             B
1176              
1177              
1178             my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
1179             my $c = dateTimeStamp; # Create some content
1180             my $if = q(/home/phil/.face); # Image file
1181              
1182             writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
1183             copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
1184              
1185              
1186             my $g = GitHub::Crud::new # Create GitHub # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1187              
1188             (userid => q(philiprbrenan),
1189             repository => q(aaa),
1190             branch => q(test),
1191             confessOnFailure => 1);
1192              
1193             $g->loadPersonalAccessToken; # Load a personal access token
1194             $g->writeCommit($f); # Upload commit - confess to any errors
1195              
1196             my $C = $g->read(q(data/1.txt)); # Read data written in commit
1197             my $I = $g->read(q(face.jpg));
1198             my $i = readBinaryFile $if;
1199              
1200             confess "Date stamp failed" unless $C eq $c; # Check text
1201             confess "Image failed" unless $i eq $I; # Check image
1202             success "Write commit succeeded";
1203              
1204              
1205             =head1 Files
1206              
1207             File actions on the contents of L repositories.
1208              
1209             =head2 list($gitHub)
1210              
1211             List all the files contained in a L repository or all the files below a specified folder in the repository.
1212              
1213             Required attributes: L, L.
1214              
1215             Optional attributes: L, L, L, L.
1216              
1217             Use the L parameter to specify the folder to start the list from, by default, the listing will start at the root folder of your repository.
1218              
1219             Use the L option if you require only the files in the start folder as otherwise all the folders in the start folder will be listed as well which might take some time.
1220              
1221             If the list operation is successful, L is set to false and L is set to refer to an array of the file names found.
1222              
1223             If the list operation fails then L is set to true and L is set to refer to an empty array.
1224              
1225             Returns the list of file names found or empty list if no files were found.
1226              
1227             Parameter Description
1228             1 $gitHub GitHub
1229              
1230             B
1231              
1232              
1233              
1234             success "list:", gitHub->list; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1235              
1236              
1237             # list: alpha.data .github/workflows/test.yaml images/aaa.txt images/aaa/bbb.txt # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1238              
1239              
1240              
1241             =head2 read($gitHub, $File)
1242              
1243             Read data from a file on L.
1244              
1245             Required attributes: L, L.
1246              
1247             Optional attributes: L = the file to read, L, L.
1248              
1249             If the read operation is successful, L is set to false and L is set to the data read from the file.
1250              
1251             If the read operation fails then L is set to true and L is set to B.
1252              
1253             Returns the data read or B if no file was found.
1254              
1255             Parameter Description
1256             1 $gitHub GitHub
1257             2 $File File to read if not specified in gitFile
1258              
1259             B
1260              
1261              
1262             my $g = gitHub;
1263             $g->gitFile = my $f = q(z'2 'z"z.data);
1264             my $d = q(𝝰𝝱𝝲);
1265             $g->write($d);
1266              
1267             confess "read FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1268              
1269             success "Read passed";
1270              
1271              
1272             =head2 write($gitHub, $data, $File)
1273              
1274             Write utf8 data into a L file.
1275              
1276             Required attributes: L, L, L. Either specify the target file on: using the L attribute or supply it as the third parameter. Returns B on success else L.
1277              
1278             Parameter Description
1279             1 $gitHub GitHub object
1280             2 $data Data to be written
1281             3 $File Optionally the name of the file on github
1282              
1283             B
1284              
1285              
1286             my $g = gitHub;
1287             $g->gitFile = "zzz.data";
1288              
1289             my $d = dateTimeStamp.q( 𝝰𝝱𝝲);
1290              
1291             if (1)
1292             {my $t = time();
1293              
1294             $g->write($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1295              
1296              
1297             lll "First write time: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1298              
1299             }
1300              
1301             my $r = $g->read;
1302             lll "Write bbb: $r";
1303             if (1)
1304             {my $t = time();
1305              
1306             $g->write($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1307              
1308              
1309             lll "Second write time: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1310              
1311             }
1312              
1313             confess "write FAILED" unless $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1314              
1315             success "Write passed";
1316              
1317              
1318             =head2 readBlob($gitHub, $sha)
1319              
1320             Read a L from L.
1321              
1322             Required attributes: L, L, L. Returns the content of the L identified by the specified L.
1323              
1324             Parameter Description
1325             1 $gitHub GitHub object
1326             2 $sha Data to be written
1327              
1328             B
1329              
1330              
1331             my $g = gitHub;
1332             $g->gitFile = "face.jpg";
1333             my $d = readBinaryFile(q(/home/phil/.face));
1334             my $s = $g->writeBlob($d);
1335             my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
1336             confess q(Write blob FAILED) unless $s eq $S;
1337              
1338              
1339             my $D = $g->readBlob($s); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1340              
1341             confess q(Write/Read blob FAILED) unless $d eq $D;
1342             success q(Write/Read blob passed);
1343              
1344              
1345             =head2 writeBlob($gitHub, $data)
1346              
1347             Write data into a L as a L that can be referenced by future commits.
1348              
1349             Required attributes: L, L, L. Returns the L of the created L or L in a failure occurred.
1350              
1351             Parameter Description
1352             1 $gitHub GitHub object
1353             2 $data Data to be written
1354              
1355             B
1356              
1357              
1358             my $g = gitHub;
1359             $g->gitFile = "face.jpg";
1360             my $d = readBinaryFile(q(/home/phil/.face));
1361              
1362             my $s = $g->writeBlob($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1363              
1364             my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
1365             confess q(Write blob FAILED) unless $s eq $S;
1366              
1367             my $D = $g->readBlob($s);
1368             confess q(Write/Read blob FAILED) unless $d eq $D;
1369             success q(Write/Read blob passed);
1370              
1371              
1372             =head2 copy($gitHub, $target)
1373              
1374             Copy a source file from one location to another target location in your L repository, overwriting the target file if it already exists.
1375              
1376             Required attributes: L, L, L, L = the file to be copied.
1377              
1378             Optional attributes: L.
1379              
1380             If the write operation is successful, L is set to false otherwise it is set to true.
1381              
1382             Returns B if the write updated the file, B if the write created the file else B if the write failed.
1383              
1384             Parameter Description
1385             1 $gitHub GitHub object
1386             2 $target The name of the file to be created
1387              
1388             B
1389              
1390              
1391             my ($f1, $f2) = ("zzz.data", "zzz2.data");
1392             my $g = gitHub;
1393             $g->gitFile = $f2; $g->delete;
1394             $g->gitFile = $f1;
1395             my $d = dateTimeStamp;
1396             my $w = $g->write($d);
1397              
1398             my $r = $g->copy($f2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1399              
1400             lll "Copy created: $r";
1401             $g->gitFile = $f2;
1402             my $D = $g->read;
1403             lll "Read ccc: $D";
1404              
1405             confess "copy FAILED" unless $d eq $D; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1406              
1407             success "Copy passed"
1408              
1409              
1410             =head2 exists($gitHub)
1411              
1412             Test whether a file exists on L or not and returns an object including the B and B fields if it does else L.
1413              
1414             Required attributes: L, L, L file to test.
1415              
1416             Optional attributes: L, L.
1417              
1418             Parameter Description
1419             1 $gitHub GitHub object
1420              
1421             B
1422              
1423              
1424             my $g = gitHub;
1425             $g->gitFile = "test4.html";
1426             my $d = dateTimeStamp;
1427             $g->write($d);
1428              
1429             confess "exists FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1430              
1431             $g->delete;
1432              
1433             confess "exists FAILED" if $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1434              
1435             success "Exists passed";
1436              
1437              
1438             =head2 rename($gitHub, $target)
1439              
1440             Rename a source file on L if the target file name is not already in use.
1441              
1442             Required attributes: L, L, L, L = the file to be renamed.
1443              
1444             Optional attributes: L.
1445              
1446             Returns the new name of the file B if the rename was successful else B if the rename failed.
1447              
1448             Parameter Description
1449             1 $gitHub GitHub object
1450             2 $target The new name of the file
1451              
1452             B
1453              
1454              
1455             my ($f1, $f2) = qw(zzz.data zzz2.data);
1456             my $g = gitHub;
1457             $g->gitFile = $f2; $g->delete;
1458              
1459             my $d = dateTimeStamp;
1460             $g->gitFile = $f1;
1461             $g->write($d);
1462              
1463             confess "rename FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1464              
1465              
1466              
1467             $g->rename($f2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1468              
1469              
1470             confess "rename FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1471              
1472              
1473             $g->gitFile = $f2;
1474              
1475             confess "rename FAILED" if $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1476              
1477             success "Rename passed";
1478              
1479              
1480             =head2 delete($gitHub)
1481              
1482             Delete a file from L.
1483              
1484             Required attributes: L, L, L, L = the file to be deleted.
1485              
1486             Optional attributes: L.
1487              
1488             If the delete operation is successful, L is set to false otherwise it is set to true.
1489              
1490             Returns true if the delete was successful else false.
1491              
1492             Parameter Description
1493             1 $gitHub GitHub object
1494              
1495             B
1496              
1497              
1498             my $g = gitHub;
1499             my $d = dateTimeStamp;
1500             $g->gitFile = "zzz.data";
1501             $g->write($d);
1502              
1503              
1504             confess "delete FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1505              
1506              
1507             if (1)
1508             {my $t = time();
1509              
1510             my $d = $g->delete; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1511              
1512             lll "Delete 1: ", $d;
1513              
1514             lll "First delete: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1515              
1516              
1517             confess "delete FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1518              
1519             }
1520              
1521             if (1)
1522             {my $t = time();
1523              
1524             my $d = $g->delete; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1525              
1526             lll "Delete 1: ", $d;
1527              
1528             lll "Second delete: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1529              
1530              
1531             confess "delete FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1532              
1533             }
1534             success "Delete passed";
1535              
1536              
1537             =head1 Repositories
1538              
1539             Perform actions on L repositories.
1540              
1541             =head2 getRepository($gitHub)
1542              
1543             Get the overall details of a repository
1544              
1545             Parameter Description
1546             1 $gitHub GitHub object
1547              
1548             B
1549              
1550              
1551              
1552             my $r = gitHub(repository => q(C))->getRepository; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1553              
1554             success "Get repository succeeded";
1555              
1556              
1557             =head2 listCommits($gitHub)
1558              
1559             List all the commits in a L repository.
1560              
1561             Required attributes: L, L.
1562              
1563             Parameter Description
1564             1 $gitHub GitHub object
1565              
1566             B
1567              
1568              
1569              
1570             my $c = gitHub->listCommits; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1571              
1572             my %s = listCommitShas $c;
1573             lll "Commits
1574             ", dump $c;
1575             lll "Commit shas
1576             ", dump \%s;
1577             success "ListCommits passed";
1578              
1579              
1580             =head2 listCommitShas($commits)
1581              
1582             Create {commit name => sha} from the results of L.
1583              
1584             Parameter Description
1585             1 $commits Commits from L
1586              
1587             B
1588              
1589              
1590             my $c = gitHub->listCommits;
1591              
1592             my %s = listCommitShas $c; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1593              
1594             lll "Commits
1595             ", dump $c;
1596             lll "Commit shas
1597             ", dump \%s;
1598             success "ListCommits passed";
1599              
1600              
1601             =head2 writeCommit($gitHub, $folder, @files)
1602              
1603             Write all the files in a B<$folder> (or just the the named files) into a L repository in parallel as a commit on the specified branch.
1604              
1605             Required attributes: L, L, L.
1606              
1607             Parameter Description
1608             1 $gitHub GitHub object
1609             2 $folder File prefix to remove
1610             3 @files Files to write
1611              
1612             B
1613              
1614              
1615             my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
1616             my $c = dateTimeStamp; # Create some content
1617             my $if = q(/home/phil/.face); # Image file
1618              
1619             writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
1620             copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
1621              
1622             my $g = GitHub::Crud::new # Create GitHub
1623             (userid => q(philiprbrenan),
1624             repository => q(aaa),
1625             branch => q(test),
1626             confessOnFailure => 1);
1627              
1628             $g->loadPersonalAccessToken; # Load a personal access token
1629              
1630             $g->writeCommit($f); # Upload commit - confess to any errors # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1631              
1632              
1633             my $C = $g->read(q(data/1.txt)); # Read data written in commit
1634             my $I = $g->read(q(face.jpg));
1635             my $i = readBinaryFile $if;
1636              
1637             confess "Date stamp failed" unless $C eq $c; # Check text
1638             confess "Image failed" unless $i eq $I; # Check image
1639             success "Write commit succeeded";
1640              
1641              
1642             =head2 listWebHooks($gitHub)
1643              
1644             List web hooks associated with your L repository.
1645              
1646             Required: L, L, L.
1647              
1648             If the list operation is successful, L is set to false otherwise it is set to true.
1649              
1650             Returns true if the list operation was successful else false.
1651              
1652             Parameter Description
1653             1 $gitHub GitHub object
1654              
1655             B
1656              
1657              
1658              
1659             success join ' ', q(Webhooks:), dump(gitHub->listWebHooks); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1660              
1661              
1662              
1663             =head2 createPushWebHook($gitHub)
1664              
1665             Create a web hook for your L userid.
1666              
1667             Required: L, L, L, L.
1668              
1669             Optional: L.
1670              
1671             If the create operation is successful, L is set to false otherwise it is set to true.
1672              
1673             Returns true if the web hook was created successfully else false.
1674              
1675             Parameter Description
1676             1 $gitHub GitHub object
1677              
1678             B
1679              
1680              
1681             my $g = gitHub;
1682              
1683             my $d = $g->createPushWebHook; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1684              
1685             success join ' ', "Create web hook:", dump($d);
1686              
1687              
1688             =head2 listRepositories($gitHub)
1689              
1690             List the repositories accessible to a user on L.
1691              
1692             Required: L.
1693              
1694             Returns details of the repositories.
1695              
1696             Parameter Description
1697             1 $gitHub GitHub object
1698              
1699             B
1700              
1701              
1702              
1703             success "List repositories: ", dump(gitHub()->listRepositories); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1704              
1705              
1706              
1707             =head2 createRepository($gitHub)
1708              
1709             Create a repository on L.
1710              
1711             Required: L, L.
1712              
1713             Returns true if the issue was created successfully else false.
1714              
1715             Parameter Description
1716             1 $gitHub GitHub object
1717              
1718             B
1719              
1720              
1721              
1722             gitHub(repository => q(ccc))->createRepository; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1723              
1724             success "Create repository succeeded";
1725              
1726              
1727             =head2 createRepositoryFromSavedToken($userid, $repository, $private, $accessFolderOrToken)
1728              
1729             Create a repository on L using an access token either as supplied or saved in a file using L.
1730              
1731             Returns true if the issue was created successfully else false.
1732              
1733             Parameter Description
1734             1 $userid Userid on GitHub
1735             2 $repository The repository name
1736             3 $private True if the repo is private
1737             4 $accessFolderOrToken Location of access token.
1738              
1739             B
1740              
1741              
1742              
1743             createRepositoryFromSavedToken(q(philiprbrenan), q(ddd)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1744              
1745             success "Create repository succeeded";
1746              
1747              
1748             =head1 Issues
1749              
1750             Create issues on L.
1751              
1752             =head2 createIssue($gitHub)
1753              
1754             Create an issue on L.
1755              
1756             Required: L, L, L, L.
1757              
1758             If the operation is successful, L is set to false otherwise it is set to true.
1759              
1760             Returns true if the issue was created successfully else false.
1761              
1762             Parameter Description
1763             1 $gitHub GitHub object
1764              
1765             B
1766              
1767              
1768              
1769             gitHub(title=>q(Hello), body=>q(World))->createIssue; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1770              
1771             success "Create issue succeeded";
1772              
1773              
1774             =head1 Using saved access tokens
1775              
1776             Call methods directly using a saved access token rather than first creating a L description object and then calling methods using it. This is often more convenient if you just want to perform one or two actions.
1777              
1778             =head2 createIssueFromSavedToken($userid, $repository, $title, $body, $accessFolderOrToken)
1779              
1780             Create an issue on L using an access token as supplied or saved in a file using L.
1781              
1782             Returns true if the issue was created successfully else false.
1783              
1784             Parameter Description
1785             1 $userid Userid on GitHub
1786             2 $repository Repository name
1787             3 $title Issue title
1788             4 $body Issue body
1789             5 $accessFolderOrToken Location of access token.
1790              
1791             B
1792              
1793              
1794              
1795             &createIssueFromSavedToken(qw(philiprbrenan ddd hello World)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1796              
1797             success "Create issue succeeded";
1798              
1799              
1800             =head2 writeFileUsingSavedToken($userid, $repository, $file, $content, $accessFolderOrToken)
1801              
1802             Write to a file on L using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
1803              
1804             Parameter Description
1805             1 $userid Userid on GitHub
1806             2 $repository Repository name
1807             3 $file File name on github
1808             4 $content File content
1809             5 $accessFolderOrToken Location of access token.
1810              
1811             B
1812              
1813              
1814             my $s = q(HelloWorld);
1815              
1816             &writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1817              
1818             my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
1819              
1820             confess "Write file using saved token FAILED" unless $s eq $S;
1821             success "Write file using saved token succeeded";
1822              
1823              
1824             =head2 writeFileFromFileUsingSavedToken($userid, $repository, $file, $localFile, $accessFolderOrToken)
1825              
1826             Copy a file to L using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
1827              
1828             Parameter Description
1829             1 $userid Userid on GitHub
1830             2 $repository Repository name
1831             3 $file File name on github
1832             4 $localFile File content
1833             5 $accessFolderOrToken Location of access token.
1834              
1835             B
1836              
1837              
1838             my $f = writeFile(undef, my $s = "World
1839             ");
1840              
1841             &writeFileFromFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1842              
1843             my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
1844             confess "Write file from file using saved token FAILED" unless $s eq $S;
1845             success "Write file from file using saved token succeeded"
1846              
1847              
1848             =head2 readFileUsingSavedToken($userid, $repository, $file, $accessFolderOrToken)
1849              
1850             Read from a file on L using a personal access token as supplied or saved in a file. Return the content of the file on success or confess to any failure.
1851              
1852             Parameter Description
1853             1 $userid Userid on GitHub
1854             2 $repository Repository name
1855             3 $file File name on GitHub
1856             4 $accessFolderOrToken Location of access token.
1857              
1858             B
1859              
1860              
1861             my $s = q(Hello to the World);
1862             &writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s);
1863              
1864             my $S = &readFileUsingSavedToken (qw(philiprbrenan ddd hello.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1865              
1866              
1867             confess "Read file using saved token FAILED" unless $s eq $S;
1868             success "Read file using saved token succeeded"
1869              
1870              
1871             =head2 writeFolderUsingSavedToken($userid, $repository, $targetFolder, $localFolder, $accessFolderOrToken)
1872              
1873             Write all the files in a local folder to a target folder on a named L repository using a personal access token as supplied or saved in a file.
1874              
1875             Parameter Description
1876             1 $userid Userid on GitHub
1877             2 $repository Repository name
1878             3 $targetFolder Target folder on GitHub
1879             4 $localFolder Local folder name
1880             5 $accessFolderOrToken Location of access token.
1881              
1882             B
1883              
1884              
1885             writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/");
1886              
1887             writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1888              
1889              
1890              
1891             =head2 writeCommitUsingSavedToken($userid, $repository, $source, $accessFolderOrToken)
1892              
1893             Write all the files in a local folder to a named L repository using a personal access token as supplied or saved in a file.
1894              
1895             Parameter Description
1896             1 $userid Userid on GitHub
1897             2 $repository Repository name
1898             3 $source Local folder on GitHub
1899             4 $accessFolderOrToken Optionally: location of access token.
1900              
1901             B
1902              
1903              
1904              
1905             writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1906              
1907             writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/");
1908              
1909              
1910             =head2 deleteFileUsingSavedToken($userid, $repository, $target, $accessFolderOrToken)
1911              
1912             Delete a file on L using a saved token
1913              
1914             Parameter Description
1915             1 $userid Userid on GitHub
1916             2 $repository Repository name
1917             3 $target File on GitHub
1918             4 $accessFolderOrToken Optional: the folder containing saved access tokens.
1919              
1920             B
1921              
1922              
1923              
1924             deleteFileUsingSavedToken("philiprbrenan", "test", "aaa.data"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1925              
1926              
1927              
1928             =head2 getRepositoryUsingSavedToken($userid, $repository, $accessFolderOrToken)
1929              
1930             Get repository details from L using a saved token
1931              
1932             Parameter Description
1933             1 $userid Userid on GitHub
1934             2 $repository Repository name
1935             3 $accessFolderOrToken Optionally: location of access token.
1936              
1937             B
1938              
1939              
1940              
1941             my $r = getRepositoryUsingSavedToken(q(philiprbrenan), q(aaa)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1942              
1943             success "Get repository using saved access token succeeded";
1944              
1945              
1946             =head2 getRepositoryUpdatedAtUsingSavedToken($userid, $repository, $accessFolderOrToken)
1947              
1948             Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
1949              
1950             Parameter Description
1951             1 $userid Userid on GitHub
1952             2 $repository Repository name
1953             3 $accessFolderOrToken Optionally: location of access token.
1954              
1955             B
1956              
1957              
1958              
1959             my $u = getRepositoryUpdatedAtUsingSavedToken(q(philiprbrenan), q(aaa)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1960              
1961             success "Get repository updated_at field succeeded";
1962              
1963              
1964             =head1 Actions
1965              
1966             Perform an action against the current repository while running as a L action. If such an action requires a security token please supply the token as shown in at the end of: L.
1967              
1968             =head2 createIssueInCurrentRepo($title, $body)
1969              
1970             Create an issue in the current L repository if we are running on L.
1971              
1972             Parameter Description
1973             1 $title Title of issue
1974             2 $body Body of issue
1975              
1976             B
1977              
1978              
1979              
1980             createIssueInCurrentRepo("Hello World", "Need to run Hello World"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1981              
1982              
1983             writeFileFromCurrentRun("output.text", "Hello World");
1984             writeFileFromFileFromCurrentRun("output.txt");
1985             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
1986              
1987              
1988             =head2 writeFileFromCurrentRun($target, $text)
1989              
1990             Write test into a file in the current L repository if we are running on L.
1991              
1992             Parameter Description
1993             1 $target The target file name in the repo
1994             2 $text The text to write into this file
1995              
1996             B
1997              
1998              
1999             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2000              
2001              
2002             writeFileFromCurrentRun("output.text", "Hello World"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2003              
2004             writeFileFromFileFromCurrentRun("output.txt");
2005             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
2006              
2007              
2008             =head2 writeFileFromFileFromCurrentRun($target)
2009              
2010             Write to a file in the current L repository by copying a local file if we are running on L.
2011              
2012             Parameter Description
2013             1 $target File name both locally and in the repo
2014              
2015             B
2016              
2017              
2018             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2019              
2020             writeFileFromCurrentRun("output.text", "Hello World");
2021              
2022             writeFileFromFileFromCurrentRun("output.txt"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2023              
2024             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
2025              
2026              
2027             =head2 writeBinaryFileFromFileInCurrentRun($target, $source)
2028              
2029             Write to a file in the current L repository by copying a local binary file if we are running on L.
2030              
2031             Parameter Description
2032             1 $target The target file name in the repo
2033             2 $source The current file name in the run
2034              
2035             B
2036              
2037              
2038             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2039              
2040             writeFileFromCurrentRun("output.text", "Hello World");
2041             writeFileFromFileFromCurrentRun("output.txt");
2042              
2043             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2044              
2045              
2046              
2047             =head1 Access tokens
2048              
2049             Load and save access tokens. Some L requests must be signed with an L access token. These methods help you store and reuse such tokens. Access tokens can be created at: L.
2050              
2051             =head2 savePersonalAccessToken($gitHub)
2052              
2053             Save a L personal access token by userid in folder L.
2054              
2055             Parameter Description
2056             1 $gitHub GitHub object
2057              
2058             B
2059              
2060              
2061             my $d = temporaryFolder;
2062             my $t = join '', 1..20;
2063              
2064             my $g = gitHub
2065             (userid => q(philiprbrenan),
2066             personalAccessToken => $t,
2067             personalAccessTokenFolder => $d,
2068             );
2069              
2070              
2071             $g->savePersonalAccessToken; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2072              
2073             my $T = $g->loadPersonalAccessToken;
2074              
2075             confess "Load/Save token FAILED" unless $t eq $T;
2076             success "Load/Save token succeeded"
2077              
2078              
2079             =head2 loadPersonalAccessToken($gitHub)
2080              
2081             Load a personal access token by userid from folder L.
2082              
2083             Parameter Description
2084             1 $gitHub GitHub object
2085              
2086             B
2087              
2088              
2089             my $d = temporaryFolder;
2090             my $t = join '', 1..20;
2091              
2092             my $g = gitHub
2093             (userid => q(philiprbrenan),
2094             personalAccessToken => $t,
2095             personalAccessTokenFolder => $d,
2096             );
2097              
2098             $g->savePersonalAccessToken;
2099              
2100             my $T = $g->loadPersonalAccessToken; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2101              
2102              
2103             confess "Load/Save token FAILED" unless $t eq $T;
2104             success "Load/Save token succeeded"
2105              
2106              
2107              
2108             =head1 Hash Definitions
2109              
2110              
2111              
2112              
2113             =head2 GitHub::Crud Definition
2114              
2115              
2116             Attributes describing the interface with L.
2117              
2118              
2119              
2120              
2121             =head3 Input fields
2122              
2123              
2124             =head4 body
2125              
2126             The body of an issue.
2127              
2128             =head4 branch
2129              
2130             Branch name (you should create this branch first) or omit it for the default branch which is usually 'master'.
2131              
2132             =head4 confessOnFailure
2133              
2134             Confess to any failures
2135              
2136             =head4 gitFile
2137              
2138             File name on L - this name can contain '/'. This is the file to be read from, written to, copied from, checked for existence or deleted.
2139              
2140             =head4 gitFolder
2141              
2142             Folder name on L - this name can contain '/'.
2143              
2144             =head4 message
2145              
2146             Optional commit message
2147              
2148             =head4 nonRecursive
2149              
2150             Fetch only one level of files with L.
2151              
2152             =head4 personalAccessToken
2153              
2154             A personal access token with scope "public_repo" as generated on page: https://github.com/settings/tokens.
2155              
2156             =head4 personalAccessTokenFolder
2157              
2158             The folder into which to save personal access tokens. Set to q(/etc/GitHubCrudPersonalAccessToken) by default.
2159              
2160             =head4 private
2161              
2162             Whether the repository being created should be private or not.
2163              
2164             =head4 repository
2165              
2166             The name of the repository to be worked on minus the userid - you should create this repository first manually.
2167              
2168             =head4 secret
2169              
2170             The secret for a web hook - this is created by the creator of the web hook and remembered by L,
2171              
2172             =head4 title
2173              
2174             The title of an issue.
2175              
2176             =head4 userid
2177              
2178             Userid on L of the repository to be worked on.
2179              
2180             =head4 webHookUrl
2181              
2182             The url for a web hook.
2183              
2184              
2185              
2186             =head3 Output fields
2187              
2188              
2189             =head4 failed
2190              
2191             Defined if the last request to L failed else B.
2192              
2193             =head4 fileList
2194              
2195             Reference to an array of files produced by L.
2196              
2197             =head4 readData
2198              
2199             Data produced by L.
2200              
2201             =head4 response
2202              
2203             A reference to L's response to the latest request.
2204              
2205              
2206              
2207             =head2 GitHub::Crud::Response Definition
2208              
2209              
2210             Attributes describing a response from L.
2211              
2212              
2213              
2214              
2215             =head3 Output fields
2216              
2217              
2218             =head4 content
2219              
2220             The actual content of the file from L.
2221              
2222             =head4 data
2223              
2224             The data received from L, normally in L format.
2225              
2226             =head4 status
2227              
2228             Our version of Status.
2229              
2230              
2231              
2232             =head1 Private Methods
2233              
2234             =head2 specialFileData($d)
2235              
2236             Do not encode or decode data with a known file signature
2237              
2238             Parameter Description
2239             1 $d String to check
2240              
2241             =head2 currentRepo()
2242              
2243             Create a L object for the current repo if we are on L actions
2244              
2245              
2246              
2247             =head1 Index
2248              
2249              
2250             1 L - Copy a source file from one location to another target location in your L repository, overwriting the target file if it already exists.
2251              
2252             2 L - Create an issue on L.
2253              
2254             3 L - Create an issue on L using an access token as supplied or saved in a file using L.
2255              
2256             4 L - Create an issue in the current L repository if we are running on L.
2257              
2258             5 L - Create a web hook for your L userid.
2259              
2260             6 L - Create a repository on L.
2261              
2262             7 L - Create a repository on L using an access token either as supplied or saved in a file using L.
2263              
2264             8 L - Create a L object for the current repo if we are on L actions
2265              
2266             9 L - Delete a file from L.
2267              
2268             10 L - Delete a file on L using a saved token
2269              
2270             11 L - Test whether a file exists on L or not and returns an object including the B and B fields if it does else L.
2271              
2272             12 L - Get the overall details of a repository
2273              
2274             13 L - Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
2275              
2276             14 L - Get repository details from L using a saved token
2277              
2278             15 L - List all the files contained in a L repository or all the files below a specified folder in the repository.
2279              
2280             16 L - List all the commits in a L repository.
2281              
2282             17 L - Create {commit name => sha} from the results of L.
2283              
2284             18 L - List the repositories accessible to a user on L.
2285              
2286             19 L - List web hooks associated with your L repository.
2287              
2288             20 L - Load a personal access token by userid from folder L.
2289              
2290             21 L - Create a new L object with attributes as described at: L.
2291              
2292             22 L - Read data from a file on L.
2293              
2294             23 L - Read a L from L.
2295              
2296             24 L - Read from a file on L using a personal access token as supplied or saved in a file.
2297              
2298             25 L - Rename a source file on L if the target file name is not already in use.
2299              
2300             26 L - Save a L personal access token by userid in folder L.
2301              
2302             27 L - Do not encode or decode data with a known file signature
2303              
2304             28 L - Write utf8 data into a L file.
2305              
2306             29 L - Write to a file in the current L repository by copying a local binary file if we are running on L.
2307              
2308             30 L - Write data into a L as a L that can be referenced by future commits.
2309              
2310             31 L - Write all the files in a B<$folder> (or just the the named files) into a L repository in parallel as a commit on the specified branch.
2311              
2312             32 L - Write all the files in a local folder to a named L repository using a personal access token as supplied or saved in a file.
2313              
2314             33 L - Write test into a file in the current L repository if we are running on L.
2315              
2316             34 L - Write to a file in the current L repository by copying a local file if we are running on L.
2317              
2318             35 L - Copy a file to L using a personal access token as supplied or saved in a file.
2319              
2320             36 L - Write to a file on L using a personal access token as supplied or saved in a file.
2321              
2322             37 L - Write all the files in a local folder to a target folder on a named L repository using a personal access token as supplied or saved in a file.
2323              
2324             =head1 Installation
2325              
2326             This module is written in 100% Pure Perl and, thus, it is easy to read,
2327             comprehend, use, modify and install via B:
2328              
2329             sudo cpan install GitHub::Crud
2330              
2331             =head1 Author
2332              
2333             L
2334              
2335             L
2336              
2337             =head1 Copyright
2338              
2339             Copyright (c) 2016-2021 Philip R Brenan.
2340              
2341             This module is free software. It may be used, redistributed and/or modified
2342             under the same terms as Perl itself.
2343              
2344             =cut
2345              
2346              
2347              
2348             # Tests and documentation
2349              
2350             sub test
2351 1     1 0 12 {my $p = __PACKAGE__;
2352 1         23 binmode($_, ":utf8") for *STDOUT, *STDERR;
2353 1 50       62 return if eval "eof(${p}::DATA)";
2354 1         59 my $s = eval "join('', <${p}::DATA>)";
2355 1 50       11 $@ and die $@;
2356 1     1 0 671 eval $s;
  1     0   68190  
  1         9  
  1         74  
  0            
2357 1 50       7 $@ and die $@;
2358 1         126 1
2359             }
2360              
2361             test unless caller;
2362              
2363             1;
2364             #podDocumentation
2365             __DATA__