-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
Copy pathupdate-copyright
executable file
·494 lines (397 loc) · 14.3 KB
/
update-copyright
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#!/usr/bin/env lua
--[[
Usage: ./scripts/update-copyright
Use `make update-copyright` is recommended without least setup.
The COPYRIGHT file should be updated after running this. Changes are not added to git, visual
review is recommended.
Assumes the following command-line utilities are available:
* lua
* mkdir
* find
* curl
* luarocks
* luasocket (luarocks install luasocket) to parse urls
This command creates a temporary "work" folder where it does a lot of temporary work,
including installing rocks inside said folder.
Requires internet connection in order to download luarocks and license files.
On Macs, you might need to set up OPENSSL_DIR and EXPAT_DIR.
The default for mac is:
OPENSSL_DIR=/usr/local/opt/openssl/ EXPAT_DIR=/usr/local/opt/expat ./scripts/update-copyright
]]
setmetatable(_G, nil)
local url = require "socket.url"
local fmt = string.format
local OPENSSL_DIR = os.getenv("OPENSSL_DIR")
assert(OPENSSL_DIR, "please set the OPENSSL_DIR env variable (needed for installing luasocket)")
local EXPAT_DIR = os.getenv("EXPAT_DIR")
assert(EXPAT_DIR, "please set the EXPAT_DIR env variable (needed for installing luaexpat)")
local work_folder = os.tmpname() .. "-update-copyright"
local MIT_LICENSE = [[
%s
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
-- The script will attempt downloading a more recent version of the license file for these projects
local HARDCODED_DEPENDENCIES = {
["OpenResty"] = {
url = "https://openresty.org",
repo_url = "https://github.com/openresty/openresty",
},
["LuaRocks"] = {
url = "https://luarocks.org",
repo_url = "https://github.com/luarocks/luarocks",
},
["OpenSSL"] = {
url = "https://github.com/openssl/openssl",
},
-- go-pdk dependencies:
["go-codec"] = {
url = "https://github.com/ugorji/go",
},
["testify"] = {
url = "https://github.com/stretchr/testify",
},
["go-difflib"] = {
url = "https://github.com/pmezard/go-difflib",
},
["go-spew"] = {
url = "https://github.com/davecgh/go-spew",
},
["luasyslog"] = {
url = "https://github.com/lunarmodules/luasyslog"
}
}
-- rocks whose license text cannot be easily found
-- i.e. they don't have a github repo, or don't have a LICENSE file in the repo root,
-- or don't have a README markdown file from which to parse the license
local HARDCODED_ROCK_LICENSES = {
["LPeg"] = {
url = "http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html#license",
text = MIT_LICENSE:format("Copyright © 2007-2019 Lua.org, PUC-Rio."),
},
["lrandom"] = {
url = "http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/install.html#license",
text = MIT_LICENSE:format("Copyright (C) 2018 Luiz Henrique de Figueiredo"),
},
["lua-MessagePack"] = {
url = "https://fperrad.frama.io/lua-MessagePack/#copyright-and-license",
text = MIT_LICENSE:format("Copyright © 2012-2019 François Perrad"),
},
["LuaSocket"] = {
url = "https://github.com/diegonehab/luasocket/blob/master/LICENSE",
text = MIT_LICENSE:format("Copyright © 2004-2013 Diego Nehab"),
},
["mimetypes"] = {
url = "https://bitbucket.org/leafstorm/lua-mimetypes/src/default/LICENSE",
text = MIT_LICENSE:format('Copyright (c) 2011 Matthew "LeafStorm" Frazier') ..
[[
======
In addition, the MIME types contained in the Software were
originally obtained from the Python 2.7.1 ``mimetypes.py`` module,
though they have been considerably modified and augmented.
Said file was made available under the Python Software Foundation
license (http://python.org/psf/license/).
]]
,
},
}
-- Try these to get the license file from a github repo:
local LICENSE_ATTEMPTS = {
"LICENSE",
"LICENSE.txt",
"LICENSE.md",
"MIT-LICENSE.txt",
"COPYRIGHT",
"COPYING",
}
-- Try to get the readme from a github repo using:
local README_ATTEMPTS = {
"README.md",
"README.markdown",
}
-- Try to find the license text inside a readme markdown using these header lines:
local README_LICENSE_HEADERS = {
"license",
"copyright and license",
"license %(mit%)",
}
local function write_dot()
local out = io.output()
out:write(".")
out:flush()
end
-- "a string. With stuff!" -> "a-string-with-stuff"
local function to_anchor(str)
return str:lower():gsub("%W+", "-"):gsub("^%-", ""):gsub("%-$", "")
end
-- returns nil for non-github urls. Returns a "cleaned up" url for github urls
local function get_github_repo_url(repo_url)
local parsed_url = assert(url.parse(repo_url))
if parsed_url.host ~= "github.com" then
return nil
end
parsed_url.scheme = "https"
local without_dotgit = parsed_url.path:match("(.*).git$")
if without_dotgit then
parsed_url.path = without_dotgit
end
local without_archive_tgz = parsed_url.path:match("(.*)/archive/.*.tar.gz$")
if without_archive_tgz then
parsed_url.path = without_archive_tgz
end
local without_wiki = parsed_url.path:match("(.*)/wiki$")
if without_wiki then
parsed_url.path = without_wiki
end
return url.build(parsed_url)
end
-- returns the homepage, or the github repo url if none provided in the rock
local function get_rock_homepage(rock)
if rock.description and rock.description.homepage then
return rock.description.homepage
end
if rock.source and rock.source.url then
local gh_url = get_github_repo_url(rock.source.url)
if gh_url then
return gh_url
end
end
error("could not find the homepage for " .. rock.package)
end
-- Downloads a file using curl. Returns the contents of the file if found, nil otherwise
local function download_file(file_url)
local filepath = work_folder .. "/download.tmp"
assert(os.execute("rm -rf " .. filepath))
if os.execute(fmt("curl --fail --silent %s > %s", file_url, filepath)) then
local f = io.open(filepath, "r")
local text = f:read("*a")
f:close()
return text
end
end
-- Finds the start of a license section inside a markdown file.
--
-- If the line is the start of a license section, it is returned,
-- plus an offset (more about this in a second).
--
-- If the line is not the start of a license section, nil is returned.
--
-- The offset is 1 for a 1-liner header or 2 for a 2-liner header.
--
-- ## This is a 1-liner header
--
-- This is a 2-liner header (it is followed by a line of - or =)
-- ==============================================================
local function detect_markdown_license_start(line, next_line)
local low_line = line:lower()
for _, header in ipairs(README_LICENSE_HEADERS) do
if low_line:match("^#+ " .. header .. "$") then
return line, 1
end
if low_line == header
and next_line
and (next_line:match("^=+$") or next_line:match("^%-+$"))
then
return line, 2
end
end
end
-- returns truthy if the passed line looks like the end of a license section in a markdown file
-- * A new markdown header is found (with 1-line or 2-line headers)
-- * The string [Back to TOC](#table-of-contents) is found
-- * Non-inline links are found (these can be put at the end of the file, after the license)
local function is_markdown_license_end(line, next_line)
return line:match("^#+ .+$") -- markdown 1-line header found
or line == "[Back to TOC](#table-of-contents)"
or line:match("^ *%[.+%]: .+$") -- non-inline links
or next_line
and (next_line:match("^=+$") or next_line:match("^%-+$")) -- markdown 2-liner header
end
-- Given some markdown text, find the markdown section where the license is, and return:
-- On the first returned value, the header of the license section
-- On the second returned value, the text of the license section
-- Or nil if no license is found
local function extract_license_from_markdown(markdown)
local lines = {}
for line in markdown:gmatch("([^\r\n]*)[\r\n]?") do
lines[#lines + 1] = line
end
local license_lines = {}
local license_header, offset
local i = 1
while i < #lines do
local line, next_line = lines[i], lines[i + 1]
if license_header then
-- We are reading the license.
if is_markdown_license_end(line, next_line) then
-- check if we have reached an end
return license_header, table.concat(license_lines, "\n")
end
-- if no end reached, attach current line
license_lines[#license_lines + 1] = line
else
license_header, offset = detect_markdown_license_start(line, next_line)
if license_header then
i = i + offset
end
end
i = i + 1
end
-- we are reading the license and reached the end of the file. concat and send
if license_header then
return license_header, table.concat(license_lines, "\n")
end
end
local function find_and_download_license(main_url, alt_url)
local gh_url = get_github_repo_url(main_url)
if not gh_url and alt_url then
gh_url = get_github_repo_url(alt_url)
end
if not gh_url then
error(fmt("Could not find github repo for: %s / %s", main_url, alt_url))
end
local parsed_url = url.parse(gh_url)
local user, reponame = parsed_url.path:match("^/(.*)/(.*)")
if user then
parsed_url.scheme = "https"
parsed_url.host = "raw.githubusercontent.com"
for _, attempt in ipairs(LICENSE_ATTEMPTS) do
parsed_url.path = fmt("/%s/%s/master/%s", user, reponame, attempt)
local attempt_url = url.build(parsed_url)
local text = download_file(attempt_url)
if text and #text > 0 then
parsed_url.host = "github.com"
parsed_url.path = fmt("/%s/%s/blob/master/%s", user, reponame, attempt)
local url_for_humans = url.build(parsed_url)
return url_for_humans, text
end
end
for _, readme_attempt in ipairs(README_ATTEMPTS) do
parsed_url.path = fmt("/%s/%s/master/%s", user, reponame, readme_attempt)
local readme_url = url.build(parsed_url)
local readme_markdown = download_file(readme_url)
if readme_markdown then
local header, text = extract_license_from_markdown(readme_markdown)
if header and #header > 0 then
parsed_url.host = "github.com"
parsed_url.path = fmt("/%s/%s", user, reponame)
parsed_url.fragment = to_anchor(header)
local url_for_humans = url.build(parsed_url)
return url_for_humans, text
end
end
end
end
print("Could not find license file for " .. gh_url)
return "undefined", "undefined"
end
-----
assert(os.execute(fmt("mkdir -p %s", work_folder)))
print("Work folder is " .. work_folder)
print("Finding and downloading license texts from non-rock dependencies")
local licenses = {}
for name, dep in pairs(HARDCODED_DEPENDENCIES) do
local license_url, license_text = find_and_download_license(dep.url, dep.repo_url)
licenses[#licenses + 1] = {
library = name,
library_url = dep.url,
url = license_url,
text = license_text,
}
write_dot()
end
print("")
print(fmt("Installing rocks in work folder. (Install log: %s/luarocks.log) ...", work_folder))
assert(os.execute(fmt("cp kong*.rockspec %s", work_folder)))
assert(os.execute(fmt("luarocks --lua-version=5.1 --tree %s make %s/kong*.rockspec OPENSSL_DIR=%s EXPAT_DIR=%s 2>&1 > %s/luarocks.log",
work_folder, work_folder, OPENSSL_DIR, EXPAT_DIR, work_folder)))
local rocklist_path = fmt("%s/rocklist.txt", work_folder)
assert(os.execute(fmt("find %s/lib | grep rockspec > %s", work_folder, rocklist_path)))
print("Parsing rockfiles ...")
local rocklist = io.open(rocklist_path, "r")
local rocks = {}
for rockpath in assert(rocklist:lines()) do
local rockfile = assert(io.open(rockpath, "r"))
local rocktext = assert(rockfile:read("*a"))
rockfile:close()
-- parse the text of the rockspec and fill up the `rock` variable
local rock = {}
local rockchunk
if _G.loadstring then
rockchunk = assert(loadstring(rocktext))
setfenv(rockchunk, rock)
else
rockchunk = assert(load(rocktext, "sandbox string", "bt", rock))
end
assert(pcall(rockchunk))
if rock.package ~= "kong" then -- skip kong itself
rocks[#rocks + 1] = rock
end
end
rocklist:close()
-- sort alphabetically by package
table.sort(rocks, function(a, b) return a.package:lower() < b.package:lower() end)
print("Searching and downloading license texts from rock repos")
for _, rock in ipairs(rocks) do
-- if it was in HARDCODED_DEPENDENCIES, it is already in licenses at this point
if not HARDCODED_DEPENDENCIES[rock.package] then
local homepage = get_rock_homepage(rock)
local license_url, license_text
local hardcoded = HARDCODED_ROCK_LICENSES[rock.package]
if hardcoded then
license_url, license_text = hardcoded.url, hardcoded.text
else
local source_url = rock.source and rock.source.url or nil
license_url, license_text = find_and_download_license(homepage, source_url)
end
licenses[#licenses + 1] = {
library = rock.package,
library_url = homepage,
url = license_url,
text = license_text,
}
end
write_dot()
end
print("")
print("Writing licenses into COPYRIGHT file")
-- sort alphabetically by library name
table.sort(licenses, function(a, b) return a.library:lower() < b.library:lower() end)
local cf = io.open("COPYRIGHT", "w")
cf:write[[
%%%%%%%%%
Library
Library URL
License URL
License text
]]
local LICENSE_FORMAT = [[
%%%%%%%%%%%%%%%%%%
%s
%s
%s
%s
]]
for _, license in ipairs(licenses) do
cf:write(fmt(LICENSE_FORMAT,
license.library,
license.library_url,
license.url,
license.text))
end
cf:close()
print("Cleaning up " .. work_folder)
assert(os.execute("rm -rf " .. work_folder))
print("All done")