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.
Files changed (66) hide show
  1. package/LICENSE +502 -502
  2. package/NOTICE +8 -8
  3. package/README.md +40 -11
  4. package/acbr.config.json +16 -16
  5. package/acbr.lock.json +0 -0
  6. package/dist/cli.js +375 -176
  7. package/dist/cli.js.map +4 -4
  8. package/dist/contract.json +0 -0
  9. package/dist/index.cjs +36 -11
  10. package/dist/index.cjs.map +2 -2
  11. package/dist/index.js +36 -11
  12. package/dist/index.js.map +2 -2
  13. package/dist/runtime.cjs +36 -11
  14. package/dist/runtime.cjs.map +2 -2
  15. package/dist/runtime.js +36 -11
  16. package/dist/runtime.js.map +2 -2
  17. package/dist/types/generated/acbr/index.d.ts +1 -0
  18. package/dist/types/generated/acbr/models.d.ts +0 -0
  19. package/dist/types/src/cli.d.ts +0 -0
  20. package/dist/types/src/config.d.ts +0 -0
  21. package/dist/types/src/contract-help.d.ts +0 -0
  22. package/dist/types/src/diagnostics.d.ts +2 -0
  23. package/dist/types/src/generator.d.ts +0 -0
  24. package/dist/types/src/index.d.ts +1 -0
  25. package/dist/types/src/native-build.d.ts +1 -0
  26. package/dist/types/src/offline-check.d.ts +0 -0
  27. package/dist/types/src/offline-fixture.d.ts +0 -0
  28. package/dist/types/src/runtime.d.ts +1 -0
  29. package/dist/types/src/source.d.ts +0 -0
  30. package/dist/types/src/types.d.ts +4 -2
  31. package/docs/API.md +116 -90
  32. package/docs/ARQUITETURA.md +19 -15
  33. package/docs/CI.md +60 -60
  34. package/docs/CLI.md +1 -1
  35. package/docs/DEPENDENCIAS-NATIVAS.md +103 -93
  36. package/docs/VALIDACAO.md +67 -67
  37. package/docs/examples/recuperar-xml.mjs +54 -0
  38. package/docs/licenses/common/Apache-2.0.txt +202 -0
  39. package/docs/licenses/common/Artistic.txt +131 -0
  40. package/docs/licenses/common/GFDL-1.2.txt +397 -0
  41. package/docs/licenses/common/GPL-1.txt +251 -0
  42. package/docs/licenses/common/GPL-2.txt +339 -0
  43. package/docs/licenses/common/GPL-3.txt +674 -0
  44. package/docs/licenses/common/GPL.txt +674 -0
  45. package/docs/licenses/common/LGPL-2.1.txt +502 -0
  46. package/docs/licenses/common/LGPL-2.txt +481 -0
  47. package/docs/licenses/common/LGPL-3.txt +165 -0
  48. package/docs/licenses/common/LGPL.txt +165 -0
  49. package/docs/licenses/common/ORIGIN.json +208 -0
  50. package/docs/licenses/fpc/COPYING.FPC +25 -25
  51. package/docs/licenses/fpc/COPYING.GPL.txt +340 -340
  52. package/docs/licenses/fpc/COPYING.LGPL.txt +504 -504
  53. package/docs/licenses/fpc/ORIGIN.json +21 -21
  54. package/docs/licenses/lazarus/COPYING.LGPL.txt +481 -481
  55. package/docs/licenses/lazarus/COPYING.modifiedLGPL.txt +26 -26
  56. package/docs/licenses/lazarus/COPYING.txt +46 -46
  57. package/docs/licenses/lazarus/ORIGIN.json +1 -1
  58. package/native/NativeMotor.pas +55 -3
  59. package/native/NativeProtocol.pas +3 -2
  60. package/native/NativeRecovery.pas +274 -0
  61. package/native/generated/AcbrModels.pas +0 -0
  62. package/package.json +6 -4
  63. package/scripts/build-native-deps.mjs +237 -237
  64. package/scripts/runtime-license-inventory.d.mts +5 -0
  65. package/scripts/runtime-license-inventory.mjs +161 -0
  66. 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
+ }
@@ -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.