cosmemilton-acbr-node 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +502 -502
- package/NOTICE +8 -8
- package/README.md +40 -11
- package/acbr.config.json +16 -16
- package/acbr.lock.json +0 -0
- package/dist/cli.js +375 -176
- package/dist/cli.js.map +4 -4
- package/dist/contract.json +0 -0
- package/dist/index.cjs +36 -11
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +36 -11
- package/dist/index.js.map +2 -2
- package/dist/runtime.cjs +36 -11
- package/dist/runtime.cjs.map +2 -2
- package/dist/runtime.js +36 -11
- package/dist/runtime.js.map +2 -2
- package/dist/types/generated/acbr/index.d.ts +1 -0
- package/dist/types/generated/acbr/models.d.ts +0 -0
- package/dist/types/src/cli.d.ts +0 -0
- package/dist/types/src/config.d.ts +0 -0
- package/dist/types/src/contract-help.d.ts +0 -0
- package/dist/types/src/diagnostics.d.ts +2 -0
- package/dist/types/src/generator.d.ts +0 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/native-build.d.ts +1 -0
- package/dist/types/src/offline-check.d.ts +0 -0
- package/dist/types/src/offline-fixture.d.ts +0 -0
- package/dist/types/src/runtime.d.ts +1 -0
- package/dist/types/src/source.d.ts +0 -0
- package/dist/types/src/types.d.ts +4 -2
- package/docs/API.md +116 -90
- package/docs/ARQUITETURA.md +19 -15
- package/docs/CI.md +60 -60
- package/docs/CLI.md +1 -1
- package/docs/DEPENDENCIAS-NATIVAS.md +103 -93
- package/docs/VALIDACAO.md +67 -67
- package/docs/examples/recuperar-xml.mjs +54 -0
- package/docs/licenses/common/Apache-2.0.txt +202 -0
- package/docs/licenses/common/Artistic.txt +131 -0
- package/docs/licenses/common/GFDL-1.2.txt +397 -0
- package/docs/licenses/common/GPL-1.txt +251 -0
- package/docs/licenses/common/GPL-2.txt +339 -0
- package/docs/licenses/common/GPL-3.txt +674 -0
- package/docs/licenses/common/GPL.txt +674 -0
- package/docs/licenses/common/LGPL-2.1.txt +502 -0
- package/docs/licenses/common/LGPL-2.txt +481 -0
- package/docs/licenses/common/LGPL-3.txt +165 -0
- package/docs/licenses/common/LGPL.txt +165 -0
- package/docs/licenses/common/ORIGIN.json +208 -0
- package/docs/licenses/fpc/COPYING.FPC +25 -25
- package/docs/licenses/fpc/COPYING.GPL.txt +340 -340
- package/docs/licenses/fpc/COPYING.LGPL.txt +504 -504
- package/docs/licenses/fpc/ORIGIN.json +21 -21
- package/docs/licenses/lazarus/COPYING.LGPL.txt +481 -481
- package/docs/licenses/lazarus/COPYING.modifiedLGPL.txt +26 -26
- package/docs/licenses/lazarus/COPYING.txt +46 -46
- package/docs/licenses/lazarus/ORIGIN.json +1 -1
- package/native/NativeMotor.pas +55 -3
- package/native/NativeProtocol.pas +3 -2
- package/native/NativeRecovery.pas +274 -0
- package/native/generated/AcbrModels.pas +0 -0
- package/package.json +6 -4
- package/scripts/build-native-deps.mjs +237 -237
- package/scripts/runtime-license-inventory.d.mts +5 -0
- package/scripts/runtime-license-inventory.mjs +161 -0
- package/tools/extract-model.lpr +119 -119
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
7
|
+
const NAME = /^[A-Za-z0-9][A-Za-z0-9.+_-]*$/;
|
|
8
|
+
const MAX_TEXT_BYTES = 4 * 1024 * 1024;
|
|
9
|
+
const COMMON = 'licenses/common/';
|
|
10
|
+
const INVENTORY = COMMON + 'INVENTORY.json';
|
|
11
|
+
const defaultPolicy = fileURLToPath(new URL('../docs/licenses/common/', import.meta.url));
|
|
12
|
+
const digest = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
13
|
+
const fail = message => { throw Error('Licenças do runtime: ' + message); };
|
|
14
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
|
|
16
|
+
async function regularFile(root, name) {
|
|
17
|
+
if (!name || path.posix.isAbsolute(name) || name.includes('\\') || name.split('/').some(p => !p || p === '.' || p === '..')) fail('caminho inválido.');
|
|
18
|
+
const parts = name.split('/');
|
|
19
|
+
let file = root;
|
|
20
|
+
for (let i = 0; i < parts.length; i++) {
|
|
21
|
+
file = path.join(file, parts[i]);
|
|
22
|
+
const info = await lstat(file);
|
|
23
|
+
if (info.isSymbolicLink() || (i < parts.length - 1 ? !info.isDirectory() : !info.isFile())) fail('arquivo não regular: ' + name);
|
|
24
|
+
if (i === parts.length - 1 && info.size > MAX_TEXT_BYTES) fail('texto excede o limite: ' + name);
|
|
25
|
+
}
|
|
26
|
+
return readFile(file);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function references(bytes, notice) {
|
|
30
|
+
const names = new Set();
|
|
31
|
+
for (const match of bytes.toString('utf8').matchAll(/\/usr\/share\/common-licenses\/([^\s"'<>`\u2018\u2019\u201c\u201d]+)/g)) {
|
|
32
|
+
// Sentence punctuation is not part of the Debian license filename.
|
|
33
|
+
const name = match[1].replace(/[.,;:)\]}]+$/, '');
|
|
34
|
+
if (!NAME.test(name)) fail('referência common-licenses inválida em ' + notice);
|
|
35
|
+
names.add(name);
|
|
36
|
+
}
|
|
37
|
+
return [...names].sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function policyAt(directory) {
|
|
41
|
+
const raw = await regularFile(directory, 'ORIGIN.json');
|
|
42
|
+
let policy;
|
|
43
|
+
try { policy = JSON.parse(raw.toString('utf8')); } catch { fail('ORIGIN.json inválido.'); }
|
|
44
|
+
if (policy.formatVersion !== 1 || !object(policy.files) || !Object.hasOwn(policy.files, 'Apache-2.0')) fail('política canônica incompleta.');
|
|
45
|
+
const files = new Map();
|
|
46
|
+
for (const name of Object.keys(policy.files).sort()) {
|
|
47
|
+
const entry = policy.files[name];
|
|
48
|
+
if (!NAME.test(name) || !object(entry) || entry.file !== name + '.txt' || !HASH.test(entry.sha256) || !Number.isSafeInteger(entry.bytes) || entry.bytes <= 0 || entry.bytes > MAX_TEXT_BYTES) fail('registro canônico inválido: ' + name);
|
|
49
|
+
const bytes = await regularFile(directory, entry.file);
|
|
50
|
+
if (bytes.length !== entry.bytes || digest(bytes) !== entry.sha256) fail('texto canônico diverge da origem aprovada: ' + name);
|
|
51
|
+
files.set(name, { entry, bytes });
|
|
52
|
+
}
|
|
53
|
+
return { sha256: digest(raw), files };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The SDK tarball must carry the same reviewed policy and canonical bytes as checkout. */
|
|
57
|
+
export async function auditLicensePolicy(directory, { policyDirectory = defaultPolicy } = {}) {
|
|
58
|
+
const approved = await policyAt(policyDirectory);
|
|
59
|
+
const actual = await policyAt(directory);
|
|
60
|
+
if (actual.sha256 !== approved.sha256) fail('política do SDK diverge da origem aprovada.');
|
|
61
|
+
return actual.sha256;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function noticesAt(runtime) {
|
|
65
|
+
const notices = [];
|
|
66
|
+
async function visit(prefix) {
|
|
67
|
+
for (const entry of (await readdir(path.join(runtime, prefix), { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
68
|
+
const name = prefix + '/' + entry.name;
|
|
69
|
+
if (entry.isSymbolicLink()) fail('link em avisos: ' + name);
|
|
70
|
+
if (entry.isDirectory()) { if (name !== 'licenses/common') await visit(name); }
|
|
71
|
+
else if (entry.isFile()) notices.push({ name, bytes: await regularFile(runtime, name) });
|
|
72
|
+
else fail('aviso não regular: ' + name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
await visit('licenses');
|
|
76
|
+
return notices;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function expectedInventory(runtime, policyDirectory) {
|
|
80
|
+
const policy = await policyAt(policyDirectory);
|
|
81
|
+
const notices = await noticesAt(runtime);
|
|
82
|
+
const required = new Map([['Apache-2.0', new Set(['policy:OpenSSL'])]]);
|
|
83
|
+
const add = (name, source) => {
|
|
84
|
+
if (!policy.files.has(name)) fail('referência common-licenses não aprovada: ' + name);
|
|
85
|
+
if (!required.has(name)) required.set(name, new Set());
|
|
86
|
+
required.get(name).add(source);
|
|
87
|
+
};
|
|
88
|
+
for (const notice of notices) for (const name of references(notice.bytes, notice.name)) add(name, notice.name);
|
|
89
|
+
const scanned = new Set();
|
|
90
|
+
while (scanned.size < required.size) {
|
|
91
|
+
const name = [...required.keys()].find(name => !scanned.has(name));
|
|
92
|
+
scanned.add(name);
|
|
93
|
+
const aliasOf = policy.files.get(name).entry.aliasOf;
|
|
94
|
+
if (aliasOf !== undefined) {
|
|
95
|
+
if (typeof aliasOf !== 'string') fail('alias canônico inválido: ' + name);
|
|
96
|
+
add(aliasOf, COMMON + name + '.txt');
|
|
97
|
+
if (policy.files.get(aliasOf).entry.sha256 !== policy.files.get(name).entry.sha256) fail('alias diverge do texto canônico: ' + name);
|
|
98
|
+
}
|
|
99
|
+
for (const referenced of references(policy.files.get(name).bytes, COMMON + name + '.txt')) add(referenced, COMMON + name + '.txt');
|
|
100
|
+
}
|
|
101
|
+
const files = {};
|
|
102
|
+
for (const name of [...required.keys()].sort()) {
|
|
103
|
+
const { entry } = policy.files.get(name);
|
|
104
|
+
files[name] = { file: COMMON + entry.file, sha256: entry.sha256, bytes: entry.bytes, requiredBy: [...required.get(name)].sort(), origin: entry };
|
|
105
|
+
}
|
|
106
|
+
return { inventory: { formatVersion: 1, policySha256: policy.sha256, files }, policy, notices };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function writeExact(file, bytes) {
|
|
110
|
+
try { await writeFile(file, bytes, { flag: 'wx' }); }
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code !== 'EEXIST') throw error;
|
|
113
|
+
const info = await lstat(file);
|
|
114
|
+
if (!info.isFile() || info.isSymbolicLink() || !(await readFile(file)).equals(bytes)) fail('recusa substituir material existente divergente.');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Complete a NEW/staged runtime before its manifest is generated. Never edits notices. */
|
|
119
|
+
export async function completeRuntimeLicenses(runtime, { policyDirectory = defaultPolicy } = {}) {
|
|
120
|
+
const { inventory, policy } = await expectedInventory(runtime, policyDirectory);
|
|
121
|
+
const directory = path.join(runtime, 'licenses/common');
|
|
122
|
+
try { if (!(await lstat(directory)).isDirectory() || (await lstat(directory)).isSymbolicLink()) fail('diretório common não regular.'); }
|
|
123
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; await mkdir(directory); }
|
|
124
|
+
for (const [name, entry] of Object.entries(inventory.files)) await writeExact(path.join(runtime, entry.file), policy.files.get(name).bytes);
|
|
125
|
+
await writeExact(path.join(runtime, INVENTORY), Buffer.from(JSON.stringify(inventory, null, 2) + '\n'));
|
|
126
|
+
return inventory;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Check canonical bytes and reference closure independently of mutable runtime hashes. */
|
|
130
|
+
export async function auditRuntimeLicenses(runtime, manifest, { policyDirectory = defaultPolicy } = {}) {
|
|
131
|
+
const { inventory, notices } = await expectedInventory(runtime, policyDirectory);
|
|
132
|
+
if (!object(manifest?.files)) fail('manifesto sem inventário.');
|
|
133
|
+
const dependencyFiles = [];
|
|
134
|
+
if (manifest.platform === 'linux') {
|
|
135
|
+
const source = 'sources/native-dependencies.json';
|
|
136
|
+
let dependencies;
|
|
137
|
+
try { dependencies = JSON.parse((await regularFile(runtime, source)).toString('utf8')); } catch { fail('proveniência Linux ausente ou inválida.'); }
|
|
138
|
+
if (!object(dependencies) || Object.keys(dependencies).length === 0) fail('proveniência Linux sem dependências.');
|
|
139
|
+
dependencyFiles.push(source);
|
|
140
|
+
for (const dependency of Object.values(dependencies)) {
|
|
141
|
+
const name = dependency?.copyright;
|
|
142
|
+
if (typeof name !== 'string' || !name.startsWith('licenses/') || name.startsWith(COMMON)) fail('aviso de dependência Linux ausente da proveniência.');
|
|
143
|
+
dependencyFiles.push(name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
let actual;
|
|
147
|
+
try { actual = JSON.parse((await regularFile(runtime, INVENTORY)).toString('utf8')); } catch { fail('inventário common-licenses ausente ou inválido.'); }
|
|
148
|
+
if (JSON.stringify(actual) !== JSON.stringify(inventory)) fail('inventário common-licenses diverge da política e dos avisos.');
|
|
149
|
+
const expectedFiles = [INVENTORY, ...Object.values(inventory.files).map(entry => entry.file)].sort();
|
|
150
|
+
const commonFiles = (await readdir(path.join(runtime, 'licenses/common'))).map(name => COMMON + name).sort();
|
|
151
|
+
if (JSON.stringify(commonFiles) !== JSON.stringify(expectedFiles)) fail('inventário common-licenses incompleto ou com arquivos extras.');
|
|
152
|
+
for (const name of [...notices.map(notice => notice.name), ...dependencyFiles, ...expectedFiles]) {
|
|
153
|
+
const bytes = await regularFile(runtime, name);
|
|
154
|
+
if (!Object.hasOwn(manifest.files, name) || manifest.files[name] !== digest(bytes)) fail('material de licença ausente do manifesto ou hash divergente: ' + name);
|
|
155
|
+
}
|
|
156
|
+
for (const entry of Object.values(inventory.files)) {
|
|
157
|
+
const bytes = await regularFile(runtime, entry.file);
|
|
158
|
+
if (bytes.length !== entry.bytes || digest(bytes) !== entry.sha256) fail('texto integral diverge da origem aprovada: ' + entry.file);
|
|
159
|
+
}
|
|
160
|
+
return inventory;
|
|
161
|
+
}
|
package/tools/extract-model.lpr
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
program ExtractModel;
|
|
2
|
-
{$mode objfpc}{$H+}
|
|
3
|
-
uses Classes, SysUtils, Contnrs, fpjson, PScanner, PParser, PasTree;
|
|
4
|
-
type
|
|
5
|
-
TContainer = class(TPasTreeContainer)
|
|
6
|
-
function CreateElement(C: TPTreeElement; const N: string; P: TPasElement;
|
|
7
|
-
V: TPasMemberVisibility; const F: string; L: Integer): TPasElement; override;
|
|
8
|
-
function FindElement(const N: string): TPasElement; override;
|
|
9
|
-
end;
|
|
10
|
-
function TContainer.CreateElement(C: TPTreeElement; const N: string; P: TPasElement;
|
|
11
|
-
V: TPasMemberVisibility; const F: string; L: Integer): TPasElement;
|
|
12
|
-
begin Result:=C.Create(N,P); Result.Visibility:=V; Result.SourceFilename:=F; Result.SourceLinenumber:=L end;
|
|
13
|
-
function TContainer.FindElement(const N: string): TPasElement;
|
|
14
|
-
begin Result:=nil end;
|
|
15
|
-
var Root,IncDir:string; Files,Queue,Seen:TStringList; Modules:TObjectList; Units:TJSONArray;
|
|
16
|
-
procedure IndexDir(const D:string);
|
|
17
|
-
var S:TSearchRec; F:string;
|
|
18
|
-
begin
|
|
19
|
-
if FindFirst(IncludeTrailingPathDelimiter(D)+'*',faAnyFile,S)=0 then begin
|
|
20
|
-
repeat
|
|
21
|
-
if (S.Name='.') or (S.Name='..') then continue;
|
|
22
|
-
F:=IncludeTrailingPathDelimiter(D)+S.Name;
|
|
23
|
-
if (S.Attr and faDirectory)<>0 then IndexDir(F)
|
|
24
|
-
else if SameText(ExtractFileExt(F),'.pas') or SameText(ExtractFileExt(F),'.pp') then
|
|
25
|
-
Files.Values[LowerCase(ChangeFileExt(S.Name,''))]:=F;
|
|
26
|
-
until FindNext(S)<>0; FindClose(S);
|
|
27
|
-
end;
|
|
28
|
-
end;
|
|
29
|
-
function TypeName(T:TPasType):string;
|
|
30
|
-
begin if T=nil then Result:='' else Result:=T.Name end;
|
|
31
|
-
function Expr(E:TPasExpr):string;
|
|
32
|
-
begin if E=nil then Result:='' else Result:=E.GetDeclaration(true) end;
|
|
33
|
-
function PublicMember(E:TPasElement):boolean;
|
|
34
|
-
begin Result:=E.Visibility in [visDefault,visPublic,visPublished] end;
|
|
35
|
-
function Node(E:TPasElement):TJSONObject;
|
|
36
|
-
var A,B:TJSONArray; C:TPasClassType; P:TPasProperty; I:integer; M:TPasElement; O:TJSONObject;
|
|
37
|
-
begin
|
|
38
|
-
Result:=TJSONObject.Create(['name',E.Name,'line',E.SourceLinenumber]);
|
|
39
|
-
if E is TPasClassType then begin
|
|
40
|
-
C:=TPasClassType(E); Result.Add('kind','class'); Result.Add('ancestor',TypeName(C.AncestorType));
|
|
41
|
-
A:=TJSONArray.Create; B:=TJSONArray.Create; Result.Add('properties',A);Result.Add('methods',B);
|
|
42
|
-
for I:=0 to C.Members.Count-1 do begin
|
|
43
|
-
M:=TPasElement(C.Members[I]); if not PublicMember(M) then continue;
|
|
44
|
-
if M is TPasProperty then begin
|
|
45
|
-
P:=TPasProperty(M);
|
|
46
|
-
O:=TJSONObject.Create(['name',P.Name,'type',TypeName(P.VarType),'indexed',P.Args.Count>0,
|
|
47
|
-
'readable',Assigned(P.ReadAccessor) or (P.ReadAccessorName<>''),'writable',Assigned(P.WriteAccessor) or (P.WriteAccessorName<>''),
|
|
48
|
-
'default',P.DefaultValue,'line',P.SourceLinenumber]); A.Add(O);
|
|
49
|
-
end else if M is TPasProcedure then begin
|
|
50
|
-
O:=TJSONObject.Create(['name',M.Name,'result','','arguments',TPasProcedure(M).ProcType.Args.Count]);
|
|
51
|
-
if TPasProcedure(M).ProcType is TPasFunctionType then
|
|
52
|
-
O.Strings['result']:=TypeName(TPasFunctionType(TPasProcedure(M).ProcType).ResultEl.ResultType);
|
|
53
|
-
B.Add(O);
|
|
54
|
-
end;
|
|
55
|
-
end;
|
|
56
|
-
end else if E is TPasEnumType then begin
|
|
57
|
-
Result.Add('kind','enum'); A:=TJSONArray.Create;Result.Add('values',A);
|
|
58
|
-
for I:=0 to TPasEnumType(E).Values.Count-1 do begin
|
|
59
|
-
M:=TPasElement(TPasEnumType(E).Values[I]);
|
|
60
|
-
A.Add(TJSONObject.Create(['name',M.Name,'assigned',TPasEnumValue(M).AssignedValue]));
|
|
61
|
-
end;
|
|
62
|
-
end else if E is TPasAliasType then begin
|
|
63
|
-
Result.Add('kind','alias');Result.Add('target',TypeName(TPasAliasType(E).DestType));
|
|
64
|
-
end else if E is TPasRangeType then begin
|
|
65
|
-
Result.Add('kind','range');Result.Add('min',TPasRangeType(E).RangeStart);Result.Add('max',TPasRangeType(E).RangeEnd);
|
|
66
|
-
end else if E is TPasType then begin
|
|
67
|
-
Result.Add('kind','unsupported');Result.Add('declarationKind',E.ClassName);
|
|
68
|
-
end else begin Result.Free; Result:=nil end;
|
|
69
|
-
end;
|
|
70
|
-
procedure ParseUnitFile(const N:string);
|
|
71
|
-
var F:string; R:TFileResolver; S:TPascalScanner; P:TPasParser; C:TContainer;
|
|
72
|
-
M:TPasModule; U:TJSONObject; A,B:TJSONArray; I:integer; O:TJSONObject; E:TPasElement;
|
|
73
|
-
begin
|
|
74
|
-
if Seen.IndexOf(LowerCase(N))>=0 then exit;Seen.Add(LowerCase(N));
|
|
75
|
-
F:=Files.Values[LowerCase(N)];if F='' then exit;
|
|
76
|
-
R:=TFileResolver.Create; S:=nil;P:=nil;C:=nil;M:=nil;
|
|
77
|
-
try
|
|
78
|
-
R.BaseDirectory:=ExtractFilePath(F);R.AddIncludePath(IncDir);R.AddIncludePath(ExtractFilePath(F));
|
|
79
|
-
S:=TPascalScanner.Create(R); S.AddDefine('FPC');S.AddDefine('FPK');
|
|
80
|
-
if SameText(ParamStr(3),'win64') then begin S.AddDefine('MSWINDOWS');S.AddDefine('WINDOWS');S.AddDefine('WIN64') end
|
|
81
|
-
else begin S.AddDefine('LINUX');S.AddDefine('UNIX') end;
|
|
82
|
-
S.AddDefine('CPUX86_64');S.AddDefine('CPU64');S.AddMacro('FPC_FULLVERSION','30202');S.AddDefine('VER3_2');S.AddDefine('NOGUI');S.AddDefine('NOREPORT');
|
|
83
|
-
S.OpenFile(F);C:=TContainer.Create;P:=TPasParser.Create(S,R,C);
|
|
84
|
-
P.Options:=P.Options+[po_IgnoreUnknownResource,po_NoOverloadedProcs,po_AsmWhole];
|
|
85
|
-
P.ParseMain(M);Modules.Add(M);
|
|
86
|
-
U:=TJSONObject.Create(['name',M.Name,'file',Copy(F,Length(Root)+2,MaxInt)]);
|
|
87
|
-
A:=TJSONArray.Create;B:=TJSONArray.Create;U.Add('uses',A);U.Add('types',B);
|
|
88
|
-
for I:=0 to High(M.InterfaceSection.UsesClause) do begin
|
|
89
|
-
E:=M.InterfaceSection.UsesClause[I]; A.Add(E.Name);
|
|
90
|
-
if not SameText(E.Name,'ACBrBase') and not (Pos('ACBrUtil.',E.Name)=1) then Queue.Add(E.Name);
|
|
91
|
-
end;
|
|
92
|
-
for I:=0 to M.InterfaceSection.Types.Count-1 do begin
|
|
93
|
-
O:=Node(TPasElement(M.InterfaceSection.Types[I]));if O<>nil then B.Add(O);
|
|
94
|
-
end;
|
|
95
|
-
for I:=0 to M.InterfaceSection.Classes.Count-1 do begin
|
|
96
|
-
O:=Node(TPasElement(M.InterfaceSection.Classes[I]));if O<>nil then B.Add(O);
|
|
97
|
-
end;
|
|
98
|
-
Units.Add(U);
|
|
99
|
-
except on Ex:Exception do begin
|
|
100
|
-
WriteLn(StdErr,'Cannot parse ',F,': ',Ex.Message);Halt(2);
|
|
101
|
-
end end;
|
|
102
|
-
P.Free;S.Free;C.Free;R.Free;
|
|
103
|
-
end;
|
|
104
|
-
var I:integer; Output:TJSONObject; OutText:TStringList;
|
|
105
|
-
begin
|
|
106
|
-
if ParamCount<2 then begin WriteLn(StdErr,'Usage: extract-model SOURCE_ROOT OUTPUT_JSON');Halt(1) end;
|
|
107
|
-
Root:=ExcludeTrailingPathDelimiter(ExpandFileName(ParamStr(1)));
|
|
108
|
-
IncDir:=Root+'/Fontes/ACBrComum';
|
|
109
|
-
Files:=TStringList.Create;Files.CaseSensitive:=false;Queue:=TStringList.Create;
|
|
110
|
-
Seen:=TStringList.Create;Modules:=TObjectList.Create(true);Units:=TJSONArray.Create;
|
|
111
|
-
try
|
|
112
|
-
IndexDir(Root+'/Fontes');Queue.Add('ACBrNFe.Classes');I:=0;
|
|
113
|
-
while I<Queue.Count do begin ParseUnitFile(Queue[I]);Inc(I) end;
|
|
114
|
-
if Units.Count=0 then raise Exception.Create('ACBrNFe.Classes source was not found');
|
|
115
|
-
Output:=TJSONObject.Create(['schemaVersion',1,'root','TNFe']);Output.Add('units',Units);
|
|
116
|
-
OutText:=TStringList.Create;OutText.Text:=Output.FormatJSON;OutText.SaveToFile(ParamStr(2));
|
|
117
|
-
OutText.Free;Output.Free;
|
|
118
|
-
finally Modules.Free;Seen.Free;Queue.Free;Files.Free end;
|
|
119
|
-
end.
|
|
1
|
+
program ExtractModel;
|
|
2
|
+
{$mode objfpc}{$H+}
|
|
3
|
+
uses Classes, SysUtils, Contnrs, fpjson, PScanner, PParser, PasTree;
|
|
4
|
+
type
|
|
5
|
+
TContainer = class(TPasTreeContainer)
|
|
6
|
+
function CreateElement(C: TPTreeElement; const N: string; P: TPasElement;
|
|
7
|
+
V: TPasMemberVisibility; const F: string; L: Integer): TPasElement; override;
|
|
8
|
+
function FindElement(const N: string): TPasElement; override;
|
|
9
|
+
end;
|
|
10
|
+
function TContainer.CreateElement(C: TPTreeElement; const N: string; P: TPasElement;
|
|
11
|
+
V: TPasMemberVisibility; const F: string; L: Integer): TPasElement;
|
|
12
|
+
begin Result:=C.Create(N,P); Result.Visibility:=V; Result.SourceFilename:=F; Result.SourceLinenumber:=L end;
|
|
13
|
+
function TContainer.FindElement(const N: string): TPasElement;
|
|
14
|
+
begin Result:=nil end;
|
|
15
|
+
var Root,IncDir:string; Files,Queue,Seen:TStringList; Modules:TObjectList; Units:TJSONArray;
|
|
16
|
+
procedure IndexDir(const D:string);
|
|
17
|
+
var S:TSearchRec; F:string;
|
|
18
|
+
begin
|
|
19
|
+
if FindFirst(IncludeTrailingPathDelimiter(D)+'*',faAnyFile,S)=0 then begin
|
|
20
|
+
repeat
|
|
21
|
+
if (S.Name='.') or (S.Name='..') then continue;
|
|
22
|
+
F:=IncludeTrailingPathDelimiter(D)+S.Name;
|
|
23
|
+
if (S.Attr and faDirectory)<>0 then IndexDir(F)
|
|
24
|
+
else if SameText(ExtractFileExt(F),'.pas') or SameText(ExtractFileExt(F),'.pp') then
|
|
25
|
+
Files.Values[LowerCase(ChangeFileExt(S.Name,''))]:=F;
|
|
26
|
+
until FindNext(S)<>0; FindClose(S);
|
|
27
|
+
end;
|
|
28
|
+
end;
|
|
29
|
+
function TypeName(T:TPasType):string;
|
|
30
|
+
begin if T=nil then Result:='' else Result:=T.Name end;
|
|
31
|
+
function Expr(E:TPasExpr):string;
|
|
32
|
+
begin if E=nil then Result:='' else Result:=E.GetDeclaration(true) end;
|
|
33
|
+
function PublicMember(E:TPasElement):boolean;
|
|
34
|
+
begin Result:=E.Visibility in [visDefault,visPublic,visPublished] end;
|
|
35
|
+
function Node(E:TPasElement):TJSONObject;
|
|
36
|
+
var A,B:TJSONArray; C:TPasClassType; P:TPasProperty; I:integer; M:TPasElement; O:TJSONObject;
|
|
37
|
+
begin
|
|
38
|
+
Result:=TJSONObject.Create(['name',E.Name,'line',E.SourceLinenumber]);
|
|
39
|
+
if E is TPasClassType then begin
|
|
40
|
+
C:=TPasClassType(E); Result.Add('kind','class'); Result.Add('ancestor',TypeName(C.AncestorType));
|
|
41
|
+
A:=TJSONArray.Create; B:=TJSONArray.Create; Result.Add('properties',A);Result.Add('methods',B);
|
|
42
|
+
for I:=0 to C.Members.Count-1 do begin
|
|
43
|
+
M:=TPasElement(C.Members[I]); if not PublicMember(M) then continue;
|
|
44
|
+
if M is TPasProperty then begin
|
|
45
|
+
P:=TPasProperty(M);
|
|
46
|
+
O:=TJSONObject.Create(['name',P.Name,'type',TypeName(P.VarType),'indexed',P.Args.Count>0,
|
|
47
|
+
'readable',Assigned(P.ReadAccessor) or (P.ReadAccessorName<>''),'writable',Assigned(P.WriteAccessor) or (P.WriteAccessorName<>''),
|
|
48
|
+
'default',P.DefaultValue,'line',P.SourceLinenumber]); A.Add(O);
|
|
49
|
+
end else if M is TPasProcedure then begin
|
|
50
|
+
O:=TJSONObject.Create(['name',M.Name,'result','','arguments',TPasProcedure(M).ProcType.Args.Count]);
|
|
51
|
+
if TPasProcedure(M).ProcType is TPasFunctionType then
|
|
52
|
+
O.Strings['result']:=TypeName(TPasFunctionType(TPasProcedure(M).ProcType).ResultEl.ResultType);
|
|
53
|
+
B.Add(O);
|
|
54
|
+
end;
|
|
55
|
+
end;
|
|
56
|
+
end else if E is TPasEnumType then begin
|
|
57
|
+
Result.Add('kind','enum'); A:=TJSONArray.Create;Result.Add('values',A);
|
|
58
|
+
for I:=0 to TPasEnumType(E).Values.Count-1 do begin
|
|
59
|
+
M:=TPasElement(TPasEnumType(E).Values[I]);
|
|
60
|
+
A.Add(TJSONObject.Create(['name',M.Name,'assigned',TPasEnumValue(M).AssignedValue]));
|
|
61
|
+
end;
|
|
62
|
+
end else if E is TPasAliasType then begin
|
|
63
|
+
Result.Add('kind','alias');Result.Add('target',TypeName(TPasAliasType(E).DestType));
|
|
64
|
+
end else if E is TPasRangeType then begin
|
|
65
|
+
Result.Add('kind','range');Result.Add('min',TPasRangeType(E).RangeStart);Result.Add('max',TPasRangeType(E).RangeEnd);
|
|
66
|
+
end else if E is TPasType then begin
|
|
67
|
+
Result.Add('kind','unsupported');Result.Add('declarationKind',E.ClassName);
|
|
68
|
+
end else begin Result.Free; Result:=nil end;
|
|
69
|
+
end;
|
|
70
|
+
procedure ParseUnitFile(const N:string);
|
|
71
|
+
var F:string; R:TFileResolver; S:TPascalScanner; P:TPasParser; C:TContainer;
|
|
72
|
+
M:TPasModule; U:TJSONObject; A,B:TJSONArray; I:integer; O:TJSONObject; E:TPasElement;
|
|
73
|
+
begin
|
|
74
|
+
if Seen.IndexOf(LowerCase(N))>=0 then exit;Seen.Add(LowerCase(N));
|
|
75
|
+
F:=Files.Values[LowerCase(N)];if F='' then exit;
|
|
76
|
+
R:=TFileResolver.Create; S:=nil;P:=nil;C:=nil;M:=nil;
|
|
77
|
+
try
|
|
78
|
+
R.BaseDirectory:=ExtractFilePath(F);R.AddIncludePath(IncDir);R.AddIncludePath(ExtractFilePath(F));
|
|
79
|
+
S:=TPascalScanner.Create(R); S.AddDefine('FPC');S.AddDefine('FPK');
|
|
80
|
+
if SameText(ParamStr(3),'win64') then begin S.AddDefine('MSWINDOWS');S.AddDefine('WINDOWS');S.AddDefine('WIN64') end
|
|
81
|
+
else begin S.AddDefine('LINUX');S.AddDefine('UNIX') end;
|
|
82
|
+
S.AddDefine('CPUX86_64');S.AddDefine('CPU64');S.AddMacro('FPC_FULLVERSION','30202');S.AddDefine('VER3_2');S.AddDefine('NOGUI');S.AddDefine('NOREPORT');
|
|
83
|
+
S.OpenFile(F);C:=TContainer.Create;P:=TPasParser.Create(S,R,C);
|
|
84
|
+
P.Options:=P.Options+[po_IgnoreUnknownResource,po_NoOverloadedProcs,po_AsmWhole];
|
|
85
|
+
P.ParseMain(M);Modules.Add(M);
|
|
86
|
+
U:=TJSONObject.Create(['name',M.Name,'file',Copy(F,Length(Root)+2,MaxInt)]);
|
|
87
|
+
A:=TJSONArray.Create;B:=TJSONArray.Create;U.Add('uses',A);U.Add('types',B);
|
|
88
|
+
for I:=0 to High(M.InterfaceSection.UsesClause) do begin
|
|
89
|
+
E:=M.InterfaceSection.UsesClause[I]; A.Add(E.Name);
|
|
90
|
+
if not SameText(E.Name,'ACBrBase') and not (Pos('ACBrUtil.',E.Name)=1) then Queue.Add(E.Name);
|
|
91
|
+
end;
|
|
92
|
+
for I:=0 to M.InterfaceSection.Types.Count-1 do begin
|
|
93
|
+
O:=Node(TPasElement(M.InterfaceSection.Types[I]));if O<>nil then B.Add(O);
|
|
94
|
+
end;
|
|
95
|
+
for I:=0 to M.InterfaceSection.Classes.Count-1 do begin
|
|
96
|
+
O:=Node(TPasElement(M.InterfaceSection.Classes[I]));if O<>nil then B.Add(O);
|
|
97
|
+
end;
|
|
98
|
+
Units.Add(U);
|
|
99
|
+
except on Ex:Exception do begin
|
|
100
|
+
WriteLn(StdErr,'Cannot parse ',F,': ',Ex.Message);Halt(2);
|
|
101
|
+
end end;
|
|
102
|
+
P.Free;S.Free;C.Free;R.Free;
|
|
103
|
+
end;
|
|
104
|
+
var I:integer; Output:TJSONObject; OutText:TStringList;
|
|
105
|
+
begin
|
|
106
|
+
if ParamCount<2 then begin WriteLn(StdErr,'Usage: extract-model SOURCE_ROOT OUTPUT_JSON');Halt(1) end;
|
|
107
|
+
Root:=ExcludeTrailingPathDelimiter(ExpandFileName(ParamStr(1)));
|
|
108
|
+
IncDir:=Root+'/Fontes/ACBrComum';
|
|
109
|
+
Files:=TStringList.Create;Files.CaseSensitive:=false;Queue:=TStringList.Create;
|
|
110
|
+
Seen:=TStringList.Create;Modules:=TObjectList.Create(true);Units:=TJSONArray.Create;
|
|
111
|
+
try
|
|
112
|
+
IndexDir(Root+'/Fontes');Queue.Add('ACBrNFe.Classes');I:=0;
|
|
113
|
+
while I<Queue.Count do begin ParseUnitFile(Queue[I]);Inc(I) end;
|
|
114
|
+
if Units.Count=0 then raise Exception.Create('ACBrNFe.Classes source was not found');
|
|
115
|
+
Output:=TJSONObject.Create(['schemaVersion',1,'root','TNFe']);Output.Add('units',Units);
|
|
116
|
+
OutText:=TStringList.Create;OutText.Text:=Output.FormatJSON;OutText.SaveToFile(ParamStr(2));
|
|
117
|
+
OutText.Free;Output.Free;
|
|
118
|
+
finally Modules.Free;Seen.Free;Queue.Free;Files.Free end;
|
|
119
|
+
end.
|