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   2066 use v5.16;
  1         12  
10             our $VERSION = 20230930;
11 1     1   6 use warnings FATAL => qw(all);
  1         2  
  1         34  
12 1     1   5 use strict;
  1         2  
  1         21  
13 1     1   17 use Carp qw(confess);
  1         2  
  1         80  
14 1     1   1419 use Data::Dump qw(dump);
  1         8491  
  1         100  
15 1     1   6764 use Data::Table::Text qw(:all !fileList);
  1         152677  
  1         1907  
16 1     1   1496 use Digest::SHA1 qw(sha1_hex);
  1         1799  
  1         67  
17             #use Date::Manip;
18 1     1   7 use Scalar::Util qw(blessed reftype looks_like_number);
  1         4  
  1         57  
19 1     1   7 use Time::HiRes qw(time);
  1         2  
  1         9  
20 1     1   750 use Encode qw(encode decode);
  1         10736  
  1         69  
21 1     1   8 use utf8; # To allow utf8 constants for testing
  1         3  
  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   3024 {use Encode 'encode';
  1         2  
  1         6266  
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   9 use Exporter qw(import);
  1         2  
  1         90  
1053              
1054 1     1   8 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         511  
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             The following code will install a personal access token for github userid
1152             B in the default location:
1153              
1154             use GitHub::Crud qw(:all);
1155            
1156             my $g = GitHub::Crud::gitHub
1157             (userid => q(uuuuuu),
1158             personalAccessToken => q(........................................),
1159             # personalAccessTokenFolder => $d,
1160             );
1161              
1162             $g->savePersonalAccessToken;
1163              
1164             =head1 Description
1165              
1166             Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
1167              
1168              
1169             Version 20230930.
1170              
1171              
1172             The following sections describe the methods in each functional area of this
1173             module. For an alphabetic listing of all methods by name see L.
1174              
1175              
1176              
1177             =head1 Constructor
1178              
1179             Create a L object with the specified attributes describing the interface with L.
1180              
1181             =head2 new(%attributes)
1182              
1183             Create a new L object with attributes as described at: L.
1184              
1185             Parameter Description
1186             1 %attributes Attribute values
1187              
1188             B
1189              
1190              
1191             my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
1192             my $c = dateTimeStamp; # Create some content
1193             my $if = q(/home/phil/.face); # Image file
1194            
1195             writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
1196             copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
1197            
1198            
1199             my $g = GitHub::Crud::new # Create GitHub # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1200              
1201             (userid => q(philiprbrenan),
1202             repository => q(aaa),
1203             branch => q(test),
1204             confessOnFailure => 1);
1205            
1206             $g->loadPersonalAccessToken; # Load a personal access token
1207             $g->writeCommit($f); # Upload commit - confess to any errors
1208            
1209             my $C = $g->read(q(data/1.txt)); # Read data written in commit
1210             my $I = $g->read(q(face.jpg));
1211             my $i = readBinaryFile $if;
1212            
1213             confess "Date stamp failed" unless $C eq $c; # Check text
1214             confess "Image failed" unless $i eq $I; # Check image
1215             success "Write commit succeeded";
1216            
1217              
1218             =head1 Files
1219              
1220             File actions on the contents of L repositories.
1221              
1222             =head2 list($gitHub)
1223              
1224             List all the files contained in a L repository or all the files below a specified folder in the repository.
1225              
1226             Required attributes: L, L.
1227              
1228             Optional attributes: L, L, L, L.
1229              
1230             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.
1231              
1232             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.
1233              
1234             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.
1235              
1236             If the list operation fails then L is set to true and L is set to refer to an empty array.
1237              
1238             Returns the list of file names found or empty list if no files were found.
1239              
1240             Parameter Description
1241             1 $gitHub GitHub
1242              
1243             B
1244              
1245              
1246            
1247             success "list:", gitHub->list; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1248              
1249            
1250             # list: alpha.data .github/workflows/test.yaml images/aaa.txt images/aaa/bbb.txt # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1251              
1252            
1253              
1254             =head2 read($gitHub, $File)
1255              
1256             Read data from a file on L.
1257              
1258             Required attributes: L, L.
1259              
1260             Optional attributes: L = the file to read, L, L.
1261              
1262             If the read operation is successful, L is set to false and L is set to the data read from the file.
1263              
1264             If the read operation fails then L is set to true and L is set to B.
1265              
1266             Returns the data read or B if no file was found.
1267              
1268             Parameter Description
1269             1 $gitHub GitHub
1270             2 $File File to read if not specified in gitFile
1271              
1272             B
1273              
1274              
1275             my $g = gitHub;
1276             $g->gitFile = my $f = q(z'2 'z"z.data);
1277             my $d = q(𝝰𝝱𝝲);
1278             $g->write($d);
1279            
1280             confess "read FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1281              
1282             success "Read passed";
1283            
1284              
1285             =head2 write($gitHub, $data, $File)
1286              
1287             Write utf8 data into a L file.
1288              
1289             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.
1290              
1291             Parameter Description
1292             1 $gitHub GitHub object
1293             2 $data Data to be written
1294             3 $File Optionally the name of the file on github
1295              
1296             B
1297              
1298              
1299             my $g = gitHub;
1300             $g->gitFile = "zzz.data";
1301            
1302             my $d = dateTimeStamp.q( 𝝰𝝱𝝲);
1303            
1304             if (1)
1305             {my $t = time();
1306            
1307             $g->write($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1308              
1309            
1310             lll "First write time: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1311              
1312             }
1313            
1314             my $r = $g->read;
1315             lll "Write bbb: $r";
1316             if (1)
1317             {my $t = time();
1318            
1319             $g->write($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1320              
1321            
1322             lll "Second write time: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1323              
1324             }
1325            
1326             confess "write FAILED" unless $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1327              
1328             success "Write passed";
1329            
1330              
1331             =head2 readBlob($gitHub, $sha)
1332              
1333             Read a L from L.
1334              
1335             Required attributes: L, L, L. Returns the content of the L identified by the specified L.
1336              
1337             Parameter Description
1338             1 $gitHub GitHub object
1339             2 $sha Data to be written
1340              
1341             B
1342              
1343              
1344             my $g = gitHub;
1345             $g->gitFile = "face.jpg";
1346             my $d = readBinaryFile(q(/home/phil/.face));
1347             my $s = $g->writeBlob($d);
1348             my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
1349             confess q(Write blob FAILED) unless $s eq $S;
1350            
1351            
1352             my $D = $g->readBlob($s); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1353              
1354             confess q(Write/Read blob FAILED) unless $d eq $D;
1355             success q(Write/Read blob passed);
1356            
1357              
1358             =head2 writeBlob($gitHub, $data)
1359              
1360             Write data into a L as a L that can be referenced by future commits.
1361              
1362             Required attributes: L, L, L. Returns the L of the created L or L in a failure occurred.
1363              
1364             Parameter Description
1365             1 $gitHub GitHub object
1366             2 $data Data to be written
1367              
1368             B
1369              
1370              
1371             my $g = gitHub;
1372             $g->gitFile = "face.jpg";
1373             my $d = readBinaryFile(q(/home/phil/.face));
1374            
1375             my $s = $g->writeBlob($d); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1376              
1377             my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
1378             confess q(Write blob FAILED) unless $s eq $S;
1379            
1380             my $D = $g->readBlob($s);
1381             confess q(Write/Read blob FAILED) unless $d eq $D;
1382             success q(Write/Read blob passed);
1383            
1384              
1385             =head2 copy($gitHub, $target)
1386              
1387             Copy a source file from one location to another target location in your L repository, overwriting the target file if it already exists.
1388              
1389             Required attributes: L, L, L, L = the file to be copied.
1390              
1391             Optional attributes: L.
1392              
1393             If the write operation is successful, L is set to false otherwise it is set to true.
1394              
1395             Returns B if the write updated the file, B if the write created the file else B if the write failed.
1396              
1397             Parameter Description
1398             1 $gitHub GitHub object
1399             2 $target The name of the file to be created
1400              
1401             B
1402              
1403              
1404             my ($f1, $f2) = ("zzz.data", "zzz2.data");
1405             my $g = gitHub;
1406             $g->gitFile = $f2; $g->delete;
1407             $g->gitFile = $f1;
1408             my $d = dateTimeStamp;
1409             my $w = $g->write($d);
1410            
1411             my $r = $g->copy($f2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1412              
1413             lll "Copy created: $r";
1414             $g->gitFile = $f2;
1415             my $D = $g->read;
1416             lll "Read ccc: $D";
1417            
1418             confess "copy FAILED" unless $d eq $D; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1419              
1420             success "Copy passed"
1421            
1422              
1423             =head2 exists($gitHub)
1424              
1425             Test whether a file exists on L or not and returns an object including the B and B fields if it does else L.
1426              
1427             Required attributes: L, L, L file to test.
1428              
1429             Optional attributes: L, L.
1430              
1431             Parameter Description
1432             1 $gitHub GitHub object
1433              
1434             B
1435              
1436              
1437             my $g = gitHub;
1438             $g->gitFile = "test4.html";
1439             my $d = dateTimeStamp;
1440             $g->write($d);
1441            
1442             confess "exists FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1443              
1444             $g->delete;
1445            
1446             confess "exists FAILED" if $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1447              
1448             success "Exists passed";
1449            
1450              
1451             =head2 rename($gitHub, $target)
1452              
1453             Rename a source file on L if the target file name is not already in use.
1454              
1455             Required attributes: L, L, L, L = the file to be renamed.
1456              
1457             Optional attributes: L.
1458              
1459             Returns the new name of the file B if the rename was successful else B if the rename failed.
1460              
1461             Parameter Description
1462             1 $gitHub GitHub object
1463             2 $target The new name of the file
1464              
1465             B
1466              
1467              
1468             my ($f1, $f2) = qw(zzz.data zzz2.data);
1469             my $g = gitHub;
1470             $g->gitFile = $f2; $g->delete;
1471            
1472             my $d = dateTimeStamp;
1473             $g->gitFile = $f1;
1474             $g->write($d);
1475            
1476             confess "rename FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1477              
1478            
1479            
1480             $g->rename($f2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1481              
1482            
1483             confess "rename FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1484              
1485            
1486             $g->gitFile = $f2;
1487            
1488             confess "rename FAILED" if $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1489              
1490             success "Rename passed";
1491            
1492              
1493             =head2 delete($gitHub)
1494              
1495             Delete a file from L.
1496              
1497             Required attributes: L, L, L, L = the file to be deleted.
1498              
1499             Optional attributes: L.
1500              
1501             If the delete operation is successful, L is set to false otherwise it is set to true.
1502              
1503             Returns true if the delete was successful else false.
1504              
1505             Parameter Description
1506             1 $gitHub GitHub object
1507              
1508             B
1509              
1510              
1511             my $g = gitHub;
1512             my $d = dateTimeStamp;
1513             $g->gitFile = "zzz.data";
1514             $g->write($d);
1515            
1516            
1517             confess "delete FAILED" unless $g->read eq $d; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1518              
1519            
1520             if (1)
1521             {my $t = time();
1522            
1523             my $d = $g->delete; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1524              
1525             lll "Delete 1: ", $d;
1526            
1527             lll "First delete: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1528              
1529            
1530             confess "delete FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1531              
1532             }
1533            
1534             if (1)
1535             {my $t = time();
1536            
1537             my $d = $g->delete; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1538              
1539             lll "Delete 1: ", $d;
1540            
1541             lll "Second delete: ", time() - $t; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1542              
1543            
1544             confess "delete FAILED" if $g->exists; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1545              
1546             }
1547             success "Delete passed";
1548            
1549              
1550             =head1 Repositories
1551              
1552             Perform actions on L repositories.
1553              
1554             =head2 getRepository($gitHub)
1555              
1556             Get the overall details of a repository
1557              
1558             Parameter Description
1559             1 $gitHub GitHub object
1560              
1561             B
1562              
1563              
1564            
1565             my $r = gitHub(repository => q(C))->getRepository; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1566              
1567             success "Get repository succeeded";
1568            
1569              
1570             =head2 listCommits($gitHub)
1571              
1572             List all the commits in a L repository.
1573              
1574             Required attributes: L, L.
1575              
1576             Parameter Description
1577             1 $gitHub GitHub object
1578              
1579             B
1580              
1581              
1582            
1583             my $c = gitHub->listCommits; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1584              
1585             my %s = listCommitShas $c;
1586             lll "Commits
1587             ", dump $c;
1588             lll "Commit shas
1589             ", dump \%s;
1590             success "ListCommits passed";
1591            
1592              
1593             =head2 listCommitShas($commits)
1594              
1595             Create {commit name => sha} from the results of L.
1596              
1597             Parameter Description
1598             1 $commits Commits from L
1599              
1600             B
1601              
1602              
1603             my $c = gitHub->listCommits;
1604            
1605             my %s = listCommitShas $c; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1606              
1607             lll "Commits
1608             ", dump $c;
1609             lll "Commit shas
1610             ", dump \%s;
1611             success "ListCommits passed";
1612            
1613              
1614             =head2 writeCommit($gitHub, $folder, @files)
1615              
1616             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.
1617              
1618             Required attributes: L, L, L.
1619              
1620             Parameter Description
1621             1 $gitHub GitHub object
1622             2 $folder File prefix to remove
1623             3 @files Files to write
1624              
1625             B
1626              
1627              
1628             my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
1629             my $c = dateTimeStamp; # Create some content
1630             my $if = q(/home/phil/.face); # Image file
1631            
1632             writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
1633             copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
1634            
1635             my $g = GitHub::Crud::new # Create GitHub
1636             (userid => q(philiprbrenan),
1637             repository => q(aaa),
1638             branch => q(test),
1639             confessOnFailure => 1);
1640            
1641             $g->loadPersonalAccessToken; # Load a personal access token
1642            
1643             $g->writeCommit($f); # Upload commit - confess to any errors # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1644              
1645            
1646             my $C = $g->read(q(data/1.txt)); # Read data written in commit
1647             my $I = $g->read(q(face.jpg));
1648             my $i = readBinaryFile $if;
1649            
1650             confess "Date stamp failed" unless $C eq $c; # Check text
1651             confess "Image failed" unless $i eq $I; # Check image
1652             success "Write commit succeeded";
1653            
1654              
1655             =head2 listWebHooks($gitHub)
1656              
1657             List web hooks associated with your L repository.
1658              
1659             Required: L, L, L.
1660              
1661             If the list operation is successful, L is set to false otherwise it is set to true.
1662              
1663             Returns true if the list operation was successful else false.
1664              
1665             Parameter Description
1666             1 $gitHub GitHub object
1667              
1668             B
1669              
1670              
1671            
1672             success join ' ', q(Webhooks:), dump(gitHub->listWebHooks); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1673              
1674            
1675              
1676             =head2 createPushWebHook($gitHub)
1677              
1678             Create a web hook for your L userid.
1679              
1680             Required: L, L, L, L.
1681              
1682             Optional: L.
1683              
1684             If the create operation is successful, L is set to false otherwise it is set to true.
1685              
1686             Returns true if the web hook was created successfully else false.
1687              
1688             Parameter Description
1689             1 $gitHub GitHub object
1690              
1691             B
1692              
1693              
1694             my $g = gitHub;
1695            
1696             my $d = $g->createPushWebHook; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1697              
1698             success join ' ', "Create web hook:", dump($d);
1699            
1700              
1701             =head2 listRepositories($gitHub)
1702              
1703             List the repositories accessible to a user on L.
1704              
1705             Required: L.
1706              
1707             Returns details of the repositories.
1708              
1709             Parameter Description
1710             1 $gitHub GitHub object
1711              
1712             B
1713              
1714              
1715            
1716             success "List repositories: ", dump(gitHub()->listRepositories); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1717              
1718            
1719              
1720             =head2 createRepository($gitHub)
1721              
1722             Create a repository on L.
1723              
1724             Required: L, L.
1725              
1726             Returns true if the issue was created successfully else false.
1727              
1728             Parameter Description
1729             1 $gitHub GitHub object
1730              
1731             B
1732              
1733              
1734            
1735             gitHub(repository => q(ccc))->createRepository; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1736              
1737             success "Create repository succeeded";
1738            
1739              
1740             =head2 createRepositoryFromSavedToken($userid, $repository, $private, $accessFolderOrToken)
1741              
1742             Create a repository on L using an access token either as supplied or saved in a file using L.
1743              
1744             Returns true if the issue was created successfully else false.
1745              
1746             Parameter Description
1747             1 $userid Userid on GitHub
1748             2 $repository The repository name
1749             3 $private True if the repo is private
1750             4 $accessFolderOrToken Location of access token.
1751              
1752             B
1753              
1754              
1755            
1756             createRepositoryFromSavedToken(q(philiprbrenan), q(ddd)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1757              
1758             success "Create repository succeeded";
1759            
1760              
1761             =head1 Issues
1762              
1763             Create issues on L.
1764              
1765             =head2 createIssue($gitHub)
1766              
1767             Create an issue on L.
1768              
1769             Required: L, L, L, L.
1770              
1771             If the operation is successful, L is set to false otherwise it is set to true.
1772              
1773             Returns true if the issue was created successfully else false.
1774              
1775             Parameter Description
1776             1 $gitHub GitHub object
1777              
1778             B
1779              
1780              
1781            
1782             gitHub(title=>q(Hello), body=>q(World))->createIssue; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1783              
1784             success "Create issue succeeded";
1785            
1786              
1787             =head1 Using saved access tokens
1788              
1789             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.
1790              
1791             =head2 createIssueFromSavedToken($userid, $repository, $title, $body, $accessFolderOrToken)
1792              
1793             Create an issue on L using an access token as supplied or saved in a file using L.
1794              
1795             Returns true if the issue was created successfully else false.
1796              
1797             Parameter Description
1798             1 $userid Userid on GitHub
1799             2 $repository Repository name
1800             3 $title Issue title
1801             4 $body Issue body
1802             5 $accessFolderOrToken Location of access token.
1803              
1804             B
1805              
1806              
1807            
1808             &createIssueFromSavedToken(qw(philiprbrenan ddd hello World)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1809              
1810             success "Create issue succeeded";
1811            
1812              
1813             =head2 writeFileUsingSavedToken($userid, $repository, $file, $content, $accessFolderOrToken)
1814              
1815             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.
1816              
1817             Parameter Description
1818             1 $userid Userid on GitHub
1819             2 $repository Repository name
1820             3 $file File name on github
1821             4 $content File content
1822             5 $accessFolderOrToken Location of access token.
1823              
1824             B
1825              
1826              
1827             my $s = q(HelloWorld);
1828            
1829             &writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1830              
1831             my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
1832            
1833             confess "Write file using saved token FAILED" unless $s eq $S;
1834             success "Write file using saved token succeeded";
1835            
1836              
1837             =head2 writeFileFromFileUsingSavedToken($userid, $repository, $file, $localFile, $accessFolderOrToken)
1838              
1839             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.
1840              
1841             Parameter Description
1842             1 $userid Userid on GitHub
1843             2 $repository Repository name
1844             3 $file File name on github
1845             4 $localFile File content
1846             5 $accessFolderOrToken Location of access token.
1847              
1848             B
1849              
1850              
1851             my $f = writeFile(undef, my $s = "World
1852             ");
1853            
1854             &writeFileFromFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $f); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1855              
1856             my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
1857             confess "Write file from file using saved token FAILED" unless $s eq $S;
1858             success "Write file from file using saved token succeeded"
1859            
1860              
1861             =head2 readFileUsingSavedToken($userid, $repository, $file, $accessFolderOrToken)
1862              
1863             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.
1864              
1865             Parameter Description
1866             1 $userid Userid on GitHub
1867             2 $repository Repository name
1868             3 $file File name on GitHub
1869             4 $accessFolderOrToken Location of access token.
1870              
1871             B
1872              
1873              
1874             my $s = q(Hello to the World);
1875             &writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s);
1876            
1877             my $S = &readFileUsingSavedToken (qw(philiprbrenan ddd hello.txt)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1878              
1879            
1880             confess "Read file using saved token FAILED" unless $s eq $S;
1881             success "Read file using saved token succeeded"
1882            
1883              
1884             =head2 writeFolderUsingSavedToken($userid, $repository, $targetFolder, $localFolder, $accessFolderOrToken)
1885              
1886             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.
1887              
1888             Parameter Description
1889             1 $userid Userid on GitHub
1890             2 $repository Repository name
1891             3 $targetFolder Target folder on GitHub
1892             4 $localFolder Local folder name
1893             5 $accessFolderOrToken Location of access token.
1894              
1895             B
1896              
1897              
1898             writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/");
1899            
1900             writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1901              
1902            
1903              
1904             =head2 writeCommitUsingSavedToken($userid, $repository, $source, $accessFolderOrToken)
1905              
1906             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.
1907              
1908             Parameter Description
1909             1 $userid Userid on GitHub
1910             2 $repository Repository name
1911             3 $source Local folder on GitHub
1912             4 $accessFolderOrToken Optionally: location of access token.
1913              
1914             B
1915              
1916              
1917            
1918             writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1919              
1920             writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/");
1921            
1922              
1923             =head2 deleteFileUsingSavedToken($userid, $repository, $target, $accessFolderOrToken)
1924              
1925             Delete a file on L using a saved token
1926              
1927             Parameter Description
1928             1 $userid Userid on GitHub
1929             2 $repository Repository name
1930             3 $target File on GitHub
1931             4 $accessFolderOrToken Optional: the folder containing saved access tokens.
1932              
1933             B
1934              
1935              
1936            
1937             deleteFileUsingSavedToken("philiprbrenan", "test", "aaa.data"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1938              
1939            
1940              
1941             =head2 getRepositoryUsingSavedToken($userid, $repository, $accessFolderOrToken)
1942              
1943             Get repository details from L using a saved token
1944              
1945             Parameter Description
1946             1 $userid Userid on GitHub
1947             2 $repository Repository name
1948             3 $accessFolderOrToken Optionally: location of access token.
1949              
1950             B
1951              
1952              
1953            
1954             my $r = getRepositoryUsingSavedToken(q(philiprbrenan), q(aaa)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1955              
1956             success "Get repository using saved access token succeeded";
1957            
1958              
1959             =head2 getRepositoryUpdatedAtUsingSavedToken($userid, $repository, $accessFolderOrToken)
1960              
1961             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.
1962              
1963             Parameter Description
1964             1 $userid Userid on GitHub
1965             2 $repository Repository name
1966             3 $accessFolderOrToken Optionally: location of access token.
1967              
1968             B
1969              
1970              
1971            
1972             my $u = getRepositoryUpdatedAtUsingSavedToken(q(philiprbrenan), q(aaa)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1973              
1974             success "Get repository updated_at field succeeded";
1975            
1976              
1977             =head1 Actions
1978              
1979             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.
1980              
1981             =head2 createIssueInCurrentRepo($title, $body)
1982              
1983             Create an issue in the current L repository if we are running on L.
1984              
1985             Parameter Description
1986             1 $title Title of issue
1987             2 $body Body of issue
1988              
1989             B
1990              
1991              
1992            
1993             createIssueInCurrentRepo("Hello World", "Need to run Hello World"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
1994              
1995            
1996             writeFileFromCurrentRun("output.text", "Hello World");
1997             writeFileFromFileFromCurrentRun("output.txt");
1998             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
1999            
2000              
2001             =head2 writeFileFromCurrentRun($target, $text)
2002              
2003             Write text into a file in the current L repository if we are running on L.
2004              
2005             Parameter Description
2006             1 $target The target file name in the repo
2007             2 $text The text to write into this file
2008              
2009             B
2010              
2011              
2012             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2013            
2014            
2015             writeFileFromCurrentRun("output.text", "Hello World"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2016              
2017             writeFileFromFileFromCurrentRun("output.txt");
2018             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
2019            
2020              
2021             =head2 writeFileFromFileFromCurrentRun($target)
2022              
2023             Write to a file in the current L repository by copying a local file if we are running on L.
2024              
2025             Parameter Description
2026             1 $target File name both locally and in the repo
2027              
2028             B
2029              
2030              
2031             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2032            
2033             writeFileFromCurrentRun("output.text", "Hello World");
2034            
2035             writeFileFromFileFromCurrentRun("output.txt"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2036              
2037             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
2038            
2039              
2040             =head2 writeBinaryFileFromFileInCurrentRun($target, $source)
2041              
2042             Write to a file in the current L repository by copying a local binary file if we are running on L.
2043              
2044             Parameter Description
2045             1 $target The target file name in the repo
2046             2 $source The current file name in the run
2047              
2048             B
2049              
2050              
2051             createIssueInCurrentRepo("Hello World", "Need to run Hello World");
2052            
2053             writeFileFromCurrentRun("output.text", "Hello World");
2054             writeFileFromFileFromCurrentRun("output.txt");
2055            
2056             writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2057              
2058            
2059              
2060             =head1 Access tokens
2061              
2062             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.
2063              
2064             =head2 savePersonalAccessToken($gitHub)
2065              
2066             Save a L personal access token by userid in folder L.
2067              
2068             Parameter Description
2069             1 $gitHub GitHub object
2070              
2071             B
2072              
2073              
2074             my $d = temporaryFolder;
2075             my $t = join '', 1..20;
2076            
2077             my $g = gitHub
2078             (userid => q(philiprbrenan),
2079             personalAccessToken => $t,
2080             personalAccessTokenFolder => $d,
2081             );
2082            
2083            
2084             $g->savePersonalAccessToken; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2085              
2086             my $T = $g->loadPersonalAccessToken;
2087            
2088             confess "Load/Save token FAILED" unless $t eq $T;
2089             success "Load/Save token succeeded"
2090            
2091              
2092             =head2 loadPersonalAccessToken($gitHub)
2093              
2094             Load a personal access token by userid from folder L.
2095              
2096             Parameter Description
2097             1 $gitHub GitHub object
2098              
2099             B
2100              
2101              
2102             my $d = temporaryFolder;
2103             my $t = join '', 1..20;
2104            
2105             my $g = gitHub
2106             (userid => q(philiprbrenan),
2107             personalAccessToken => $t,
2108             personalAccessTokenFolder => $d,
2109             );
2110            
2111             $g->savePersonalAccessToken;
2112            
2113             my $T = $g->loadPersonalAccessToken; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
2114              
2115            
2116             confess "Load/Save token FAILED" unless $t eq $T;
2117             success "Load/Save token succeeded"
2118            
2119              
2120              
2121             =head1 Hash Definitions
2122              
2123              
2124              
2125              
2126             =head2 GitHub::Crud Definition
2127              
2128              
2129             Attributes describing the interface with L.
2130              
2131              
2132              
2133              
2134             =head3 Input fields
2135              
2136              
2137             =head4 body
2138              
2139             The body of an issue.
2140              
2141             =head4 branch
2142              
2143             Branch name (you should create this branch first) or omit it for the default branch which is usually 'master'.
2144              
2145             =head4 confessOnFailure
2146              
2147             Confess to any failures
2148              
2149             =head4 gitFile
2150              
2151             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.
2152              
2153             =head4 gitFolder
2154              
2155             Folder name on L - this name can contain '/'.
2156              
2157             =head4 message
2158              
2159             Optional commit message
2160              
2161             =head4 nonRecursive
2162              
2163             Fetch only one level of files with L.
2164              
2165             =head4 personalAccessToken
2166              
2167             A personal access token with scope "public_repo" as generated on page: https://github.com/settings/tokens.
2168              
2169             =head4 personalAccessTokenFolder
2170              
2171             The folder into which to save personal access tokens. Set to q(/etc/GitHubCrudPersonalAccessToken) by default.
2172              
2173             =head4 private
2174              
2175             Whether the repository being created should be private or not.
2176              
2177             =head4 repository
2178              
2179             The name of the repository to be worked on minus the userid - you should create this repository first manually.
2180              
2181             =head4 secret
2182              
2183             The secret for a web hook - this is created by the creator of the web hook and remembered by L,
2184              
2185             =head4 title
2186              
2187             The title of an issue.
2188              
2189             =head4 userid
2190              
2191             Userid on L of the repository to be worked on.
2192              
2193             =head4 webHookUrl
2194              
2195             The url for a web hook.
2196              
2197              
2198              
2199             =head3 Output fields
2200              
2201              
2202             =head4 failed
2203              
2204             Defined if the last request to L failed else B.
2205              
2206             =head4 fileList
2207              
2208             Reference to an array of files produced by L.
2209              
2210             =head4 readData
2211              
2212             Data produced by L.
2213              
2214             =head4 response
2215              
2216             A reference to L's response to the latest request.
2217              
2218              
2219              
2220             =head2 GitHub::Crud::Response Definition
2221              
2222              
2223             Attributes describing a response from L.
2224              
2225              
2226              
2227              
2228             =head3 Output fields
2229              
2230              
2231             =head4 content
2232              
2233             The actual content of the file from L.
2234              
2235             =head4 data
2236              
2237             The data received from L, normally in L format.
2238              
2239             =head4 status
2240              
2241             Our version of Status.
2242              
2243              
2244              
2245             =head1 Private Methods
2246              
2247             =head2 specialFileData($d)
2248              
2249             Do not encode or decode data with a known file signature
2250              
2251             Parameter Description
2252             1 $d String to check
2253              
2254             =head2 currentRepo()
2255              
2256             Create a L object for the current repo if we are on L actions
2257              
2258              
2259              
2260             =head1 Index
2261              
2262              
2263             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.
2264              
2265             2 L - Create an issue on L.
2266              
2267             3 L - Create an issue on L using an access token as supplied or saved in a file using L.
2268              
2269             4 L - Create an issue in the current L repository if we are running on L.
2270              
2271             5 L - Create a web hook for your L userid.
2272              
2273             6 L - Create a repository on L.
2274              
2275             7 L - Create a repository on L using an access token either as supplied or saved in a file using L.
2276              
2277             8 L - Create a L object for the current repo if we are on L actions
2278              
2279             9 L - Delete a file from L.
2280              
2281             10 L - Delete a file on L using a saved token
2282              
2283             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.
2284              
2285             12 L - Get the overall details of a repository
2286              
2287             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.
2288              
2289             14 L - Get repository details from L using a saved token
2290              
2291             15 L - List all the files contained in a L repository or all the files below a specified folder in the repository.
2292              
2293             16 L - List all the commits in a L repository.
2294              
2295             17 L - Create {commit name => sha} from the results of L.
2296              
2297             18 L - List the repositories accessible to a user on L.
2298              
2299             19 L - List web hooks associated with your L repository.
2300              
2301             20 L - Load a personal access token by userid from folder L.
2302              
2303             21 L - Create a new L object with attributes as described at: L.
2304              
2305             22 L - Read data from a file on L.
2306              
2307             23 L - Read a L from L.
2308              
2309             24 L - Read from a file on L using a personal access token as supplied or saved in a file.
2310              
2311             25 L - Rename a source file on L if the target file name is not already in use.
2312              
2313             26 L - Save a L personal access token by userid in folder L.
2314              
2315             27 L - Do not encode or decode data with a known file signature
2316              
2317             28 L - Write utf8 data into a L file.
2318              
2319             29 L - Write to a file in the current L repository by copying a local binary file if we are running on L.
2320              
2321             30 L - Write data into a L as a L that can be referenced by future commits.
2322              
2323             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.
2324              
2325             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.
2326              
2327             33 L - Write text into a file in the current L repository if we are running on L.
2328              
2329             34 L - Write to a file in the current L repository by copying a local file if we are running on L.
2330              
2331             35 L - Copy a file to L using a personal access token as supplied or saved in a file.
2332              
2333             36 L - Write to a file on L using a personal access token as supplied or saved in a file.
2334              
2335             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.
2336              
2337             =head1 Installation
2338              
2339             This module is written in 100% Pure Perl and, thus, it is easy to read,
2340             comprehend, use, modify and install via B:
2341              
2342             sudo cpan install GitHub::Crud
2343              
2344             =head1 Author
2345              
2346             L
2347              
2348             L
2349              
2350             =head1 Copyright
2351              
2352             Copyright (c) 2016-2023 Philip R Brenan.
2353              
2354             This module is free software. It may be used, redistributed and/or modified
2355             under the same terms as Perl itself.
2356              
2357             =cut
2358              
2359              
2360              
2361             # Tests and documentation
2362              
2363             sub test
2364 1     1 0 9 {my $p = __PACKAGE__;
2365 1         9 binmode($_, ":utf8") for *STDOUT, *STDERR;
2366 1 50       58 return if eval "eof(${p}::DATA)";
2367 1         50 my $s = eval "join('', <${p}::DATA>)";
2368 1 50       8 $@ and die $@;
2369 1     1 0 2490 eval $s;
  1     0   71012  
  1         8  
  1         82  
  0            
2370 1 50       5 $@ and die $@;
2371 1         124 1
2372             }
2373              
2374             test unless caller;
2375              
2376             1;
2377             #podDocumentation
2378             __DATA__