From 82ed5333a0a723ffa3a19a03bd5f598c8be407e4 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sun, 11 Oct 2026 23:43:29 +0200 Subject: [PATCH 01/15] Generics: a contributed section of one page for its seven tuple classes, with its snapshot, sample template and book chapter; two compiler bugs it found queued with reproducers (an array type argument crashes the compiler; New of a package's generic class needs a declaration naming its type arguments) --- BUGS-TO-REPORT.md | 121 ++++++++++++++ WIP.md | 2 +- ...generic-array-type-argument-crash.twinproj | Bin 0 -> 2289 bytes .../repro.json | 6 + .../src/Settings | 61 +++++++ .../src/Sources/Startup.twin | 10 ++ ...ackage-generic-new-untyped-target.twinproj | Bin 0 -> 4210 bytes .../repro.json | 7 + .../src/Packages/BoxLib/Settings | 35 ++++ .../src/Packages/BoxLib/Sources/Box.twin | 8 + .../src/Settings | 74 +++++++++ .../src/Sources/Startup.twin | 9 + builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 1 + docs/Reference/Contributed/Generics/index.md | 156 ++++++++++++++++++ docs/_book.yml | 7 + .../snapshots/contributed/Generics/api.json | 155 +++++++++++++++++ .../contributed-Generics/Settings | 74 +++++++++ 18 files changed, 726 insertions(+), 2 deletions(-) create mode 100644 bugs/generic-array-type-argument-crash/generic-array-type-argument-crash.twinproj create mode 100644 bugs/generic-array-type-argument-crash/repro.json create mode 100644 bugs/generic-array-type-argument-crash/src/Settings create mode 100644 bugs/generic-array-type-argument-crash/src/Sources/Startup.twin create mode 100644 bugs/package-generic-new-untyped-target/package-generic-new-untyped-target.twinproj create mode 100644 bugs/package-generic-new-untyped-target/repro.json create mode 100644 bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Settings create mode 100644 bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Sources/Box.twin create mode 100644 bugs/package-generic-new-untyped-target/src/Settings create mode 100644 bugs/package-generic-new-untyped-target/src/Sources/Startup.twin create mode 100644 docs/Reference/Contributed/Generics/index.md create mode 100644 indexer/snapshots/contributed/Generics/api.json create mode 100644 test/example-projects/contributed-Generics/Settings diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index f31576111..e7b2175f4 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -3510,3 +3510,124 @@ The button's handler (`showErrorWidget`, `ide/main.js`) is `root.Shell('cmd.exe Severity: low. The search still opens, on part of the text. + +--- + +## An array as the type argument of a generic class, interface, `Type` or procedure crashes the compiler + +**Describe the bug** +Naming an array type as a type argument, as in `Box(Of Long())`, crashes the compiler. The IDE prints `NATIVE EXCEPTION: ACCESS_VIOLATION` in the DEBUG CONSOLE, restarts the compiler on the same project, and it crashes again, three times in a run, so nothing in the project compiles while the declaration is there. The declaration alone does it: nothing is constructed or called. A type argument that is not an array compiles, and `Variant` is refused with a diagnostic (TB5124, *Cannot pass Variant datatype as a generic specifier*), so the array is the one type argument that crashes the compiler instead of being compiled or refused. Observed in the IDE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `generic-array-type-argument-crash.twinproj` (attached as `generic-array-type-argument-crash.zip`). Its one source file, `Startup.twin`, holds the whole bug: + ``` + Class Box(Of T) + End Class + + Module Startup + + Public Sub Main() + Dim b As Box(Of Long()) + End Sub + + End Module + ``` +2. Let the IDE compile it (it does so on opening). +3. See the DEBUG CONSOLE: `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code} [$1ED792A6:twinBASIC_win32.dll+003792A6]`, then `restarting from MEMORY [...]`, and the same exception twice more, each with a list of the compiler's threads in which the failing one is `>>> thread 0005: ResolveStageY_DataTypesStart3, /GenericArrayTypeArgumentCrash/Sources/Startup.twin; module Startup; line 4`. With `Box(Of Long)` in place of `Box(Of Long())` the project compiles without a diagnostic. + +Each of these was a project of its own, and crashed in the same way: +- the class is empty or has a field, with or without `[COMCreatable(False)]`; +- another array type: `String()`, `Byte()`, `Variant()`, `Long(,)`, `Long(3)`, an array of a user-defined type, and an array of an instantiated class, `Box(Of Box(Of Long)())`; +- the declaration is at module level, inside a class (`Private b As Box(Of Long())`), a parameter's type or a function's result type, or `Set b = New Box(Of Long())`; +- the generic is an `Interface` (`IBox(Of T)`, `Dim b As IBox(Of Long())`); +- the generic is a `Type` declared in a module (`Type ListU(Of T)` with `value() As T`, and `Dim x As ListU(Of Long())`); +- the generic is a procedure, and the array is given explicitly: `P(Of Long())` for `Sub P(Of T)()`, and `r = Ident(Of Long())(a)` for `Function Ident(Of T)(a As T) As T`; +- the class comes from a package: `Dim t As TupleOf1(Of Long())` with the contributed package Generics 1.0.0.0. + +What does not crash it: `Box(Of Long)` and `Box(Of Collection)`; a generic function called with an array argument and *T* deduced (`Ident(a)`, with `a` a `Long()`), which compiles. + +**Expected behavior** +A diagnostic, or a compiled project. An array that the compiler cannot use as a type argument is refused the way `Variant` is, with a message that names the type argument, and the project keeps compiling; one that it can use compiles, as `Box(Of Long)` does. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997, 995 and 957 (the compiler is restarted two or three times there before the IDE gives up). + +Severity: high for a project that has the declaration. The compiler crashes on every attempt and restarts into the same crash, so the IDE reports nothing else about the project, and the line has to be found and removed by hand. No wrong code is produced. + + + +--- + +## `New` of a generic class from a package does not bind when its type arguments are named in no declaration: TB5074 + +**Describe the bug** +When a generic class comes from a package, a `New` expression that gives its type arguments fails to compile if that combination of type arguments appears in no declaration of the project or the package, that is, as the type of no variable, parameter, field or function result. TB5074, *Could not bind to parameterized constructor of class 'Box'. No compatible Sub New() method found*, is reported, followed by TB5032 (target `As Any`, a bare `Dim t = ...`, `With`), TB5044 (`As Object`, `As Variant`, `Set o = ...`) or TB5093 (an argument of a call). The constructor exists and the call matches it. Naming the same combination in a declaration anywhere in the project, one that is never used included, makes the same line compile; so does declaring the class in the project itself instead of in a package. Observed in the IDE's diagnostics. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `package-generic-new-untyped-target.twinproj` (attached as `package-generic-new-untyped-target.zip`). It embeds a package, `BoxLib` (in `Packages/BoxLib`), that holds one class: + ``` + [COMCreatable(False)] + Public Class Box(Of T) + + Public Sub New(value As T) + End Sub + + End Class + ``` + The project's one source file, `Startup.twin`, holds the whole bug: + ``` + Module Startup + + Public Sub Main() + Dim a As Any = New Box(Of Long)(1) ' TB5074 and TB5032 + Dim o As Object = New Box(Of Double)(2) ' TB5074 and TB5044 + Dim t As Box(Of Byte) = New Box(Of Byte)(3) ' compiles: the declaration names Box(Of Byte) + End Sub + + End Module + ``` +2. Let the IDE compile it (it does so on opening). +3. See four errors, two for each of the first two lines of `Main`: + - `TB5074 Could not bind to parameterized constructor of class 'Box'. No compatible Sub New() method found` and `TB5032 Set-expression must result in an object type. Type [Box] is not an object (2).`, at `New` in the `Dim a` line; + - `TB5074` as above and `TB5044 Unable to determine default interface of coclass 'Box'`, at the `Dim o` line. + + The third line, where the target is declared with the instantiated type, compiles. + +What decides it, each tried in the project above with `Box(Of Long)`: + +| change | result | +|---|---| +| none: `Dim a As Any = New Box(Of Long)(1)` | TB5074 and TB5032 | +| `Dim u As Box(Of Long)` before the line, after it, or in another module as a `Private` field | compiles | +| `Private Function Unused() As Box(Of Long)` anywhere in the project | compiles | +| a declaration with other type arguments, `Dim u As Box(Of Byte)` | TB5074 and TB5032 | +| the package itself declaring `Box(Of Long)`, such as a function's result type | compiles | +| the same class declared in the project's own code | compiles | +| the class without `[COMCreatable(False)]`, or with one type parameter or three | TB5074 and TB5032 | +| the class without a constructor, `Dim a As Any = New Box(Of Long)` | TB5032 only | +| `Dim v As Variant` then `v = New Box(Of Long)(1)`; `Show New Box(Of Long)(1)` for `Show(x As Object)`; `Debug.Print TypeName(New Box(Of Long)(1))` | TB5074 with TB5044 / TB5093 | + +With the contributed package Generics 1.0.0.0, `Dim t As Any = New TupleOf2(Of String, Long)("a", 2)` fails as above, and so do `TupleOf3`, `TupleOf5` to `TupleOf7`, and `TupleOf1(Of Long)(1)`. `New TupleOf1(Of String)("a")` compiles, and so does `New TupleOf7(Of String, String, String, String, String, String, String)(...)`: the package's own module `HelloWorldModule` declares exactly those two instantiations. The number of type parameters is not the cause. + +**Expected behavior** +`New Box(Of Long)(1)` binds to the constructor whether or not another declaration has named `Box(Of Long)`, as it does for a class declared in the project, and as `Dim t As Box(Of Long) = New Box(Of Long)(1)` does. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997 and 995. On BETA 957 the first line reports TB5074 and then TB5028, *Unable to convert type 'Box' to type 'Any'*, in place of TB5032. + +Not tried: a generic class from a compiler package, such as `EventLog(Of T1, T2)` of WinEventLogLib. + +Severity: low. A compile error is reported, nothing goes wrong silently, and a declaration that names the instantiation avoids it, such as `Dim t As Box(Of Long) = New Box(Of Long)(1)`. The diagnostic points at a correct line and says nothing about the type arguments. + + + diff --git a/WIP.md b/WIP.md index 13a44040d..0a9124b28 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Fourteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp and VBA_JSON complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. +Fifteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp and VBA_JSON complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics is one page for its seven tuple classes (owner, 2026-10-11), so `analyze` in audit mode lists the classes and their members as undocumented. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names, and Generics its `HelloWorldModule` the same way. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/bugs/generic-array-type-argument-crash/generic-array-type-argument-crash.twinproj b/bugs/generic-array-type-argument-crash/generic-array-type-argument-crash.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..458edb890dbbe718df4ac5209d21dc4b3ff12df0 GIT binary patch literal 2289 zcmcImOK;;g5O&hn+O3bhwE$t{kN}k-ONtZcvXEpsFdA=+Buxs$K_N?w&8eJ|&}&@k8)JF^)c4 zkSTFDh!=v-4v$I$ga#*s!D#{BY+x#Tnucqt6yvjOJIl}C!VXu>M!gjY{>Wyz3>6c+ z&G;P3rn1%^ z#6#U=ay8h*0<;rNz6a>vBUAGTb1D4R>Sy7%rs_R$qv2hR|nB{ROV-oxv z?3%?iizG{NuV{EhVOwzyJ#a=`GAnwPM7ny6X8@yE2pvhDn3|K2lMwrvD2M~oqN7QK zElo3`=9ENYkwTVoH!6L`#>U+avs()SE1AY{%*uj@dH}w|8=C z1a9B9JKeyuPEJ4CI0v%O_kYwv==}%JDfAmtucagDBbAf`FQC1L^4GCf>h)Hcr)ip& z>DZQU%7bl8gn9&YNA4;er^=WGHZeBZUFYs} z;?L=G|EWIpujq__1RJioMp2_a)YAFv-PvzvXU5-r{P^2d{Ki@dT}29hOGn)MGBOB2;joGMtlf^{{kU zB5)-?t~sZKLfKh58hH&)|~Zv%%n%rAkKMidKxlsHge<= z=a25oNj7IcRl7&`$keZnc~CaU<^|B$E1WjX|1JvOvFQh-RqDH2p3tQ!Ea(rQIZ;P#MoJ&z<2De=U- zm9(12i-sxt-D5bEA^~E`|+%7jKM)7|G_*er#{>^p7UQ;#OT31!NmSHM-qh~9fR->-e>z!t& zs~h^U)=oPj6IT4u^CV&0S^!qxZ~QQrW*CLvsDEOg5|k>&EBlI|$TW?!zC{#rZW4rs zo*FlIl59XKy@0PoHEDF)KARbcyJ$LqN>YG`1J}x^jG`Tsboaz6+ey#OWH9M=kxYZt zN#Eb;fjgs_?gxiWY)|QXP$`k{YSgjo?gibdceI|_02!rQhN5>nref;H$BNNxs#;HP z+da$r_k(V%*JR-ZQ^!m6d=RyeG?E`QO=jRTV+}jLiZ+ZidZL3rCHyEswt*fz6#hu_ zY@_dhZnLBZ;yPEj34Q%zsrz89_Xnfl>DcaGVJihek_lj}5}zmKEuQZg#1Z}*gKXXA zR_@epS-LMHwN DA|`fc literal 0 HcmV?d00001 diff --git a/bugs/package-generic-new-untyped-target/repro.json b/bugs/package-generic-new-untyped-target/repro.json new file mode 100644 index 000000000..00eeaa4c3 --- /dev/null +++ b/bugs/package-generic-new-untyped-target/repro.json @@ -0,0 +1,7 @@ +{ + "mode": "compile", + "expect": { + "exit": 1, + "diagnostics": ["TB5074", "TB5032", "TB5044"] + } +} diff --git a/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Settings b/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Settings new file mode 100644 index 000000000..c12275e20 --- /dev/null +++ b/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Settings @@ -0,0 +1,35 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "BoxLib", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Package TWINPACK", + "project.description": "One generic class, for the reproducer", + "project.id": "{3E7F2B54-8A1D-4C60-B2F9-6D0A41C85E73}", + "project.licence": "MIT", + "project.name": "BoxLib", + "project.optionExplicit": true, + "project.packageVisibility": "PUBLIC", + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.versionMajor": 1, + "project.versionMinor": 0, + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} \ No newline at end of file diff --git a/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Sources/Box.twin b/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Sources/Box.twin new file mode 100644 index 000000000..7d5b0c590 --- /dev/null +++ b/bugs/package-generic-new-untyped-target/src/Packages/BoxLib/Sources/Box.twin @@ -0,0 +1,8 @@ +' A generic class with a constructor that takes the type argument. +[COMCreatable(False)] +Public Class Box(Of T) + + Public Sub New(value As T) + End Sub + +End Class diff --git a/bugs/package-generic-new-untyped-target/src/Settings b/bugs/package-generic-new-untyped-target/src/Settings new file mode 100644 index 000000000..42ea39386 --- /dev/null +++ b/bugs/package-generic-new-untyped-target/src/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "PackageGenericNewUntypedTarget", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: New of a generic class from a package does not bind when its type arguments are named in no declaration", + "project.exportPathIsV2": true, + "project.id": "{46FA899C-86A6-4F83-A29F-D66D2292EE9D}", + "project.name": "PackageGenericNewUntypedTarget", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{3E7F2B54-8A1D-4C60-B2F9-6D0A41C85E73}", + "licence": "MIT", + "name": "[IMPORTED] BoxLib", + "path32": "/Packages/BoxLib", + "path64": "/Packages/BoxLib", + "symbolId": "BoxLib", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} \ No newline at end of file diff --git a/bugs/package-generic-new-untyped-target/src/Sources/Startup.twin b/bugs/package-generic-new-untyped-target/src/Sources/Startup.twin new file mode 100644 index 000000000..70dc08230 --- /dev/null +++ b/bugs/package-generic-new-untyped-target/src/Sources/Startup.twin @@ -0,0 +1,9 @@ +Module Startup + + Public Sub Main() + Dim a As Any = New Box(Of Long)(1) ' TB5074 and TB5032 + Dim o As Object = New Box(Of Double)(2) ' TB5074 and TB5044 + Dim t As Box(Of Byte) = New Box(Of Byte)(3) ' compiles: the declaration names Box(Of Byte) + End Sub + +End Module diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 72fa8f6e7..953f937c0 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1137, + "pages": 1138, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index bfecc3c41..f78e29391 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -15,6 +15,7 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**AutoCompleteLib**](AutoCompleteLib/) -- Windows shell auto-complete for text-input controls - [**FileProps**](FileProps/) -- the Windows shell properties of a file, read and written - [**Fmt**](Fmt/) -- **StringFormat**, a string built from a format string and values, in the style of C#'s **String.Format** +- [**Generics**](Generics/) -- tuples, read-only objects that hold one to seven values of declared types - [**OLEGuids**](OLEGuids/) -- OLE and COM interfaces and structures in twinBASIC source, in place of `OLEGuids.tlb` - [**TBMANLIB_Dialog**](TBMANLIB_Dialog/) -- the **cDialog** class: the Open, Save and folder dialogs, with file filters and path helpers - [**TBMANLIB_ListBoxHScroll**](TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox** diff --git a/docs/Reference/Contributed/Generics/index.md b/docs/Reference/Contributed/Generics/index.md new file mode 100644 index 000000000..27552102d --- /dev/null +++ b/docs/Reference/Contributed/Generics/index.md @@ -0,0 +1,156 @@ +--- +title: Generics Package +parent: Contributed Packages +has_toc: false +exclude_from_docs: + - HelloWorldModule +indexed_from: 1.0.0.0 +permalink: /tB/Packages/Contributed/Generics/ +--- + +# Generics Package +{: .no_toc } + +**Author:** mansellan. + +The **Generics** contributed package provides seven tuple classes, **TupleOf1** to **TupleOf7**. A tuple is a read-only object that holds one to seven values, called its items, each of a type given when the tuple is declared. + +A tuple lets a function return several values at once, without a user-defined type declared for the purpose or **ByRef** parameters for the results. Each class is a [generic class](../../../../Features/Language/Generics): **TupleOf2(Of Long, String)** holds a **Long** and a **String**, and its **Item1** and **Item2** properties return them with those types. The classes follow .NET's **System.Tuple** classes, and their source comments are taken from .NET's, under the MIT licence. This section documents version 1.0.0.0, published under the MIT licence. + +```tb check_build check_run +Dim t As TupleOf3(Of String, Long, Date) = New TupleOf3(Of String, Long, Date)("Ada", 36, #12/10/1815#) +Debug.Print t.Item1 ' Ada +Debug.Print t.Item2 ' 36 +Debug.Print Year(t.Item3) ' 1815 +``` + +## Syntax + +**New TupleOf***n* **(Of** *T1* [ **,** *T2* ... ] **) (** *item1* [ **,** *item2* ... ] **)** + +*tuple*.**Item***k* + +*n* +: The number of items, 1 to 7. Each number has a class of its own, from **TupleOf1** to **TupleOf7**. + +*T1*, *T2* ... +: The types of the items, one for each item, in order. All *n* must be given. [Types of the items](#types-of-the-items) lists the types that work. + +*item1*, *item2* ... +: *required* The values of the items, one for each type, in the same order. The parameters are **ByRef**; see [Creating a tuple](#creating-a-tuple). + +*tuple* +: An object expression that evaluates to a tuple. + +*k* +: The number of an item, 1 to *n*. + +**Item***k* returns the *k*th item, of type *Tk*. The properties are read-only: an item is set only when the tuple is created, and nothing changes it afterwards. The classes have no other members. + +| Class | Type parameters | Properties | +|:------|:----------------|:-----------| +| **TupleOf1** | *T1* | **Item1** | +| **TupleOf2** | *T1*, *T2* | **Item1**, **Item2** | +| **TupleOf3** | *T1* to *T3* | **Item1** to **Item3** | +| **TupleOf4** | cannot be used; see [TupleOf4](#tupleof4) | | +| **TupleOf5** | *T1* to *T5* | **Item1** to **Item5** | +| **TupleOf6** | *T1* to *T6* | **Item1** to **Item6** | +| **TupleOf7** | *T1* to *T7* | **Item1** to **Item7** | + +There is no class for eight or more items. The package's source holds one, **TupleOfN**, as comments only. + +## Creating a tuple + +[**New**](../../../Core/New) creates a tuple and passes the items to the class's constructor. The **As New** form of **Dim** takes no arguments, so a tuple is created with **New** and assigned to a variable declared with the tuple's type: + +```tb check_build check_run +Dim size As TupleOf2(Of Long, Long) +Set size = New TupleOf2(Of Long, Long)(640, 480) +Debug.Print size.Item1 * size.Item2 ' 307200 +``` + +A function that returns a tuple declares the tuple's type as its return type: + +```tb check_build slot=file +Module GenericsMinMaxDemo + ' Returns the smallest and the largest value of an array. + Public Function MinMax(values() As Double) As TupleOf2(Of Double, Double) + Dim lo As Double = values(LBound(values)) + Dim hi As Double = lo + Dim i As Long + For i = LBound(values) + 1 To UBound(values) + If values(i) < lo Then lo = values(i) + If values(i) > hi Then hi = values(i) + Next + Return New TupleOf2(Of Double, Double)(lo, hi) + End Function + + Public Sub Report() + Dim data(2) As Double + data(0) = 4.5 + data(1) = -2 + data(2) = 9 + Dim r As TupleOf2(Of Double, Double) = MinMax(data) + Debug.Print "Smallest:"; r.Item1; " Largest:"; r.Item2 + End Sub +End Module +``` + +> [!NOTE] +> In BETA 1005, **New** with a generic class from a package compiles only when some declaration in the project or the package names the same class with the same type arguments. A variable declared with the tuple's type is such a declaration. Without one, `Dim t As Any = New TupleOf2(Of String, Long)("a", 2)` fails with TB5074, *Could not bind to parameterized constructor of class 'TupleOf2'*, and so does **New** assigned to an **Object** or **Variant** variable or passed as an argument. A generic class declared in the project's own code is not affected. Assign the tuple to a variable of its own type first; that variable can then be assigned to an **Object** or **Variant** variable, through which the items are read late-bound: +> +> ```tb check_build check_run +> Dim t As TupleOf2(Of String, Long) = New TupleOf2(Of String, Long)("width", 640) +> Dim o As Object +> Set o = t +> Debug.Print o.Item1; o.Item2 ' width 640 +> ``` + +The constructor's parameters are declared without **ByVal**, so they are **ByRef**. A variable passed for an item must have the item's type exactly: a **Byte** variable passed for a **Long** item fails with TB5001, *cannot coerce type 'Byte' to [ByRef] 'Long'*. A literal or any other expression is converted as usual, so wrap such a variable in a conversion function. The tuple keeps a copy of each value, and a later change to the variable does not change the tuple: + +```tb check_build check_run +Dim b As Byte = 200 +Dim t As TupleOf1(Of Long) = New TupleOf1(Of Long)(CLng(b)) +b = 1 +Debug.Print t.Item1 ' 200 +``` + +## Reading the items + +Each item is read through its **Item** property, which returns it with the type it was declared with. A tuple is an object, so it is assigned with [**Set**](../../../Core/Set), and a variable that has not been assigned one is **Nothing**. Two tuples with equal items are still two objects: [**Is**](../../../Core/Is) compares the objects themselves, so the items are compared one by one. [**TypeName**](../../../Modules/Information/TypeName) returns the class's name without its type arguments: + +```tb check_build check_run +Dim a As TupleOf2(Of Long, String) = New TupleOf2(Of Long, String)(1, "x") +Dim b As TupleOf2(Of Long, String) = New TupleOf2(Of Long, String)(1, "x") +Debug.Print TypeName(a) ' TupleOf2 +Debug.Print a Is b ' False +Debug.Print a.Item1 = b.Item1 And a.Item2 = b.Item2 ' True +``` + +Unlike .NET's tuples, the classes have no method that compares two tuples or converts one to a string. + +## Types of the items + +An item can have any numeric type, **String**, **Boolean**, **Date**, **Currency** or **LongPtr**. Three kinds of type cannot be used: + +- **Variant.** twinBASIC refuses **Variant** as a type argument: TB5124, *Cannot pass Variant datatype as a generic specifier, use a more explicit type instead*. +- **Object types.** The constructor stores each item with `=`, not with **Set**. A class whose default member takes an argument, such as **Collection**, fails to compile, with TB5092 *Missing argument 'Index'* reported in the package's source. **Object** compiles, but creating the tuple raises run-time error 91, *Object variable or With block variable not set*, even when the item passed is **Nothing**. So a tuple cannot hold another tuple either. +- **Arrays**, such as **Long()**. + +> [!WARNING] +> In BETA 1005, a tuple type with an array as a type argument, such as `TupleOf1(Of Long())`, crashes the compiler wherever it is written: in a declaration, a parameter, a function's result type or after **New**. The compiler restarts and crashes again, so nothing in the project compiles until the line is removed. The same happens with a generic class declared in a project's own code. + +## TupleOf4 + +> [!NOTE] +> **TupleOf4** cannot be used in version 1.0.0.0. The package declares it with three type parameters, `TupleOf4(Of T1, T2, T3)`, but its fourth item uses a fourth, *T4*, which is not declared. With four type arguments, a declaration fails with TB5124, *Too many generic specifiers*. With three, the package's source fails with TB5079, *Unrecognized datatype symbol 'T4'*. A project that does not use **TupleOf4** is not affected. For four values, use **TupleOf5** and leave its last item unused. + +## Other public names + +The package also exposes `HelloWorldModule`, test code that was left in the package. Its `Show` procedure displays two message boxes: *Hello World*, held by a **TupleOf1**, and *One Two Three Four Five Six Seven*, the seven items of a **TupleOf7** joined together. The module has no page of its own. + +## See Also + +- [Generics](../../../../Features/Language/Generics) -- generic classes, procedures and types in twinBASIC +- [Parameterized class constructors](../../../../Features/Advanced/Classes-and-Modules#parameterized-class-constructors) -- the **Sub New** that each tuple class declares +- [New](../../../Core/New) operator -- creates an object, passing arguments to its constructor diff --git a/docs/_book.yml b/docs/_book.yml index 2ad3cd56e..9e8c25b2c 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -509,6 +509,13 @@ parts: landing_is_target: true outline_closed: true + - title: Generics Package + subtitle: Tuples, read-only objects that hold one to seven values of declared types + landing_page: /tB/Packages/Contributed/Generics/ + page: /tB/Packages/Contributed/Generics/ + landing_is_target: true + outline_closed: true + - title: OLEGuids Package subtitle: OLE and COM interfaces and structures in twinBASIC source, in place of OLEGuids.tlb landing_page: /tB/Packages/Contributed/OLEGuids/ diff --git a/indexer/snapshots/contributed/Generics/api.json b/indexer/snapshots/contributed/Generics/api.json new file mode 100644 index 000000000..918ec8fb5 --- /dev/null +++ b/indexer/snapshots/contributed/Generics/api.json @@ -0,0 +1,155 @@ +{ + "format": 3, + "package": "Generics", + "project": "Generics", + "version": "1.0.0.0", + "types": [ + { + "name": "HelloWorldModule", + "kind": "module", + "vis": "public", + "file": "HelloWorldModule.twin", + "signature": "Module HelloWorldModule", + "members": [ + {"name":"GetBigTuple","kind":"function","vis":"private","signature":"Private Function GetBigTuple() As TupleOf7(Of String, String, String, String, String, String, String)","returnType":"TupleOf7(Of String, String, String, String, String, String, String)"}, + {"name":"GetTuple","kind":"function","vis":"private","signature":"Private Function GetTuple() As TupleOf1(Of String)","returnType":"TupleOf1(Of String)"}, + {"name":"Show","kind":"sub","vis":"public","signature":"Public Sub Show()"} + ] + }, + { + "name": "TupleOf1", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf1(Of T1)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"} + ] + }, + { + "name": "TupleOf2", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf2(Of T1, T2)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"} + ] + }, + { + "name": "TupleOf3", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf3(Of T1, T2, T3)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"Item3","kind":"property","vis":"public","signature":"Public Property Get Item3() As T3","returnType":"T3","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2, item3 As T3)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"}, + {"name":"_item3","kind":"field","vis":"private","signature":"Private _item3 As T3"} + ] + }, + { + "name": "TupleOf4", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf4(Of T1, T2, T3)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"Item3","kind":"property","vis":"public","signature":"Public Property Get Item3() As T3","returnType":"T3","accessor":"Get"}, + {"name":"Item4","kind":"property","vis":"public","signature":"Public Property Get Item4() As T4","returnType":"T4","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2, item3 As T3, item4 As T4)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"}, + {"name":"_item3","kind":"field","vis":"private","signature":"Private _item3 As T3"}, + {"name":"_item4","kind":"field","vis":"private","signature":"Private _item4 As T4"} + ] + }, + { + "name": "TupleOf5", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf5(Of T1, T2, T3, T4, T5)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"Item3","kind":"property","vis":"public","signature":"Public Property Get Item3() As T3","returnType":"T3","accessor":"Get"}, + {"name":"Item4","kind":"property","vis":"public","signature":"Public Property Get Item4() As T4","returnType":"T4","accessor":"Get"}, + {"name":"Item5","kind":"property","vis":"public","signature":"Public Property Get Item5() As T5","returnType":"T5","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2, item3 As T3, item4 As T4, item5 As T5)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"}, + {"name":"_item3","kind":"field","vis":"private","signature":"Private _item3 As T3"}, + {"name":"_item4","kind":"field","vis":"private","signature":"Private _item4 As T4"}, + {"name":"_item5","kind":"field","vis":"private","signature":"Private _item5 As T5"} + ] + }, + { + "name": "TupleOf6", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf6(Of T1, T2, T3, T4, T5, T6)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"Item3","kind":"property","vis":"public","signature":"Public Property Get Item3() As T3","returnType":"T3","accessor":"Get"}, + {"name":"Item4","kind":"property","vis":"public","signature":"Public Property Get Item4() As T4","returnType":"T4","accessor":"Get"}, + {"name":"Item5","kind":"property","vis":"public","signature":"Public Property Get Item5() As T5","returnType":"T5","accessor":"Get"}, + {"name":"Item6","kind":"property","vis":"public","signature":"Public Property Get Item6() As T6","returnType":"T6","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2, item3 As T3, item4 As T4, item5 As T5, item6 As T6)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"}, + {"name":"_item3","kind":"field","vis":"private","signature":"Private _item3 As T3"}, + {"name":"_item4","kind":"field","vis":"private","signature":"Private _item4 As T4"}, + {"name":"_item5","kind":"field","vis":"private","signature":"Private _item5 As T5"}, + {"name":"_item6","kind":"field","vis":"private","signature":"Private _item6 As T6"} + ] + }, + { + "name": "TupleOf7", + "kind": "class", + "vis": "public", + "file": "Tuple.twin", + "signature": "Public Class TupleOf7(Of T1, T2, T3, T4, T5, T6, T7)", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Item1","kind":"property","vis":"public","signature":"Public Property Get Item1() As T1","returnType":"T1","accessor":"Get"}, + {"name":"Item2","kind":"property","vis":"public","signature":"Public Property Get Item2() As T2","returnType":"T2","accessor":"Get"}, + {"name":"Item3","kind":"property","vis":"public","signature":"Public Property Get Item3() As T3","returnType":"T3","accessor":"Get"}, + {"name":"Item4","kind":"property","vis":"public","signature":"Public Property Get Item4() As T4","returnType":"T4","accessor":"Get"}, + {"name":"Item5","kind":"property","vis":"public","signature":"Public Property Get Item5() As T5","returnType":"T5","accessor":"Get"}, + {"name":"Item6","kind":"property","vis":"public","signature":"Public Property Get Item6() As T6","returnType":"T6","accessor":"Get"}, + {"name":"Item7","kind":"property","vis":"public","signature":"Public Property Get Item7() As T7","returnType":"T7","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(item1 As T1, item2 As T2, item3 As T3, item4 As T4, item5 As T5, item6 As T6, item7 As T7)"}, + {"name":"_item1","kind":"field","vis":"private","signature":"Private _item1 As T1"}, + {"name":"_item2","kind":"field","vis":"private","signature":"Private _item2 As T2"}, + {"name":"_item3","kind":"field","vis":"private","signature":"Private _item3 As T3"}, + {"name":"_item4","kind":"field","vis":"private","signature":"Private _item4 As T4"}, + {"name":"_item5","kind":"field","vis":"private","signature":"Private _item5 As T5"}, + {"name":"_item6","kind":"field","vis":"private","signature":"Private _item6 As T6"}, + {"name":"_item7","kind":"field","vis":"private","signature":"Private _item7 As T7"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-Generics/Settings b/test/example-projects/contributed-Generics/Settings new file mode 100644 index 000000000..da2cb69dd --- /dev/null +++ b/test/example-projects/contributed-Generics/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{1D8B86A6-14F3-4BC1-A333-511A46219D1F}", + "licence": "MIT", + "name": "[IMPORTED] Generics", + "path32": "/Packages/Generics", + "path64": "/Packages/Generics", + "symbolId": "Generics", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 090722115201b29df1c8cda3ce8d6d34c8134350 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sun, 11 Oct 2026 23:51:10 +0200 Subject: [PATCH 02/15] Generics: the page no longer describes the package's test module --- WIP.md | 2 +- docs/Reference/Contributed/Generics/index.md | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/WIP.md b/WIP.md index 0a9124b28..1f67c916f 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Fifteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp and VBA_JSON complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics is one page for its seven tuple classes (owner, 2026-10-11), so `analyze` in audit mode lists the classes and their members as undocumented. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names, and Generics its `HelloWorldModule` the same way. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. +Fifteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp and VBA_JSON complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics is one page for its seven tuple classes (owner, 2026-10-11), so `analyze` in audit mode lists the classes and their members as undocumented. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/Generics/index.md b/docs/Reference/Contributed/Generics/index.md index 27552102d..239703196 100644 --- a/docs/Reference/Contributed/Generics/index.md +++ b/docs/Reference/Contributed/Generics/index.md @@ -145,10 +145,6 @@ An item can have any numeric type, **String**, **Boolean**, **Date**, **Currency > [!NOTE] > **TupleOf4** cannot be used in version 1.0.0.0. The package declares it with three type parameters, `TupleOf4(Of T1, T2, T3)`, but its fourth item uses a fourth, *T4*, which is not declared. With four type arguments, a declaration fails with TB5124, *Too many generic specifiers*. With three, the package's source fails with TB5079, *Unrecognized datatype symbol 'T4'*. A project that does not use **TupleOf4** is not affected. For four values, use **TupleOf5** and leave its last item unused. -## Other public names - -The package also exposes `HelloWorldModule`, test code that was left in the package. Its `Show` procedure displays two message boxes: *Hello World*, held by a **TupleOf1**, and *One Two Three Four Five Six Seven*, the seven items of a **TupleOf7** joined together. The module has no page of its own. - ## See Also - [Generics](../../../../Features/Language/Generics) -- generic classes, procedures and types in twinBASIC From aa33e0de04c758e547880d8d5ff929fcc88414fb Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 09:08:39 +0200 Subject: [PATCH 03/15] DateAdd: BETA 1005 runs past the end of a shorter month with m, q and yyyy (Jan 31 + 1 month is Mar 3; VB6 gives Feb 28); WARNINGs on DateAdd and the DateTime module page, and the bug queued with a reproducer --- BUGS-TO-REPORT.md | 60 +++++++++++++++++ .../dateadd-month-end-overflow.twinproj | Bin 0 -> 4224 bytes bugs/dateadd-month-end-overflow/repro.json | 26 ++++++++ bugs/dateadd-month-end-overflow/src/Settings | 61 ++++++++++++++++++ .../src/Sources/Startup.twin | 35 ++++++++++ .../vb6/Module1.bas | 40 ++++++++++++ bugs/dateadd-month-end-overflow/vb6/Probe.vbp | 7 ++ .../Reference/Default/VBA/DateTime/DateAdd.md | 3 + docs/Reference/Default/VBA/DateTime/index.md | 3 + 9 files changed, 235 insertions(+) create mode 100644 bugs/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj create mode 100644 bugs/dateadd-month-end-overflow/repro.json create mode 100644 bugs/dateadd-month-end-overflow/src/Settings create mode 100644 bugs/dateadd-month-end-overflow/src/Sources/Startup.twin create mode 100644 bugs/dateadd-month-end-overflow/vb6/Module1.bas create mode 100644 bugs/dateadd-month-end-overflow/vb6/Probe.vbp diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index e7b2175f4..67fced789 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -3631,3 +3631,63 @@ Severity: low. A compile error is reported, nothing goes wrong silently, and a d +--- + +## `DateAdd` with a month, quarter or year interval runs into the next month instead of stopping at the last day of a shorter month + +**Describe the bug** +`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` keeps the day of the month and lets it overflow into the next month, where it should stop at the last day of the month it lands in. `DateAdd("m", 1, #1/31/2026#)` returns March 3, 2026, the date `DateSerial(2026, 2, 31)` gives, and not February 28. No error is raised, so nothing shows that the date is wrong. The VBA documentation, and twinBASIC's own page for `DateAdd` after it, say that `DateAdd` does not return an invalid date and that adding a month to January 31 returns February 28; VB6 does that. The other intervals (`"d"`, `"y"`, `"w"`, `"ww"`, `"h"`, `"n"` and `"s"`) give the right dates. Observed in the IDE, with the result of each call written to the DEBUG CONSOLE as `Format(..., "yyyy-mm-dd")`. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `dateadd-month-end-overflow.twinproj` (attached as `dateadd-month-end-overflow.zip`). Its one source file, `Startup.twin`, holds 18 calls, the first four of which are enough: + ``` + Module Startup + + Public Sub Main() + Debug.Print Format(DateAdd("m", 1, #1/31/2026#), "yyyy-mm-dd") + Debug.Print Format(DateAdd("m", -1, #3/31/2026#), "yyyy-mm-dd") + Debug.Print Format(DateAdd("yyyy", 1, #2/29/2024#), "yyyy-mm-dd") + Debug.Print Format(DateAdd("q", 1, #11/30/2025#), "yyyy-mm-dd") + End Sub + + End Module + ``` +2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. +3. See `2026-03-03`, `2026-03-03`, `2025-03-01` and `2026-03-02`. VB6 (the same calls, attached as `dateadd-month-end-overflow-vb6.zip`, which writes each result to `out.txt`) gives `2026-02-28`, `2026-02-28`, `2025-02-28` and `2026-02-28`. The calls of the project, with the dates each gives: + + | call | twinBASIC | VB6 | + |---|---|---| + | `DateAdd("m", 1, #1/31/2026#)` | 2026-03-03 | 2026-02-28 | + | the same, with the date in a `Date` variable | 2026-03-03 | 2026-02-28 | + | `DateAdd("m", 1, "31-Jan-95")` | 1995-03-03 | 1995-02-28 | + | `DateAdd("m", 1, #1/30/2026#)` | 2026-03-02 | 2026-02-28 | + | `DateAdd("m", 1, #1/29/2026#)` | 2026-03-01 | 2026-02-28 | + | `DateAdd("m", 1, #1/31/2024#)` | 2024-03-02 | 2024-02-29 | + | `DateAdd("m", 1, #10/31/2026#)` | 2026-12-01 | 2026-11-30 | + | `DateAdd("m", -1, #3/31/2026#)` | 2026-03-03 | 2026-02-28 | + | `DateAdd("m", -1, #3/30/2026#)` | 2026-03-02 | 2026-02-28 | + | `DateAdd("yyyy", 1, #2/29/2024#)` | 2025-03-01 | 2025-02-28 | + | `DateAdd("yyyy", -1, #2/29/2024#)` | 2023-03-01 | 2023-02-28 | + | `DateAdd("q", 1, #11/30/2025#)` | 2026-03-02 | 2026-02-28 | + +What decides it: the result is the date `DateSerial(year, month + number, day)` gives, so a day that exists in the target month is not affected, and one that does not is counted into the month after. +- A day that exists in the target month is right: `DateAdd("m", 1, #1/28/2026#)` is 2026-02-28, and `DateAdd("yyyy", 4, #2/29/2024#)` is 2028-02-29, in both. +- The other intervals are right: on January 31, 2026, `"d"` and `"y"` with 1 give 2026-02-01 and `"ww"` with 1 gives 2026-02-07, in both. +- It makes no difference whether the date is a literal, a `Date` variable, `DateSerial(2026, 1, 31)` or a string; whether the number is positive or negative; or whether the date has a time of day, which is kept (`DateAdd("m", 1, #1/31/2026 13:45:10#)` is 2026-03-03 13:45:10). `DateAdd("m", 13, #1/31/2025#)` is 2026-03-03 as well. +- `DateSerial(2026, 2, 31)` is 2026-03-03 in VB6 too, by design: it is not the defect. + +**Expected behavior** +`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` returns the last day of the target month when the day of the date does not exist in it, as VB6 does and as the VBA documentation says: January 31 plus one month is February 28 (29 in a leap year), and February 29 plus one year is February 28. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997, 995 and 957. Not tried: an exe built from the project, and a project compiled with LLVM. + +Severity: high. The date is wrong and nothing signals it. A program that steps by months or years from a date late in the month (due dates, subscriptions, schedules, a calendar control's next-month button) is up to three days late, and a second step continues from the wrong day: `DateAdd("m", 1, DateAdd("m", 1, #1/31/2026#))` is 2026-04-03, where VB6 gives 2026-03-28. A workaround: `DateSerial(Year(d), Month(d) + n + 1, 0)` is the last day of the month `n` months after `d`, so the result is `DateSerial(Year(d), Month(d) + n, Day(d))` when `Day(d)` is smaller than that day, and that day otherwise. + + + diff --git a/bugs/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj b/bugs/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..b4cd4462c2ba6445c1655d50620bf8c72e771413 GIT binary patch literal 4224 zcmcgwTXWh*6vpY*v^;kD(uWQu8bLv ze@_2ge?`w(N!*MLd1&<@OWJe3J^P(2{^{@U{@Kg?n8{?CuB5K#b;3Z7T7h?RL;1BI zPGMzneGQM#GC#n*J)VSIQtx~=p>J8AyPQ$65X@x0!k1^69Narh45;tB0S%)S8H59w zZ{dFG4nDfWRfuom<$d^bMnxF$!Rq?6cpb&M4_Cj~gYSEp?{QVSTt<_EoU%YEp(0*i z!`3edp1kIHWXj}-xMYkBcZGZaT*zfRp%?j-BuB|* zb6En{DQ7oeJ#nI7AXyj?=g=cHp^kKf4_(PwFpPy!!6Cm8C*)&S9(sAA&EV3IxY(i6H&x#vN4(OxY)*nZJwL2ismcJu*%OD3on+~qJf7z zx{`!Rur1`8v>y!%aJ)bgZA@O8M22=+%b~~sM^Sd3;s_q16Rd)r9U12AO z-r@@T`O_Aj4CIc!HRdt(1fp(2obv66I)+Q}a}c+=B}Lrk7tA{hYKQho>>9=78jUEG z1hw@lcFhw^Doi-~PMFHj9Y8;!mG8nEQdCDzgM7`Q2LUPT3o zYhYSN=`9ly>R_Pi1JvswomWe&swfx2ZBd7z$fg;$xmZ}oyM_GmmXKGxzO;Aa)rN@L zfmUy)isq%XZVyovOT})L)fso)Zd_yVQa%y7q``17N>zd zHHmbol+ZH8@}ThL?=WQC<+A(QAPlbAFybzD+X4$9BAFE0YoayjwHx{HoJY9{4?oa> zEVz@&Ig>tB5@7a*32RlMA2HuMb>#>(4({T?2H)>{y*jS-dIxu>G5R~msQbST?rMB6 zViLBCIK3|%+&Rprty@V00Y=_$z{jZ#@nM<>JePZ<^=E5U%%ft!*#u9Cf@c&CjzU1d=tnAdR|RIK>GR+v}zN*#hDYiXp0LA%#u8!D{M0ljlHWht0kjk zR`gc2(bDZ>)6r|yVqI^PN{&;hSY^v})@TWUO4P;B+UlcF@zT1Tz*NNKCPO&Ah6yhK zzF_qV0%9MdXMbNyEHn(mGVG$E!$Wdwq)+(CFzj+{A?Wx6rDfnz;Wah&=u@j8{r<8}+1{ih>9}ZZ)Dg3B zKlIy*@E9^hgINyauH$|PIbd3eVd-ZEbpSpac$j;&wn~jtGU~@x)6koZT3xq`PD`(r zVRkajYN^_^Yj(vto>Rj_BOFg4+$V$2hw+>sII!DiK9e(&Y(WljfEGt7+=B7tFEO9ChS z`DOc^+C)zcNX3>(y{M;O6XsO*BXetnCOofyPI+FHKBG4by#qK!^D!4R%&O(oi&edB zH)^`wtk(6qU8(4`QpvC!`?%#a8vlz6YLf{W`e7fs2b2gQNZ7}!niBILF%P@GQJXe! z&!pC-d+z28bCvbZR%gWN01kPNn)CJ{l4f)f=M;ENAurbBU$!SPEf8DjZ!~4@Tx{y{ z0DBtft7OwC=OI_MN$t0 [!WARNING] +> In BETA 1005, **DateAdd** with the "m", "q" or "yyyy" interval does not stop at the last day of a shorter month: it counts the extra days into the next month and raises no error. `DateAdd("m", 1, "31-Jan-95")` returns 3-Mar-95, `DateAdd("m", -1, #3/31/2026#)` returns 3-Mar-2026, and `DateAdd("yyyy", 1, #2/29/2024#)` returns 1-Mar-2025. VB6 returns 28-Feb-95, 28-Feb-2026 and 28-Feb-2025. + If the calculated date would precede the year 100, an error occurs. If *number* isn't a **Long** value, it is rounded to the nearest whole number before being evaluated. diff --git a/docs/Reference/Default/VBA/DateTime/index.md b/docs/Reference/Default/VBA/DateTime/index.md index 9ae6a6c42..c44ed4de3 100644 --- a/docs/Reference/Default/VBA/DateTime/index.md +++ b/docs/Reference/Default/VBA/DateTime/index.md @@ -58,6 +58,9 @@ Debug.Print DateAdd("m", 1, #1/31/2026#) ' 2/28/2026 — clamped to las Debug.Print DateDiff("d", #1/1/2026#, #5/9/2026#) ' 128 ``` +> [!WARNING] +> In BETA 1005, **DateAdd** does not clamp to the last day of the month, and the first line of the sample prints `3/3/2026`. VB6 prints `2/28/2026`. [**DateAdd**](DateAdd) describes the cases. + ## Calendar selection The [**Calendar**](Calendar) property selects the calendar --- **vbCalGreg** (Gregorian, the default) or **vbCalHijri** (Hijri) --- used by the rest of the module. The setting controls how **Date$** formats the system date, how arguments to **DateSerial**, **DateValue**, **DateAdd**, and **DateDiff** are interpreted, and how the parts returned by **DatePart**, **Year**, **Month**, **Day**, and **Weekday** are reported. From dbe9c7bef4014eb63d37dd5227dcdaf7df3cf91c Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 09:08:39 +0200 Subject: [PATCH 04/15] xCalendar: a contributed section of one page for its calendar control, with its snapshot, sample template and book chapter; what the page says it does at run time comes from a probe kept in test/package-probes/, which runs the control on a form in an exe on a private desktop --- WIP.ExamplesBuild.md | 4 + WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 1 + docs/Reference/Contributed/xCalendar/index.md | 612 +++++ docs/_book.yml | 7 + .../snapshots/contributed/xCalendar/api.json | 304 +++ test/README.md | 3 +- .../contributed-xCalendar/Settings | 74 + .../Sources/tbxStageXCalendar.twin | 15 + test/package-probes/xCalendar/README.md | 85 + test/package-probes/xCalendar/designer.mjs | 179 ++ .../package-probes/xCalendar/patch-stream.mjs | 48 + test/package-probes/xCalendar/probe.mjs | 124 + test/package-probes/xCalendar/run-exe.mjs | 168 ++ .../package-probes/xCalendar/src/Form1.tbform | 229 ++ test/package-probes/xCalendar/src/Form1.twin | 63 + test/package-probes/xCalendar/src/Probe.twin | 2285 +++++++++++++++++ test/package-probes/xCalendar/src/Win.twin | 171 ++ test/package-probes/xCalendar/stage.mjs | 76 + 20 files changed, 4449 insertions(+), 3 deletions(-) create mode 100644 docs/Reference/Contributed/xCalendar/index.md create mode 100644 indexer/snapshots/contributed/xCalendar/api.json create mode 100644 test/example-projects/contributed-xCalendar/Settings create mode 100644 test/example-projects/contributed-xCalendar/Sources/tbxStageXCalendar.twin create mode 100644 test/package-probes/xCalendar/README.md create mode 100644 test/package-probes/xCalendar/designer.mjs create mode 100644 test/package-probes/xCalendar/patch-stream.mjs create mode 100644 test/package-probes/xCalendar/probe.mjs create mode 100644 test/package-probes/xCalendar/run-exe.mjs create mode 100644 test/package-probes/xCalendar/src/Form1.tbform create mode 100644 test/package-probes/xCalendar/src/Form1.twin create mode 100644 test/package-probes/xCalendar/src/Probe.twin create mode 100644 test/package-probes/xCalendar/src/Win.twin create mode 100644 test/package-probes/xCalendar/stage.mjs diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index 68f9ff0ed..e27b62510 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -322,6 +322,10 @@ unpacks it beside, and copies it into every batch staged from the template. The the repository's to commit: four of the nine ship no licence file, and WinReg embeds WinDevLib, 14 MB. +- **A control package's template also declares the control its samples name.** + `contributed-xCalendar` has `Sources/tbxStageXCalendar.twin` beside its `Settings`, with + `Public xCalendar1 As xCalendar.xCalendar`, as `tbxStage.twin` declares `Text1` for the + built-in controls. - **One template per package, because a reader's project references one.** With the nine in one project, VBA_JSON's `Dictionary` class hid the `Scripting.Dictionary` that WinReg's samples name. diff --git a/WIP.md b/WIP.md index 1f67c916f..0b99f5ba1 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Fifteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp and VBA_JSON complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics is one page for its seven tuple classes (owner, 2026-10-11), so `analyze` in audit mode lists the classes and their members as undocumented. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. +Sixteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 953f937c0..74483225a 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1138, + "pages": 1139, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index f78e29391..fe8db31d1 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -25,3 +25,4 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**TwinTimerPackage**](TwinTimerPackage/) -- a Win32 timer class that reports the elapsed time - [**VBA_JSON**](VBA_JSON/) -- JSON parsing and serialization, with UTC and ISO 8601 date conversion - [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs +- [**xCalendar**](xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX` diff --git a/docs/Reference/Contributed/xCalendar/index.md b/docs/Reference/Contributed/xCalendar/index.md new file mode 100644 index 000000000..4dccb0b62 --- /dev/null +++ b/docs/Reference/Contributed/xCalendar/index.md @@ -0,0 +1,612 @@ +--- +title: xCalendar Package +parent: Contributed Packages +has_toc: false +indexed_from: 1.0.7.0 +permalink: /tB/Packages/Contributed/xCalendar/ +--- + +# xCalendar Package +{: .no_toc } + +**Author:** sokinkeso. + +The **xCalendar** contributed package provides a calendar control: a grid of the days of one month, in which the user selects a date by clicking a day, by pressing a key, or by choosing a month or a year from a list. It is meant as a replacement for the Calendar control of `MSCAL.OCX`. + +The package has one public class, **xCalendar**, a user control that draws itself with GDI calls. The class has 27 properties, 10 methods and 12 events, and four enumerations belong to it. This section documents version 1.0.7.0, published under the MIT licence. The package describes itself as "Calendar MSCAL replacement (Experimental, 32-bit and 64-bit)", and its 32-bit and 64-bit builds behave the same. + +The package has defects: choosing a month from the month list can hang the program, and the colour properties of the text have no effect. [Known problems in version 1.0.7.0](#known-problems-in-version-1070) lists them. + +```tb check_build +Private Sub Form_Load() + xCalendar1.FirstDay = WeekDaysEnum.Monday + xCalendar1.DayLength = DayLengthEnum.MediumLength + xCalendar1.Value = #3/15/2026# +End Sub + +Private Sub xCalendar1_AfterUpdate() + Debug.Print "Selected: " & Format$(xCalendar1.Value, "yyyy-mm-dd") +End Sub +``` + +* TOC +{:toc} + +## Adding the control to a form + +Add the package to the project as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. The control is then in the [Toolbox](../../../IDE/Project/Toolbox) like any other: select **xCalendar** and draw it on the form. In code the control is named as any other control on the form is, **xCalendar1** for the first one. + +A control placed on a form starts with today's date selected and with the defaults given with each property. Every property except **ValueStr** is stored with the form. **Year**, **Month** and **Day** are stored as they were when the control was placed, and **Value** is stored only when it was set to a date other than the day of saving. A program that runs on a later day therefore starts with **Value** at today's date and **Year**, **Month** and **Day** of the day the control was placed: the control shows the month of that day, and no cell is selected when today is in another month. To start on a given date, set **Value** in the form's `Form_Load` procedure, or in the Properties panel. + +### Declaring a variable + +The project and its class are both named **xCalendar**, so a variable that holds a calendar is declared `As xCalendar.xCalendar`: + +```tb check_build +Dim cal As xCalendar.xCalendar +Set cal = xCalendar1 +Debug.Print cal.Year +``` + +> [!NOTE] +> `Dim cal As xCalendar` fails in BETA 1005 with TB5139, *[xCalendar] is a project or library, not a datatype*. +> +> ```tb check_build expect-error=TB5139 +> Dim cal As xCalendar +> ``` + +The enumerations need the same care. **ShortLength** and **LongLength** are members of both **DayLengthEnum** and **MonthLengthEnum**, so the bare name is ambiguous, even in an assignment to a variable of one of the two enumeration types. Write the enumeration's name before the member: + +```tb check_build +Dim d As DayLengthEnum = DayLengthEnum.LongLength +Dim m As MonthLengthEnum = MonthLengthEnum.LongLength +Debug.Print d, m ' 2 1 +``` + +> [!NOTE] +> In BETA 1005, `ShortLength` and `LongLength` written alone fail with TB5073, *'ShortLength' is ambiguous*. +> +> ```tb check_build expect-error=TB5073 +> Dim d As DayLengthEnum = ShortLength +> ``` + +### A control that is not on a form + +The start values of the properties are set when the control is placed on a form. An **xCalendar** created in code with `New xCalendar.xCalendar` and never placed on a form has **Year** 0 and **Value** 0 (12:00:00 AM), and none of the defaults given below. + +## How the control is drawn + +The control is drawn in up to three areas, one above the other: the title area, the row of day names and the grid of days. It draws into a hidden picture box and copies the result to its own surface. The control can have any size: the seven columns share the width equally, and the six rows share the height that the other areas leave. + +The sizes inside the control, such as the margins and the two lists, are in device pixels and are not scaled with the DPI of the display. At 144 DPI a control that is 256 pixels wide at design time is 384 pixels wide, and its lists and margins keep their pixel sizes. + +### Title area + +The title area is present when **ShowTitle** or **ShowDateSelectors** is **True**, and has no height when both are **False**. Its height is the height of the title text in **TitleFont**, plus **TitleMargin** above and below. Its background is **BackColor**. It holds two things. + +- **The title** is the name of the shown month and the year, such as `October 2026`, in **TitleFont**, centred in the space to the left of the date selectors. **MonthLength** decides whether the month is written in full or as its first three letters. It is drawn only when **ShowTitle** is **True**. The month name comes from the [**MonthName**](../../../Modules/Strings/MonthName) function, so it is in the language of the system. +- **The date selectors** are two drop-down lists at the right edge, drawn only when **ShowDateSelectors** is **True**. The month list is 80 pixels wide and holds the names of the twelve months. The year list is 50 pixels wide and holds the years 1900 to 2100. Both are 21 pixels high and centred in the height of the title area. The year list ends 5 pixels from the right edge of the control, and the month list ends 5 pixels before the year list. Choosing an entry changes the shown month or year, keeps the selected day of the month, and raises **NewMonth** or **NewYear**. + +The title is centred in the width of the control less 140 pixels, the space that the two lists take, whether or not the lists are shown. At 144 DPI the lists are too small for their text: the year list shows `20` for 2026 and the month list `Octobe` for October. [Known problems](#known-problems-in-version-1070) describes both. + +### Row of day names + +The row is present when **ShowDays** is **True**. It has seven cells of the same width, filled with **DayBackColor**, each holding the name of a weekday centred, in **DayFont**. The first cell is the weekday that **FirstDay** names. **DayLength** decides whether a name is one letter, three letters or the whole name. The names come from the [**WeekdayName**](../../../Modules/Strings/WeekdayName) function, so they are in the language of the system. The height of the row is the height of the letter M in **DayFont**, plus **DaysMargin** above and below. + +### Grid of days + +The grid has six rows of seven cells, 42 days in all, whatever the month. The first cell holds the latest date, on or before the first day of the shown month, that falls on the weekday **FirstDay** names. A month therefore starts in the first row, and the cells before its first day hold the last days of the month before. Each cell shows the day of the month at its top left corner, in **GridFont**. The cell of the selected date, **Value**, is filled with a fixed warm grey (`&H82868E`) and has a black line along its left and top edges. + +All the text of the control is drawn in the same near-black, whatever the colour properties say: the days of the month before and the month after are not grey, and the title is not in **TitleFontColor**. See [Known problems](#known-problems-in-version-1070). + +**ShowVerticalGrid** and **ShowHorizontalGrid** switch the lines between the cells on and off, and **GridCellEffect** and **GridLinesColor** decide how the lines look. A black frame of one pixel always surrounds the row of day names and the grid, whichever lines are on. + +A cell is selected only when its date is equal to **Value**. A **Value** with a time of day other than midnight, such as `#3/15/2026 2:30:00 PM#`, is equal to no cell, so no cell is drawn as selected. **Value** keeps the time, and **Day**, **Month** and **Year** return the parts of the date. + +### Disabled state + +When **Enabled** is **False**, the two lists are disabled and drawn in grey, and the row of day names has the background `&HEEEEEE` in place of **DayBackColor**. With **Flat** lines, the grid lines are `&H5B5B5B` in place of **GridLinesColor**. **BackColor** is the same as for an enabled control, and the text keeps its colour. + +A disabled control does not receive the mouse or the keyboard from the user, and the focus cannot be given to it. Code can still change its date, for example with **NextDay** or **Value**. + +## Choosing a date + +### With the mouse + +Pressing the left mouse button on a cell of the grid selects the date of that cell. The control raises **BeforeUpdate**, which can cancel the selection, then moves the selection and redraws, and raises **AfterUpdate**. A day of the month before or the month after is selected in the same way, and the control then shows its month; no **NewMonth** or **NewYear** event is raised for that change of month. A click with the right button, and a click on the title area or the row of day names, select nothing: they raise **MouseUp** and **Click** only. + +A press near the right edge or the bottom edge of the grid can select a date that the grid does not show: see [Known problems](#known-problems-in-version-1070). + +### With the keyboard + +The control handles these keys while the keyboard focus is in the control. A click on the control gives it the focus, and Tab does not: see [Known problems](#known-problems-in-version-1070). + +| Key | Effect | Method it calls | +|:----|:-------|:----------------| +| Left arrow | selects the day before | **PreviousDay** | +| Right arrow | selects the day after | **NextDay** | +| Up arrow | selects the day one week before | **PreviousWeek** | +| Down arrow | selects the day one week after | **NextWeek** | +| Page Up | selects the same day one month before | **PreviousMonth** | +| Page Down | selects the same day one month after | **NextMonth** | +| Shift, Ctrl or Alt with Page Up | selects the same day one year before | **PreviousYear** | +| Shift, Ctrl or Alt with Page Down | selects the same day one year after | **NextYear** | +| Home | selects today | **Today** | + +Ctrl and Alt step by a year as Shift does. Other keys, End among them, change nothing. + +**KeyDown** is raised once for each key, handled or not, after the selection has moved, so a handler that reads **Value** gets the new date. A character key raises **KeyDown**, **KeyPress** and **KeyUp**, in that order. An arrow key, a Page key, Home and End raise **KeyDown** and **KeyUp**. Tab raises **KeyPress** and **KeyUp**, and no **KeyDown**. + +### By program + +Setting **Value**, **Day**, **Month** or **Year**, and calling the methods that move the selection (**NextDay**, **PreviousMonth**, **Today** and the others), change the selection without raising any event: **BeforeUpdate**, **AfterUpdate**, **NewMonth**, **NewYear**, **Click** and **KeyDown** are not raised. + +## What raises the events + +| What happens | Events raised, in order | +|:-------------|:------------------------| +| The user clicks a day with the left button | **MouseMove** (when the pointer moved), **BeforeUpdate**, **AfterUpdate**, **MouseUp**, **Click** | +| The user clicks with the right button, or on the title or a day name | **MouseMove** (when the pointer moved), **MouseUp**, **Click** | +| The user double-clicks a day | **BeforeUpdate**, **AfterUpdate**, **MouseUp**, **Click**, **DblClick**, **MouseUp** | +| The user chooses a month in the month list | **NewMonth** | +| The user chooses a year in the year list | **NewYear** | +| The user presses a key with the focus in the control | **KeyDown**, **KeyPress**, **KeyUp** (see [With the keyboard](#with-the-keyboard)) | +| The program changes the selection or the shown month | none | + +**MouseDown** is declared but never raised. + +## Known problems in version 1.0.7.0 + +> [!WARNING] +> Version 1.0.7.0 of the package stops the program, or gives a wrong result without an error, in these cases. +> +> - **A month or a year chosen from a list can hang the program.** Choosing a month in which the selected day does not exist, such as February when the selected day is the 31st, or the year 2025 when the selected date is February 29, 2024, makes the program run at full processor load and never return. The same dates set from code raise run-time error 13 instead. +> - **The control asks to be painted again after every paint while the date selectors are shown**, a few hundred times a second, so a program that calls **DoEvents** does not come back from it, and the processor is busy. With **ShowDateSelectors** **False** it stops. +> - **The colour properties of the text have no effect.** **DayFontColor**, **GridFontColor** and **TitleFontColor** are stored and returned, but the day names, the day numbers and the title are always drawn in the same near-black (`&H4`), and so are the days of the month before and the month after, which the control means to draw in grey. The disabled state has no effect on the text colour either. +> - **Setting Value to a date in another month, or setting FirstDay, does not place the dates of the grid again.** After **Value** is set, the title and the two lists show the new month, but the grid still shows the dates of the month before, with no cell selected. After **FirstDay** is set, the day names move to the new order and the dates do not, so the names and the dates disagree. **Refresh** does not correct either. The dates are placed again when the selection is next moved, by a click on a cell, by **Today** or by one of the **Next** and **Previous** methods. +> - **A press near the right edge or the bottom edge can select a date that the grid does not show.** In a control 255 pixels wide, a cell is 36.43 pixels, which the control takes as 36, and a press at 252 to 254 pixels from the left selects the date of a column after the last, the first date of the next row. In a control 292 pixels high, a press at 290 or 291 pixels from the top selects a date a row below the grid. +> - **A flat grid is drawn near-black with the default GridLinesColor.** With **GridCellEffect** **Flat**, the default `&H80000010`, the system colour **vbButtonShadow**, is drawn as `&H10`, a near-black, and not as grey. A colour given as an RGB value, such as `RGB(255, 0, 0)`, is drawn as it is. +> - **Year, Month and Day can disagree with Value.** See [Adding the control to a form](#adding-the-control-to-a-form). +> - **A year outside 1900 to 2100 is not shown in the year list.** Setting **Year** or **Value** to 1899 or earlier leaves the year list blank, and to 2101 or later leaves it as it was. No error is raised, and **Value** and the title show the year. + +> [!NOTE] +> Version 1.0.7.0 of the package has these other problems. Each raises an error, or concerns how the control looks or is reached. +> +> - **Setting ValueIsNull raises run-time error 438**, *Object doesn't support this property or method*, to **True** or to **False**. The value is stored all the same, and nothing else in the control uses it. +> - **A day, month or year that makes an impossible date raises run-time error 13**, *Type mismatch*: **Day** 0 or 32, or 31 in a month of 30 days, **Month** 13 or 0, **Month** 2 when **Day** is 31, **Year** 2025 when the selected date is February 29, 2024. The property then returns the value assigned and **Value** keeps its old date, until the next valid assignment. After a **Month** of 13, **Refresh** raises an error (-2147352567, *Exception occurred.*) until **Month** is set to a valid value. +> - **Tab never moves the keyboard focus to the control**, so a user who does not click on it cannot reach the arrow keys. A click on the control, and a choice in either list, give it the focus. +> - **MouseDown is never raised.** +> - **The title is centred in the width of the control less 140 pixels**, also when the date selectors are not shown, so it stands left of the centre. +> - **The two lists are 80 and 50 pixels wide at any DPI.** At 144 DPI the month list shows `Octobe` for October, and the year list `20` for 2026. + +## Properties + +A property is read and written as *object*.**Name** [ = *value* ]. The properties are listed in alphabetical order. A default is the value of a property when the control is placed on a form. + +### BackColor +{: .no_toc } + +The background colour of the title area and of the cells of the grid, apart from the selected cell. **OLE_COLOR**. Default: light grey (`&HE0E0E0`). + +### Day +{: .no_toc } + +The day of the month of the selected date, from 1 to 31. **Integer**. Default: the day of the month on which the control was placed on the form. + +Setting **Day** selects that day of the shown month and year, and redraws the control. A day that the shown month does not have, such as 31 in June, or a value below 1 or above 31, raises run-time error 13, *Type mismatch*. **Day** then returns the value assigned, and **Value** is unchanged. See [Known problems](#known-problems-in-version-1070). + +### DayBackColor +{: .no_toc } + +The background colour of the row of day names. **OLE_COLOR**. Default: light blue (`&HDCA86F`, red 111, green 168, blue 220). + +### DayFont +{: .no_toc } + +The font of the day names. A **Font** object. Default: Arial, 8 points. + +The control keeps the object that is assigned, not a copy of it. Assigning a font with **Set** redraws the control. A change to a property of the object after that, such as its **Size**, is shown at the next paint, which the change itself causes, so **Refresh** is not needed. Two properties that are assigned the same object share it, and a change to the object changes both. + +### DayFontColor +{: .no_toc } + +The colour of the day names. **OLE_COLOR**. Default: black. The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). + +### DayLength +{: .no_toc } + +How much of each weekday name the row of day names shows. A [**DayLengthEnum**](#daylengthenum) member. Default: **MediumLength**. + +A value other than **ShortLength** and **MediumLength** shows the whole name. + +### DaysMargin +{: .no_toc } + +The space above and below the day names, in device pixels. **Integer**. Default: `5`. The height of the row of day names is the height of its text plus twice this value. At 144 DPI, raising the value from 5 to 15 moves the bottom edge of the row down by 20 pixels, twice the change, so the value is not scaled with the DPI. + +### Enabled +{: .no_toc } + +Whether the control responds to the user. **Boolean**. Default: **True**. A disabled control is drawn as the [disabled state](#disabled-state) describes, and the user cannot reach it with the mouse or the keyboard. + +### FirstDay +{: .no_toc } + +The weekday of the first column of the grid. A [**WeekDaysEnum**](#weekdaysenum) member. Default: **Monday**. + +Changing **FirstDay** at run time moves the day names at once, but the dates of the grid are placed again only when the selection next moves, so the names and the dates disagree until then. **Refresh** does not correct it. See [Known problems](#known-problems-in-version-1070). A value outside 0 to 7 raises run-time error 5, *Invalid procedure call or argument*, and **FirstDay** returns the value assigned. + +### GridCellEffect +{: .no_toc } + +How the lines between the cells look. A [**GridCellEffectEnum**](#gridcelleffectenum) member. Default: **Raised**. + +With **Flat** the lines are drawn in **GridLinesColor**, one line between two cells. **Raised** and **Sunken** draw each line in black or white, and a second line of the other colour one pixel to the right of a vertical line and one pixel below a horizontal one: for **Raised** the line is black and the second white, for **Sunken** the reverse. **GridLinesColor** has no effect with **Raised** and **Sunken**. + +### GridFont +{: .no_toc } + +The font of the day numbers in the grid. A **Font** object. Default: Arial, 8 points. The remarks for [**DayFont**](#dayfont) apply. + +### GridFontColor +{: .no_toc } + +The colour of the day numbers. **OLE_COLOR**. Default: dark blue (`&HA00000`, blue 160). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). + +### GridLinesColor +{: .no_toc } + +The colour of the lines of the grid when **GridCellEffect** is **Flat**. **OLE_COLOR**. Default: `&H80000010`, the system colour [**vbButtonShadow**](../../VBRUN/Constants/SystemColorConstants). The default is drawn as near-black, and not as the system colour: see [Known problems](#known-problems-in-version-1070). + +### Month +{: .no_toc } + +The month of the selected date, from 1 to 12. **Integer**. Default: the month in which the control was placed on the form. + +Setting **Month** selects the same day of that month in the shown year, shows the month, and updates the month list. A month number outside 1 to 12, or a month that lacks the selected day (**Day** is 31 and the month has 30 days), raises run-time error 13, *Type mismatch*. **Month** then returns the value assigned, and **Value** and the month list are unchanged. See [Known problems](#known-problems-in-version-1070). + +### MonthLength +{: .no_toc } + +How much of the month name the title shows. A [**MonthLengthEnum**](#monthlengthenum) member. Default: **LongLength**. + +A value other than **ShortLength** shows the whole name. + +### ShowDateSelectors +{: .no_toc } + +Whether the month list and the year list are shown. **Boolean**. Default: **True**. + +While the lists are shown the control is painted again continuously, and the title is centred in the width less 140 pixels whether or not they are shown. See [Known problems](#known-problems-in-version-1070). + +### ShowDays +{: .no_toc } + +Whether the row of day names is shown. **Boolean**. Default: **True**. + +### ShowHorizontalGrid +{: .no_toc } + +Whether the horizontal lines between the rows of the grid are drawn. **Boolean**. Default: **True**. + +### ShowTitle +{: .no_toc } + +Whether the title, the month and the year as text, is drawn. **Boolean**. Default: **True**. With **ShowTitle** **False** and **ShowDateSelectors** **True**, the title area keeps its height and shows only the two lists. + +### ShowVerticalGrid +{: .no_toc } + +Whether the vertical lines between the columns of the grid are drawn. **Boolean**. Default: **True**. + +### TitleFont +{: .no_toc } + +The font of the title. A **Font** object. Default: Arial, 16 points. The remarks for [**DayFont**](#dayfont) apply. + +### TitleFontColor +{: .no_toc } + +The colour of the title. **OLE_COLOR**. Default: orange (`&H80FF&`, red 255, green 128, blue 0). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-1070). + +### TitleMargin +{: .no_toc } + +The space above and below the title, in device pixels. **Integer**. Default: `5`. The height of the title area is the height of the title text plus twice this value. At 144 DPI, raising the value from 5 to 15 moves the top edge of the row of day names down by 20 pixels, twice the change. + +### Value +{: .no_toc } + +The selected date. A **Date**. Default: the date on which the control was placed on the form. + +Setting **Value** changes **Year**, **Month** and **Day** to the parts of the date, updates the two lists, and redraws the control. The time of day of the date is kept, and a **Value** with a time of day selects no cell. Setting **Value** raises no event. A String that is a date is converted; a String that is not a date raises run-time error 13, *Type mismatch*, and **Null** raises error 94. + +When the new date is in a month other than the shown one, the title and the lists show the new month, and the grid keeps the dates of the month it showed before, with no cell selected, until the selection next moves. See [Known problems](#known-problems-in-version-1070). + +### ValueIsNull +{: .no_toc } + +**Boolean**. Default: **False**. + +Setting the property raises run-time error 438, *Object doesn't support this property or method*. The value is stored all the same, and nothing else in the control uses it: it has no effect on **Value** or on how the control is drawn. See [Known problems](#known-problems-in-version-1070). + +### ValueStr +{: .no_toc } + +The selected date as a **String**. Reading it returns **Value** as **CStr** converts it: the short date of the regional settings, such as `3/15/2026` with US regional settings, followed by the time, such as `2:30:00 PM`, only when **Value** has a time of day. Setting it converts the string with **CDate** and assigns the result to **Value**. A string that **CDate** cannot read, an empty string among them, raises run-time error 13, *Type mismatch*. + +The source marks the property as temporary: it is there until the property page can edit a date. + +### Year +{: .no_toc } + +The year of the selected date. **Integer**. Default: the year in which the control was placed on the form. + +Setting **Year** selects the same day and month in that year, shows the year, and updates the year list, which holds 1900 to 2100. A year that lacks the selected day (**Day** is 29, **Month** is 2, and the year is not a leap year) raises run-time error 13, *Type mismatch*; **Year** then returns the value assigned, and **Value** is unchanged. A year outside 1900 to 2100 raises no error, but it is not in the year list. See [Known problems](#known-problems-in-version-1070). + +## Methods + +A method is called as *object*.**Name**. None takes an argument or returns a value. The methods that move the selection show the month of the new date, update the two lists, and raise no event. They do not check that the new date is in the range of the year list, 1900 to 2100: past either end, **Value** and the title follow, and the year list is blank before 1900 and unchanged after 2100. + +> [!WARNING] +> In BETA 1005, [**DateAdd**](../../../Modules/DateTime/DateAdd) with the interval `"m"`, `"q"` or `"yyyy"` does not stop at the end of a shorter month: the date overflows into the next month. **NextMonth**, **PreviousMonth**, **NextYear** and **PreviousYear** call **DateAdd**, so in BETA 1005 **NextMonth** from January 31, 2026 selects March 3, 2026, **PreviousMonth** from March 31, 2026 selects March 3, 2026, and **NextYear** from February 29, 2024 selects March 1, 2025. VB6 gives the last day of the shorter month in every such case: February 28, 2026 for the first one. + +### NextDay +{: .no_toc } + +Selects the day after the selected one. + +### NextMonth +{: .no_toc } + +Selects the same day one month after the selected one, or the last day of that month when it has no such day, so January 31 gives February 28 or 29. The change stays: **NextMonth** from February 28 gives March 28, not March 31. In BETA 1005 the result differs for the 29th to the 31st: see the warning at the start of this section. + +### NextWeek +{: .no_toc } + +Selects the day seven days after the selected one. + +### NextYear +{: .no_toc } + +Selects the same day one year after the selected one, or February 28 when the selected day is February 29 and the next year is not a leap year. In BETA 1005 the result differs for February 29: see the warning at the start of this section. + +### PreviousDay +{: .no_toc } + +Selects the day before the selected one. + +### PreviousMonth +{: .no_toc } + +Selects the same day one month before the selected one. The remarks for [**NextMonth**](#nextmonth) apply, and so does the warning at the start of this section. + +### PreviousWeek +{: .no_toc } + +Selects the day seven days before the selected one. + +### PreviousYear +{: .no_toc } + +Selects the same day one year before the selected one. The remarks for [**NextYear**](#nextyear) apply, and so does the warning at the start of this section. + +### Refresh +{: .no_toc } + +Draws the control again. It does not place the dates of the grid again, and so does not correct a grid that disagrees with the day names or with **Value** (see [Known problems](#known-problems-in-version-1070)). + +### Today +{: .no_toc } + +Selects today's date, and shows its month. The time of day of **Value** is kept: with **Value** at 3:30 PM on another day, **Today** gives today at 3:30 PM. + +```tb check_build +Private Sub ShowMoves() + xCalendar1.Value = #6/15/2026# + xCalendar1.NextMonth ' July 15, 2026 + xCalendar1.PreviousYear ' July 15, 2025 + xCalendar1.NextWeek ' July 22, 2025 + xCalendar1.Today + Debug.Print xCalendar1.Value +End Sub +``` + +## Events + +An event is handled by a procedure named *control*\_*event*, such as `xCalendar1_AfterUpdate`. + +### AfterUpdate +{: .no_toc } + +Raised after a left click on a day has moved the selection. + +Syntax: *object*\_**AfterUpdate**( ) + +### BeforeUpdate +{: .no_toc } + +Raised when the user clicks a day, before the selection is moved. + +Syntax: *object*\_**BeforeUpdate**( *Cancel* **As Integer** ) + +*Cancel* +: Passed in as `0`. Set to any value other than `0`, such as **True**, to cancel the selection: the date and the drawing are not changed and **AfterUpdate** is not raised. **MouseUp** and **Click** are still raised. The event does not say which date was clicked, and **Value** is still the date selected before. + +```tb check_build +Private SelectionLocked As Boolean + +Private Sub xCalendar1_BeforeUpdate(Cancel As Integer) + If SelectionLocked Then Cancel = True +End Sub + +Private Sub xCalendar1_AfterUpdate() + Debug.Print "Selected " & xCalendar1.Value +End Sub +``` + +### Click +{: .no_toc } + +Raised when the user clicks the control with any mouse button, after the button is released, on a day or elsewhere. + +Syntax: *object*\_**Click**( ) + +### DblClick +{: .no_toc } + +Raised when the user double-clicks the control, after the first **Click** and before the second **MouseUp**. + +Syntax: *object*\_**DblClick**( ) + +### KeyDown +{: .no_toc } + +Raised for each key pressed while the control has the focus, Tab excepted, after the control has handled the keys listed in [With the keyboard](#with-the-keyboard). A key pressed with Alt held raises it with Alt in *Shift*. + +Syntax: *object*\_**KeyDown**( *KeyCode* **As Integer**, *Shift* **As Integer** ) + +*KeyCode* +: The [key code](../../VBRUN/Constants/KeyCodeConstants) of the key. + +*Shift* +: The state of the Shift, Ctrl and Alt keys, as [**ShiftConstants**](../../VBRUN/Constants/ShiftConstants) gives it. + +### KeyPress +{: .no_toc } + +Raised when the user presses a key that produces a character, Enter, Space and Tab among them. + +Syntax: *object*\_**KeyPress**( *KeyAscii* **As Integer** ) + +*KeyAscii* +: The character code of the key. + +### KeyUp +{: .no_toc } + +Raised when the user releases a key. + +Syntax: *object*\_**KeyUp**( *KeyCode* **As Integer**, *Shift* **As Integer** ) + +The parameters are those of [**KeyDown**](#keydown). + +### MouseDown +{: .no_toc } + +Declared, and never raised. A handler for it is never called. See [Known problems](#known-problems-in-version-1070). + +Syntax: *object*\_**MouseDown**( *Button* **As Integer**, *Shift* **As Integer**, *X* **As Single**, *Y* **As Single** ) + +### MouseMove +{: .no_toc } + +Raised when the pointer moves over the control. + +Syntax: *object*\_**MouseMove**( *Button* **As Integer**, *Shift* **As Integer**, *X* **As Single**, *Y* **As Single** ) + +*Button* +: The mouse buttons that are down. + +*Shift* +: The state of the Shift, Ctrl and Alt keys. + +*X*, *Y* +: The position of the pointer in device pixels, from the top left corner of the control. The values are not scaled to the size of the control at design time: at 144 DPI a control that is 256 pixels wide at design time is 384 pixels wide, and a press 192 pixels from its left edge gives *X* 192. + +### MouseUp +{: .no_toc } + +Raised when the user releases a mouse button over the control. + +Syntax: *object*\_**MouseUp**( *Button* **As Integer**, *Shift* **As Integer**, *X* **As Single**, *Y* **As Single** ) + +The parameters are those of [**MouseMove**](#mousemove). + +### NewMonth +{: .no_toc } + +Raised after the user chooses a month in the month list, once the selection has moved to the same day of that month. + +Syntax: *object*\_**NewMonth**( ) + +A change of month by any other means, such as a click on a day of another month, a key, a method or a property, does not raise it. Choosing a month in which the selected day does not exist can hang the program: see [Known problems](#known-problems-in-version-1070). + +### NewYear +{: .no_toc } + +Raised after the user chooses a year in the year list. + +Syntax: *object*\_**NewYear**( ) + +A change of year by any other means does not raise it. Choosing a year in which the selected day does not exist, February 29 in a year that is not a leap year, can hang the program: see [Known problems](#known-problems-in-version-1070). + +## Enumerations + +### DayLengthEnum +{: .no_toc } + +The value of [**DayLength**](#daylength). + +| Member | Value | Shows | +|:-------|------:|:------| +| **ShortLength** | 0 | the first letter, such as `M` | +| **MediumLength** | 1 | the first three letters, such as `Mon` | +| **LongLength** | 2 | the whole name, such as `Monday` | + +### GridCellEffectEnum +{: .no_toc } + +The value of [**GridCellEffect**](#gridcelleffect). + +| Member | Value | Lines | +|:-------|------:|:------| +| **Flat** | 0 | one line in **GridLinesColor** | +| **Raised** | 1 | a black line and a white line beside it | +| **Sunken** | 2 | a white line and a black line beside it | + +### MonthLengthEnum +{: .no_toc } + +The value of [**MonthLength**](#monthlength). + +| Member | Value | Shows | +|:-------|------:|:------| +| **ShortLength** | 0 | the first three letters, such as `Oct` | +| **LongLength** | 1 | the whole name, such as `October` | + +### WeekDaysEnum +{: .no_toc } + +The value of [**FirstDay**](#firstday). The values are those of the [**VbDayOfWeek**](../../../Modules/Constants/VbDayOfWeek) constants. + +| Member | Value | +|:-------|------:| +| **Sunday** | 1 | +| **Monday** | 2 | +| **Tuesday** | 3 | +| **Wednesday** | 4 | +| **Thursday** | 5 | +| **Friday** | 6 | +| **Saturday** | 7 | + +```tb check_build +Private Sub Form_Load() + With xCalendar1 + .FirstDay = WeekDaysEnum.Sunday + .DayLength = DayLengthEnum.LongLength + .MonthLength = MonthLengthEnum.ShortLength + .GridCellEffect = GridCellEffectEnum.Flat + .GridLinesColor = RGB(200, 200, 200) + .DayBackColor = RGB(220, 230, 241) + .ShowHorizontalGrid = False + End With +End Sub +``` + +## See Also + +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project +- [Toolbox](../../../IDE/Project/Toolbox) -- placing a control on a form +- [MonthView](../../WinNativeCommonCtls/MonthView) class -- the month calendar of the Windows common controls +- [DTPicker](../../WinNativeCommonCtls/DTPicker) class -- the date and time picker of the same package diff --git a/docs/_book.yml b/docs/_book.yml index 9e8c25b2c..8cb501ac9 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -579,6 +579,13 @@ parts: landing_is_target: true outline_closed: true + - title: xCalendar Package + subtitle: A calendar control, in place of the Calendar control of MSCAL.OCX + landing_page: /tB/Packages/Contributed/xCalendar/ + page: /tB/Packages/Contributed/xCalendar/ + landing_is_target: true + outline_closed: true + - title: Appendices outline_closed: false diff --git a/indexer/snapshots/contributed/xCalendar/api.json b/indexer/snapshots/contributed/xCalendar/api.json new file mode 100644 index 000000000..918b74c15 --- /dev/null +++ b/indexer/snapshots/contributed/xCalendar/api.json @@ -0,0 +1,304 @@ +{ + "format": 3, + "package": "xCalendar", + "project": "xCalendar", + "version": "1.0.7.0", + "types": [ + { + "name": "xCalendar", + "kind": "class", + "vis": "public", + "file": "xCalendar.twin", + "signature": "Class xCalendar", + "attrs": [{"name":"FormDesignerId","args":"\"3F6E0F31-7ED4-498F-91BF-B6BA27A52E6D\""},{"name":"ClassId","args":"\"F178D13A-5115-463B-9A49-775B7D3B1A9D\""},{"name":"InterfaceId","args":"\"93C5008A-DB94-42DA-BF2E-19B36FC902D2\""},{"name":"EventInterfaceId","args":"\"AE960030-B636-4F85-9CE1-BC6284E062A4\""},{"name":"COMControl"}], + "members": [ + {"name":"AfterUpdate","kind":"event","vis":"public","signature":"Event AfterUpdate()"}, + {"name":"BackColor","kind":"property","vis":"public","signature":"Public Property Get BackColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Gets the main background color"}, + {"name":"BackColor","kind":"property","vis":"public","signature":"Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)","accessor":"Let","description":"Sets the main background color"}, + {"name":"BeforeUpdate","kind":"event","vis":"public","signature":"Event BeforeUpdate(Cancel As Integer)"}, + {"name":"BitBlt","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function BitBlt Lib \"gdi32\" (ByVal hDestDC As LongPtr, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As LongPtr, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long","returnType":"Long"}, + {"name":"BypassClick","kind":"field","vis":"private","signature":"Private BypassClick As Boolean"}, + {"name":"CellCol","kind":"field","vis":"private","signature":"Private CellCol As Integer"}, + {"name":"CellHeight","kind":"field","vis":"private","signature":"Private CellHeight As Double"}, + {"name":"CellRow","kind":"field","vis":"private","signature":"Private CellRow As Integer"}, + {"name":"CellWidth","kind":"field","vis":"private","signature":"Private CellWidth As Double"}, + {"name":"Click","kind":"event","vis":"public","signature":"Event Click()"}, + {"name":"CreatePen","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CreatePen Lib \"gdi32\" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long","returnType":"Long"}, + {"name":"CreateSolidBrush","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CreateSolidBrush Lib \"gdi32\" (ByVal crColor As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"CtrlCurrentState","kind":"field","vis":"private","signature":"Private CtrlCurrentState As CtrlStateEnum"}, + {"name":"DT_BOTTOM","kind":"const","vis":"private","signature":"Private Const DT_BOTTOM = &H8"}, + {"name":"DT_CENTER","kind":"const","vis":"private","signature":"Private Const DT_CENTER = &H1"}, + {"name":"DT_LEFT","kind":"const","vis":"private","signature":"Private Const DT_LEFT = &H0"}, + {"name":"DT_RIGHT","kind":"const","vis":"private","signature":"Private Const DT_RIGHT = &H2"}, + {"name":"DT_SINGLELINE","kind":"const","vis":"private","signature":"Private Const DT_SINGLELINE = &H20"}, + {"name":"DT_TOP","kind":"const","vis":"private","signature":"Private Const DT_TOP = &H0"}, + {"name":"DT_VCENTER","kind":"const","vis":"private","signature":"Private Const DT_VCENTER = &H4"}, + {"name":"DT_WORDBREAK","kind":"const","vis":"private","signature":"Private Const DT_WORDBREAK = &H10"}, + {"name":"DatesArr","kind":"field","vis":"private","signature":"Private DatesArr(5, 6) As Date"}, + {"name":"Day","kind":"property","vis":"public","signature":"Public Property Get Day() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the selected day of the month"}, + {"name":"Day","kind":"property","vis":"public","signature":"Public Property Let Day(ByVal New_Day As Integer)","accessor":"Let","description":"Sets the selected day of the month"}, + {"name":"DayBackColor","kind":"property","vis":"public","signature":"Public Property Get DayBackColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Gets the background color in the days area"}, + {"name":"DayBackColor","kind":"property","vis":"public","signature":"Public Property Let DayBackColor(ByVal New_DayBackColor As OLE_COLOR)","accessor":"Let","description":"Sets the background color in the days area"}, + {"name":"DayFont","kind":"property","vis":"public","signature":"Public Property Get DayFont() As Font","returnType":"Font","accessor":"Get","description":"Gets the font in the days area"}, + {"name":"DayFont","kind":"property","vis":"public","signature":"Public Property Set DayFont(ByVal New_DayFont As Font)","accessor":"Set","description":"Sets the font in the days area"}, + {"name":"DayFontColor","kind":"property","vis":"public","signature":"Public Property Get DayFontColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Sets the font color in the days area"}, + {"name":"DayFontColor","kind":"property","vis":"public","signature":"Public Property Let DayFontColor(ByVal New_DayFontColor As OLE_COLOR)","accessor":"Let","description":"Sets the font color in the days area"}, + {"name":"DayLength","kind":"property","vis":"public","signature":"Public Property Get DayLength() As DayLengthEnum","returnType":"DayLengthEnum","accessor":"Get","description":"Gets the length of the day name displayed"}, + {"name":"DayLength","kind":"property","vis":"public","signature":"Public Property Let DayLength(ByVal New_DayLength As DayLengthEnum)","accessor":"Let","description":"Sets the length of the day name displayed"}, + {"name":"DaysHeight","kind":"field","vis":"private","signature":"Private DaysHeight As Long"}, + {"name":"DaysMargin","kind":"property","vis":"public","signature":"Public Property Get DaysMargin() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the days area top and bottom margin"}, + {"name":"DaysMargin","kind":"property","vis":"public","signature":"Public Property Let DaysMargin(ByVal New_DaysMargin As Integer)","accessor":"Let","description":"Sets the days area top and bottom margin"}, + {"name":"DblClick","kind":"event","vis":"public","signature":"Event DblClick()"}, + {"name":"DeleteObject","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function DeleteObject Lib \"gdi32\" (ByVal hObject As LongPtr) As Long","returnType":"Long"}, + {"name":"Draw","kind":"sub","vis":"private","signature":"Private Sub Draw()"}, + {"name":"DrawLine","kind":"sub","vis":"private","signature":"Private Sub DrawLine(hDC As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal vWidth As Long, ByVal vColor As Long)"}, + {"name":"DrawRect","kind":"sub","vis":"private","signature":"Private Sub DrawRect(hDC As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal vWidth As Long, ByVal vColor As Long)"}, + {"name":"DrawRectFilled","kind":"sub","vis":"private","signature":"Private Sub DrawRectFilled(hDC As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal vColor As Long)"}, + {"name":"DrawTextW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function DrawTextW Lib \"user32\" (ByVal hdc As LongPtr, ByVal lpStr As LongPtr, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long","returnType":"Long"}, + {"name":"Enabled","kind":"property","vis":"public","signature":"Public Property Get Enabled() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the Enabled state of the control"}, + {"name":"Enabled","kind":"property","vis":"public","signature":"Public Property Let Enabled(ByVal New_Enabled As Boolean)","accessor":"Let","description":"Sets the Enabled state of the control"}, + {"name":"FillRect","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FillRect Lib \"user32\" (ByVal hDC As LongPtr, lpRect As RECT, ByVal hBrush As LongPtr) As Long","returnType":"Long"}, + {"name":"FirstDate","kind":"field","vis":"private","signature":"Private FirstDate As Date"}, + {"name":"FirstDay","kind":"property","vis":"public","signature":"Public Property Get FirstDay() As WeekDaysEnum","returnType":"WeekDaysEnum","accessor":"Get","description":"Gets the first day of the week"}, + {"name":"FirstDay","kind":"property","vis":"public","signature":"Public Property Let FirstDay(ByVal New_FirstDay As WeekDaysEnum)","accessor":"Let","description":"Sets the first day of the week"}, + {"name":"FontDup","kind":"function","vis":"private","signature":"Private Function FontDup(vFont As Font) As Font","returnType":"Font"}, + {"name":"GetFirstDate","kind":"function","vis":"private","signature":"Private Function GetFirstDate() As Date","returnType":"Date"}, + {"name":"GotoDate","kind":"sub","vis":"private","signature":"Private Sub GotoDate(ByVal Interval As String, ByVal Number As Long)"}, + {"name":"GridCellEffect","kind":"property","vis":"public","signature":"Public Property Get GridCellEffect() As GridCellEffectEnum","returnType":"GridCellEffectEnum","accessor":"Get","description":"Gets the effect of the grid cell lines"}, + {"name":"GridCellEffect","kind":"property","vis":"public","signature":"Public Property Let GridCellEffect(ByVal New_GridCellEffect As GridCellEffectEnum)","accessor":"Let","description":"Sets the effect of the grid cell lines"}, + {"name":"GridFont","kind":"property","vis":"public","signature":"Public Property Get GridFont() As Font","returnType":"Font","accessor":"Get","description":"Sets the grid font"}, + {"name":"GridFont","kind":"property","vis":"public","signature":"Public Property Set GridFont(ByVal New_GridFont As Font)","accessor":"Set","description":"Sets the grid font"}, + {"name":"GridFontColor","kind":"property","vis":"public","signature":"Public Property Get GridFontColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Gets the color of the grid font"}, + {"name":"GridFontColor","kind":"property","vis":"public","signature":"Public Property Let GridFontColor(ByVal New_GridFontColor As OLE_COLOR)","accessor":"Let","description":"Sets the color of the grid font"}, + {"name":"GridLinesColor","kind":"property","vis":"public","signature":"Public Property Get GridLinesColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Gets the color of the grid lines"}, + {"name":"GridLinesColor","kind":"property","vis":"public","signature":"Public Property Let GridLinesColor(ByVal New_GridLinesColor As OLE_COLOR)","accessor":"Let","description":"Sets the color of the grid lines"}, + {"name":"KeyDown","kind":"event","vis":"public","signature":"Event KeyDown(KeyCode As Integer, Shift As Integer)"}, + {"name":"KeyPress","kind":"event","vis":"public","signature":"Event KeyPress(KeyAscii As Integer)"}, + {"name":"KeyUp","kind":"event","vis":"public","signature":"Event KeyUp(KeyCode As Integer, Shift As Integer)"}, + {"name":"LineTo","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function LineTo Lib \"gdi32\" (ByVal hDC As LongPtr, ByVal X As Long, ByVal Y As Long) As Long","returnType":"Long"}, + {"name":"Month","kind":"property","vis":"public","signature":"Public Property Get Month() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the selected month"}, + {"name":"Month","kind":"property","vis":"public","signature":"Public Property Let Month(ByVal New_Month As Integer)","accessor":"Let","description":"Sets the selected month"}, + {"name":"MonthLength","kind":"property","vis":"public","signature":"Public Property Get MonthLength() As MonthLengthEnum","returnType":"MonthLengthEnum","accessor":"Get","description":"Gets the month name displayed length"}, + {"name":"MonthLength","kind":"property","vis":"public","signature":"Public Property Let MonthLength(ByVal New_MonthLength As MonthLengthEnum)","accessor":"Let","description":"Sets the month name displayed length"}, + {"name":"MouseDown","kind":"event","vis":"public","signature":"Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"MouseMove","kind":"event","vis":"public","signature":"Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"MouseUp","kind":"event","vis":"public","signature":"Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"MoveToEx","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MoveToEx Lib \"gdi32\" (ByVal hDC As LongPtr, ByVal X As Long, ByVal Y As Long, lpPoint As POINTAPI) As Long","returnType":"Long"}, + {"name":"NewMonth","kind":"event","vis":"public","signature":"Event NewMonth()"}, + {"name":"NewYear","kind":"event","vis":"public","signature":"Event NewYear()"}, + {"name":"NextDay","kind":"sub","vis":"public","signature":"Public Sub NextDay()","description":"Selects the next day"}, + {"name":"NextMonth","kind":"sub","vis":"public","signature":"Public Sub NextMonth()","description":"Moves the selection one month in the future"}, + {"name":"NextWeek","kind":"sub","vis":"public","signature":"Public Sub NextWeek()","description":"Moves the selection one week in the future"}, + {"name":"NextYear","kind":"sub","vis":"public","signature":"Public Sub NextYear()","description":"Moves the selection one year in the future"}, + {"name":"PreviousDay","kind":"sub","vis":"public","signature":"Public Sub PreviousDay()","description":"Selects the previous day"}, + {"name":"PreviousMonth","kind":"sub","vis":"public","signature":"Public Sub PreviousMonth()","description":"Moves the selection one month in the past"}, + {"name":"PreviousWeek","kind":"sub","vis":"public","signature":"Public Sub PreviousWeek()","description":"Moves the selection one week in the past"}, + {"name":"PreviousYear","kind":"sub","vis":"public","signature":"Public Sub PreviousYear()","description":"Moves the selection one year in the past"}, + {"name":"Rectangle","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function Rectangle Lib \"gdi32\" (ByVal hDC As LongPtr, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long","returnType":"Long"}, + {"name":"Refresh","kind":"sub","vis":"public","signature":"Public Sub Refresh()","description":"Forces a complete repaint of the control"}, + {"name":"SelectObject","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SelectObject Lib \"gdi32\" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"SetCtrlState","kind":"sub","vis":"private","signature":"Private Sub SetCtrlState(ByVal NewState As CtrlStateEnum, Optional RedrawCtrl As Boolean = True)"}, + {"name":"SetupComboBoxes","kind":"sub","vis":"private","signature":"Private Sub SetupComboBoxes()"}, + {"name":"ShowDateSelectors","kind":"property","vis":"public","signature":"Public Property Get ShowDateSelectors() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the month and year selectors"}, + {"name":"ShowDateSelectors","kind":"property","vis":"public","signature":"Public Property Let ShowDateSelectors(ByVal New_ShowDateSelectors As Boolean)","accessor":"Let","description":"Sets the visibility of the month and year selectors"}, + {"name":"ShowDays","kind":"property","vis":"public","signature":"Public Property Get ShowDays() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the day names area"}, + {"name":"ShowDays","kind":"property","vis":"public","signature":"Public Property Let ShowDays(ByVal New_ShowDays As Boolean)","accessor":"Let","description":"Sets the visibility of the day names area"}, + {"name":"ShowHorizontalGrid","kind":"property","vis":"public","signature":"Public Property Get ShowHorizontalGrid() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the horizontal grid lines"}, + {"name":"ShowHorizontalGrid","kind":"property","vis":"public","signature":"Public Property Let ShowHorizontalGrid(ByVal New_ShowHorizontalGrid As Boolean)","accessor":"Let","description":"Sets the visibility of the horizontal grid lines"}, + {"name":"ShowTitle","kind":"property","vis":"public","signature":"Public Property Get ShowTitle() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the title"}, + {"name":"ShowTitle","kind":"property","vis":"public","signature":"Public Property Let ShowTitle(ByVal New_ShowTitle As Boolean)","accessor":"Let","description":"Sets the visibility of the title"}, + {"name":"ShowVerticalGrid","kind":"property","vis":"public","signature":"Public Property Get ShowVerticalGrid() As Boolean","returnType":"Boolean","accessor":"Get","description":"Gets the visibility of the vertical grid lines"}, + {"name":"ShowVerticalGrid","kind":"property","vis":"public","signature":"Public Property Let ShowVerticalGrid(ByVal New_ShowVerticalGrid As Boolean)","accessor":"Let","description":"Sets the visibility of the vertical grid lines"}, + {"name":"TitleFont","kind":"property","vis":"public","signature":"Public Property Get TitleFont() As Font","returnType":"Font","accessor":"Get","description":"Gets the font of the title"}, + {"name":"TitleFont","kind":"property","vis":"public","signature":"Public Property Set TitleFont(ByVal New_TitleFont As Font)","accessor":"Set","description":"Sets the font of the title"}, + {"name":"TitleFontColor","kind":"property","vis":"public","signature":"Public Property Get TitleFontColor() As OLE_COLOR","returnType":"OLE_COLOR","accessor":"Get","description":"Gets the color of the title font"}, + {"name":"TitleFontColor","kind":"property","vis":"public","signature":"Public Property Let TitleFontColor(ByVal New_TitleFontColor As OLE_COLOR)","accessor":"Let","description":"Sets the color of the title font"}, + {"name":"TitleHeight","kind":"field","vis":"private","signature":"Private TitleHeight As Long"}, + {"name":"TitleMargin","kind":"property","vis":"public","signature":"Public Property Get TitleMargin() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the title area top and bottom margin"}, + {"name":"TitleMargin","kind":"property","vis":"public","signature":"Public Property Let TitleMargin(ByVal New_TitleMargin As Integer)","accessor":"Let","description":"Sets the title area top and bottom margin"}, + {"name":"Today","kind":"sub","vis":"public","signature":"Public Sub Today()","description":"Moves the selection to today"}, + {"name":"UserControl_Click","kind":"sub","vis":"private","signature":"Private Sub UserControl_Click()"}, + {"name":"UserControl_DblClick","kind":"sub","vis":"private","signature":"Private Sub UserControl_DblClick()"}, + {"name":"UserControl_GotFocus","kind":"sub","vis":"private","signature":"Private Sub UserControl_GotFocus()"}, + {"name":"UserControl_InitProperties","kind":"sub","vis":"private","signature":"Private Sub UserControl_InitProperties()"}, + {"name":"UserControl_KeyDown","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyDown(KeyCode As Integer, Shift As Integer)"}, + {"name":"UserControl_KeyPress","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyPress(KeyAscii As Integer)"}, + {"name":"UserControl_KeyUp","kind":"sub","vis":"private","signature":"Private Sub UserControl_KeyUp(KeyCode As Integer, Shift As Integer)"}, + {"name":"UserControl_LostFocus","kind":"sub","vis":"private","signature":"Private Sub UserControl_LostFocus()"}, + {"name":"UserControl_MouseDown","kind":"sub","vis":"private","signature":"Private Sub UserControl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"UserControl_MouseMove","kind":"sub","vis":"private","signature":"Private Sub UserControl_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"UserControl_MouseUp","kind":"sub","vis":"private","signature":"Private Sub UserControl_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)"}, + {"name":"UserControl_Paint","kind":"sub","vis":"private","signature":"Private Sub UserControl_Paint()"}, + {"name":"UserControl_ReadProperties","kind":"sub","vis":"private","signature":"Private Sub UserControl_ReadProperties(PropBag As PropertyBag)"}, + {"name":"UserControl_Resize","kind":"sub","vis":"private","signature":"Private Sub UserControl_Resize()"}, + {"name":"UserControl_WriteProperties","kind":"sub","vis":"private","signature":"Private Sub UserControl_WriteProperties(PropBag As PropertyBag)"}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Get Value() As Date","returnType":"Date","accessor":"Get","description":"Gets the calendar value"}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Let Value(ByVal New_Value As Date)","accessor":"Let","description":"Sets the calendar value"}, + {"name":"ValueIsNull","kind":"property","vis":"public","signature":"Public Property Get ValueIsNull() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ValueIsNull","kind":"property","vis":"public","signature":"Public Property Let ValueIsNull(ByVal New_ValueIsNull As Boolean)","accessor":"Let"}, + {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Get ValueStr() As String","returnType":"String","accessor":"Get","description":"Gets the calendar value as string"}, + {"name":"ValueStr","kind":"property","vis":"public","signature":"Public Property Let ValueStr(ByVal NewValueStr As String)","accessor":"Let","description":"Sets the calendar value as string"}, + {"name":"Year","kind":"property","vis":"public","signature":"Public Property Get Year() As Integer","returnType":"Integer","accessor":"Get","description":"Gets the year of the selected date"}, + {"name":"Year","kind":"property","vis":"public","signature":"Public Property Let Year(ByVal New_Year As Integer)","accessor":"Let","description":"Sets the year of the selected date"}, + {"name":"cmbMonth_Click","kind":"sub","vis":"private","signature":"Private Sub cmbMonth_Click()"}, + {"name":"cmbYear_Click","kind":"sub","vis":"private","signature":"Private Sub cmbYear_Click()"}, + {"name":"m_Day","kind":"field","vis":"private","signature":"Dim m_Day As Integer"}, + {"name":"m_DayBackColor","kind":"field","vis":"private","signature":"Dim m_DayBackColor As OLE_COLOR"}, + {"name":"m_DayBackDisabledColor","kind":"field","vis":"private","signature":"Dim m_DayBackDisabledColor As OLE_COLOR"}, + {"name":"m_DayFont","kind":"field","vis":"private","signature":"Dim m_DayFont As StdFont"}, + {"name":"m_DayFontColor","kind":"field","vis":"private","signature":"Dim m_DayFontColor As OLE_COLOR"}, + {"name":"m_DayFontDisabledColor","kind":"field","vis":"private","signature":"Dim m_DayFontDisabledColor As OLE_COLOR"}, + {"name":"m_DayLength","kind":"field","vis":"private","signature":"Dim m_DayLength As Integer"}, + {"name":"m_DaysMargin","kind":"field","vis":"private","signature":"Dim m_DaysMargin As Integer"}, + {"name":"m_FirstDay","kind":"field","vis":"private","signature":"Dim m_FirstDay As Integer"}, + {"name":"m_GridCellEffect","kind":"field","vis":"private","signature":"Dim m_GridCellEffect As Integer"}, + {"name":"m_GridFont","kind":"field","vis":"private","signature":"Dim m_GridFont As StdFont"}, + {"name":"m_GridFontColor","kind":"field","vis":"private","signature":"Dim m_GridFontColor As OLE_COLOR"}, + {"name":"m_GridFontDisabledColor","kind":"field","vis":"private","signature":"Dim m_GridFontDisabledColor As OLE_COLOR"}, + {"name":"m_GridLinesColor","kind":"field","vis":"private","signature":"Dim m_GridLinesColor As OLE_COLOR"}, + {"name":"m_GridLinesDisabledColor","kind":"field","vis":"private","signature":"Dim m_GridLinesDisabledColor As OLE_COLOR"}, + {"name":"m_Month","kind":"field","vis":"private","signature":"Dim m_Month As Integer"}, + {"name":"m_MonthLength","kind":"field","vis":"private","signature":"Dim m_MonthLength As Integer"}, + {"name":"m_ShowDateSelectors","kind":"field","vis":"private","signature":"Dim m_ShowDateSelectors As Boolean"}, + {"name":"m_ShowDays","kind":"field","vis":"private","signature":"Dim m_ShowDays As Boolean"}, + {"name":"m_ShowHorizontalGrid","kind":"field","vis":"private","signature":"Dim m_ShowHorizontalGrid As Boolean"}, + {"name":"m_ShowTitle","kind":"field","vis":"private","signature":"Dim m_ShowTitle As Boolean"}, + {"name":"m_ShowVerticalGrid","kind":"field","vis":"private","signature":"Dim m_ShowVerticalGrid As Boolean"}, + {"name":"m_TitleFont","kind":"field","vis":"private","signature":"Dim m_TitleFont As StdFont"}, + {"name":"m_TitleFontColor","kind":"field","vis":"private","signature":"Dim m_TitleFontColor As OLE_COLOR"}, + {"name":"m_TitleFontDisabledColor","kind":"field","vis":"private","signature":"Dim m_TitleFontDisabledColor As OLE_COLOR"}, + {"name":"m_TitleMargin","kind":"field","vis":"private","signature":"Dim m_TitleMargin As Integer"}, + {"name":"m_Value","kind":"field","vis":"private","signature":"Dim m_Value As Date"}, + {"name":"m_ValueIsNull","kind":"field","vis":"private","signature":"Dim m_ValueIsNull As Boolean"}, + {"name":"m_Year","kind":"field","vis":"private","signature":"Dim m_Year As Integer"}, + {"name":"m_def_Day","kind":"const","vis":"private","signature":"Const m_def_Day = 1"}, + {"name":"m_def_DayBackColor","kind":"const","vis":"private","signature":"Const m_def_DayBackColor = &HDCA86F"}, + {"name":"m_def_DayFontColor","kind":"const","vis":"private","signature":"Const m_def_DayFontColor = 0"}, + {"name":"m_def_DayLength","kind":"const","vis":"private","signature":"Const m_def_DayLength = 1"}, + {"name":"m_def_DaysMargin","kind":"const","vis":"private","signature":"Const m_def_DaysMargin = 5"}, + {"name":"m_def_FirstDay","kind":"const","vis":"private","signature":"Const m_def_FirstDay = 2"}, + {"name":"m_def_GridCellEffect","kind":"const","vis":"private","signature":"Const m_def_GridCellEffect = 1"}, + {"name":"m_def_GridFontColor","kind":"const","vis":"private","signature":"Const m_def_GridFontColor = &HA00000"}, + {"name":"m_def_GridLinesColor","kind":"const","vis":"private","signature":"Const m_def_GridLinesColor = &H80000010"}, + {"name":"m_def_Month","kind":"const","vis":"private","signature":"Const m_def_Month = 1"}, + {"name":"m_def_MonthLength","kind":"const","vis":"private","signature":"Const m_def_MonthLength = 1"}, + {"name":"m_def_ShowDateSelectors","kind":"const","vis":"private","signature":"Const m_def_ShowDateSelectors = True"}, + {"name":"m_def_ShowDays","kind":"const","vis":"private","signature":"Const m_def_ShowDays = True"}, + {"name":"m_def_ShowHorizontalGrid","kind":"const","vis":"private","signature":"Const m_def_ShowHorizontalGrid = True"}, + {"name":"m_def_ShowTitle","kind":"const","vis":"private","signature":"Const m_def_ShowTitle = True"}, + {"name":"m_def_ShowVerticalGrid","kind":"const","vis":"private","signature":"Const m_def_ShowVerticalGrid = True"}, + {"name":"m_def_TitleFontColor","kind":"const","vis":"private","signature":"Const m_def_TitleFontColor = &H80FF&"}, + {"name":"m_def_TitleMargin","kind":"const","vis":"private","signature":"Const m_def_TitleMargin = 5"}, + {"name":"m_def_ValueIsNull","kind":"const","vis":"private","signature":"Const m_def_ValueIsNull = 0"}, + {"name":"m_def_Year","kind":"const","vis":"private","signature":"Const m_def_Year = 2000"}, + {"name":"picFocus_KeyDown","kind":"sub","vis":"private","signature":"Private Sub picFocus_KeyDown(KeyCode As Integer, Shift As Integer)"}, + {"name":"s_DayBackColor","kind":"field","vis":"private","signature":"Dim s_DayBackColor As OLE_COLOR"}, + {"name":"s_DayFontColor","kind":"field","vis":"private","signature":"Dim s_DayFontColor As OLE_COLOR"}, + {"name":"s_GridFontColor","kind":"field","vis":"private","signature":"Dim s_GridFontColor As OLE_COLOR"}, + {"name":"s_GridLinesColor","kind":"field","vis":"private","signature":"Dim s_GridLinesColor As OLE_COLOR"}, + {"name":"s_TitleFontColor","kind":"field","vis":"private","signature":"Dim s_TitleFontColor As OLE_COLOR"} + ] + }, + { + "name": "CtrlStateEnum", + "kind": "enum", + "in": "xCalendar", + "vis": "private", + "public": false, + "file": "xCalendar.twin", + "signature": "Private Enum CtrlStateEnum", + "values": [ + {"name":"StateEnabled","value":"0"}, + {"name":"StateDisabled","value":"1"}, + {"name":"StateGotFucus","value":"2"} + ] + }, + { + "name": "DayLengthEnum", + "kind": "enum", + "in": "xCalendar", + "vis": "public", + "file": "xCalendar.twin", + "signature": "Public Enum DayLengthEnum", + "values": [ + {"name":"ShortLength","value":"0"}, + {"name":"MediumLength","value":"1"}, + {"name":"LongLength","value":"2"} + ] + }, + { + "name": "GridCellEffectEnum", + "kind": "enum", + "in": "xCalendar", + "vis": "public", + "file": "xCalendar.twin", + "signature": "Public Enum GridCellEffectEnum", + "values": [ + {"name":"Flat"}, + {"name":"Raised"}, + {"name":"Sunken"} + ] + }, + { + "name": "MonthLengthEnum", + "kind": "enum", + "in": "xCalendar", + "vis": "public", + "file": "xCalendar.twin", + "signature": "Public Enum MonthLengthEnum", + "values": [ + {"name":"ShortLength","value":"0"}, + {"name":"LongLength","value":"1"} + ] + }, + { + "name": "POINTAPI", + "kind": "type", + "in": "xCalendar", + "vis": "private", + "public": false, + "file": "xCalendar.twin", + "signature": "Private Type POINTAPI", + "fields": [ + {"name":"X","vis":"public","signature":"X As Long"}, + {"name":"Y","vis":"public","signature":"Y As Long"} + ], + "members": [] + }, + { + "name": "RECT", + "kind": "type", + "in": "xCalendar", + "vis": "private", + "public": false, + "file": "xCalendar.twin", + "signature": "Private Type RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Long"}, + {"name":"Top","vis":"public","signature":"Top As Long"}, + {"name":"Right","vis":"public","signature":"Right As Long"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Long"} + ], + "members": [] + }, + { + "name": "WeekDaysEnum", + "kind": "enum", + "in": "xCalendar", + "vis": "public", + "file": "xCalendar.twin", + "signature": "Public Enum WeekDaysEnum", + "values": [ + {"name":"Sunday","value":"1"}, + {"name":"Monday","value":"2"}, + {"name":"Tuesday","value":"3"}, + {"name":"Wednesday","value":"4"}, + {"name":"Thursday","value":"5"}, + {"name":"Friday","value":"6"}, + {"name":"Saturday","value":"7"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/README.md b/test/README.md index 96720a624..1c8ebf541 100644 --- a/test/README.md +++ b/test/README.md @@ -16,7 +16,8 @@ is input for three tools: `lanes.mjs`, and its scenario files use `scenario` from `addin/scenario.mjs`. - `package-probes/` for a person documenting a contributed package: a probe of what a package does at run time that only a built program can show, run by hand and outside every - gate, with a README of its own. `TBMANLIB_Dialog/` operates the package's dialogs. + gate, with a README of its own. `TBMANLIB_Dialog/` operates the package's dialogs, and + `xCalendar/` the package's calendar control on a form. If a fixture case has just gone red, start at [The invariant, and how it breaks](#the-invariant-and-how-it-breaks) --- the commit that broke it need not have touched diff --git a/test/example-projects/contributed-xCalendar/Settings b/test/example-projects/contributed-xCalendar/Settings new file mode 100644 index 000000000..bd5cc6b7e --- /dev/null +++ b/test/example-projects/contributed-xCalendar/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{A71984B4-31CF-402F-81AA-B3D90D73100D}", + "licence": "MIT", + "name": "[IMPORTED] Calendar MSCAL replacement (Experimental, 32-bit and 64-bit)", + "path32": "/Packages/xCalendar", + "path64": "/Packages/xCalendar", + "symbolId": "xCalendar", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "7" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/example-projects/contributed-xCalendar/Sources/tbxStageXCalendar.twin b/test/example-projects/contributed-xCalendar/Sources/tbxStageXCalendar.twin new file mode 100644 index 000000000..a966e2fd3 --- /dev/null +++ b/test/example-projects/contributed-xCalendar/Sources/tbxStageXCalendar.twin @@ -0,0 +1,15 @@ +' The stage set for the xCalendar section: the control instance its samples assume. +' +' This template is a delta over `console`, as every `contributed-` template is: its +' Settings references the package, and this is the one file besides that differs. A sample +' on the xCalendar page says `xCalendar1.Value = Date`, which is form code-behind: the reader +' has a form with an xCalendar on it, and the designer, not the code, declares xCalendar1. +' +' The variable's type is written `xCalendar.xCalendar`, the project's name and then the +' class's: the two are the same word, and `xCalendar` alone is the project (TB5139). + +Module tbxStageXCalendar + + Public xCalendar1 As xCalendar.xCalendar + +End Module diff --git a/test/package-probes/xCalendar/README.md b/test/package-probes/xCalendar/README.md new file mode 100644 index 000000000..88a1e8efa --- /dev/null +++ b/test/package-probes/xCalendar/README.md @@ -0,0 +1,85 @@ +# xCalendar probe + +What the contributed package xCalendar's **xCalendar** control does at run time: a form in a built +exe on a private desktop holds one control, the exe operates it from inside and writes every event +and every value it reads to a file. The page `docs/Reference/Contributed/xCalendar/index.md` states +what this probe printed, on package 1.0.7.0 and BETA 1005, in 32-bit and 64-bit builds (the two give +the same output, pixel positions included). Run it again when the section's `indexed_from` moves to a +new version of the package. + +It is outside every gate and CI: it needs Windows, a twinBASIC install (the newest +`twinBASIC_IDE_BETA_` on the Desktop, as `tbrun` finds it) and the package, which `stage.mjs` +takes from the cache `check_examples` keeps (`%TEMP%\tb-twinserv`) or downloads from TWINSERV into it. +The store's copy under `indexer/.packages/` has only the `.twin` file; the control's windows are +declared in the package's `xCalendar.tbcontrol`. + +## Running it + +```sh +node test/package-probes/xCalendar/probe.mjs 1 2,3 4,5 6,7,8 9 10 11,12 13 14 +node test/package-probes/xCalendar/probe.mjs --arch win64 --port 9911 1 2,3 4,5 6,7,8 9 10 11,12 13 14 +node test/package-probes/xCalendar/probe.mjs --shots 15 15b +node test/package-probes/xCalendar/probe.mjs p2,p3 p5,p7 p9 p10 p11 p12 p13 p14 p18 p19 p20,p21 p24,p25 p29 p33 p22x +node test/package-probes/xCalendar/probe.mjs 12x 12y 12z # each ends at the timeout: see below +``` + +Each argument is one run of the exe; the scenario ids are listed at the top of +[`src/Probe.twin`](src/Probe.twin), the numbers of the questions they answer. A build takes about ten +seconds and a run 3 to 20 s (the picture runs, 2 s a picture, the longest); every run is ended after 33 +s. Exit 1 means a run crashed, opened a box or reached the timeout, which is the answer for `12x`, +`12y` and `12z`: choosing a month or a year from a list that makes an impossible date (31 January to +February, 29 February 2024 to 2025) leaves the exe in a loop at full processor load, which +`run-exe.mjs` shows by sampling its processor time at the timeout. + +Ports 9910 to 9940 are this probe's; `--port` keys the IDE's DevTools port, the work folders +(`%TEMP%\xcal-probe\tree-`) and the private desktop's name, so two runs with different ports can +run at once. + +- [`stage.mjs`](stage.mjs) writes the project: the `contributed-xCalendar` sample template's Settings, + the package embedded under `Packages/xCalendar`, and `src/`. +- [`probe.mjs`](probe.mjs) stages it, builds it with `tbrun` (its `[RunAfterBuild]` Sub only prints; no + form opens in the IDE) and runs the exe once for each list of ids. `--form FILE` builds with another + `Form1.tbform`; `--set K=JSON` adds a key to the control's entry in it. +- [`run-exe.mjs`](run-exe.mjs) runs a built exe on a private desktop. `launchOnDesktop` closes the boxes + the exe opens, and the runner lists them. On a `SHOT hwnd=` line of the exe's output it + photographs that window with `captureWindow` (PrintWindow) into `--shots /.png` and writes + the file the exe waits for; it fails the picture if the Windows user name is in the window's texts. +- [`designer.mjs`](designer.mjs) opens the project in an IDE, selects the control in Form1's designer, + types values into the Properties panel's cells, saves, and prints the control's entry in the + `Form1.tbform` the IDE wrote. What it wrote: every property as a plain key (enumerations by name), and + `_IPersistStreamData`, the property bag the control's `ReadProperties` reads. Plain keys written by + hand (`probe.mjs --set`) are ignored at run time; the bag is what counts. +- [`patch-stream.mjs`](patch-stream.mjs) writes that file again with a bag whose Year, Month and Day are + other ones, with no Value (the control writes no Value equal to the current date): a control saved on + another day. +- [`src/Form1.tbform`](src/Form1.tbform) is the form: two command buttons, and the control between them + (`Form1.xCalendar1`), so that the keyboard focus can be moved to and from it. `src/Win.twin` is the + Win32 declarations and the key and modifier helpers; `src/Form1.twin` writes every event the control + raises with its arguments. + +## How the input is made + +- A mouse click is `WM_MOUSEMOVE`, `WM_LBUTTONDOWN` and `WM_LBUTTONUP` for a point in the control's + window, sent (`SendMessage`; scenarios 9, p7) or posted to the queue and dispatched by `DoEvents`, the + runtime's own loop (`p13`). The two give the same events. +- A key is `WM_KEYDOWN` and `WM_KEYUP` (`WM_SYSKEYDOWN` with Alt) posted to the window that has the + focus, the key-up dispatched after the key-down, through `DoEvents` (`p9`). The runtime's loop makes the + `KeyPress` and `KeyUp` events and the `Shift` argument (a modifier is held with `SetKeyboardState`, + which `GetKeyState` reads). **A key sent with `SendMessage` straight to the picture box raises + `KeyDown` only** (scenario 10): the loop is part of what a key press does. **`DoEvents` does not + return while the date selectors are visible**, because the control asks for a repaint after every + paint (scenario 16); the probe's own `Pump` stops after three `WM_PAINT`, and the scenarios that need + `DoEvents` hide the selectors first. +- Pixels are read with `GetPixel` from the control's window DC, after it has painted. +- A real mouse or keyboard cannot be used: the exe runs on a private desktop, not the input desktop. + Windows sends no input to a disabled window, so what a click on a disabled control does is only the + control's own code, which scenario 11 shows. +- The sizes are device pixels at the private desktop's DPI, 144 (150%) on the machine the probe was + written on: the control's date selectors are positioned in unscaled pixels (a list 50 wide), so at + that DPI the year list shows "20" of "2026". + +## Rules + +- Never run the exe on the user's desktop, or a form in the IDE: the probe's `[RunAfterBuild]` Sub only + prints. +- Never call `ShellExecute` or start another program from the exe; the probe starts none. diff --git a/test/package-probes/xCalendar/designer.mjs b/test/package-probes/xCalendar/designer.mjs new file mode 100644 index 000000000..efe8b4f86 --- /dev/null +++ b/test/package-probes/xCalendar/designer.mjs @@ -0,0 +1,179 @@ +// Opens the xCalendar probe's project in an IDE on a private desktop, edits the control's +// properties in the form designer's Properties panel as a person does, saves, and prints what the +// IDE wrote for the control into Form1.tbform. +// +// node test/package-probes/xCalendar/designer.mjs [--port N] [--edit Name=text ...] [--out FILE] +// +// Why: the control keeps its design-time values in a property bag (its ReadProperties and +// WriteProperties), and what the IDE stores in the form's designer file for it cannot be written by +// hand: keys named after the properties in the control's entry are ignored when the exe starts +// (probe.mjs --set). This script gets the IDE to write the entry. The file it saves can be built +// into the probe with `probe.mjs --form FILE`, and scenario 1 then says what the control reads at +// run time. +// +// The IDE is the newest twinBASIC_IDE_BETA_ on the Desktop, in a lane's own copy of the install +// with the lane's own APPDATA (scripts/lib/tb-lane.mjs), on a private desktop. +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { exitOnCrash, numberOption, parseCli, printHelpAndExit, withUsageError } from "../../../lib/cli.mjs"; +import { sleep } from "../../../scripts/lib/tb-ide.mjs"; +import { findIde } from "../../../scripts/lib/tb-install.mjs"; +import { Lane } from "../../../scripts/lib/tb-lane.mjs"; +import { openFile, pressKey, typeText, waitFor } from "../../../scripts/lib/tb-operate.mjs"; +import { wrapProbe } from "../../../scripts/lib/tb-probe.mjs"; +import { unpackProject } from "../../../scripts/lib/tb-project.mjs"; +import { stageTree } from "./stage.mjs"; + +const USAGE = `usage: node test/package-probes/xCalendar/designer.mjs [--port N] [--edit Name=text ...] [--out FILE] [-h, --help] + +Opens the probe's project in an IDE, selects xCalendar1 in Form1's designer, types each --edit +value into the Properties panel's cell for that property (Name=text), saves the project and prints +the control's entry in the Form1.tbform the IDE wrote. + + --port N the IDE's DevTools port (default 9915; ports 9910 to 9940 are this probe's) + --edit N=T a property and the text to type in its cell; repeatable + --out FILE also write the saved Form1.tbform to FILE + -h, --help print this text and exit + +Exit codes: + 0 the form was edited and saved + 1 the designer, the control or a property cell was not found + 2 the command line was refused, or the IDE could not be run`; + +exitOnCrash(); +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + port: { type: "string", default: "9915" }, + edit: { type: "string", multiple: true }, + out: { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + positionals: 0, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const port = withUsageError( + () => numberOption(values.port, { option: "--port", integer: true, min: 1024, max: 65535 }), + usageError, +); + +const ide = findIde(); +if (!ide) { + console.error("designer: no twinBASIC_IDE_BETA_ on the Desktop (TB_IDE names one)"); + process.exit(2); +} +const work = path.join(tmpdir(), "xcal-designer", String(port)); +rmSync(work, { recursive: true, force: true }); +mkdirSync(work, { recursive: true }); +const tree = await stageTree({ key: `d${port}` }); +// tbrun adds the TbRun module the probe writes with; an IDE opening the tree needs it too. +{ + const probe = path.join(tree, "Sources", "Probe.twin"); + const wrapped = wrapProbe([{ name: "Probe.twin", text: readFileSync(probe, "utf8") }]); + for (const f of wrapped.files) writeFileSync(path.join(tree, "Sources", f.name), f.text, "utf8"); +} +const lane = new Lane({ name: "xcal-designer", port, work, ide }); + +const GLOBALS = "getCurrentFormEditorGlobals()"; +let code = 0; +try { + const c = await lane.open(tree); + await openFile(c, "/DocSamples/Sources/Form1.tbform"); + const shown = await waitFor( + c, + () => + c.evaluate( + `(() => { const g = ${GLOBALS}; return !!g && g.gotInitialFormData === true && g.resyncOverlay.style.visibility !== "visible"; })()`, + ), + { timeout: 30000, interval: 200 }, + ); + if (!shown) throw new Error("the designer never showed its form"); + await sleep(1500); + + // Select the control with a click on its centre. + const at = await c.evaluate(`(() => { + const g = ${GLOBALS}; + const d = g.designer.getBoundingClientRect(); + const p = g.allControls.find((x) => x.properties.Name === "xCalendar1")?.properties; + if (!p) return null; + return { x: d.x + p.Left + p.Width / 2, y: d.y + p.Top + p.Height / 2 }; + })()`); + if (!at) throw new Error("the designer does not hold xCalendar1"); + for (const type of ["mouseMoved", "mousePressed", "mouseReleased"]) { + await c.send("Input.dispatchMouseEvent", { type, x: at.x, y: at.y, button: "left", clickCount: 1 }); + } + await sleep(1000); + const selected = await c.evaluate(`(${GLOBALS}.selectedControls || []).map((x) => x.properties.Name).sort()`); + console.log(`selected: ${JSON.stringify(selected)}`); + + const names = () => c.evaluate(`[...document.querySelectorAll(".propertyName")].map((e) => e.textContent.trim())`); + if ((await names()).length === 0) { + await c.evaluate(`executeIdeCommand("tbPanels_SetActiveLayoutDefault")`); + await sleep(1500); + } + console.log(`properties panel rows: ${JSON.stringify(await names())}`); + + for (const edit of values.edit ?? []) { + const i = edit.indexOf("="); + const name = edit.slice(0, i); + const text = edit.slice(i + 1); + const cell = await c.evaluate(`(() => { + const n = [...document.querySelectorAll(".propertyName")].find((e) => e.textContent.trim() === ${JSON.stringify(name)}); + if (!n) return null; + n.scrollIntoView({ block: "center" }); + const r = n.nextElementSibling.getBoundingClientRect(); + return { x: r.x + 20, y: r.y + r.height / 2 }; + })()`); + if (!cell) { + console.log(`designer: the Properties panel has no ${name}`); + code = 1; + continue; + } + for (const type of ["mouseMoved", "mousePressed", "mouseReleased"]) { + await c.send("Input.dispatchMouseEvent", { type, x: cell.x, y: cell.y, button: "left", clickCount: 1 }); + } + await sleep(400); + await pressKey(c, "End"); + for (let k = 0; k < 12; k++) await pressKey(c, "Backspace"); + await typeText(c, text); + await pressKey(c, "Enter"); + await sleep(1200); + const after = await c.evaluate(`(() => { + const n = [...document.querySelectorAll(".propertyName")].find((e) => e.textContent.trim() === ${JSON.stringify(name)}); + const v = n?.nextElementSibling; + return v ? (v.querySelector("input,select,textarea")?.value ?? v.textContent.trim()) : null; + })()`); + console.log( + `typed ${JSON.stringify(text)} into ${name}; the cell now reads ${JSON.stringify(after)}; dialogs: ${JSON.stringify(c.dialogs)}`, + ); + } + + await c.evaluate(`executeIdeCommand("tbProject_SaveAllChanges")`); + await sleep(2500); +} catch (e) { + console.error(`designer: ${e.message}`); + code = code || 1; +} finally { + await lane.closeProject().catch((e) => console.error(`designer: ${e.message}`)); +} + +// What the IDE wrote. +try { + const out = path.join(work, "saved"); + unpackProject(lane.project, out); + const text = readFileSync(path.join(out, "Sources", "Form1.tbform"), "utf8"); + const control = JSON.parse(text)[0]._children.find((c) => c.Name === "xCalendar1"); + console.log("the control's entry in Form1.tbform:"); + console.log(JSON.stringify(control, null, "\t")); + if (values.out) writeFileSync(values.out, text, "utf8"); +} catch (e) { + console.error(`designer: cannot read what the IDE saved: ${e.message}`); + code = code || 1; +} +process.exitCode = code; diff --git a/test/package-probes/xCalendar/patch-stream.mjs b/test/package-probes/xCalendar/patch-stream.mjs new file mode 100644 index 000000000..12729ee26 --- /dev/null +++ b/test/package-probes/xCalendar/patch-stream.mjs @@ -0,0 +1,48 @@ +// Makes the designer data of a control that was saved on another day. +// +// node test/package-probes/xCalendar/patch-stream.mjs Year=2020 Month=5 Day=17 +// +// designer.mjs gets the IDE to write xCalendar1's entry in a Form1.tbform: the properties as plain +// keys, and `_IPersistStreamData`, the property bag the control's ReadProperties reads at run time +// (probe.mjs --set shows that the plain keys alone are ignored). Saved on the day it is placed, the +// bag holds Day, Month and Year of that day and no Value, because the control's WriteProperties +// leaves out a Value equal to the current date. To see what the control reads on a later day, this +// writes the file again with a bag whose Day, Month and Year are the ones given, and the plain +// keys to match; the date given is the day the file was "saved" on. +// +// The bag stores each of these three as a 16-bit integer straight after its key, the key in UTF-16. +import { readFileSync, writeFileSync } from "node:fs"; + +const [inFile, outFile, ...pairs] = process.argv.slice(2); +if (!inFile || !outFile || pairs.length === 0) { + console.error("usage: node patch-stream.mjs Year=N Month=N Day=N"); + process.exit(2); +} +const want = Object.fromEntries( + pairs.map((p) => { + const [k, v] = p.split("="); + return [k, Number(v)]; + }), +); +const form = JSON.parse(readFileSync(inFile, "utf8").replace(/^/, "")); +const control = form[0]._children.find((c) => c.Name === "xCalendar1"); +const bag = Buffer.from(control._IPersistStreamData, "base64"); +for (const [key, value] of Object.entries(want)) { + const needle = Buffer.from(key.toLowerCase(), "utf16le"); + const at = bag.indexOf(needle); + if (at < 0) { + console.error(`patch-stream: the bag has no ${key}`); + process.exit(1); + } + const old = bag.readInt16LE(at + needle.length); + bag.writeInt16LE(value, at + needle.length); + console.log(`${key}: ${old} -> ${value}`); + control[key] = value; +} +control._IPersistStreamData = bag.toString("base64"); +if (want.Year && want.Month && want.Day) { + const d = new Date(Date.UTC(want.Year, want.Month - 1, want.Day)); + control.Value = Math.round(d.getTime() / 86400000) + 25569; + control.ValueStr = `${want.Month}/${want.Day}/${want.Year}`; +} +writeFileSync(outFile, `${JSON.stringify(form, null, "\t")}\n`, "utf8"); diff --git a/test/package-probes/xCalendar/probe.mjs b/test/package-probes/xCalendar/probe.mjs new file mode 100644 index 000000000..0f0d659c8 --- /dev/null +++ b/test/package-probes/xCalendar/probe.mjs @@ -0,0 +1,124 @@ +// Builds the xCalendar probe once, then runs the built exe once for each list of scenario ids, +// on a private desktop. What the package's xCalendar control does at run time, for the pages under +// docs/Reference/Contributed/xCalendar/: see README.md beside this file. +// +// node test/package-probes/xCalendar/probe.mjs [--arch win32|win64] [--port N] [--shots DIR] [--form FILE] [--set K=JSON] [ ...] +// +// 1. stage.mjs writes the source tree to %TEMP%\xcal-probe\tree-. +// 2. tbrun builds it (its [RunAfterBuild] Sub only prints; no form opens in the IDE), with +// --timeout 35. Not --exe: run-exe.mjs says why. +// 3. run-exe.mjs runs the exe tbrun built once per , each run ended after 33 s. +import { spawnSync } from "node:child_process"; +import { existsSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { + choiceOption, + exitOnCrash, + numberOption, + parseCli, + printHelpAndExit, + withUsageError, +} from "../../../lib/cli.mjs"; +import { stageTree } from "./stage.mjs"; + +const USAGE = `usage: node test/package-probes/xCalendar/probe.mjs [--arch win32|win64] [--port N] [--shots DIR] [--form FILE] [--set K=JSON] [ ...] [-h, --help] + +Builds the xCalendar probe (src/) with tbrun, then runs the exe on a private desktop once for each +, a comma-separated list of the scenario ids listed at the top of src/Probe.twin. + + --arch A win32 (default) or win64 + --port N the IDE's DevTools port (default 9910, ports 9910 to 9940 are this probe's); it also + keys the work folders + --shots DIR write the pictures scenario 15 asks for into DIR + --form FILE build with this .tbform file in place of src/Form1.tbform + --set K=JSON add the key K, with the JSON value, to the control's entry in Form1.tbform (the + designer data of xCalendar1); repeatable, e.g. --set FirstDay=1 --set ShowTitle=false + -h, --help print this text and exit + +Exit codes: + 0 the probe was built, and every run of the exe ended normally + 1 a run of the exe crashed, exited with another code, opened a box, or was still running at its timeout + 2 the command line was refused, the package could not be fetched, the build failed, or the probe could not run`; + +exitOnCrash(); +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values, positionals } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + arch: { type: "string", default: "win32" }, + port: { type: "string", default: "9910" }, + shots: { type: "string" }, + form: { type: "string" }, + set: { type: "string", multiple: true }, + help: { type: "boolean", short: "h" }, + }, + positionals: { min: 1 }, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const { arch, port } = withUsageError( + () => ({ + arch: choiceOption(values.arch, { option: "--arch", choices: ["win32", "win64"] }), + port: numberOption(values.port, { option: "--port", integer: true, min: 1024, max: 65535 }), + }), + usageError, +); + +const here = import.meta.dirname; +const repo = path.resolve(here, "..", "..", ".."); + +// 1. The source tree. +let tree; +try { + tree = await stageTree({ key: port, form: values.form, set: values.set }); +} catch (e) { + console.error(`probe: ${e.message}`); + process.exit(2); +} + +// 2. The build. +const tbrun = path.join(repo, "scripts", "tbrun.mjs"); +const build = spawnSync(process.execPath, [tbrun, tree, "--arch", arch, "--port", String(port), "--timeout", "35"], { + cwd: repo, + encoding: "utf8", +}); +if (build.status !== 0) { + console.error(`probe: the build failed (tbrun exit ${build.status})`); + console.error(`${build.stdout ?? ""}${build.stderr ?? ""}`.trim().split("\n").slice(-60).join("\n")); + process.exit(2); +} +const outDir = path.join(tmpdir(), "tbrun", String(port), "out"); +const exe = existsSync(outDir) && readdirSync(outDir).find((f) => f.toLowerCase().endsWith(`_${arch}.exe`)); +if (!exe) { + console.error(`probe: tbrun wrote no ${arch} exe to ${outDir}`); + process.exit(2); +} + +// 3. The runs. +let failed = false; +for (const ids of positionals) { + console.log(`######## ${arch} run: ${ids}`); + const args = [ + path.join(here, "run-exe.mjs"), + path.join(outDir, exe), + ids, + "--timeout", + "33", + "--desktop", + `xcalprobe-${port}`, + ]; + if (values.shots) args.push("--shots", values.shots); + const run = spawnSync(process.execPath, args, { cwd: repo, stdio: "inherit" }); + if (run.status === 2 || run.status === null) { + console.error(`probe: run-exe failed (exit ${run.status})`); + process.exit(2); + } + if (run.status !== 0) failed = true; +} +rmSync(tree, { recursive: true, force: true }); +rmSync(path.join(tmpdir(), "tbrun", String(port)), { recursive: true, force: true }); +process.exitCode = failed ? 1 : 0; diff --git a/test/package-probes/xCalendar/run-exe.mjs b/test/package-probes/xCalendar/run-exe.mjs new file mode 100644 index 000000000..9c8801509 --- /dev/null +++ b/test/package-probes/xCalendar/run-exe.mjs @@ -0,0 +1,168 @@ +// Runs a built xCalendar probe exe on a private desktop. +// +// node test/package-probes/xCalendar/run-exe.mjs [--timeout S] [--desktop NAME] [--shots DIR] +// +// The exe is started as tbrun --exe starts it (launchOnDesktop: a private desktop, in a +// kill-on-close job, so nothing appears on the user's desktop), and what it wrote with TbRun.Out +// is read from the file TBRUN_OUT names. Unlike the Dialog probe's runner this one lets +// launchOnDesktop close the boxes the exe opens (an unhandled error in the control opens one) and +// lists them, since the probe's own automation operates no dialog. +// +// A line "SHOT hwnd=" in the exe's output asks for a picture of the form window: with +// --shots the runner photographs that window (PrintWindow, through captureWindow) into +// /.png and writes the file PROBE_ACK, which the exe is waiting for. Without +// --shots no acknowledgement is ever written, and the exe stops waiting after 12 s. +// +// It prints the exe's output, then "--- exit code", "--- fault recorded" when the event log has +// one for the exe (win-fault.mjs, as tbrun --exe reads it), and "--- boxes" for any box the exe +// opened. A run still going after the timeout is ended by its pid. +import { spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import os, { tmpdir } from "node:os"; +import path from "node:path"; +import { exitOnCrash, numberOption, parseCli, printHelpAndExit, withUsageError } from "../../../lib/cli.mjs"; +import { captureWindow, desktopWindows, killTree, launchOnDesktop } from "../../../scripts/lib/tb-ide.mjs"; +import { faultText, recordedFault } from "../../../scripts/lib/win-fault.mjs"; + +const USAGE = `usage: node test/package-probes/xCalendar/run-exe.mjs [--timeout S] [--desktop NAME] [--shots DIR] [-h, --help] + +Runs the xCalendar probe exe on a private desktop, with the scenarios names +(comma-separated, as listed at the top of src/Probe.twin), and prints what they report. + + --timeout S end the exe after S seconds (default 33) + --desktop NAME the private desktop's name (default xcalprobe-) + --shots DIR photograph the form window when the exe asks (scenario 15) into DIR + -h, --help print this text and exit + +Exit codes: + 0 the exe ran to its end, exited 0, and opened no box + 1 the exe exited with another code, opened a box, faulted, or was still running at the timeout + 2 the command line was refused, or the runner failed`; + +exitOnCrash(); +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values, positionals } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + timeout: { type: "string", default: "33" }, + desktop: { type: "string" }, + shots: { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + positionals: 2, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const timeoutS = withUsageError(() => numberOption(values.timeout, { option: "--timeout", above: 0 }), usageError); + +const [exe, probe] = positionals; +const timeoutMs = timeoutS * 1000; +const desktop = values.desktop || `xcalprobe-${process.pid}`; +const outFile = path.join(tmpdir(), `xcalprobe-out-${process.pid}.txt`); +const ackBase = path.join(tmpdir(), `xcalprobe-ack-${process.pid}-`); +rmSync(outFile, { force: true }); +const since = Date.now(); +if (values.shots) mkdirSync(values.shots, { recursive: true }); + +const run = await launchOnDesktop({ + exe: path.resolve(exe), + desktop, + env: { ...process.env, TBRUN_OUT: outFile, PROBE: probe, PROBE_ACK: ackBase }, + dialogs: "close", +}); + +// Pictures: watch the output for SHOT lines. +let watching = true; +const shotNotes = []; +const user = os.userInfo().username.toLowerCase(); +const done = new Set(); +const watcher = (async () => { + while (watching) { + try { + const text = existsSync(outFile) ? readFileSync(outFile, "utf8") : ""; + for (const m of text.matchAll(/^SHOT (\w+) hwnd=(\d+)\s*$/gm)) { + if (done.has(m[1])) continue; + done.add(m[1]); + if (values.shots) { + const file = path.join(values.shots, `${m[1]}.png`); + try { + const info = await captureWindow(desktop, Number(m[2]), file); + const hit = (info.texts ?? []).some((t) => user && t.toLowerCase().includes(user)); + shotNotes.push( + `${m[1]}.png ${info.width}x${info.height} dpi=${info.dpi} frame=${info.frame} client=${info.client} user-name-in-window-texts=${hit}`, + ); + if (hit) rmSync(file, { force: true }); + } catch (e) { + shotNotes.push(`${m[1]}.png FAILED ${e.message}`); + } + } + writeFileSync(ackBase + m[1], "x"); + } + } catch { + // the file may be mid-write + } + await new Promise((r) => setTimeout(r, 150)); + } +})(); + +let timer; +const timedOut = await Promise.race([ + run.exited.then(() => false), + new Promise((r) => { + timer = setTimeout(() => r(true), timeoutMs); + }), +]); +clearTimeout(timer); +let leftover = []; +let cpuNote = ""; +if (timedOut) { + try { + leftover = await desktopWindows(desktop, { children: false }); + } catch (e) { + leftover = [{ class: "?", title: String(e.message), visible: true }]; + } + // Is it spinning or waiting? The exe's processor time, twice, a second apart. + const cpu = () => { + const r = spawnSync( + "powershell", + [ + "-NoProfile", + "-NonInteractive", + "-Command", + `(Get-Process -Id ${run.pid}).TotalProcessorTime.TotalMilliseconds`, + ], + { encoding: "utf8", windowsHide: true }, + ); + return Number.parseFloat(String(r.stdout).trim()); + }; + const c0 = cpu(); + await new Promise((r) => setTimeout(r, 1500)); + const c1 = cpu(); + cpuNote = `processor time of pid ${run.pid}: ${Math.round(c0)} ms, then ${Math.round(c1)} ms 1.5 s later`; + killTree(run.pid); + run.launcher.kill(); +} +const { code, dialogs } = await run.finished; +watching = false; +await watcher; +process.stdout.write(existsSync(outFile) ? readFileSync(outFile, "utf8") : ""); +const f = timedOut ? null : recordedFault(path.resolve(exe), since); +const hex = code !== null && (code < 0 || code > 0xffff) ? ` (0x${(code >>> 0).toString(16)})` : ""; +console.log(`--- exit code: ${timedOut ? `(still running after ${timeoutS}s, ended)` : code}${hex}`); +if (f) console.log(`--- fault recorded: ${faultText(f)}`); +if (dialogs?.length) { + console.log("--- boxes the exe opened (each closed):"); + for (const d of dialogs) console.log(` title=${JSON.stringify(d.title)} text=${JSON.stringify(d.text)}`); +} +for (const n of shotNotes) console.log(`--- shot: ${n}`); +if (timedOut) { + console.log(`--- ${cpuNote}`); + console.log("--- windows left on the desktop at the timeout (visible ones):"); + for (const w of leftover.filter((x) => x.visible)) console.log(` ${w.class} title=${JSON.stringify(w.title)}`); +} +rmSync(outFile, { force: true }); +for (const n of done) rmSync(ackBase + n, { force: true }); +process.exitCode = timedOut || code !== 0 || dialogs?.length ? 1 : 0; diff --git a/test/package-probes/xCalendar/src/Form1.tbform b/test/package-probes/xCalendar/src/Form1.tbform new file mode 100644 index 000000000..9f181d80c --- /dev/null +++ b/test/package-probes/xCalendar/src/Form1.tbform @@ -0,0 +1,229 @@ +[ + { + "AlwaysShowKeyboardCues": false, + "Appearance": "vbAppear3d", + "AutoRedraw": false, + "BackColor": -2147483633, + "BorderStyle": "vbSizable", + "Caption": "xCalendar probe", + "ClipControls": true, + "ControlBox": true, + "DrawMode": "vbCopyPen", + "DrawStyle": "vbSolid", + "DrawWidth": 1, + "Enabled": true, + "FillColor": 0, + "FillStyle": "vbFSTransparent", + "FontBold": false, + "FontItalic": false, + "FontName": "Segoe UI", + "FontSize": 8, + "FontStrikethru": false, + "FontTransparent": true, + "FontUnderline": false, + "ForeColor": -2147483630, + "FormDesignerId": "{7B247400-0000-4000-9000-7B2474000F02}", + "HasDC": true, + "Height": 300, + "HelpContextID": 0, + "Icon": "", + "KeyPreview": false, + "Left": 0, + "LinkMode": "vbLinkNone", + "LinkTopic": null, + "MDIChild": false, + "MaxButton": true, + "MaxHeight": 0, + "MaxWidth": 0, + "MinButton": true, + "MinHeight": 0, + "MinWidth": 0, + "MouseIcon": "", + "MousePointer": "vbDefault", + "Moveable": true, + "Name": "Form1", + "NegotiateMenus": false, + "OLEDropMode": "vbOLEDropNone", + "Opacity": 100, + "Palette": "", + "PaletteMode": "vbPaletteModeHalftone", + "Picture": "", + "PictureDpiScaling": false, + "RightToLeft": false, + "ScaleHeight": 4500, + "ScaleLeft": 0, + "ScaleMode": "vbTwips", + "ScaleTop": 0, + "ScaleWidth": 4500, + "ShowInTaskbar": true, + "StartUpPosition": "vbStartUpScreen", + "TabFocusAutoSelect": false, + "Tag": null, + "Top": 0, + "TopMost": false, + "TransparencyKey": -1, + "Visible": true, + "WhatsThisButton": false, + "WhatsThisHelp": false, + "Width": 300, + "WindowState": "vbNormal", + "__IDEOptions": { + "alignToGrid": false, + "gridHeight": 10, + "gridWidth": 10, + "lockedControls": [], + "multiColoredGrabbers": false, + "showGrid": true, + "showOutlines": false + }, + "__lastUpdateMarker": 3573907441, + "_children": [ + { + "Anchors": { + "Bottom": false, + "Left": true, + "Right": false, + "Top": true, + "_className": "Anchors", + "_clsid": "{8524D4B5-72A9-40A9-A189-29E7905C40CA}" + }, + "Appearance": "vbAppear3d", + "BackColor": -2147483633, + "Cancel": false, + "Caption": "Command1", + "CausesValidation": true, + "Default": false, + "DisabledPicture": "", + "Dock": "vbDockNone", + "DownPicture": "", + "DragIcon": "", + "DragMode": "vbManual", + "Enabled": true, + "FontBold": false, + "FontItalic": false, + "FontName": "Segoe UI", + "FontSize": 8, + "FontStrikethru": false, + "FontUnderline": false, + "ForeColor": -2147483630, + "Height": 20, + "HelpContextID": 0, + "Index": -1, + "Left": 20, + "MaskColor": 12632256, + "MouseIcon": "", + "MousePointer": "vbDefault", + "Name": "Command1", + "OLEDropMode": "vbOLEDropNone", + "Opacity": 100, + "Padding": 2, + "Picture": "", + "PictureAlignment": "vbAlignTop", + "PictureDpiScaling": false, + "RightToLeft": false, + "Style": "vbButtonStandard", + "TabIndex": 0, + "TabStop": true, + "Tag": null, + "ToolTipText": null, + "Top": 5, + "TransparencyKey": -1, + "UseMaskColor": false, + "Visible": true, + "VisualStyles": true, + "WhatsThisHelpID": 0, + "Width": 100, + "_Default": false, + "_className": "CommandButton", + "_clsid": "{33AD4EF0-6699-11CF-B70C-00AA0060D393}", + "_hasExclusiveCancelProp": true, + "_hasExclusiveDefaultProp": true + }, + { + "Anchors": { + "Bottom": false, + "Left": true, + "Right": false, + "Top": true, + "_className": "Anchors", + "_clsid": "{8524D4B5-72A9-40A9-A189-29E7905C40CA}" + }, + "Height": 192, + "Index": -1, + "Left": 20, + "Name": "xCalendar1", + "TabIndex": 1, + "TabStop": true, + "Tag": null, + "Top": 30, + "Visible": true, + "Width": 256, + "_className": "xCalendar", + "_clsid": "{F178D13A-5115-463B-9A49-775B7D3B1A9D}" + }, + { + "Anchors": { + "Bottom": false, + "Left": true, + "Right": false, + "Top": true, + "_className": "Anchors", + "_clsid": "{8524D4B5-72A9-40A9-A189-29E7905C40CA}" + }, + "Appearance": "vbAppear3d", + "BackColor": -2147483633, + "Cancel": false, + "Caption": "Command2", + "CausesValidation": true, + "Default": false, + "DisabledPicture": "", + "Dock": "vbDockNone", + "DownPicture": "", + "DragIcon": "", + "DragMode": "vbManual", + "Enabled": true, + "FontBold": false, + "FontItalic": false, + "FontName": "Segoe UI", + "FontSize": 8, + "FontStrikethru": false, + "FontUnderline": false, + "ForeColor": -2147483630, + "Height": 20, + "HelpContextID": 0, + "Index": -1, + "Left": 20, + "MaskColor": 12632256, + "MouseIcon": "", + "MousePointer": "vbDefault", + "Name": "Command2", + "OLEDropMode": "vbOLEDropNone", + "Opacity": 100, + "Padding": 2, + "Picture": "", + "PictureAlignment": "vbAlignTop", + "PictureDpiScaling": false, + "RightToLeft": false, + "Style": "vbButtonStandard", + "TabIndex": 2, + "TabStop": true, + "Tag": null, + "ToolTipText": null, + "Top": 228, + "TransparencyKey": -1, + "UseMaskColor": false, + "Visible": true, + "VisualStyles": true, + "WhatsThisHelpID": 0, + "Width": 100, + "_Default": false, + "_className": "CommandButton", + "_clsid": "{33AD4EF0-6699-11CF-B70C-00AA0060D393}", + "_hasExclusiveCancelProp": true, + "_hasExclusiveDefaultProp": true + } + ], + "_className": "Form", + "_clsid": "{33AD4F38-6699-11CF-B70C-00AA0060D393}" + } +] diff --git a/test/package-probes/xCalendar/src/Form1.twin b/test/package-probes/xCalendar/src/Form1.twin new file mode 100644 index 000000000..4b820aa71 --- /dev/null +++ b/test/package-probes/xCalendar/src/Form1.twin @@ -0,0 +1,63 @@ +' Form1: the startup form of the xCalendar probe. It holds one xCalendar control (xCalendar1, +' declared in Form1.tbform) and writes every event the control raises, with its arguments, +' through Probe.Ev. + +[Description("")] +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F02")] +[PredeclaredId] +Class Form1 + + Private Sub xCalendar1_Click() Handles xCalendar1.Click + Probe.Ev "Click" + End Sub + + Private Sub xCalendar1_DblClick() Handles xCalendar1.DblClick + Probe.Ev "DblClick" + End Sub + + Private Sub xCalendar1_KeyDown(KeyCode As Integer, Shift As Integer) Handles xCalendar1.KeyDown + Probe.Ev "KeyDown KeyCode=" & KeyCode & " Shift=" & Shift & " Value=" & Probe.Dt(xCalendar1.Value) + End Sub + + Private Sub xCalendar1_KeyPress(KeyAscii As Integer) Handles xCalendar1.KeyPress + Probe.Ev "KeyPress KeyAscii=" & KeyAscii + End Sub + + Private Sub xCalendar1_KeyUp(KeyCode As Integer, Shift As Integer) Handles xCalendar1.KeyUp + Probe.Ev "KeyUp KeyCode=" & KeyCode & " Shift=" & Shift + End Sub + + Private Sub xCalendar1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Handles xCalendar1.MouseDown + Probe.Ev "MouseDown Button=" & Button & " Shift=" & Shift & " X=" & X & " Y=" & Y + End Sub + + Private Sub xCalendar1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Handles xCalendar1.MouseMove + If Probe.gLogMouseMove Then Probe.Ev "MouseMove Button=" & Button & " Shift=" & Shift & " X=" & X & " Y=" & Y + End Sub + + Private Sub xCalendar1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) Handles xCalendar1.MouseUp + Probe.Ev "MouseUp Button=" & Button & " Shift=" & Shift & " X=" & X & " Y=" & Y + End Sub + + Private Sub xCalendar1_BeforeUpdate(Cancel As Integer) Handles xCalendar1.BeforeUpdate + Probe.gBefore = Probe.gBefore + 1 + Probe.Ev "BeforeUpdate Cancel(in)=" & Cancel & " Value=" & Probe.Dt(xCalendar1.Value) + If Probe.gCancel Then + Cancel = Probe.gCancelVal + Probe.Ev " (handler sets Cancel = " & Probe.gCancelVal & ")" + End If + End Sub + + Private Sub xCalendar1_AfterUpdate() Handles xCalendar1.AfterUpdate + Probe.Ev "AfterUpdate Value=" & Probe.Dt(xCalendar1.Value) + End Sub + + Private Sub xCalendar1_NewMonth() Handles xCalendar1.NewMonth + Probe.Ev "NewMonth Value=" & Probe.Dt(xCalendar1.Value) + End Sub + + Private Sub xCalendar1_NewYear() Handles xCalendar1.NewYear + Probe.Ev "NewYear Value=" & Probe.Dt(xCalendar1.Value) + End Sub + +End Class diff --git a/test/package-probes/xCalendar/src/Probe.twin b/test/package-probes/xCalendar/src/Probe.twin new file mode 100644 index 000000000..10dc6828c --- /dev/null +++ b/test/package-probes/xCalendar/src/Probe.twin @@ -0,0 +1,2285 @@ +' Probe: what the contributed package xCalendar's xCalendar control does at run time. +' +' The exe runs Sub Main. The scenarios named by the PROBE environment variable, a comma-separated +' list of ids ("1,2,9"), are run in the order of ScenarioIds, and each prints what it observed. +' A scenario's id is the number of the question it answers (the list below). Every line is written +' with TbRun.Out, so that it reaches the file the runner reads at once; every event the control +' raises is written by Form1's handlers through Ev, indented under the action that caused it +' ("ev> ..."). +' +' Input is simulated by sending window messages (Win.twin): mouse messages to the control's window, +' key messages to the window that has the keyboard focus. The message loop is run by Pump, a +' bounded loop: the control asks for a repaint again after every paint while its selectors are +' visible (scenario 16), and DoEvents would not return. +' +' The IDE-side [RunAfterBuild] Sub only prints: the form is shown only in the exe. +' +' Scenario ids and what they ask: +' 1 the control's windows, and every property's value right after the form loads +' 2 Value set to a date in the same month, another month, another year, with a time, odd values +' 3 Day, Month and Year set one at a time, and out of range +' 4 NextDay ... PreviousYear, and at the boundaries +' 5 Today +' 6 ValueIsNull +' 7 ValueStr +' 8 BeforeUpdate with Cancel = True, for code and clicks +' 9 clicks on day cells, the grey days, other places, right button, double click +' 10 keys, with and without Shift and Ctrl, and sent to the wrong window +' 11 Enabled = False +' 12 the Month and Year selectors (two combo boxes) +' 12x a selector change that makes an impossible date (Jan 31 to February); never returns +' 12y the same for the year list (Feb 29 2024 to 2025) +' 13 FirstDay, DayLength, MonthLength, GridCellEffect and the margins, out of range +' 14 the colours and fonts set from code; Show* flags and the layout they give +' 15 pictures of the form window (the run-exe.mjs runner takes them); 15b more of them +' 16 the message loop: how many WM_PAINT messages the control keeps asking for +' +' The scenarios below answer the page author's numbered questions (xcal-probe-questions.md), and +' read the control's pixels with GetPixel from its window DC: +' p2 the title's position with ShowDateSelectors False (Q2) +' p3 the selected cell for a Value with a time of day (Q3) +' p5 Enabled = False and the colours, with the keyboard focus (Q4, Q5) +' p7 hit-testing in a control 255 pixels wide (Q7) +' p9 the keys as the system sends them, through DoEvents: Shift, Ctrl, Alt (Q8, Q9, Q11) +' p10 Tab into the control; arrow keys after a click; KeyDown, KeyPress, KeyUp order (Q10, Q11) +' p11 keys posted to the queue, through the probe's loop and through DoEvents (Q8, Q11) +' p12 where the focus is after a click, from several starting points, and the arrow keys (Q10) +' p13 the clicks of scenario 9 as the system delivers them, through DoEvents (Q6, Q13, Q14, Q34) +' p14 pixels of the three text colours: do they reach the text? (not a question of the page) +' p18 changing a font object in place; one font object in two properties (Q18) +' p19 TitleMargin and DaysMargin in pixels (Q19) +' p20 FirstDay and the grid (Q20, Q31) +' p21 the grid-line colours (Q21) +' p24 Value and the grid (Q24) +' p25 ValueIsNull: any change in the drawing (Q25) +' p29 NextMonth twice, PreviousMonth twice (Q29) +' p33 Cancel = True, 1 and 0 in BeforeUpdate (Q33) +' p22x Month = 13, then a repaint; may end the run (Q22) + +Module Probe + + Public gCancel As Boolean + Public gLogMouseMove As Boolean + Public gQuiet As Boolean + Public gEvCount As Long + Public gBefore As Long + Public gPumped As Long + Public gCancelVal As Integer + Public gReal As Boolean + + Private Cal As xCalendar.xCalendar + Private Cmd1H As LongPtr + Private Cmd2H As LongPtr + Private gDC As LongPtr + Private mAckBase As String + + ' The control's windows (FindWindows) and the grid's geometry in device pixels (Calibrate). + Private CalH As LongPtr + Private FocusH As LongPtr + Private YearH As LongPtr + Private MonthH As LongPtr + Private CalW As Long + Private CalHt As Long + Private gHeader As Long ' the height above the first row of day cells: title + day names + Private gRowH As Double + Private gColW As Double + + [RunAfterBuild] + Public Sub Hook() + Debug.Cls + Debug.Print "hook ran in the IDE; the form runs only in the exe" + End Sub + + Private Const ScenarioIds As String = ",1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15b,16,p2,p3,p5,p7,p9,p10,p11,p12,p13,p14,p18,p19,p20,p21,p24,p25,p29,p33,12x,12y,12z,p22x," + + Public Sub Main() + Dim sel As String = "," & Replace(Environ$("PROBE"), " ", "") & "," + mAckBase = Environ$("PROBE_ACK") + #If Win64 Then + Say "build: 64-bit (Win64), LenB(LongPtr)=" & LenB(CLngPtr(0)) + #Else + Say "build: 32-bit, LenB(LongPtr)=" & LenB(CLngPtr(0)) + #End If + Win.SetTimer 0, 0, 30000, AddressOf Watchdog + Say "date: " & Format$(Date, "yyyy-mm-dd") & " (" & Format$(Date, "dddd") & ")" + Say "CStr(DateSerial(2026, 6, 15)) = [" & CStr(DateSerial(2026, 6, 15)) & "]" + Say "CStr(DateSerial(2026, 6, 15) + TimeSerial(14, 30, 5)) = [" & CStr(DateSerial(2026, 6, 15) + TimeSerial(14, 30, 5)) & "]" + Load Form1 + Form1.Show + Pump + Say "form loaded and shown; the first pump dispatched " & gPumped & " messages" + Set Cal = Form1.xCalendar1 + FindWindows + Dim ids() As String + Dim i As Long + ids = Split(Mid$(ScenarioIds, 2, Len(ScenarioIds) - 2), ",") + For i = 0 To UBound(ids) + If InStr(sel, "," & ids(i) & ",") > 0 Then RunScenario ids(i) + Next + Say "== done" + Set Cal = Nothing + Unload Form1 + End Sub + + Public Sub Watchdog(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As Long) + Say "WATCHDOG: the probe ran for 30 s, ending the process" + Win.ExitProcess 97 + End Sub + + Private Sub RunScenario(ByVal id As String) + On Error GoTo EH + Say "" + Say "######## scenario " & id + If id <> "1" And id <> "15" And id <> "16" Then Reset ' 15 shows the control as loaded first + Select Case id + Case "1": S1 + Case "2": S2 + Case "3": S3 + Case "4": S4 + Case "5": S5 + Case "6": S6 + Case "7": S7 + Case "8": S8 + Case "9": S9 + Case "10": S10 + Case "11": S11 + Case "12": S12 + Case "12x": S12x + Case "12y": S12y + Case "12z": S12z + Case "13": S13 + Case "14": S14 + Case "15": S15 + Case "15b": S15b + Case "16": S16 + Case "p2": P2 + Case "p3": P3 + Case "p5": P5 + Case "p7": P7 + Case "p9": P9 + Case "p10": P10 + Case "p11": P11 + Case "p12": P12 + Case "p13": P13 + Case "p14": P14 + Case "p18": P18 + Case "p19": P19 + Case "p20": P20 + Case "p21": P21 + Case "p24": P24 + Case "p25": P25 + Case "p29": P29 + Case "p33": P33 + Case "p22x": P22x + End Select + Exit Sub +EH: + Say " ERROR escaped scenario " & id & ": " & Err.Number & " " & Err.Description + End Sub + + '========================================================================================= + ' Helpers + '========================================================================================= + + Public Sub Say(ByVal s As String) + TbRun.Out s + End Sub + + ' An event the control raised. + Public Sub Ev(ByVal s As String) + gEvCount = gEvCount + 1 + If Not gQuiet Then TbRun.Out " ev> " & s + End Sub + + Public Function Dt(ByVal d As Date) As String + Dt = Format$(d, "yyyy-mm-dd hh:nn:ss") + End Function + + ' Processes the messages that are waiting, until the queue is empty or three WM_PAINT + ' messages have been dispatched (a control that keeps invalidating itself never empties it), + ' or 400 messages have. Sets gPumped to how many were dispatched. With dlg, each message goes + ' to the form's dialog manager first, as a modal loop does for Tab and the other dialog keys. + Public Sub Pump(Optional ByVal logFirst As Long = 0, Optional ByVal dlg As Boolean = False) + Dim m As Win.MSG + Dim n As Long + Dim nPaint As Long + Sleep 20 + Do While n < 400 And nPaint < 3 + If Win.PeekMessageW(m, 0, 0, 0, 1) = 0 Then Exit Do + If n < logFirst Then Say " msg 0x" & Hex$(m.message) & " to [" & Win.ClassOf(m.hWnd) & "] wParam=" & m.wParam & " lParam=" & m.lParam + If m.message = &HF Then nPaint = nPaint + 1 + If dlg And Win.IsDialogMessageW(Form1.hWnd, m) <> 0 Then + ' handled by the dialog manager + Else + Win.TranslateMessage m + Win.DispatchMessageW m + End If + n = n + 1 + Loop + gPumped = n + End Sub + + ' Reports the error the last statement raised, or "ok", and clears it. + Private Sub Chk(ByVal what As String) + If Err.Number <> 0 Then + Say " " & what & " -> ERROR " & Err.Number & " [" & Err.Description & "] source=[" & Err.Source & "]" + Err.Clear + Else + Say " " & what & " -> ok" + End If + End Sub + + ' Value and what follows it, on one line. + Private Sub St(ByVal tag As String) + On Error Resume Next + Dim v As Date = Cal.Value + Say " state[" & tag & "]: Value=" & Dt(v) & " (" & CDbl(v) & ") Day=" & Cal.Day & " Month=" & Cal.Month & " Year=" & Cal.Year & " ValueIsNull=" & Cal.ValueIsNull & " ValueStr=[" & Cal.ValueStr & "] lists=[" & Win.TextOf(MonthH) & "|" & Win.TextOf(YearH) & "]" + If Err.Number <> 0 Then Chk "reading the state" + End Sub + + ' Announces an action, and zeroes the event counter. + Private Sub Act(ByVal what As String) + Say " >> " & what + gEvCount = 0 + gBefore = 0 + End Sub + + ' After an action: process the messages, and say so when no event was raised. + Private Sub Done() + Pump + If gEvCount = 0 Then Say " (no event)" + End Sub + + ' The known state every scenario but 1, 15 and 16 starts from: 2026-06-15, a Monday. + Private Sub Reset() + On Error Resume Next + gCancel = False + gCancelVal = -1 + gReal = False + gLogMouseMove = False + gQuiet = False + Win.SetMods 0 + Cal.Enabled = True + Cal.ShowDateSelectors = True + Cal.ShowTitle = True + Cal.ShowDays = True + Cal.ShowHorizontalGrid = True + Cal.ShowVerticalGrid = True + Cal.FirstDay = 2 + Cal.TitleMargin = 5 + Cal.DaysMargin = 5 + Cal.ValueIsNull = False + Cal.Value = DateSerial(2026, 6, 15) + Cal.Month = 6 + If Err.Number <> 0 Then Chk "Reset" + Pump + gEvCount = 0 + End Sub + + ' The photographed window: the runner takes the picture while the probe waits for its answer. + Private Sub Shot(ByVal name As String) + Pump + Say "SHOT " & name & " hwnd=" & Form1.hWnd + If Len(mAckBase) = 0 Then Exit Sub + Dim t As Long = GetTickCount + Do While Len(Dir$(mAckBase & name)) = 0 And GetTickCount - t < 12000 + Pump + Sleep 40 + Loop + Say " (shot " & name & IIf(Len(Dir$(mAckBase & name)) > 0, " taken)", " NOT taken)") + End Sub + + Private Sub Tree(ByVal h As LongPtr, ByVal depth As Long, ByVal org As RECT) + Dim c As LongPtr = Win.GetWindow(h, Win.GW_CHILD) + Dim r As RECT + Do While c <> 0 + Win.GetWindowRect c, r + Say Space$(2 + depth * 2) & "- " & Win.ClassOf(c) & " id=" & Win.GetDlgCtrlID(c) & " visible=" & Win.IsWindowVisible(c) & " enabled=" & Win.IsWindowEnabled(c) & " size=" & (r.Right - r.Left) & "x" & (r.Bottom - r.Top) & " at(" & (r.Left - org.Left) & "," & (r.Top - org.Top) & ") style=0x" & Hex$(Win.GetWindowLongW(c, -16)) & " exstyle=0x" & Hex$(Win.GetWindowLongW(c, -20)) & " text=[" & Win.TextOf(c) & "]" + Tree c, depth + 1, org + c = Win.GetWindow(c, Win.GW_HWNDNEXT) + Loop + End Sub + + ' The control's window, and its picture box (the one that takes the keys) and two combo boxes. + Private Sub FindWindows() + Dim c As LongPtr = Win.GetWindow(Form1.hWnd, Win.GW_CHILD) + Do While c <> 0 + Select Case Win.ClassOf(c) + Case "ThunderRT6UserControlDC": CalH = c + Case "ThunderRT6CommandButton" + If Win.TextOf(c) = "Command1" Then Cmd1H = c + If Win.TextOf(c) = "Command2" Then Cmd2H = c + End Select + c = Win.GetWindow(c, Win.GW_HWNDNEXT) + Loop + Say "form children: control [" & IIf(CalH <> 0, "found", "NOT found") & "] Command1 [" & IIf(Cmd1H <> 0, "found", "NOT found") & "] Command2 [" & IIf(Cmd2H <> 0, "found", "NOT found") & "]" + Dim r As RECT + Win.GetClientRect CalH, r + CalW = r.Right + CalHt = r.Bottom + c = Win.GetWindow(CalH, Win.GW_CHILD) + Do While c <> 0 + Select Case Win.ClassOf(c) + Case "ThunderRT6PictureBoxDC" + If Win.IsWindowVisible(c) <> 0 Then FocusH = c + Case "ThunderRT6ComboBox" + If IsNumeric(Win.TextOf(c)) Then YearH = c Else MonthH = c + End Select + c = Win.GetWindow(c, Win.GW_HWNDNEXT) + Loop + Say "control window [" & Win.ClassOf(CalH) & "] client " & CalW & "x" & CalHt & "; picture box (keys) [" & IIf(FocusH <> 0, "found", "NOT found") & "]; year combo [" & IIf(YearH <> 0, "found", "NOT found") & "]; month combo [" & IIf(MonthH <> 0, "found", "NOT found") & "]" + End Sub + + ' ---- mouse ---- + + ' A mouse message to the control window: sent, or (gReal) posted to the queue and dispatched + ' by DoEvents, the runtime's own loop, as the system delivers a real mouse's. gReal needs the + ' selectors hidden, or DoEvents does not return (scenario 16). + Private Sub Msg(ByVal m As Long, ByVal w As LongPtr, ByVal l As LongPtr) + If gReal Then + Win.PostMessageW CalH, m, w, l + DoEvents + Else + Win.SendMessageW CalH, m, w, l + End If + End Sub + + Private Sub MouseAt(ByVal x As Long, ByVal y As Long) + Msg Win.WM_MOUSEMOVE, 0, Win.MakeLParam(x, y) + End Sub + + ' A click as a mouse sends it: the move, the button down, the button up. button 1 is the left. + Private Sub ClickAt(ByVal x As Long, ByVal y As Long, Optional ByVal button As Long = 1) + If gLogMouseMove Then Say " (" & IIf(gReal, "posted", "sent") & " at client x=" & x & " y=" & y & " of the control window, button " & button & ")" + MouseAt x, y + If button = 1 Then + Msg Win.WM_LBUTTONDOWN, Win.MK_LBUTTON, Win.MakeLParam(x, y) + Msg Win.WM_LBUTTONUP, 0, Win.MakeLParam(x, y) + Else + Msg Win.WM_RBUTTONDOWN, Win.MK_RBUTTON, Win.MakeLParam(x, y) + Msg Win.WM_RBUTTONUP, 0, Win.MakeLParam(x, y) + End If + End Sub + + ' The cell of the grid at row r (0 to 5) and column c (0 to 6), by its centre. + Private Sub ClickCell(ByVal r As Long, ByVal c As Long, Optional ByVal button As Long = 1) + ClickAt CLng((c + 0.5) * gColW), CLng(gHeader + (r + 0.5) * gRowH), button + End Sub + + ' Finds where the day cells start: the first y at which a left button press raises BeforeUpdate + ' (the control tests Y > title height + day-names height). Cancels each press so that nothing + ' changes. + Private Sub Calibrate() + Dim lo As Long = 0 + Dim hi As Long = CalHt + Dim mid As Long + Dim saveCancel As Boolean = gCancel + Dim saveQuiet As Boolean = gQuiet + Dim saveReal As Boolean = gReal + gReal = False + gCancel = True + gQuiet = True + Do While hi - lo > 1 + mid = (lo + hi) \ 2 + gBefore = 0 + Win.SendMessageW CalH, Win.WM_LBUTTONDOWN, Win.MK_LBUTTON, Win.MakeLParam(10, mid) + Win.SendMessageW CalH, Win.WM_LBUTTONUP, 0, Win.MakeLParam(10, mid) + If gBefore > 0 Then hi = mid Else lo = mid + Loop + gCancel = saveCancel + gQuiet = saveQuiet + gReal = saveReal + gHeader = lo + gRowH = (CalHt - gHeader) / 6 + gColW = CalW / 7 + gEvCount = 0 + gBefore = 0 + End Sub + + ' ---- keys ---- + + Private Sub KeyTo(ByVal h As LongPtr, ByVal vk As Long, Optional ByVal mods As Long = 0) + Win.SendKey h, vk, mods + End Sub + + Private Function VkName(ByVal vk As Long) As String + Select Case vk + Case Win.VK_LEFT: VkName = "Left" + Case Win.VK_RIGHT: VkName = "Right" + Case Win.VK_UP: VkName = "Up" + Case Win.VK_DOWN: VkName = "Down" + Case Win.VK_PRIOR: VkName = "PageUp" + Case Win.VK_NEXT: VkName = "PageDown" + Case Win.VK_HOME: VkName = "Home" + Case Win.VK_END: VkName = "End" + Case Else: VkName = "vk" & vk + End Select + End Function + + '========================================================================================= + ' 1: the windows and the defaults + '========================================================================================= + + Private Sub S1() + On Error Resume Next + Dim fr As RECT + Win.GetWindowRect Form1.hWnd, fr + Say "form window: " & Win.ClassOf(Form1.hWnd) & " size=" & (fr.Right - fr.Left) & "x" & (fr.Bottom - fr.Top) & " dpi=" & Win.GetDpiForWindow(Form1.hWnd) + Dim cr As RECT + Win.GetClientRect Form1.hWnd, cr + Say "form client: " & cr.Right & "x" & cr.Bottom + Say "window tree (positions relative to the form window's top left corner; the control is 256x192 at 100% scale):" + Tree Form1.hWnd, 0, fr + Say "class style of the control window: 0x" & Hex$(Win.GetClassLongW(CalH, -26)) & " (CS_DBLCLKS is 0x8: " & ((Win.GetClassLongW(CalH, -26) And 8) <> 0) & ")" + Say "focus: [" & Win.ClassOf(Win.GetFocus) & "] foreground is the form: " & (Win.GetForegroundWindow = Form1.hWnd) + Dim v As Date = Cal.Value + Say "Value = " & Dt(v) & " CDbl=" & CDbl(v) & " VarType=" & VarType(Cal.Value) + Say "Value is today: " & (Int(CDbl(v)) = Int(CDbl(Date))) & " (Date = " & Dt(Date) & ")" + Say "ValueIsNull = " & Cal.ValueIsNull + Say "ValueStr = [" & Cal.ValueStr & "]" + Say "Day = " & Cal.Day & " Month = " & Cal.Month & " Year = " & Cal.Year + Say "FirstDay = " & Cal.FirstDay & " DayLength = " & Cal.DayLength & " MonthLength = " & Cal.MonthLength & " GridCellEffect = " & Cal.GridCellEffect + Say "DayFontColor = &H" & Hex$(Cal.DayFontColor) & " DayBackColor = &H" & Hex$(Cal.DayBackColor) + Say "GridFontColor = &H" & Hex$(Cal.GridFontColor) & " GridLinesColor = &H" & Hex$(Cal.GridLinesColor) + Say "TitleFontColor = &H" & Hex$(Cal.TitleFontColor) & " BackColor = &H" & Hex$(Cal.BackColor) + Say "ShowDateSelectors = " & Cal.ShowDateSelectors & " ShowDays = " & Cal.ShowDays & " ShowTitle = " & Cal.ShowTitle + Say "ShowHorizontalGrid = " & Cal.ShowHorizontalGrid & " ShowVerticalGrid = " & Cal.ShowVerticalGrid + Say "TitleMargin = " & Cal.TitleMargin & " DaysMargin = " & Cal.DaysMargin & " Enabled = " & Cal.Enabled + Say "DayFont is Nothing: " & (Cal.DayFont Is Nothing) & " GridFont is Nothing: " & (Cal.GridFont Is Nothing) & " TitleFont is Nothing: " & (Cal.TitleFont Is Nothing) + Say "DayFont: " & Cal.DayFont.Name & ", " & Cal.DayFont.Size & " pt, Bold=" & Cal.DayFont.Bold + Say "GridFont: " & Cal.GridFont.Name & ", " & Cal.GridFont.Size & " pt, Bold=" & Cal.GridFont.Bold + Say "TitleFont: " & Cal.TitleFont.Name & ", " & Cal.TitleFont.Size & " pt, Bold=" & Cal.TitleFont.Bold + If Err.Number <> 0 Then Chk "S1" + Say "events raised while the form loaded and was read: " & gEvCount + Calibrate + Say "geometry (device pixels): header (title + day names) = " & gHeader & ", row height = " & Format$(gRowH, "0.00") & ", column width = " & Format$(gColW, "0.00") & ", client " & CalW & "x" & CalHt + End Sub + + '========================================================================================= + ' 2: Value + '========================================================================================= + + Private Sub SetValue(ByVal what As String, ByVal v As Variant) + On Error Resume Next + Act "Value = " & what + Cal.Value = v + Chk "Value = " & what + Done + St "after" + End Sub + + Private Sub S2() + On Error Resume Next + St "start" + SetValue "#2026-06-20# (same month)", DateSerial(2026, 6, 20) + SetValue "#2026-08-03# (another month)", DateSerial(2026, 8, 3) + SetValue "#2027-01-09# (another year)", DateSerial(2027, 1, 9) + SetValue "#2027-01-09# again (the same date)", DateSerial(2027, 1, 9) + SetValue "#2026-06-15 14:30:05# (with a time)", DateSerial(2026, 6, 15) + TimeSerial(14, 30, 5) + SetValue "#1900-01-01# (the first year of the selector)", DateSerial(1900, 1, 1) + SetValue "#2100-12-31# (the last year of the selector)", DateSerial(2100, 12, 31) + SetValue "#2101-01-01# (above the selector's years)", DateSerial(2101, 1, 1) + SetValue "#1899-12-31# (below the selector's years)", DateSerial(1899, 12, 31) + SetValue "#1850-05-05# (far below)", DateSerial(1850, 5, 5) + SetValue "0 (an empty date)", CDate(0) + SetValue "Null", Null + SetValue """2026-09-09"" (a String)", "2026-09-09" + SetValue """abc"" (a String that is no date)", "abc" + SetValue "46000 (a Double)", 46000# + SetValue "#2026-06-15# (back)", DateSerial(2026, 6, 15) + End Sub + + '========================================================================================= + ' 3: Day, Month, Year + '========================================================================================= + + Private Sub SetDay(ByVal d As Variant) + On Error Resume Next + Act "Day = " & d + Cal.Day = d + Chk "Day = " & d + Done + St "after" + End Sub + + Private Sub SetMonth(ByVal d As Variant) + On Error Resume Next + Act "Month = " & d + Cal.Month = d + Chk "Month = " & d + Done + St "after" + End Sub + + Private Sub SetYear(ByVal d As Variant) + On Error Resume Next + Act "Year = " & d + Cal.Year = d + Chk "Year = " & d + Done + St "after" + End Sub + + Private Sub S3() + On Error Resume Next + St "start" + Say "-- valid values, one at a time" + SetDay 20 + SetMonth 8 + SetYear 2027 + SetYear 2024 + SetMonth 2 + SetDay 29 + SetYear 2025 + Say "-- invalid values, each from 2026-06-15" + Cal.Value = DateSerial(2026, 6, 15): Pump + SetDay 31 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetDay 0 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetDay 32 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetDay -1 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetMonth 13 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetMonth 0 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetYear 0 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetYear 1899 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetYear 2101 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetYear 100 + Cal.Value = DateSerial(2026, 6, 15): Pump + SetYear 32767 + Say "-- a month with fewer days than the current Day" + Cal.Value = DateSerial(2026, 1, 31): Pump + SetMonth 2 + Cal.Value = DateSerial(2026, 1, 31): Pump + SetMonth 4 + Cal.Value = DateSerial(2024, 2, 29): Pump + SetYear 2025 + Say "-- is the state usable after an error? Day = 31 in February, then Value = a date" + Cal.Value = DateSerial(2026, 2, 10): Pump + SetDay 31 + SetValue "#2026-03-03#", DateSerial(2026, 3, 3) + End Sub + + '========================================================================================= + ' 4: NextDay ... PreviousYear + '========================================================================================= + + Private Sub Nav(ByVal start As Date, ByVal op As String) + On Error Resume Next + Cal.Value = start + Pump + Say " >> Value = " & Dt(start) & ", then " & op + gEvCount = 0 + Select Case op + Case "NextDay": Cal.NextDay + Case "PreviousDay": Cal.PreviousDay + Case "NextWeek": Cal.NextWeek + Case "PreviousWeek": Cal.PreviousWeek + Case "NextMonth": Cal.NextMonth + Case "PreviousMonth": Cal.PreviousMonth + Case "NextYear": Cal.NextYear + Case "PreviousYear": Cal.PreviousYear + Case "Today": Cal.Today + End Select + Chk op + Done + St "after" + End Sub + + Private Sub S4() + Dim b As Date = DateSerial(2026, 6, 15) + Say "VBA.DateAdd on its own: m,1,#2026-01-31# = " & Dt(DateAdd("m", 1, DateSerial(2026, 1, 31))) & "; m,-1,#2026-03-31# = " & Dt(DateAdd("m", -1, DateSerial(2026, 3, 31))) & "; yyyy,1,#2024-02-29# = " & Dt(DateAdd("yyyy", 1, DateSerial(2024, 2, 29))) & "; yyyy,-1,#2024-02-29# = " & Dt(DateAdd("yyyy", -1, DateSerial(2024, 2, 29))) & "; d,1,#2026-12-31# = " & Dt(DateAdd("d", 1, DateSerial(2026, 12, 31))) + Nav b, "NextDay" + Nav b, "PreviousDay" + Nav b, "NextWeek" + Nav b, "PreviousWeek" + Nav b, "NextMonth" + Nav b, "PreviousMonth" + Nav b, "NextYear" + Nav b, "PreviousYear" + Say "-- boundaries" + Nav DateSerial(2026, 1, 31), "NextMonth" + Nav DateSerial(2026, 3, 31), "PreviousMonth" + Nav DateSerial(2026, 12, 31), "NextDay" + Nav DateSerial(2026, 1, 1), "PreviousDay" + Nav DateSerial(2026, 12, 28), "NextWeek" + Nav DateSerial(2026, 1, 3), "PreviousWeek" + Nav DateSerial(2026, 12, 15), "NextMonth" + Nav DateSerial(2026, 1, 15), "PreviousMonth" + Nav DateSerial(2024, 2, 29), "NextYear" + Nav DateSerial(2024, 2, 29), "PreviousYear" + Nav DateSerial(2026, 8, 31), "NextMonth" + Say "-- the selector's year range" + Nav DateSerial(2100, 12, 31), "NextDay" + Nav DateSerial(2100, 12, 15), "NextMonth" + Nav DateSerial(1900, 1, 15), "PreviousMonth" + Nav DateSerial(2100, 6, 1), "NextYear" + Nav DateSerial(1900, 1, 1), "PreviousDay" + Nav DateSerial(1900, 6, 1), "PreviousYear" + End Sub + + '========================================================================================= + ' 5: Today + '========================================================================================= + + Private Sub S5() + Nav DateSerial(2026, 6, 15), "Today" + Nav DateSerial(2020, 1, 31), "Today" + Nav Date, "Today" + Nav Date + TimeSerial(14, 30, 5), "Today" + Nav DateSerial(2020, 1, 1) + TimeSerial(15, 30, 0), "Today" + Nav DateSerial(2100, 1, 1), "Today" + Nav DateSerial(1900, 1, 1), "Today" + End Sub + + '========================================================================================= + ' 6: ValueIsNull + '========================================================================================= + + Private Sub S6() + On Error Resume Next + St "start" + Act "ValueIsNull = True" + Cal.ValueIsNull = True + Chk "ValueIsNull = True" + Done + St "after" + SetValue "#2026-07-04#", DateSerial(2026, 7, 4) + Act "ValueIsNull = False" + Cal.ValueIsNull = False + Chk "ValueIsNull = False" + St "after" + Act "ValueIsNull = True, then a click on a day cell" + Cal.ValueIsNull = True + Calibrate + ClickCell 1, 3 + Done + St "after" + Act "ValueIsNull = True, then NextDay" + Cal.NextDay + Done + St "after" + Act "ValueIsNull = True, then ValueStr = ""2026-12-24""" + Cal.ValueStr = "2026-12-24" + Chk "ValueStr = 2026-12-24" + St "after" + Act "ValueIsNull = 5 (a number)" + Cal.ValueIsNull = 5 + Chk "ValueIsNull = 5" + Say " ValueIsNull reads back: " & Cal.ValueIsNull & " (" & TypeName(Cal.ValueIsNull) & ")" + Cal.ValueIsNull = False + End Sub + + '========================================================================================= + ' 7: ValueStr + '========================================================================================= + + Private Sub S7() + On Error Resume Next + Dim d As Variant + Dim s As String + Say "-- reading ValueStr" + For Each d In Array(DateSerial(2026, 3, 15), DateSerial(2026, 3, 15) + TimeSerial(14, 30, 0), DateSerial(2026, 6, 15), DateSerial(2026, 1, 2), DateSerial(2024, 2, 29), DateSerial(1900, 1, 1), DateSerial(2026, 12, 31), DateSerial(2026, 6, 15) + TimeSerial(14, 30, 5), DateSerial(2026, 6, 15) + TimeSerial(0, 0, 1)) + Cal.Value = d + s = Cal.ValueStr + Say " Value=" & Dt(CDate(d)) & " ValueStr=[" & s & "] len=" & Len(s) & " CStr(Value)=[" & CStr(Cal.Value) & "] same: " & (s = CStr(Cal.Value)) + Next + Say "-- setting ValueStr" + For Each d In Array("2026-12-24", "12/24/2026", "24 Dec 2026", "December 24, 2026", "12/24/26", "13/12/2026", "2026-02-30", "2026-12-24 14:30:05", "14:30", "", "not a date", "1/1/1850", "2101-01-01", " 2026-07-07 ") + Act "ValueStr = """ & d & """" + Cal.ValueStr = d + Chk "ValueStr = """ & d & """" + Done + St "after" + Next + Say "-- is ValueStr a stored property (Let works with a Date)?" + Act "ValueStr = a Date value #2026-03-03#" + Cal.ValueStr = DateSerial(2026, 3, 3) + Chk "ValueStr = Date" + St "after" + End Sub + + '========================================================================================= + ' 8: BeforeUpdate with Cancel = True + '========================================================================================= + + Private Sub S8() + On Error Resume Next + gCancel = True + Say "gCancel = True: Form1's BeforeUpdate handler sets Cancel = -1" + Act "Value = #2026-06-20# (code)" + Cal.Value = DateSerial(2026, 6, 20) + Done + St "after" + Act "NextDay (code)" + Cal.NextDay + Done + St "after" + Act "Day = 5 (code)" + Cal.Day = 5 + Done + St "after" + Cal.Value = DateSerial(2026, 6, 15) + Pump + Calibrate + Act "a click on the cell for 2026-06-09 (row 1, column 1), with gCancel = True" + gCancel = True + ClickCell 1, 1 + Done + St "after" + Act "the same click with gCancel = False" + gCancel = False + ClickCell 1, 1 + Done + St "after" + End Sub + + '========================================================================================= + ' 9: clicks + '========================================================================================= + + Private Sub S9() + On Error Resume Next + Rebase DateSerial(2026, 7, 15) + Calibrate + Say "geometry: header=" & gHeader & " rowH=" & Format$(gRowH, "0.00") & " colW=" & Format$(gColW, "0.00") + Say "base Value is 2026-07-15; July 2026 starts on a Wednesday, so with Monday first row 0 is Jun 29 .. Jul 5 and row 5 is Aug 3 .. Aug 9" + gLogMouseMove = True + Rebase DateSerial(2026, 7, 15) + Act "click on row 1, column 3 (Thursday 2026-07-09, this month)" + ClickCell 1, 3 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click on row 0, column 0 (2026-06-29, a grey day of the previous month)" + ClickCell 0, 0 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click on row 5, column 2 (2026-08-05, a grey day of the next month)" + ClickCell 5, 2 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click on row 5, column 6 (2026-08-09, the last cell)" + ClickCell 5, 6 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + ClickCell 1, 3 + Pump + gEvCount = 0 + Act "click again on the day that is already selected (row 1, column 3 = 2026-07-09)" + ClickCell 1, 3 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "right-click on row 2, column 2" + ClickCell 2, 2, 2 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click in the title band (y = 4)" + ClickAt 20, 4 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click in the day-names band (y = header - 3)" + ClickAt 20, gHeader - 3 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click on the last pixel row of the header (y = header) and on the first of the cells (y = header + 1)" + ClickAt 20, gHeader + ClickAt 20, gHeader + 1 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click at the very bottom of the control (y = height - 1), column 3" + ClickAt CLng(3.5 * gColW), CalHt - 1 + Done + St "after" + Rebase DateSerial(2026, 7, 15) + Act "click on the grid line between two columns (x = column width * 2 exactly), row 1" + ClickAt CLng(2 * gColW), CLng(gHeader + 1.5 * gRowH) + Done + St "after" + Say "focus after the clicks: [" & Win.ClassOf(Win.GetFocus) & "]" + Rebase DateSerial(2026, 7, 15) + Act "double click on row 1, column 5 (2026-07-11): down, up, double-click, up" + MouseAt CLng(5.5 * gColW), CLng(gHeader + 1.5 * gRowH) + Msg Win.WM_LBUTTONDOWN, Win.MK_LBUTTON, Win.MakeLParam(CLng(5.5 * gColW), CLng(gHeader + 1.5 * gRowH)) + Msg Win.WM_LBUTTONUP, 0, Win.MakeLParam(CLng(5.5 * gColW), CLng(gHeader + 1.5 * gRowH)) + Msg Win.WM_LBUTTONDBLCLK, Win.MK_LBUTTON, Win.MakeLParam(CLng(5.5 * gColW), CLng(gHeader + 1.5 * gRowH)) + Msg Win.WM_LBUTTONUP, 0, Win.MakeLParam(CLng(5.5 * gColW), CLng(gHeader + 1.5 * gRowH)) + Done + St "after" + Act "the same double click as down, up, down, up (a window class without CS_DBLCLKS)" + MouseAt CLng(1.5 * gColW), CLng(gHeader + 3.5 * gRowH) + ClickCell 3, 1 + ClickCell 3, 1 + Done + St "after" + gLogMouseMove = False + End Sub + + ' The clicks of scenario 9 again, as the system delivers a mouse's messages: posted to the + ' queue and dispatched by DoEvents (Q6, Q13, Q14, Q34). + Private Sub P13() + On Error Resume Next + Cal.ShowDateSelectors = False + Pump + gReal = True + S9 + gReal = False + Cal.ShowDateSelectors = True + Pump + End Sub + + '========================================================================================= + ' 10: keys + '========================================================================================= + + Private Sub KeyCase(ByVal h As LongPtr, ByVal vk As Long, ByVal mods As Long, ByVal toWhat As String) + On Error Resume Next + Cal.Value = DateSerial(2026, 6, 15) + Pump + Act "key " & VkName(vk) & IIf((mods And 1) <> 0, " + Shift", "") & IIf((mods And 2) <> 0, " + Ctrl", "") & IIf((mods And 4) <> 0, " + Alt", "") & " sent to " & toWhat + KeyTo h, vk, mods + Done + St "after" + End Sub + + Private Sub S10() + On Error Resume Next + Dim vk As Variant + Say "focus before: [" & Win.ClassOf(Win.GetFocus) & "]" + Dim prev As LongPtr = Win.SetFocus(FocusH) + Pump + Say "SetFocus(picture box) returned [" & Win.ClassOf(prev) & "]; GetFocus is now the picture box: " & (Win.GetFocus = FocusH) & " (" & Win.ClassOf(Win.GetFocus) & ")" + Say "GetKeyState(Shift/Ctrl) after SetMods(1)/(2):" + Win.SetMods 1 + Say " Shift: " & Win.GetKeyState(Win.VK_SHIFT) & ", Ctrl: " & Win.GetKeyState(Win.VK_CONTROL) + Win.SetMods 2 + Say " Shift: " & Win.GetKeyState(Win.VK_SHIFT) & ", Ctrl: " & Win.GetKeyState(Win.VK_CONTROL) + Win.SetMods 0 + Say "-- keys sent to the picture box that has the focus" + For Each vk In Array(Win.VK_LEFT, Win.VK_RIGHT, Win.VK_UP, Win.VK_DOWN, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME, Win.VK_END) + KeyCase FocusH, CLng(vk), 0, "the picture box" + Next + Say "-- with Shift" + For Each vk In Array(Win.VK_LEFT, Win.VK_UP, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + KeyCase FocusH, CLng(vk), 1, "the picture box" + Next + Say "-- with Ctrl" + For Each vk In Array(Win.VK_LEFT, Win.VK_UP, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + KeyCase FocusH, CLng(vk), 2, "the picture box" + Next + Say "-- with Shift and Ctrl" + KeyCase FocusH, Win.VK_PRIOR, 3, "the picture box" + KeyCase FocusH, Win.VK_NEXT, 3, "the picture box" + Say "-- with Alt (sent as WM_SYSKEYDOWN and WM_SYSKEYUP, what Windows sends then)" + For Each vk In Array(Win.VK_LEFT, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + KeyCase FocusH, CLng(vk), 4, "the picture box" + Next + Say "-- characters" + Act "WM_CHAR 'a' sent to the picture box" + Win.SendMessageW FocusH, Win.WM_CHAR, 97, 1 + Done + St "after" + Act "WM_CHAR Enter (13) sent to the picture box" + Win.SendMessageW FocusH, Win.WM_CHAR, 13, 1 + Done + St "after" + Say "-- the same keys sent to the control's own window instead of the picture box" + For Each vk In Array(Win.VK_LEFT, Win.VK_PRIOR, Win.VK_HOME) + KeyCase CalH, CLng(vk), 0, "the control window" + Next + Act "WM_CHAR 'a' sent to the control window" + Win.SendMessageW CalH, Win.WM_CHAR, 97, 1 + Done + Say "-- after a click the keyboard focus is" + Win.SetFocus Form1.hWnd + Pump + Calibrate + ClickCell 2, 2 + Pump + Say " focus after a click on a cell (a click sent as a message does not move the focus itself): [" & Win.ClassOf(Win.GetFocus) & "]" + End Sub + + '========================================================================================= + ' 11: Enabled = False + '========================================================================================= + + Private Sub S11() + On Error Resume Next + Win.SetFocus FocusH + Pump + Calibrate + Say "enabled state of the windows before: control=" & Win.IsWindowEnabled(CalH) & " picture box=" & Win.IsWindowEnabled(FocusH) & " year combo=" & Win.IsWindowEnabled(YearH) & " month combo=" & Win.IsWindowEnabled(MonthH) + Act "Enabled = False" + Cal.Enabled = False + Chk "Enabled = False" + Done + Say " Enabled reads back: " & Cal.Enabled + Say "enabled state of the windows after: control=" & Win.IsWindowEnabled(CalH) & " picture box=" & Win.IsWindowEnabled(FocusH) & " year combo=" & Win.IsWindowEnabled(YearH) & " month combo=" & Win.IsWindowEnabled(MonthH) + Say "focus: [" & Win.ClassOf(Win.GetFocus) & "]" + Dim r As LongPtr = Win.SetFocus(FocusH) + Say "SetFocus(picture box) while disabled returned [" & Win.ClassOf(r) & "], GetFocus is [" & Win.ClassOf(Win.GetFocus) & "]" + Say "(Windows never sends mouse or key input to a disabled window, so a real click or key does nothing; the messages below are sent directly and show what the control's own code does)" + Act "a click on row 1, column 3, sent as a message" + ClickCell 1, 3 + Done + St "after" + Act "key Right sent as a message to the picture box" + KeyTo FocusH, Win.VK_RIGHT + Done + St "after" + Act "NextDay (code)" + Cal.NextDay + Done + St "after" + Act "Value = #2026-09-09# (code)" + Cal.Value = DateSerial(2026, 9, 9) + Done + St "after" + Act "key Down sent as a message to the month combo" + KeyTo MonthH, Win.VK_DOWN + Done + St "after" + Act "Enabled = True" + Cal.Enabled = True + Done + Say "enabled state of the windows after: control=" & Win.IsWindowEnabled(CalH) & " picture box=" & Win.IsWindowEnabled(FocusH) & " year combo=" & Win.IsWindowEnabled(YearH) & " month combo=" & Win.IsWindowEnabled(MonthH) + End Sub + + '========================================================================================= + ' 12: the selectors + '========================================================================================= + + Private Sub ComboInfo(ByVal name As String, ByVal h As LongPtr) + Dim n As Long = CLng(Win.SendMessageW(h, Win.CB_GETCOUNT, 0, 0)) + Dim cur As Long = CLng(Win.SendMessageW(h, Win.CB_GETCURSEL, 0, 0)) + Dim r As RECT + Win.GetWindowRect h, r + Dim style As Long = Win.GetWindowLongW(h, -16) + Say " " & name & ": class=" & Win.ClassOf(h) & " items=" & n & " cursel=" & cur & " text=[" & Win.TextOf(h) & "] size=" & (r.Right - r.Left) & "x" & (r.Bottom - r.Top) & " visible=" & Win.IsWindowVisible(h) & " enabled=" & Win.IsWindowEnabled(h) & " CBS type=" & (style And 3) & " (3 = CBS_DROPDOWNLIST)" + End Sub + + Private Sub ItemText(ByVal h As LongPtr, ByVal i As Long) + Dim ln As Long = CLng(Win.SendMessageW(h, Win.CB_GETLBTEXTLEN, i, 0)) + Dim buf As String = String$(ln + 2, 0) + Win.SendMessageW h, Win.CB_GETLBTEXT, i, StrPtr(buf) + Say " item " & i & ": [" & Left$(buf, ln) & "]" + End Sub + + Private Sub S12() + On Error Resume Next + Dim i As Long + ComboInfo "month combo", MonthH + ComboInfo "year combo", YearH + For i = 0 To 11 + ItemText MonthH, i + Next + ItemText YearH, 0 + ItemText YearH, 1 + ItemText YearH, 200 + Say "-- choosing from the month combo by key (Down: July)" + Win.SetFocus MonthH + Pump + Act "key Down sent to the month combo" + KeyTo MonthH, Win.VK_DOWN + Done + St "after" + Say " focus: [" & Win.ClassOf(Win.GetFocus) & "], is the picture box: " & (Win.GetFocus = FocusH) + ComboInfo "month combo", MonthH + Act "key Up sent to the month combo twice (back to June, then May)" + KeyTo MonthH, Win.VK_UP + KeyTo MonthH, Win.VK_UP + Done + St "after" + Act "key Down sent to the year combo (2027)" + KeyTo YearH, Win.VK_DOWN + Done + St "after" + ComboInfo "year combo", YearH + Act "key Up sent to the year combo (2026)" + KeyTo YearH, Win.VK_UP + Done + St "after" + Say "-- choosing as the combo does: CB_SETCURSEL then WM_COMMAND/CBN_SELCHANGE to the control window" + Act "month combo: CB_SETCURSEL 11 (December) and the notification" + Win.SendMessageW MonthH, Win.CB_SETCURSEL, 11, 0 + Win.SendMessageW CalH, Win.WM_COMMAND, Win.MakeLParam(Win.GetDlgCtrlID(MonthH), 1), MonthH + Done + St "after" + Act "CB_SETCURSEL 3 (April) only, with no notification" + Win.SendMessageW MonthH, Win.CB_SETCURSEL, 3, 0 + Done + St "after" + Say "-- the drop-down" + Act "CB_SHOWDROPDOWN True on the month combo" + Win.SendMessageW MonthH, Win.CB_SHOWDROPDOWN, 1, 0 + Pump + Say " dropped state: " & Win.SendMessageW(MonthH, Win.CB_GETDROPPEDSTATE, 0, 0) + Win.SendMessageW MonthH, Win.CB_SHOWDROPDOWN, 0, 0 + Pump + Say " dropped state after closing: " & Win.SendMessageW(MonthH, Win.CB_GETDROPPEDSTATE, 0, 0) + Say "-- ShowDateSelectors" + Cal.Value = DateSerial(2026, 6, 15) + Pump + Act "ShowDateSelectors = False" + Cal.ShowDateSelectors = False + Chk "ShowDateSelectors = False" + Done + Say " visible: month combo=" & Win.IsWindowVisible(MonthH) & " year combo=" & Win.IsWindowVisible(YearH) + Act "ShowDateSelectors = False, then NextMonth (code)" + Cal.NextMonth + Done + St "after" + Act "ShowDateSelectors = True" + Cal.ShowDateSelectors = True + Chk "ShowDateSelectors = True" + Done + Say " visible: month combo=" & Win.IsWindowVisible(MonthH) & " year combo=" & Win.IsWindowVisible(YearH) + ComboInfo "month combo", MonthH + ComboInfo "year combo", YearH + End Sub + + ' A selector change that makes an impossible date: January 31 to February. The control builds + ' the date from year, month and the old day, in its own event handler, so the error is raised + ' inside the control with no caller to take it. Run on its own: it may end the run. + Private Sub S12x() + On Error Resume Next + Cal.Value = DateSerial(2026, 1, 31) + Pump + St "start" + Act "key Down sent to the month combo, on 2026-01-31" + KeyTo MonthH, Win.VK_DOWN + Done + St "after" + ComboInfo "month combo", MonthH + End Sub + + ' 12x again with the key posted to the queue and dispatched by the probe's loop, not sent. + Private Sub S12z() + On Error Resume Next + Cal.Value = DateSerial(2026, 1, 31) + Pump + Act "key Down posted to the month combo, on 2026-01-31" + Win.PostKey MonthH, Win.VK_DOWN + Pump + Say " returned from the loop" + St "after" + End Sub + + ' The same for the year list: 2024-02-29 to 2025. Run on its own: it may end the run. + Private Sub S12y() + On Error Resume Next + Cal.Value = DateSerial(2024, 2, 29) + Pump + St "start" + Act "key Down sent to the year combo, on 2024-02-29" + KeyTo YearH, Win.VK_DOWN + Done + St "after" + End Sub + + '========================================================================================= + ' 13: out-of-range values + '========================================================================================= + + Private Sub TryProp(ByVal prop As String, ByVal v As Variant) + On Error Resume Next + Dim back As Variant + Select Case prop + Case "FirstDay": Cal.FirstDay = v: back = Cal.FirstDay + Case "DayLength": Cal.DayLength = v: back = Cal.DayLength + Case "MonthLength": Cal.MonthLength = v: back = Cal.MonthLength + Case "GridCellEffect": Cal.GridCellEffect = v: back = Cal.GridCellEffect + Case "TitleMargin": Cal.TitleMargin = v: back = Cal.TitleMargin + Case "DaysMargin": Cal.DaysMargin = v: back = Cal.DaysMargin + End Select + Dim msg As String = prop & " = " & v + If Err.Number <> 0 Then + Say " " & msg & " -> ERROR " & Err.Number & " [" & Err.Description & "]; reads back " & back + Err.Clear + Else + Say " " & msg & " -> ok; reads back " & back + End If + ' put the property back at once, before any paint + Select Case prop + Case "FirstDay": Cal.FirstDay = 2 + Case "DayLength": Cal.DayLength = 1 + Case "MonthLength": Cal.MonthLength = 1 + Case "GridCellEffect": Cal.GridCellEffect = 1 + Case "TitleMargin": Cal.TitleMargin = 5 + Case "DaysMargin": Cal.DaysMargin = 5 + End Select + Err.Clear + Pump + End Sub + + Private Sub S13() + Dim v As Variant + Say "(each value is put back to its default right after, before the next paint)" + For Each v In Array(1, 2, 7, 0, 8, 9, -1, 100) + TryProp "FirstDay", v + Next + For Each v In Array(0, 1, 2, 3, -1, 5) + TryProp "DayLength", v + Next + For Each v In Array(0, 1, 2, -1) + TryProp "MonthLength", v + Next + For Each v In Array(0, 1, 2, 3, -1, 7) + TryProp "GridCellEffect", v + Next + For Each v In Array(0, -5, 20, 100, 32767, 40000) + TryProp "TitleMargin", v + Next + For Each v In Array(0, -5, 20, 100) + TryProp "DaysMargin", v + Next + Say "-- the same, left in place until the next paint (FirstDay = 9)" + On Error Resume Next + Cal.FirstDay = 9 + Chk "FirstDay = 9" + Say " Pump (a paint with FirstDay = 9):" + gQuiet = False + Pump + Say " pump dispatched " & gPumped & " messages" + Cal.FirstDay = 2 + Err.Clear + Pump + End Sub + + '========================================================================================= + ' 14: colours, fonts, Show* and the layout + '========================================================================================= + + Private Sub Layout(ByVal what As String) + Pump + Calibrate + Say " layout[" & what & "]: header=" & gHeader & " px, row height=" & Format$(gRowH, "0.00") & " px" + End Sub + + Private Sub S14() + On Error Resume Next + Layout "default" + Cal.ShowTitle = False + Layout "ShowTitle = False (selectors visible)" + Cal.ShowDateSelectors = False + Layout "ShowTitle = False, ShowDateSelectors = False" + Cal.ShowTitle = True + Layout "ShowTitle = True, ShowDateSelectors = False" + Cal.ShowDateSelectors = True + Cal.ShowDays = False + Layout "ShowDays = False" + Cal.ShowDays = True + Cal.TitleMargin = 15 + Layout "TitleMargin = 15" + Cal.TitleMargin = 5 + Cal.DaysMargin = 15 + Layout "DaysMargin = 15" + Cal.DaysMargin = 5 + Cal.TitleFont.Size = 10 + Layout "TitleFont.Size = 10 (changed in place)" + Cal.TitleFont.Size = 16 + Cal.DayFont.Size = 14 + Layout "DayFont.Size = 14 (changed in place)" + Cal.DayFont.Size = 8 + Chk "layout tests" + Say "-- colours" + Cal.DayBackColor = vbRed + Cal.DayFontColor = vbWhite + Cal.GridFontColor = vbBlue + Cal.GridLinesColor = vbGreen + Cal.TitleFontColor = vbMagenta + Cal.BackColor = vbYellow + Chk "setting six colours" + Say " read back: DayBackColor=&H" & Hex$(Cal.DayBackColor) & " DayFontColor=&H" & Hex$(Cal.DayFontColor) & " GridFontColor=&H" & Hex$(Cal.GridFontColor) & " GridLinesColor=&H" & Hex$(Cal.GridLinesColor) & " TitleFontColor=&H" & Hex$(Cal.TitleFontColor) & " BackColor=&H" & Hex$(Cal.BackColor) + Pump + Shot "colours" + Cal.DayBackColor = &HDCA86F + Cal.DayFontColor = 0 + Cal.GridFontColor = &HA00000 + Cal.GridLinesColor = &H80000010 + Cal.TitleFontColor = &H80FF& + Cal.BackColor = &HE0E0E0 + Say "-- fonts: Set TitleFont to a new StdFont (Courier New, 12, bold)" + Dim f As New StdFont + f.Name = "Courier New" + f.Size = 12 + f.Bold = True + Set Cal.TitleFont = f + Chk "Set TitleFont" + Say " TitleFont reads back: " & Cal.TitleFont.Name & ", " & Cal.TitleFont.Size & " pt, Bold=" & Cal.TitleFont.Bold & "; same object: " & (Cal.TitleFont Is f) + Set Cal.GridFont = f + Set Cal.DayFont = f + Chk "Set GridFont, DayFont to the same object" + Say " GridFont is the same object: " & (Cal.GridFont Is f) & ", DayFont is: " & (Cal.DayFont Is f) + Pump + Shot "fonts" + Set Cal.TitleFont = Nothing + Chk "Set TitleFont = Nothing" + Pump + Chk "paint after TitleFont = Nothing" + End Sub + + '========================================================================================= + ' 15: pictures + '========================================================================================= + + Private Sub S15() + On Error Resume Next + Shot "default" + Cal.Value = DateSerial(2026, 7, 15) + Pump + Shot "selected_stale" + Rebase DateSerial(2026, 7, 15) + Shot "selected" + Cal.Value = DateSerial(2026, 7, 15) + TimeSerial(14, 30, 5) + Pump + Shot "selected_with_time" + Cal.Value = DateSerial(2026, 7, 15) + Cal.Enabled = False + Pump + Shot "disabled" + Cal.Enabled = True + Chk "S15" + End Sub + + ' More pictures: the grid-line effects, the day and month names, Sunday first, nothing but the grid. + Private Sub S15b() + On Error Resume Next + Rebase DateSerial(2026, 7, 15) + Cal.GridCellEffect = 0 + Pump + Shot "effect0" + Cal.GridCellEffect = 2 + Pump + Shot "effect2" + Cal.GridCellEffect = 1 + Cal.DayLength = 0 + Cal.MonthLength = 0 + Cal.FirstDay = 1 + Rebase DateSerial(2026, 7, 15) + Shot "short_sunday" + Cal.DayLength = 2 + Cal.MonthLength = 1 + Cal.FirstDay = 2 + Rebase DateSerial(2026, 7, 15) + Shot "longdays" + Cal.DayLength = 1 + Cal.ShowDateSelectors = False + Cal.ShowTitle = False + Cal.ShowDays = False + Pump + Shot "bare" + Cal.ShowDateSelectors = True + Cal.ShowTitle = True + Cal.ShowDays = True + Chk "S15b" + End Sub + + '========================================================================================= + ' 16: the repaint loop + '========================================================================================= + + ' A run of the message loop for ms milliseconds: how many messages, and how many of them are + ' WM_PAINT. Reports how long it really took. + Private Sub Storm(ByVal what As String, ByVal ms As Long) + Dim m As Win.MSG + Dim t As Long = GetTickCount + Dim n As Long + Dim nPaint As Long + Dim nOther As Long + Dim other As String + Do While GetTickCount - t < ms + If Win.PeekMessageW(m, 0, 0, 0, 1) = 0 Then + Sleep 5 + Else + n = n + 1 + If m.message = &HF Then + nPaint = nPaint + 1 + Else + nOther = nOther + 1 + If nOther <= 6 Then other = other & " 0x" & Hex$(m.message) + End If + Win.TranslateMessage m + Win.DispatchMessageW m + End If + Loop + Say " storm[" & what & "]: " & n & " messages in " & (GetTickCount - t) & " ms, " & nPaint & " WM_PAINT; first others:" & other + End Sub + + Private Sub S16() + On Error Resume Next + Storm "as loaded", 1500 + Cal.ShowDateSelectors = False + Storm "ShowDateSelectors = False", 1000 + Cal.ShowTitle = False + Storm "ShowTitle = False as well", 1000 + Cal.ShowTitle = True + Cal.ShowDateSelectors = True + Cal.Refresh + Storm "both back, after Refresh", 1000 + Form1.Hide + Storm "form hidden", 1000 + Form1.Show + Storm "form shown again", 1000 + Chk "S16" + End Sub + + '========================================================================================= + ' Pixels: read from the control's window DC with GetPixel (a COLORREF, 0x00BBGGRR, the same + ' number as an OLE_COLOR property), after the control has painted. + '========================================================================================= + + Private Sub PxOpen() + gDC = Win.GetDC(CalH) + End Sub + + Private Sub PxClose() + Win.ReleaseDC CalH, gDC + gDC = 0 + End Sub + + Private Function Hx(ByVal c As Long) As String + Hx = "&H" & Hex$(c) + End Function + + ' The runs of one colour down column x from y0 to y1, as "y0-y1:colour". + Private Function ColRuns(ByVal x As Long, ByVal y0 As Long, ByVal y1 As Long) As String + Dim y As Long + Dim c As Long + Dim prev As Long = -2 + Dim start As Long + Dim s As String + PxOpen + For y = y0 To y1 + c = Win.GetPixel(gDC, x, y) + If c <> prev Then + If y > y0 Then s = s & start & "-" & (y - 1) & ":" & Hx(prev) & " " + start = y + prev = c + End If + Next + s = s & start & "-" & y1 & ":" & Hx(prev) + PxClose + ColRuns = s + End Function + + ' The same along row y from x0 to x1. + Private Function RowRuns(ByVal y As Long, ByVal x0 As Long, ByVal x1 As Long) As String + Dim x As Long + Dim c As Long + Dim prev As Long = -2 + Dim start As Long + Dim s As String + PxOpen + For x = x0 To x1 + c = Win.GetPixel(gDC, x, y) + If c <> prev Then + If x > x0 Then s = s & start & "-" & (x - 1) & ":" & Hx(prev) & " " + start = x + prev = c + End If + Next + s = s & start & "-" & x1 & ":" & Hx(prev) + PxClose + RowRuns = s + End Function + + Private Function PxAt(ByVal x As Long, ByVal y As Long) As Long + PxOpen + PxAt = Win.GetPixel(gDC, x, y) + PxClose + End Function + + ' The first (or last) y from y0 to y1 at which column x has colour c; -1 when none. + Private Function FirstY(ByVal x As Long, ByVal c As Long, ByVal y0 As Long, ByVal y1 As Long) As Long + Dim y As Long + FirstY = -1 + PxOpen + For y = y0 To y1 + If Win.GetPixel(gDC, x, y) = c Then + FirstY = y + Exit For + End If + Next + PxClose + End Function + + Private Function LastY(ByVal x As Long, ByVal c As Long, ByVal y0 As Long, ByVal y1 As Long) As Long + Dim y As Long + LastY = -1 + PxOpen + For y = y0 To y1 + If Win.GetPixel(gDC, x, y) = c Then LastY = y + Next + PxClose + End Function + + Private Function CountPx(ByVal c As Long, ByVal x0 As Long, ByVal y0 As Long, ByVal x1 As Long, ByVal y1 As Long) As Long + Dim x As Long + Dim y As Long + Dim n As Long + PxOpen + For y = y0 To y1 + For x = x0 To x1 + If Win.GetPixel(gDC, x, y) = c Then n = n + 1 + Next + Next + PxClose + CountPx = n + End Function + + ' The bounding box of the pixels that are not colour bg in the rectangle. + Private Sub BBox(ByVal x0 As Long, ByVal y0 As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal bg As Long, ByRef minX As Long, ByRef minY As Long, ByRef maxX As Long, ByRef maxY As Long) + Dim x As Long + Dim y As Long + minX = 99999: minY = 99999: maxX = -1: maxY = -1 + PxOpen + For y = y0 To y1 + For x = x0 To x1 + If Win.GetPixel(gDC, x, y) <> bg Then + If x < minX Then minX = x + If x > maxX Then maxX = x + If y < minY Then minY = y + If y > maxY Then maxY = y + End If + Next + Next + PxClose + End Sub + + ' A number that changes when any sampled pixel of the control changes. + Private Function Fingerprint() As Double + Dim x As Long + Dim y As Long + Dim h As Double + PxOpen + For y = 0 To CalHt - 1 Step 5 + For x = 0 To CalW - 1 Step 5 + h = h + CDbl(Win.GetPixel(gDC, x, y)) * (1 + ((x * 7 + y * 13) Mod 17)) + Next + Next + PxClose + Fingerprint = h + End Function + + ' Re-bases the grid on a date: Value, then Month, whose setter recomputes the first date of + ' the grid (Value's setter does not). + Private Sub Rebase(ByVal d As Date) + On Error Resume Next + Cal.Value = d + Cal.Month = VBA.Month(d) + Pump + gEvCount = 0 + gBefore = 0 + End Sub + + ' Where the keyboard focus is, by name. + Private Function FocusName() As String + Dim h As LongPtr = Win.GetFocus + Dim s As String + If h = 0 Then + s = "nothing" + ElseIf h = FocusH Then + s = "the control's picture box (picFocus)" + ElseIf h = CalH Then + s = "the control window itself" + ElseIf h = Cmd1H Then + s = "Command1" + ElseIf h = Cmd2H Then + s = "Command2" + ElseIf h = YearH Then + s = "the year combo" + ElseIf h = MonthH Then + s = "the month combo" + ElseIf h = Form1.hWnd Then + s = "the form" + Else + s = "another window" + End If + FocusName = s & " [" & Win.ClassOf(h) & "]" + End Function + + '========================================================================================= + ' p2: the title with the selectors hidden (Q2) + '========================================================================================= + + Private Sub TitleSpan(ByVal what As String, ByVal xmax As Long) + Pump + Calibrate + Dim top As Long = FirstY(3, Cal.DayBackColor, 0, CalHt - 1) + Dim x0 As Long + Dim y0 As Long + Dim x1 As Long + Dim y1 As Long + BBox 1, 2, xmax, top - 3, Cal.BackColor, x0, y0, x1, y1 + Say " title[" & what & "]: day-names band starts at y=" & top & "; title text pixels span x=" & x0 & ".." & x1 & " (centre " & ((x0 + x1) / 2) & "), y=" & y0 & ".." & y1 & "; control width " & CalW & ": W/2 = " & (CalW / 2) & ", (W - 140)/2 = " & ((CalW - 140) / 2) + End Sub + + Private Sub P2() + On Error Resume Next + TitleSpan "ShowDateSelectors = True", CalW - 145 + Cal.ShowDateSelectors = False + TitleSpan "ShowDateSelectors = False", CalW - 1 + Shot "title_noselectors" + Cal.ShowDateSelectors = True + Chk "P2" + End Sub + + '========================================================================================= + ' p14: do the three text colours reach the text? Pixels of exactly those colours (not a hit + ' of the page's questions: found in the pictures) + '========================================================================================= + + ' The most frequent colours in a rectangle, "colour x count" each, the background first left out. + Private Function TopColours(ByVal x0 As Long, ByVal y0 As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal skip As Long) As String + Dim cols(0 To 63) As Long + Dim cnt(0 To 63) As Long + Dim n As Long + Dim x As Long + Dim y As Long + Dim c As Long + Dim i As Long + Dim j As Long + Dim s As String + PxOpen + For y = y0 To y1 + For x = x0 To x1 + c = Win.GetPixel(gDC, x, y) + If c <> skip Then + For i = 0 To n - 1 + If cols(i) = c Then cnt(i) = cnt(i) + 1: Exit For + Next + If i = n And n <= 63 Then cols(n) = c: cnt(n) = 1: n = n + 1 + End If + Next + Next + PxClose + ' the five largest + For j = 1 To 5 + Dim best As Long = -1 + Dim bi As Long = -1 + For i = 0 To n - 1 + If cnt(i) > best Then best = cnt(i): bi = i + Next + If bi >= 0 Then + s = s & Hx(cols(bi)) & " x" & cnt(bi) & " " + cnt(bi) = -2 + End If + Next + TopColours = s + End Function + + Private Sub TextColours(ByVal what As String) + Pump + Calibrate + Say " " & what & ": TitleFontColor=" & Hx(Cal.TitleFontColor) & ", the colours in the title text's box: " & TopColours(40, 8, 200, 36, Cal.BackColor) + Say " " & what & ": DayFontColor=" & Hx(Cal.DayFontColor) & ", the colours in the first day name's box: " & TopColours(4, 52, 50, gHeader - 4, Cal.DayBackColor) + Say " " & what & ": GridFontColor=" & Hx(Cal.GridFontColor) & ", the colours in the first cell's box: " & TopColours(4, gHeader + 3, 50, gHeader + 22, Cal.BackColor) + End Sub + + Private Sub P14() + On Error Resume Next + TextColours "defaults" + Cal.TitleFontColor = RGB(255, 0, 255) + Cal.GridFontColor = RGB(0, 0, 255) + Cal.DayFontColor = RGB(255, 255, 255) + TextColours "magenta title, blue grid, white day names" + Chk "P14" + End Sub + + '========================================================================================= + ' p3: a Value with a time of day (Q3) + '========================================================================================= + + Private Sub P3() + On Error Resume Next + Dim hl As Long = &H82868E + Calibrate + Rebase DateSerial(2026, 3, 15) + Say " grid for March 2026, Value = 2026-03-15: pixels of the selected-cell colour " & Hx(hl) & " in the grid: " & CountPx(hl, 0, gHeader + 1, CalW - 1, CalHt - 1) + St "date only" + Cal.Value = DateSerial(2026, 3, 15) + TimeSerial(14, 30, 0) + Pump + Say " Value = 2026-03-15 14:30:00: pixels of the selected-cell colour in the grid: " & CountPx(hl, 0, gHeader + 1, CalW - 1, CalHt - 1) + St "with a time" + Shot "timesel" + Chk "P3" + End Sub + + '========================================================================================= + ' p5: Enabled = False, the colours and the keyboard focus (Q4, Q5) + '========================================================================================= + + Private Sub BandColour(ByVal what As String) + Pump + Calibrate + Say " " & what & ": day-names band colour " & Hx(PxAt(3, gHeader - 6)) & " (enabled &HDCA86F, disabled &HEEEEEE); Enabled=" & Cal.Enabled & " window enabled=" & Win.IsWindowEnabled(CalH) & "; focus is " & FocusName() + End Sub + + Private Sub P5() + On Error Resume Next + BandColour "enabled, nothing focused" + Win.SetFocus Cmd1H + Pump + Win.SetFocus FocusH + Pump + BandColour "enabled, the control's picture box has the focus" + Act "Enabled = False" + Cal.Enabled = False + Done + BandColour "after Enabled = False" + Say " windows enabled now: control=" & Win.IsWindowEnabled(CalH) & " picture box=" & Win.IsWindowEnabled(FocusH) & " year combo=" & Win.IsWindowEnabled(YearH) & " month combo=" & Win.IsWindowEnabled(MonthH) + Shot "disabled_nofocus_move" + Act "the focus moves to Command2" + Win.SetFocus Cmd2H + Done + BandColour "after the focus moved to Command2" + Shot "disabled_after_blur" + Act "the focus moves back to the picture box, then to Command1" + Win.SetFocus FocusH + Pump + BandColour "after SetFocus on the picture box" + Win.SetFocus Cmd1H + Done + BandColour "after the focus moved to Command1" + Cal.Enabled = True + Pump + Say "-- the focus placed by a left click on a day cell (what puts it in the picture box), then Enabled = False" + Win.SetFocus Cmd1H + Pump + Calibrate + ClickCell 2, 2 + Pump + BandColour "enabled, after a click" + Act "Enabled = False" + Cal.Enabled = False + Done + BandColour "after Enabled = False" + Say " windows enabled now: control=" & Win.IsWindowEnabled(CalH) & " picture box=" & Win.IsWindowEnabled(FocusH) & " year combo=" & Win.IsWindowEnabled(YearH) & " month combo=" & Win.IsWindowEnabled(MonthH) + Act "the focus moves to Command2" + Win.SetFocus Cmd2H + Done + BandColour "after the focus moved to Command2" + Shot "disabled_blur2" + Cal.Enabled = True + Pump + BandColour "Enabled = True again" + Chk "P5" + End Sub + + '========================================================================================= + ' p7: hit-testing in a control 255 pixels wide (Q7) + '========================================================================================= + + Private Sub P7() + On Error Resume Next + Dim r As RECT + Dim pt As Win.POINT + Win.GetWindowRect CalH, r + pt.x = r.Left + pt.y = r.Top + Win.ScreenToClient Form1.hWnd, pt + Win.MoveWindow CalH, pt.x, pt.y, 255, 292, 1 + Pump + Win.GetClientRect CalH, r + CalW = r.Right + CalHt = r.Bottom + Calibrate + Say "control resized: client " & CalW & "x" & CalHt & ", header " & gHeader & ", cell width " & Format$(CalW / 7, "0.0000") & " (VBA rounds it to " & CLng(CalW / 7) & " for \), cell height " & Format$((CalHt - gHeader) / 6, "0.0000") & " (rounds to " & CLng((CalHt - gHeader) / 6) & ")" + Dim base As Date = DateSerial(2026, 6, 1) + Say "the grid is June 2026 with Monday first: the cell at row r, column c is 2026-06-01 + 7r + c days" + Dim xs As Variant + Dim ys As Variant + Dim x As Variant + Dim y As Variant + For Each x In Array(3, 218, 219, 251, 252, 254) + Rebase DateSerial(2026, 6, 15) + Act "left press at x=" & x & ", y=" & (gHeader + 5) & " (first row, the middle of the top of the cell)" + ClickAt CLng(x), gHeader + 5 + Pump + Say " Value is 2026-06-01 + " & CLng(Cal.Value - base) & " days = " & Dt(Cal.Value) & " (a column of the grid is 0 to 6)" + Next + For Each y In Array(CalHt - 1, CalHt - 2, CalHt - 3, CalHt - 5) + Rebase DateSerial(2026, 6, 15) + Act "left press at x=3, y=" & y & " (column 0)" + ClickAt 3, CLng(y) + Pump + Say " Value is 2026-06-01 + " & CLng(Cal.Value - base) & " days = " & Dt(Cal.Value) & " (a row of the grid is 0 to 5, so 0, 7, ... 35 are the valid first-column offsets)" + Next + Win.MoveWindow CalH, pt.x, pt.y, 384, 288, 1 + Pump + Win.GetClientRect CalH, r + CalW = r.Right + CalHt = r.Bottom + Calibrate + Chk "P7" + End Sub + + '========================================================================================= + ' p10: Tab into the control; arrow keys after a click; the order of the key events (Q10, Q11) + '========================================================================================= + + Private Sub P10() + On Error Resume Next + Dim i As Long + Cal.ShowDateSelectors = False + Pump + Win.SetFocus Cmd1H + Pump + Say "focus: " & FocusName() + Say "-- Tab, posted to the queue and dispatched through the form's dialog manager (IsDialogMessage)" + For i = 1 To 3 + Win.PostKey Win.GetFocus, Win.VK_TAB + Pump 0, True + Say " after Tab #" & i & ": focus is " & FocusName() + Next + Say "-- Shift+Tab" + Win.SetMods 1 + Win.PostKey Win.GetFocus, Win.VK_TAB + Pump 0, True + Win.SetMods 0 + Say " after Shift+Tab: focus is " & FocusName() + Say "-- Tab through DoEvents (the runtime's own loop)" + Win.SetFocus Cmd1H + Pump + Win.PostKey Win.GetFocus, Win.VK_TAB + DoEvents + Say " after Tab: focus is " & FocusName() + Cal.ShowDateSelectors = True + Pump + Say "-- arrow keys after a left click on a day cell (the click is sent as messages)" + Win.SetFocus Cmd1H + Pump + Calibrate + Rebase DateSerial(2026, 6, 15) + ClickCell 2, 2 + Pump + Say " focus after the click: " & FocusName() + St "after the click" + Act "Right arrow sent to the window that has the focus" + KeyTo Win.GetFocus, Win.VK_RIGHT + Done + St "after" + Say "-- the order of the key events, with the focus in the picture box" + Win.SetFocus FocusH + Pump + Rebase DateSerial(2026, 6, 15) + gLogMouseMove = False + Act "the A key posted to the queue: WM_KEYDOWN and WM_KEYUP, and TranslateMessage adds WM_CHAR" + Win.PostKey FocusH, 65 + Pump + St "after" + Act "the Right arrow key posted to the queue" + Win.PostKey FocusH, Win.VK_RIGHT + Pump + St "after" + Act "WM_KEYDOWN, WM_CHAR 'a', WM_KEYUP sent in turn to the picture box" + Win.SendMessageW FocusH, Win.WM_KEYDOWN, 65, Win.KeyLParam(65, False, False) + Win.SendMessageW FocusH, Win.WM_CHAR, 97, 1 + Win.SendMessageW FocusH, Win.WM_KEYUP, 65, Win.KeyLParam(65, True, False) + Done + Act "WM_KEYDOWN, WM_CHAR 'a', WM_KEYUP sent in turn to the control window" + Win.SendMessageW CalH, Win.WM_KEYDOWN, 65, Win.KeyLParam(65, False, False) + Win.SendMessageW CalH, Win.WM_CHAR, 97, 1 + Win.SendMessageW CalH, Win.WM_KEYUP, 65, Win.KeyLParam(65, True, False) + Done + Chk "P10" + End Sub + + '========================================================================================= + ' p9: the keys, as the system sends them: posted to the queue and dispatched by the runtime's + ' own loop (DoEvents), the key-down first, the key-up after it (Q8, Q9, Q11) + '========================================================================================= + + ' A key press: WM_KEYDOWN (WM_SYSKEYDOWN with Alt) posted to the window h and dispatched by + ' DoEvents, which also delivers the WM_CHAR that TranslateMessage makes from it, then the + ' key-up the same way. mods: bit 0 Shift, bit 1 Ctrl, bit 2 Alt, held by SetKeyboardState. + Private Sub KeyReal(ByVal h As LongPtr, ByVal vk As Long, Optional ByVal mods As Long = 0) + Dim alt As Boolean = (mods And 4) <> 0 + If mods <> 0 Then Win.SetMods mods + Win.PostMessageW h, IIf(alt, Win.WM_SYSKEYDOWN, Win.WM_KEYDOWN), vk, Win.KeyLParam(vk, False, alt) + DoEvents + Win.PostMessageW h, IIf(alt, Win.WM_SYSKEYUP, Win.WM_KEYUP), vk, Win.KeyLParam(vk, True, alt) + DoEvents + If mods <> 0 Then Win.SetMods 0 + End Sub + + Private Sub RealCase(ByVal h As LongPtr, ByVal vk As Long, ByVal mods As Long) + On Error Resume Next + Cal.Value = DateSerial(2026, 6, 15) + Pump + Act "key " & VkName(vk) & IIf((mods And 1) <> 0, " + Shift", "") & IIf((mods And 2) <> 0, " + Ctrl", "") & IIf((mods And 4) <> 0, " + Alt", "") + KeyReal h, vk, mods + Pump + If gEvCount = 0 Then Say " (no event)" + St "after" + End Sub + + Private Sub P9() + On Error Resume Next + Dim vk As Variant + Dim h As LongPtr + Cal.ShowDateSelectors = False + Pump + Calibrate + ClickCell 2, 2 + Pump + h = Win.GetFocus + Say "after a click on a cell the focus is " & FocusName() & "; the keys are posted to it" + Say "-- plain keys" + For Each vk In Array(Win.VK_LEFT, Win.VK_RIGHT, Win.VK_UP, Win.VK_DOWN, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME, Win.VK_END, 65, 13, 32, 9) + RealCase h, CLng(vk), 0 + Next + Say "-- with Shift" + For Each vk In Array(Win.VK_LEFT, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + RealCase h, CLng(vk), 1 + Next + Say "-- with Ctrl" + For Each vk In Array(Win.VK_LEFT, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + RealCase h, CLng(vk), 2 + Next + Say "-- with Alt (WM_SYSKEYDOWN / WM_SYSKEYUP)" + For Each vk In Array(Win.VK_LEFT, Win.VK_PRIOR, Win.VK_NEXT, Win.VK_HOME) + RealCase h, CLng(vk), 4 + Next + Say "-- with Shift and Ctrl" + RealCase h, Win.VK_PRIOR, 3 + RealCase h, Win.VK_NEXT, 3 + Cal.ShowDateSelectors = True + Pump + Chk "P9" + End Sub + + '========================================================================================= + ' p11: which key events a real key press raises: keys posted to the queue (Q8, Q11) + '========================================================================================= + + Private Sub P11() + On Error Resume Next + Dim h As LongPtr + Calibrate + Win.SetFocus Cmd1H + Pump + Rebase DateSerial(2026, 6, 15) + ClickCell 2, 2 + Pump + Say "after a click on a cell, focus is " & FocusName() + h = Win.GetFocus + Rebase DateSerial(2026, 6, 15) + Act "only WM_KEYDOWN (Right) posted to the focus window and dispatched by the probe's loop" + Win.PostMessageW h, Win.WM_KEYDOWN, Win.VK_RIGHT, Win.KeyLParam(Win.VK_RIGHT, False, False) + Pump + St "after" + Act "only WM_KEYUP (Right) posted" + Win.PostMessageW h, Win.WM_KEYUP, Win.VK_RIGHT, Win.KeyLParam(Win.VK_RIGHT, True, False) + Pump + St "after" + Act "WM_KEYDOWN and WM_KEYUP (Right) posted, probe's loop" + Win.PostKey h, Win.VK_RIGHT + Pump + St "after" + Act "WM_KEYDOWN and WM_KEYUP ('A', 65) posted, probe's loop" + Win.PostKey h, 65 + Pump + Say "-- the same through DoEvents, the runtime's own loop (selectors hidden so that it returns)" + Cal.ShowDateSelectors = False + Pump + Rebase DateSerial(2026, 6, 15) + ClickCell 2, 2 + Pump + h = Win.GetFocus + Say " focus is " & FocusName() + Rebase DateSerial(2026, 6, 15) + Act "WM_KEYDOWN and WM_KEYUP (Right) posted, DoEvents" + Win.PostKey h, Win.VK_RIGHT + DoEvents + St "after" + Act "WM_KEYDOWN and WM_KEYUP ('A') posted, DoEvents" + Win.PostKey h, 65 + DoEvents + Act "WM_KEYDOWN (Shift+Page Down) posted, DoEvents (Shift set with SetKeyboardState)" + Win.SetMods 1 + Win.PostKey h, Win.VK_NEXT + DoEvents + Win.SetMods 0 + St "after" + Cal.ShowDateSelectors = True + Chk "P11" + End Sub + + '========================================================================================= + ' p12: where the keyboard focus is after a click, and whether the arrow keys then work (Q10) + '========================================================================================= + + ' A click on a cell (focus first on `from`), then where the focus is, and what Right does. + Private Sub FocusAfterClick(ByVal what As String, ByVal from As LongPtr, Optional ByVal outsideGrid As Boolean = False) + On Error Resume Next + Dim v0 As Date + Win.SetFocus from + Pump + Rebase DateSerial(2026, 6, 15) + Say " " & what & ": focus before the click: " & FocusName() + gQuiet = True + If outsideGrid Then ClickAt 20, 4 Else ClickCell 2, 2 + Pump + gQuiet = False + v0 = Cal.Value + Say " " & what & ": focus after the click: " & FocusName() & "; Value " & Dt(v0) + gEvCount = 0 + KeyReal Win.GetFocus, Win.VK_RIGHT, 0 + Pump + Say " " & what & ": after Right arrow (posted to the focus window, DoEvents): Value " & Dt(Cal.Value) & IIf(Cal.Value <> v0, " -> the arrow key moved the selection", " -> the arrow key did nothing") & "; events " & gEvCount + End Sub + + Private Sub P12() + On Error Resume Next + Cal.ShowDateSelectors = False + Pump + Calibrate + FocusAfterClick "1 from Command1", Cmd1H + FocusAfterClick "2 from Command1 again", Cmd1H + FocusAfterClick "3 from Command2", Cmd2H + FocusAfterClick "4 from the form", Form1.hWnd + FocusAfterClick "5 from the control window", CalH + FocusAfterClick "6 from the picture box", FocusH + Cal.Enabled = False + Cal.Enabled = True + Pump + FocusAfterClick "7 after Enabled False then True, from Command1", Cmd1H + gCancel = True + FocusAfterClick "8 with BeforeUpdate cancelling the click, from Command1", Cmd1H + gCancel = False + FocusAfterClick "9 a click in the title band, from Command1", Cmd1H, True + Say "-- clicks in a row: the first, then the second and third with the focus where the first left it" + Win.SetFocus Cmd1H + Pump + Rebase DateSerial(2026, 6, 15) + ClickCell 2, 2 + Pump + Say " after the 1st click: focus is " & FocusName() + ClickCell 3, 3 + Pump + Say " after the 2nd click: focus is " & FocusName() + ClickCell 1, 1 + Pump + Say " after the 3rd click: focus is " & FocusName() + Cal.ShowDateSelectors = True + Pump + Chk "P12" + End Sub + + '========================================================================================= + ' p18: a font object changed in place (Q18) + '========================================================================================= + + Private Sub P18() + On Error Resume Next + Dim f As Font + Dim g As New StdFont + Dim a As Long + Dim b As Long + Dim c As Long + Dim d As Long + Dim x0 As Long + Dim y0 As Long + Dim x1 As Long + Dim y1 As Long + Dim h0 As Long + Say "-- selectors visible (the control repaints all the time)" + Pump + Calibrate + h0 = gHeader + Set f = Cal.TitleFont + f.Size = 24 + Pump + Calibrate + Say " header (title + day names) before: " & h0 & "; after f.Size = 24 on f = Cal.TitleFont (no Set) and a pump: " & gHeader + f.Size = 16 + Pump + Say "-- selectors hidden (nothing repaints until something asks)" + Cal.ShowDateSelectors = False + Pump + Calibrate + h0 = gHeader + f.Size = 24 + Calibrate + Say " header before: " & h0 & "; right after f.Size = 24 (no message processed yet): " & gHeader + Pump + Calibrate + Say " after a pump of the message loop: " & gHeader & " (" & gPumped & " messages)" + Cal.Refresh + Calibrate + Say " right after Cal.Refresh: " & gHeader + Pump + Calibrate + Say " after another pump: " & gHeader + f.Size = 16 + Cal.Refresh + Pump + Cal.ShowDateSelectors = True + Pump + Say "-- one font object (Arial) in DayFont and GridFont, changed in place" + g.Name = "Arial" + g.Size = 8 + Set Cal.DayFont = g + Set Cal.GridFont = g + Cal.Refresh + Pump + Calibrate + h0 = gHeader + BBox 2, gHeader + 2, CLng(gColW) - 2, CLng(gHeader + gRowH) - 2, Cal.BackColor, x0, y0, x1, y1 + Say " g.Size = 8: header " & gHeader & "; digits of the first cell span " & (x1 - x0 + 1) & " x " & (y1 - y0 + 1) & " pixels" + g.Size = 20 + Cal.Refresh + Pump + Calibrate + BBox 2, gHeader + 2, CLng(gColW) - 2, CLng(gHeader + gRowH) - 2, Cal.BackColor, x0, y0, x1, y1 + Say " g.Size = 20 (no Set): header " & gHeader & "; digits of the first cell span " & (x1 - x0 + 1) & " x " & (y1 - y0 + 1) & " pixels" + Say " Cal.DayFont Is Cal.GridFont: " & (Cal.DayFont Is Cal.GridFont) + Shot "sharedfont" + Set Cal.DayFont = Nothing + Set Cal.GridFont = Nothing + Chk "restoring" + End Sub + + '========================================================================================= + ' p19: TitleMargin and DaysMargin in pixels (Q19) + '========================================================================================= + + Private Sub Edges(ByVal what As String) + Pump + Calibrate + Say " " & what & ": day-names band y=" & FirstY(3, Cal.DayBackColor, 0, CalHt - 1) & ".." & LastY(3, Cal.DayBackColor, 0, CalHt - 1) & "; hit-test header " & gHeader & " (dpi " & Win.GetDpiForWindow(CalH) & ")" + End Sub + + Private Sub P19() + On Error Resume Next + Edges "defaults 5, 5" + Cal.TitleMargin = 15 + Edges "TitleMargin = 15" + Cal.TitleMargin = 5 + Cal.DaysMargin = 15 + Edges "DaysMargin = 15" + Cal.DaysMargin = 5 + Edges "defaults again" + Chk "P19" + End Sub + + '========================================================================================= + ' p20: FirstDay and the grid (Q20, Q31) + '========================================================================================= + + Private Sub P20() + On Error Resume Next + Rebase DateSerial(2026, 6, 15) + Calibrate + Say "June 2026 with Monday first: row 0 is Mon 1 ... Sun 7" + Shot "fd_monday" + Act "FirstDay = 1 (Sunday)" + Cal.FirstDay = 1 + Pump + Shot "fd_sunday_stale" + Act "Refresh" + Cal.Refresh + Pump + Shot "fd_refresh" + Act "NextMonth then PreviousMonth (a change of month places the dates again)" + Cal.NextMonth + Cal.PreviousMonth + Pump + Shot "fd_replaced" + Say "-- the dates the stale grid holds, read through a click on cell (0, 0)" + Cal.FirstDay = 2 + Rebase DateSerial(2026, 6, 15) + Cal.FirstDay = 1 + Pump + gLogMouseMove = False + Act "left click on cell (0, 0) with FirstDay = 1 and no change of month since" + ClickCell 0, 0 + Pump + St "Value after the click is the first date the grid held" + Cal.FirstDay = 2 + Chk "P20" + End Sub + + '========================================================================================= + ' p21: the grid-line colours (Q21) + '========================================================================================= + + Private Sub P21() + On Error Resume Next + Dim y As Long + Cal.GridCellEffect = 0 + Pump + Calibrate + y = CLng(gHeader + gRowH * 0.85) + Say "GridCellEffect = 0 (Flat), default GridLinesColor " & Hx(Cal.GridLinesColor) & "; vertical lines near x = " & CLng(gColW) & ", " & CLng(2 * gColW) & ": colours along row y=" & y + Say " " & RowRuns(y, 0, CLng(2 * gColW) + 3) + Say " colours down column x=20 over the first two rows: " & ColRuns(20, gHeader - 2, CLng(gHeader + gRowH) + 3) + Cal.GridLinesColor = RGB(255, 0, 0) + Pump + Say "GridLinesColor = RGB(255, 0, 0) = " & Hx(Cal.GridLinesColor) & ": colours along row y=" & y + Say " " & RowRuns(y, 0, CLng(2 * gColW) + 3) + Say " colours down column x=20 over the first two rows: " & ColRuns(20, gHeader - 2, CLng(gHeader + gRowH) + 3) + Shot "linecolour" + Cal.GridLinesColor = &H80000010 + Cal.GridCellEffect = 1 + Chk "P21" + End Sub + + '========================================================================================= + ' p24: Value and the grid (Q24) + '========================================================================================= + + Private Sub P24() + On Error Resume Next + Calibrate + Rebase DateSerial(2026, 3, 15) + Shot "val_march" + Act "Value = #2030-06-15#" + Cal.Value = DateSerial(2030, 6, 15) + Pump + Say " title says: " & Cal.Month & "/" & Cal.Year + Shot "val_2030" + Say "-- the first date the grid holds, read through a click on cell (0, 0)" + ClickCell 0, 0 + Pump + St "after the click on cell (0, 0)" + Shot "val_afterclick" + Chk "P24" + End Sub + + '========================================================================================= + ' p25: ValueIsNull and the drawing (Q25) + '========================================================================================= + + Private Sub P25() + On Error Resume Next + Dim f0 As Double + Dim f1 As Double + Dim f2 As Double + Pump + f0 = Fingerprint() + Act "ValueIsNull = True" + Cal.ValueIsNull = True + Chk "ValueIsNull = True" + Done + Pump + f1 = Fingerprint() + Say " ValueIsNull reads " & Cal.ValueIsNull + Act "ValueIsNull = False" + Cal.ValueIsNull = False + Chk "ValueIsNull = False" + Done + Pump + f2 = Fingerprint() + Say " ValueIsNull reads " & Cal.ValueIsNull + Say " fingerprints of the control's pixels: before " & f0 & ", ValueIsNull = True " & f1 & ", False again " & f2 & ": identical = " & (f0 = f1 And f1 = f2) + Chk "P25" + End Sub + + '========================================================================================= + ' p29: chains of NextMonth and PreviousMonth (Q29) + '========================================================================================= + + Private Sub Step(ByVal op As String) + On Error Resume Next + Select Case op + Case "NextMonth": Cal.NextMonth + Case "PreviousMonth": Cal.PreviousMonth + Case "NextYear": Cal.NextYear + Case "PreviousYear": Cal.PreviousYear + End Select + Chk op + Pump + St "after" + End Sub + + Private Sub P29() + On Error Resume Next + Cal.Value = DateSerial(2026, 1, 31) + Pump + Say "from 2026-01-31:" + Step "NextMonth" + Step "NextMonth" + Step "NextMonth" + Cal.Value = DateSerial(2026, 3, 31) + Pump + Say "from 2026-03-31:" + Step "PreviousMonth" + Step "PreviousMonth" + Cal.Value = DateSerial(2024, 2, 29) + Pump + Say "from 2024-02-29:" + Step "NextYear" + Step "PreviousYear" + Step "PreviousYear" + Cal.Value = DateSerial(2024, 2, 29) + Pump + Step "PreviousYear" + Chk "P29" + End Sub + + '========================================================================================= + ' p33: the value of Cancel in BeforeUpdate (Q33) + '========================================================================================= + + Private Sub P33() + On Error Resume Next + Dim v As Variant + Dim f0 As Double + Calibrate + gCancel = True + For Each v In Array(-1, 1, 0) + gCancelVal = CInt(v) + Rebase DateSerial(2026, 6, 15) + Pump + f0 = Fingerprint() + Act "left click on cell (1, 3) (2026-06-11), the handler sets Cancel = " & v + ClickCell 1, 3 + Pump + St "after" + Say " drawing unchanged: " & (Fingerprint() = f0) + Next + gCancel = False + gCancelVal = -1 + Chk "P33" + End Sub + + '========================================================================================= + ' p22x: Month = 13 and then a repaint (Q22). Run on its own: it may end the run. + '========================================================================================= + + Private Sub P22x() + On Error Resume Next + Act "Month = 13" + Cal.Month = 13 + Chk "Month = 13" + St "after" + Say " Month reads " & Cal.Month & " and the month list shows: [" & Win.TextOf(MonthH) & "]" + Act "Refresh" + Cal.Refresh + Chk "Refresh" + Done + Say " pumped " & gPumped & " messages" + Cal.Month = 6 + Chk "Month = 6" + St "after" + End Sub + +End Module diff --git a/test/package-probes/xCalendar/src/Win.twin b/test/package-probes/xCalendar/src/Win.twin new file mode 100644 index 000000000..a118aa088 --- /dev/null +++ b/test/package-probes/xCalendar/src/Win.twin @@ -0,0 +1,171 @@ +' Win: the Win32 declarations and small helpers of the xCalendar probe. +' +' The probe simulates input by sending window messages to the control's windows with SendMessage: +' it is delivered synchronously, in the order the probe sends it, to the window procedure the +' control's events are raised from. A modifier key is simulated with SetKeyboardState, which +' writes the table GetKeyState reads (the Shift argument of a key or mouse event comes from it). + +Module Win + + Public Type RECT + Left As Long + Top As Long + Right As Long + Bottom As Long + End Type + + Public Type POINT + x As Long + y As Long + End Type + + Public Type MSG + hWnd As LongPtr + message As Long + wParam As LongPtr + lParam As LongPtr + time As Long + pt As POINT + End Type + + Public Declare PtrSafe Function PeekMessageW Lib "user32" (ByRef lpMsg As MSG, ByVal hWnd As LongPtr, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long + Public Declare PtrSafe Function TranslateMessage Lib "user32" (ByRef lpMsg As MSG) As Long + Public Declare PtrSafe Function DispatchMessageW Lib "user32" (ByRef lpMsg As MSG) As LongPtr + Public Declare PtrSafe Function SendMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr + Public Declare PtrSafe Function PostMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long + Public Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal uCmd As Long) As LongPtr + Public Declare PtrSafe Function GetParent Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Public Declare PtrSafe Function GetClassNameW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long + Public Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As LongPtr, ByRef lpRect As RECT) As Long + Public Declare PtrSafe Function GetClientRect Lib "user32" (ByVal hWnd As LongPtr, ByRef lpRect As RECT) As Long + Public Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long + Public Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long + Public Declare PtrSafe Function GetDlgCtrlID Lib "user32" (ByVal hWnd As LongPtr) As Long + Public Declare PtrSafe Function GetFocus Lib "user32" () As LongPtr + Public Declare PtrSafe Function SetFocus Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Public Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr + Public Declare PtrSafe Function GetKeyboardState Lib "user32" (ByRef lpKeyState As Byte) As Long + Public Declare PtrSafe Function SetKeyboardState Lib "user32" (ByRef lpKeyState As Byte) As Long + Public Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer + Public Declare PtrSafe Function MapVirtualKeyW Lib "user32" (ByVal uCode As Long, ByVal uMapType As Long) As Long + Public Declare PtrSafe Function GetWindowLongW Lib "user32" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long + Public Declare PtrSafe Function GetClassLongW Lib "user32" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long + Public Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr + Public Declare PtrSafe Function GetDpiForWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) + Public Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long + Public Declare PtrSafe Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) + Public Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Public Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As Long + Public Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hDC As LongPtr, ByVal x As Long, ByVal y As Long) As Long + Public Declare PtrSafe Function MoveWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long + Public Declare PtrSafe Function IsDialogMessageW Lib "user32" (ByVal hDlg As LongPtr, ByRef lpMsg As MSG) As Long + Public Declare PtrSafe Function GetAncestor Lib "user32" (ByVal hWnd As LongPtr, ByVal gaFlags As Long) As LongPtr + Public Declare PtrSafe Function IsChild Lib "user32" (ByVal hWndParent As LongPtr, ByVal hWnd As LongPtr) As Long + Public Declare PtrSafe Function ScreenToClient Lib "user32" (ByVal hWnd As LongPtr, ByRef lpPoint As POINT) As Long + + Public Const WM_SYSKEYDOWN As Long = &H104 + Public Const WM_SYSKEYUP As Long = &H105 + Public Const VK_TAB As Long = 9 + Public Const VK_MENU_ALT As Long = &H12 + + Public Const WM_SETFOCUS As Long = &H7 + Public Const WM_GETTEXT As Long = &HD + Public Const WM_GETTEXTLENGTH As Long = &HE + Public Const WM_KEYDOWN As Long = &H100 + Public Const WM_KEYUP As Long = &H101 + Public Const WM_CHAR As Long = &H102 + Public Const WM_COMMAND As Long = &H111 + Public Const WM_MOUSEMOVE As Long = &H200 + Public Const WM_LBUTTONDOWN As Long = &H201 + Public Const WM_LBUTTONUP As Long = &H202 + Public Const WM_LBUTTONDBLCLK As Long = &H203 + Public Const WM_RBUTTONDOWN As Long = &H204 + Public Const WM_RBUTTONUP As Long = &H205 + Public Const CB_GETCOUNT As Long = &H146 + Public Const CB_GETCURSEL As Long = &H147 + Public Const CB_GETLBTEXT As Long = &H148 + Public Const CB_GETLBTEXTLEN As Long = &H149 + Public Const CB_SETCURSEL As Long = &H14E + Public Const CB_SHOWDROPDOWN As Long = &H14F + Public Const CB_GETDROPPEDSTATE As Long = &H157 + Public Const GW_HWNDNEXT As Long = 2 + Public Const GW_CHILD As Long = 5 + Public Const MK_LBUTTON As Long = 1 + Public Const MK_RBUTTON As Long = 2 + Public Const MK_SHIFT As Long = 4 + Public Const MK_CONTROL As Long = 8 + Public Const VK_SHIFT As Long = &H10 + Public Const VK_CONTROL As Long = &H11 + Public Const VK_MENU As Long = &H12 + + Public Function ClassOf(ByVal h As LongPtr) As String + Dim s As String = String$(256, 0) + Dim n As Long = GetClassNameW(h, StrPtr(s), 256) + ClassOf = Left$(s, n) + End Function + + Public Function TextOf(ByVal h As LongPtr) As String + Dim n As Long = CLng(SendMessageW(h, WM_GETTEXTLENGTH, 0, 0)) + Dim s As String = String$(n + 2, 0) + n = CLng(SendMessageW(h, WM_GETTEXT, n + 1, StrPtr(s))) + TextOf = Left$(s, n) + End Function + + Public Function MakeLParam(ByVal x As Long, ByVal y As Long) As LongPtr + MakeLParam = CLngPtr(((y And &HFFFF&) * &H10000) Or (x And &HFFFF&)) + End Function + + ' The virtual key codes the probe sends. + Public Const VK_PRIOR As Long = &H21 + Public Const VK_NEXT As Long = &H22 + Public Const VK_END As Long = &H23 + Public Const VK_HOME As Long = &H24 + Public Const VK_LEFT As Long = &H25 + Public Const VK_UP As Long = &H26 + Public Const VK_RIGHT As Long = &H27 + Public Const VK_DOWN As Long = &H28 + + ' Sets the modifier keys GetKeyState reports: bit 0 Shift, bit 1 Ctrl, bit 2 Alt. + Public Sub SetMods(ByVal mods As Long) + Dim ks(0 To 255) As Byte + GetKeyboardState ks(0) + ks(VK_SHIFT) = IIf((mods And 1) <> 0, &H80, 0) + ks(&HA0) = ks(VK_SHIFT) + ks(VK_CONTROL) = IIf((mods And 2) <> 0, &H80, 0) + ks(&HA2) = ks(VK_CONTROL) + ks(VK_MENU) = IIf((mods And 4) <> 0, &H80, 0) + ks(&HA4) = ks(VK_MENU) + SetKeyboardState ks(0) + End Sub + + ' The lParam of a key message: repeat count 1, the scan code, the extended-key bit for the + ' cursor block, the context bit (Alt held) for a system key, and the key-up bits. + Public Function KeyLParam(ByVal vk As Long, ByVal isUp As Boolean, ByVal alt As Boolean) As LongPtr + Dim scan As Long = MapVirtualKeyW(vk, 0) + Dim lp As Long = 1 Or (scan * &H10000) + If vk >= VK_PRIOR And vk <= VK_DOWN Then lp = lp Or &H1000000 + If alt Then lp = lp Or &H20000000 + If isUp Then lp = lp Or &HC0000000 + KeyLParam = CLngPtr(lp) + End Function + + ' WM_KEYDOWN then WM_KEYUP for a virtual key, sent to the window h, with the modifiers mods + ' held (bit 0 Shift, bit 1 Ctrl, bit 2 Alt). With Alt held they are the system messages + ' WM_SYSKEYDOWN and WM_SYSKEYUP, which is what Windows sends then. + Public Sub SendKey(ByVal h As LongPtr, ByVal vk As Long, Optional ByVal mods As Long = 0) + Dim alt As Boolean = (mods And 4) <> 0 + If mods <> 0 Then SetMods mods + SendMessageW h, IIf(alt, WM_SYSKEYDOWN, WM_KEYDOWN), vk, KeyLParam(vk, False, alt) + SendMessageW h, IIf(alt, WM_SYSKEYUP, WM_KEYUP), vk, KeyLParam(vk, True, alt) + If mods <> 0 Then SetMods 0 + End Sub + + ' The same two messages posted to the queue of the window h, for the message loop to dispatch: + ' its TranslateMessage then adds the WM_CHAR a character key makes. + Public Sub PostKey(ByVal h As LongPtr, ByVal vk As Long) + PostMessageW h, WM_KEYDOWN, vk, KeyLParam(vk, False, False) + PostMessageW h, WM_KEYUP, vk, KeyLParam(vk, True, False) + End Sub + +End Module diff --git a/test/package-probes/xCalendar/stage.mjs b/test/package-probes/xCalendar/stage.mjs new file mode 100644 index 000000000..dc39d86ef --- /dev/null +++ b/test/package-probes/xCalendar/stage.mjs @@ -0,0 +1,76 @@ +// Stages the xCalendar probe's source tree: the contributed template's Settings +// (test/example-projects/contributed-xCalendar), the package embedded under Packages/xCalendar as +// check_examples embeds it, and src/. Used by probe.mjs (build and run the exe) and designer.mjs +// (open the tree in an IDE and edit the control in the form designer). +// +// The package comes from the cache check_examples keeps, %TEMP%\tb-twinserv, and is downloaded from +// TWINSERV into it when it is not there. It is not read from the indexer's store: the store keeps +// only the .twin file, and the control's windows (the two combo boxes, the picture boxes) are +// declared in the xCalendar.tbcontrol designer file. +import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { downloadPackage } from "../../../indexer/lib/twinserv-client.mjs"; +import { embeddedPackages } from "../../../scripts/lib/example-batches.mjs"; +import { unpackProject } from "../../../scripts/lib/tb-project.mjs"; + +const here = import.meta.dirname; +const template = path.join(here, "..", "..", "example-projects", "contributed-xCalendar"); + +/** The text with CRLF line ends, as the IDE writes a source file. */ +export const crlf = (text) => text.replace(/\r?\n/g, "\r\n"); + +/** + * Write the source tree to %TEMP%\xcal-probe\tree- and return its path. + * + * @param {object} o + * @param {string} o.key names the folder (the caller's port) + * @param {string} [o.form] a .tbform file to use in place of src/Form1.tbform + * @param {string[]} [o.set] "Name=JSON" entries added to the control's entry in Form1.tbform + * @throws {Error} when the package cannot be fetched or an entry of `set` is not Name=JSON + */ +export async function stageTree({ key, form, set = [] }) { + const [embed] = embeddedPackages(JSON.parse(readFileSync(path.join(template, "Settings"), "utf8"))); + if (!embed) throw new Error("the contributed-xCalendar template embeds no package"); + const cache = path.join(tmpdir(), "tb-twinserv"); + const pkg = path.join(cache, embed.key); + if (!existsSync(path.join(pkg, "Settings"))) { + const file = `${pkg}.twinpack`; + if (!existsSync(file)) { + console.error(`probe: downloading ${embed.folder} (${embed.key}) from TWINSERV`); + try { + mkdirSync(cache, { recursive: true }); + writeFileSync(file, await downloadPackage(embed.id, embed.version)); + } catch (e) { + throw new Error(`cannot download the package: ${e.message}`); + } + } + unpackProject(file, pkg); + } + + const tree = path.join(tmpdir(), "xcal-probe", `tree-${key}`); + rmSync(tree, { recursive: true, force: true }); + mkdirSync(path.join(tree, "Sources"), { recursive: true }); + cpSync(path.join(template, "Settings"), path.join(tree, "Settings")); + cpSync(pkg, path.join(tree, "Packages", embed.folder), { recursive: true }); + for (const f of readdirSync(path.join(here, "src"))) { + writeFileSync(path.join(tree, "Sources", f), crlf(readFileSync(path.join(here, "src", f), "utf8")), "utf8"); + } + const formFile = path.join(tree, "Sources", "Form1.tbform"); + if (form) writeFileSync(formFile, crlf(readFileSync(form, "utf8")), "utf8"); + if (set.length) { + const data = JSON.parse(readFileSync(formFile, "utf8")); + const control = data[0]._children.find((c) => c.Name === "xCalendar1"); + for (const kv of set) { + const i = kv.indexOf("="); + if (i < 1) throw new Error(`--set wants Name=JSON, not ${kv}`); + try { + control[kv.slice(0, i)] = JSON.parse(kv.slice(i + 1)); + } catch (e) { + throw new Error(`--set ${kv}: ${e.message}`); + } + } + writeFileSync(formFile, crlf(`${JSON.stringify(data, null, "\t")}\n`), "utf8"); + } + return tree; +} From e0a71a92db4b5cfd65239c1bd4fba88d67bf23c0 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 11:14:57 +0200 Subject: [PATCH 05/15] Experimental Packages: a page after Contributed Packages for contributed packages not yet ready for use, holding BigNumberLib and WinReg (published again) and xCalendar; their URLs stay under Contributed, so a fixed package moves back without changing them --- WIP.ExamplesBuild.md | 3 +-- WIP.md | 4 ++-- builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 5 ++--- .../Contributed/BigNumberLib/index.md | 2 +- docs/Reference/Contributed/WinReg/index.md | 2 +- docs/Reference/Contributed/xCalendar/index.md | 2 +- docs/Reference/Experimental.md | 17 ++++++++++++++++ docs/Reference/Packages.md | 1 + docs/_book.yml | 20 +++++++++++++++++++ docs/_config.yml | 6 ------ 11 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 docs/Reference/Experimental.md diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index e27b62510..d888534e4 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -339,8 +339,7 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em template-alone build finds those rows; the run prints them as a note naming the package rather than as a template FAIL, every other template row still fails, and the samples are judged by their own rows as before. Their landing pages say so in a NOTE, and both sections - are excluded from the site until the packages compile; `examples.bat` still compiles their - samples, since it reads every page under `docs/` whatever `exclude:` says. + are listed under Experimental Packages until the packages compile. - **When a section's `indexed_from` moves to a new version, change the version fields in its `test/example-projects/contributed-/Settings` with it**, or its samples compile against the old package. A new contributed section needs a template of its own: a copy of diff --git a/WIP.md b/WIP.md index 0b99f5ba1..645d50a9a 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Sixteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each published one has a chapter in the book's Packages part. **BigNumberLib and WinReg are not published**: their packages do not compile on BETA 1005, so their pages are kept but excluded in `_config.yml`'s `exclude:` (owner, 2026-10-11), whose comment says how to bring one back. +Sixteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, and xCalendar has defects that can hang a program. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. @@ -79,7 +79,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad > would file the page as a *static file* and serve the raw markdown. Editors on > Windows add a BOM unasked. `lib/frontmatter.mjs` strips a leading BOM before parsing. - `docs/Reference/Built-In/tbIDE/` — IDE Extensibility package (this is the **addin SDK**). The package is type-only — it ships **public interfaces + CoClasses** that an addin DLL binds to; every implementation behind them lives in the twinBASIC IDE itself. The user-facing surface is one entry-point factory (`tbCreateCompilerAddin`) plus 23 CoClasses grouped by role: the addin contract (`AddIn`), the root API (`Host`), the loaded `Project`, the editors collection (`Editor` / `CodeEditor` / `Editors`), the virtual file system (`FileSystem` / `FileSystemItem` / `Folder` / `File`), the in-IDE UI surface (`Toolbar` / `Toolbars` / `Button` / `ToolWindow` / `ToolWindows`), the HTML DOM inside a tool window (`HtmlElement` / `HtmlElements` / `HtmlElementProperty` / `HtmlElementProperties` / `HtmlEventProperty` / `HtmlEventProperties`), the `DebugConsole`, `KeyboardShortcuts`, `Themes`, and the single concrete user-instantiable helper class `AddinTimer`. Flat layout — one page per CoClass / Class plus the index landing. -- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner, 2026-10-11): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner, 2026-10-11): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1005") names that newest build. +- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner, 2026-10-11): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner, 2026-10-11): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1005") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner, 2026-10-12): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and its book chapter follows the Experimental Packages chapter. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three, and its pages keep their addresses. - `docs/Reference/Statements.md` — alphabetical index of language statements. - `docs/Reference/Procedures and Functions.md` — alphabetical index of procedures/functions. - `docs/LLVM/` — the LLVM section: compiling with the LLVM back end. A top-level section between Features and Reference Section in the nav, at `nav_order: 6` (Features is 5). `index.md` is the landing page and `Getting-Started.md` the only page so far, with its screenshots under `Images/`. Everything it describes arrived in **BETA 984**. Its language claims are measured on BETA 995 with `tbrun --llvm` and `--exe`: the comma, space and comma-space spellings of `[CompilerOptions]` all turn LLVM and `+optimize` on, `[CompilerOptions("")]` turns it off under project-wide LLVM, the long CPU-flag sample runs, and an error is lost between procedures when either the callee or the caller is LLVM-compiled. Its samples are marked `check_build`, which compiles them; `examples.bat --llvm` also builds them with LLVM. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 74483225a..a43f65060 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1139, + "pages": 1157, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index fe8db31d1..928ff88eb 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -10,7 +10,7 @@ has_toc: false Community-authored packages published through TWINSERV, the twinBASIC package registry. To use a contributed package, add it to Project → References (Ctrl-T) → Available Packages. -Each package is versioned apart from twinBASIC, and its section says which version it documents. +Each package is versioned apart from twinBASIC, and its section says which version it documents. Packages that are not yet ready for use in a program are listed under [Experimental Packages](../Experimental/). - [**AutoCompleteLib**](AutoCompleteLib/) -- Windows shell auto-complete for text-input controls - [**FileProps**](FileProps/) -- the Windows shell properties of a file, read and written @@ -24,5 +24,4 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**tbRegExp**](tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object - [**TwinTimerPackage**](TwinTimerPackage/) -- a Win32 timer class that reports the elapsed time - [**VBA_JSON**](VBA_JSON/) -- JSON parsing and serialization, with UTC and ISO 8601 date conversion -- [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs -- [**xCalendar**](xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX` +- [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs \ No newline at end of file diff --git a/docs/Reference/Contributed/BigNumberLib/index.md b/docs/Reference/Contributed/BigNumberLib/index.md index 6f0a4c213..3f3c865cc 100644 --- a/docs/Reference/Contributed/BigNumberLib/index.md +++ b/docs/Reference/Contributed/BigNumberLib/index.md @@ -1,6 +1,6 @@ --- title: BigNumberLib Package -parent: Contributed Packages +parent: Experimental Packages has_toc: false indexed_from: 0.0.0.0 permalink: /tB/Packages/Contributed/BigNumberLib/ diff --git a/docs/Reference/Contributed/WinReg/index.md b/docs/Reference/Contributed/WinReg/index.md index 881afe78b..e3fc74d6e 100644 --- a/docs/Reference/Contributed/WinReg/index.md +++ b/docs/Reference/Contributed/WinReg/index.md @@ -1,6 +1,6 @@ --- title: WinReg Package -parent: Contributed Packages +parent: Experimental Packages has_toc: false indexed_from: 0.0.0.0 permalink: /tB/Packages/Contributed/WinReg/ diff --git a/docs/Reference/Contributed/xCalendar/index.md b/docs/Reference/Contributed/xCalendar/index.md index 4dccb0b62..50dc5947d 100644 --- a/docs/Reference/Contributed/xCalendar/index.md +++ b/docs/Reference/Contributed/xCalendar/index.md @@ -1,6 +1,6 @@ --- title: xCalendar Package -parent: Contributed Packages +parent: Experimental Packages has_toc: false indexed_from: 1.0.7.0 permalink: /tB/Packages/Contributed/xCalendar/ diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md new file mode 100644 index 000000000..a3bfdd501 --- /dev/null +++ b/docs/Reference/Experimental.md @@ -0,0 +1,17 @@ +--- +title: Experimental Packages +parent: Packages +nav_order: 4 +permalink: /tB/Packages/Experimental/ +has_toc: false +--- + +# Experimental Packages + +Community-authored packages from TWINSERV that are not yet ready for use in a program. + +These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. + +- [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. diff --git a/docs/Reference/Packages.md b/docs/Reference/Packages.md index 566d3a49e..8af4d6a56 100644 --- a/docs/Reference/Packages.md +++ b/docs/Reference/Packages.md @@ -12,3 +12,4 @@ A *package* groups related code --- modules, classes, controls, and enumerations - [**Default Packages**](Default/) -- VB, VBA, and VBRUN -- included in every project by default - [**Built-In Packages**](Built-In/) -- {{tbdocs:builtInPackages}} optional packages shipped with twinBASIC, always available offline - [**Contributed Packages**](Contributed/) -- community-authored packages published through TWINSERV, the twinBASIC package registry +- [**Experimental Packages**](Experimental/) -- community-authored packages from TWINSERV that are not yet ready for use in a program diff --git a/docs/_book.yml b/docs/_book.yml index 8cb501ac9..245f09edf 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -579,6 +579,26 @@ parts: landing_is_target: true outline_closed: true + - title: Experimental Packages + subtitle: Community packages from TWINSERV that are not yet ready for use in a program + landing_page: /tB/Packages/Experimental/ + landing_is_target: true + outline_closed: true + + - title: BigNumberLib Package + subtitle: Arbitrary-precision signed integer arithmetic + landing_page: /tB/Packages/Contributed/BigNumberLib/ + page: /tB/Packages/Contributed/BigNumberLib/ + landing_is_target: true + outline_closed: true + + - title: WinReg Package + subtitle: Registry keys and values, read, written, deleted and enumerated + landing_page: /tB/Packages/Contributed/WinReg/ + page: /tB/Packages/Contributed/WinReg/ + landing_is_target: true + outline_closed: true + - title: xCalendar Package subtitle: A calendar control, in place of the Calendar control of MSCAL.OCX landing_page: /tB/Packages/Contributed/xCalendar/ diff --git a/docs/_config.yml b/docs/_config.yml index c89703083..81c50e863 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -158,12 +158,6 @@ exclude: # Affinity sources are the only thing _Images holds, and a stray one # filed anywhere else must not ship either. - "**/*.af" - # Contributed packages that do not compile on the newest twinBASIC BETA. Their - # pages are kept, unpublished, until a version of the package compiles again: - # delete the line, take the package's chapter back into _book.yml and its line - # back into Reference/Contributed.md, and remove the page's NOTE that says so. - - "Reference/Contributed/BigNumberLib/**" - - "Reference/Contributed/WinReg/**" # Defensive: caches and unrelated trees that should never be in docs/. - "**/.git/**" - "**/node_modules/**" From ef6ae37f829a7eac862927586d8979ef33961925 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 12:01:26 +0200 Subject: [PATCH 06/15] TBMANLIB_Xml: a contributed section of four pages (cXml, cXmlNode, mXml), listed under Experimental Packages: its parser misreads self-closing elements, entities and some tags, and does not return for some malformed text; each defect shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../Contributed/TBMANLIB_Xml/cXml.md | 1146 +++++++++++++++++ .../Contributed/TBMANLIB_Xml/cXmlNode.md | 363 ++++++ .../Contributed/TBMANLIB_Xml/index.md | 128 ++ .../Contributed/TBMANLIB_Xml/mXml.md | 228 ++++ docs/Reference/Experimental.md | 1 + docs/_book.yml | 7 + .../contributed/TBMANLIB_Xml/api.json | 206 +++ .../contributed-TBMANLIB_Xml/Settings | 74 ++ 10 files changed, 2155 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB_Xml/cXml.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Xml/cXmlNode.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Xml/index.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Xml/mXml.md create mode 100644 indexer/snapshots/contributed/TBMANLIB_Xml/api.json create mode 100644 test/example-projects/contributed-TBMANLIB_Xml/Settings diff --git a/WIP.md b/WIP.md index 645d50a9a..910f64e32 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Sixteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, and xCalendar has defects that can hang a program. See *Where things live* for how a package moves between the two lists. +Seventeen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index a43f65060..107905ada 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1157, + "pages": 1161, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md new file mode 100644 index 000000000..c53ba948b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md @@ -0,0 +1,1146 @@ +--- +title: cXml +parent: TBMANLIB_Xml Package +permalink: /tB/Packages/Contributed/TBMANLIB_Xml/cXml +has_toc: false +--- + +# cXml class +{: .no_toc } + +A **cXml** holds one XML document. It reads the document from a string or a file, or starts a new one with a root element. It finds, reads and changes elements and attributes by a path such as `library/book`, and writes the document back as a string or a file. The elements, text and comments of the document are [**cXmlNode**](cXmlNode) objects, which the [**Document**](#document) and [**RootElement**](#rootelement) properties and the search methods return. + +An object is created with `Dim x As New cXml`. A new object holds no document. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**CreateNew**](#createnew) or the [**XmlString**](#xmlstring) property gives it one, and until then the members that take a path find nothing. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Debug.Print x.GetElementText("library/book") +Debug.Print x.GetAttribute("library/book", "id") +Debug.Print x.SelectElements("book").Count + +x.AddElement "library", "note" +x.SetElementText "library/note", "checked" +Debug.Print x.ToXmlString(False) +' Output: +' Dune +' 1 +' 2 +' DuneEmmachecked +``` + +> [!WARNING] +> In version 1.0.0.0, the parser reads some valid XML wrongly and raises no error: a self-closing element, `&` and other entities in text, a tag that uses both kinds of quote or has a line break between its attributes, and a processing instruction. A comment, a CDATA section or a DOCTYPE declaration in the wrong place makes **Parse** loop forever. [Defects in version 1.0.0.0](#defects) lists each case. + +* TOC +{:toc} + +## Paths +{: #paths } + +The members that take an *xpath* parameter find an element by a path: [**SelectElement**](#selectelement), [**GetElementText**](#getelementtext), [**SetElementText**](#setelementtext), [**GetAttribute**](#getattribute), [**SetAttribute**](#setattribute), [**RemoveAttribute**](#removeattribute) and [**RemoveElement**](#removeelement), and [**AddElement**](#addelement) for its *parentXPath*. The name of the parameter is misleading: the syntax is a small one of the package's own and is not XPath. + +- A path is a list of names separated by `/`, such as `library/book`. The search starts at the document node, so the first name is the name of the root element: `library/book` finds a book, and `book` finds nothing. +- Each name selects the first child, of the node found so far, whose name equals it. Letter case is ignored. Only direct children count, and when several children have the name, the first one is taken. +- White space around a name is removed, and an empty name is ignored. `/library//book` and ` library / book ` both mean `library/book`. +- A name is compared as a whole, so a namespace prefix is part of it: `ns:item` is the name of an element written ``. The package does no namespace processing. +- Nothing else has a meaning. A position (`book[2]`), an attribute (`@id`), `*`, `.` and `..` are names that no element has, so the path selects nothing. `//book` does not search at any depth: the empty name is ignored, and the path means `book`. [**SelectElements**](#selectelements) searches at any depth, and the **ChildNodes** of a node reach the other children. +- The nodes that are not elements have names too, `#text`, `#comment` and `#cdata-section`, and a path can select them: `library/#comment` is the first comment directly inside the root element. +- A path with no name, an empty string or `/`, selects nothing, and the Debug Console shows `SelectElement failed: Subscript out of range`. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Debug.Print "[" & x.GetElementText("book") & "]" +Debug.Print "[" & x.GetElementText("library/book") & "]" +Debug.Print "[" & x.GetElementText("/LIBRARY/Book") & "]" +Debug.Print "[" & x.GetElementText(" library // book ") & "]" +Debug.Print "[" & x.GetElementText("library/book[2]") & "]" +' Output: +' [] +' [Dune] +' [Dune] +' [Dune] +' [] +``` + +To reach the second book, [**SelectElements**](#selectelements) returns every book, or the **ChildNodes** of the parent can be searched: + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim books As Collection +Set books = x.SelectElements("book") +Debug.Print books(2).GetAttribute("id") & " " & books(2).ChildNodes(1).Value + +Dim children As Collection +Set children = x.RootElement.GetChildNodesByName("book") +Debug.Print children(2).GetAttribute("id") +' Output: +' 2 Emma +' 2 +``` + +## Reporting failures +{: #reporting-failures } + +A member that cannot do its work returns a value that says so, and does not stop the program. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**SaveToFile**](#savetofile), [**Validate**](#validate) and [**RemoveElement**](#removeelement) return **False**. [**SelectElement**](#selectelement) and [**AddElement**](#addelement) return **Nothing**. [**GetElementText**](#getelementtext) and [**GetAttribute**](#getattribute) return an empty string, and [**SelectElements**](#selectelements) an empty collection. A method that returns nothing, such as [**SetAttribute**](#setattribute), does nothing. + +When an error causes the failure, the member also: + +- writes a line to the Debug Console that names the member and gives the error's description, such as `Parse failed: XML string cannot be empty` or `SelectElement failed: Document not initialized`. In a built executable, **Debug.Print** writes nothing (see [**Debug**](../../../Modules/Debug)), so the line appears in the IDE only; +- leaves the error in the **Err** object. After the call returns, **Err.Number**, **Err.Source** and **Err.Description** describe it. A program reads them straight after the call, because the next call of most members clears them, and calls `Err.Clear` before the call so that an earlier error does not remain in them. + +The errors that occur are [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document), which [**Parse**](#parse) raises for an empty string and [**SelectElement**](#selectelement) for a document that has not been read or created, and the errors of the Visual Basic runtime that the code meets while it works: 5 (*Invalid procedure call or argument*) for a tag cut off by the end of the text, 9 (*Subscript out of range*) for a path with no name, and 91 (*Object variable or With block variable not set*) in [**RemoveElement**](#removeelement) for a path that selects nothing. The other error constants of [**mXml**](mXml#constants) are not used. + +The members that let an error reach the program are [**XmlString**](#xmlstring), [**ToXmlString**](#toxmlstring) and [**Merge**](#merge). + +```tb check_build check_run +Dim x As New cXml + +Err.Clear +Debug.Print x.Parse("") +Debug.Print Err.Number = ERR_EMPTY_DOCUMENT +Debug.Print Err.Source & ": " & Err.Description + +Err.Clear +Debug.Print (x.SelectElement("library") Is Nothing) +Debug.Print Err.Number = ERR_EMPTY_DOCUMENT +' Output: +' Parse failed: XML string cannot be empty +' False +' True +' cXml.Parse: XML string cannot be empty +' SelectElement failed: Document not initialized +' True +' True +``` + +## Properties + +### Document +{: .no_toc } + +Returns the document node. + +Syntax: *object*.**Document** **As cXmlNode** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +Read-only. The node has the **NodeType** **Document** and the **Name** `#document`. Its **ChildNodes** hold the root element and any comments and text outside it, in document order, including text that is only white space. A new object holds an empty document node. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**CreateNew**](#createnew) and [**XmlString**](#xmlstring) replace it. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Debug.Print x.Document.Name & " " & x.Document.NodeType +Dim n As cXmlNode +For Each n In x.Document.ChildNodes + Debug.Print n.Name +Next +' Output: +' #document 9 +' #comment +' r +``` + +### Encoding +{: .no_toc } + +Returns or sets the encoding name that the XML declaration states. + +Syntax: *object*.**Encoding** [ **=** *value* ] + +*object* +: *required* An object expression that evaluates to a **cXml**. + +A **String**. The default is `UTF-8`, the value of [**DEFAULT_ENCODING**](mXml#default_encoding). [**ToXmlString**](#toxmlstring) and [**SaveToFile**](#savetofile) write it into the declaration. [**Parse**](#parse) and [**LoadFromFile**](#loadfromfile) set it from the `encoding` of the declaration of the text they read, when the declaration has one written with double quotes. Without one, the property keeps the value it had, which is the value of an earlier document if the object read one. [**CreateNew**](#createnew) leaves it as it is. + +The property is a label. [**SaveToFile**](#savetofile) always writes UTF-8 and [**LoadFromFile**](#loadfromfile) always reads UTF-8, whatever the label says. + +```tb check_build check_run +Dim x As New cXml +Debug.Print x.Encoding + +x.Parse "" +Debug.Print x.Encoding + +x.Encoding = "UTF-16" +Debug.Print x.ToXmlString() +' Output: +' UTF-8 +' ISO-8859-1 +' +``` + +### Initialized +{: .no_toc } + +Returns whether the object holds a document. + +Syntax: *object*.**Initialized** **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +Read-only. It is **False** for a new object. It becomes **True** when [**Parse**](#parse), [**LoadFromFile**](#loadfromfile) or [**XmlString**](#xmlstring) reads text, even text with no element in it, and when [**CreateNew**](#createnew) creates a document, and [**Clone**](#clone) and [**Merge**](#merge) can set it. A **Parse** that fails leaves it as it was. + +While it is **False**, [**SelectElement**](#selectelement) returns **Nothing**, and the members built on it find nothing. [**SelectElements**](#selectelements) returns an empty collection, and [**Validate**](#validate) returns **False**. + +```tb check_build check_run +Dim x As New cXml +Debug.Print x.Initialized +x.CreateNew "r" +Debug.Print x.Initialized +' Output: +' False +' True +``` + +### RootElement +{: .no_toc } + +Returns the root element. + +Syntax: *object*.**RootElement** **As cXmlNode** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +Read-only. It returns the first element directly inside the document node, or **Nothing** for a new object and for text that holds no element. [**Parse**](#parse), [**LoadFromFile**](#loadfromfile), [**XmlString**](#xmlstring), [**CreateNew**](#createnew) and [**Clone**](#clone) set it. Nothing else changes it, so it can name an element that is no longer in the document; see [Clone, Merge and the root element](#defect-clone). + +```tb check_build check_run +Dim x As New cXml +Debug.Print (x.RootElement Is Nothing) + +x.Parse "1" +Debug.Print x.RootElement.Name +Debug.Print x.RootElement.ChildNodes.Count +' Output: +' True +' a +' 1 +``` + +### Version +{: .no_toc } + +Returns or sets the XML version that the XML declaration states. + +Syntax: *object*.**Version** [ **=** *value* ] + +*object* +: *required* An object expression that evaluates to a **cXml**. + +A **String**. The default is `1.0`, the value of [**DEFAULT_XML_VERSION**](mXml#default_xml_version). [**ToXmlString**](#toxmlstring) and [**SaveToFile**](#savetofile) write it into the declaration. [**Parse**](#parse) and [**LoadFromFile**](#loadfromfile) set it from the `version` of the declaration of the text they read, when the declaration has one written with double quotes; otherwise the property keeps the value it had. The package does not check the value. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" +Debug.Print x.Version +Debug.Print x.ToXmlString() +' Output: +' 1.1 +' +``` + +### XmlString +{: .no_toc } + +Returns or sets the text of the document that was last read. + +Syntax: *object*.**XmlString** [ **=** *value* ] + +*object* +: *required* An object expression that evaluates to a **cXml**. + +A **String**. Assigning to it reads the text as [**Parse**](#parse) does and replaces the document. Reading it returns the text that [**Parse**](#parse), [**LoadFromFile**](#loadfromfile) or an assignment last read. It is not made again from the document, so it does not show later changes: [**ToXmlString**](#toxmlstring) does. [**Clone**](#clone) does not copy it. + +Assigning an empty string is not an error, unlike **Parse** with an empty string: it gives an initialised document with no nodes. + +> [!NOTE] +> Assigning text that the parser cannot read raises an error, where **Parse** returns **False** for the same text. Assigning `<` raises error 5, *Invalid procedure call or argument*. The text is kept, so reading the property returns it. + +```tb check_build check_run +Dim x As New cXml +x.XmlString = "1" +Debug.Print x.GetElementText("r/a") + +x.SetElementText "r/a", "2" +Debug.Print x.XmlString +Debug.Print x.ToXmlString(False) +' Output: +' 1 +' 1 +' 2 +``` + +## Methods + +### New +{: .no_toc } + +Creates a **cXml** object. + +Syntax: **New cXml** + +The constructor takes no arguments. The new object has an empty document node, the [**Version**](#version) `1.0` and the [**Encoding**](#encoding) `UTF-8`, and [**Initialized**](#initialized) is **False**. + +```tb check_build check_run +Dim x As New cXml +Debug.Print x.Initialized & " " & x.Version & " " & x.Encoding +' Output: +' False 1.0 UTF-8 +``` + +### AddElement +{: .no_toc } + +Adds an empty element as the last child of an element. + +Syntax: *object*.**AddElement** ( *parentXPath* , *elementName* ) **As cXmlNode** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*parentXPath* +: *required* A **String**, the [path](#paths) of the element that receives the new one. + +*elementName* +: *required* A **String**, the name of the new element. + +Returns the new [**cXmlNode**](cXmlNode), whose **Parent** is the element found. Returns **Nothing**, and adds nothing, when the path selects nothing. A document has one root element, which [**CreateNew**](#createnew) or the text read gives it, so there is no way to add a second. + +The name is not checked. An empty name or a name with a space is accepted, and [**Validate**](#validate) rejects an empty one. The new element has no text. [**SetElementText**](#setelementtext) gives it some, by the path of the new element, which is the path of the parent and the name; when several siblings have the name, the path reaches the first. A program that holds the returned node can instead add a text node to its **ChildNodes** (see [**ChildNodes**](cXmlNode#childnodes)). + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "library" + +Dim book As cXmlNode +Set book = x.AddElement("library", "book") +book.SetAttribute "id", "1" +x.SetElementText "library/book", "Dune" + +Debug.Print (x.AddElement("library/shelf", "book") Is Nothing) +Debug.Print x.ToXmlString(False) +' Output: +' True +' Dune +``` + +### Clone +{: .no_toc } + +Returns a copy of the document. + +Syntax: *object*.**Clone** ( ) **As cXml** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +Returns a new **cXml** with its own copy of every node, so that a change to one document does not reach the other. The copy has the same [**Version**](#version) and [**Encoding**](#encoding), and is initialised when the original is. For an object that holds no document, the result holds none either. The [**XmlString**](#xmlstring) of the copy is empty. + +The copied nodes have no **Parent**, which stops [**RemoveElement**](#removeelement) from removing them; see [Clone, Merge and the root element](#defect-clone). + +```tb check_build check_run +Dim x As New cXml +x.Parse "1" + +Dim y As cXml +Set y = x.Clone() +y.SetElementText "r/a", "2" + +Debug.Print x.GetElementText("r/a") & " " & y.GetElementText("r/a") +' Output: +' 1 2 +``` + +### CreateNew +{: .no_toc } + +Replaces the document with a new one that holds only a root element. + +Syntax: *object*.**CreateNew** ( *rootElementName* ) + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*rootElementName* +: *required* A **String**, the name of the root element. It is not checked. + +Discards the document and creates an empty root element with that name. [**Initialized**](#initialized) becomes **True**. [**Version**](#version), [**Encoding**](#encoding) and [**XmlString**](#xmlstring) keep the values they had. + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "config" +Debug.Print x.ToXmlString() +' Output: +' +``` + +### GetAttribute +{: .no_toc } + +Returns an attribute of an element. + +Syntax: *object*.**GetAttribute** ( *xpath* , *attrName* ) **As String** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored. + +Returns the value of the attribute, as [**GetAttribute**](cXmlNode#getattribute) of the element's node does. Returns an empty string when the path selects nothing or the element has no such attribute, which a program cannot tell from an attribute whose value is empty. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Debug.Print "[" & x.GetAttribute("r/a", "id") & "]" +Debug.Print "[" & x.GetAttribute("r/a", "ID") & "]" +Debug.Print "[" & x.GetAttribute("r/a", "empty") & "]" +Debug.Print "[" & x.GetAttribute("r/a", "missing") & "]" +Debug.Print "[" & x.GetAttribute("r/nothing", "id") & "]" +' Output: +' [7] +' [7] +' [] +' [] +' [] +``` + +### GetElementText +{: .no_toc } + +Returns the text inside an element. + +Syntax: *object*.**GetElementText** ( *xpath* ) **As String** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +Returns the text of the element: the values of all the text nodes and CDATA sections inside it, at any depth, one after the other in document order with nothing between them. Comments add nothing. Text that is only white space is included, so the text of an element in an indented document holds the line breaks and the indentation. Entities in the text are returned as written; see [Entities](#defect-entities). + +Returns an empty string when the path selects nothing, which a program cannot tell from an element with no text. + +```tb check_build check_run +Dim x As New cXml +x.Parse "

Hello big world

" + +Debug.Print "[" & x.GetElementText("p") & "]" +Debug.Print "[" & x.GetElementText("p/b") & "]" +Debug.Print "[" & x.GetElementText("p/i") & "]" + +x.Parse "" & vbCrLf & " 1" & vbCrLf & "" +Debug.Print Replace(Replace(x.GetElementText("r"), " ", "."), vbCrLf, "|") +' Output: +' [Hello big world & more] +' [big] +' [] +' |..1| +``` + +### LoadFromFile +{: .no_toc } + +Reads a document from a file. + +Syntax: *object*.**LoadFromFile** ( *filePath* ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*filePath* +: *required* A **String**, the path of the file. + +Reads the whole file, removes a UTF-8 byte order mark if the file starts with one, converts the bytes as UTF-8 with [**UTF8BytesToString**](mXml#utf8bytestostring) and reads the text as [**Parse**](#parse) does. Returns **True** when the file was read. + +Returns **False**, and writes `LoadFromFile failed:` and the description to the Debug Console, when the file cannot be opened, is empty, or has fewer than three bytes. It returns **True** when the file was read but the text then fails to parse, because it ignores the result of **Parse**; see [Files](#defect-files). The file is read as UTF-8 whatever its declaration says, and the declaration's encoding only sets [**Encoding**](#encoding). + +```tb check_build check_run +Dim path As String +path = Environ$("TEMP") & "\tbxml_loadfromfile.xml" + +Dim f As Long +f = FreeFile +Open path For Output As #f +Print #f, "" +Print #f, "demo" +Close #f + +Dim x As New cXml +Debug.Print x.LoadFromFile(path) +Debug.Print x.GetElementText("config/name") +Kill path +' Output: +' True +' demo +``` + +### Merge +{: .no_toc } + +Appends the content of another document to this one. + +Syntax: *object*.**Merge** ( *otherXml* ) + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*otherXml* +: *required* A **cXml**, the document to copy from. It is not changed. + +Appends a copy of every child of the root element of *otherXml*, text and comments included, to the end of the root element of this document. The names and attributes of the two root elements are not compared or merged, and this document keeps its own. Nothing happens when *otherXml* holds no document. + +When this object holds no document, it becomes a copy of the whole of *otherXml*, and [**Initialized**](#initialized) becomes **True**. Its [**RootElement**](#rootelement) stays **Nothing**, so a second **Merge** into it does nothing. + +The copies have no **Parent**; see [Clone, Merge and the root element](#defect-clone). + +> [!NOTE] +> **Merge** with **Nothing** as *otherXml* raises error 91, *Object variable or With block variable not set*, because the code tests the object and its property in one expression. + +```tb check_build check_run +Dim a As New cXml +Dim b As New cXml +a.Parse "1" +b.Parse "2" + +a.Merge b +Debug.Print a.ToXmlString(False) +Debug.Print b.ToXmlString(False) +' Output: +' 12 +' 2 +``` + +### Parse +{: .no_toc } + +Reads a document from a string. + +Syntax: *object*.**Parse** ( *xml* ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xml* +: *required* A **String**, the text to read. + +Replaces the document with the one in the text, sets [**RootElement**](#rootelement) and [**XmlString**](#xmlstring), and returns **True**. The `version` and `encoding` of an XML declaration, written with double quotes, set [**Version**](#version) and [**Encoding**](#encoding). + +The parser builds these nodes: elements with their attributes, text, comments and CDATA sections. Text that is only white space is kept, in the document and inside elements. An end tag closes the innermost open element whatever its name is, and an element still open at the end of the text is closed by it. Processing instructions and the declaration are not kept. + +Returns **False**, and writes `Parse failed:` and the description to the Debug Console, for an empty string and for a tag that the end of the text cuts off, such as `<` or `text") +Debug.Print x.Version & " " & x.Encoding & " " & x.RootElement.Name + +Debug.Print x.Parse("") +Debug.Print x.Parse("not xml") +Debug.Print x.Parse("") +Debug.Print x.Parse("
" + +x.RemoveAttribute "r", "A" +x.RemoveAttribute "r", "missing" +x.RemoveAttribute "nothing", "b" +Debug.Print x.ToXmlString(False) +' Output: +' +``` + +### RemoveElement +{: .no_toc } + +Removes an element, with everything inside it. + +Syntax: *object*.**RemoveElement** ( *xpath* ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +Takes the first element that the path selects out of the **ChildNodes** of its parent, and returns **True**. Returns **False** when the path selects nothing, and for a node that has no **Parent**, which includes the nodes that [**Clone**](#clone) and [**Merge**](#merge) copy. In the first case the Debug Console shows `RemoveElement failed: Object variable or With block variable not set`. + +Removing the root element leaves [**RootElement**](#rootelement) as it was; see [Clone, Merge and the root element](#defect-clone). + +```tb check_build check_run +Dim x As New cXml +x.Parse "12" + +Debug.Print x.RemoveElement("r/a") +Debug.Print x.GetElementText("r/a") +Debug.Print x.RemoveElement("r/missing") +' Output: +' True +' 2 +' RemoveElement failed: Object variable or With block variable not set +' False +``` + +### SaveToFile +{: .no_toc } + +Writes the document to a file. + +Syntax: *object*.**SaveToFile** ( *filePath* [ , *withDeclaration* ] ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*filePath* +: *required* A **String**, the path of the file. An existing file is deleted first. + +*withDeclaration* +: *optional* A **Boolean**. **True** (the default) writes the XML declaration before the document. + +Writes the three bytes of a UTF-8 byte order mark, then the document as [**ToXmlString**](#toxmlstring) returns it with *withDeclaration* and no other option, converted with [**StringToUTF8Bytes**](mXml#stringtoutf8bytes). The text is never indented. Returns **True** when the file was written, and **False**, with `SaveToFile failed:` and the description in the Debug Console, when it could not be, for example because the folder does not exist. + +The file is UTF-8 whatever [**Encoding**](#encoding) says, and the declaration states that value. A document with no content written without a declaration is the byte order mark followed by one zero byte. + +```tb check_build check_run +Dim path As String +path = Environ$("TEMP") & "\tbxml_savetofile.xml" + +Dim x As New cXml +x.CreateNew "note" +x.AddElement "note", "to" +x.SetElementText "note/to", "Ada" +Debug.Print x.SaveToFile(path) + +Dim f As Long +f = FreeFile +Open path For Binary Access Read As #f +Dim head(0 To 2) As Byte +Get #f, , head +Close #f +Debug.Print Hex$(head(0)) & " " & Hex$(head(1)) & " " & Hex$(head(2)) + +Dim y As New cXml +Debug.Print y.LoadFromFile(path) +Debug.Print y.GetElementText("note/to") +Kill path +' Output: +' True +' EF BB BF +' True +' Ada +``` + +### SelectElement +{: .no_toc } + +Returns the element that a path selects. + +Syntax: *object*.**SelectElement** ( *xpath* ) **As cXmlNode** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +Returns the [**cXmlNode**](cXmlNode) that the path selects, or **Nothing** when it selects nothing. It also returns **Nothing** when [**Initialized**](#initialized) is **False** and when the path holds no name, and in both cases the Debug Console shows a line that begins `SelectElement failed:`. + +The other members that take a path call this one. + +```tb check_build check_run +Dim x As New cXml +x.Parse "DuneEmma" + +Dim n As cXmlNode +Set n = x.SelectElement("library/book") +Debug.Print n.GetAttribute("id") & " " & n.ChildNodes(1).Value +Debug.Print (x.SelectElement("library/magazine") Is Nothing) +' Output: +' 1 Dune +' True +``` + +### SelectElements +{: .no_toc } + +Returns every node with a given name. + +Syntax: *object*.**SelectElements** ( *tagName* ) **As Collection** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*tagName* +: *required* A **String**, one name and not a path. Letter case is ignored. + +Returns a new **Collection** of the [**cXmlNode**](cXmlNode) objects, at any depth in the document, whose **Name** matches, in document order. An element inside another element of the same name is returned too. The collection is empty when nothing matches and when [**Initialized**](#initialized) is **False**; the method never returns **Nothing**. + +The names of the nodes that are not elements match, so `#text` returns every text node. The method does not read paths: `r/a` matches nothing. + +```tb check_build check_run +Dim x As New cXml +x.Parse "123" + +Dim found As Collection +Set found = x.SelectElements("a") +Debug.Print found.Count + +Dim n As cXmlNode +For Each n In found + Debug.Print n.Name & " " & n.ChildNodes(1).Value +Next +Debug.Print x.SelectElements("r/a").Count +' Output: +' 3 +' a 1 +' a 2 +' A 3 +' 0 +``` + +### SetAttribute +{: .no_toc } + +Sets an attribute of an element. + +Syntax: *object*.**SetAttribute** ( *xpath* , *attrName* , *attrValue* ) + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +*attrName* +: *required* A **String**, the name of the attribute. + +*attrValue* +: *required* A **String**, the value. + +Adds the attribute, or replaces the one with the same name, as [**SetAttribute**](cXmlNode#setattribute) of the element's node does: a replaced attribute moves to the end of the element's attributes. Nothing happens, and no error is raised, when the path selects nothing. + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "r" + +x.SetAttribute "r", "a", "1" +x.SetAttribute "r", "b", "2" +x.SetAttribute "r", "a", "3" +x.SetAttribute "r/missing", "z", "1" +Debug.Print x.ToXmlString(False) +' Output: +' +``` + +### SetElementText +{: .no_toc } + +Replaces the text of an element. + +Syntax: *object*.**SetElementText** ( *xpath* , *text* ) + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*xpath* +: *required* A **String**, the [path](#paths) of the element. + +*text* +: *required* A **String**, the new text, as plain text: [**ToXmlString**](#toxmlstring) escapes it when it writes it. + +Removes the text nodes directly inside the element and adds one text node that holds *text*, after the element's other children. The child elements, comments and CDATA sections stay, so the text moves to the end of an element with mixed content. Setting an empty string leaves an empty text node, which makes [**ToXmlString**](#toxmlstring) write `` and not ``. Nothing happens, and no error is raised, when the path selects nothing. + +```tb check_build check_run +Dim x As New cXml +x.Parse "onebeetwo" + +x.SetElementText "r/a", "NEW" +x.SetElementText "r/missing", "lost" +Debug.Print x.ToXmlString(False) +' Output: +' beeNEW +``` + +### ToXmlString +{: .no_toc } + +Returns the document as text. + +Syntax: *object*.**ToXmlString** ( [ *withDeclaration* [ , *prettyPrint* [ , *indent* ] ] ] ) **As String** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +*withDeclaration* +: *optional* A **Boolean**. **True** (the default) starts the text with a declaration such as ``, written from [**Version**](#version) and [**Encoding**](#encoding), with no line break after it. + +*prettyPrint* +: *optional* A **Boolean**. **False** (the default) writes the document compactly: nothing is added between the nodes. **True** puts each element, comment, CDATA section and text on a line of its own, ends every line with a carriage return and a line feed, and leaves out the text nodes that are only white space. See [Pretty printing](#defect-pretty) for what the indentation looks like. + +*indent* +: *optional* A **String**, the indentation for each level of nesting. The default is two spaces. Only the first character of the string is used. + +The text holds elements, text, comments and CDATA sections. An element with no child is written as ``, and an element with children as `...`. The characters `&`, `<`, `>`, `"` and `'` are replaced by `&`, `<`, `>`, `"` and `'` in text and in the values of attributes. Comments and CDATA sections are written as they are. The attributes of an element are written in the order of its attribute list. + +A document that holds nothing gives the declaration alone, or an empty string without one. + +> [!NOTE] +> An empty *indent* with *prettyPrint* **True** raises error 5, *Invalid procedure call or argument*, as soon as the document has an element. + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "r" +x.SetAttribute "r", "id", "1" +x.AddElement "r", "a" +x.SetElementText "r/a", "x < y" +x.AddElement "r", "empty" + +Debug.Print x.ToXmlString() +Debug.Print x.ToXmlString(False) +' Output: +' x < y +' x < y +``` + +### Validate +{: .no_toc } + +Checks that every element and attribute has a name. + +Syntax: *object*.**Validate** ( ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a **cXml**. + +Returns **True** when the object holds a document and every element in it has a name that is not empty and does not begin with `/`, and every attribute has a name that is not empty. Returns **False** otherwise, and for an object that holds no document. + +That is all it checks. It does not test that the text was well formed, that the names are legal XML names, or anything about a schema or a DTD. In practice it detects an element added with an empty name, an attribute set with an empty name, and the element named `/a` that the parser makes from the self-closing tag `` (see [Self-closing elements](#defect-self-closing)). + +```tb check_build check_run +Dim x As New cXml +Debug.Print x.Validate() + +x.Parse "" +Debug.Print x.Validate() + +x.Parse "" +Debug.Print x.Validate() + +x.Parse "" +Debug.Print x.Validate() +' Output: +' False +' True +' False +' True +``` + +## Defects in version 1.0.0.0 +{: #defects } + +Each of these sections describes a case in which the package gives a wrong result or never returns. The samples show the result of the version named here. + +### Self-closing elements +{: #defect-self-closing .no_toc } + +> [!WARNING] +> A self-closing tag is read wrongly. ``, a name and nothing else, makes an element named `/a`. A tag with attributes, or with a space before the slash, such as `` or ``, is read as a start tag, so the elements that follow it become its children, and the end tag of the parent closes it. [**ToXmlString**](#toxmlstring) writes an empty element as ``, so text that a **cXml** wrote is read wrongly by a **cXml**. + +```tb check_build check_run +Dim x As New cXml +x.Parse "t" + +Debug.Print x.ToXmlString(False) +Debug.Print "[" & x.GetElementText("r/c") & "]" +Debug.Print x.GetElementText("r/b/c") +Debug.Print x.Validate() + +x.CreateNew "r" +x.AddElement "r", "empty" +Dim y As New cXml +y.Parse x.ToXmlString(False) +Debug.Print y.RootElement.ChildNodes(1).Name +' Output: +' t
+' [] +' t +' False +' /empty +``` + +### Entities and character references +{: #defect-entities .no_toc } + +> [!WARNING] +> Entities in text are not decoded. [**GetElementText**](#getelementtext) and the **Value** of a text node return `&`, `<` and `A` as they stand in the XML, and [**ToXmlString**](#toxmlstring) escapes the `&` again, so each read and write adds `amp;`. In the value of an attribute the five predefined entities `<`, `>`, `&`, `"` and `'` are decoded, in that order, so `&quot;` becomes a quotation mark. Numeric character references such as `A` and `A` are not decoded anywhere. + +```tb check_build check_run +Dim x As New cXml +x.Parse "Tom & Jerry" + +Debug.Print x.GetAttribute("a", "title") +Debug.Print x.GetElementText("a") +Debug.Print x.ToXmlString(False) +' Output: +' Tom & Jerry A +' Tom & Jerry +' Tom &amp; Jerry +``` + +Text set with [**SetElementText**](#setelementtext) or [**SetAttribute**](#setattribute) is plain text and is written correctly, once. A program that reads text with entities in it has to decode them itself. + +### Tags +{: #defect-tags .no_toc } + +> [!WARNING] +> A tag is cut apart with plain searches, so these valid forms give wrong attributes and no error. A tag that uses both kinds of quote loses an attribute: the first double quote is taken as the start of the first value, even when a single-quoted value comes before it. A `>` inside an attribute value ends the tag at that point, and the rest of the value becomes text. A line break between two attributes becomes the name of an attribute, and the real name is lost. A line break between the element name and the first attribute becomes part of the element's name. An attribute with no value takes the value of the next one. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" +Debug.Print x.GetAttribute("a", "b") & "|" & x.GetAttribute("a", "c") + +x.Parse "y"">" +Debug.Print x.GetAttribute("a", "b") & "|" & x.GetElementText("a") + +x.Parse "" +Debug.Print x.GetAttribute("a", "b") & "|" & x.GetAttribute("a", "c") & "|" & x.RootElement.Attributes.Count + +x.Parse "" +Debug.Print Replace(x.RootElement.Name, vbLf, "\n") + +x.Parse "" +Debug.Print x.GetAttribute("a", "disabled") & "|" & x.GetAttribute("a", "c") +' Output: +' 2| +' |y"> +' 1||4 +' a\nb="1" +' 2| +``` + +A tab between the attributes is read correctly, and so is a space around the `=`. + +### Comments, CDATA sections and declarations +{: #defect-declarations .no_toc } + +> [!WARNING] +> **Parse** never returns for text that holds a comment with no `-->`, a CDATA section with no `]]>`, or a `` anywhere later in the text, which discards everything between, or, when there is none, up to the first `>` of the whole text, which is the end of the item itself only when the item is the first thing in the text. In every other position the search goes back to an earlier `>`, and the parser reads the item again, without end. A processing instruction `` is skipped, but the `>` that ends it stays behind as a text node. + +These calls return, and show how a processing instruction and a DOCTYPE declaration are read: + +```tb check_build check_run +Dim x As New cXml +x.Parse "x" +Debug.Print "[" & x.GetElementText("root") & "]" + +x.Parse "text" +Debug.Print "[" & x.GetElementText("note") & "]" + +Dim y As New cXml +y.Parse "text" +Debug.Print (y.RootElement Is Nothing) +' Output: +' [>x] +' [text] +' True +``` + +Each of these calls never returns. The sample is compiled and not run: + +```tb check_build +Dim x As New cXml +x.Parse "" + +Dim book As cXmlNode +Set book = x.RootElement.GetChildNode("book") +Debug.Print book.Name & " " & book.GetAttribute("lang") +Debug.Print book.Parent.Name +Debug.Print book.ChildNodes(1).Value + +Dim n As cXmlNode +For Each n In x.RootElement.ChildNodes + Debug.Print n.Name & " " & n.NodeType +Next +' Output: +' book en +' library +' Dune +' book 1 +' #comment 8 +``` + +* TOC +{:toc} + +## Kinds of node + +The parser creates five kinds of node. The text of an element is not in the element's **Value**: it is in the **Text** nodes among the element's **ChildNodes**. + +| Kind | **NodeType** | **Name** | **Value** | +|:-----|:-------------|:---------|:----------| +| Document | **Document** (9) | `#document` | empty | +| Element | **Element** (1) | the name in the tag | empty | +| Text | **Text** (3) | `#text` | the text, with entities as written | +| CDATA section | **CData** (4) | `#cdata-section` | the characters between `` | +| Comment | **Comment** (8) | `#comment` | the characters between `` | + +```tb check_build check_run +Dim x As New cXml +x.Parse "text" + +Dim n As cXmlNode +For Each n In x.RootElement.ChildNodes + Debug.Print n.Name & " " & n.NodeType & " " & n.Value +Next +' Output: +' #text 3 text +' #comment 8 note +' #cdata-section 4 data +``` + +The other members of [**XmlNodeType**](mXml#xmlnodetype), **Attribute**, **DocumentFragment**, **Notation** and **None**, are never given to a parsed node. A node made with `New` has the **NodeType** **None**. Attributes are not nodes: they are held in the **Attributes** field of their element. + +> [!NOTE] +> Names are compared without regard to letter case, in the attribute methods here and in the path members of [**cXml**](cXml#paths). XML itself distinguishes `Item` from `item`, and this package does not. + +## Fields + +### Attributes +{: .no_toc } + +The attributes of an element, as a flat list of names and values. + +Syntax: *node*.**Attributes** **As Collection** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +A **Collection** of strings, numbered from 1. Item 1 is the name of the first attribute, item 2 its value, item 3 the name of the second attribute, and so on, in the order the attributes were set or read. A loop with `Step 2` visits the attributes. A new node holds an empty collection, and so does every node that is not an element. + +[**GetAttribute**](#getattribute), [**SetAttribute**](#setattribute) and [**RemoveAttribute**](#removeattribute) keep the names and values in pairs. A program that adds to or removes from the collection itself must keep them in pairs too. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim n As cXmlNode +Set n = x.RootElement +Debug.Print n.Attributes.Count + +Dim i As Long +For i = 1 To n.Attributes.Count Step 2 + Debug.Print n.Attributes(i) & " = " & n.Attributes(i + 1) +Next +' Output: +' 4 +' id = 7 +' kind = tool +``` + +### ChildNodes +{: .no_toc } + +The nodes directly inside the node, in document order. + +Syntax: *node*.**ChildNodes** **As Collection** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +A **Collection** of **cXmlNode** objects, numbered from 1. It holds elements, text, CDATA sections and comments, including text that is only white space. The collection is empty for a text node, a CDATA section and a comment. [**GetChildNode**](#getchildnode) and [**GetChildNodesByName**](#getchildnodesbyname) search the collection by name. + +To add a node by hand, set its **NodeType**, **Name** and **Value**, set its [**Parent**](#parent), and add it to the collection. The writer chooses what to write by **NodeType**, not by **Name**. + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "r" + +Dim t As New cXmlNode +t.NodeType = XmlNodeType.Text +t.Name = "#text" +t.Value = "hello" +Set t.Parent = x.RootElement +x.RootElement.ChildNodes.Add t + +Debug.Print x.ToXmlString(False) +' Output: +' hello +``` + +### Name +{: .no_toc } + +The name of the node. + +Syntax: *node*.**Name** [ **=** *value* ] **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +A **String**. For an element it is the name written in its tag, with any prefix, such as `ns:item`. For the other kinds it is the fixed name in the table [above](#kinds-of-node). A new node has an empty name. The package does not check a name when it is set. + +The path members of [**cXml**](cXml#paths) and the name methods of this class select nodes by **Name**, so `#text` and `#comment` select text nodes and comments. + +### NodeType +{: .no_toc } + +The kind of the node. + +Syntax: *node*.**NodeType** [ **=** *value* ] **As** [**XmlNodeType**](mXml#xmlnodetype) + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +An [**XmlNodeType**](mXml#xmlnodetype) value. A new node has **None** (0). [**ToXmlString**](cXml#toxmlstring) writes a node according to its **NodeType**: a node of any kind not in the [table](#kinds-of-node) is left out of the output. + +### Parent +{: .no_toc } + +The node that holds this node. + +Syntax: *node*.**Parent** **As cXmlNode** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +A **cXmlNode**, or **Nothing**. The parser and [**AddElement**](cXml#addelement) set it. The root element's parent is the document node, and the document node's is **Nothing**. A node made with `New` has none until the program sets it with **Set**. + +[**RemoveElement**](cXml#removeelement) takes an element out of its parent's **ChildNodes**, and returns **False** for a node whose **Parent** is **Nothing**. [**Clone**](cXml#clone) and [**Merge**](cXml#merge) leave the **Parent** of the nodes they copy empty; see [Clone, Merge and the root element](cXml#defect-clone). + +### Value +{: .no_toc } + +The text of a text node, a CDATA section or a comment. + +Syntax: *node*.**Value** [ **=** *value* ] **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +A **String**. It is empty for the document node and for elements. For a text node it is the text as it stood in the XML, with any entity left undecoded (see [Entities](cXml#defect-entities)); [**ToXmlString**](cXml#toxmlstring) escapes it again when it writes the node. + +## Methods + +### New +{: .no_toc } + +Creates a node. + +Syntax: **New cXmlNode** + +The constructor takes no arguments. It gives the node an empty **Attributes** collection and an empty **ChildNodes** collection. **Name** and **Value** are empty, **NodeType** is **None** and **Parent** is **Nothing**. + +```tb check_build check_run +Dim n As New cXmlNode +Debug.Print n.Name & "|" & n.NodeType & "|" & n.Attributes.Count & "|" & n.ChildNodes.Count & "|" & (n.Parent Is Nothing) +' Output: +' |0|0|0|True +``` + +### GetAttribute +{: .no_toc } + +Returns the value of an attribute. + +Syntax: *node*.**GetAttribute** ( *attrName* ) **As String** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored. + +Returns the value of the first attribute whose name matches. Returns an empty string when there is no such attribute, which a program cannot tell from an attribute whose value is empty: it compares **Attributes** itself when it needs to. + +```tb check_build check_run +Dim x As New cXml +x.Parse "" + +Dim n As cXmlNode +Set n = x.RootElement +Debug.Print "[" & n.GetAttribute("id") & "]" +Debug.Print "[" & n.GetAttribute("ID") & "]" +Debug.Print "[" & n.GetAttribute("empty") & "]" +Debug.Print "[" & n.GetAttribute("missing") & "]" +' Output: +' [7] +' [7] +' [] +' [] +``` + +### GetChildNode +{: .no_toc } + +Returns the first child with a given name. + +Syntax: *node*.**GetChildNode** ( *nodeName* ) **As cXmlNode** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*nodeName* +: *required* A **String**, the name to look for. Letter case is ignored. + +Returns the first node in **ChildNodes** whose **Name** matches, or **Nothing**. It looks at the direct children only, not at deeper nodes. The names of the other kinds of node, such as `#text`, match too. + +```tb check_build check_run +Dim x As New cXml +x.Parse "123" + +Dim n As cXmlNode +Set n = x.RootElement.GetChildNode("A") +Debug.Print n.ChildNodes(1).Value +Debug.Print (x.RootElement.GetChildNode("c") Is Nothing) +' Output: +' 1 +' True +``` + +### GetChildNodesByName +{: .no_toc } + +Returns all the children with a given name. + +Syntax: *node*.**GetChildNodesByName** ( *nodeName* ) **As Collection** + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*nodeName* +: *required* A **String**, the name to look for. Letter case is ignored. + +Returns a new **Collection** of the nodes in **ChildNodes** whose **Name** matches, in order. The collection is empty when none matches. It looks at the direct children only; [**SelectElements**](cXml#selectelements) searches the whole document. + +```tb check_build check_run +Dim x As New cXml +x.Parse "123" + +Dim found As Collection +Set found = x.RootElement.GetChildNodesByName("a") +Debug.Print found.Count +Debug.Print found(2).ChildNodes(1).Value +Debug.Print x.RootElement.GetChildNodesByName("c").Count +' Output: +' 2 +' 3 +' 0 +``` + +### RemoveAttribute +{: .no_toc } + +Removes an attribute. + +Syntax: *node*.**RemoveAttribute** ( *attrName* ) + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored. + +Removes the first attribute whose name matches, with its value. Nothing happens, and no error is raised, when there is none. + +```tb check_build check_run +Dim n As New cXmlNode +n.SetAttribute "a", "1" +n.SetAttribute "b", "2" + +n.RemoveAttribute "A" +n.RemoveAttribute "missing" +Debug.Print n.Attributes.Count +Debug.Print n.Attributes(1) & " = " & n.Attributes(2) +' Output: +' 2 +' b = 2 +``` + +### SetAttribute +{: .no_toc } + +Sets the value of an attribute. + +Syntax: *node*.**SetAttribute** ( *attrName* , *attrValue* ) + +*node* +: *required* An object expression that evaluates to a **cXmlNode**. + +*attrName* +: *required* A **String**, the name of the attribute. Letter case is ignored when the attribute is looked for. + +*attrValue* +: *required* A **String**, the new value. [**ToXmlString**](cXml#toxmlstring) escapes it when it writes the attribute. + +Adds the attribute, or replaces the value of the first attribute with that name. A replaced attribute does not stay where it was: the old name and value are removed and the new ones are added at the end of **Attributes**, and the name takes the spelling given to **SetAttribute**. The parser uses the same method, so an attribute that occurs twice in a tag keeps the last value. + +Neither the name nor the value is checked. An empty name is accepted, and [**Validate**](cXml#validate) then returns **False**. + +```tb check_build check_run +Dim n As New cXmlNode +n.SetAttribute "a", "1" +n.SetAttribute "b", "2" +n.SetAttribute "A", "3" + +Dim i As Long +For i = 1 To n.Attributes.Count Step 2 + Debug.Print n.Attributes(i) & " = " & n.Attributes(i + 1) +Next +' Output: +' b = 2 +' A = 3 +``` + +## See Also + +- [cXml](cXml) class -- holds the document and finds nodes by path +- [mXml](mXml) module -- the node types and the error numbers +- [TBMANLIB_Xml](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/index.md b/docs/Reference/Contributed/TBMANLIB_Xml/index.md new file mode 100644 index 000000000..20ef674ee --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Xml/index.md @@ -0,0 +1,128 @@ +--- +title: TBMANLIB_Xml Package +parent: Experimental Packages +has_toc: false +indexed_from: 1.0.0.0 +exclude_from_docs: + - mXmlTests +permalink: /tB/Packages/Contributed/TBMANLIB_Xml/ +--- + +# TBMANLIB_Xml Package +{: .no_toc } + +**Author:** woeoio. + +The **TBMANLIB_Xml** contributed package reads, changes and writes XML documents. It has the shape of a small DOM. A [**cXml**](cXml) object holds a document as a tree of [**cXmlNode**](cXmlNode) objects, finds elements by a path such as `library/book`, and reads and writes the document as a string or as a UTF-8 file. The [**mXml**](mXml) module holds the node types, the error numbers and two functions that convert between strings and UTF-8 bytes. The package is written in twinBASIC and calls two functions of the Windows API for the conversion. This section documents version 1.0.0.0, published under the MIT licence. + +```tb check_build check_run +Dim x As New cXml +x.CreateNew "library" +x.SetAttribute "library", "city", "Oslo" + +Dim book As cXmlNode +Set book = x.AddElement("library", "book") +book.SetAttribute "id", "1" +x.SetElementText "library/book", "Dune" + +Dim y As New cXml +y.Parse x.ToXmlString() +Debug.Print y.GetAttribute("library", "city") +Debug.Print y.GetElementText("library/book") +Debug.Print y.ToXmlString(False) +' Output: +' Oslo +' Dune +' Dune +``` + +> [!WARNING] +> The parser of version 1.0.0.0 reads some valid XML wrongly and raises no error, and for some text it never returns. [Defects in version 1.0.0.0](cXml#defects) lists the cases: self-closing elements, entities in text, tags with both kinds of quote or with line breaks, processing instructions, and comments, CDATA sections and DOCTYPE declarations in some positions. + +## What the package reads and writes + +A document is read from a string by [**Parse**](cXml#parse), from a file by [**LoadFromFile**](cXml#loadfromfile), or started with [**CreateNew**](cXml#createnew). The package supports: + +- elements and their attributes, with attribute values in double or single quotes; +- text, comments and CDATA sections, each held as a node. Text that is only white space is kept; +- the `version` and `encoding` of the XML declaration, which are read, kept and written; +- reading and setting an element's text and attributes, adding and removing elements, copying a document ([**Clone**](cXml#clone)) and appending one to another ([**Merge**](cXml#merge)); +- writing the document compactly or with line breaks, with `&`, `<`, `>`, `"` and `'` escaped; +- files in UTF-8. A byte order mark is accepted when a file is read and always written. + +The package does not support, in version 1.0.0.0: + +- DTDs and schemas. A DOCTYPE declaration is not read, and nothing is validated against a DTD or a schema. [**Validate**](cXml#validate) only checks that elements and attributes have names; +- XPath. A path is a list of element names separated by `/`, with no positions, attributes or wildcards; see [Paths](cXml#paths); +- namespaces. A prefix is part of an element's or attribute's name, and no namespace is resolved; +- entities defined in a DTD, and character references such as `A`. The five predefined entities are decoded in attribute values and not in text; +- processing instructions, which are skipped, and the `standalone` pseudo-attribute of the declaration; +- encodings other than UTF-8 in files, whatever the declaration says; +- checking that text is well formed. [**Parse**](cXml#parse) accepts text that is not XML, elements that are not closed and end tags that name another element. + +The [**ParseOption**](mXml#parseoption) enumeration, with members such as **PreserveWhitespace** and **ValidateOnParse**, is declared by the module, but no member of the package reads it. + +## Failures + +No member of the package ends a program with an error of its own. A member that cannot do its work returns **False**, **Nothing**, an empty string or an empty collection, and in the IDE writes a line that names the member to the Debug Console, for example `Parse failed: XML string cannot be empty`. The error stays in the **Err** object after the call. The error numbers are constants of [**mXml**](mXml#constants), and only [**ERR_EMPTY_DOCUMENT**](mXml#err_empty_document) is used. [Reporting failures](cXml#reporting-failures) has the details. + +## Classes and module + +- [cXml](cXml) -- holds an XML document, reads it from a string or a file, finds, changes and adds elements by path, and writes it as a string or a file + - [Document](cXml#document) -- returns the document node + - [Encoding](cXml#encoding) -- returns or sets the encoding name of the declaration + - [Initialized](cXml#initialized) -- returns whether the object holds a document + - [RootElement](cXml#rootelement) -- returns the root element + - [Version](cXml#version) -- returns or sets the XML version of the declaration + - [XmlString](cXml#xmlstring) -- returns or sets the text of the document that was last read + - [New](cXml#new) -- creates an object + - [AddElement](cXml#addelement) -- adds an empty element as the last child of an element + - [Clone](cXml#clone) -- returns a copy of the document + - [CreateNew](cXml#createnew) -- replaces the document with a new one that has a root element + - [GetAttribute](cXml#getattribute) -- returns an attribute of an element + - [GetElementText](cXml#getelementtext) -- returns the text inside an element + - [LoadFromFile](cXml#loadfromfile) -- reads a document from a file + - [Merge](cXml#merge) -- appends the content of another document + - [Parse](cXml#parse) -- reads a document from a string + - [RemoveAttribute](cXml#removeattribute) -- removes an attribute + - [RemoveElement](cXml#removeelement) -- removes an element + - [SaveToFile](cXml#savetofile) -- writes the document to a file + - [SelectElement](cXml#selectelement) -- returns the element that a path selects + - [SelectElements](cXml#selectelements) -- returns every node with a given name + - [SetAttribute](cXml#setattribute) -- sets an attribute + - [SetElementText](cXml#setelementtext) -- replaces the text of an element + - [ToXmlString](cXml#toxmlstring) -- returns the document as text + - [Validate](cXml#validate) -- checks that every element and attribute has a name +- [cXmlNode](cXmlNode) -- one node of a document: an element, text, a CDATA section, a comment or the document itself + - [Attributes](cXmlNode#attributes) -- holds the attributes as a list of names and values + - [ChildNodes](cXmlNode#childnodes) -- holds the nodes directly inside the node + - [Name](cXmlNode#name) -- returns or sets the name + - [NodeType](cXmlNode#nodetype) -- returns or sets the kind of node + - [Parent](cXmlNode#parent) -- returns the node that holds this one + - [Value](cXmlNode#value) -- returns or sets the text of a text node, a CDATA section or a comment + - [New](cXmlNode#new) -- creates a node + - [GetAttribute](cXmlNode#getattribute) -- returns the value of an attribute + - [GetChildNode](cXmlNode#getchildnode) -- returns the first child with a given name + - [GetChildNodesByName](cXmlNode#getchildnodesbyname) -- returns all the children with a given name + - [RemoveAttribute](cXmlNode#removeattribute) -- removes an attribute + - [SetAttribute](cXmlNode#setattribute) -- sets the value of an attribute +- [mXml](mXml) -- the node types, the parse options, the error numbers, the defaults and the UTF-8 conversion functions + - [XmlNodeType](mXml#xmlnodetype) -- the kinds of node + - [ParseOption](mXml#parseoption) -- four flags that no member reads + - [ERR_BASE](mXml#err_base) and the other [error numbers](mXml#constants) -- the base of the package's error numbers, and the numbers + - [DEFAULT_XML_VERSION](mXml#default_xml_version), [DEFAULT_ENCODING](mXml#default_encoding) and [DEFAULT_INDENT](mXml#default_indent) -- the defaults + - [StringToUTF8Bytes](mXml#stringtoutf8bytes) -- converts a string to UTF-8 bytes + - [UTF8BytesToString](mXml#utf8bytestostring) -- converts UTF-8 bytes to a string + +## The test module + +The package also contains **mXmlTests**, a module of unit tests for **cXml**, which has no page of its own here. **mXmlTests.RunAllTests** runs 86 checks and writes a line for each to the Debug Console: `[PASS]` or `[FAIL]` and the check's message, grouped under the name of the test. It ends with a summary of the totals, the pass rate and a closing line; in version 1.0.0.0 all 86 pass. Each test is also a public **Sub** of the module that can be run alone. + +The checks compare one value at a time and do not exercise the cases listed under [Defects in version 1.0.0.0](cXml#defects): the test of self-closing tags tests that the output contains `/>`, and the namespace test tests that **Parse** returns **True**. The file tests write `test_output.xml` in the current folder, and leave it there. + +## See Also + +- [cXml](cXml) class -- the document +- [cXmlNode](cXmlNode) class -- the nodes +- [mXml](mXml) module -- constants, enumerations and UTF-8 conversion +- [Experimental Packages](../../Experimental/) -- the packages from TWINSERV that are not yet ready for use in a program diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md b/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md new file mode 100644 index 000000000..126a6fee4 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Xml/mXml.md @@ -0,0 +1,228 @@ +--- +title: mXml +parent: TBMANLIB_Xml Package +permalink: /tB/Packages/Contributed/TBMANLIB_Xml/mXml +has_toc: false +--- + +# mXml module +{: .no_toc } + +The **mXml** module holds the enumerations, the constants and the two UTF-8 conversion functions of the package. The [**cXml**](cXml) and [**cXmlNode**](cXmlNode) classes use them, and so can a program: the members are public, and a program reaches one by its own name (`XmlNodeType.Element`, `ERR_BASE`, `StringToUTF8Bytes`) or with the module's name in front (`mXml.XmlNodeType.Element`). + +```tb check_build check_run +Dim x As New cXml +x.Parse "text" + +Debug.Print x.RootElement.ChildNodes(1).NodeType = XmlNodeType.Text +Debug.Print mXml.DEFAULT_ENCODING +' Output: +' True +' UTF-8 +``` + +* TOC +{:toc} + +## Enumerations + +### XmlNodeType +{: .no_toc } + +The kind of a [**cXmlNode**](cXmlNode). The numbers are those of the node types of the W3C Document Object Model. + +| Member | Value | Used for | +|:-------|------:|:---------| +| **None** | 0 | a node made with `New cXmlNode`, before its kind is set | +| **Element** | 1 | an element | +| **Attribute** | 2 | nothing: attributes are held in the **Attributes** field of their element | +| **Text** | 3 | text | +| **CData** | 4 | a CDATA section | +| **Comment** | 8 | a comment | +| **Document** | 9 | the document node | +| **DocumentFragment** | 11 | nothing | +| **Notation** | 12 | nothing | + +The parser creates nodes of the kinds **Document**, **Element**, **Text**, **CData** and **Comment**. [**ToXmlString**](cXml#toxmlstring) writes those five kinds and leaves out a node of any other kind. + +### ParseOption +{: .no_toc } + +Four flags whose names describe options of a parser. + +| Member | Value | +|:-------|------:| +| **None** | 0 | +| **PreserveWhitespace** | 1 | +| **IgnoreComments** | 2 | +| **IgnoreProcessingInstructions** | 4 | +| **ValidateOnParse** | 8 | + +Nothing in version 1.0.0.0 of the package reads a **ParseOption**: [**Parse**](cXml#parse) and the other members that read XML take no such parameter. They always keep text that is only white space, always create nodes for comments and always skip processing instructions, and no member checks the text while it reads it. + +## Constants + +### ERR_BASE +{: .no_toc } + +The base of the package's error numbers. + +Syntax: **ERR_BASE** **As Long** + +`vbObjectError + 5000`, which is -2147216504. The other error numbers are **ERR_BASE** plus 1 to 6. See [Reporting failures](cXml#reporting-failures) for how the numbers reach a program. + +### ERR_INVALID_XML +{: .no_toc } + +Syntax: **ERR_INVALID_XML** **As Long** + +`ERR_BASE + 1`, which is -2147216503. No member of the package raises it. + +### ERR_INVALID_NODE +{: .no_toc } + +Syntax: **ERR_INVALID_NODE** **As Long** + +`ERR_BASE + 2`, which is -2147216502. No member of the package raises it. + +### ERR_NODE_NOT_FOUND +{: .no_toc } + +Syntax: **ERR_NODE_NOT_FOUND** **As Long** + +`ERR_BASE + 3`, which is -2147216501. No member of the package raises it: a path that selects nothing gives **Nothing**, an empty string or **False**. + +### ERR_ATTRIBUTE_NOT_FOUND +{: .no_toc } + +Syntax: **ERR_ATTRIBUTE_NOT_FOUND** **As Long** + +`ERR_BASE + 4`, which is -2147216500. No member of the package raises it: a missing attribute gives an empty string. + +### ERR_EMPTY_DOCUMENT +{: .no_toc } + +Syntax: **ERR_EMPTY_DOCUMENT** **As Long** + +`ERR_BASE + 5`, which is -2147216499. [**Parse**](cXml#parse) raises it, with the description `XML string cannot be empty`, when it is given an empty string, and [**SelectElement**](cXml#selectelement) raises it, with the description `Document not initialized`, when the document has not been read or created. Both members handle the error themselves, so a program sees a **False** or a **Nothing**, and finds the number in the **Err** object after the call. + +```tb check_build check_run +Dim x As New cXml + +Err.Clear +Debug.Print x.Parse("") +Debug.Print Err.Number = ERR_EMPTY_DOCUMENT +Debug.Print Err.Description +' Output: +' Parse failed: XML string cannot be empty +' False +' True +' XML string cannot be empty +``` + +The first line of the output is the one that a failing member writes to the Debug Console; [Reporting failures](cXml#reporting-failures) describes it. + +### ERR_INVALID_OPERATION +{: .no_toc } + +Syntax: **ERR_INVALID_OPERATION** **As Long** + +`ERR_BASE + 6`, which is -2147216498. No member of the package raises it. + +### DEFAULT_XML_VERSION +{: .no_toc } + +Syntax: **DEFAULT_XML_VERSION** **As String** + +The string `1.0`. A new [**cXml**](cXml) object has it as its [**Version**](cXml#version). + +### DEFAULT_ENCODING +{: .no_toc } + +Syntax: **DEFAULT_ENCODING** **As String** + +The string `UTF-8`. A new [**cXml**](cXml) object has it as its [**Encoding**](cXml#encoding). + +### DEFAULT_INDENT +{: .no_toc } + +Syntax: **DEFAULT_INDENT** **As String** + +Two space characters. No member reads it: the default of the *indent* parameter of [**ToXmlString**](cXml#toxmlstring) is the same two spaces, written there as a literal. + +## Functions + +### StringToUTF8Bytes +{: .no_toc } + +Converts a string to the bytes of its UTF-8 form. + +Syntax: **StringToUTF8Bytes** ( *str* ) **As Byte()** + +*str* +: *required* A **String**. + +Returns a **Byte** array that starts at index 0 and holds the UTF-8 encoding of *str*, with no byte order mark and no terminating zero byte. A character outside the Basic Multilingual Plane, held in *str* as a surrogate pair, becomes one four-byte sequence. + +For an empty string the array has one element, which holds 0, so a program cannot tell the result from the encoding of a single `ChrW(0)`. + +[**SaveToFile**](cXml#savetofile) uses the function to write a file. + +```tb check_build check_run +Dim b() As Byte +b = StringToUTF8Bytes("A" & ChrW(233) & ChrW(&H20AC)) + +Dim i As Long +Dim s As String +For i = 0 To UBound(b) + s = s & Right$("0" & Hex$(b(i)), 2) & " " +Next +Debug.Print Trim$(s) + +Dim e() As Byte +e = StringToUTF8Bytes("") +Debug.Print UBound(e) & " " & e(0) +' Output: +' 41 C3 A9 E2 82 AC +' 0 0 +``` + +### UTF8BytesToString +{: .no_toc } + +Converts UTF-8 bytes to a string. + +Syntax: **UTF8BytesToString** ( *utf8Bytes* ) **As String** + +*utf8Bytes* +: *required* A **Byte** array, passed by reference, so the argument is an array variable and not an expression. The array may start at any index. + +Returns the characters that the bytes encode. An array that has never been given elements gives an empty string. A byte sequence that is not valid UTF-8 becomes the character U+FFFD, and a zero byte becomes `ChrW(0)`, so the one-element array that [**StringToUTF8Bytes**](#stringtoutf8bytes) returns for an empty string gives a string of length 1. A byte order mark, the bytes `EF BB BF`, becomes the character U+FEFF and is kept. + +[**LoadFromFile**](cXml#loadfromfile) uses the function to read a file. + +```tb check_build check_run +Dim b() As Byte +b = StringToUTF8Bytes("caf" & ChrW(233)) +Debug.Print UTF8BytesToString(b) = "caf" & ChrW(233) + +ReDim b(0 To 1) +b(0) = &HC3 +b(1) = &H28 +Dim s As String +s = UTF8BytesToString(b) +Debug.Print Hex$(AscW(s) And &HFFFF&) & " " & Mid$(s, 2) + +Dim unsized() As Byte +Debug.Print Len(UTF8BytesToString(unsized)) +' Output: +' True +' FFFD ( +' 0 +``` + +## See Also + +- [cXml](cXml) class -- uses the enumerations, the constants and the functions +- [cXmlNode](cXmlNode) class -- has the **NodeType** field of type **XmlNodeType** +- [TBMANLIB_Xml](.) package -- overview diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index a3bfdd501..65dbe8f63 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -13,5 +13,6 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. diff --git a/docs/_book.yml b/docs/_book.yml index 245f09edf..394799122 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -592,6 +592,13 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB_Xml Package + subtitle: XML documents parsed, edited and written, in twinBASIC code + landing_page: /tB/Packages/Contributed/TBMANLIB_Xml/ + page: /tB/Packages/Contributed/TBMANLIB_Xml/ + landing_is_target: true + outline_closed: true + - title: WinReg Package subtitle: Registry keys and values, read, written, deleted and enumerated landing_page: /tB/Packages/Contributed/WinReg/ diff --git a/indexer/snapshots/contributed/TBMANLIB_Xml/api.json b/indexer/snapshots/contributed/TBMANLIB_Xml/api.json new file mode 100644 index 000000000..fc24f6054 --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB_Xml/api.json @@ -0,0 +1,206 @@ +{ + "format": 3, + "package": "TBMANLIB_Xml", + "project": "TBMANLIB_Xml", + "version": "1.0.0.0", + "types": [ + { + "name": "cXml", + "kind": "class", + "vis": "public", + "file": "Src/cXml.twin", + "signature": "Public Class cXml", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AddElement","kind":"function","vis":"public","signature":"Public Function AddElement(ByVal parentXPath As String, ByVal elementName As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As cXml","returnType":"cXml"}, + {"name":"CloneNode","kind":"function","vis":"private","signature":"Private Function CloneNode(ByRef node As cXmlNode) As cXmlNode","returnType":"cXmlNode"}, + {"name":"CreateNew","kind":"sub","vis":"public","signature":"Public Sub CreateNew(ByVal rootElementName As String)"}, + {"name":"DecodeXmlEntities","kind":"function","vis":"private","signature":"Private Function DecodeXmlEntities(ByVal text As String) As String","returnType":"String"}, + {"name":"Document","kind":"property","vis":"public","signature":"Public Property Get Document() As cXmlNode","returnType":"cXmlNode","accessor":"Get"}, + {"name":"EncodeXmlEntities","kind":"function","vis":"private","signature":"Private Function EncodeXmlEntities(ByVal text As String) As String","returnType":"String"}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding() As String","returnType":"String","accessor":"Get"}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Let Encoding(ByVal value As String)","accessor":"Let"}, + {"name":"FindChildByName","kind":"function","vis":"private","signature":"Private Function FindChildByName(ByRef parent As cXmlNode, ByVal name As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"FindElementsByName","kind":"sub","vis":"private","signature":"Private Sub FindElementsByName(ByRef node As cXmlNode, ByVal name As String, ByRef result As Collection)"}, + {"name":"GetAttribute","kind":"function","vis":"public","signature":"Public Function GetAttribute(ByVal xpath As String, ByVal attrName As String) As String","returnType":"String"}, + {"name":"GetElementText","kind":"function","vis":"public","signature":"Public Function GetElementText(ByVal xpath As String) As String","returnType":"String"}, + {"name":"GetNodeText","kind":"function","vis":"private","signature":"Private Function GetNodeText(ByRef node As cXmlNode) As String","returnType":"String"}, + {"name":"Initialized","kind":"property","vis":"public","signature":"Public Property Get Initialized() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"LoadFromFile","kind":"function","vis":"public","signature":"Public Function LoadFromFile(ByVal filePath As String) As Boolean","returnType":"Boolean"}, + {"name":"Merge","kind":"sub","vis":"public","signature":"Public Sub Merge(ByVal otherXml As cXml)"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"Parse","kind":"function","vis":"public","signature":"Public Function Parse(ByVal xml As String) As Boolean","returnType":"Boolean"}, + {"name":"ParseAttributes","kind":"sub","vis":"private","signature":"Private Sub ParseAttributes(ByVal tagContent As String, ByRef element As cXmlNode)"}, + {"name":"ParseCData","kind":"sub","vis":"private","signature":"Private Sub ParseCData(ByRef parent As cXmlNode)"}, + {"name":"ParseComment","kind":"sub","vis":"private","signature":"Private Sub ParseComment(ByRef parent As cXmlNode)"}, + {"name":"ParseContent","kind":"sub","vis":"private","signature":"Private Sub ParseContent(ByRef parent As cXmlNode)"}, + {"name":"ParseDeclaration","kind":"sub","vis":"private","signature":"Private Sub ParseDeclaration()"}, + {"name":"ParseElement","kind":"function","vis":"private","signature":"Private Function ParseElement() As cXmlNode","returnType":"cXmlNode"}, + {"name":"ParseInternal","kind":"sub","vis":"private","signature":"Private Sub ParseInternal(ByVal xml As String)"}, + {"name":"ParseText","kind":"function","vis":"private","signature":"Private Function ParseText() As String","returnType":"String"}, + {"name":"RemoveAttribute","kind":"sub","vis":"public","signature":"Public Sub RemoveAttribute(ByVal xpath As String, ByVal attrName As String)"}, + {"name":"RemoveElement","kind":"function","vis":"public","signature":"Public Function RemoveElement(ByVal xpath As String) As Boolean","returnType":"Boolean"}, + {"name":"RootElement","kind":"property","vis":"public","signature":"Public Property Get RootElement() As cXmlNode","returnType":"cXmlNode","accessor":"Get"}, + {"name":"SaveToFile","kind":"function","vis":"public","signature":"Public Function SaveToFile(ByVal filePath As String, Optional ByVal withDeclaration As Boolean = True) As Boolean","returnType":"Boolean"}, + {"name":"SelectElement","kind":"function","vis":"public","signature":"Public Function SelectElement(ByVal xpath As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"SelectElements","kind":"function","vis":"public","signature":"Public Function SelectElements(ByVal tagName As String) As Collection","returnType":"Collection"}, + {"name":"SerializeNodeCompact","kind":"sub","vis":"private","signature":"Private Sub SerializeNodeCompact(ByRef node As cXmlNode, ByRef sb As Collection)"}, + {"name":"SerializeNodePretty","kind":"sub","vis":"private","signature":"Private Sub SerializeNodePretty(ByRef node As cXmlNode, ByRef sb As Collection, ByVal level As Long, ByVal indent As String)"}, + {"name":"SetAttribute","kind":"sub","vis":"public","signature":"Public Sub SetAttribute(ByVal xpath As String, ByVal attrName As String, ByVal attrValue As String)"}, + {"name":"SetDocument","kind":"property","vis":"friend","signature":"Friend Property Let SetDocument(ByVal value As cXmlNode)","accessor":"Let"}, + {"name":"SetElementText","kind":"sub","vis":"public","signature":"Public Sub SetElementText(ByVal xpath As String, ByVal text As String)"}, + {"name":"SetInitialized","kind":"property","vis":"friend","signature":"Friend Property Let SetInitialized(ByVal value As Boolean)","accessor":"Let"}, + {"name":"SetNodeText","kind":"sub","vis":"private","signature":"Private Sub SetNodeText(ByRef node As cXmlNode, ByVal text As String)"}, + {"name":"SetRootElement","kind":"property","vis":"friend","signature":"Friend Property Let SetRootElement(ByVal value As cXmlNode)","accessor":"Let"}, + {"name":"SkipWhitespace","kind":"sub","vis":"private","signature":"Private Sub SkipWhitespace()"}, + {"name":"SplitPath","kind":"function","vis":"private","signature":"Private Function SplitPath(ByVal path As String) As String()","returnType":"String()"}, + {"name":"ToXmlString","kind":"function","vis":"public","signature":"Public Function ToXmlString(Optional ByVal withDeclaration As Boolean = True, Optional ByVal prettyPrint As Boolean = False, Optional ByVal indent As String = \" \") As String","returnType":"String"}, + {"name":"Validate","kind":"function","vis":"public","signature":"Public Function Validate() As Boolean","returnType":"Boolean"}, + {"name":"ValidateNode","kind":"function","vis":"private","signature":"Private Function ValidateNode(ByRef node As cXmlNode) As Boolean","returnType":"Boolean"}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As String","returnType":"String","accessor":"Get"}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Let Version(ByVal value As String)","accessor":"Let"}, + {"name":"XmlString","kind":"property","vis":"public","signature":"Public Property Get XmlString() As String","returnType":"String","accessor":"Get"}, + {"name":"XmlString","kind":"property","vis":"public","signature":"Public Property Let XmlString(ByVal value As String)","accessor":"Let"}, + {"name":"m_Document","kind":"field","vis":"private","signature":"Private m_Document As cXmlNode"}, + {"name":"m_Encoding","kind":"field","vis":"private","signature":"Private m_Encoding As String"}, + {"name":"m_Initialized","kind":"field","vis":"private","signature":"Private m_Initialized As Boolean"}, + {"name":"m_ParserLength","kind":"field","vis":"private","signature":"Private m_ParserLength As Long"}, + {"name":"m_ParserPosition","kind":"field","vis":"private","signature":"Private m_ParserPosition As Long"}, + {"name":"m_RootElement","kind":"field","vis":"private","signature":"Private m_RootElement As cXmlNode"}, + {"name":"m_Version","kind":"field","vis":"private","signature":"Private m_Version As String"}, + {"name":"m_XmlString","kind":"field","vis":"private","signature":"Private m_XmlString As String"} + ] + }, + { + "name": "cXmlNode", + "kind": "class", + "vis": "public", + "file": "Src/cXmlNode.twin", + "signature": "Public Class cXmlNode", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Attributes","kind":"field","vis":"public","signature":"Public Attributes As Collection"}, + {"name":"ChildNodes","kind":"field","vis":"public","signature":"Public ChildNodes As Collection"}, + {"name":"GetAttribute","kind":"function","vis":"public","signature":"Public Function GetAttribute(ByVal attrName As String) As String","returnType":"String"}, + {"name":"GetChildNode","kind":"function","vis":"public","signature":"Public Function GetChildNode(ByVal nodeName As String) As cXmlNode","returnType":"cXmlNode"}, + {"name":"GetChildNodesByName","kind":"function","vis":"public","signature":"Public Function GetChildNodesByName(ByVal nodeName As String) As Collection","returnType":"Collection"}, + {"name":"Name","kind":"field","vis":"public","signature":"Public Name As String"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"NodeType","kind":"field","vis":"public","signature":"Public NodeType As mXml.XmlNodeType"}, + {"name":"Parent","kind":"field","vis":"public","signature":"Public Parent As cXmlNode"}, + {"name":"RemoveAttribute","kind":"sub","vis":"public","signature":"Public Sub RemoveAttribute(ByVal attrName As String)"}, + {"name":"SetAttribute","kind":"sub","vis":"public","signature":"Public Sub SetAttribute(ByVal attrName As String, ByVal attrValue As String)"}, + {"name":"Value","kind":"field","vis":"public","signature":"Public Value As String"} + ] + }, + { + "name": "mXml", + "kind": "module", + "vis": "public", + "file": "Src/mXml.twin", + "signature": "Public Module mXml", + "members": [ + {"name":"CP_UTF8","kind":"const","vis":"private","signature":"Private Const CP_UTF8 As Long = 65001"}, + {"name":"DEFAULT_ENCODING","kind":"const","vis":"public","signature":"Public Const DEFAULT_ENCODING As String = \"UTF-8\""}, + {"name":"DEFAULT_INDENT","kind":"const","vis":"public","signature":"Public Const DEFAULT_INDENT As String = \" \""}, + {"name":"DEFAULT_XML_VERSION","kind":"const","vis":"public","signature":"Public Const DEFAULT_XML_VERSION As String = \"1.0\""}, + {"name":"ERR_ATTRIBUTE_NOT_FOUND","kind":"const","vis":"public","signature":"Public Const ERR_ATTRIBUTE_NOT_FOUND As Long = ERR_BASE + 4"}, + {"name":"ERR_BASE","kind":"const","vis":"public","signature":"Public Const ERR_BASE As Long = vbObjectError + 5000"}, + {"name":"ERR_EMPTY_DOCUMENT","kind":"const","vis":"public","signature":"Public Const ERR_EMPTY_DOCUMENT As Long = ERR_BASE + 5"}, + {"name":"ERR_INVALID_NODE","kind":"const","vis":"public","signature":"Public Const ERR_INVALID_NODE As Long = ERR_BASE + 2"}, + {"name":"ERR_INVALID_OPERATION","kind":"const","vis":"public","signature":"Public Const ERR_INVALID_OPERATION As Long = ERR_BASE + 6"}, + {"name":"ERR_INVALID_XML","kind":"const","vis":"public","signature":"Public Const ERR_INVALID_XML As Long = ERR_BASE + 1"}, + {"name":"ERR_NODE_NOT_FOUND","kind":"const","vis":"public","signature":"Public Const ERR_NODE_NOT_FOUND As Long = ERR_BASE + 3"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"StringToUTF8Bytes","kind":"function","vis":"public","signature":"Public Function StringToUTF8Bytes(ByVal str As String) As Byte()","returnType":"Byte()"}, + {"name":"UTF8BytesToString","kind":"function","vis":"public","signature":"Public Function UTF8BytesToString(ByRef utf8Bytes() As Byte) As String","returnType":"String"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function WideCharToMultiByte Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpDefaultChar As LongPtr, ByVal lpUsedDefaultChar As LongPtr) As Long","returnType":"Long"} + ] + }, + { + "name": "ParseOption", + "kind": "enum", + "in": "mXml", + "vis": "public", + "file": "Src/mXml.twin", + "signature": "Public Enum ParseOption", + "values": [ + {"name":"None","value":"0"}, + {"name":"PreserveWhitespace","value":"1"}, + {"name":"IgnoreComments","value":"2"}, + {"name":"IgnoreProcessingInstructions","value":"4"}, + {"name":"ValidateOnParse","value":"8"} + ] + }, + { + "name": "XmlNodeType", + "kind": "enum", + "in": "mXml", + "vis": "public", + "file": "Src/mXml.twin", + "signature": "Public Enum XmlNodeType", + "values": [ + {"name":"None","value":"0"}, + {"name":"Element","value":"1"}, + {"name":"Attribute","value":"2"}, + {"name":"Text","value":"3"}, + {"name":"CData","value":"4"}, + {"name":"Comment","value":"8"}, + {"name":"Document","value":"9"}, + {"name":"DocumentFragment","value":"11"}, + {"name":"Notation","value":"12"} + ] + }, + { + "name": "mXmlTests", + "kind": "module", + "vis": "public", + "file": "mXmlTests.twin", + "signature": "Public Module mXmlTests", + "members": [ + {"name":"Assert","kind":"sub","vis":"private","signature":"Private Sub Assert(ByVal condition As Boolean, ByVal message As String)"}, + {"name":"AssertEqual","kind":"sub","vis":"private","signature":"Private Sub AssertEqual(ByVal actual As Variant, ByVal expected As Variant, ByVal message As String)"}, + {"name":"AssertInStr","kind":"sub","vis":"private","signature":"Private Sub AssertInStr(ByVal text As String, ByVal substring As String, ByVal message As String)"}, + {"name":"AssertNotNothing","kind":"sub","vis":"private","signature":"Private Sub AssertNotNothing(ByVal obj As Variant, ByVal message As String)"}, + {"name":"PrintSummary","kind":"sub","vis":"private","signature":"Private Sub PrintSummary()"}, + {"name":"PrintTestFooter","kind":"sub","vis":"private","signature":"Private Sub PrintTestFooter()"}, + {"name":"PrintTestHeader","kind":"sub","vis":"private","signature":"Private Sub PrintTestHeader(ByVal testName As String)"}, + {"name":"RunAllTests","kind":"sub","vis":"public","signature":"Public Sub RunAllTests()"}, + {"name":"TestAddElement","kind":"sub","vis":"public","signature":"Public Sub TestAddElement()"}, + {"name":"TestClone","kind":"sub","vis":"public","signature":"Public Sub TestClone()"}, + {"name":"TestCreateNew","kind":"sub","vis":"public","signature":"Public Sub TestCreateNew()"}, + {"name":"TestEmptyDocument","kind":"sub","vis":"public","signature":"Public Sub TestEmptyDocument()"}, + {"name":"TestGetAttribute","kind":"sub","vis":"public","signature":"Public Sub TestGetAttribute()"}, + {"name":"TestGetElementText","kind":"sub","vis":"public","signature":"Public Sub TestGetElementText()"}, + {"name":"TestLoadFromFile","kind":"sub","vis":"public","signature":"Public Sub TestLoadFromFile()"}, + {"name":"TestMerge","kind":"sub","vis":"public","signature":"Public Sub TestMerge()"}, + {"name":"TestNamespaces","kind":"sub","vis":"public","signature":"Public Sub TestNamespaces()"}, + {"name":"TestParseNestedElements","kind":"sub","vis":"public","signature":"Public Sub TestParseNestedElements()"}, + {"name":"TestParseSimple","kind":"sub","vis":"public","signature":"Public Sub TestParseSimple()"}, + {"name":"TestParseWithAttributes","kind":"sub","vis":"public","signature":"Public Sub TestParseWithAttributes()"}, + {"name":"TestParseWithCData","kind":"sub","vis":"public","signature":"Public Sub TestParseWithCData()"}, + {"name":"TestParseWithComments","kind":"sub","vis":"public","signature":"Public Sub TestParseWithComments()"}, + {"name":"TestRemoveAttribute","kind":"sub","vis":"public","signature":"Public Sub TestRemoveAttribute()"}, + {"name":"TestRemoveElement","kind":"sub","vis":"public","signature":"Public Sub TestRemoveElement()"}, + {"name":"TestSaveToFile","kind":"sub","vis":"public","signature":"Public Sub TestSaveToFile()"}, + {"name":"TestSelectElement","kind":"sub","vis":"public","signature":"Public Sub TestSelectElement()"}, + {"name":"TestSelectElements","kind":"sub","vis":"public","signature":"Public Sub TestSelectElements()"}, + {"name":"TestSelfClosingTags","kind":"sub","vis":"public","signature":"Public Sub TestSelfClosingTags()"}, + {"name":"TestSetAttribute","kind":"sub","vis":"public","signature":"Public Sub TestSetAttribute()"}, + {"name":"TestSetElementText","kind":"sub","vis":"public","signature":"Public Sub TestSetElementText()"}, + {"name":"TestSpecialCharacters","kind":"sub","vis":"public","signature":"Public Sub TestSpecialCharacters()"}, + {"name":"TestToStringCompact","kind":"sub","vis":"public","signature":"Public Sub TestToStringCompact()"}, + {"name":"TestToStringPretty","kind":"sub","vis":"public","signature":"Public Sub TestToStringPretty()"}, + {"name":"TestToStringWithDeclaration","kind":"sub","vis":"public","signature":"Public Sub TestToStringWithDeclaration()"}, + {"name":"TestValidate","kind":"sub","vis":"public","signature":"Public Sub TestValidate()"}, + {"name":"m_TestsFailed","kind":"field","vis":"private","signature":"Private m_TestsFailed As Long"}, + {"name":"m_TestsPassed","kind":"field","vis":"private","signature":"Private m_TestsPassed As Long"}, + {"name":"m_TestsRun","kind":"field","vis":"private","signature":"Private m_TestsRun As Long"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-TBMANLIB_Xml/Settings b/test/example-projects/contributed-TBMANLIB_Xml/Settings new file mode 100644 index 000000000..141e59f81 --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB_Xml/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{AE06A746-A67C-435C-982A-085188FCA023}", + "licence": "MIT", + "name": "[IMPORTED] TBMAN: A complete XML object written in pure Twinbasic", + "path32": "/Packages/TBMANLIB_Xml", + "path64": "/Packages/TBMANLIB_Xml", + "symbolId": "TBMANLIB_Xml", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From d4e045926e6559e07e5c6ceb897282a2a7357d6f Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 12:11:47 +0200 Subject: [PATCH 07/15] tsPwGen: a contributed section of six pages (PwGen, PwGenOptions, PwGenError, PwGenRandom, PwGenCryptoSource), with its snapshot, sample template and book chapter; its known problems each shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 1 + docs/Reference/Contributed/tsPwGen/PwGen.md | 224 ++++++++ .../Contributed/tsPwGen/PwGenCryptoSource.md | 93 ++++ .../Contributed/tsPwGen/PwGenError.md | 186 +++++++ .../Contributed/tsPwGen/PwGenOptions.md | 496 ++++++++++++++++++ .../Contributed/tsPwGen/PwGenRandom.md | 127 +++++ docs/Reference/Contributed/tsPwGen/index.md | 165 ++++++ docs/_book.yml | 7 + .../snapshots/contributed/tsPwGen/api.json | 457 ++++++++++++++++ .../contributed-tsPwGen/Settings | 74 +++ 12 files changed, 1832 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/tsPwGen/PwGen.md create mode 100644 docs/Reference/Contributed/tsPwGen/PwGenCryptoSource.md create mode 100644 docs/Reference/Contributed/tsPwGen/PwGenError.md create mode 100644 docs/Reference/Contributed/tsPwGen/PwGenOptions.md create mode 100644 docs/Reference/Contributed/tsPwGen/PwGenRandom.md create mode 100644 docs/Reference/Contributed/tsPwGen/index.md create mode 100644 indexer/snapshots/contributed/tsPwGen/api.json create mode 100644 test/example-projects/contributed-tsPwGen/Settings diff --git a/WIP.md b/WIP.md index 910f64e32..421f2bddb 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Seventeen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph. See *Where things live* for how a package moves between the two lists. +Eighteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 107905ada..fa6365134 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1161, + "pages": 1167, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index 928ff88eb..67237507c 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -22,6 +22,7 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**TBMANLIB_RES**](TBMANLIB_RES/) -- resources embedded in an executable, as bytes, strings, hex, Base64 or pictures - [**tbProjPacker**](tbProjPacker/) -- a `.twinproj` or `.twinpack` file unpacked into a folder of source files, and packed again - [**tbRegExp**](tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object +- [**tsPwGen**](tsPwGen/) -- passwords, PINs and passphrases, with their entropy known before they are generated - [**TwinTimerPackage**](TwinTimerPackage/) -- a Win32 timer class that reports the elapsed time - [**VBA_JSON**](VBA_JSON/) -- JSON parsing and serialization, with UTC and ISO 8601 date conversion - [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs \ No newline at end of file diff --git a/docs/Reference/Contributed/tsPwGen/PwGen.md b/docs/Reference/Contributed/tsPwGen/PwGen.md new file mode 100644 index 000000000..8a1ea28c3 --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/PwGen.md @@ -0,0 +1,224 @@ +--- +title: PwGen +parent: tsPwGen Package +permalink: /tB/Packages/Contributed/tsPwGen/PwGen +has_toc: false +--- + +# PwGen module +{: .no_toc } + +The **PwGen** module generates a password or a passphrase from a recipe, reports the entropy of a recipe without generating anything, and reads the two built-in word lists. A recipe is a [**PwGenOptions**](PwGenOptions) object. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Hex +Debug.Print CLng(PwGen.Entropy(o)) ' 128 + +Dim token As String = PwGen.Generate(o) +Debug.Print Len(token) ' 32 +Debug.Print token Like "*[!0-9a-f]*" ' False +``` + +The module has no settings. Its only state is the last error, which [**PwGenError**](PwGenError) reports. A recipe that cannot be used does not raise a run-time error: the function returns an empty string, or 0, and sets the last error. Every function of the module clears the last error when it starts, so after a call the last error belongs to that call. + +> [!NOTE] +> Write the module name in every call, as in `PwGen.Generate`. In version 1.0.8.0 the package's internal module **PwGenEngine** also declares public functions named **Generate** and **Entropy**, so a call written without the module name fails with TB5073, *'Entropy' is ambiguous*, or *'Generate' is ambiguous*. +> +> ```tb check_build expect-error=TB5073 +> Debug.Print CLng(Entropy()) +> ``` + +* TOC +{:toc} + +## Constants + +### PWGEN_ALPHABET_HEX +{: .no_toc } + +The characters of a lowercase hexadecimal number, `0123456789abcdef`. A **String** constant. The [**Hex**](PwGenOptions#hex) preset sets its [**Alphabet**](PwGenOptions#alphabet) to this value. + +### PWGEN_DEFAULT_AMBIGUOUS_CHARS +{: .no_toc } + +The characters that are often mistaken for one another when they are read: the digit zero, the capital `O`, the digit one, the lowercase `l` and the capital `I`. The value is `0O1lI`. A **String** constant. It is the default of [**AmbiguousChars**](PwGenOptions#ambiguouschars). + +### PWGEN_DEFAULT_SPECIAL_CHARS +{: .no_toc } + +The 20 special characters that a recipe uses by default, `!#$%&()+,-.;=@[]_{}~`. A **String** constant. It is the default of [**SpecialChars**](PwGenOptions#specialchars). + +### PWGEN_MAX_LENGTH +{: .no_toc } + +The largest value of [**Length**](PwGenOptions#length), 4096. A **Long** constant. A longer password is an `InvalidLength` error. + +### PWGEN_MAX_WORD_COUNT +{: .no_toc } + +The largest value of [**WordCount**](PwGenOptions#wordcount), 256. A **Long** constant. A larger count is an `InvalidWordCount` error. + +## Enumerations + +### ePwGenMode +{: .no_toc } + +The way a recipe produces its result. [**Mode**](PwGenOptions#mode) takes one of these members. Any other value is an `InvalidMode` error when the recipe is used. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **pgmRandom** | 0 | The result is a string of characters drawn from a pool. | +| **pgmWordlist** | 1 | The result is a passphrase of words drawn from a list. | + +### ePwGenWordlist +{: .no_toc } + +The built-in word lists. [**Wordlist**](PwGenOptions#wordlist) and the functions [**BuiltInWord**](#builtinword) and [**BuiltInWordCount**](#builtinwordcount) take one of these members. Any other value is an `InvalidWordlist` error. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **pgwEnglish** | 0 | The English list of 8192 words. | +| **pgwGerman** | 1 | The German list of 8192 words. | + +## Functions + +### BuiltInWord +{: .no_toc } + +Returns one word of a built-in word list. + +Syntax: **PwGen.BuiltInWord** ( *list*, *index* ) **As String** + +*list* +: *required* An [**ePwGenWordlist**](#epwgenwordlist) member that names the list. + +*index* +: *required* A **Long**, the position of the word in the list. The first word has index 0, so the valid values run from 0 to [**BuiltInWordCount**](#builtinwordcount) minus 1. + +Returns the word, or an empty string when the call fails. The call fails with `InvalidWordlist` when *list* is not a member of **ePwGenWordlist**, and with `IndexOutOfRange` when *index* is below 0 or not below the number of words. Each call copies the whole list internally. + +```tb check_build check_run +Debug.Print PwGen.BuiltInWord(pgwEnglish, 0) ' abandon +Debug.Print PwGen.BuiltInWord(pgwGerman, 0) ' aal + +Dim w As String = PwGen.BuiltInWord(pgwEnglish, 8192) +Debug.Print w = "" ' True +Debug.Print PwGenError.Code ' IndexOutOfRange +Debug.Print PwGenError.Origin ' PwGen.BuiltInWord +``` + +### BuiltInWordCount +{: .no_toc } + +Returns the number of words in a built-in word list. + +Syntax: **PwGen.BuiltInWordCount** ( *list* ) **As Long** + +*list* +: *required* An [**ePwGenWordlist**](#epwgenwordlist) member that names the list. + +Returns 8192 for **pgwEnglish** and for **pgwGerman**. Returns 0 and sets the error `InvalidWordlist` when *list* is not a member of **ePwGenWordlist**. + +```tb check_build check_run +Debug.Print PwGen.BuiltInWordCount(pgwEnglish) ' 8192 +Debug.Print PwGen.BuiltInWordCount(pgwGerman) ' 8192 + +Debug.Print PwGen.BuiltInWordCount(99) ' 0 +Debug.Print PwGenError.Code ' InvalidWordlist +``` + +### Entropy +{: .no_toc } + +Returns the entropy of a recipe in bits, without generating a result. + +Syntax: **PwGen.Entropy** ( [ *options* ] ) **As Double** + +*options* +: *optional* The [**PwGenOptions**](PwGenOptions) recipe whose entropy is wanted. **Nothing**, the default, means a new **Alphanumeric** recipe. The recipe is not changed. + +Returns the entropy as a **Double**, or 0 and the last error when the recipe cannot be used. The recipe is checked as [**Generate**](#generate) checks it, with the origin `PwGen.Entropy`. The errors that can arise only while characters are drawn are not found: `RngFailed`, `CannotAvoidRepeats` with the message *could not place characters without consecutive repeats*, and `LengthTooShort` with the message *Length is too small for MinPerClass and StartsWithLetter*. **Entropy** does not use the recipe's [**RandomSource**](PwGenOptions#randomsource). The codes are listed under [Error codes](PwGenError#error-codes). + +The figure is computed as follows: + +- **Random mode.** `Length * log2(p)`, where *p* is the number of different characters in the pool. A character that appears twice in [**Alphabet**](PwGenOptions#alphabet), [**SpecialChars**](PwGenOptions#specialchars) or [**IncludeChars**](PwGenOptions#includechars) counts once. A character that [**ExcludeChars**](PwGenOptions#excludechars) or [**IsAmbiguousExcluded**](PwGenOptions#isambiguousexcluded) removes does not count. +- **Wordlist mode.** `WordCount * log2(n)`, where *n* is the number of words in the list: the custom list when [**CustomWords**](PwGenOptions#customwords) has words, otherwise the built-in list. When [**HasUniqueWords**](PwGenOptions#hasuniquewords) is **True**, the sum of `log2(n - i)` for *i* from 0 to `WordCount - 1`. + +The logarithm is computed as `Log(x) / Log(2)`, so a result can differ from the exact figure in its last digits: the entropy of the **Passphrase** preset is 65 up to rounding. The figure does not take the constraints of random mode into account: [**MinPerClass**](PwGenOptions#minperclass), [**StartsWithLetter**](PwGenOptions#startswithletter) and [**HasNoConsecutiveRepeats**](PwGenOptions#hasnoconsecutiverepeats) make some results impossible or less likely than others, so the entropy of the passwords actually drawn can be lower. The separator and [**Capitalize**](PwGenOptions#capitalize) of a passphrase add nothing. + +```tb check_build check_run +Debug.Print Int(PwGen.Entropy()) ' 93 +Debug.Print Int(PwGen.Entropy(PwGenOptions.Complex)) ' 100 +Debug.Print Int(PwGen.Entropy(PwGenOptions.Pin)) ' 19 +Debug.Print CLng(PwGen.Entropy(PwGenOptions.Hex)) ' 128 +Debug.Print CLng(PwGen.Entropy(PwGenOptions.Passphrase)) ' 65 + +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Alphabet = "aabbcc" +o.Length = 10 +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 3 +``` + +The last line shows that the three different characters of `aabbcc` are counted, and not the six. The expression `2 ^ (bits / Length)` turns the entropy back into the size of the pool. + +### Generate +{: .no_toc } + +Generates a password or a passphrase from a recipe. + +Syntax: **PwGen.Generate** ( [ *options* ] ) **As String** + +*options* +: *optional* The [**PwGenOptions**](PwGenOptions) recipe to use. **Nothing**, the default, means a new **Alphanumeric** recipe: 16 characters from the letters and digits, without the look-alikes `0O1lI`. The recipe is not changed. + +Returns the password or passphrase. When the recipe cannot be used, or the random source fails, returns an empty string and sets the last error, which [**PwGenError**](PwGenError) reports. The last error is cleared first, so **PwGenError.HasError** after a successful call is **False**. The codes that can be set are listed under [Error codes](PwGenError#error-codes), with the origin `PwGen.Generate`. + +The [**Mode**](PwGenOptions#mode) of the recipe decides what is generated. In random mode the result has [**Length**](PwGenOptions#length) characters, in wordlist mode [**WordCount**](PwGenOptions#wordcount) words. [Random mode](PwGenOptions#random-mode) and [Wordlist mode](PwGenOptions#wordlist-mode) describe how the pool is built and what is drawn. All randomness comes from the recipe's [**RandomSource**](PwGenOptions#randomsource), or from [**PwGenCryptoSource**](PwGenCryptoSource) when it is **Nothing**. + +```tb check_build check_run +Dim pw As String = PwGen.Generate() +Debug.Print Len(pw) ' 16 +Debug.Print pw Like "*[!A-HJ-NP-Za-km-z2-9]*" ' False + +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Length = 0 +Debug.Print PwGen.Generate(o) = "" ' True +Debug.Print PwGenError.ToString() ' PwGen.Generate: InvalidLength - Length must be between 1 and 4096. +``` + +The pattern in the second line is true for a character outside the pool of the default recipe, so **False** means that every character of the password is in it. + +### TryGenerate +{: .no_toc } + +Generates a password or a passphrase into a variable and returns whether it succeeded. + +Syntax: **PwGen.TryGenerate** ( *password* [ , *options* ] ) **As Boolean** + +*password* +: *required* A **String** variable that receives the result. It is passed **ByRef**. After a failed call it is an empty string. + +*options* +: *optional* The [**PwGenOptions**](PwGenOptions) recipe to use. **Nothing**, the default, means a new **Alphanumeric** recipe. + +Returns **True** when a result was generated and **False** when not. It calls [**Generate**](#generate) and tests the last error, so the last error is set as **Generate** sets it, with the origin `PwGen.Generate`, and the codes are the same. + +```tb check_build check_run +Dim pw As String +Dim o As PwGenOptions = PwGenOptions.Pin +Debug.Print PwGen.TryGenerate(pw, o) ' True +Debug.Print Len(pw) ' 6 +Debug.Print pw Like "*[!0-9]*" ' False + +o.Length = 0 +Debug.Print PwGen.TryGenerate(pw, o) ' False +Debug.Print pw = "" ' True +Debug.Print PwGenError.Code ' InvalidLength +``` + +## See Also + +- [PwGenOptions](PwGenOptions) class -- the recipe that every function takes +- [PwGenError](PwGenError) class -- reports the last error and lists the error codes +- [PwGenRandom](PwGenRandom) interface -- the random source of a recipe +- [tsPwGen Package](.) -- the package diff --git a/docs/Reference/Contributed/tsPwGen/PwGenCryptoSource.md b/docs/Reference/Contributed/tsPwGen/PwGenCryptoSource.md new file mode 100644 index 000000000..5ccdb662d --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/PwGenCryptoSource.md @@ -0,0 +1,93 @@ +--- +title: PwGenCryptoSource +parent: tsPwGen Package +permalink: /tB/Packages/Contributed/tsPwGen/PwGenCryptoSource +has_toc: false +--- + +# PwGenCryptoSource class +{: .no_toc } + +A **PwGenCryptoSource** is the random source that the package uses when a recipe's [**RandomSource**](PwGenOptions#randomsource) is **Nothing**. It implements [**PwGenRandom**](PwGenRandom) with the random numbers of the Windows Cryptography API: Next Generation (CNG). + +The class has no public member of its own. Its one method, **NextInRange**, is **Private** and is reached through a variable of the type **PwGenRandom**. The package's own description of the class calls it internal. It is declared without **Private**, and has neither a **Private** constructor nor `[COMCreatable(False)]`, so a project can create one with **New**. + +```tb check_build check_run +Dim src As PwGenRandom = New PwGenCryptoSource +PwGenError.Clear +Dim n As Long = src.NextInRange(1, 6) +Debug.Print n >= 1 And n <= 6 ' True +Debug.Print src.NextInRange(3, 3) ' 3 +``` + +A variable declared with the class itself cannot call the method, because it is **Private**. The compiler reports TB5027, *Unrecognized member 'NextInRange'*: + +```tb check_build expect-error=TB5027 +Dim s As New PwGenCryptoSource +Debug.Print s.NextInRange(1, 6) +``` + +## How the numbers are produced + +The source asks Windows for random bytes with **BCryptGenRandom** from `bcrypt.dll`. It passes no algorithm handle and the flag `BCRYPT_USE_SYSTEM_PREFERRED_RNG`, so the generator is the one that Windows prefers for the system. The package's source comments call the source cryptographically secure. The strength is that of this Windows generator, and the package adds no random numbers of its own. + +For each number the source works as follows: + +1. It asks for four random bytes and reads them as an unsigned 32-bit number *r*, from 0 to 4294967295. +2. It computes the size of the interval, *s* = *upperBound* - *lowerBound* + 1, and the largest multiple of *s* that is not above 2^32, called the limit. +3. If *r* is not below the limit, it discards *r* and asks for four new bytes. The discarded numbers are the reason that every number of the interval is equally likely: a plain remainder of *r* divided by *s* would favour the small numbers when *s* does not divide 2^32. +4. Otherwise it returns *lowerBound* + (*r* mod *s*). + +The class keeps no state. The package creates one instance when it first needs it and shares it. + +## Errors + +The source raises no run-time error. It reports a problem through the last error of the package, [**PwGenError**](PwGenError), and returns 0: + +- `InvalidRange`, with the message *lowerBound is greater than upperBound*, when the bounds are reversed. The origin is an empty string. +- `RngFailed`, with the message *BCryptGenRandom failed*, when the Windows function does not return success. The origin is an empty string. When [**PwGen.Generate**](PwGen#generate) calls the source, it replaces the origin with its own, such as `PwGen.Generate`. + +```tb check_build check_run +Dim src As PwGenRandom = New PwGenCryptoSource +PwGenError.Clear +Debug.Print src.NextInRange(5, 1) ' 0 +Debug.Print PwGenError.Code ' InvalidRange +Debug.Print PwGenError.Origin = "" ' True +``` + +> [!WARNING] +> In version 1.0.8.0, **NextInRange** does not clear the last error before it starts, and it returns 0 whenever the last error is already set. It raises no error and sets no error of its own, so a direct call made after any earlier failure of the package returns 0 even when the bounds do not include 0, and the last error still belongs to the earlier failure. Call [**PwGenError.Clear**](PwGenError#clear) first. The functions of [**PwGen**](PwGen) do this themselves, so a recipe is not affected. +> +> ```tb check_build check_run +> Dim src As PwGenRandom = New PwGenCryptoSource +> Dim o As PwGenOptions = PwGenOptions.Alphanumeric +> o.Length = 0 +> PwGen.Generate o +> Debug.Print PwGenError.Code ' InvalidLength +> Debug.Print src.NextInRange(1, 6) ' 0 +> Debug.Print PwGenError.Code ' InvalidLength +> +> PwGenError.Clear +> Dim n As Long = src.NextInRange(1, 6) +> Debug.Print n >= 1 And n <= 6 ' True +> ``` + +## Methods + +### NextInRange +{: .no_toc } + +Returns a whole number between two bounds, both included. + +Syntax: *source*.**NextInRange** ( *lowerBound*, *upperBound* ) **As Long** + +The method is **Private**: it is called through a **PwGenRandom** variable, as [**PwGenRandom.NextInRange**](PwGenRandom#nextinrange) describes. The two parameters are **Long** values; *lowerBound* must not be above *upperBound*. + +Returns the number, or 0 after `InvalidRange` or `RngFailed`. The bounds can be any two **Long** values, in the right order. + +## See Also + +- [PwGenRandom](PwGenRandom) interface -- the interface the class implements +- [PwGenOptions](PwGenOptions#randomsource) class -- the **RandomSource** property +- [PwGenError](PwGenError) class -- reports `InvalidRange` and `RngFailed` +- [tsPwGen Package](.) -- the package diff --git a/docs/Reference/Contributed/tsPwGen/PwGenError.md b/docs/Reference/Contributed/tsPwGen/PwGenError.md new file mode 100644 index 000000000..82840c093 --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/PwGenError.md @@ -0,0 +1,186 @@ +--- +title: PwGenError +parent: tsPwGen Package +permalink: /tB/Packages/Contributed/tsPwGen/PwGenError +has_toc: false +--- + +# PwGenError class +{: .no_toc } + +The **PwGenError** object reports the last error of the package: whether there is one, its code, its message and the function that found it. + +**PwGenError** is a predeclared object. It is used by its name, without a variable. The error itself is stored in the package, not in the object, and the package keeps a single last error, so an object created with **New** reports the same error as the predeclared object. There is no member that sets it. + +The functions of [**PwGen**](PwGen) do not raise run-time errors. A function that fails returns an empty string, or 0, and sets the last error. Each function of **PwGen** clears the last error when it starts, so the last error belongs to the latest call. To keep an error across the next call, copy it with [**Snapshot**](#snapshot). + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Length = 0 +Dim pw As String = PwGen.Generate(o) + +Debug.Print pw = "" ' True +Debug.Print PwGenError.HasError ' True +Debug.Print PwGenError.Code ' InvalidLength +Debug.Print PwGenError.Message ' Length must be between 1 and 4096. +Debug.Print PwGenError.Origin ' PwGen.Generate +Debug.Print PwGenError.ToString() ' PwGen.Generate: InvalidLength - Length must be between 1 and 4096. +``` + +* TOC +{:toc} + +## Error codes + +The code is a **String** that names the error. It is case-sensitive. These are the codes that [**PwGen**](PwGen) and [**PwGenCryptoSource**](PwGenCryptoSource) set, with the message that goes with each. A code with two messages is set in two situations. + +| Code | Message | Set when | +|:-----|:--------|:---------| +| `InvalidMode` | Mode is not a known ePwGenMode member. | [**Mode**](PwGenOptions#mode) is neither **pgmRandom** nor **pgmWordlist**. | +| `InvalidLength` | Length must be between 1 and 4096. | In random mode, [**Length**](PwGenOptions#length) is below 1 or above [**PWGEN_MAX_LENGTH**](PwGen#pwgen_max_length). | +| `InvalidMinPerClass` | MinPerClass must be >= 0. | In random mode, [**MinPerClass**](PwGenOptions#minperclass) is negative. | +| `NoCharset` | no character class is enabled and Alphabet is empty. | In random mode, the four class properties are **False** and [**Alphabet**](PwGenOptions#alphabet) is empty. | +| `EmptyClass` | an enabled class is empty after exclusions. | An enabled class has no character left after [**ExcludeChars**](PwGenOptions#excludechars) and [**AmbiguousChars**](PwGenOptions#ambiguouschars) are applied, or its characters are an empty [**SpecialChars**](PwGenOptions#specialchars). | +| `LengthTooShort` | Length is smaller than MinPerClass times the number of enabled classes. | [**Length**](PwGenOptions#length) is below [**MinPerClass**](PwGenOptions#minperclass) times the number of enabled classes. | +| | Length is too small for MinPerClass and StartsWithLetter. | While a password is drawn, no free position is left for a character of a class, for example when the first letter came from [**IncludeChars**](PwGenOptions#includechars) and so counts for no class. [**Entropy**](PwGen#entropy) does not find this. | +| `CannotStartWithLetter` | the pool has no ASCII letter for StartsWithLetter. | [**StartsWithLetter**](PwGenOptions#startswithletter) is **True** and the pool has no letter from `A` to `Z` or `a` to `z`. | +| `CannotAvoidRepeats` | HasNoConsecutiveRepeats needs at least two distinct characters. | [**HasNoConsecutiveRepeats**](PwGenOptions#hasnoconsecutiverepeats) is **True**, [**Length**](PwGenOptions#length) is above 1 and the pool has one character. | +| | could not place characters without consecutive repeats. | After 48 shuffles the drawn characters still have two equal neighbours. [**Entropy**](PwGen#entropy) does not find this. | +| `RngFailed` | random source returned a value outside the requested interval. | A [**PwGenRandom**](PwGenRandom) source returned a number outside the bounds it was given. | +| | BCryptGenRandom failed. | The Windows function failed, in [**PwGenCryptoSource**](PwGenCryptoSource). | +| `InvalidRange` | lowerBound is greater than upperBound. | [**NextInRange**](PwGenRandom#nextinrange) of the package's own source is called with the bounds reversed. The package never does this. | +| `InvalidWordCount` | WordCount must be between 1 and 256. | In wordlist mode, [**WordCount**](PwGenOptions#wordcount) is below 1 or above [**PWGEN_MAX_WORD_COUNT**](PwGen#pwgen_max_word_count). | +| `EmptyWord` | CustomWords contains an empty string. | A word of [**CustomWords**](PwGenOptions#customwords) is an empty string. | +| `DuplicateWord` | CustomWords contains a duplicate word. | Two words of [**CustomWords**](PwGenOptions#customwords) are equal, compared by character code. | +| `NotEnoughWords` | WordCount is larger than the word list. | [**HasUniqueWords**](PwGenOptions#hasuniquewords) is **True** and [**WordCount**](PwGenOptions#wordcount) is above the number of words in the list. | +| `InvalidWordlist` | Wordlist is not a known built-in list. | [**Wordlist**](PwGenOptions#wordlist), or the list passed to [**BuiltInWord**](PwGen#builtinword) or [**BuiltInWordCount**](PwGen#builtinwordcount), is not a member of **ePwGenWordlist**. | +| `IndexOutOfRange` | word index is outside the list. | The index passed to [**BuiltInWord**](PwGen#builtinword) is negative or not below the number of words. | + +A recipe with several faults sets one error, the first fault that the package finds. In random mode the properties of wordlist mode are not checked, and the other way round. + +> [!NOTE] +> The package declares two more codes, `EmptyCharset` and `EmptyWordlist`. No recipe sets them: the package's own tests list them as having no trigger, together with `InvalidRange`, which only a direct call of the default random source can set. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Length = 2 +PwGen.Generate o +Debug.Print PwGenError.Code ' LengthTooShort + +Set o = PwGenOptions.Pin +o.StartsWithLetter = True +PwGen.Generate o +Debug.Print PwGenError.Code ' CannotStartWithLetter + +Set o = PwGenOptions.Passphrase +o.WordCount = 257 +PwGen.Entropy o +Debug.Print PwGenError.Code ' InvalidWordCount +Debug.Print PwGenError.Origin ' PwGen.Entropy +``` + +## Properties + +### Code +{: .no_toc } + +The code of the last error. A **String**, read-only. + +Syntax: *text* **=** **PwGenError.Code** + +Returns the code, such as `InvalidLength`, or an empty string when there is no error. The codes are listed under [Error codes](#error-codes). + +### HasError +{: .no_toc } + +Whether there is a last error. A **Boolean**, read-only. + +Syntax: *flag* **=** **PwGenError.HasError** + +Returns **True** after a function of [**PwGen**](PwGen) has failed, and **False** after one has succeeded, because each function clears the last error when it starts. It is also **False** after [**Clear**](#clear). + +### Message +{: .no_toc } + +The message of the last error. A **String**, read-only. + +Syntax: *text* **=** **PwGenError.Message** + +Returns the message in English, such as `Length must be between 1 and 4096.`, or an empty string when there is no error. The [Error codes](#error-codes) table gives the message of each code. + +### Origin +{: .no_toc } + +The function that found the last error. A **String**, read-only. + +Syntax: *text* **=** **PwGenError.Origin** + +Returns the name of the module and of the public function, such as `PwGen.Generate`, or an empty string when there is no error. A call to [**TryGenerate**](PwGen#trygenerate) gives `PwGen.Generate`, because it calls **Generate**. The origin of an error set by a direct call to the package's random source is an empty string. + +## Methods + +### Clear +{: .no_toc } + +Clears the last error. + +Syntax: **PwGenError.Clear** + +After the call, [**HasError**](#haserror) is **False** and [**Code**](#code), [**Message**](#message) and [**Origin**](#origin) are empty strings. The functions of [**PwGen**](PwGen) call it themselves when they start, so a program needs it only to discard an error that it has read. + +### Snapshot +{: .no_toc } + +Returns a copy of the last error. + +Syntax: *snap* **=** **PwGenError.Snapshot** ( ) **As** [**tPwGenError**](#tpwgenerror) + +Returns a [**tPwGenError**](#tpwgenerror) with the values of **HasError**, **Code**, **Message** and **Origin** at the time of the call. The copy belongs to the caller: the next call of a **PwGen** function, which clears the last error, does not change it. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Length = 0 +PwGen.Generate o +Dim snap As PwGenError.tPwGenError = PwGenError.Snapshot() + +PwGen.Generate ' succeeds, and clears the last error +Debug.Print PwGenError.HasError ' False +Debug.Print snap.HasError ' True +Debug.Print snap.Code ' InvalidLength +Debug.Print snap.Origin ' PwGen.Generate +``` + +The type is declared inside the class. A variable of that type can be declared `As PwGenError.tPwGenError` or, as the package's own tests do, `As tPwGenError`. + + +### ToString +{: .no_toc } + +Returns the last error as one line of text. + +Syntax: *text* **=** **PwGenError.ToString** ( ) **As String** + +Returns an empty string when there is no error. Otherwise returns **Origin**, a colon and a space, **Code**, a space, a hyphen, a space and **Message**, as in `PwGen.Generate: InvalidLength - Length must be between 1 and 4096.`. The text is meant for logs and for **Debug.Print**. + +## Types + +### tPwGenError +{: .no_toc } + +A copy of the last error, returned by [**Snapshot**](#snapshot). A user-defined type, declared inside the class. + +| Field | Type | Content | +|:------|:-----|:--------| +| **HasError** | **Boolean** | Whether there was an error. | +| **Code** | **String** | The code, or an empty string. | +| **Message** | **String** | The message, or an empty string. | +| **Origin** | **String** | The origin, or an empty string. | + +The fields are separate values. Unlike the properties of **PwGenError**, they do not change when the last error changes. + +## See Also + +- [PwGen](PwGen) module -- the functions whose errors it reports +- [PwGenOptions](PwGenOptions) class -- the recipe whose settings the codes refer to +- [PwGenCryptoSource](PwGenCryptoSource) class -- the default random source, which sets `RngFailed` and `InvalidRange` +- [tsPwGen Package](.) -- the package diff --git a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md new file mode 100644 index 000000000..79120d290 --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md @@ -0,0 +1,496 @@ +--- +title: PwGenOptions +parent: tsPwGen Package +permalink: /tB/Packages/Contributed/tsPwGen/PwGenOptions +has_toc: false +--- + +# PwGenOptions class +{: .no_toc } + +A **PwGenOptions** object is a recipe for [**PwGen.Generate**](PwGen#generate) and [**PwGen.Entropy**](PwGen#entropy): the mode, the characters or words to draw from, the constraints on the result, and the random source. + +A recipe is created from a preset. The predeclared object **PwGenOptions**, written without a variable, is a factory: its five preset properties, [**Alphanumeric**](#alphanumeric), [**Complex**](#complex), [**Hex**](#hex), [**Passphrase**](#passphrase) and [**Pin**](#pin), each return a new recipe with every property set, and [**Clone**](#clone) copies one. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Complex +o.Length = 20 +o.StartsWithLetter = True + +Dim pw As String = PwGen.Generate(o) +Debug.Print Len(pw) ' 20 +Debug.Print Left$(pw, 1) Like "[A-Za-z]" ' True +``` + +A property of a recipe checks nothing when it is set. A value that cannot be used, such as a **Length** of 0, is found when the recipe is passed to **Generate** or **Entropy**, and is reported there as an error code. + +> [!IMPORTANT] +> Assigning a property on the predeclared object itself raises run-time error 5, *Invalid procedure call or argument*, with the source `PwGenOptions` and the message *PwGenOptions (default instance) is a factory. Use Alphanumeric, Complex, Passphrase, Pin, or Hex.* Create a recipe first and assign the properties of that. +> +> ```tb check_build check_run +> On Error Resume Next +> PwGenOptions.Length = 20 +> Debug.Print Err.Number ' 5 +> Debug.Print Err.Source ' PwGenOptions +> ``` +> +> Reading a property of the predeclared object does not raise an error. It returns the empty value of the type, 0, **False** or an empty string, and not the default of the property. +> +> ```tb check_build check_run +> Debug.Print PwGenOptions.Length ' 0 +> Debug.Print PwGenOptions.HasUpperCase ' False +> Debug.Print "[" & PwGenOptions.WordSeparator & "]" ' [] +> ``` + +> [!NOTE] +> The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, but a project that references the package can still create an object with **New**. It is a recipe in which every property has the empty value of its type: **Length** 0, no class enabled, empty strings, **MinPerClass** 0, **Mode** **pgmRandom** and **RandomSource** **Nothing**. **Generate** returns an empty string for it, with the error `InvalidLength`, until the properties are set. The presets are the way to get the defaults. +> +> ```tb check_build check_run +> Dim o As PwGenOptions = New PwGenOptions +> Debug.Print PwGen.Generate(o) = "" ' True +> Debug.Print PwGenError.Code ' InvalidLength +> +> o.Length = 6 +> o.HasDigits = True +> Debug.Print Len(PwGen.Generate(o)) ' 6 +> Debug.Print o.MinPerClass ' 0 +> ``` + +* TOC +{:toc} + +## Random mode + +In random mode ([**Mode**](#mode) is **pgmRandom**, the default) the result is a string of [**Length**](#length) characters, each drawn from a pool. The properties of this section apply only in this mode. In wordlist mode they are not read. + +**The pool.** If [**Alphabet**](#alphabet) is not empty, the pool is the different characters of the **Alphabet**, and the settings for classes below have no effect. Otherwise the pool is built from classes: + +1. A class is enabled by [**HasUpperCase**](#hasuppercase) (`A` to `Z`), [**HasLowerCase**](#haslowercase) (`a` to `z`), [**HasDigits**](#hasdigits) (`0` to `9`) and [**HasSpecialChars**](#hasspecialchars) (the characters of [**SpecialChars**](#specialchars)). With no class enabled and an empty **Alphabet**, the error is `NoCharset`. +2. Each enabled class loses the characters of [**ExcludeChars**](#excludechars) and, when [**IsAmbiguousExcluded**](#isambiguousexcluded) is **True**, the characters of [**AmbiguousChars**](#ambiguouschars). A class with no character left is an `EmptyClass` error. +3. The pool is all the enabled classes together with the characters of [**IncludeChars**](#includechars), after the same removals, each different character once. + +Characters are compared by their character codes, so `a` and `A` are different characters. Each character is one UTF-16 code unit, so a character outside the Basic Multilingual Plane, which is written as two code units, counts as two characters of the pool, and a password can contain one half of it alone: + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Alphabet = ChrW(&HD83D) & ChrW(&HDE00) ' U+1F600 as a surrogate pair +o.Length = 1 +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 2 +Dim pw As String = PwGen.Generate(o) +Debug.Print Len(pw) ' 1 +Debug.Print AscW(pw) = AscW(ChrW(&HD83D)) Or AscW(pw) = AscW(ChrW(&HDE00)) ' True +``` + +**The draw.** The result is built in these steps: + +1. When [**StartsWithLetter**](#startswithletter) is **True**, the first character is drawn from the letters `A` to `Z` and `a` to `z` that are in the pool. +2. For each enabled class, [**MinPerClass**](#minperclass) characters are drawn from that class and placed at random free positions. The first character, when it was drawn for **StartsWithLetter**, counts for each class it belongs to. The characters of **IncludeChars** belong to no class. +3. Every position that is still free is filled with a character drawn from the whole pool. +4. The characters are shuffled with the Fisher-Yates method. When **StartsWithLetter** is **True**, the first character stays where it is. +5. When [**HasNoConsecutiveRepeats**](#hasnoconsecutiverepeats) is **True** and two equal characters are next to each other, the characters are shuffled again, up to 48 times. + +Each draw is one call to the recipe's [**RandomSource**](#randomsource). The constraints change how likely the results are: a password that must contain a digit is not a uniform choice among all strings of the pool. The entropy that [**PwGen.Entropy**](PwGen#entropy) reports ignores this. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 57 + +o.IncludeChars = "!" ' adds a character to the pool +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 58 + +o.ExcludeChars = "!A" ' removes A from its class and ! from the added characters +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 56 + +o.ExcludeChars = "" +o.IncludeChars = "0" ' 0 is one of the ambiguous characters, so it is removed +Debug.Print CLng(2 ^ (PwGen.Entropy(o) / o.Length)) ' 57 +``` + +## Wordlist mode + +In wordlist mode ([**Mode**](#mode) is **pgmWordlist**) the result is a passphrase of [**WordCount**](#wordcount) words, each drawn from a list and joined with [**WordSeparator**](#wordseparator). The properties of random mode are not read. + +The list is the array in [**CustomWords**](#customwords) when it has words, otherwise the built-in list that [**Wordlist**](#wordlist) names. When [**HasUniqueWords**](#hasuniquewords) is **False**, each word is drawn from the whole list, so a word can appear more than once. When it is **True**, a word that was drawn is taken out of the list before the next draw. When [**Capitalize**](#capitalize) is **True**, the first character of each word is changed to uppercase. + +Each draw is one call to the recipe's [**RandomSource**](#randomsource). The bounds are 0 and *n* - 1, where *n* is the number of words that can still be drawn: the whole list, or, with **HasUniqueWords**, the words that are left. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Passphrase +Dim words(0 To 0) As String +words(0) = "tango" +o.CustomWords = words +o.WordCount = 3 +o.WordSeparator = "+" +o.Capitalize = True +Debug.Print PwGen.Generate(o) ' Tango+Tango+Tango +Debug.Print PwGen.Entropy(o) ' 0 +``` + +The list has one word, so every draw returns it and the entropy is 0. + +## Properties + +### Alphabet +{: .no_toc } + +The complete pool of characters for random mode. A **String**, read/write. Default: an empty string. + +Syntax: *options*.**Alphabet** [ **=** *value* ] + +When the value is not empty, it is the whole pool. [**HasUpperCase**](#hasuppercase), [**HasLowerCase**](#haslowercase), [**HasDigits**](#hasdigits), [**HasSpecialChars**](#hasspecialchars), [**SpecialChars**](#specialchars), [**IncludeChars**](#includechars), [**ExcludeChars**](#excludechars), [**IsAmbiguousExcluded**](#isambiguousexcluded), [**AmbiguousChars**](#ambiguouschars) and [**MinPerClass**](#minperclass) then have no effect. [**StartsWithLetter**](#startswithletter) and [**HasNoConsecutiveRepeats**](#hasnoconsecutiverepeats) still apply, to this pool. A character that appears more than once counts once, so `aabbcc` is a pool of three characters. The [**Hex**](#hex) preset sets this property to [**PWGEN_ALPHABET_HEX**](PwGen#pwgen_alphabet_hex). + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Alphabet = "ABC" +o.Length = 12 +Dim pw As String = PwGen.Generate(o) +Debug.Print Len(pw) ' 12 +Debug.Print pw Like "*[!ABC]*" ' False +``` + +### AmbiguousChars +{: .no_toc } + +The characters that [**IsAmbiguousExcluded**](#isambiguousexcluded) removes. A **String**, read/write. Default: `0O1lI`, the value of [**PWGEN_DEFAULT_AMBIGUOUS_CHARS**](PwGen#pwgen_default_ambiguous_chars). + +Syntax: *options*.**AmbiguousChars** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set, or when **IsAmbiguousExcluded** is **False**. + +### Capitalize +{: .no_toc } + +Whether the first character of each word of a passphrase is changed to uppercase. A **Boolean**, read/write. Default: **False**. + +Syntax: *options*.**Capitalize** [ **=** *value* ] + +Wordlist mode only. The change is made with **UCase$** on the first character of the word. It is not drawn, so it adds no entropy. + +### CustomWords +{: .no_toc } + +The word list to use instead of a built-in list. A **String** array, read/write. Default: an empty array. + +Syntax: *options*.**CustomWords** [ **=** *value*( ) ] + +Wordlist mode only. When the array has words, it overrides [**Wordlist**](#wordlist). Assigning an empty array, or an array that was never allocated, makes the recipe use the built-in list again. The array can have any lower bound. + +Assigning copies the array, and reading returns a new array that starts at index 0 and has one element for each word, so changing an array after assigning it or after reading it does not change the recipe. A list must not contain an empty string (`EmptyWord`), and no two words may be equal (`DuplicateWord`). The comparison is by character code, so `alpha` and `Alpha` are different words. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Passphrase +Dim words(1 To 2) As String +words(1) = "red" +words(2) = "blue" +o.CustomWords = words +o.WordCount = 3 +o.HasUniqueWords = True + +Debug.Print PwGen.Generate(o) = "" ' True +Debug.Print PwGenError.Code ' NotEnoughWords + +Dim copy() As String = o.CustomWords +Debug.Print LBound(copy) ' 0 +Debug.Print UBound(copy) ' 1 +``` + +### ExcludeChars +{: .no_toc } + +Characters that are removed from every class and from [**IncludeChars**](#includechars). A **String**, read/write. Default: an empty string. + +Syntax: *options*.**ExcludeChars** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. Removing every character of an enabled class is an `EmptyClass` error. + +### HasDigits +{: .no_toc } + +Whether the digits `0` to `9` are a class of the pool. A **Boolean**, read/write. Default: **True**. + +Syntax: *options*.**HasDigits** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. The [**Pin**](#pin) preset sets it to **True** and disables the other classes. + +### HasLowerCase +{: .no_toc } + +Whether the letters `a` to `z` are a class of the pool. A **Boolean**, read/write. Default: **True**. + +Syntax: *options*.**HasLowerCase** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. + +### HasNoConsecutiveRepeats +{: .no_toc } + +Whether two equal characters may not be next to each other. A **Boolean**, read/write. Default: **False**. + +Syntax: *options*.**HasNoConsecutiveRepeats** [ **=** *value* ] + +Random mode only. The property applies to the pool that [**Alphabet**](#alphabet) gives, too. With a **Length** above 1 and a pool of one character, the error is `CannotAvoidRepeats`, in [**Entropy**](PwGen#entropy) as well as in [**Generate**](PwGen#generate). + +The characters are drawn first, and then shuffled again, up to 48 times, until no two equal ones are neighbours. With the default pool this succeeds every time in practice. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.HasNoConsecutiveRepeats = True +Dim pw As String = PwGen.Generate(o) + +Dim i As Long +Dim repeated As Boolean +For i = 2 To Len(pw) + If Mid$(pw, i, 1) = Mid$(pw, i - 1, 1) Then repeated = True +Next +Debug.Print repeated ' False + +o.Alphabet = "A" +o.Length = 4 +Debug.Print PwGen.Generate(o) = "" ' True +Debug.Print PwGenError.Code ' CannotAvoidRepeats +``` + +> [!NOTE] +> In version 1.0.8.0 the shuffle only rearranges the characters that were drawn, and the property never draws them again. When the drawn characters cannot be arranged without a repeat, **Generate** returns an empty string and sets `CannotAvoidRepeats`, although other passwords of the same pool would be valid. With a small pool this happens often. From the pool `AB` and a **Length** of 3, the draws `AAA` and `BBB` cannot be arranged, and together they are one draw in four. With a **Length** of 4 the failures are about three calls in five. With [**StartsWithLetter**](#startswithletter) the first character is not moved either, which makes the failures more frequent. A call that fails can be repeated, because the next call draws again. +> +> ```tb check_build check_run +> Dim o As PwGenOptions = PwGenOptions.Alphanumeric +> o.Alphabet = "AB" +> o.Length = 3 +> o.HasNoConsecutiveRepeats = True +> +> Dim i As Long +> Dim failures As Long +> For i = 1 To 200 +> If PwGen.Generate(o) = "" Then failures += 1 +> Next +> Debug.Print failures >= 20 And failures <= 80 ' True +> ``` + +### HasSpecialChars +{: .no_toc } + +Whether the characters of [**SpecialChars**](#specialchars) are a class of the pool. A **Boolean**, read/write. Default: **False**. The [**Complex**](#complex) preset sets it to **True**. + +Syntax: *options*.**HasSpecialChars** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. + +### HasUniqueWords +{: .no_toc } + +Whether the words of one passphrase must be different. A **Boolean**, read/write. Default: **False**. + +Syntax: *options*.**HasUniqueWords** [ **=** *value* ] + +Wordlist mode only. When **True**, each word is drawn from the words that are left, and a [**WordCount**](#wordcount) larger than the number of words in the list is a `NotEnoughWords` error. The entropy is then the sum of `log2(n - i)` over the words, and not `WordCount * log2(n)`. + +### HasUpperCase +{: .no_toc } + +Whether the letters `A` to `Z` are a class of the pool. A **Boolean**, read/write. Default: **True**. + +Syntax: *options*.**HasUpperCase** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. + +### IncludeChars +{: .no_toc } + +Characters added to the pool besides the classes. A **String**, read/write. Default: an empty string. + +Syntax: *options*.**IncludeChars** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. The added characters belong to no class, so [**MinPerClass**](#minperclass) does not count them. They are filtered with the pool: [**ExcludeChars**](#excludechars) removes them, and so does [**IsAmbiguousExcluded**](#isambiguousexcluded) for the characters of [**AmbiguousChars**](#ambiguouschars). A character that is already in the pool is not added twice. + +### IsAmbiguousExcluded +{: .no_toc } + +Whether the characters of [**AmbiguousChars**](#ambiguouschars) are removed from the pool. A **Boolean**, read/write. Default: **True**. The [**Pin**](#pin) preset sets it to **False**. + +Syntax: *options*.**IsAmbiguousExcluded** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. With the default **AmbiguousChars** the pool of the three default classes has 57 characters, and 62 when this property is **False**. + +### Length +{: .no_toc } + +The number of characters of the password. A **Long**, read/write. Default: 16. The valid values run from 1 to [**PWGEN_MAX_LENGTH**](PwGen#pwgen_max_length), 4096. + +Syntax: *options*.**Length** [ **=** *value* ] + +Random mode only. A value outside the valid range is an `InvalidLength` error when the recipe is used, not when the property is set. With [**MinPerClass**](#minperclass), the **Length** must also be at least **MinPerClass** times the number of enabled classes (`LengthTooShort`). + +### MinPerClass +{: .no_toc } + +The smallest number of characters that the password has from each enabled class. A **Long**, read/write. Default: 1. + +Syntax: *options*.**MinPerClass** [ **=** *value* ] + +Random mode only. Has no effect when [**Alphabet**](#alphabet) is set. The value 0 turns the guarantee off. A negative value is an `InvalidMinPerClass` error. When the guarantee is on, [**Length**](#length) must be at least the value times the number of enabled classes, or the error is `LengthTooShort`. The characters of [**IncludeChars**](#includechars) belong to no class and are not counted. + +With the default recipe, the password has at least one uppercase letter, one lowercase letter and one digit. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +Dim i As Long +Dim ok As Boolean = True +For i = 1 To 200 + Dim pw As String = PwGen.Generate(o) + If Not (pw Like "*[A-Z]*" And pw Like "*[a-z]*" And pw Like "*[0-9]*") Then ok = False +Next +Debug.Print ok ' True + +o.Length = 2 +PwGen.Generate o +Debug.Print PwGenError.Code ' LengthTooShort +``` + +### Mode +{: .no_toc } + +Whether the recipe generates a string of characters or a passphrase of words. A [**ePwGenMode**](PwGen#epwgenmode) member, read/write. Default: **pgmRandom**. + +Syntax: *options*.**Mode** [ **=** *value* ] + +The mode decides which of the other properties are read. [Random mode](#random-mode) and [Wordlist mode](#wordlist-mode) list them. A value that is not a member of the enumeration is an `InvalidMode` error when the recipe is used. The [**Passphrase**](#passphrase) preset sets it to **pgmWordlist**. + +### RandomSource +{: .no_toc } + +The object that supplies the random numbers for this recipe. A [**PwGenRandom**](PwGenRandom), read/write. Default: **Nothing**. + +Syntax: *options*.**RandomSource** [ **=** *value* ] + +The property is assigned with [**Set**](../../../Core/Set). **Nothing** means the package's own source, [**PwGenCryptoSource**](PwGenCryptoSource), which calls the Windows CNG function **BCryptGenRandom**. Any object that implements [**PwGenRandom**](PwGenRandom) can be assigned. [**Clone**](#clone) copies the reference, not the object. + +[**PwGen.Entropy**](PwGen#entropy) does not use the source. + +### SpecialChars +{: .no_toc } + +The characters of the special-character class. A **String**, read/write. Default: `!#$%&()+,-.;=@[]_{}~`, the value of [**PWGEN_DEFAULT_SPECIAL_CHARS**](PwGen#pwgen_default_special_chars). + +Syntax: *options*.**SpecialChars** [ **=** *value* ] + +Random mode only. The class is used when [**HasSpecialChars**](#hasspecialchars) is **True** and [**Alphabet**](#alphabet) is empty. A character that appears more than once counts once, and the pool and the entropy are smaller by the repeats. With an empty string, an enabled class is an `EmptyClass` error. + +### StartsWithLetter +{: .no_toc } + +Whether the first character must be an ASCII letter. A **Boolean**, read/write. Default: **False**. + +Syntax: *options*.**StartsWithLetter** [ **=** *value* ] + +Random mode only. The first character is drawn from the letters `A` to `Z` and `a` to `z` that are in the final pool. When the pool has no such letter, for example for the [**Pin**](#pin) preset, the error is `CannotStartWithLetter`. The property applies to the pool that [**Alphabet**](#alphabet) gives, too. The first character is always a letter. The other positions hold a letter only as often as the pool has them, so the results are not evenly spread over the pool. + +### WordCount +{: .no_toc } + +The number of words of a passphrase. A **Long**, read/write. Default: 5. The valid values run from 1 to [**PWGEN_MAX_WORD_COUNT**](PwGen#pwgen_max_word_count), 256. + +Syntax: *options*.**WordCount** [ **=** *value* ] + +Wordlist mode only. A value outside the valid range is an `InvalidWordCount` error when the recipe is used. + +### Wordlist +{: .no_toc } + +The built-in word list to draw from. An [**ePwGenWordlist**](PwGen#epwgenwordlist) member, read/write. Default: **pgwEnglish**. + +Syntax: *options*.**Wordlist** [ **=** *value* ] + +Wordlist mode only. The property is not read when [**CustomWords**](#customwords) has words. A value that is not a member of the enumeration is an `InvalidWordlist` error when the recipe is used. + +### WordSeparator +{: .no_toc } + +The string placed between the words of a passphrase. A **String**, read/write. Default: `-`. + +Syntax: *options*.**WordSeparator** [ **=** *value* ] + +Wordlist mode only. The separator can be any string, including an empty one. It adds no entropy. + +## Presets + +Each preset is a read-only property of the predeclared object. Every call returns a new recipe, which the caller can change without affecting any other. All the presets start from **Alphanumeric**, which sets the properties to the defaults given above, so a property that a preset does not mention has its default. + +### Alphanumeric +{: .no_toc } + +Returns a new recipe for a password of letters and digits. + +Syntax: *options* **=** **PwGenOptions.Alphanumeric** + +The recipe has a **Length** of 16, the classes uppercase, lowercase and digits, no special characters, and the five look-alikes `0O1lI` removed, which leaves 57 characters. [**MinPerClass**](#minperclass) is 1. Its entropy is about 93.3 bits. + +### Complex +{: .no_toc } + +Returns a new recipe like **Alphanumeric**, with the special characters added. + +Syntax: *options* **=** **PwGenOptions.Complex** + +[**HasSpecialChars**](#hasspecialchars) is **True**, which adds the 20 characters of [**SpecialChars**](#specialchars) and makes 77 characters. The password has at least one of each of the four classes. Its entropy is about 100.3 bits. + +### Hex +{: .no_toc } + +Returns a new recipe for a lowercase hexadecimal token. + +Syntax: *options* **=** **PwGenOptions.Hex** + +The [**Alphabet**](#alphabet) is [**PWGEN_ALPHABET_HEX**](PwGen#pwgen_alphabet_hex), `0123456789abcdef`, and the [**Length**](#length) is 32. Its entropy is 128 bits. + +### Passphrase +{: .no_toc } + +Returns a new recipe for a passphrase of five English words. + +Syntax: *options* **=** **PwGenOptions.Passphrase** + +The [**Mode**](#mode) is **pgmWordlist**. The other properties of wordlist mode have their defaults: 5 words from the English list, joined by `-`, drawn with replacement, in lowercase. Its entropy is 65 bits. + +### Pin +{: .no_toc } + +Returns a new recipe for a PIN of six digits. + +Syntax: *options* **=** **PwGenOptions.Pin** + +The [**Length**](#length) is 6, and the only class is the digits. [**IsAmbiguousExcluded**](#isambiguousexcluded) is **False**, so `0` and `1` are used. Its entropy is about 19.9 bits. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Pin +Dim pw As String = PwGen.Generate(o) +Debug.Print Len(pw) ' 6 +Debug.Print pw Like "*[!0-9]*" ' False +``` + +## Methods + +### Clone +{: .no_toc } + +Returns an independent copy of a recipe. + +Syntax: *options*.**Clone** ( ) **As PwGenOptions** + +Returns a new **PwGenOptions** with the same value in each property. Changing the copy does not change the original. The [**CustomWords**](#customwords) array is copied, and the [**RandomSource**](#randomsource) is not: the copy refers to the same object. + +```tb check_build check_run +Dim a As PwGenOptions = PwGenOptions.Alphanumeric +a.Length = 20 +Dim b As PwGenOptions = a.Clone() +b.Length = 8 +Debug.Print a.Length ' 20 +Debug.Print b.Length ' 8 +``` + +## See Also + +- [PwGen](PwGen) module -- generates results from a recipe and reports its entropy +- [PwGenError](PwGenError) class -- reports why a recipe could not be used +- [PwGenRandom](PwGenRandom) interface -- the type of the random source +- [tsPwGen Package](.) -- the package diff --git a/docs/Reference/Contributed/tsPwGen/PwGenRandom.md b/docs/Reference/Contributed/tsPwGen/PwGenRandom.md new file mode 100644 index 000000000..1570440b5 --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/PwGenRandom.md @@ -0,0 +1,127 @@ +--- +title: PwGenRandom +parent: tsPwGen Package +permalink: /tB/Packages/Contributed/tsPwGen/PwGenRandom +has_toc: false +--- + +# PwGenRandom interface +{: .no_toc } + +A **PwGenRandom** object supplies the random numbers that [**PwGen**](PwGen) uses to draw characters and words. The interface has one method, [**NextInRange**](#nextinrange), which returns a whole number between two bounds. + +A recipe names its source in [**RandomSource**](PwGenOptions#randomsource). When the property is **Nothing**, the package uses [**PwGenCryptoSource**](PwGenCryptoSource). A class of the program that implements **PwGenRandom** can replace it, for example to make a result predictable in a test. The class below always returns the lower bound: + +```tb check_build projname=pwgenrandom-lowest slot=file +Class LowestRandom + Implements PwGenRandom + + Private Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long Implements PwGenRandom.NextInRange + Return lowerBound + End Function +End Class +``` + +Every draw then picks the first character of the pool, or the first word that is left: + +```tb check_run projname=pwgenrandom-lowest +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +o.Alphabet = "QR" +o.Length = 5 +Set o.RandomSource = New LowestRandom +Debug.Print PwGen.Generate(o) ' QQQQQ + +Dim words(0 To 3) As String +words(0) = "alpha" +words(1) = "bravo" +words(2) = "charlie" +words(3) = "delta" +Dim p As PwGenOptions = PwGenOptions.Passphrase +p.CustomWords = words +p.WordCount = 4 +Set p.RandomSource = New LowestRandom + +Debug.Print PwGen.Generate(p) ' alpha-alpha-alpha-alpha +p.HasUniqueWords = True +Debug.Print PwGen.Generate(p) ' alpha-delta-charlie-bravo +``` + +With **HasUniqueWords**, the word that was drawn is replaced by the last word that is left, so the second draw returns `delta`, the third `charlie`, and the fourth is the only word left, `bravo`. + +## The contract of a source + +- **The bounds.** [**NextInRange**](#nextinrange) must return a **Long** that is not below the lower bound and not above the upper bound. The package passes a lower bound of 0 or 1, never a lower bound that is above the upper bound, and equal bounds when there is only one choice, as for a pool of one character. +- **A number outside the bounds is an error.** [**Generate**](PwGen#generate) then returns an empty string and sets the error `RngFailed`, with the message *random source returned a value outside the requested interval*. +- **Evenness.** The package takes each call as an independent, even choice among the whole numbers of the interval. The entropy that [**PwGen.Entropy**](PwGen#entropy) reports is correct only for a source that is even and independent. For any other source the figure is too high. +- **Errors raised inside a source.** The package has no error handler around the call, so a run-time error that a source raises reaches the caller of **Generate** with the number and the source the source gave it, and the last error of the package is not set. The caller handles it with [**On Error**](../../../Core/On-Error). The result variable keeps the value it had. + +```tb check_build projname=pwgenrandom-lowest slot=file +Class OutOfRangeRandom + Implements PwGenRandom + + Private Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long Implements PwGenRandom.NextInRange + Return upperBound + 1 + End Function +End Class +``` + +```tb check_run projname=pwgenrandom-lowest +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +Set o.RandomSource = New OutOfRangeRandom +Debug.Print PwGen.Generate(o) = "" ' True +Debug.Print PwGenError.Code ' RngFailed +``` + +A source that raises an error: + +```tb check_build projname=pwgenrandom-lowest slot=file +Class RaisingRandom + Implements PwGenRandom + + Private Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long Implements PwGenRandom.NextInRange + Err.Raise 5, "RaisingRandom", "no random numbers" + End Function +End Class +``` + +```tb check_run projname=pwgenrandom-lowest +Dim o As PwGenOptions = PwGenOptions.Alphanumeric +Set o.RandomSource = New RaisingRandom +Dim pw As String = "unchanged" + +On Error Resume Next +pw = PwGen.Generate(o) +Debug.Print Err.Number ' 5 +Debug.Print Err.Source ' RaisingRandom +Debug.Print pw ' unchanged +On Error GoTo 0 + +Debug.Print PwGenError.HasError ' False +``` + +## Methods + +### NextInRange +{: .no_toc } + +Returns a whole number between two bounds, both included. + +Syntax: *source*.**NextInRange** ( *lowerBound*, *upperBound* ) **As Long** + +*lowerBound* +: *required* A **Long**, the smallest number that may be returned. + +*upperBound* +: *required* A **Long**, the largest number that may be returned. It is not below *lowerBound*. + +Returns a **Long** *n* with `lowerBound <= n <= upperBound`. The numbers should be equally likely and independent of one another. [**PwGenCryptoSource**](PwGenCryptoSource) implements the method with the Windows CNG generator. + +A class implements the method with a **Private** function whose declaration names the interface member, as the classes above do. + +## See Also + +- [PwGenCryptoSource](PwGenCryptoSource) class -- the default source, which uses the Windows CNG generator +- [PwGenOptions](PwGenOptions#randomsource) class -- the **RandomSource** property +- [Interface](../../../Core/Interface) statement -- declares an interface +- [Implements](../../../Core/Implements) statement -- implements an interface in a class +- [tsPwGen Package](.) -- the package diff --git a/docs/Reference/Contributed/tsPwGen/index.md b/docs/Reference/Contributed/tsPwGen/index.md new file mode 100644 index 000000000..746d33571 --- /dev/null +++ b/docs/Reference/Contributed/tsPwGen/index.md @@ -0,0 +1,165 @@ +--- +title: tsPwGen Package +parent: Contributed Packages +has_toc: false +exclude_from_docs: + - PwGenCrypto + - PwGenEngine + - PwGenErrorStore + - PwGenWords + - PwGenWordsDe + - PwGenWordsEn + - Tests + - TestsCore + - TestsFakeRandom + - TestsRandom + - TestsWordlist +indexed_from: 1.0.8.0 +permalink: /tB/Packages/Contributed/tsPwGen/ +--- + +# tsPwGen Package +{: .no_toc } + +**Author:** thosti. + +The **tsPwGen** contributed package generates passwords, PINs, hexadecimal tokens and passphrases, and computes the entropy of a recipe before anything is generated. The package describes itself as "Passwords you can measure before you generate." This section documents version 1.0.8.0, published under the MIT licence. + +A recipe is a [**PwGenOptions**](PwGenOptions) object. Its five presets, **Alphanumeric**, **Complex**, **Passphrase**, **Pin** and **Hex**, cover the common cases, and its properties turn a preset into any other recipe. [**PwGen.Generate**](PwGen#generate) draws a result from a recipe. [**PwGen.Entropy**](PwGen#entropy) reports the entropy of a recipe in bits without drawing anything. Entropy is the base-2 logarithm of the number of results a recipe can produce, when each result is equally likely: a recipe with 128 bits can produce about 2^128 different results. + +```tb check_build check_run +Dim o As PwGenOptions = PwGenOptions.Complex +o.Length = 20 +Debug.Print CLng(PwGen.Entropy(o)) ' 125 + +Dim password As String +If PwGen.TryGenerate(password, o) Then + Debug.Print Len(password) ' 20 +End If + +Dim phrase As PwGenOptions = PwGenOptions.Passphrase +Debug.Print CLng(PwGen.Entropy(phrase)) ' 65 +Debug.Print UBound(Split(PwGen.Generate(phrase), "-")) + 1 ' 5 +``` + +## The presets + +Each preset is a read-only property of **PwGenOptions** that returns a new recipe. The values below are the ones the presets start with; every property of the new recipe can be changed. + +| Preset | Result | Entropy | +|:-------|:-------|--------:| +| **Alphanumeric** | 16 characters from the letters and digits, without the five look-alikes `0`, `O`, `1`, `l` and `I`, so from 57 characters. The password has at least one uppercase letter, one lowercase letter and one digit. | 93.3 bits | +| **Complex** | Like **Alphanumeric**, with the 20 special characters `!#$%&()+,-.;=@[]_{}~` added, so from 77 characters. The password also has at least one special character. | 100.3 bits | +| **Passphrase** | Five words from the English word list, separated by `-`. | 65 bits | +| **Pin** | 6 digits. The look-alikes are kept, so all ten digits are used. | 19.9 bits | +| **Hex** | 32 characters from `0123456789abcdef`. | 128 bits | + +Every function of the package that takes a recipe also accepts **Nothing**, which means a new **Alphanumeric** recipe. + +## Entropy + +[**PwGen.Entropy**](PwGen#entropy) computes the entropy of a recipe from its settings alone: + +- In random mode, `Length * log2(p)`, where *p* is the number of different characters the password is drawn from. +- In wordlist mode, `WordCount * log2(n)`, where *n* is the number of words in the list. When the words of a passphrase must be different ([**HasUniqueWords**](PwGenOptions#hasuniquewords)), the sum of `log2(n)`, `log2(n - 1)`, `log2(n - 2)` and so on, one term for each word. + +The separator and the capital letters of a passphrase add nothing. The constraints of random mode, [**MinPerClass**](PwGenOptions#minperclass), [**StartsWithLetter**](PwGenOptions#startswithletter) and [**HasNoConsecutiveRepeats**](PwGenOptions#hasnoconsecutiverepeats), do not lower the figure, so the entropy of a password drawn under them can be smaller than the figure. + +## Errors + +A recipe that cannot be used does not raise a run-time error. [**PwGen.Generate**](PwGen#generate) returns an empty string, [**PwGen.Entropy**](PwGen#entropy) returns 0, and the last error is set. The predeclared object [**PwGenError**](PwGenError) reports it: **HasError** is **True**, **Code** is a short name such as `InvalidLength`, and **Message** and **Origin** describe the error and the function that found it. Each function of [**PwGen**](PwGen) clears the last error when it starts, so the last error always belongs to the latest call. [**PwGen.TryGenerate**](PwGen#trygenerate) returns **False** instead of an empty string when it fails. + +The package raises a run-time error in one case: assigning a property on the predeclared object **PwGenOptions** itself, which is a factory and not a recipe. The error is 5, *Invalid procedure call or argument*. + +## Random sources + +Characters and words are drawn with a **PwGenRandom** object. A recipe whose [**RandomSource**](PwGenOptions#randomsource) is **Nothing** uses [**PwGenCryptoSource**](PwGenCryptoSource), which calls the Windows CNG function **BCryptGenRandom**. Any object that implements the [**PwGenRandom**](PwGenRandom) interface can be assigned to **RandomSource** instead. A source that returns fixed values makes the result predictable, which the samples on these pages use. + +## Word lists + +The package has two built-in word lists, English and German, of 8192 words each. No word appears twice in a list, and 8192 is 2^13, so each word of a passphrase from a built-in list adds 13 bits. Every word of a list is written with the letters `a` to `z` only: the German list spells the umlauts and the sharp s as `ae`, `oe`, `ue` and `ss`. + +```tb check_build check_run +Debug.Print PwGen.BuiltInWordCount(pgwEnglish) ' 8192 +Debug.Print PwGen.BuiltInWord(pgwEnglish, 0) ' abandon +Debug.Print PwGen.BuiltInWord(pgwEnglish, 8191) ' zurich +Debug.Print PwGen.BuiltInWord(pgwGerman, 0) ' aal +Debug.Print PwGen.BuiltInWord(pgwGerman, 8191) ' zypresse +``` + +A recipe can use its own list instead, through [**CustomWords**](PwGenOptions#customwords). + +## Classes and modules + +- [PwGen](PwGen) -- the module that generates passwords and passphrases, reports entropy and reads the built-in word lists + - [BuiltInWord](PwGen#builtinword) -- returns one word of a built-in list + - [BuiltInWordCount](PwGen#builtinwordcount) -- returns the number of words in a built-in list + - [Entropy](PwGen#entropy) -- returns the entropy of a recipe in bits, without generating a result + - [Generate](PwGen#generate) -- generates a password or a passphrase from a recipe + - [TryGenerate](PwGen#trygenerate) -- generates into a variable and returns whether it succeeded + - [ePwGenMode](PwGen#epwgenmode) -- the two generation modes + - [ePwGenWordlist](PwGen#epwgenwordlist) -- the two built-in word lists + - [PWGEN_ALPHABET_HEX](PwGen#pwgen_alphabet_hex), [PWGEN_DEFAULT_AMBIGUOUS_CHARS](PwGen#pwgen_default_ambiguous_chars), [PWGEN_DEFAULT_SPECIAL_CHARS](PwGen#pwgen_default_special_chars), [PWGEN_MAX_LENGTH](PwGen#pwgen_max_length), [PWGEN_MAX_WORD_COUNT](PwGen#pwgen_max_word_count) -- the constants +- [PwGenOptions](PwGenOptions) -- a recipe: the mode, the characters or words to draw from, and the constraints; its predeclared object creates the five presets + - [Alphanumeric](PwGenOptions#alphanumeric), [Complex](PwGenOptions#complex), [Hex](PwGenOptions#hex), [Passphrase](PwGenOptions#passphrase), [Pin](PwGenOptions#pin) -- the presets + - [Clone](PwGenOptions#clone) -- returns an independent copy of a recipe + - the 22 properties: [Alphabet](PwGenOptions#alphabet), [AmbiguousChars](PwGenOptions#ambiguouschars), [Capitalize](PwGenOptions#capitalize), [CustomWords](PwGenOptions#customwords), [ExcludeChars](PwGenOptions#excludechars), [HasDigits](PwGenOptions#hasdigits), [HasLowerCase](PwGenOptions#haslowercase), [HasNoConsecutiveRepeats](PwGenOptions#hasnoconsecutiverepeats), [HasSpecialChars](PwGenOptions#hasspecialchars), [HasUniqueWords](PwGenOptions#hasuniquewords), [HasUpperCase](PwGenOptions#hasuppercase), [IncludeChars](PwGenOptions#includechars), [IsAmbiguousExcluded](PwGenOptions#isambiguousexcluded), [Length](PwGenOptions#length), [MinPerClass](PwGenOptions#minperclass), [Mode](PwGenOptions#mode), [RandomSource](PwGenOptions#randomsource), [SpecialChars](PwGenOptions#specialchars), [StartsWithLetter](PwGenOptions#startswithletter), [WordCount](PwGenOptions#wordcount), [Wordlist](PwGenOptions#wordlist), [WordSeparator](PwGenOptions#wordseparator) +- [PwGenError](PwGenError) -- the predeclared object that reports the last error; it also holds the table of error codes + - [Clear](PwGenError#clear), [Code](PwGenError#code), [HasError](PwGenError#haserror), [Message](PwGenError#message), [Origin](PwGenError#origin), [Snapshot](PwGenError#snapshot), [ToString](PwGenError#tostring) -- its members + - [tPwGenError](PwGenError#tpwgenerror) -- the type that **Snapshot** returns +- [PwGenRandom](PwGenRandom) -- the interface of a random source + - [NextInRange](PwGenRandom#nextinrange) -- returns a whole number between two bounds +- [PwGenCryptoSource](PwGenCryptoSource) -- the default random source, which uses the Windows CNG generator + +## Test code + +The package also contains its own test code, which has no pages: the modules **Tests**, **TestsCore**, **TestsRandom** and **TestsWordlist**, and the class **TestsFakeRandom**. **Tests.RunAll** runs three areas and prints the result of each check with **Debug.Print**, so the lines appear in the DEBUG CONSOLE of the IDE and nowhere else. The **Core** area (**TestsCore**) checks the constants, the presets, the guard on the predeclared **PwGenOptions** object, **Clone**, the last error and its snapshot, **TryGenerate** and an invalid mode. The **Random** area (**TestsRandom**) checks the entropy formulas, the recipes, **Alphabet**, **IncludeChars** and **ExcludeChars**, **MinPerClass**, **StartsWithLetter**, **HasNoConsecutiveRepeats**, results drawn with a fixed random source, and the error codes of random mode. The **Wordlist** area (**TestsWordlist**) checks the built-in lists, the default passphrase, **CustomWords**, **HasUniqueWords**, the separator and **Capitalize**, the entropy of passphrases and the error codes of wordlist mode. Each check prints `OK`, `FAIL` or `SKIP`, a failed check does not stop the run, and the last lines give the counts and the elapsed time and say that the tests passed or failed. In version 1.0.8.0 the run has 349 checks that pass, none that fail and 3 that are skipped, and it ends with a line that says the tests passed. The three skipped checks stand for the error codes `EmptyCharset`, `EmptyWordlist` and `InvalidRange`, which no recipe sets. + +**TestsFakeRandom** implements [**PwGenRandom**](PwGenRandom) for the tests. It returns the lower bound of each interval it is given; after `SetAlwaysUpper True` it returns the upper bound, and after `SetOutOfRange True` it returns a value one above the upper bound, which makes the generator report `RngFailed`. + +## Internal modules + +Six more modules are part of the package's implementation and have no pages: **PwGenEngine** (the generator), **PwGenCrypto** (the CNG call and the shared default random source), **PwGenErrorStore** (the storage behind [**PwGenError**](PwGenError)), **PwGenWords** (access to the word lists), and **PwGenWordsEn** and **PwGenWordsDe** (the data of the two lists). The package describes each of them as not part of its public API. None of them is declared **Private**, so a project that references the package can name them. + +```tb check_build check_run +Debug.Print PwGenWords.CountOf(pgwEnglish, "x") ' 8192 +``` + +The names are visible to the whole project: the **Generate** and **Entropy** functions of **PwGenEngine** make an unqualified call of the functions of [**PwGen**](PwGen) ambiguous (see [Known problems](#known-problems-in-version-1080)). + +## Known problems in version 1.0.8.0 + +> [!WARNING] +> Called directly, the **NextInRange** method of [**PwGenCryptoSource**](PwGenCryptoSource#errors) returns 0, with no error of its own, while an earlier failure of the package is still the last error. Clear the last error first. The functions of [**PwGen**](PwGen) are not affected. + +> [!NOTE] +> The package has four more problems. None of them returns a wrong password. +> +> - **`HasNoConsecutiveRepeats` fails for small pools.** A recipe with [**HasNoConsecutiveRepeats**](PwGenOptions#hasnoconsecutiverepeats) returns an empty string with the error `CannotAvoidRepeats` in about one call of four for the pool `AB` and a **Length** of 3, although valid passwords exist. +> - **An unqualified call is ambiguous.** `Generate()` and `Entropy()` written without `PwGen.` fail with TB5073, because the internal module **PwGenEngine** declares functions with the same names. See [**PwGen**](PwGen). +> - **`Tests.SetContinueOnFail` has no effect.** A failed check never stops **Tests.RunAll**, whatever the setting. +> - **Two error codes are never set.** The package declares `EmptyCharset` and `EmptyWordlist`, but no recipe produces them. See [**PwGenError**](PwGenError#error-codes). + +The first check of this sample fails on purpose. The second check still runs after `SetContinueOnFail False`: + +```tb check_build check_run +Tests.BeginRun +Tests.SetContinueOnFail False +Tests.CheckTrue "first", False +Tests.CheckTrue "second", True +Debug.Print Tests.FailCount() +' Output: +' === tsPwGen tests ========================================== +' FAIL first +' expected: True +' actual: False +' OK second +' 1 +``` + +## See Also + +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project +- [Interface](../../../Core/Interface) statement -- declares an interface such as **PwGenRandom** +- [Implements](../../../Core/Implements) statement -- implements an interface in a class +- [Like](../../../Core/Like) operator -- compares a string with a pattern, as the samples on these pages do to test the characters of a result diff --git a/docs/_book.yml b/docs/_book.yml index 394799122..05878f27f 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -558,6 +558,13 @@ parts: landing_is_target: true outline_closed: true + - title: tsPwGen Package + subtitle: Passwords, PINs and passphrases, with their entropy known before they are generated + landing_page: /tB/Packages/Contributed/tsPwGen/ + page: /tB/Packages/Contributed/tsPwGen/ + landing_is_target: true + outline_closed: true + - title: TwinTimerPackage Package subtitle: A Win32 timer class that reports the elapsed time landing_page: /tB/Packages/Contributed/TwinTimerPackage/ diff --git a/indexer/snapshots/contributed/tsPwGen/api.json b/indexer/snapshots/contributed/tsPwGen/api.json new file mode 100644 index 000000000..17e47587c --- /dev/null +++ b/indexer/snapshots/contributed/tsPwGen/api.json @@ -0,0 +1,457 @@ +{ + "format": 3, + "package": "tsPwGen", + "project": "tsPwGen", + "version": "1.0.8.0", + "types": [ + { + "name": "PwGen", + "kind": "module", + "vis": "public", + "file": "PwGen.twin", + "signature": "Public Module PwGen", + "description": "tsPWGen facade: generate passwords and passphrases, report entropy.", + "members": [ + {"name":"BuiltInWord","kind":"function","vis":"public","signature":"Public Function BuiltInWord(ByVal list As ePwGenWordlist, ByVal index As Long) As String","returnType":"String","description":"One word from a built-in list. Index is 0-based."}, + {"name":"BuiltInWordCount","kind":"function","vis":"public","signature":"Public Function BuiltInWordCount(ByVal list As ePwGenWordlist) As Long","returnType":"Long","description":"Word count of a built-in list. Valid indexes are `0` ... `Count-1`."}, + {"name":"Entropy","kind":"function","vis":"public","signature":"Public Function Entropy(Optional ByVal options As PwGenOptions = Nothing) As Double","returnType":"Double","description":"Entropy in bits for `options`, without drawing a password.\nOn error returns `0` and sets `PwGenError`."}, + {"name":"Generate","kind":"function","vis":"public","signature":"Public Function Generate(Optional ByVal options As PwGenOptions = Nothing) As String","returnType":"String","description":"Generates a password from `options`.\n`Nothing` uses `PwGenOptions.Alphanumeric` defaults. On error returns an empty string and sets `PwGenError`.\n\n### Syntax\n`pw = PwGen.Generate(opts)`"}, + {"name":"PWGEN_ALPHABET_HEX","kind":"const","vis":"public","signature":"Public Const PWGEN_ALPHABET_HEX As String = \"0123456789abcdef\""}, + {"name":"PWGEN_DEFAULT_AMBIGUOUS_CHARS","kind":"const","vis":"public","signature":"Public Const PWGEN_DEFAULT_AMBIGUOUS_CHARS As String = \"0O1lI\""}, + {"name":"PWGEN_DEFAULT_SPECIAL_CHARS","kind":"const","vis":"public","signature":"Public Const PWGEN_DEFAULT_SPECIAL_CHARS As String = \"!#$%&()+,-.;=@[]_{}~\""}, + {"name":"PWGEN_MAX_LENGTH","kind":"const","vis":"public","signature":"Public Const PWGEN_MAX_LENGTH As Long = 4096"}, + {"name":"PWGEN_MAX_WORD_COUNT","kind":"const","vis":"public","signature":"Public Const PWGEN_MAX_WORD_COUNT As Long = 256"}, + {"name":"TryGenerate","kind":"function","vis":"public","signature":"Public Function TryGenerate( ByRef password As String, Optional ByVal options As PwGenOptions = Nothing) As Boolean","returnType":"Boolean","description":"Generate into `password`. `False` on error; then `password` is empty."} + ] + }, + { + "name": "ePwGenMode", + "kind": "enum", + "in": "PwGen", + "vis": "public", + "file": "PwGen.twin", + "signature": "Public Enum ePwGenMode", + "description": "Generation mode: random character pool or word list.", + "values": [ + {"name":"pgmRandom"}, + {"name":"pgmWordlist"} + ] + }, + { + "name": "ePwGenWordlist", + "kind": "enum", + "in": "PwGen", + "vis": "public", + "file": "PwGen.twin", + "signature": "Public Enum ePwGenWordlist", + "description": "Built-in word list. A non-empty `CustomWords` array overrides this.", + "values": [ + {"name":"pgwEnglish"}, + {"name":"pgwGerman"} + ] + }, + { + "name": "PwGenCrypto", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenCrypto.twin", + "signature": "Module PwGenCrypto", + "description": "Internal CNG random source. Not part of the public API.", + "members": [ + {"name":"BCRYPT_USE_SYSTEM_PREFERRED_RNG","kind":"const","vis":"private","signature":"Private Const BCRYPT_USE_SYSTEM_PREFERRED_RNG As Long = &H2"}, + {"name":"BCryptGenRandom","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function BCryptGenRandom Lib \"bcrypt.dll\" ( ByVal hAlgorithm As LongPtr, ByVal pbBuffer As LongPtr, ByVal cbBuffer As Long, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"FillBytes","kind":"function","vis":"public","signature":"Public Function FillBytes(ByRef buffer() As Byte) As Boolean","returnType":"Boolean","description":"Fills `buffer` with CNG random bytes. True on success.\nAn empty or unallocated `buffer` succeeds trivially, without calling BCrypt."}, + {"name":"Rng","kind":"function","vis":"public","signature":"Public Function Rng() As PwGenRandom","returnType":"PwGenRandom","description":"Shared internal CNG random source."}, + {"name":"STATUS_SUCCESS","kind":"const","vis":"private","signature":"Private Const STATUS_SUCCESS As Long = 0"}, + {"name":"mRng","kind":"field","vis":"private","signature":"Private mRng As PwGenCryptoSource"} + ] + }, + { + "name": "PwGenCryptoSource", + "kind": "class", + "vis": "public", + "file": "Internal/PwGenCryptoSource.twin", + "signature": "Class PwGenCryptoSource", + "implements": ["PwGenRandom"], + "description": "Internal CNG implementation of PwGenRandom.", + "members": [ + {"name":"NextInRange","kind":"function","vis":"private","signature":"Private Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long Implements PwGenRandom.NextInRange","returnType":"Long","description":"Uniform integer with both bounds inclusive.\nSets `InvalidRange` when `lowerBound > upperBound`, or `RngFailed` on CNG error."}, + {"name":"NextUnsigned32","kind":"function","vis":"private","signature":"Private Function NextUnsigned32() As LongLong","returnType":"LongLong","description":"Uniform value in 0..2^32-1 from four random bytes. Sets `RngFailed` and returns `0` on CNG error."} + ] + }, + { + "name": "PwGenEngine", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenEngine.twin", + "signature": "Module PwGenEngine", + "description": "Internal generator. Not part of the public API.", + "members": [ + {"name":"ArrayLen","kind":"function","vis":"private","signature":"Private Function ArrayLen(ByRef a() As String) As Long","returnType":"Long","description":"Element count of a string array; unallocated is 0."}, + {"name":"BuildPool","kind":"function","vis":"private","signature":"Private Function BuildPool( ByVal opts As PwGenOptions, ByRef pool As String, ByRef classPools() As String, ByRef classCount As Long, ByVal origin As String) As Boolean","returnType":"Boolean","description":"Builds the final pool and the filtered class pools."}, + {"name":"DIGIT_CHARS","kind":"const","vis":"private","signature":"Private Const DIGIT_CHARS As String = \"0123456789\""}, + {"name":"Draw","kind":"function","vis":"private","signature":"Private Function Draw( ByVal rng As PwGenRandom, ByVal lo As Long, ByVal hi As Long, ByVal origin As String) As Long","returnType":"Long","description":"Draws an integer and checks it lies in the closed interval."}, + {"name":"Entropy","kind":"function","vis":"public","signature":"Public Function Entropy(ByVal options As PwGenOptions, ByVal origin As String) As Double","returnType":"Double","description":"Entropy in bits for the recipe, without drawing a password."}, + {"name":"EntropyRandom","kind":"function","vis":"private","signature":"Private Function EntropyRandom(ByVal opts As PwGenOptions, ByVal origin As String) As Double","returnType":"Double","description":"Entropy in bits for random mode."}, + {"name":"EntropyWordlist","kind":"function","vis":"private","signature":"Private Function EntropyWordlist(ByVal opts As PwGenOptions, ByVal origin As String) As Double","returnType":"Double","description":"Entropy in bits for wordlist mode."}, + {"name":"Fail","kind":"sub","vis":"private","signature":"Private Sub Fail(ByVal code As String, ByVal message As String, ByVal origin As String)","description":"Sets the last-error."}, + {"name":"FilterClass","kind":"function","vis":"private","signature":"Private Function FilterClass(ByVal raw As String, ByVal opts As PwGenOptions) As String","returnType":"String","description":"Removes exclude/ambiguous characters from one class, and deduplicates.\nDuplicate characters in `raw` collapse to one, shrinking the pool and its entropy."}, + {"name":"FormatWord","kind":"function","vis":"private","signature":"Private Function FormatWord(ByVal word As String, ByVal capitalize As Boolean) As String","returnType":"String","description":"Title-case or unchanged."}, + {"name":"Generate","kind":"function","vis":"public","signature":"Public Function Generate(ByVal options As PwGenOptions, ByVal origin As String) As String","returnType":"String","description":"Generates a password from the recipe. Sets last-error on failure."}, + {"name":"GenerateRandom","kind":"function","vis":"private","signature":"Private Function GenerateRandom( ByVal opts As PwGenOptions, ByVal rng As PwGenRandom, ByVal origin As String) As String","returnType":"String","description":"Generates a random-mode password."}, + {"name":"GenerateWordlist","kind":"function","vis":"private","signature":"Private Function GenerateWordlist( ByVal opts As PwGenOptions, ByVal rng As PwGenRandom, ByVal origin As String) As String","returnType":"String","description":"Generates a passphrase from the effective word list."}, + {"name":"HasConsecutive","kind":"function","vis":"private","signature":"Private Function HasConsecutive(ByRef chars() As String, ByVal n As Long) As Boolean","returnType":"Boolean","description":"True when two identical characters are adjacent."}, + {"name":"IsAsciiLetter","kind":"function","vis":"private","signature":"Private Function IsAsciiLetter(ByVal ch As String) As Boolean","returnType":"Boolean","description":"True when ch is in A-Z or a-z."}, + {"name":"JoinChars","kind":"function","vis":"private","signature":"Private Function JoinChars(ByRef chars() As String, ByVal n As Long) As String","returnType":"String","description":"Joins chars into one string."}, + {"name":"LOWER_CHARS","kind":"const","vis":"private","signature":"Private Const LOWER_CHARS As String = \"abcdefghijklmnopqrstuvwxyz\""}, + {"name":"LettersOf","kind":"function","vis":"private","signature":"Private Function LettersOf(ByVal pool As String) As String","returnType":"String","description":"ASCII letters in the pool."}, + {"name":"Log2","kind":"function","vis":"private","signature":"Private Function Log2(ByVal value As Double) As Double","returnType":"Double","description":"log2(value)."}, + {"name":"NextFreeSlot","kind":"function","vis":"private","signature":"Private Function NextFreeSlot( ByRef filled() As Boolean, ByVal n As Long, ByVal rng As PwGenRandom, ByVal origin As String) As Long","returnType":"Long","description":"Random unfilled index in filled()."}, + {"name":"PickChar","kind":"function","vis":"private","signature":"Private Function PickChar(ByVal pool As String, ByVal rng As PwGenRandom, ByVal origin As String) As String","returnType":"String","description":"Picks one character from a non-empty pool."}, + {"name":"RepairRepeats","kind":"function","vis":"private","signature":"Private Function RepairRepeats( ByRef chars() As String, ByVal n As Long, ByVal lockFirst As Boolean, ByVal rng As PwGenRandom, ByVal origin As String) As Boolean","returnType":"Boolean","description":"Removes adjacent repeats by reshuffling."}, + {"name":"ResolveWords","kind":"function","vis":"private","signature":"Private Function ResolveWords( ByVal opts As PwGenOptions, ByRef words() As String, ByRef n As Long, ByVal origin As String) As Boolean","returnType":"Boolean","description":"Resolves the effective word list and checks the contract."}, + {"name":"Resolved","kind":"function","vis":"private","signature":"Private Function Resolved(ByVal options As PwGenOptions) As PwGenOptions","returnType":"PwGenOptions","description":"Nothing becomes a new Alphanumeric instance."}, + {"name":"ResolvedRng","kind":"function","vis":"private","signature":"Private Function ResolvedRng(ByVal opts As PwGenOptions) As PwGenRandom","returnType":"PwGenRandom","description":"Nothing becomes the internal CNG source."}, + {"name":"Shuffle","kind":"sub","vis":"private","signature":"Private Sub Shuffle( ByRef chars() As String, ByVal fromIndex As Long, ByVal toIndex As Long, ByVal rng As PwGenRandom, ByVal origin As String)","description":"Fisher-Yates on chars(fromIndex..toIndex)."}, + {"name":"UPPER_CHARS","kind":"const","vis":"private","signature":"Private Const UPPER_CHARS As String = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\""}, + {"name":"UniqueChars","kind":"function","vis":"private","signature":"Private Function UniqueChars(ByVal src As String) As String","returnType":"String","description":"Unique characters in first-seen order."} + ] + }, + { + "name": "PwGenError", + "kind": "class", + "vis": "public", + "file": "PwGenError.twin", + "signature": "Public Class PwGenError", + "attrs": [{"name":"PredeclaredId"},{"name":"COMCreatable","args":"False"}], + "description": "Last error of the PwGen facade.\nThe predeclared instance `PwGenError` tracks the current error. `Snapshot` copies it into `tPwGenError` before the next operation overwrites it.\n\n### Notes\n- There is no `SetError` on this class.\n- Getters read the last-error store. A `tPwGenError` value holds its own copy.\n\n### Example\n```basic\npw = PwGen.Generate(opts)\nIf PwGenError.HasError Then Debug.Print PwGenError.ToString()\n```", + "members": [ + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()","description":"Clears this error.\nClears the last-error store.\n\n### Syntax\n`PwGenError.Clear`"}, + {"name":"Code","kind":"property","vis":"public","signature":"Public Property Get Code() As String","returnType":"String","accessor":"Get","description":"Machine-readable error code.\n\n### Return value\nA code such as `EmptyCharset`, or an empty string when `HasError` is false."}, + {"name":"HasError","kind":"property","vis":"public","signature":"Public Property Get HasError() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when an error is present.\nThe predeclared instance reads the last-error store.\n\n### Syntax\n`ok = Not PwGenError.HasError`\n\n### Return value\n`True` if this instance currently holds an error; otherwise `False`."}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Get Message() As String","returnType":"String","accessor":"Get","description":"Human-readable message for this error.\n\n### Return value\nThe message text, or an empty string when `HasError` is false."}, + {"name":"New","kind":"sub","vis":"private","signature":"Private Sub New()","description":"Blocks external construction. The predeclared instance is the only instance."}, + {"name":"Origin","kind":"property","vis":"public","signature":"Public Property Get Origin() As String","returnType":"String","accessor":"Get","description":"Origin of the operation that produced this error.\n\n### Return value\n`CurrentComponentName.CurrentProcedureName` of the public entry, e.g. `PwGen.Generate`, or an empty string when `HasError` is false."}, + {"name":"Snapshot","kind":"function","vis":"public","signature":"Public Function Snapshot() As tPwGenError","returnType":"tPwGenError","description":"Snapshot of the current error.\nThe next facade call does not change this value.\n\n### Syntax\n`snap = PwGenError.Snapshot()`\n\n### Return value\nA `tPwGenError` with its own fields, independent of the last-error store."}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String","description":"One-line summary for logs.\n\n### Syntax\n`text = PwGenError.ToString()`\n\n### Return value\nEmpty when `HasError` is false; otherwise `Origin: Code - Message`."} + ] + }, + { + "name": "tPwGenError", + "kind": "type", + "in": "PwGenError", + "vis": "public", + "file": "PwGenError.twin", + "signature": "Public Type tPwGenError", + "fields": [ + {"name":"HasError","vis":"public","signature":"HasError As Boolean"}, + {"name":"Code","vis":"public","signature":"Code As String"}, + {"name":"Message","vis":"public","signature":"Message As String"}, + {"name":"Origin","vis":"public","signature":"Origin As String"} + ], + "members": [] + }, + { + "name": "PwGenErrorStore", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenErrorStore.twin", + "signature": "Module PwGenErrorStore", + "description": "Internal last-error store. Not part of the public API.", + "members": [ + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()","description":"Clears the last-error."}, + {"name":"Code","kind":"function","vis":"public","signature":"Public Function Code() As String","returnType":"String","description":"Machine-readable error code, or empty."}, + {"name":"HasError","kind":"function","vis":"public","signature":"Public Function HasError() As Boolean","returnType":"Boolean","description":"True when a last-error is set."}, + {"name":"Message","kind":"function","vis":"public","signature":"Public Function Message() As String","returnType":"String","description":"Human-readable message, or empty."}, + {"name":"Origin","kind":"function","vis":"public","signature":"Public Function Origin() As String","returnType":"String","description":"Origin of the operation, e.g. PwGen.Generate."}, + {"name":"SetError","kind":"sub","vis":"public","signature":"Public Sub SetError(ByVal code As String, ByVal message As String, ByVal origin As String)","description":"Sets the last-error."}, + {"name":"mCode","kind":"field","vis":"private","signature":"Private mCode As String"}, + {"name":"mHasError","kind":"field","vis":"private","signature":"Private mHasError As Boolean"}, + {"name":"mMessage","kind":"field","vis":"private","signature":"Private mMessage As String"}, + {"name":"mOrigin","kind":"field","vis":"private","signature":"Private mOrigin As String"} + ] + }, + { + "name": "PwGenOptions", + "kind": "class", + "vis": "public", + "file": "PwGenOptions.twin", + "signature": "Public Class PwGenOptions", + "attrs": [{"name":"PredeclaredId"},{"name":"COMCreatable","args":"False"}], + "description": "Per-call options for password generation.\nThe predeclared instance is a factory (`Alphanumeric`, `Complex`, `Passphrase`, `Pin`, `Hex`), not shared mutable state.\n\n### Notes\n- `PwGenOptions.Alphanumeric` starts from the everyday random defaults.\n- Do not set properties on `PwGenOptions` itself; use a preset or `Clone` first.\n- Validation runs in `PwGen.Generate` / `Entropy`, not in setters.\n\n### Example\n```basic\nDim o As PwGenOptions = PwGenOptions.Complex\no.Length = 20\nDebug.Print PwGen.Generate(o)\n```", + "members": [ + {"name":"Alphabet","kind":"property","vis":"public","signature":"Public Property Get Alphabet() As String","returnType":"String","accessor":"Get","description":"When non-empty, this is the entire pool.\nClass, include/exclude, ambiguous, and `MinPerClass` settings do not apply. `StartsWithLetter` and `HasNoConsecutiveRepeats` still apply, to this pool."}, + {"name":"Alphabet","kind":"property","vis":"public","signature":"Public Property Let Alphabet(ByVal value As String)","accessor":"Let"}, + {"name":"Alphanumeric","kind":"property","vis":"public","signature":"Public Property Get Alphanumeric() As PwGenOptions","returnType":"PwGenOptions","accessor":"Get","description":"New options with alphanumeric defaults.\nEach call returns a fresh mutable instance.\n\n### Syntax\n`opts = PwGenOptions.Alphanumeric`\n\n### Return value\nA new `PwGenOptions` (length 16, upper/lower/digits, lookalikes out)."}, + {"name":"AmbiguousChars","kind":"property","vis":"public","signature":"Public Property Get AmbiguousChars() As String","returnType":"String","accessor":"Get","description":"Ambiguous-character set. Default `PwGen.PWGEN_DEFAULT_AMBIGUOUS_CHARS` (`0O1lI`). Ignored when `Alphabet` is set."}, + {"name":"AmbiguousChars","kind":"property","vis":"public","signature":"Public Property Let AmbiguousChars(ByVal value As String)","accessor":"Let"}, + {"name":"ApplyAlphanumericDefaults","kind":"sub","vis":"private","signature":"Private Sub ApplyAlphanumericDefaults(ByVal o As PwGenOptions)","description":"Applies the alphanumeric defaults to a fresh instance."}, + {"name":"Capitalize","kind":"property","vis":"public","signature":"Public Property Get Capitalize() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when the first character of each word is uppercased. Default `False`. Deterministic; no extra entropy."}, + {"name":"Capitalize","kind":"property","vis":"public","signature":"Public Property Let Capitalize(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As PwGenOptions","returnType":"PwGenOptions","description":"Independent copy of this instance.\nIncludes the `RandomSource` reference.\n\n### Syntax\n`copy = opts.Clone()`\n\n### Return value\nA new `PwGenOptions` with the same property values."}, + {"name":"Complex","kind":"property","vis":"public","signature":"Public Property Get Complex() As PwGenOptions","returnType":"PwGenOptions","accessor":"Get","description":"New options like `Alphanumeric`, with special characters enabled.\n\n### Syntax\n`opts = PwGenOptions.Complex`\n\n### Return value\nA new `PwGenOptions` with `HasSpecialChars = True`."}, + {"name":"CustomWords","kind":"property","vis":"public","signature":"Public Property Get CustomWords() As String()","returnType":"String()","accessor":"Get","description":"Custom word list. A non-empty array overrides `Wordlist`."}, + {"name":"CustomWords","kind":"property","vis":"public","signature":"Public Property Let CustomWords(ByRef value() As String)","accessor":"Let"}, + {"name":"ExcludeChars","kind":"property","vis":"public","signature":"Public Property Get ExcludeChars() As String","returnType":"String","accessor":"Get","description":"Characters removed from the class union. Ignored when `Alphabet` is set."}, + {"name":"ExcludeChars","kind":"property","vis":"public","signature":"Public Property Let ExcludeChars(ByVal value As String)","accessor":"Let"}, + {"name":"GuardFactory","kind":"sub","vis":"private","signature":"Private Sub GuardFactory()","description":"Raises an error when called on the predeclared instance, since it is a factory, not a settable recipe."}, + {"name":"HasDigits","kind":"property","vis":"public","signature":"Public Property Get HasDigits() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when `0-9` is part of the pool. Default `True`. Ignored when `Alphabet` is set."}, + {"name":"HasDigits","kind":"property","vis":"public","signature":"Public Property Let HasDigits(ByVal value As Boolean)","accessor":"Let"}, + {"name":"HasLowerCase","kind":"property","vis":"public","signature":"Public Property Get HasLowerCase() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when `a-z` is part of the pool. Default `True`. Ignored when `Alphabet` is set."}, + {"name":"HasLowerCase","kind":"property","vis":"public","signature":"Public Property Let HasLowerCase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"HasNoConsecutiveRepeats","kind":"property","vis":"public","signature":"Public Property Get HasNoConsecutiveRepeats() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when no two identical characters may be adjacent. Default `False`."}, + {"name":"HasNoConsecutiveRepeats","kind":"property","vis":"public","signature":"Public Property Let HasNoConsecutiveRepeats(ByVal value As Boolean)","accessor":"Let"}, + {"name":"HasSpecialChars","kind":"property","vis":"public","signature":"Public Property Get HasSpecialChars() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when `SpecialChars` is part of the pool. Default `False`. Ignored when `Alphabet` is set."}, + {"name":"HasSpecialChars","kind":"property","vis":"public","signature":"Public Property Let HasSpecialChars(ByVal value As Boolean)","accessor":"Let"}, + {"name":"HasUniqueWords","kind":"property","vis":"public","signature":"Public Property Get HasUniqueWords() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when words in one passphrase are unique. Default `False`."}, + {"name":"HasUniqueWords","kind":"property","vis":"public","signature":"Public Property Let HasUniqueWords(ByVal value As Boolean)","accessor":"Let"}, + {"name":"HasUpperCase","kind":"property","vis":"public","signature":"Public Property Get HasUpperCase() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when `A-Z` is part of the pool. Default `True`. Ignored when `Alphabet` is set."}, + {"name":"HasUpperCase","kind":"property","vis":"public","signature":"Public Property Let HasUpperCase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Hex","kind":"property","vis":"public","signature":"Public Property Get Hex() As PwGenOptions","returnType":"PwGenOptions","accessor":"Get","description":"New options for a 32-character lowercase hex token.\n\n### Syntax\n`opts = PwGenOptions.Hex`\n\n### Return value\nA new `PwGenOptions` with `Alphabet = PwGen.PWGEN_ALPHABET_HEX`, length 32."}, + {"name":"IncludeChars","kind":"property","vis":"public","signature":"Public Property Get IncludeChars() As String","returnType":"String","accessor":"Get","description":"Characters added to the class union. Ignored when `Alphabet` is set."}, + {"name":"IncludeChars","kind":"property","vis":"public","signature":"Public Property Let IncludeChars(ByVal value As String)","accessor":"Let"}, + {"name":"IsAmbiguousExcluded","kind":"property","vis":"public","signature":"Public Property Get IsAmbiguousExcluded() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when `AmbiguousChars` are removed from the class union. Default `True`. Ignored when `Alphabet` is set."}, + {"name":"IsAmbiguousExcluded","kind":"property","vis":"public","signature":"Public Property Let IsAmbiguousExcluded(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Length","kind":"property","vis":"public","signature":"Public Property Get Length() As Long","returnType":"Long","accessor":"Get","description":"Password length in random mode. Default `16`."}, + {"name":"Length","kind":"property","vis":"public","signature":"Public Property Let Length(ByVal value As Long)","accessor":"Let"}, + {"name":"MinPerClass","kind":"property","vis":"public","signature":"Public Property Get MinPerClass() As Long","returnType":"Long","accessor":"Get","description":"Minimum characters per enabled class. `0` disables the guarantee. Default `1`. Ignored when `Alphabet` is set."}, + {"name":"MinPerClass","kind":"property","vis":"public","signature":"Public Property Let MinPerClass(ByVal value As Long)","accessor":"Let"}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Get Mode() As PwGen.ePwGenMode","returnType":"PwGen.ePwGenMode","accessor":"Get","description":"Generation mode. Default `pgmRandom`."}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Let Mode(ByVal value As PwGen.ePwGenMode)","accessor":"Let"}, + {"name":"New","kind":"sub","vis":"private","signature":"Private Sub New()","description":"Blocks external construction. Only this class's own factory members create instances."}, + {"name":"Passphrase","kind":"property","vis":"public","signature":"Public Property Get Passphrase() As PwGenOptions","returnType":"PwGenOptions","accessor":"Get","description":"New options for a five-word English passphrase.\n\n### Syntax\n`opts = PwGenOptions.Passphrase`\n\n### Return value\nA new `PwGenOptions` with `Mode = pgmWordlist`."}, + {"name":"Pin","kind":"property","vis":"public","signature":"Public Property Get Pin() As PwGenOptions","returnType":"PwGenOptions","accessor":"Get","description":"New options for a 6-digit PIN. Ambiguous characters stay (`0` is kept).\n\n### Syntax\n`opts = PwGenOptions.Pin`\n\n### Return value\nA new `PwGenOptions`: digits only, length 6, lookalikes kept."}, + {"name":"RandomSource","kind":"property","vis":"public","signature":"Public Property Get RandomSource() As PwGenRandom","returnType":"PwGenRandom","accessor":"Get","description":"RNG for this recipe. `Nothing` (default) uses the built-in CNG source."}, + {"name":"RandomSource","kind":"property","vis":"public","signature":"Public Property Set RandomSource(ByVal value As PwGenRandom)","accessor":"Set"}, + {"name":"SpecialChars","kind":"property","vis":"public","signature":"Public Property Get SpecialChars() As String","returnType":"String","accessor":"Get","description":"Special-character class. Default `PwGen.PWGEN_DEFAULT_SPECIAL_CHARS`. Ignored when `Alphabet` is set."}, + {"name":"SpecialChars","kind":"property","vis":"public","signature":"Public Property Let SpecialChars(ByVal value As String)","accessor":"Let"}, + {"name":"StartsWithLetter","kind":"property","vis":"public","signature":"Public Property Get StartsWithLetter() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when the first character must be in `A-Za-z` from the final pool. Default `False`."}, + {"name":"StartsWithLetter","kind":"property","vis":"public","signature":"Public Property Let StartsWithLetter(ByVal value As Boolean)","accessor":"Let"}, + {"name":"WordCount","kind":"property","vis":"public","signature":"Public Property Get WordCount() As Long","returnType":"Long","accessor":"Get","description":"Word count in wordlist mode. Default `5`."}, + {"name":"WordCount","kind":"property","vis":"public","signature":"Public Property Let WordCount(ByVal value As Long)","accessor":"Let"}, + {"name":"WordSeparator","kind":"property","vis":"public","signature":"Public Property Get WordSeparator() As String","returnType":"String","accessor":"Get","description":"String inserted between words. Default `-`. Does not add entropy."}, + {"name":"WordSeparator","kind":"property","vis":"public","signature":"Public Property Let WordSeparator(ByVal value As String)","accessor":"Let"}, + {"name":"Wordlist","kind":"property","vis":"public","signature":"Public Property Get Wordlist() As PwGen.ePwGenWordlist","returnType":"PwGen.ePwGenWordlist","accessor":"Get","description":"Built-in word list when `CustomWords` is empty. Default `pgwEnglish`."}, + {"name":"Wordlist","kind":"property","vis":"public","signature":"Public Property Let Wordlist(ByVal value As PwGen.ePwGenWordlist)","accessor":"Let"}, + {"name":"mAlphabet","kind":"field","vis":"private","signature":"Private mAlphabet As String"}, + {"name":"mAmbiguousChars","kind":"field","vis":"private","signature":"Private mAmbiguousChars As String"}, + {"name":"mCapitalize","kind":"field","vis":"private","signature":"Private mCapitalize As Boolean"}, + {"name":"mCustomCount","kind":"field","vis":"private","signature":"Private mCustomCount As Long"}, + {"name":"mCustomWords","kind":"field","vis":"private","signature":"Private mCustomWords() As String"}, + {"name":"mExcludeChars","kind":"field","vis":"private","signature":"Private mExcludeChars As String"}, + {"name":"mHasDigits","kind":"field","vis":"private","signature":"Private mHasDigits As Boolean"}, + {"name":"mHasLowerCase","kind":"field","vis":"private","signature":"Private mHasLowerCase As Boolean"}, + {"name":"mHasNoConsecutiveRepeats","kind":"field","vis":"private","signature":"Private mHasNoConsecutiveRepeats As Boolean"}, + {"name":"mHasSpecialChars","kind":"field","vis":"private","signature":"Private mHasSpecialChars As Boolean"}, + {"name":"mHasUniqueWords","kind":"field","vis":"private","signature":"Private mHasUniqueWords As Boolean"}, + {"name":"mHasUpperCase","kind":"field","vis":"private","signature":"Private mHasUpperCase As Boolean"}, + {"name":"mIncludeChars","kind":"field","vis":"private","signature":"Private mIncludeChars As String"}, + {"name":"mIsAmbiguousExcluded","kind":"field","vis":"private","signature":"Private mIsAmbiguousExcluded As Boolean"}, + {"name":"mLength","kind":"field","vis":"private","signature":"Private mLength As Long"}, + {"name":"mMinPerClass","kind":"field","vis":"private","signature":"Private mMinPerClass As Long"}, + {"name":"mMode","kind":"field","vis":"private","signature":"Private mMode As PwGen.ePwGenMode"}, + {"name":"mRandomSource","kind":"field","vis":"private","signature":"Private mRandomSource As PwGenRandom"}, + {"name":"mSpecialChars","kind":"field","vis":"private","signature":"Private mSpecialChars As String"}, + {"name":"mStartsWithLetter","kind":"field","vis":"private","signature":"Private mStartsWithLetter As Boolean"}, + {"name":"mWordCount","kind":"field","vis":"private","signature":"Private mWordCount As Long"}, + {"name":"mWordSeparator","kind":"field","vis":"private","signature":"Private mWordSeparator As String"}, + {"name":"mWordlist","kind":"field","vis":"private","signature":"Private mWordlist As PwGen.ePwGenWordlist"} + ] + }, + { + "name": "PwGenRandom", + "kind": "interface", + "vis": "public", + "file": "PwGenRandom.twin", + "signature": "Public Interface PwGenRandom", + "description": "Injectable RNG: uniform integer in a closed interval.\n`Nothing` on `PwGenOptions.RandomSource` selects the built-in CNG source.\n\n### Syntax\n`n = src.NextInRange(lowerBound, upperBound)`\n\n### Parameters\n- `lowerBound`, `upperBound`: inclusive; `lowerBound` must be `<= upperBound`.\n\n### Return value\nAn integer `n` with `lowerBound <= n <= upperBound`.", + "members": [ + {"name":"NextInRange","kind":"function","vis":"public","signature":"Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long","returnType":"Long","description":"Uniform integer with both bounds inclusive."} + ] + }, + { + "name": "PwGenWords", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenWords.twin", + "signature": "Module PwGenWords", + "description": "Built-in word lists. Not part of the public API.", + "members": [ + {"name":"ArrayLen","kind":"function","vis":"private","signature":"Private Function ArrayLen(ByRef a() As String) As Long","returnType":"Long","description":"Element count of a string array; unallocated is 0."}, + {"name":"CountOf","kind":"function","vis":"public","signature":"Public Function CountOf(ByVal wordlist As PwGen.ePwGenWordlist, ByVal origin As String) As Long","returnType":"Long","description":"Word count of a built-in list, or 0 on error."}, + {"name":"EnsureLoaded","kind":"sub","vis":"private","signature":"Private Sub EnsureLoaded()","description":"Loads both built-in lists on first access."}, + {"name":"List","kind":"function","vis":"public","signature":"Public Function List(ByVal wordlist As PwGen.ePwGenWordlist, ByVal origin As String) As String()","returnType":"String()","description":"Copy of a built-in list, or empty on error."}, + {"name":"WordAt","kind":"function","vis":"public","signature":"Public Function WordAt(ByVal wordlist As PwGen.ePwGenWordlist, ByVal index As Long, ByVal origin As String) As String","returnType":"String","description":"Word at a 0-based index, or empty on error."}, + {"name":"mDe","kind":"field","vis":"private","signature":"Private mDe() As String"}, + {"name":"mDeCount","kind":"field","vis":"private","signature":"Private mDeCount As Long"}, + {"name":"mEn","kind":"field","vis":"private","signature":"Private mEn() As String"}, + {"name":"mEnCount","kind":"field","vis":"private","signature":"Private mEnCount As Long"} + ] + }, + { + "name": "PwGenWordsDe", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenWordsDe.twin", + "signature": "Module PwGenWordsDe", + "description": "Built-in German word-list data. Not part of the public API.", + "members": [ + {"name":"GermanCsv","kind":"function","vis":"public","signature":"Public Function GermanCsv() As String","returnType":"String","description":"Comma-separated words in fixed order, for Split."} + ] + }, + { + "name": "PwGenWordsEn", + "kind": "module", + "vis": "public", + "file": "Internal/PwGenWordsEn.twin", + "signature": "Module PwGenWordsEn", + "description": "Built-in English word-list data. Not part of the public API.", + "members": [ + {"name":"EnglishCsv","kind":"function","vis":"public","signature":"Public Function EnglishCsv() As String","returnType":"String","description":"Comma-separated words in fixed order, for Split."} + ] + }, + { + "name": "Tests", + "kind": "module", + "vis": "public", + "file": "Tests/Tests.twin", + "signature": "Module Tests", + "description": "Test run: start areas, check claims, print a uniform report. Copy between projects; edit `RunAll`.", + "members": [ + {"name":"AREA_WIDTH","kind":"const","vis":"private","signature":"Private Const AREA_WIDTH As Long = 14"}, + {"name":"AtSite","kind":"function","vis":"private","signature":"Private Function AtSite() As String","returnType":"String"}, + {"name":"BeginArea","kind":"sub","vis":"public","signature":"Public Sub BeginArea(ByVal area As String)"}, + {"name":"BeginRun","kind":"sub","vis":"public","signature":"Public Sub BeginRun()"}, + {"name":"CheckEmpty","kind":"sub","vis":"public","signature":"Public Sub CheckEmpty(ByVal name As String, ByVal isEmptyValue As Boolean)"}, + {"name":"CheckEqual","kind":"sub","vis":"public","signature":"Public Sub CheckEqual(ByVal name As String, ByVal expected As String, ByVal actual As String)"}, + {"name":"CheckEqualDbl","kind":"sub","vis":"public","signature":"Public Sub CheckEqualDbl( ByVal name As String, ByVal expected As Double, ByVal actual As Double, Optional ByVal epsilon As Double = 0.0000001)"}, + {"name":"CheckEqualLong","kind":"sub","vis":"public","signature":"Public Sub CheckEqualLong(ByVal name As String, ByVal expected As Long, ByVal actual As Long)"}, + {"name":"CheckError","kind":"sub","vis":"public","signature":"Public Sub CheckError( ByVal name As String, ByVal expectedCode As String, ByVal hasError As Boolean, ByVal actualCode As String, Optional ByVal expectedOrigin As String = \"\", Optional ByVal actualOrigin As String = \"\", Optional ByVal errText As String = \"\")"}, + {"name":"CheckFalse","kind":"sub","vis":"public","signature":"Public Sub CheckFalse(ByVal name As String, ByVal condition As Boolean)"}, + {"name":"CheckNoError","kind":"sub","vis":"public","signature":"Public Sub CheckNoError(ByVal name As String, ByVal hasError As Boolean, Optional ByVal errText As String = \"\")"}, + {"name":"CheckNotNothing","kind":"sub","vis":"public","signature":"Public Sub CheckNotNothing(ByVal name As String, ByVal isNothingRef As Boolean)"}, + {"name":"CheckNothing","kind":"sub","vis":"public","signature":"Public Sub CheckNothing(ByVal name As String, ByVal isNothingRef As Boolean)"}, + {"name":"CheckNull","kind":"sub","vis":"public","signature":"Public Sub CheckNull(ByVal name As String, ByVal isNullValue As Boolean)"}, + {"name":"CheckTrue","kind":"sub","vis":"public","signature":"Public Sub CheckTrue(ByVal name As String, ByVal condition As Boolean)"}, + {"name":"ElapsedSince","kind":"function","vis":"private","signature":"Private Function ElapsedSince(ByVal t0 As Double) As Double","returnType":"Double"}, + {"name":"EndRun","kind":"sub","vis":"public","signature":"Public Sub EndRun()"}, + {"name":"Fail","kind":"sub","vis":"public","signature":"Public Sub Fail( ByVal name As String, Optional ByVal expected As String = \"\", Optional ByVal actual As String = \"\", Optional ByVal errText As String = \"\")"}, + {"name":"FailCount","kind":"function","vis":"public","signature":"Public Function FailCount() As Long","returnType":"Long"}, + {"name":"FlushArea","kind":"sub","vis":"private","signature":"Private Sub FlushArea()"}, + {"name":"HereModule","kind":"sub","vis":"public","signature":"Public Sub HereModule(ByVal componentName As String)"}, + {"name":"HereProc","kind":"sub","vis":"public","signature":"Public Sub HereProc(ByVal procedureName As String)"}, + {"name":"Ok","kind":"sub","vis":"public","signature":"Public Sub Ok(ByVal name As String)"}, + {"name":"PadLeft","kind":"function","vis":"private","signature":"Private Function PadLeft(ByVal text As String, ByVal width As Long) As String","returnType":"String"}, + {"name":"PadRight","kind":"function","vis":"private","signature":"Private Function PadRight(ByVal text As String, ByVal width As Long) As String","returnType":"String"}, + {"name":"PrintAt","kind":"sub","vis":"private","signature":"Private Sub PrintAt()"}, + {"name":"RULE_WIDTH","kind":"const","vis":"private","signature":"Private Const RULE_WIDTH As Long = 60"}, + {"name":"RuleLine","kind":"function","vis":"private","signature":"Private Function RuleLine(ByVal mark As String, ByVal title As String) As String","returnType":"String"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"SetContinueOnFail","kind":"sub","vis":"public","signature":"Public Sub SetContinueOnFail(ByVal value As Boolean)"}, + {"name":"SetQuiet","kind":"sub","vis":"public","signature":"Public Sub SetQuiet(ByVal value As Boolean)"}, + {"name":"Skip","kind":"sub","vis":"public","signature":"Public Sub Skip(ByVal name As String, ByVal reason As String)"}, + {"name":"mAreaFail","kind":"field","vis":"private","signature":"Private mAreaFail As Long"}, + {"name":"mAreaName","kind":"field","vis":"private","signature":"Private mAreaName As String"}, + {"name":"mAreaPass","kind":"field","vis":"private","signature":"Private mAreaPass As Long"}, + {"name":"mAreaSkip","kind":"field","vis":"private","signature":"Private mAreaSkip As Long"}, + {"name":"mComponent","kind":"field","vis":"private","signature":"Private mComponent As String"}, + {"name":"mContinueOnFail","kind":"field","vis":"private","signature":"Private mContinueOnFail As Boolean"}, + {"name":"mFail","kind":"field","vis":"private","signature":"Private mFail As Long"}, + {"name":"mPass","kind":"field","vis":"private","signature":"Private mPass As Long"}, + {"name":"mProc","kind":"field","vis":"private","signature":"Private mProc As String"}, + {"name":"mQuiet","kind":"field","vis":"private","signature":"Private mQuiet As Boolean"}, + {"name":"mSkip","kind":"field","vis":"private","signature":"Private mSkip As Long"}, + {"name":"mStats","kind":"field","vis":"private","signature":"Private mStats As String"}, + {"name":"mT0","kind":"field","vis":"private","signature":"Private mT0 As Double"} + ] + }, + { + "name": "TestsCore", + "kind": "module", + "vis": "public", + "file": "Tests/TestsCore.twin", + "signature": "Module TestsCore", + "description": "Core checks: constants, presets, factory, clone, last-error, TryGenerate.", + "members": [ + {"name":"Clone_And_CustomWords_Copy","kind":"sub","vis":"public","signature":"Public Sub Clone_And_CustomWords_Copy()"}, + {"name":"Constants","kind":"sub","vis":"public","signature":"Public Sub Constants()"}, + {"name":"Error_Store_And_Snapshot","kind":"sub","vis":"public","signature":"Public Sub Error_Store_And_Snapshot()"}, + {"name":"Factory_And_Guard","kind":"sub","vis":"public","signature":"Public Sub Factory_And_Guard()"}, + {"name":"Invalid_Mode","kind":"sub","vis":"public","signature":"Public Sub Invalid_Mode()"}, + {"name":"Nothing_Is_Alphanumeric","kind":"sub","vis":"public","signature":"Public Sub Nothing_Is_Alphanumeric()"}, + {"name":"Presets","kind":"sub","vis":"public","signature":"Public Sub Presets()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Setters_Are_Silent","kind":"sub","vis":"public","signature":"Public Sub Setters_Are_Silent()"}, + {"name":"TryGenerate_Contract","kind":"sub","vis":"public","signature":"Public Sub TryGenerate_Contract()"}, + {"name":"Unreachable_Codes","kind":"sub","vis":"public","signature":"Public Sub Unreachable_Codes()"} + ] + }, + { + "name": "TestsFakeRandom", + "kind": "class", + "vis": "public", + "file": "Tests/TestsFakeRandom.twin", + "signature": "Class TestsFakeRandom", + "implements": ["PwGenRandom"], + "description": "Test RNG that returns an interval bound, or a value outside it.", + "members": [ + {"name":"NextInRange","kind":"function","vis":"private","signature":"Private Function NextInRange(ByVal lowerBound As Long, ByVal upperBound As Long) As Long Implements PwGenRandom.NextInRange","returnType":"Long"}, + {"name":"SetAlwaysUpper","kind":"sub","vis":"public","signature":"Public Sub SetAlwaysUpper(ByVal value As Boolean)"}, + {"name":"SetOutOfRange","kind":"sub","vis":"public","signature":"Public Sub SetOutOfRange(ByVal value As Boolean)"}, + {"name":"mAlwaysUpper","kind":"field","vis":"private","signature":"Private mAlwaysUpper As Boolean"}, + {"name":"mOutOfRange","kind":"field","vis":"private","signature":"Private mOutOfRange As Boolean"} + ] + }, + { + "name": "TestsRandom", + "kind": "module", + "vis": "public", + "file": "Tests/TestsRandom.twin", + "signature": "Module TestsRandom", + "description": "Random-mode checks: pool, entropy, constraints, and error codes.", + "members": [ + {"name":"AMBIGUOUS","kind":"const","vis":"private","signature":"Private Const AMBIGUOUS As String = \"0O1lI\""}, + {"name":"Alphabet_And_Include_Exclude","kind":"sub","vis":"public","signature":"Public Sub Alphabet_And_Include_Exclude()"}, + {"name":"AlphanumericPool","kind":"function","vis":"private","signature":"Private Function AlphanumericPool() As String","returnType":"String"}, + {"name":"DIGITS","kind":"const","vis":"private","signature":"Private Const DIGITS As String = \"0123456789\""}, + {"name":"Default_And_Recipes","kind":"sub","vis":"public","signature":"Public Sub Default_And_Recipes()"}, + {"name":"Entropy_Formulas","kind":"sub","vis":"public","signature":"Public Sub Entropy_Formulas()"}, + {"name":"Expect","kind":"sub","vis":"private","signature":"Private Sub Expect(ByVal name As String, ByVal code As String, ByVal origin As String)"}, + {"name":"Fake_Rng_Exact","kind":"sub","vis":"public","signature":"Public Sub Fake_Rng_Exact()"}, + {"name":"HEX","kind":"const","vis":"private","signature":"Private Const HEX As String = \"0123456789abcdef\""}, + {"name":"HasCharFrom","kind":"function","vis":"private","signature":"Private Function HasCharFrom(ByVal text As String, ByVal pool As String) As Boolean","returnType":"Boolean"}, + {"name":"HasConsecutiveChars","kind":"function","vis":"private","signature":"Private Function HasConsecutiveChars(ByVal text As String) As Boolean","returnType":"Boolean"}, + {"name":"IsOnlyFrom","kind":"function","vis":"private","signature":"Private Function IsOnlyFrom(ByVal text As String, ByVal pool As String) As Boolean","returnType":"Boolean"}, + {"name":"LETTERS","kind":"const","vis":"private","signature":"Private Const LETTERS As String = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\""}, + {"name":"MinPerClass_And_Length","kind":"sub","vis":"public","signature":"Public Sub MinPerClass_And_Length()"}, + {"name":"No_Consecutive_Repeats","kind":"sub","vis":"public","signature":"Public Sub No_Consecutive_Repeats()"}, + {"name":"Random_Error_Codes","kind":"sub","vis":"public","signature":"Public Sub Random_Error_Codes()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"StartsWithLetter","kind":"sub","vis":"public","signature":"Public Sub StartsWithLetter()"} + ] + }, + { + "name": "TestsWordlist", + "kind": "module", + "vis": "public", + "file": "Tests/TestsWordlist.twin", + "signature": "Module TestsWordlist", + "description": "Wordlist-mode checks: built-in lists, custom words, entropy, and error codes.", + "members": [ + {"name":"BuiltIn_Lists","kind":"sub","vis":"public","signature":"Public Sub BuiltIn_Lists()"}, + {"name":"CountChar","kind":"function","vis":"private","signature":"Private Function CountChar(ByVal text As String, ByVal ch As String) As Long","returnType":"Long"}, + {"name":"CountParts","kind":"function","vis":"private","signature":"Private Function CountParts(ByVal text As String, ByVal sep As String) As Long","returnType":"Long"}, + {"name":"Custom_Words_And_Unique","kind":"sub","vis":"public","signature":"Public Sub Custom_Words_And_Unique()"}, + {"name":"Expect","kind":"sub","vis":"private","signature":"Private Sub Expect(ByVal name As String, ByVal code As String, ByVal origin As String)"}, + {"name":"IsAz","kind":"function","vis":"private","signature":"Private Function IsAz(ByVal text As String) As Boolean","returnType":"Boolean"}, + {"name":"Passphrase_Defaults","kind":"sub","vis":"public","signature":"Public Sub Passphrase_Defaults()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Separator_And_Capitalize","kind":"sub","vis":"public","signature":"Public Sub Separator_And_Capitalize()"}, + {"name":"Wordlist_Entropy","kind":"sub","vis":"public","signature":"Public Sub Wordlist_Entropy()"}, + {"name":"Wordlist_Error_Codes","kind":"sub","vis":"public","signature":"Public Sub Wordlist_Error_Codes()"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-tsPwGen/Settings b/test/example-projects/contributed-tsPwGen/Settings new file mode 100644 index 000000000..ef6ed9e58 --- /dev/null +++ b/test/example-projects/contributed-tsPwGen/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{C3774956-E7A2-4B23-B344-D71A2A9EE50C}", + "licence": "MIT", + "name": "[IMPORTED] Passwords you can measure before you generate.", + "path32": "/Packages/tsPwGen", + "path64": "/Packages/tsPwGen", + "symbolId": "tsPwGen", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "8" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 19522b630edb7c988e62e127d9b15b1f313e3121 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 12:33:30 +0200 Subject: [PATCH 08/15] TBMANLIB_Aes: a contributed section of four pages (cAes, cAesHelper, mAesConstants), listed under Experimental Packages: its cipher modes are not the ones named (the default CBC runs ECB, OFB and CTR run CFB) and it fails the NIST test vectors; each defect shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../Contributed/TBMANLIB_Aes/cAes.md | 908 ++++++++++++++++++ .../Contributed/TBMANLIB_Aes/cAesHelper.md | 309 ++++++ .../Contributed/TBMANLIB_Aes/index.md | 148 +++ .../Contributed/TBMANLIB_Aes/mAesConstants.md | 155 +++ docs/Reference/Experimental.md | 1 + docs/_book.yml | 7 + .../contributed/TBMANLIB_Aes/api.json | 311 ++++++ .../contributed-TBMANLIB_Aes/Settings | 74 ++ 10 files changed, 1915 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB_Aes/cAes.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Aes/cAesHelper.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Aes/index.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Aes/mAesConstants.md create mode 100644 indexer/snapshots/contributed/TBMANLIB_Aes/api.json create mode 100644 test/example-projects/contributed-TBMANLIB_Aes/Settings diff --git a/WIP.md b/WIP.md index 421f2bddb..57ee790f6 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Eighteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Nineteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index fa6365134..35ee15ff1 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1167, + "pages": 1171, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md b/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md new file mode 100644 index 000000000..d42338f08 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md @@ -0,0 +1,908 @@ +--- +title: cAes +parent: TBMANLIB_Aes Package +permalink: /tB/Packages/Contributed/TBMANLIB_Aes/cAes +has_toc: false +--- + +# cAes class +{: .no_toc } + +Encrypts and decrypts text and byte arrays with AES, in one of six cipher modes, with a key of 128, 192 or 256 bits. + +A **cAes** keeps a [**Key**](#key), an [**IV**](#iv), a [**CipherMode**](#ciphermode) and a [**PaddingMode**](#paddingmode). [**EncryptString**](#encryptstring) and [**EncryptBytes**](#encryptbytes) encrypt with them, and [**DecryptString**](#decryptstring) and [**DecryptBytes**](#decryptbytes) decrypt. In the GCM mode, [**GcmTag**](#gcmtag) and [**GcmAAD**](#gcmaad) take part as well. + +```tb check_build check_run +Dim aes As New cAes +aes.CipherMode = AesCipherMode.GCM +aes.SetKeyFromString "0123456789abcdef0123456789abcdef" ' 32 characters: an AES-256 key +aes.GenerateRandomIV + +Dim cipherText As String +cipherText = aes.EncryptString("Hello, AES!") +Debug.Print aes.DecryptString(cipherText) ' Hello, AES! +``` + +The output of **EncryptString** and **EncryptBytes** holds the encrypted bytes and nothing else: not the IV, not the padding mode and, in GCM mode, not the tag. The program keeps them, and gives them to the **cAes** that decrypts. + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cAes**. + +## Defaults + +A new **cAes** starts with these values. + +| Property | Starting value | +|---|---| +| [**KeySize**](#keysize) | **KeySize_256** | +| [**CipherMode**](#ciphermode) | **CBC** | +| [**PaddingMode**](#paddingmode) | **PKCS7** | +| [**Key**](#key), [**IV**](#iv), [**GcmTag**](#gcmtag), [**GcmAAD**](#gcmaad) | an array with no elements | +| [**IsInitialized**](#isinitialized) | **False** | +| [**LastError**](#lasterror) | an empty string | + +```tb check_build check_run +Dim aes As New cAes +Dim helper As New cAesHelper +Dim keyBytes() As Byte + +keyBytes = aes.Key +Debug.Print aes.KeySize ' 256 +Debug.Print aes.CipherMode ' 2 +Debug.Print aes.PaddingMode ' 1 +Debug.Print helper.IsArrayEmpty(keyBytes) ' True +Debug.Print aes.IsInitialized ' False +Debug.Print Len(aes.LastError) ' 0 +``` + +Every call of an encrypt or a decrypt method starts again from **Key** and **IV**: it makes new Windows key objects, and it does not continue the chaining of an earlier call. Two encryptions in a row with the same properties give the same output. + +## Which mode runs + +> [!WARNING] +> In version 1.0.0.2 the name of a mode is not always the mode that runs. **ECB** runs CBC with an IV of zero bytes, **CBC** runs ECB, and **OFB** and **CTR** run CFB. Neither **ECB** nor **CBC** uses the IV that the program sets, so the same key and plaintext always give the same ciphertext, and in the default mode **CBC** equal plaintext blocks give equal ciphertext blocks. A round trip succeeds in every case, because encryption and decryption use the same mode, so a test that only decrypts what it has encrypted shows nothing. + +| Mode | Windows interface | What runs | The IV | +|---|---|---|---| +| **ECB** | Crypto API | CBC | Not passed to Windows; the IV is all zero bytes | +| **CBC** | Crypto API | ECB | Passed to Windows, and ECB ignores it | +| **CFB**, **OFB**, **CTR** | CNG | CFB with an 8-bit feedback | 16 bytes from [**IV**](#iv); all zero bytes when none is set | +| **GCM** | CNG | GCM with a 12-byte nonce and a 16-byte tag | The first 12 bytes of [**IV**](#iv) are the nonce | + +The swap of **ECB** and **CBC** comes from the constants of the package: **CRYPT_MODE_ECB** is 1 and **CRYPT_MODE_CBC** is 2, and the Crypto API defines 1 as CBC and 2 as ECB. See [mAesConstants](mAesConstants#windows-constants). The class sets the IV of a Crypto API key only when the mode is not **ECB**, so the mode **ECB**, which runs CBC, starts with the zero IV that Windows gives a new key, and the mode **CBC**, which runs ECB, sets an IV that ECB does not read. **OFB** and **CTR** have no code of their own: the class sets the CNG chaining mode to CFB for every mode but GCM, and accepts the two modes without an error. + +The samples on this page print bytes with `HexOf` and read hexadecimal text with `FromHex`. The two functions are not part of the package. + +```tb check_build slot=module projname=aes-class +Public Function HexOf(ByRef bytes() As Byte) As String + Dim i As Long + Dim text As String + For i = LBound(bytes) To UBound(bytes) + text = text & Right$("0" & LCase$(Hex$(bytes(i))), 2) + Next + HexOf = text +End Function + +Public Function FromHex(ByVal text As String) As Byte() + Dim bytes() As Byte + Dim i As Long + ReDim bytes(Len(text) \ 2 - 1) + For i = 0 To UBound(bytes) + bytes(i) = CByte("&H" & Mid$(text, i * 2 + 1, 2)) + Next + FromHex = bytes +End Function +``` + +### The Crypto API modes + +The modes **ECB** and **CBC** acquire a context of the Crypto API for the provider type **PROV_RSA_AES** with no container name. When Windows refuses, the class asks again with **CRYPT_NEWKEYSET**, which creates the default key container of the user. It then hashes the bytes of [**Key**](#key) once with SHA-256, without a salt, and gives the hash to `CryptDeriveKey`, which makes the AES key. The algorithm is AES-128, AES-192 or AES-256 as [**KeySize**](#keysize) says, and the key is the first 16, 24 or 32 bytes of the hash. + +The **Key** array is therefore not the AES key of these two modes. Other software that is given the same array and uses it as the AES key cannot decrypt their output; it has to hash the array in the same way first. + +The class pads the data as [**PaddingMode**](#paddingmode) says, and then calls `CryptEncrypt` for the last block. Windows adds its own PKCS #5 padding at that call, whatever **PaddingMode** is, so the output is longer than the padded data by 1 to 16 bytes: a whole block when the padded data already fills its blocks. On decryption Windows takes off its padding, and then the class takes off its own. + +In this sample, the default mode **CBC** encrypts two equal blocks. The first two blocks of the output are equal, and so are the last two, which come from the two paddings. **Key** is the 16-byte key of the examples of NIST SP 800-38A, and the mode hashes it before it uses it. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes ' CBC, KeySize_256 +Dim keyBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte +Dim cipherHex As String + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +plain = StrConv(String$(32, "A"), vbFromUnicode) ' two equal blocks + +aes.Key = keyBytes +cipher = aes.EncryptBytes(plain) +cipherHex = HexOf(cipher) +Debug.Print UBound(cipher) + 1 ' 64 +Debug.Print Mid$(cipherHex, 1, 32) = Mid$(cipherHex, 33, 32) ' True +Debug.Print cipherHex ' db9c065d2a475f8f9eff8b040db4701edb9c065d2a475f8f9eff8b040db4701e9a77fe2fe55ee4f0e139d67ab6fa7eda9a77fe2fe55ee4f0e139d67ab6fa7eda +``` + +The mode **ECB** runs CBC, and the IV that the program sets does not reach Windows. Its output is the same with an IV and without one, and its second block differs from its first, since the blocks are chained. Its first block equals the first block of the sample above, because the zero IV changes nothing in the first block. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte +Dim withoutIV As String + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +ivBytes = FromHex("000102030405060708090a0b0c0d0e0f") +plain = StrConv(String$(32, "A"), vbFromUnicode) + +aes.CipherMode = AesCipherMode.ECB +aes.Key = keyBytes +cipher = aes.EncryptBytes(plain) ' no IV set +withoutIV = HexOf(cipher) + +aes.IV = ivBytes +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) = withoutIV ' True +Debug.Print withoutIV ' db9c065d2a475f8f9eff8b040db4701e438fd62477608499b6746bf92dfc4fa264d3d3facff9b49957177244d0ad2fa78b80b345771fac552c30f1fc7ce6c9a2 +``` + +The next sample encrypts one block of NIST SP 800-38A, with **NoPadding** so that the only padding is the one that Windows adds. The standard results for the key `2b7e151628aed2a6abf7158809cf4f3c` are `3ad77bb40d7a3660a89ecaf32466ef97` for ECB (F.1.1) and `7649abac8119b246cee98e9b12e9197d` for CBC with the IV `000102030405060708090a0b0c0d0e0f` (F.2.1). Neither mode of the class gives them: the first 16 bytes of the two outputs are equal to each other, and are the encryption of the block under the SHA-256 hash of the key. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +ivBytes = FromHex("000102030405060708090a0b0c0d0e0f") +plain = FromHex("6bc1bee22e409f96e93d7e117393172a") +aes.PaddingMode = AesPaddingMode.NoPadding +aes.Key = keyBytes +aes.IV = ivBytes + +aes.CipherMode = AesCipherMode.CBC +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) ' 4a25e445195fc88dd0b7fff307d49a9a9a77fe2fe55ee4f0e139d67ab6fa7eda + +aes.CipherMode = AesCipherMode.ECB +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) ' 4a25e445195fc88dd0b7fff307d49a9a3daa1722a6ebdf652bd36c4723d038fd +``` + +### The CNG modes + +The modes **CFB**, **OFB**, **CTR** and **GCM** open the CNG provider for AES, set its chaining mode to `ChainingModeCFB` (**CFB**, **OFB**, **CTR**) or `ChainingModeGCM` (**GCM**), and make the key from the bytes of **Key** as they are. The key length is the length of the array: 16, 24 and 32 bytes give AES-128, AES-192 and AES-256. An array of any other length, such as 20 bytes, makes the call fail, and [**KeySize**](#keysize) has no effect: a 32-byte key with **KeySize_128** is an AES-256 key. + +The class does not set the message block length of the chaining mode, and CNG then feeds back one byte at a time: the CFB of these modes is CFB with an 8-bit feedback, the mode that NIST SP 800-38A lists as CFB8, and not CFB with a 128-bit feedback. Output of other software in CFB mode, which is usually CFB128, is different from it. + +The sample encrypts the first 18 bytes of the plaintext of SP 800-38A with **NoPadding**, so that the output has the length of the input. Its output is the ciphertext of the CFB8-AES128 example F.3.7. The first 16 bytes of plaintext give `3b3fd92eb72dad20333449f8e83cfb4a` in the CFB128 example F.3.13; that block and the output below share only their first byte. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +ivBytes = FromHex("000102030405060708090a0b0c0d0e0f") +plain = FromHex("6bc1bee22e409f96e93d7e117393172aae2d") + +aes.CipherMode = AesCipherMode.CFB +aes.PaddingMode = AesPaddingMode.NoPadding +aes.Key = keyBytes +aes.IV = ivBytes +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) ' 3b79424c9c0dd436bace9e0ed4586a4f32b9 +``` + +**OFB** and **CTR** give the output of **CFB**. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +ivBytes = FromHex("000102030405060708090a0b0c0d0e0f") +plain = FromHex("6bc1bee22e409f96e93d7e117393172aae2d") +aes.PaddingMode = AesPaddingMode.NoPadding +aes.Key = keyBytes +aes.IV = ivBytes + +aes.CipherMode = AesCipherMode.CFB +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) +aes.CipherMode = AesCipherMode.OFB +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) +aes.CipherMode = AesCipherMode.CTR +cipher = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher) +' Output: +' 3b79424c9c0dd436bace9e0ed4586a4f32b9 +' 3b79424c9c0dd436bace9e0ed4586a4f32b9 +' 3b79424c9c0dd436bace9e0ed4586a4f32b9 +``` + +The IV of these modes is passed to CNG as it is, from the first element, with its length. CNG needs 16 bytes for CFB, and an IV of another length, such as 12 bytes, makes the call fail. With no IV set, the class passes none, and CNG uses 16 zero bytes: the output equals that of an IV of 16 zero bytes. + +### Reading the output with other software + +The output of a mode is the output of a standard mode under particular keys and paddings. These are the equivalents, with the names of the OpenSSL ciphers; *N* is the key length in bits. + +| Mode | Standard mode | Key | IV | +|---|---|---|---| +| **CBC** | `aes-N-ecb`, *N* from [**KeySize**](#keysize) | The first *N*/8 bytes of the SHA-256 hash of [**Key**](#key) | None | +| **ECB** | `aes-N-cbc`, *N* from [**KeySize**](#keysize) | The same | 16 zero bytes | +| **CFB**, **OFB**, **CTR** | `aes-N-cfb8`, *N* from the length of [**Key**](#key) | [**Key**](#key) | [**IV**](#iv) | +| **GCM** | `aes-N-gcm`, *N* from the length of [**Key**](#key) | [**Key**](#key) | The first 12 bytes of [**IV**](#iv); the tag is [**GcmTag**](#gcmtag) | + +What the standard decryption returns is the data followed by the padding that [**PaddingMode**](#paddingmode) added. With **NoPadding** it is the data itself in the CNG modes. In the Crypto API modes a standard decryption also returns the PKCS #7 block of Windows unless the software removes it, which standard software does; with **NoPadding** the output is then the data, and with **PKCS7** or **ZeroPadding** it is the data and the padding of the class. The Base64 text from [**EncryptString**](#encryptstring) is the plain Base64 of these bytes. + +### Data that is not checked + +None of the modes but **GCM** checks that the data is the data that was encrypted. A decryption with a wrong key, a wrong IV or damaged bytes gives other bytes, and nothing reports it. With the **PKCS7** padding of the default, the padding check rejects most of these results, about 255 of 256, and the decryption fails; the rest pass as data. With **NoPadding** or **ISO10126** there is no check, and [**DecryptString**](#decryptstring) returns whatever text the bytes decode to. + +```tb check_build check_run +Dim writer As New cAes +Dim reader As New cAes +Dim rightKey() As Byte +Dim wrongKey() As Byte +Dim ivBytes() As Byte +Dim text As String + +rightKey = StrConv("0123456789abcdef", vbFromUnicode) +wrongKey = StrConv("0123456789abcdeg", vbFromUnicode) ' one character differs +ivBytes = StrConv("fedcba9876543210", vbFromUnicode) + +writer.CipherMode = AesCipherMode.CFB +writer.PaddingMode = AesPaddingMode.NoPadding +writer.Key = rightKey +writer.IV = ivBytes +reader.CipherMode = AesCipherMode.CFB +reader.PaddingMode = AesPaddingMode.NoPadding +reader.Key = wrongKey +reader.IV = ivBytes + +text = reader.DecryptString(writer.EncryptString("Hello, AES!")) +Debug.Print Len(text) > 0 ' True +Debug.Print text = "Hello, AES!" ' False +``` + +## GCM mode + +GCM encrypts and authenticates. A **cAes** in GCM mode works in these steps. + +1. Set [**Key**](#key) and [**IV**](#iv). The nonce is the first 12 bytes of **IV**. An IV of 16 bytes, such as [**GenerateRandomIV**](#generaterandomiv) makes, is cut to 12 bytes; a shorter IV is extended with zero bytes to 12. +2. Set [**GcmAAD**](#gcmaad), data that is authenticated and not encrypted, if the message has any. +3. Encrypt. The return value is the ciphertext, and [**GcmTag**](#gcmtag) holds the 16-byte tag of this encryption. +4. Keep the ciphertext, the IV, the tag and the additional data. + +To decrypt, a **cAes** with the same key needs the same **IV**, **GcmTag** and **GcmAAD**. Windows checks the tag against the ciphertext and the additional data. When the check fails, [**DecryptBytes**](#decryptbytes) raises the error described under [Failures](#failures) and [**DecryptString**](#decryptstring) returns an empty string. No text is recorded in [**LastError**](#lasterror). + +**PaddingMode** applies in GCM mode as in the others. With the default **PKCS7** the ciphertext is 1 to 16 bytes longer than the plaintext, which is not what other GCM software produces, and the tag is a tag of the padded data. Use **NoPadding** to get standard GCM output. The sample is test case 4 of the GCM specification by McGrew and Viega, the document that NIST SP 800-38D is based on: a 60-byte plaintext, 20 bytes of additional data and the standard IV length of 96 bits. + +```tb check_build check_run projname=aes-class +Dim aes As cAes = New cAes(AesKeySize.KeySize_128, AesCipherMode.GCM, AesPaddingMode.NoPadding) +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim aad() As Byte +Dim plain() As Byte +Dim cipher() As Byte +Dim tag() As Byte +Dim decrypted() As Byte + +keyBytes = FromHex("feffe9928665731c6d6a8f9467308308") +ivBytes = FromHex("cafebabefacedbaddecaf888") +aad = FromHex("feedfacedeadbeeffeedfacedeadbeefabaddad2") +plain = FromHex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39") + +aes.Key = keyBytes +aes.IV = ivBytes +aes.GcmAAD = aad +cipher = aes.EncryptBytes(plain) +tag = aes.GcmTag +Debug.Print HexOf(cipher) ' 42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091 +Debug.Print HexOf(tag) ' 5bc94fbc3221a5db94fae95ae7121a47 + +decrypted = aes.DecryptBytes(cipher) +Debug.Print HexOf(decrypted) = HexOf(plain) ' True +``` + +With a tag that has one bit changed, the decryption fails. The failure is the same for changed additional data, a changed IV and changed ciphertext. The package prints five lines with `Debug.Print` on the way, and the Debug Console of the IDE shows them; the first line holds the Windows status `0xC000A002`, which is `STATUS_AUTH_TAG_MISMATCH`, and a message in Chinese. The error that reaches the program is **Unspecified error**. + +```tb check_build check_run projname=aes-class +Dim aes As cAes = New cAes(AesKeySize.KeySize_128, AesCipherMode.GCM, AesPaddingMode.NoPadding) +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte +Dim tag() As Byte +Dim decrypted() As Byte + +keyBytes = FromHex("feffe9928665731c6d6a8f9467308308") +ivBytes = FromHex("cafebabefacedbaddecaf888") +plain = StrConv("Hello, AES!", vbFromUnicode) +aes.Key = keyBytes +aes.IV = ivBytes +cipher = aes.EncryptBytes(plain) +tag = aes.GcmTag + +tag(0) = tag(0) Xor 1 ' damage the tag +aes.GcmTag = tag +On Error Resume Next +decrypted = aes.DecryptBytes(cipher) +Debug.Print Err.Number +' Output: +' BCryptDecrypt_GCM failed: 0xC000A002 - CNG 错误: 0xC000A002 +' DoDecryptCNG_GCM failed: Unspecified error +' DoDecryptCNG failed: Unspecified error +' DoDecrypt failed: Unspecified error +' DecryptBytes error: Unspecified error +' -2147467259 +``` + +A decryption with no tag set fails in the same way, after the line `DoDecryptCNG_GCM failed: GCM tag not set for decryption`. A tag of 16 bytes is the length that the encryption makes. The class passes the length of **GcmTag** to Windows as it is: a tag cut to its first 12 bytes is accepted and the decryption returns the plaintext, and one cut to 4 bytes makes Windows fail with an invalid parameter. + +> [!WARNING] +> In version 1.0.0.2, a GCM encryption with no IV set makes a random 12-byte nonce, uses it, and keeps it nowhere. [**IV**](#iv) stays empty. A decryption with no IV uses a nonce of 12 zero bytes, so its tag check fails and the data cannot be decrypted. The encryption raises no error. Set **IV** before every GCM encryption, and keep it with the ciphertext. + +```tb check_build check_run projname=aes-class +Dim aes As cAes = New cAes(AesKeySize.KeySize_128, AesCipherMode.GCM, AesPaddingMode.NoPadding) +Dim helper As New cAesHelper +Dim keyBytes() As Byte +Dim plain() As Byte +Dim first() As Byte +Dim second() As Byte +Dim ivBytes() As Byte + +keyBytes = FromHex("feffe9928665731c6d6a8f9467308308") +plain = StrConv("Hello, AES!", vbFromUnicode) +aes.Key = keyBytes ' no IV is set +first = aes.EncryptBytes(plain) +second = aes.EncryptBytes(plain) +ivBytes = aes.IV +Debug.Print HexOf(first) = HexOf(second) ' False +Debug.Print helper.IsArrayEmpty(ivBytes) ' True +``` + +GCM requires that no nonce is used twice with the same key (NIST SP 800-38D). The class never changes **IV**, so two encryptions in a row use the same nonce unless the program sets a new **IV** between them. + +## Failures + +A failed call does not tell why, and what the caller gets depends on the member. + +- [**EncryptBytes**](#encryptbytes) and [**DecryptBytes**](#decryptbytes) raise the run-time error -2147467259 (`&H80004005`) with the description `Unspecified error` and an empty `Err.Source`. The cause is lost: the code that handles the original error, such as `Key not set`, assigns **Nothing** to the **Byte** array it returns, and that assignment raises this error in place of it. +- [**EncryptString**](#encryptstring) and [**DecryptString**](#decryptstring) return an empty string and raise no error. `Err` keeps the last error and is not cleared. For an empty text it is the error that the method raised itself, `Plain text cannot be empty` (`vbObjectError + 3`) or `Cipher text cannot be empty` (`vbObjectError + 8`); for the other failures it is **Unspecified error**. +- [**SetKeyFromString**](#setkeyfromstring) raises an error of its own, `vbObjectError + 2`. +- [**GenerateRandomIV**](#generaterandomiv) and [**Cleanup**](#cleanup) cannot fail, and the properties raise nothing. + +> [!NOTE] +> A program that calls **EncryptBytes** or **DecryptBytes** needs an `On Error` statement, and cannot tell a missing key from a wrong tag or a damaged ciphertext by the error. The Debug Console of the IDE shows more: the package prints lines with `Debug.Print`, such as `DecryptBytes error: Key not set` and `BCryptEncrypt failed: 0xC0000206` followed by a message, and a built program writes none of them. + +> [!WARNING] +> In version 1.0.0.2 the string members hide a failure in an empty string. **DecryptString** of a GCM message whose tag does not match, of a message with a wrong key in a mode with padding, and of Base64 text that is not valid, all give the same empty string as an empty plaintext would, and **EncryptString** gives an empty string for a missing key. A program must test the result with `Len`. + +These are the causes of a failure in the source: + +- the text or the array to encrypt or decrypt is empty, so an empty input is a failure and not an empty output; +- no key is set; +- the key or the IV has a length that Windows does not accept in the mode; +- the padding of a decryption is not valid, or in GCM mode the tag check fails or no tag is set; +- the Base64 text of [**DecryptString**](#decryptstring) is not valid; +- Windows reports an error, for example when the CNG provider is not available. + +[**LastError**](#lasterror) holds a text only for the failures of the Base64, UTF-8 and padding functions of [**cAesHelper**](cAesHelper#lasterror), and is never cleared. `Err.Number` can also be 9 after a call that succeeded: the class tests its arrays with [**IsArrayEmpty**](cAesHelper#isarrayempty), which leaves the error 9 in `Err` when an array was never sized, for example when no IV is set in the mode **CFB**. A program should not take a non-zero `Err.Number` after a call that raised nothing for a failure. + +```tb check_build check_run +Dim aes As New cAes ' no key is set +Dim plain() As Byte +Dim cipher() As Byte + +plain = StrConv("text", vbFromUnicode) +On Error Resume Next +cipher = aes.EncryptBytes(plain) +Debug.Print Err.Number ' -2147467259 +Debug.Print Err.Description ' Unspecified error +Err.Clear +Debug.Print Len(aes.EncryptString("text")) ' 0 +Debug.Print Len(aes.LastError) ' 0 +``` + +## Constructors + +### New +{: .no_toc } + +Creates a **cAes**. + +Syntax: **Dim** *aes* **As New cAes** + +Syntax: **Dim** *aes* **As cAes = New cAes** ( *keySize*, *cipherMode*, *paddingMode* ) + +*keySize* +: *required* An [**AesKeySize**](mAesConstants#aeskeysize) member, stored in [**KeySize**](#keysize). + +*cipherMode* +: *required* An [**AesCipherMode**](mAesConstants#aesciphermode) member, stored in [**CipherMode**](#ciphermode). + +*paddingMode* +: *required* An [**AesPaddingMode**](mAesConstants#aespaddingmode) member, stored in [**PaddingMode**](#paddingmode). + +With no arguments the object starts with the values in [Defaults](#defaults). The constructor does not check the values. The class has the attribute `[COMCreatable(False)]`, so a program creates it with `New` and not with `CreateObject`. + +```tb check_build check_run +Dim aes As cAes = New cAes(AesKeySize.KeySize_128, AesCipherMode.GCM, AesPaddingMode.NoPadding) + +Debug.Print aes.KeySize ' 128 +Debug.Print aes.CipherMode ' 6 +Debug.Print aes.PaddingMode ' 3 +``` + +## Properties + +### CipherMode +{: .no_toc } + +The cipher mode. An [**AesCipherMode**](mAesConstants#aesciphermode) member, read/write. Default: **CBC**. + +Syntax: *object*.**CipherMode** [ **=** *mode* ] + +[Which mode runs](#which-mode-runs) says what each member does in version 1.0.0.2, and it differs from the names. The setter stores the value without a check, and sets [**IsInitialized**](#isinitialized) to **False**. A value that is not a member makes an encryption or a decryption fail. + +### GcmAAD +{: .no_toc } + +The additional authenticated data of the GCM mode. A **Byte** array, read/write. Default: no elements. + +Syntax: *object*.**GcmAAD** [ **=** *bytes* ] + +The data is authenticated and not encrypted. A decryption needs the same bytes as the encryption that made the tag, or its tag check fails. The data is not part of the output, and the program keeps it. Assigning an array that was never sized removes the data. + +The other modes do not read it. The property keeps its value across calls, and [**Cleanup**](#cleanup) does not clear it. [**SetGcmAADFromString**](#setgcmaadfromstring) sets it from a string. + +### GcmTag +{: .no_toc } + +The authentication tag of the GCM mode. A **Byte** array, read/write. Default: no elements. + +Syntax: *object*.**GcmTag** [ **=** *bytes* ] + +A GCM encryption sets it to 16 bytes, replacing the previous value. A decryption reads it, and passes its whole length to Windows as the tag; the class does not check that the length is 16. A tag of 12 bytes is accepted, and one of 4 bytes is not. With no tag set, the decryption fails. The tag is not part of the output of the encrypt methods, so the program keeps it. + +The other modes do not read it. + +### IsInitialized +{: .no_toc } + +Whether the Windows key objects of the object exist. **Boolean**, read-only. Default: **False**. + +Syntax: *object*.**IsInitialized** + +An encrypt or a decrypt call makes the key objects, and **IsInitialized** is **True** after one succeeded in making them. The setters of [**Key**](#key), [**IV**](#iv), [**KeySize**](#keysize), [**CipherMode**](#ciphermode) and [**PaddingMode**](#paddingmode), [**SetKeyFromString**](#setkeyfromstring) and [**Cleanup**](#cleanup) set it to **False**. The property does not say that the key is valid or that the last call succeeded. No call needs it: each call makes its own key objects. + +```tb check_build check_run +Dim aes As New cAes +Dim text As String + +aes.SetKeyFromString "key" +Debug.Print aes.IsInitialized ' False +text = aes.EncryptString("text") +Debug.Print aes.IsInitialized ' True +aes.KeySize = AesKeySize.KeySize_256 +Debug.Print aes.IsInitialized ' False +text = aes.EncryptString("text") +aes.Cleanup +Debug.Print aes.IsInitialized ' False +``` + +### IV +{: .no_toc } + +The initialisation vector. A **Byte** array, read/write. Default: no elements. + +Syntax: *object*.**IV** [ **=** *bytes* ] + +The output of the encrypt methods does not hold the IV, so the program keeps it with the ciphertext and sets it before it decrypts. [**GenerateRandomIV**](#generaterandomiv) sets it and returns it. How each mode uses it: + +| Mode | Use of the IV | +|---|---| +| **ECB** | Not used. The class does not pass it to Windows, and the mode runs with an IV of zero bytes. | +| **CBC** | Not used. The class passes it to Windows, and the mode that runs, ECB, ignores it. | +| **CFB**, **OFB**, **CTR** | Passed with its length, which must be 16 bytes. All zero bytes when none is set. | +| **GCM** | The first 12 bytes are the nonce; see [GCM mode](#gcm-mode). | + +In the default mode **CBC** two IVs give the same ciphertext. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes ' CBC, the default mode +Dim keyBytes() As Byte +Dim iv1() As Byte +Dim iv2() As Byte +Dim plain() As Byte +Dim cipher1() As Byte +Dim cipher2() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +iv1 = FromHex("000102030405060708090a0b0c0d0e0f") +iv2 = FromHex("ffffffffffffffffffffffffffffffff") +plain = StrConv("Hello, AES!", vbFromUnicode) +aes.Key = keyBytes + +aes.IV = iv1 +cipher1 = aes.EncryptBytes(plain) +aes.IV = iv2 +cipher2 = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher1) = HexOf(cipher2) ' True +``` + +In GCM mode a longer IV gives the output of its first 12 bytes. + +```tb check_build check_run projname=aes-class +Dim aes As cAes = New cAes(AesKeySize.KeySize_128, AesCipherMode.GCM, AesPaddingMode.NoPadding) +Dim keyBytes() As Byte +Dim iv12() As Byte +Dim iv16() As Byte +Dim plain() As Byte +Dim cipher12() As Byte +Dim cipher16() As Byte + +keyBytes = FromHex("feffe9928665731c6d6a8f9467308308") +iv12 = FromHex("cafebabefacedbaddecaf888") +iv16 = FromHex("cafebabefacedbaddecaf88801020304") +plain = StrConv("Hello, AES!", vbFromUnicode) +aes.Key = keyBytes + +aes.IV = iv12 +cipher12 = aes.EncryptBytes(plain) +aes.IV = iv16 +cipher16 = aes.EncryptBytes(plain) +Debug.Print HexOf(cipher12) = HexOf(cipher16) ' True +``` + +### Key +{: .no_toc } + +The key bytes. A **Byte** array, read/write. Default: no elements. + +Syntax: *object*.**Key** [ **=** *bytes* ] + +The setter stores a copy of the array without a check of its length, and sets [**IsInitialized**](#isinitialized) to **False**. What the key is depends on the mode: + +- The modes **ECB** and **CBC** hash the bytes with SHA-256 and make the AES key from the hash, as [The Crypto API modes](#the-crypto-api-modes) describes. The array may have any length; an array of 5 bytes and one of 40 bytes both work. +- The modes **CFB**, **OFB**, **CTR** and **GCM** use the bytes as the AES key. The array must have 16, 24 or 32 bytes. + +[**SetKeyFromString**](#setkeyfromstring) fills the property from a string. The package has no function that generates a key. + +### KeySize +{: .no_toc } + +The key length in bits. An [**AesKeySize**](mAesConstants#aeskeysize) member, read/write. Default: **KeySize_256**. + +Syntax: *object*.**KeySize** [ **=** *size* ] + +The setter stores the value and sets [**IsInitialized**](#isinitialized) to **False**. The value is read in two places only: + +- [**SetKeyFromString**](#setkeyfromstring) makes a key of 16, 24 or 32 bytes. Setting **KeySize** afterwards does not change a key that is already set. +- The modes **ECB** and **CBC** choose the AES algorithm, AES-128, AES-192 or AES-256, for the key they derive from the hash of **Key**: the first 16, 24 or 32 bytes of the hash. + +The modes **CFB**, **OFB**, **CTR** and **GCM** ignore it and take the length of the [**Key**](#key) array. A 32-byte key with **KeySize_128** is an AES-256 key in these modes. + +### LastError +{: .no_toc } + +The text of the last failure of a conversion or a padding function. **String**, read-only. + +Syntax: *object*.**LastError** + +The property returns the [**LastError**](cAesHelper#lasterror) of the **cAesHelper** that the object holds. It is an empty string for a new object. A failure sets it only when it happens in the Base64, UTF-8 or padding functions of that helper, and the text is then of the form `Base64 decoding exception: Unspecified error` or `PKCS7 unpadding exception: Unspecified error`; the table under **cAesHelper** lists them. A later success does not clear it, and the object has no way to clear it. The failures of the rest of the class, such as a tag that does not match, set nothing; see [Failures](#failures). + +### PaddingMode +{: .no_toc } + +The padding of the last block. An [**AesPaddingMode**](mAesConstants#aespaddingmode) member, read/write. Default: **PKCS7**. + +Syntax: *object*.**PaddingMode** [ **=** *mode* ] + +The setter stores the value and sets [**IsInitialized**](#isinitialized) to **False**. The class pads the data before it encrypts and takes the padding off after it decrypts, in every cipher mode. [**AesPaddingMode**](mAesConstants#aespaddingmode) lists what each member does. + +The modes **ECB** and **CBC** add the padding of Windows as well, on top of the padding of the class, so their output is 1 to 16 bytes longer than the padded data. The sample encrypts 16 bytes, one whole block, in the default mode **CBC**. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes ' CBC, the default mode +Dim keyBytes() As Byte +Dim plain() As Byte +Dim cipher() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +plain = StrConv("0123456789abcdef", vbFromUnicode) +aes.Key = keyBytes + +aes.PaddingMode = AesPaddingMode.PKCS7 +cipher = aes.EncryptBytes(plain) +Debug.Print UBound(cipher) + 1 ' 48 +aes.PaddingMode = AesPaddingMode.ZeroPadding +cipher = aes.EncryptBytes(plain) +Debug.Print UBound(cipher) + 1 ' 48 +aes.PaddingMode = AesPaddingMode.NoPadding +cipher = aes.EncryptBytes(plain) +Debug.Print UBound(cipher) + 1 ' 32 +aes.PaddingMode = AesPaddingMode.ISO10126 +cipher = aes.EncryptBytes(plain) +Debug.Print UBound(cipher) + 1 ' 32 +``` + +With **NoPadding** in the modes **CFB**, **OFB**, **CTR** and **GCM**, the output has the length of the input. In the modes **ECB** and **CBC** it never does. + +**ISO10126** gives the output of **NoPadding**, and no padding is added. With **ZeroPadding** the decryption removes every zero byte at the end of the data, and with the data `41 42 00 00` it returns the two bytes `41 42`. + +> [!WARNING] +> With **ZeroPadding**, version 1.0.0.2 gives back less than it was given when the data ends in zero bytes. The decryption removes every zero byte at the end of the data, and not only the ones that the encryption added, and raises no error. Data that is all zero bytes fails the decryption. Use **PKCS7** for data that can end in zero bytes. + +## Methods + +### Cleanup +{: .no_toc } + +Releases the Windows handles of the object. + +Syntax: *object*.**Cleanup** + +The object holds a Crypto API provider and key and a CNG algorithm and key after an encrypt or a decrypt call. **Cleanup** closes and destroys them, and sets [**IsInitialized**](#isinitialized) to **False**. The class calls it when the object is destroyed, so a program needs it only to release the handles earlier. The next encrypt or decrypt call makes new ones, so the object stays usable. + +**Cleanup** does not clear the memory of [**Key**](#key), [**IV**](#iv), [**GcmTag**](#gcmtag) or [**GcmAAD**](#gcmaad), and does not empty them. + +### DecryptBytes +{: .no_toc } + +Decrypts a byte array. + +Syntax: *object*.**DecryptBytes** ( *cipherBytes* ) **As Byte()** + +*cipherBytes* +: *required* A **Byte** array of encrypted bytes, passed by reference. The method does not change it. + +Returns a new array with the plaintext. The method decrypts with the current [**Key**](#key), [**IV**](#iv) and [**CipherMode**](#ciphermode), in the mode GCM with [**GcmTag**](#gcmtag) and [**GcmAAD**](#gcmaad), and then takes off the padding that [**PaddingMode**](#paddingmode) names. + +The method raises **Unspecified error** in these cases: *cipherBytes* is empty; no key is set; Windows rejects the key, the IV or the ciphertext; the padding is not valid; the tag check of the GCM mode fails; or the plaintext, once the padding is off, has no bytes. For the Crypto API modes the ciphertext must be a multiple of 16 bytes. See [Failures](#failures). When the padding is not valid, [**LastError**](#lasterror) holds `PKCS7 unpadding exception: Unspecified error`. + +### DecryptString +{: .no_toc } + +Decrypts Base64 text that [**EncryptString**](#encryptstring) made. + +Syntax: *object*.**DecryptString** ( *cipherText* ) **As String** + +*cipherText* +: *required* A **String** of Base64 text. + +Returns the plaintext as a **String**. The method decodes the Base64 text, calls [**DecryptBytes**](#decryptbytes), and reads the bytes as UTF-8. Bytes that are not valid UTF-8 become the character U+FFFD, with no error. + +The method returns an empty string, and raises no error, when *cipherText* is empty or is not Base64 text, and whenever **DecryptBytes** fails. It prints a line `DecryptString error: ` and the description with `Debug.Print`, which the Debug Console of the IDE shows. Text that is not valid Base64 sets [**LastError**](#lasterror) to `Base64 decoding exception: Unspecified error`. Only the GCM mode verifies the data; [Data that is not checked](#data-that-is-not-checked) says what a wrong key gives in the others. + +```tb check_build check_run +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte + +keyBytes = StrConv("0123456789abcdef", vbFromUnicode) ' 16 bytes +ivBytes = StrConv("fedcba9876543210", vbFromUnicode) +aes.CipherMode = AesCipherMode.CFB +aes.Key = keyBytes +aes.IV = ivBytes + +Debug.Print aes.DecryptString(aes.EncryptString("Hello, AES!")) +Debug.Print Len(aes.DecryptString("!!! not base64")) +Debug.Print aes.LastError +' Output: +' Hello, AES! +' DecryptString error: Unspecified error +' 0 +' Base64 decoding exception: Unspecified error +``` + +### EncryptBytes +{: .no_toc } + +Encrypts a byte array. + +Syntax: *object*.**EncryptBytes** ( *plainBytes* ) **As Byte()** + +*plainBytes* +: *required* A **Byte** array of at least one element, passed by reference. The method does not change it. + +Returns a new array with the ciphertext, whose first element has the index 0. The method pads the data as [**PaddingMode**](#paddingmode) says, and encrypts it with the current [**Key**](#key), [**IV**](#iv) and [**CipherMode**](#ciphermode). In the mode GCM it also sets [**GcmTag**](#gcmtag) and uses [**GcmAAD**](#gcmaad). The length of the result depends on the mode and the padding, as [Which mode runs](#which-mode-runs) and [**PaddingMode**](#paddingmode) say. + +The method raises **Unspecified error** when *plainBytes* is empty, no key is set, or Windows rejects the key or the IV; see [Failures](#failures). Nothing in the error tells the program which of the causes it was. + +### EncryptString +{: .no_toc } + +Encrypts text and returns the ciphertext as Base64 text. + +Syntax: *object*.**EncryptString** ( *plainText* ) **As String** + +*plainText* +: *required* A **String** of at least one character. + +Returns a **String**. The method converts *plainText* to UTF-8, calls [**EncryptBytes**](#encryptbytes), and encodes the result as Base64: the usual alphabet, `=` at the end when the last group is short, on one line. The text holds only the ciphertext. For the IV, and in GCM mode the tag, see the notes under [**IV**](#iv) and [**GcmTag**](#gcmtag). + +The method returns an empty string, and raises no error, when *plainText* is empty and whenever **EncryptBytes** fails. + +The sample uses the mode **CFB** with the key and the IV of the first block of NIST SP 800-38A. The last line uses the default mode **CBC**, whose key is the SHA-256 hash of the key bytes and whose output holds the two paddings. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte + +keyBytes = FromHex("2b7e151628aed2a6abf7158809cf4f3c") +ivBytes = FromHex("000102030405060708090a0b0c0d0e0f") +aes.CipherMode = AesCipherMode.CFB +aes.Key = keyBytes +aes.IV = ivBytes + +Debug.Print aes.EncryptString("Hello, AES!") ' GL1oJTEdiyUmaKuRzd/eAw== +aes.PaddingMode = AesPaddingMode.NoPadding +Debug.Print aes.EncryptString("Hello, AES!") ' GL1oJTEdiyUmaKs= + +aes.CipherMode = AesCipherMode.CBC +aes.PaddingMode = AesPaddingMode.PKCS7 +Debug.Print aes.EncryptString("Hello, AES!") ' 34qvE3+1wgC5hBWfIIK7XJp3/i/lXuTw4TnWerb6fto= +``` + +### GenerateRandomIV +{: .no_toc } + +Fills [**IV**](#iv) with 16 random bytes, and returns them. + +Syntax: *object*.**GenerateRandomIV** ( ) **As Byte()** + +Returns a **Byte** array of 16 bytes, which is also the new value of **IV**. The method calls `Randomize`, which mixes the system timer into the state of `Rnd` for the whole program, and then takes 16 values of `Rnd`. + +> [!WARNING] +> In version 1.0.0.2 the bytes come from `Rnd`, a pseudo-random generator, and not from a cryptographic one; the comment in the source says that the Crypto API would give better randomness. The state of `Rnd` has 24 bits: its sequence repeats after 16,777,216 values, so the method can return at most 16,777,216 different IVs, and two calls that start from the same state of `Rnd` return the same IV. A program that needs an IV that cannot be predicted can set [**IV**](#iv) from a source of its own. + +The first sample counts the calls of `Rnd` between a value and its next appearance. The second puts `Rnd` into the same state before each of two calls, with `Rnd -1` and `Randomize 7`, and finds two calls that return the same IV. + +```tb check_build check_run projname=aes-class +Dim first As Single +Dim value As Single +Dim count As Long + +Rnd -1 +Randomize 7 +first = Rnd +Do + value = Rnd + count = count + 1 +Loop Until value = first Or count > 20000000 +Debug.Print count ' 16777216 +``` + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim firstIV() As Byte +Dim secondIV() As Byte +Dim attempt As Long +Dim found As Boolean + +For attempt = 1 To 50 + Rnd -1 + Randomize 7 + firstIV = aes.GenerateRandomIV() + Rnd -1 + Randomize 7 + secondIV = aes.GenerateRandomIV() + If HexOf(firstIV) = HexOf(secondIV) Then + found = True + Exit For + End If +Next +Debug.Print found ' True +``` + +For the modes **CFB**, **OFB**, **CTR** and **GCM**, an IV of 16 bytes is the length that CNG needs, and GCM cuts it to 12 bytes. The modes **ECB** and **CBC** do not use the IV. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim iv() As Byte +Dim stored() As Byte + +iv = aes.GenerateRandomIV() +stored = aes.IV +Debug.Print UBound(iv) + 1 ' 16 +Debug.Print HexOf(iv) = HexOf(stored) ' True +``` + +### SetGcmAADFromString +{: .no_toc } + +Sets [**GcmAAD**](#gcmaad) from a string. + +Syntax: *object*.**SetGcmAADFromString** ( *aadString* [, *encoding*] ) + +*aadString* +: *required* A **String**. + +*encoding* +: *optional* A **Long**, a conversion argument of [**StrConv**](../../../Modules/Strings/StrConv). Default: **vbFromUnicode**, which gives the bytes of the string in the ANSI code page of the system; **vbUTF8** gives UTF-8. + +The method does not raise an error. When the conversion fails, it prints `SetGcmAADFromString failed: ` and the description with `Debug.Print`, which a built program does not show, and leaves **GcmAAD** as it was. The program that decrypts has to convert the string in the same way, since the bytes with another encoding are other bytes. [**EncryptString**](#encryptstring) uses UTF-8 for its text, and this method does not. + +> [!WARNING] +> In version 1.0.0.2 an empty string does not remove the additional data. The method fails for an empty string and keeps the data that was set before, and only the Debug Console shows the message `SetGcmAADFromString failed: Unspecified error`. The next GCM encryption then authenticates the old data. To remove the data, assign an array that was never sized to [**GcmAAD**](#gcmaad). + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim aad() As Byte + +aes.SetGcmAADFromString "header" +aad = aes.GcmAAD +Debug.Print HexOf(aad) +aes.SetGcmAADFromString "" +aad = aes.GcmAAD +Debug.Print HexOf(aad) +' Output: +' 686561646572 +' SetGcmAADFromString failed: Unspecified error +' 686561646572 +``` + +### SetKeyFromString +{: .no_toc } + +Sets [**Key**](#key) from a string. + +Syntax: *object*.**SetKeyFromString** ( *keyString* [, *encoding*] ) + +*keyString* +: *required* A **String** of at least one character. + +*encoding* +: *optional* A **Long**, a conversion argument of [**StrConv**](../../../Modules/Strings/StrConv). Default: **vbFromUnicode**, which gives the bytes of the string in the ANSI code page of the system; **vbUTF8** gives UTF-8. + +The method converts *keyString* to bytes with **StrConv** and makes an array of the length that [**KeySize**](#keysize) names, 16, 24 or 32 bytes: the first bytes of the conversion when it is longer, and the bytes followed by zero bytes when it is shorter. The array is the new value of **Key**. The method does not use the string in any other way. + +> [!WARNING] +> In version 1.0.0.2 there is no key derivation. The key is the bytes of the text, cut or extended with zero bytes: a string longer than the key is cut without a message, so that two strings that begin with the same 32 bytes give the same key (with **KeySize_256**), and a short string is a short key followed by zeros. The modes **ECB** and **CBC** then hash the bytes once with SHA-256, without a salt; the modes **CFB**, **OFB**, **CTR** and **GCM** use them as the key. With the default encoding, a character that is not in the ANSI code page of the system is converted to another character, and the same text gives different bytes on systems with another code page. + +The method raises an error when *keyString* is empty: **Err.Number** is `vbObjectError + 2`, **Err.Source** is `cAes.SetKeyFromString` and **Err.Description** is `Key cannot be empty`. **Key** is not changed, and [**IsInitialized**](#isinitialized) is set to **False**. When **KeySize** has a value that is not a member of the enumeration, the key is left as it was and no error is raised. + +```tb check_build check_run projname=aes-class +Dim aes As New cAes +Dim keyBytes() As Byte + +aes.SetKeyFromString "abc" +keyBytes = aes.Key +Debug.Print HexOf(keyBytes) ' 6162630000000000000000000000000000000000000000000000000000000000 + +aes.SetKeyFromString "0123456789ABCDEF0123456789ABCDEF-extra" +keyBytes = aes.Key +Debug.Print HexOf(keyBytes) ' 3031323334353637383941424344454630313233343536373839414243444546 + +aes.KeySize = AesKeySize.KeySize_128 +aes.SetKeyFromString "e" & ChrW(233), vbUTF8 +keyBytes = aes.Key +Debug.Print HexOf(keyBytes) ' 65c3a900000000000000000000000000 + +On Error Resume Next +aes.SetKeyFromString "" +Debug.Print Err.Number - vbObjectError ' 2 +Debug.Print Err.Source ' cAes.SetKeyFromString +Debug.Print Err.Description ' Key cannot be empty +``` + +## See Also + +- [cAesHelper](cAesHelper) class -- the Base64, UTF-8 and padding functions that this class calls +- [mAesConstants](mAesConstants) module -- the enumerations of the modes, key sizes and padding modes +- [TBMANLIB_Aes](.) package -- the landing page diff --git a/docs/Reference/Contributed/TBMANLIB_Aes/cAesHelper.md b/docs/Reference/Contributed/TBMANLIB_Aes/cAesHelper.md new file mode 100644 index 000000000..5dd8990e5 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Aes/cAesHelper.md @@ -0,0 +1,309 @@ +--- +title: cAesHelper +parent: TBMANLIB_Aes Package +permalink: /tB/Packages/Contributed/TBMANLIB_Aes/cAesHelper +has_toc: false +--- + +# cAesHelper class +{: .no_toc } + +Converts byte arrays to Base64 text and back, converts text to UTF-8 bytes and back, adds and takes off PKCS #7 and zero padding, and tests a byte array for emptiness. [**cAes**](cAes) holds one of these objects and calls it for all of this work. + +A program can create its own **cAesHelper** with `New`, for example to test the result of a call of **cAes** with [**IsArrayEmpty**](#isarrayempty), or to encode the ciphertext in a way of its own choice. The object holds no key and no state apart from [**LastError**](#lasterror). + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte +Dim text As String + +data = StrConv("Man", vbFromUnicode) +text = helper.Base64Encode(data) +Debug.Print text ' TWFu + +data = helper.Base64Decode(text) +Debug.Print StrConv(data, vbUnicode) ' Man +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cAesHelper**. + +## Failures + +A member that fails does not tell why, and what the caller gets depends on the type of the result. + +- **ApplyPKCS7Padding**, **Base64Decode**, **RemovePKCS7Padding**, **RemoveZeroPadding** and **StringToUtf8Bytes**, members that return a **Byte** array, raise the run-time error -2147467259 (`&H80004005`) with the description `Unspecified error` and an empty `Err.Source`. They raise it for an empty input as well as for bad input. The cause of the failure is lost, because the code assigns **Nothing** to the result, and that assignment raises this error in place of the one it was handling. What remains of the cause is the text in [**LastError**](#lasterror). **ApplyZeroPadding**, which also returns a **Byte** array, gives a block of zero bytes for an empty input and raises the error only when the code itself fails. +- **Base64Encode** and **Utf8BytesToString**, the members that return a **String**, return an empty string and raise no error. They leave the error in `Err`, which is not cleared: after an array that was never sized, `Err.Number` is 9. +- **IsArrayEmpty** raises no error, and leaves `Err.Number` at 9 when it tests an array that was never sized. + +> [!NOTE] +> The members that raise **Unspecified error** do so for the failures that [**cAes**](cAes) cannot recover from as well. A program that calls one of them with an input that can be empty or damaged needs an `On Error` statement, and cannot tell the causes apart by the error. + +Every member that reads an array reads its first element as the element with index 0. An array with another lower bound fails: **Base64Encode** returns an empty string with the [**LastError**](#lasterror) text `Base64 encoding exception: Subscript out of range`, and **ApplyPKCS7Padding** raises **Unspecified error**, after it has set the text `PKCS7 padding exception: Subscript out of range`. + +## Properties + +### LastError +{: .no_toc } + +The text of the last failure that a member of this object recorded, or an empty string when none did. **String**, read-only. + +Syntax: *object*.**LastError** + +The text stays after later calls succeed, and the object has no way to clear it; a new **cAesHelper** starts with an empty string. A **cAes** has a **cAesHelper** of its own, and its [**LastError**](cAes#lasterror) property returns the text of that object. + +A member that fails by an error ends with the text ` exception: ` and the description of the error. For the members that raise **Unspecified error**, the description is `Unspecified error`, and this text replaces any more specific text that the member had set before it assigned **Nothing**. These are the texts that remain: + +| Member | Text after a failure | +|---|---| +| [**Base64Decode**](#base64decode) | `Base64 decoding exception: Unspecified error` | +| [**Base64Encode**](#base64encode) | `Base64 encoding exception: ` and the description, such as `Subscript out of range` | +| [**StringToUtf8Bytes**](#stringtoutf8bytes) | `String conversion exception: Unspecified error` | +| [**Utf8BytesToString**](#utf8bytestostring) | `Byte conversion exception: ` and the description, such as `Subscript out of range` | +| [**ApplyPKCS7Padding**](#applypkcs7padding) | `PKCS7 padding exception: Unspecified error` | +| [**RemovePKCS7Padding**](#removepkcs7padding) | `PKCS7 unpadding exception: Unspecified error` | +| [**RemoveZeroPadding**](#removezeropadding) | `Zero unpadding exception: Unspecified error` | +| [**ApplyZeroPadding**](#applyzeropadding), [**IsArrayEmpty**](#isarrayempty) | none | + +The source of the class has more specific texts, such as `Invalid PKCS7 padding length: 102`, `Padding length exceeds data length` and `PKCS7 padding validation failed` for the padding, and `Failed to get Base64 decoding length` for Base64 text. They are set just before the member assigns **Nothing**, so the text above replaces them and a program does not see them. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte +Dim result() As Byte + +data = StrConv("0123456789abcdef", vbFromUnicode) ' the last byte is 102 +On Error Resume Next +result = helper.RemovePKCS7Padding(data) +Debug.Print Err.Number ' -2147467259 +Debug.Print Err.Description ' Unspecified error +Debug.Print helper.IsArrayEmpty(result) ' True +Debug.Print helper.LastError ' PKCS7 unpadding exception: Unspecified error +``` + +## Methods + +### ApplyPKCS7Padding +{: .no_toc } + +Returns a copy of a byte array with PKCS #7 padding added. + +Syntax: *object*.**ApplyPKCS7Padding** ( *data*, *blockSize* ) **As Byte()** + +*data* +: *required* A **Byte** array, passed by reference. The function does not change it. + +*blockSize* +: *required* A **Long**, the block size in bytes. [**cAes**](cAes) passes 16. The padding bytes hold their own count, so the value must be at most 255. + +The function adds from 1 to *blockSize* bytes, to make the length a multiple of *blockSize*. Each added byte holds the number of bytes added. Data whose length is already a multiple of *blockSize* gets a whole block, *blockSize* bytes holding the value *blockSize*. The result is a new array with index 0 as its first element. + +An array that was never sized raises **Unspecified error**, as [Failures](#failures) describes. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte +Dim padded() As Byte + +data = StrConv("Hello", vbFromUnicode) +padded = helper.ApplyPKCS7Padding(data, 16) +Debug.Print UBound(padded) + 1 ' 16 +Debug.Print Hex$(padded(5)) ' B +Debug.Print Hex$(padded(15)) ' B + +data = StrConv("0123456789abcdef", vbFromUnicode) +padded = helper.ApplyPKCS7Padding(data, 16) +Debug.Print UBound(padded) + 1 ' 32 +Debug.Print Hex$(padded(16)) ' 10 +``` + +### ApplyZeroPadding +{: .no_toc } + +Returns a copy of a byte array with zero bytes added. + +Syntax: *object*.**ApplyZeroPadding** ( *data*, *blockSize* ) **As Byte()** + +*data* +: *required* A **Byte** array, passed by reference. The function does not change it. + +*blockSize* +: *required* A **Long**, the block size in bytes. [**cAes**](cAes) passes 16. + +The function adds from 1 to *blockSize* zero bytes, to make the length a multiple of *blockSize*. Data whose length is already a multiple of *blockSize* gets a whole block of zero bytes. An array that was never sized gives one block of *blockSize* zero bytes, and no error. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte +Dim padded() As Byte + +data = StrConv("Hello", vbFromUnicode) +padded = helper.ApplyZeroPadding(data, 16) +Debug.Print UBound(padded) + 1 ' 16 + +data = StrConv("0123456789abcdef", vbFromUnicode) +padded = helper.ApplyZeroPadding(data, 16) +Debug.Print UBound(padded) + 1 ' 32 +``` + +### Base64Decode +{: .no_toc } + +Converts Base64 text to the bytes it stands for. + +Syntax: *object*.**Base64Decode** ( *base64* ) **As Byte()** + +*base64* +: *required* A **String** of Base64 text. + +The function calls the Windows function `CryptStringToBinaryW` with the format `CRYPT_STRING_BASE64`, Base64 without a header. Line breaks and spaces in the text are skipped. The result is a new array with index 0 as its first element. + +An empty string and text that Windows cannot decode both raise **Unspecified error**, and [**LastError**](#lasterror) holds `Base64 decoding exception: Unspecified error`. + +### Base64Encode +{: .no_toc } + +Converts bytes to Base64 text. + +Syntax: *object*.**Base64Encode** ( *data* ) **As String** + +*data* +: *required* A **Byte** array, passed by reference. + +The function calls the Windows function `CryptBinaryToStringW` with the format `CRYPT_STRING_BASE64` and the flag `CRYPT_STRING_NOCRLF`. The result is Base64 text of the usual alphabet (`A`-`Z`, `a`-`z`, `0`-`9`, `+` and `/`) with `=` at the end when the last group is short, on one line, with no header and no line break. An array that was never sized gives an empty string, with no text in [**LastError**](#lasterror). + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte + +data = StrConv("Ma", vbFromUnicode) +Debug.Print helper.Base64Encode(data) ' TWE= +``` + +### IsArrayEmpty +{: .no_toc } + +Tests whether a **Byte** array has no elements. + +Syntax: *object*.**IsArrayEmpty** ( *arr* ) **As Boolean** + +*arr* +: *required* A **Byte** array variable, passed by reference. + +Returns **True** when the array has no elements, because its upper bound is lower than its lower bound or because it was never given a size. Returns **False** otherwise. The function does not raise an error and does not use [**LastError**](#lasterror). It leaves `Err.Number` at 9 when the array was never sized, because it tests the array with `UBound` under `On Error Resume Next` and does not clear the error. + +A program uses it to test the arrays that [**cAes**](cAes) keeps in its properties, such as [**Key**](cAes#key) and [**GcmTag**](cAes#gcmtag), when they may not be set. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim unsized() As Byte +Dim data(3) As Byte + +Debug.Print helper.IsArrayEmpty(unsized) ' True +Debug.Print Err.Number ' 9 +Debug.Print helper.IsArrayEmpty(data) ' False +``` + +### RemovePKCS7Padding +{: .no_toc } + +Returns a copy of a byte array without its PKCS #7 padding. + +Syntax: *object*.**RemovePKCS7Padding** ( *data* ) **As Byte()** + +*data* +: *required* A **Byte** array, passed by reference. The function does not change it. + +The function reads the last byte as the count of padding bytes. It checks that the count is from 1 to 16, that it is not greater than the length of the array, and that the last *count* bytes are all equal to it. The largest count is the constant **BLOCK_SIZE_128**, whatever block size the data was padded with; the function does not check that the length of the array is a multiple of 16. + +When a check fails, and when the padding is the whole array, the function raises **Unspecified error**, and [**LastError**](#lasterror) holds `PKCS7 unpadding exception: Unspecified error`. The check cannot tell padded data from other data that happens to end in valid padding bytes, so it does not prove that the data is intact. + +### RemoveZeroPadding +{: .no_toc } + +Returns a copy of a byte array without its trailing zero bytes. + +Syntax: *object*.**RemoveZeroPadding** ( *data* ) **As Byte()** + +*data* +: *required* A **Byte** array, passed by reference. The function does not change it. + +The function finds the last byte that is not zero and returns the array up to and including that byte. It does not know how many bytes were added, so it also removes zero bytes that belong to the data. An array that holds only zero bytes raises **Unspecified error**, and [**LastError**](#lasterror) holds `Zero unpadding exception: Unspecified error`. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data(3) As Byte +Dim padded() As Byte +Dim result() As Byte + +data(0) = 1 +data(1) = 2 ' data(2) and data(3) are zero +padded = helper.ApplyZeroPadding(data, 16) +result = helper.RemoveZeroPadding(padded) +Debug.Print UBound(padded) + 1 ' 16 +Debug.Print UBound(result) + 1 ' 2 +``` + +### StringToUtf8Bytes +{: .no_toc } + +Converts a string to the bytes of its UTF-8 encoding. + +Syntax: *object*.**StringToUtf8Bytes** ( *text* ) **As Byte()** + +*text* +: *required* A **String**. + +The function calls the Windows function `WideCharToMultiByte` with the UTF-8 code page. The result is a new array with index 0 as its first element, without a byte order mark and without a terminating zero byte. An empty string raises **Unspecified error**, and [**LastError**](#lasterror) holds `String conversion exception: Unspecified error`. + +[**EncryptString**](cAes#encryptstring) encodes its text with this function. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data() As Byte + +data = helper.StringToUtf8Bytes("caf" & ChrW(233)) ' cafe with an acute accent +Debug.Print UBound(data) + 1 ' 5 +Debug.Print Hex$(data(3)) ' C3 +Debug.Print Hex$(data(4)) ' A9 +``` + +### Utf8BytesToString +{: .no_toc } + +Converts UTF-8 bytes to a string. + +Syntax: *object*.**Utf8BytesToString** ( *bytes* ) **As String** + +*bytes* +: *required* A **Byte** array, passed by reference. + +The function calls the Windows function `MultiByteToWideChar` with the UTF-8 code page. A byte sequence that is not valid UTF-8 becomes the character U+FFFD. An array with no elements gives an empty string. An array that was never sized also gives an empty string, with `Err.Number` left at 9 and the [**LastError**](#lasterror) text `Byte conversion exception: Subscript out of range`. + +[**DecryptString**](cAes#decryptstring) decodes its result with this function. + +```tb check_build check_run +Dim helper As New cAesHelper +Dim data(1) As Byte +Dim text As String + +data(0) = &HC3 +data(1) = &HA9 +text = helper.Utf8BytesToString(data) +Debug.Print Len(text) ' 1 +Debug.Print AscW(text) ' 233 + +Dim bad(0) As Byte +bad(0) = &HFF ' not valid UTF-8 +text = helper.Utf8BytesToString(bad) +Debug.Print Len(text) ' 1 +Debug.Print Hex$(AscW(text) And &HFFFF&) ' FFFD +``` + +## See Also + +- [cAes](cAes) class -- encrypts and decrypts, and uses this class for the conversions and the padding +- [mAesConstants](mAesConstants) module -- the padding modes and the block size +- [TBMANLIB_Aes](.) package -- the landing page diff --git a/docs/Reference/Contributed/TBMANLIB_Aes/index.md b/docs/Reference/Contributed/TBMANLIB_Aes/index.md new file mode 100644 index 000000000..542371b73 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Aes/index.md @@ -0,0 +1,148 @@ +--- +title: TBMANLIB_Aes Package +parent: Experimental Packages +has_toc: false +exclude_from_docs: + - mWin32CNG + - mWin32CryptoAPI + - mAesUsage +indexed_from: 1.0.0.2 +permalink: /tB/Packages/Contributed/TBMANLIB_Aes/ +--- + +# TBMANLIB_Aes Package +{: .no_toc } + +**Author:** woeoio. + +The **TBMANLIB_Aes** contributed package encrypts and decrypts text and byte arrays with AES. Its class [**cAes**](cAes) has six cipher modes, named ECB, CBC, CFB, OFB, CTR and GCM, four padding modes, and keys of 128, 192 and 256 bits. The package calls two interfaces of Windows. The older Crypto API (`advapi32.dll`) does the modes named ECB and CBC, and Cryptography API: Next Generation, CNG (`bcrypt.dll`), does the other four. A second class, [**cAesHelper**](cAesHelper), holds the Base64, UTF-8 and padding functions that **cAes** uses, and the module [**mAesConstants**](mAesConstants) holds the enumerations and the constants. This section documents version 1.0.0.2, published on TWINSERV under the MIT licence. The package describes itself as "TBMAN: A crypto library contains AES (ECB/CBC/CFB/OFB/CTR/GCM)". + +Add the package to the project as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. The classes are then created with `New`. + +```tb check_build check_run +Dim aes As New cAes +Dim keyBytes() As Byte +Dim ivBytes() As Byte +Dim tag() As Byte +Dim cipherText As String + +aes.CipherMode = AesCipherMode.GCM +keyBytes = StrConv("0123456789abcdef", vbFromUnicode) ' 16 bytes: an AES-128 key +ivBytes = StrConv("unique nonce", vbFromUnicode) ' 12 bytes: the GCM nonce +aes.Key = keyBytes +aes.IV = ivBytes + +cipherText = aes.EncryptString("Hello, AES!") +tag = aes.GcmTag +Debug.Print cipherText ' 0PfWIAMiX5mbxhPnfrGRLg== +Debug.Print UBound(tag) + 1 ' 16 +Debug.Print aes.DecryptString(cipherText) ' Hello, AES! +``` + +The text that **EncryptString** returns is Base64 of the ciphertext alone. The program keeps the IV, and in GCM mode the tag, and sets them in the object that decrypts. + +> [!WARNING] +> In version 1.0.0.2 the name of a cipher mode is not always the mode that runs: **ECB** runs CBC, **CBC** runs ECB, and **OFB** and **CTR** run CFB. Neither **ECB** nor **CBC** uses the IV, so the same key and plaintext always give the same ciphertext, and the default mode, **CBC**, gives equal ciphertext blocks for equal plaintext blocks. A test that decrypts what it has encrypted does not show this. [Known problems in version 1.0.0.2](#known-problems-in-version-1002) lists this and the other defects. + +* TOC +{:toc} + +## Which Windows function does what + +| Mode | Interface | Functions | +|---|---|---| +| ECB, CBC | Crypto API | `CryptAcquireContext`, `CryptCreateHash`, `CryptHashData`, `CryptDeriveKey`, `CryptSetKeyParam`, `CryptEncrypt`, `CryptDecrypt` | +| CFB, OFB, CTR, GCM | CNG | `BCryptOpenAlgorithmProvider`, `BCryptSetProperty`, `BCryptGenerateSymmetricKey`, `BCryptEncrypt`, `BCryptDecrypt` | + +The conversion of text to Base64 and back uses `CryptBinaryToStringW` and `CryptStringToBinaryW` of `crypt32.dll`, and the conversion to UTF-8 and back uses `WideCharToMultiByte` and `MultiByteToWideChar` of `kernel32.dll`. The four CNG modes need CNG, which Windows has had since Windows Vista; the sources name Vista as the oldest version. + +## Known problems in version 1.0.0.2 + +- **The mode names.** **ECB** runs CBC with an IV of zero bytes, **CBC** runs ECB, **OFB** and **CTR** run CFB, and the CFB of the CNG modes has an 8-bit feedback. [Which mode runs](cAes#which-mode-runs) has the details and a sample for each. +- **The key of ECB and CBC.** These modes hash [**Key**](cAes#key) with SHA-256 and use the hash as the AES key, and Windows adds its padding to the output. Other AES software that is given the same key does not decrypt it, and the standard test vectors of NIST SP 800-38A for ECB and CBC do not come out. See [The Crypto API modes](cAes#the-crypto-api-modes) and [Reading the output with other software](cAes#reading-the-output-with-other-software). +- **Keys from text.** [**SetKeyFromString**](cAes#setkeyfromstring) cuts the text at the key length and extends a short text with zero bytes. There is no key derivation. See its warning. +- **The IV.** [**GenerateRandomIV**](cAes#generaterandomiv) takes its bytes from `Rnd`, whose state has 24 bits, so that it can return only 16,777,216 different IVs. The encrypted output never holds the IV. +- **GCM without an IV.** A GCM encryption with no IV set uses a random nonce that nothing keeps, and the data cannot be decrypted. See [GCM mode](cAes#gcm-mode). +- **Padding.** With **ZeroPadding** the decryption removes all trailing zero bytes of the data, and **ISO10126** is **NoPadding**. See [**AesPaddingMode**](mAesConstants#aespaddingmode) and [**PaddingMode**](cAes#paddingmode). +- **Data that is not checked.** Only GCM checks that the data is the data that was encrypted. A wrong key or a damaged ciphertext in another mode gives other bytes, or a failed decryption, and no sign of which. See [Data that is not checked](cAes#data-that-is-not-checked). +- **Failures have no reason.** **EncryptBytes** and **DecryptBytes** raise the error -2147467259, `Unspecified error`, whatever the cause, and **EncryptString** and **DecryptString** return an empty string. See [Failures](cAes#failures). +- **An empty additional string.** [**SetGcmAADFromString**](cAes#setgcmaadfromstring) with an empty string fails and keeps the data that was set before. + +## Classes and modules + +- [cAes](cAes) -- encrypts and decrypts strings and byte arrays with a key, an IV, a cipher mode and a padding mode +- [cAesHelper](cAesHelper) -- converts to and from Base64 and UTF-8, adds and removes padding, and tests an array for emptiness +- [mAesConstants](mAesConstants) -- the enumerations `AesCipherMode`, `AesKeySize` and `AesPaddingMode`, the sizes of keys and blocks, and the Windows constants + +## Properties (cAes) + +- [CipherMode](cAes#ciphermode) -- the cipher mode, **AesCipherMode**; the default is CBC +- [GcmAAD](cAes#gcmaad) -- the additional authenticated data of the GCM mode +- [GcmTag](cAes#gcmtag) -- the authentication tag that a GCM encryption makes and a GCM decryption checks +- [IsInitialized](cAes#isinitialized) -- whether the Windows key objects exist +- [IV](cAes#iv) -- the initialisation vector +- [Key](cAes#key) -- the key bytes +- [KeySize](cAes#keysize) -- the key length in bits, **AesKeySize**; the default is 256 +- [LastError](cAes#lasterror) -- the text of the last failure of a Base64, UTF-8 or padding function +- [PaddingMode](cAes#paddingmode) -- the padding, **AesPaddingMode**; the default is PKCS7 + +## Methods (cAes) + +- [Cleanup](cAes#cleanup) -- releases the Windows handles +- [DecryptBytes](cAes#decryptbytes) -- decrypts a byte array +- [DecryptString](cAes#decryptstring) -- decrypts Base64 text to a string +- [EncryptBytes](cAes#encryptbytes) -- encrypts a byte array +- [EncryptString](cAes#encryptstring) -- encrypts a string to Base64 text +- [GenerateRandomIV](cAes#generaterandomiv) -- fills the IV with 16 bytes from `Rnd` +- [SetGcmAADFromString](cAes#setgcmaadfromstring) -- sets the additional data from a string +- [SetKeyFromString](cAes#setkeyfromstring) -- sets the key from a string, cut or extended to the key length + +## Methods (cAesHelper) + +- [ApplyPKCS7Padding](cAesHelper#applypkcs7padding) -- adds PKCS #7 padding +- [ApplyZeroPadding](cAesHelper#applyzeropadding) -- adds zero bytes +- [Base64Decode](cAesHelper#base64decode) -- converts Base64 text to bytes +- [Base64Encode](cAesHelper#base64encode) -- converts bytes to Base64 text +- [IsArrayEmpty](cAesHelper#isarrayempty) -- tests whether a byte array has no elements +- [RemovePKCS7Padding](cAesHelper#removepkcs7padding) -- takes off PKCS #7 padding +- [RemoveZeroPadding](cAesHelper#removezeropadding) -- takes off trailing zero bytes +- [StringToUtf8Bytes](cAesHelper#stringtoutf8bytes) -- converts a string to UTF-8 bytes +- [Utf8BytesToString](cAesHelper#utf8bytestostring) -- converts UTF-8 bytes to a string + +## Declaration modules + +Two more public modules hold Windows declarations. They have no page of their own, since a program does not need them, but a project that references the package can call their members. + +**mWin32CNG** declares the CNG functions of `bcrypt.dll` that **cAes** calls, and others: `BCryptOpenAlgorithmProvider`, `BCryptCloseAlgorithmProvider`, `BCryptGetProperty`, `BCryptSetProperty`, `BCryptGenerateSymmetricKey`, `BCryptImportKey`, `BCryptDestroyKey`, `BCryptEncrypt` and `BCryptDecrypt`. Their parameters are `LongPtr` and `Long`, as in the C declarations. The module also holds the names of the algorithm, the chaining modes and the properties as **String** constants (`BCRYPT_AES_ALGORITHM` is `AES`, and `BCRYPT_CHAIN_MODE_GCM` is `ChainingModeGCM`), the type `BCRYPT_AUTH_MODE_INFO` that GCM needs, and five status codes (`STATUS_SUCCESS` and four errors). Three procedures belong to it: + +- **IsCNGAvailable** returns **True** when Windows opens the AES algorithm provider. +- **CNGErrorMessage** returns a message for a status code. For four of the status codes the message is a Chinese phrase followed by the English name in parentheses, and for any other code it is a short Chinese label and the code in hexadecimal. +- **InitGcmModeInfo** fills a `BCRYPT_AUTH_MODE_INFO` from the arrays of a nonce, additional data and a tag. **cAes** does not call it. + +**mWin32CryptoAPI** holds the Crypto API functions that **cAes** needs, and the UTF-8 conversions. The declarations of `advapi32.dll` and `crypt32.dll` are `Private`; the module exposes each through a `Public` function with the prefix `AES_`, such as `AES_CryptDeriveKey` and `AES_CryptEncrypt`, that passes its arguments on. `AES_CopyMemory` and `AES_ZeroMemory` do the same for `RtlMoveMemory` and `RtlZeroMemory`. The constant `CP_UTF8` is 65001, and `WideStringToUtf8Bytes` and `Utf8BytesToWideString` are the functions that [**StringToUtf8Bytes**](cAesHelper#stringtoutf8bytes) and [**Utf8BytesToString**](cAesHelper#utf8bytestostring) call. + +## The usage examples + +The module **mAesUsage** holds ten `Public Sub` procedures that show the package, and `RunAllExamples`, which calls them in the order below with an empty line between. They print with [`Debug.Print`](../../../Modules/Debug), so the text appears in the Debug Console of the IDE and a built program writes nothing. All use the key `MySecretKey12345`, set with **SetKeyFromString**, except **Example_KeySizes**, which uses `TestKeyForEncryption1234567890`. **Example_CFB**, **Example_OFB**, **Example_CTR** and **Example_GCM** call **GenerateRandomIV**, so their Base64 text changes from run to run. The Base64 text of the others stays the same from run to run, since neither of the two Crypto API modes uses the IV; **Example_CBC_Basic** and **Example_ECB** use the same key and text, and their two Base64 texts begin with the same block. + +| Procedure | Prints | +|---|---| +| **Example_CBC_Basic** | `=== Example 1: CBC Mode (CryptoAPI) ===`, the Base64 text of `Hello, AES Encryption!` after `Encrypted: `, and the decrypted text after `Decrypted: ` | +| **Example_ECB** | `=== Example 2: ECB Mode (CryptoAPI) ===` and the same two lines for the mode ECB, which has no IV set | +| **Example_CFB** | `=== Example 3: CFB Mode (CNG) ===` and the two lines for `Hello, AES CFB Mode!`; when CNG is not available, `CNG not available (requires Vista or later)` instead | +| **Example_OFB** | `=== Example 4: OFB Mode (CNG) ===` and the two lines for `Hello, AES OFB Mode!` | +| **Example_CTR** | `=== Example 5: CTR Mode (CNG) ===` and the two lines for `Hello, AES CTR Mode!` | +| **Example_GCM** | `=== Example 6: GCM Mode (CNG - Authenticated Encryption) ===`, the Base64 text, `Auth Tag: ` and 32 hexadecimal digits, and the decrypted text; the example sets the additional data `Additional authenticated data` | +| **Example_KeySizes** | `=== Example 7: Different Key Sizes ===` and the Base64 text of `Test message for AES-128`, `AES-192` and `AES-256`, one line each, after the label `AES-128: ` and so on | +| **Example_PaddingModes** | `=== Example 8: Different Padding Modes ===`, then `PKCS7: OK - Test message`, and the same line with `Zero`, `None` and `ISO10126` in place of `PKCS7` | +| **Example_BinaryData** | `=== Example 9: Binary Data Encryption ===`, `Encrypted bytes: 48`, `Decrypted bytes: 16` and `Data matches: True` for 16 bytes of data | +| **Example_API_Info** | `=== Example 10: API Backend Information ===`, `CNG Available: ` and **True** or **False**, `Mode -> API Backend:`, and six lines that name ECB and CBC as the Crypto API and the other four modes as CNG | + +**Example_PaddingModes** prints `OK` for all four padding modes, since **ISO10126** works as **NoPadding**. **Example_API_Info** prints the assignment of interfaces in the table [above](#which-windows-function-does-what), and it does not say which mode runs; see [Which mode runs](cAes#which-mode-runs). + +## See Also + +- [cAes](cAes) class -- the AES encryption class +- [cAesHelper](cAesHelper) class -- the Base64, UTF-8 and padding functions +- [mAesConstants](mAesConstants) module -- the enumerations and constants +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project diff --git a/docs/Reference/Contributed/TBMANLIB_Aes/mAesConstants.md b/docs/Reference/Contributed/TBMANLIB_Aes/mAesConstants.md new file mode 100644 index 000000000..b579e848a --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Aes/mAesConstants.md @@ -0,0 +1,155 @@ +--- +title: mAesConstants +parent: TBMANLIB_Aes Package +permalink: /tB/Packages/Contributed/TBMANLIB_Aes/mAesConstants +has_toc: false +--- + +# mAesConstants module +{: .no_toc } + +Holds the three enumerations of the package, the sizes of AES keys and blocks, the version string, and the Windows constants that [**cAes**](cAes) and [**cAesHelper**](cAesHelper) pass to the Crypto API. + +The enumerations are the types of the [**CipherMode**](cAes#ciphermode), [**KeySize**](cAes#keysize) and [**PaddingMode**](cAes#paddingmode) properties of **cAes** and of the parameters of its constructor. A member is written with its enumeration, as in `AesCipherMode.GCM`. The constants are written with the module, as in `mAesConstants.KEY_SIZE_256`. + +```tb check_build check_run +Debug.Print AesCipherMode.GCM ' 6 +Debug.Print AesPaddingMode.NoPadding ' 3 +Debug.Print AesKeySize.KeySize_192 ' 192 +Debug.Print mAesConstants.KEY_SIZE_192 ' 24 +Debug.Print mAesConstants.BLOCK_SIZE_128 ' 16 +Debug.Print mAesConstants.VERSION ' 1.0.0 +``` + +* TOC +{:toc} + +## Enumerations + +### AesCipherMode +{: .no_toc } + +The cipher mode that a [**cAes**](cAes) uses. Default: **CBC**. + +| Member | Value | Windows interface | Runs as | +|---|---:|---|---| +| **ECB** | 1 | Crypto API | CBC, with an IV of zero bytes | +| **CBC** | 2 | Crypto API | ECB | +| **CFB** | 3 | CNG | CFB with an 8-bit feedback | +| **OFB** | 4 | CNG | CFB with an 8-bit feedback | +| **CTR** | 5 | CNG | CFB with an 8-bit feedback | +| **GCM** | 6 | CNG | GCM, with a 12-byte nonce and a 16-byte tag | + +> [!WARNING] +> In version 1.0.0.2 the name of a mode is not always the mode that runs. **ECB** runs CBC with an IV of zero bytes, **CBC** runs ECB, and **OFB** and **CTR** run CFB. Neither **ECB** nor **CBC** uses the IV that the program sets, so the same key and plaintext always give the same ciphertext, and the default mode, **CBC**, gives equal ciphertext blocks for equal plaintext blocks. Encryption and decryption agree with each other, so a round trip succeeds and shows nothing. [**cAes**](cAes#which-mode-runs) describes each mode in detail. + +The Crypto API is the older interface of Windows (`advapi32.dll`), and CNG, Cryptography API: Next Generation, the newer one (`bcrypt.dll`). **cAes** uses the Crypto API for **ECB** and **CBC**, and CNG for the other four modes. + +### AesKeySize +{: .no_toc } + +The length of the AES key, in bits. Default: **KeySize_256**. + +| Member | Value | Key length | +|---|---:|---| +| **KeySize_128** | 128 | 16 bytes | +| **KeySize_192** | 192 | 24 bytes | +| **KeySize_256** | 256 | 32 bytes | + +The value is the number of bits, and not an index. [**cAes**](cAes) reads it in two places only. [**SetKeyFromString**](cAes#setkeyfromstring) makes a key of the matching number of bytes, and the two modes that use the Crypto API choose from it the AES algorithm of the key they derive. The four modes that use CNG take the length of the [**Key**](cAes#key) array instead, and ignore **KeySize**; see [**KeySize**](cAes#keysize). + +### AesPaddingMode +{: .no_toc } + +How [**cAes**](cAes) fills the last block of the data before it encrypts, and how it takes the filling off after it decrypts. Default: **PKCS7**. + +| Member | Value | Encryption | Decryption | +|---|---:|---|---| +| **PKCS7** | 1 | Adds 1 to 16 bytes, each holding the number of bytes added. Data that already fills its blocks gets a whole block of 16 bytes of the value 16. | Takes off the bytes the last byte counts, after it checks that the last byte is from 1 to 16 and that the bytes it counts are all equal to it. Data that fails the check fails the decryption. | +| **ZeroPadding** | 2 | Adds 1 to 16 zero bytes. Data that already fills its blocks gets a whole block of 16 zero bytes. | Takes off every zero byte at the end of the data, including those that belong to the data. | +| **NoPadding** | 3 | Adds nothing. | Takes nothing off. | +| **ISO10126** | 4 | Adds nothing, as **NoPadding**. | Takes nothing off, as **NoPadding**. | + +> [!WARNING] +> **ISO10126** is not implemented in version 1.0.0.2. It behaves as **NoPadding**, and no error says so. The comment in the source calls the member "ISO/IEC 7816-4 padding", a scheme that differs from ISO 10126, and neither is applied. + +The functions that add and take off **PKCS7** and **ZeroPadding** padding are public members of [**cAesHelper**](cAesHelper). The padding of the data is only part of what the Crypto API modes do: Windows adds its own padding to the output of **ECB** and **CBC**, whatever the padding mode is. See [**PaddingMode**](cAes#paddingmode). + +## Package constants + +### BLOCK_SIZE_128 +{: .no_toc } + +The size of an AES block in bytes: 16, a **Long**. [**cAes**](cAes) uses it as the block size for padding and as the length of a generated IV, and [**RemovePKCS7Padding**](cAesHelper#removepkcs7padding) of **cAesHelper** uses it as the largest padding count it accepts. + +### KEY_SIZE_128 +{: .no_toc } + +The length in bytes of a 128-bit key: 16, a **Long**. [**SetKeyFromString**](cAes#setkeyfromstring) makes a key of this length when [**KeySize**](cAes#keysize) is **KeySize_128**. + +### KEY_SIZE_192 +{: .no_toc } + +The length in bytes of a 192-bit key: 24, a **Long**. + +### KEY_SIZE_256 +{: .no_toc } + +The length in bytes of a 256-bit key: 32, a **Long**. + +### VERSION +{: .no_toc } + +The text `1.0.0`, a **String**. It is the version in the headers of the source files. The version of the package is 1.0.0.2, and **VERSION** is not used by any code of the package. + +## Windows constants + +These constants have the names and, with five exceptions, the values of the constants that the Windows header `wincrypt.h` defines. **cAes** and **cAesHelper** pass them to the Crypto API functions. A program does not need them. + +| Constant | Value | Names | Used by the package | +|---|---:|---|:---:| +| **PROV_RSA_AES** | 24 | The provider type of the Microsoft Enhanced RSA and AES Cryptographic Provider | yes | +| **PROV_RSA_FULL** | 1 | The provider type of the RSA full provider | no | +| **CRYPT_EXPORTABLE** | &H1 | A flag of `CryptDeriveKey`: the key can be exported | yes | +| **CRYPT_NEWKEYSET** | &H8 | A flag of `CryptAcquireContext`: create the default key container | yes | +| **CALG_AES_128** | 26126 | The algorithm identifier of AES with a 128-bit key | yes | +| **CALG_AES_192** | 26127 | The algorithm identifier of AES with a 192-bit key | yes | +| **CALG_AES_256** | 26128 | The algorithm identifier of AES with a 256-bit key | yes | +| **CALG_SHA_256** | 32780 | The algorithm identifier of SHA-256 | yes | +| **CALG_MD5** | 32771 | The algorithm identifier of MD5 | no | +| **CRYPT_MODE_ECB** | 1 | The `KP_MODE` value for ECB | yes | +| **CRYPT_MODE_CBC** | 2 | The `KP_MODE` value for CBC | yes | +| **CRYPT_MODE_CFB** | 3 | The `KP_MODE` value for CFB | in code that never runs | +| **CRYPT_MODE_OFB** | 4 | The `KP_MODE` value for OFB | in code that never runs | +| **KP_IV** | 1 | The key parameter that holds the IV | yes | +| **KP_PADDING** | 3 | The key parameter that holds the padding method | no | +| **KP_MODE** | 4 | The key parameter that holds the cipher mode | yes | +| **KP_BLOCKLEN** | 8 | The key parameter that holds the block length in bits | yes | +| **PKCS5_PADDING** | 1 | The `KP_PADDING` value for PKCS #5 | no | +| **ZERO_PADDING** | 2 | The `KP_PADDING` value for zero padding | no | +| **HP_HASHVAL** | 2 | The hash parameter that holds the hash value | no | +| **HP_HASHSIZE** | 4 | The hash parameter that holds the size of the hash value | no | +| **AT_KEYEXCHANGE** | 1 | The key specification for a key exchange key | no | +| **AT_SIGNATURE** | 2 | The key specification for a signature key | no | +| **CRYPT_STRING_BASE64** | &H1 | The `CryptBinaryToString` and `CryptStringToBinary` format: Base64 without a header | yes | +| **CRYPT_STRING_NOCRLF** | &H40000000 | The `CryptBinaryToString` flag: no line breaks | yes | + +All are **Long** constants. They are meant for the Crypto API functions that the module **mWin32CryptoAPI** wraps, described on the [landing page](.#declaration-modules). + +Five values differ from `wincrypt.h`: + +| Constant | Package value | Windows value | +|---|---:|---:| +| **CRYPT_MODE_ECB** | 1 | 2 | +| **CRYPT_MODE_CBC** | 2 | 1 | +| **CRYPT_MODE_CFB** | 3 | 4 | +| **CRYPT_MODE_OFB** | 4 | 3 | +| **ZERO_PADDING** | 2 | 3 | + +**cAes** gives the value of **CRYPT_MODE_ECB** to the Crypto API when the mode is **ECB**, and the Crypto API reads 1 as CBC. That is why the modes **ECB** and **CBC** are swapped, as the warning under [AesCipherMode](#aesciphermode) says. Nothing in the package reads **ZERO_PADDING**. + +## See Also + +- [cAes](cAes) class -- encrypts and decrypts with these modes and sizes +- [cAesHelper](cAesHelper) class -- adds and takes off the padding, and converts to Base64 and UTF-8 +- [TBMANLIB_Aes](.) package -- the landing page diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 65dbe8f63..2aaca4fe1 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -13,6 +13,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.2 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. diff --git a/docs/_book.yml b/docs/_book.yml index 05878f27f..b9461f1ee 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -599,6 +599,13 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB_Aes Package + subtitle: AES encryption of text and byte arrays, through the Windows Crypto API and CNG + landing_page: /tB/Packages/Contributed/TBMANLIB_Aes/ + page: /tB/Packages/Contributed/TBMANLIB_Aes/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Xml Package subtitle: XML documents parsed, edited and written, in twinBASIC code landing_page: /tB/Packages/Contributed/TBMANLIB_Xml/ diff --git a/indexer/snapshots/contributed/TBMANLIB_Aes/api.json b/indexer/snapshots/contributed/TBMANLIB_Aes/api.json new file mode 100644 index 000000000..30c245c4e --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB_Aes/api.json @@ -0,0 +1,311 @@ +{ + "format": 3, + "package": "TBMANLIB_Aes", + "project": "TBMANLIB_Aes", + "version": "1.0.0.2", + "types": [ + { + "name": "cAes", + "kind": "class", + "vis": "public", + "file": "Core/cAes.twin", + "signature": "Public Class cAes", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"CipherMode","kind":"property","vis":"public","signature":"Public Property Get CipherMode() As AesCipherMode","returnType":"AesCipherMode","accessor":"Get"}, + {"name":"CipherMode","kind":"property","vis":"public","signature":"Public Property Let CipherMode(ByVal value As AesCipherMode)","accessor":"Let"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Cleanup","kind":"sub","vis":"public","signature":"Public Sub Cleanup()"}, + {"name":"DecryptBytes","kind":"function","vis":"public","signature":"Public Function DecryptBytes(ByRef cipherBytes() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DecryptString","kind":"function","vis":"public","signature":"Public Function DecryptString(ByVal cipherText As String) As String","returnType":"String"}, + {"name":"DoDecrypt","kind":"function","vis":"private","signature":"Private Function DoDecrypt(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoDecryptCNG","kind":"function","vis":"private","signature":"Private Function DoDecryptCNG(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoDecryptCNG_GCM","kind":"function","vis":"private","signature":"Private Function DoDecryptCNG_GCM(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoDecryptCryptoAPI","kind":"function","vis":"private","signature":"Private Function DoDecryptCryptoAPI(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoEncrypt","kind":"function","vis":"private","signature":"Private Function DoEncrypt(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoEncryptCNG","kind":"function","vis":"private","signature":"Private Function DoEncryptCNG(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoEncryptCNG_GCM","kind":"function","vis":"private","signature":"Private Function DoEncryptCNG_GCM(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"DoEncryptCryptoAPI","kind":"function","vis":"private","signature":"Private Function DoEncryptCryptoAPI(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"EncryptBytes","kind":"function","vis":"public","signature":"Public Function EncryptBytes(ByRef plainBytes() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"EncryptString","kind":"function","vis":"public","signature":"Public Function EncryptString(ByVal plainText As String) As String","returnType":"String"}, + {"name":"GcmAAD","kind":"property","vis":"public","signature":"Public Property Get GcmAAD() As Byte()","returnType":"Byte()","accessor":"Get"}, + {"name":"GcmAAD","kind":"property","vis":"public","signature":"Public Property Let GcmAAD(ByRef value() As Byte)","accessor":"Let"}, + {"name":"GcmTag","kind":"property","vis":"public","signature":"Public Property Get GcmTag() As Byte()","returnType":"Byte()","accessor":"Get"}, + {"name":"GcmTag","kind":"property","vis":"public","signature":"Public Property Let GcmTag(ByRef value() As Byte)","accessor":"Let"}, + {"name":"GenerateRandomIV","kind":"function","vis":"public","signature":"Public Function GenerateRandomIV() As Byte()","returnType":"Byte()"}, + {"name":"GetKeyBytes","kind":"function","vis":"private","signature":"Private Function GetKeyBytes(ByVal keyString As String, ByVal keySize As Long, ByVal encoding As Long) As Byte()","returnType":"Byte()"}, + {"name":"IV","kind":"property","vis":"public","signature":"Public Property Get IV() As Byte()","returnType":"Byte()","accessor":"Get"}, + {"name":"IV","kind":"property","vis":"public","signature":"Public Property Let IV(ByRef value() As Byte)","accessor":"Let"}, + {"name":"InitializeCNGProvider","kind":"function","vis":"private","signature":"Private Function InitializeCNGProvider() As Boolean","returnType":"Boolean"}, + {"name":"InitializeCryptoAPIProvider","kind":"function","vis":"private","signature":"Private Function InitializeCryptoAPIProvider() As Boolean","returnType":"Boolean"}, + {"name":"IsInitialized","kind":"property","vis":"public","signature":"Public Property Get IsInitialized() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Key","kind":"property","vis":"public","signature":"Public Property Get Key() As Byte()","returnType":"Byte()","accessor":"Get"}, + {"name":"Key","kind":"property","vis":"public","signature":"Public Property Let Key(ByRef value() As Byte)","accessor":"Let"}, + {"name":"KeySize","kind":"property","vis":"public","signature":"Public Property Get KeySize() As AesKeySize","returnType":"AesKeySize","accessor":"Get"}, + {"name":"KeySize","kind":"property","vis":"public","signature":"Public Property Let KeySize(ByVal value As AesKeySize)","accessor":"Let"}, + {"name":"LastError","kind":"property","vis":"public","signature":"Public Property Get LastError() As String","returnType":"String","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal keySize As AesKeySize, ByVal cipherMode As AesCipherMode, ByVal paddingMode As AesPaddingMode)"}, + {"name":"PaddingMode","kind":"property","vis":"public","signature":"Public Property Get PaddingMode() As AesPaddingMode","returnType":"AesPaddingMode","accessor":"Get"}, + {"name":"PaddingMode","kind":"property","vis":"public","signature":"Public Property Let PaddingMode(ByVal value As AesPaddingMode)","accessor":"Let"}, + {"name":"SetGcmAADFromString","kind":"sub","vis":"public","signature":"Public Sub SetGcmAADFromString(ByVal aadString As String, Optional ByVal encoding As Long = vbFromUnicode)"}, + {"name":"SetKeyFromString","kind":"sub","vis":"public","signature":"Public Sub SetKeyFromString(ByVal keyString As String, Optional ByVal encoding As Long = vbFromUnicode)"}, + {"name":"m_CipherMode","kind":"field","vis":"private","signature":"Private m_CipherMode As AesCipherMode"}, + {"name":"m_GcmAAD","kind":"field","vis":"private","signature":"Private m_GcmAAD() As Byte"}, + {"name":"m_GcmTag","kind":"field","vis":"private","signature":"Private m_GcmTag() As Byte"}, + {"name":"m_Helper","kind":"field","vis":"private","signature":"Private m_Helper As cAesHelper"}, + {"name":"m_IV","kind":"field","vis":"private","signature":"Private m_IV() As Byte"}, + {"name":"m_Initialized","kind":"field","vis":"private","signature":"Private m_Initialized As Boolean"}, + {"name":"m_Key","kind":"field","vis":"private","signature":"Private m_Key() As Byte"}, + {"name":"m_KeySize","kind":"field","vis":"private","signature":"Private m_KeySize As AesKeySize"}, + {"name":"m_PaddingMode","kind":"field","vis":"private","signature":"Private m_PaddingMode As AesPaddingMode"}, + {"name":"m_hCNGAlg","kind":"field","vis":"private","signature":"Private m_hCNGAlg As LongPtr"}, + {"name":"m_hCNGKey","kind":"field","vis":"private","signature":"Private m_hCNGKey As LongPtr"}, + {"name":"m_hKey","kind":"field","vis":"private","signature":"Private m_hKey As LongPtr"}, + {"name":"m_hProv","kind":"field","vis":"private","signature":"Private m_hProv As LongPtr"} + ] + }, + { + "name": "cAesHelper", + "kind": "class", + "vis": "public", + "file": "Core/cAesHelper.twin", + "signature": "Public Class cAesHelper", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"ApplyPKCS7Padding","kind":"function","vis":"public","signature":"Public Function ApplyPKCS7Padding(ByRef data() As Byte, ByVal blockSize As Long) As Byte()","returnType":"Byte()"}, + {"name":"ApplyZeroPadding","kind":"function","vis":"public","signature":"Public Function ApplyZeroPadding(ByRef data() As Byte, ByVal blockSize As Long) As Byte()","returnType":"Byte()"}, + {"name":"Base64Decode","kind":"function","vis":"public","signature":"Public Function Base64Decode(ByVal base64 As String) As Byte()","returnType":"Byte()"}, + {"name":"Base64Encode","kind":"function","vis":"public","signature":"Public Function Base64Encode(ByRef data() As Byte) As String","returnType":"String"}, + {"name":"IsArrayEmpty","kind":"function","vis":"public","signature":"Public Function IsArrayEmpty(ByRef arr() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"LastError","kind":"property","vis":"public","signature":"Public Property Get LastError() As String","returnType":"String","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"RemovePKCS7Padding","kind":"function","vis":"public","signature":"Public Function RemovePKCS7Padding(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"RemoveZeroPadding","kind":"function","vis":"public","signature":"Public Function RemoveZeroPadding(ByRef data() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"StringToUtf8Bytes","kind":"function","vis":"public","signature":"Public Function StringToUtf8Bytes(ByVal text As String) As Byte()","returnType":"Byte()"}, + {"name":"Utf8BytesToString","kind":"function","vis":"public","signature":"Public Function Utf8BytesToString(ByRef bytes() As Byte) As String","returnType":"String"}, + {"name":"m_LastError","kind":"field","vis":"private","signature":"Private m_LastError As String"} + ] + }, + { + "name": "mAesConstants", + "kind": "module", + "vis": "public", + "file": "Core/mAesConstants.twin", + "signature": "Public Module mAesConstants", + "members": [ + {"name":"AT_KEYEXCHANGE","kind":"const","vis":"public","signature":"Public Const AT_KEYEXCHANGE As Long = 1"}, + {"name":"AT_SIGNATURE","kind":"const","vis":"public","signature":"Public Const AT_SIGNATURE As Long = 2"}, + {"name":"BLOCK_SIZE_128","kind":"const","vis":"public","signature":"Public Const BLOCK_SIZE_128 As Long = 16"}, + {"name":"CALG_AES_128","kind":"const","vis":"public","signature":"Public Const CALG_AES_128 As Long = 26126"}, + {"name":"CALG_AES_192","kind":"const","vis":"public","signature":"Public Const CALG_AES_192 As Long = 26127"}, + {"name":"CALG_AES_256","kind":"const","vis":"public","signature":"Public Const CALG_AES_256 As Long = 26128"}, + {"name":"CALG_MD5","kind":"const","vis":"public","signature":"Public Const CALG_MD5 As Long = 32771"}, + {"name":"CALG_SHA_256","kind":"const","vis":"public","signature":"Public Const CALG_SHA_256 As Long = 32780"}, + {"name":"CRYPT_EXPORTABLE","kind":"const","vis":"public","signature":"Public Const CRYPT_EXPORTABLE As Long = &H1"}, + {"name":"CRYPT_MODE_CBC","kind":"const","vis":"public","signature":"Public Const CRYPT_MODE_CBC As Long = 2"}, + {"name":"CRYPT_MODE_CFB","kind":"const","vis":"public","signature":"Public Const CRYPT_MODE_CFB As Long = 3"}, + {"name":"CRYPT_MODE_ECB","kind":"const","vis":"public","signature":"Public Const CRYPT_MODE_ECB As Long = 1"}, + {"name":"CRYPT_MODE_OFB","kind":"const","vis":"public","signature":"Public Const CRYPT_MODE_OFB As Long = 4"}, + {"name":"CRYPT_NEWKEYSET","kind":"const","vis":"public","signature":"Public Const CRYPT_NEWKEYSET As Long = &H8"}, + {"name":"CRYPT_STRING_BASE64","kind":"const","vis":"public","signature":"Public Const CRYPT_STRING_BASE64 As Long = &H1"}, + {"name":"CRYPT_STRING_NOCRLF","kind":"const","vis":"public","signature":"Public Const CRYPT_STRING_NOCRLF As Long = &H40000000"}, + {"name":"HP_HASHSIZE","kind":"const","vis":"public","signature":"Public Const HP_HASHSIZE As Long = 4"}, + {"name":"HP_HASHVAL","kind":"const","vis":"public","signature":"Public Const HP_HASHVAL As Long = 2"}, + {"name":"KEY_SIZE_128","kind":"const","vis":"public","signature":"Public Const KEY_SIZE_128 As Long = 16"}, + {"name":"KEY_SIZE_192","kind":"const","vis":"public","signature":"Public Const KEY_SIZE_192 As Long = 24"}, + {"name":"KEY_SIZE_256","kind":"const","vis":"public","signature":"Public Const KEY_SIZE_256 As Long = 32"}, + {"name":"KP_BLOCKLEN","kind":"const","vis":"public","signature":"Public Const KP_BLOCKLEN As Long = 8"}, + {"name":"KP_IV","kind":"const","vis":"public","signature":"Public Const KP_IV As Long = 1"}, + {"name":"KP_MODE","kind":"const","vis":"public","signature":"Public Const KP_MODE As Long = 4"}, + {"name":"KP_PADDING","kind":"const","vis":"public","signature":"Public Const KP_PADDING As Long = 3"}, + {"name":"PKCS5_PADDING","kind":"const","vis":"public","signature":"Public Const PKCS5_PADDING As Long = 1"}, + {"name":"PROV_RSA_AES","kind":"const","vis":"public","signature":"Public Const PROV_RSA_AES As Long = 24"}, + {"name":"PROV_RSA_FULL","kind":"const","vis":"public","signature":"Public Const PROV_RSA_FULL As Long = 1"}, + {"name":"VERSION","kind":"const","vis":"public","signature":"Public Const VERSION As String = \"1.0.0\""}, + {"name":"ZERO_PADDING","kind":"const","vis":"public","signature":"Public Const ZERO_PADDING As Long = 2"} + ] + }, + { + "name": "AesCipherMode", + "kind": "enum", + "in": "mAesConstants", + "vis": "public", + "file": "Core/mAesConstants.twin", + "signature": "Public Enum AesCipherMode", + "values": [ + {"name":"ECB","value":"1"}, + {"name":"CBC","value":"2"}, + {"name":"CFB","value":"3"}, + {"name":"OFB","value":"4"}, + {"name":"CTR","value":"5"}, + {"name":"GCM","value":"6"} + ] + }, + { + "name": "AesKeySize", + "kind": "enum", + "in": "mAesConstants", + "vis": "public", + "file": "Core/mAesConstants.twin", + "signature": "Public Enum AesKeySize", + "values": [ + {"name":"KeySize_128","value":"128"}, + {"name":"KeySize_192","value":"192"}, + {"name":"KeySize_256","value":"256"} + ] + }, + { + "name": "AesPaddingMode", + "kind": "enum", + "in": "mAesConstants", + "vis": "public", + "file": "Core/mAesConstants.twin", + "signature": "Public Enum AesPaddingMode", + "values": [ + {"name":"PKCS7","value":"1"}, + {"name":"ZeroPadding","value":"2"}, + {"name":"NoPadding","value":"3"}, + {"name":"ISO10126","value":"4"} + ] + }, + { + "name": "mAesUsage", + "kind": "module", + "vis": "public", + "file": "mAesUsage.twin", + "signature": "Public Module mAesUsage", + "members": [ + {"name":"BytesToHex","kind":"function","vis":"private","signature":"Private Function BytesToHex(ByRef bytes() As Byte) As String","returnType":"String"}, + {"name":"Example_API_Info","kind":"sub","vis":"public","signature":"Public Sub Example_API_Info()"}, + {"name":"Example_BinaryData","kind":"sub","vis":"public","signature":"Public Sub Example_BinaryData()"}, + {"name":"Example_CBC_Basic","kind":"sub","vis":"public","signature":"Public Sub Example_CBC_Basic()"}, + {"name":"Example_CFB","kind":"sub","vis":"public","signature":"Public Sub Example_CFB()"}, + {"name":"Example_CTR","kind":"sub","vis":"public","signature":"Public Sub Example_CTR()"}, + {"name":"Example_ECB","kind":"sub","vis":"public","signature":"Public Sub Example_ECB()"}, + {"name":"Example_GCM","kind":"sub","vis":"public","signature":"Public Sub Example_GCM()"}, + {"name":"Example_KeySizes","kind":"sub","vis":"public","signature":"Public Sub Example_KeySizes()"}, + {"name":"Example_OFB","kind":"sub","vis":"public","signature":"Public Sub Example_OFB()"}, + {"name":"Example_PaddingModes","kind":"sub","vis":"public","signature":"Public Sub Example_PaddingModes()"}, + {"name":"RunAllExamples","kind":"sub","vis":"public","signature":"Public Sub RunAllExamples()"} + ] + }, + { + "name": "mWin32CNG", + "kind": "module", + "vis": "public", + "file": "Core/mWin32CNG.twin", + "signature": "Public Module mWin32CNG", + "members": [ + {"name":"BCRYPT_AES_ALGORITHM","kind":"const","vis":"public","signature":"Public Const BCRYPT_AES_ALGORITHM As String = \"AES\""}, + {"name":"BCRYPT_ALG_HANDLE_HMAC_FLAG","kind":"const","vis":"public","signature":"Public Const BCRYPT_ALG_HANDLE_HMAC_FLAG As Long = &H8"}, + {"name":"BCRYPT_AUTH_MODE_CHAIN_MODE_CCM","kind":"const","vis":"public","signature":"Public Const BCRYPT_AUTH_MODE_CHAIN_MODE_CCM As String = \"ChainingModeCCM\""}, + {"name":"BCRYPT_AUTH_MODE_CHAIN_MODE_GCM","kind":"const","vis":"public","signature":"Public Const BCRYPT_AUTH_MODE_CHAIN_MODE_GCM As String = \"ChainingModeGCM\""}, + {"name":"BCRYPT_AUTH_MODE_INFO_VERSION","kind":"const","vis":"public","signature":"Public Const BCRYPT_AUTH_MODE_INFO_VERSION As Long = 1"}, + {"name":"BCRYPT_AUTH_TAG_LENGTH","kind":"const","vis":"public","signature":"Public Const BCRYPT_AUTH_TAG_LENGTH As String = \"AuthTagLength\""}, + {"name":"BCRYPT_BLOCK_LENGTH","kind":"const","vis":"public","signature":"Public Const BCRYPT_BLOCK_LENGTH As String = \"BlockLength\""}, + {"name":"BCRYPT_CHAINING_MODE","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAINING_MODE As String = \"ChainingMode\""}, + {"name":"BCRYPT_CHAIN_MODE_CBC","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAIN_MODE_CBC As String = \"ChainingModeCBC\""}, + {"name":"BCRYPT_CHAIN_MODE_CCM","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAIN_MODE_CCM As String = \"ChainingModeCCM\""}, + {"name":"BCRYPT_CHAIN_MODE_CFB","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAIN_MODE_CFB As String = \"ChainingModeCFB\""}, + {"name":"BCRYPT_CHAIN_MODE_ECB","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAIN_MODE_ECB As String = \"ChainingModeECB\""}, + {"name":"BCRYPT_CHAIN_MODE_GCM","kind":"const","vis":"public","signature":"Public Const BCRYPT_CHAIN_MODE_GCM As String = \"ChainingModeGCM\""}, + {"name":"BCRYPT_EFFECTIVE_KEY_LENGTH","kind":"const","vis":"public","signature":"Public Const BCRYPT_EFFECTIVE_KEY_LENGTH As String = \"EffectiveKeyLength\""}, + {"name":"BCRYPT_KEY_DATA_BLOB","kind":"const","vis":"public","signature":"Public Const BCRYPT_KEY_DATA_BLOB As String = \"KeyDataBlob\""}, + {"name":"BCRYPT_KEY_LENGTH","kind":"const","vis":"public","signature":"Public Const BCRYPT_KEY_LENGTH As String = \"KeyLength\""}, + {"name":"BCRYPT_OBJECT_LENGTH","kind":"const","vis":"public","signature":"Public Const BCRYPT_OBJECT_LENGTH As String = \"ObjectLength\""}, + {"name":"BCRYPT_PROV_DISPATCH","kind":"const","vis":"public","signature":"Public Const BCRYPT_PROV_DISPATCH As Long = &H1"}, + {"name":"BCryptCloseAlgorithmProvider","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptCloseAlgorithmProvider Lib \"bcrypt.dll\" ( ByVal hAlgorithm As LongPtr, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptDecrypt","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptDecrypt Lib \"bcrypt.dll\" ( ByVal hKey As LongPtr, ByVal pbInput As LongPtr, ByVal cbInput As Long, ByVal pPaddingInfo As LongPtr, ByVal pbIV As LongPtr, ByVal cbIV As Long, ByVal pbOutput As LongPtr, ByVal cbOutput As Long, ByRef pcbResult As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptDestroyKey","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptDestroyKey Lib \"bcrypt.dll\" ( ByVal hKey As LongPtr ) As Long","returnType":"Long"}, + {"name":"BCryptEncrypt","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptEncrypt Lib \"bcrypt.dll\" ( ByVal hKey As LongPtr, ByVal pbInput As LongPtr, ByVal cbInput As Long, ByVal pPaddingInfo As LongPtr, ByVal pbIV As LongPtr, ByVal cbIV As Long, ByVal pbOutput As LongPtr, ByVal cbOutput As Long, ByRef pcbResult As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptGenerateSymmetricKey","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptGenerateSymmetricKey Lib \"bcrypt.dll\" ( ByVal hAlgorithm As LongPtr, ByRef phKey As LongPtr, ByVal pbKeyObject As LongPtr, ByVal cbKeyObject As Long, ByVal pbSecret As LongPtr, ByVal cbSecret As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptGetProperty","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptGetProperty Lib \"bcrypt.dll\" ( ByVal hObject As LongPtr, ByVal pszProperty As LongPtr, ByVal pbOutput As LongPtr, ByVal cbOutput As Long, ByRef pcbResult As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptImportKey","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptImportKey Lib \"bcrypt.dll\" ( ByVal hAlgorithm As LongPtr, ByVal hImportKey As LongPtr, ByVal pszBlobType As LongPtr, ByRef phKey As LongPtr, ByVal pbKeyObject As LongPtr, ByVal cbKeyObject As Long, ByVal pbInput As LongPtr, ByVal cbInput As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptOpenAlgorithmProvider","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptOpenAlgorithmProvider Lib \"bcrypt.dll\" ( ByRef phAlgorithm As LongPtr, ByVal pszAlgId As LongPtr, ByVal pszImplementation As LongPtr, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"BCryptSetProperty","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function BCryptSetProperty Lib \"bcrypt.dll\" ( ByVal hObject As LongPtr, ByVal pszProperty As LongPtr, ByVal pbInput As LongPtr, ByVal cbInput As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CNGErrorMessage","kind":"function","vis":"public","signature":"Public Function CNGErrorMessage(ByVal status As Long) As String","returnType":"String"}, + {"name":"InitGcmModeInfo","kind":"sub","vis":"public","signature":"Public Sub InitGcmModeInfo(ByRef info As BCRYPT_AUTH_MODE_INFO, ByRef nonce() As Byte, ByRef authData() As Byte, ByRef tag() As Byte, Optional ByVal tagLen As Long = 16)"}, + {"name":"IsCNGAvailable","kind":"function","vis":"public","signature":"Public Function IsCNGAvailable() As Boolean","returnType":"Boolean"}, + {"name":"STATUS_BUFFER_TOO_SMALL","kind":"const","vis":"public","signature":"Public Const STATUS_BUFFER_TOO_SMALL As Long = &HC0000023"}, + {"name":"STATUS_INVALID_HANDLE","kind":"const","vis":"public","signature":"Public Const STATUS_INVALID_HANDLE As Long = &HC0000008"}, + {"name":"STATUS_INVALID_PARAMETER","kind":"const","vis":"public","signature":"Public Const STATUS_INVALID_PARAMETER As Long = &HC000000D"}, + {"name":"STATUS_NOT_SUPPORTED","kind":"const","vis":"public","signature":"Public Const STATUS_NOT_SUPPORTED As Long = &HC00000BB"}, + {"name":"STATUS_SUCCESS","kind":"const","vis":"public","signature":"Public Const STATUS_SUCCESS As Long = 0"} + ] + }, + { + "name": "BCRYPT_AUTH_MODE_INFO", + "kind": "type", + "in": "mWin32CNG", + "vis": "public", + "file": "Core/mWin32CNG.twin", + "signature": "Public Type BCRYPT_AUTH_MODE_INFO", + "fields": [ + {"name":"cbSize","vis":"public","signature":"cbSize As Long"}, + {"name":"dwInfoVersion","vis":"public","signature":"dwInfoVersion As Long"}, + {"name":"pbNonce","vis":"public","signature":"pbNonce As LongPtr"}, + {"name":"cbNonce","vis":"public","signature":"cbNonce As Long"}, + {"name":"pbAuthData","vis":"public","signature":"pbAuthData As LongPtr"}, + {"name":"cbAuthData","vis":"public","signature":"cbAuthData As Long"}, + {"name":"pbTag","vis":"public","signature":"pbTag As LongPtr"}, + {"name":"cbTag","vis":"public","signature":"cbTag As Long"}, + {"name":"pbMacContext","vis":"public","signature":"pbMacContext As LongPtr"}, + {"name":"cbMacContext","vis":"public","signature":"cbMacContext As Long"}, + {"name":"cbAAD","vis":"public","signature":"cbAAD As Long"}, + {"name":"cbData","vis":"public","signature":"cbData As LongLong"}, + {"name":"dwFlags","vis":"public","signature":"dwFlags As Long"} + ], + "members": [] + }, + { + "name": "mWin32CryptoAPI", + "kind": "module", + "vis": "public", + "file": "Core/mWin32CryptoAPI.twin", + "signature": "Public Module mWin32CryptoAPI", + "members": [ + {"name":"AES_CopyMemory","kind":"sub","vis":"public","signature":"Public Sub AES_CopyMemory(ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long)"}, + {"name":"AES_CryptAcquireContext","kind":"function","vis":"public","signature":"Public Function AES_CryptAcquireContext(ByRef phProv As LongPtr, ByVal pszContainer As LongPtr, ByVal pszProvider As LongPtr, ByVal dwProvType As Long, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptBinaryToString","kind":"function","vis":"public","signature":"Public Function AES_CryptBinaryToString(ByVal pbBinary As LongPtr, ByVal cbBinary As Long, ByVal dwFlags As Long, ByVal pszString As LongPtr, ByRef pcchString As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptCreateHash","kind":"function","vis":"public","signature":"Public Function AES_CryptCreateHash(ByVal hProv As LongPtr, ByVal Algid As Long, ByVal hKey As LongPtr, ByVal dwFlags As Long, ByRef phHash As LongPtr) As Long","returnType":"Long"}, + {"name":"AES_CryptDecrypt","kind":"function","vis":"public","signature":"Public Function AES_CryptDecrypt(ByVal hKey As LongPtr, ByVal hHash As LongPtr, ByVal Final As Long, ByVal dwFlags As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptDeriveKey","kind":"function","vis":"public","signature":"Public Function AES_CryptDeriveKey(ByVal hProv As LongPtr, ByVal Algid As Long, ByVal hBaseData As LongPtr, ByVal dwFlags As Long, ByRef phKey As LongPtr) As Long","returnType":"Long"}, + {"name":"AES_CryptDestroyHash","kind":"function","vis":"public","signature":"Public Function AES_CryptDestroyHash(ByVal hHash As LongPtr) As Long","returnType":"Long"}, + {"name":"AES_CryptDestroyKey","kind":"function","vis":"public","signature":"Public Function AES_CryptDestroyKey(ByVal hKey As LongPtr) As Long","returnType":"Long"}, + {"name":"AES_CryptEncrypt","kind":"function","vis":"public","signature":"Public Function AES_CryptEncrypt(ByVal hKey As LongPtr, ByVal hHash As LongPtr, ByVal Final As Long, ByVal dwFlags As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwBufLen As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptGetHashParam","kind":"function","vis":"public","signature":"Public Function AES_CryptGetHashParam(ByVal hHash As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptGetKeyParam","kind":"function","vis":"public","signature":"Public Function AES_CryptGetKeyParam(ByVal hKey As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptHashData","kind":"function","vis":"public","signature":"Public Function AES_CryptHashData(ByVal hHash As LongPtr, ByVal pbData As LongPtr, ByVal dwDataLen As Long, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptReleaseContext","kind":"function","vis":"public","signature":"Public Function AES_CryptReleaseContext(ByVal hProv As LongPtr, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptSetKeyParam","kind":"function","vis":"public","signature":"Public Function AES_CryptSetKeyParam(ByVal hKey As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByVal dwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_CryptStringToBinary","kind":"function","vis":"public","signature":"Public Function AES_CryptStringToBinary(ByVal pszString As LongPtr, ByVal cchString As Long, ByVal dwFlags As Long, ByVal pbBinary As LongPtr, ByRef pcbBinary As Long, ByRef pdwSkip As Long, ByRef pdwFlags As Long) As Long","returnType":"Long"}, + {"name":"AES_ZeroMemory","kind":"sub","vis":"public","signature":"Public Sub AES_ZeroMemory(ByVal Destination As LongPtr, ByVal Length As Long)"}, + {"name":"CP_UTF8","kind":"const","vis":"public","signature":"Public Const CP_UTF8 As Long = 65001"}, + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" ( ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long )"}, + {"name":"CryptAcquireContext","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptAcquireContext Lib \"advapi32.dll\" Alias \"CryptAcquireContextW\" ( ByRef phProv As LongPtr, ByVal pszContainer As LongPtr, ByVal pszProvider As LongPtr, ByVal dwProvType As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptBinaryToString","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptBinaryToString Lib \"crypt32.dll\" Alias \"CryptBinaryToStringW\" ( ByVal pbBinary As LongPtr, ByVal cbBinary As Long, ByVal dwFlags As Long, ByVal pszString As LongPtr, ByRef pcchString As Long ) As Long","returnType":"Long"}, + {"name":"CryptCreateHash","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptCreateHash Lib \"advapi32.dll\" ( ByVal hProv As LongPtr, ByVal Algid As Long, ByVal hKey As LongPtr, ByVal dwFlags As Long, ByRef phHash As LongPtr ) As Long","returnType":"Long"}, + {"name":"CryptDecrypt","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptDecrypt Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal hHash As LongPtr, ByVal Final As Long, ByVal dwFlags As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long ) As Long","returnType":"Long"}, + {"name":"CryptDeriveKey","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptDeriveKey Lib \"advapi32.dll\" ( ByVal hProv As LongPtr, ByVal Algid As Long, ByVal hBaseData As LongPtr, ByVal dwFlags As Long, ByRef phKey As LongPtr ) As Long","returnType":"Long"}, + {"name":"CryptDestroyHash","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptDestroyHash Lib \"advapi32.dll\" ( ByVal hHash As LongPtr ) As Long","returnType":"Long"}, + {"name":"CryptDestroyKey","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptDestroyKey Lib \"advapi32.dll\" ( ByVal hKey As LongPtr ) As Long","returnType":"Long"}, + {"name":"CryptEncrypt","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptEncrypt Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal hHash As LongPtr, ByVal Final As Long, ByVal dwFlags As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwBufLen As Long ) As Long","returnType":"Long"}, + {"name":"CryptGenKey","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptGenKey Lib \"advapi32.dll\" ( ByVal hProv As LongPtr, ByVal Algid As Long, ByVal dwFlags As Long, ByRef phKey As LongPtr ) As Long","returnType":"Long"}, + {"name":"CryptGetHashParam","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptGetHashParam Lib \"advapi32.dll\" ( ByVal hHash As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptGetKeyParam","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptGetKeyParam Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByRef pdwDataLen As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptHashData","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptHashData Lib \"advapi32.dll\" ( ByVal hHash As LongPtr, ByVal pbData As LongPtr, ByVal dwDataLen As Long, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptReleaseContext","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptReleaseContext Lib \"advapi32.dll\" ( ByVal hProv As LongPtr, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptSetKeyParam","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptSetKeyParam Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal dwParam As Long, ByVal pbData As LongPtr, ByVal dwFlags As Long ) As Long","returnType":"Long"}, + {"name":"CryptStringToBinary","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CryptStringToBinary Lib \"crypt32.dll\" Alias \"CryptStringToBinaryW\" ( ByVal pszString As LongPtr, ByVal cchString As Long, ByVal dwFlags As Long, ByVal pbBinary As LongPtr, ByRef pcbBinary As Long, ByRef pdwSkip As Long, ByRef pdwFlags As Long ) As Long","returnType":"Long"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32.dll\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long ) As Long","returnType":"Long"}, + {"name":"Utf8BytesToWideString","kind":"function","vis":"public","signature":"Public Function Utf8BytesToWideString(ByRef bytes() As Byte) As String","returnType":"String"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function WideCharToMultiByte Lib \"kernel32.dll\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpDefaultChar As LongPtr, ByVal lpUsedDefaultChar As LongPtr ) As Long","returnType":"Long"}, + {"name":"WideStringToUtf8Bytes","kind":"function","vis":"public","signature":"Public Function WideStringToUtf8Bytes(ByVal str As String) As Byte()","returnType":"Byte()"}, + {"name":"ZeroMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub ZeroMemory Lib \"kernel32\" Alias \"RtlZeroMemory\" ( ByVal Destination As LongPtr, ByVal Length As Long )"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-TBMANLIB_Aes/Settings b/test/example-projects/contributed-TBMANLIB_Aes/Settings new file mode 100644 index 000000000..83e412091 --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB_Aes/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{C3CEB995-F6FA-46BC-B7DA-76C78B052D5F}", + "licence": "MIT", + "name": "[IMPORTED] TBMAN: A crypto library contains AES (ECB/CBC/CFB/OFB/CTR/GCM)", + "path32": "/Packages/TBMANLIB_Aes", + "path64": "/Packages/TBMANLIB_Aes", + "symbolId": "TBMANLIB_Aes", + "versionBuild": "2", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 5a2d5b8797651f2e03f49262cd967d9e727447bb Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 15:06:15 +0200 Subject: [PATCH 09/15] TBMANLIB: a contributed section of 16 pages (cJson, cLogs, cCsv, cLayer, cTimeuse, TBMAN and ten Tools modules), listed under Experimental Packages: a 64-bit build crashes the compiler and several members fail or give wrong results; each defect shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Reference/Contributed/TBMANLIB/TBMAN.md | 94 +++ .../Contributed/TBMANLIB/ToolsArray.md | 356 +++++++++++ .../Contributed/TBMANLIB/ToolsDic.md | 202 ++++++ .../Contributed/TBMANLIB/ToolsFso.md | 131 ++++ .../Contributed/TBMANLIB/ToolsJScript.md | 77 +++ .../Contributed/TBMANLIB/ToolsJsonVba.md | 120 ++++ .../Contributed/TBMANLIB/ToolsMath.md | 63 ++ .../Contributed/TBMANLIB/ToolsStr.md | 585 ++++++++++++++++++ .../Contributed/TBMANLIB/ToolsStream.md | 204 ++++++ .../Contributed/TBMANLIB/ToolsUtf8.md | 102 +++ docs/Reference/Contributed/TBMANLIB/cCsv.md | 169 +++++ docs/Reference/Contributed/TBMANLIB/cJson.md | 397 ++++++++++++ docs/Reference/Contributed/TBMANLIB/cLayer.md | 36 ++ docs/Reference/Contributed/TBMANLIB/cLogs.md | 294 +++++++++ .../Contributed/TBMANLIB/cTimeuse.md | 55 ++ docs/Reference/Contributed/TBMANLIB/index.md | 125 ++++ docs/Reference/Experimental.md | 1 + docs/_book.yml | 7 + .../snapshots/contributed/TBMANLIB/api.json | 484 +++++++++++++++ .../contributed-TBMANLIB/Settings | 85 +++ 22 files changed, 3589 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB/TBMAN.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsArray.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsDic.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsFso.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsJScript.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsMath.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsStr.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsStream.md create mode 100644 docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md create mode 100644 docs/Reference/Contributed/TBMANLIB/cCsv.md create mode 100644 docs/Reference/Contributed/TBMANLIB/cJson.md create mode 100644 docs/Reference/Contributed/TBMANLIB/cLayer.md create mode 100644 docs/Reference/Contributed/TBMANLIB/cLogs.md create mode 100644 docs/Reference/Contributed/TBMANLIB/cTimeuse.md create mode 100644 docs/Reference/Contributed/TBMANLIB/index.md create mode 100644 indexer/snapshots/contributed/TBMANLIB/api.json create mode 100644 test/example-projects/contributed-TBMANLIB/Settings diff --git a/WIP.md b/WIP.md index 57ee790f6..d5b8ac09c 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Nineteen **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 35ee15ff1..ea0d368e2 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1171, + "pages": 1187, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/TBMANLIB/TBMAN.md b/docs/Reference/Contributed/TBMANLIB/TBMAN.md new file mode 100644 index 000000000..f8e60eb81 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/TBMAN.md @@ -0,0 +1,94 @@ +--- +title: TBMAN +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/TBMAN +has_toc: false +--- + +# TBMAN module +{: .no_toc } + +The module that gives the package its name. It holds one ready-made instance of each of the package's classes, as public variables, and a **Version** property. A program can use them without a `New`, and every part of the program shares the same instance of each. + +```tb check_build check_run +TBMAN.Json.Clear +TBMAN.Json.Decode "{""name"":""Ann""}" +Debug.Print TBMAN.Json.Item("name") +Debug.Print TypeName(TBMAN.Json) +' Output: +' Ann +' cJson +``` + +The variables are declared `As New`, so each object is created the first time it is used. Because the instance is shared, its state is shared: the document in [**Json**](#json), the data in [**Csv**](#csv) and the settings in [**Logs**](#logs) are the same wherever the program reads them. A program that needs two documents, or logs with different settings, makes its own objects with `New`. + +The package [TBMANLIB_RES](../TBMANLIB_RES/TBMAN) also has a module named **TBMAN**. The name of the package can be put in front of the name of the module, as `TBMANLIB.TBMAN.Json`, to say which module is meant. + +```tb check_build check_run +Debug.Print TypeName(TBMANLIB.TBMAN.Json) +' Output: +' cJson +``` + +* TOC +{:toc} + +## Members + +### Csv +{: .no_toc } + +A shared [**cCsv**](cCsv): in version 1.0.0.5, the reader and writer of INI files. + +Syntax: **TBMAN.Csv** **As cCsv** + +### Ini +{: .no_toc } + +A shared **cIni**. The class has no members; see [Types without members](.#types-without-members). + +Syntax: **TBMAN.Ini** **As cIni** + +### Json +{: .no_toc } + +A shared [**cJson**](cJson), a JSON document that can be read from and written to text and files. + +Syntax: **TBMAN.Json** **As cJson** + +Call [**Clear**](cJson#clear) before decoding a new document into it: the shared object keeps whatever an earlier part of the program stored. + +### Layer +{: .no_toc } + +A shared [**cLayer**](cLayer), which shows a message box. + +Syntax: **TBMAN.Layer** **As cLayer** + +### Logs +{: .no_toc } + +A shared [**cLogs**](cLogs), which writes log lines. + +Syntax: **TBMAN.Logs** **As cLogs** + +### TimerUse +{: .no_toc } + +A shared [**cTimeuse**](cTimeuse), a stopwatch. + +Syntax: **TBMAN.TimerUse** **As cTimeuse** + +### Version +{: .no_toc } + +Returns a version number as text. + +Syntax: **TBMAN.Version** **As String** + +The text is `v`, then `App.Major`, a period, `App.Minor`, a period and `App.Build`. The `App` is that of the program that uses the package, not that of the package: in a program whose version is 2.3.4.7 (major, minor, revision, build), the result is `v2.3.7`. The revision number is not in the text, and the version of the package, 1.0.0.5, is not in it either. + +## See Also + +- [cJson](cJson), [cLogs](cLogs), [cCsv](cCsv), [cLayer](cLayer), [cTimeuse](cTimeuse) classes -- the classes of the instances +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsArray.md b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md new file mode 100644 index 000000000..b79e3378e --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md @@ -0,0 +1,356 @@ +--- +title: ToolsArray +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsArray +has_toc: false +--- + +# ToolsArray module +{: .no_toc } + +Functions for arrays: the search for a value, tests for an empty array, copying a part of a **Byte** or **Long** array, and splitting a **Byte** array at a keyword. The functions that copy use the Windows function **RtlMoveMemory**. The module needs no other component. + +```tb check_build check_run +Dim Data() As Byte +Dim Parts As Variant +Dim P() As Byte + +Data = ToolsStr.ToBytes("a,b,c") +Parts = ToolsArray.SplitByteArrayByKeyword(Data, ",") +Debug.Print UBound(Parts) + 1 +P = Parts(1) +Debug.Print ToolsStr.ToString(P) +' Output: +' 3 +' b +``` + +The functions that copy or split a **Byte** or **Long** array work on arrays whose lower bound is 0: they use the position they are given as an index, without adding the lower bound. Positions are counted from 0, and an end position is included in the slice. + +The module also holds four procedures that the author wrote to try the functions: **Test**, **TestSliceArray**, **TestSliceArray2** and **TestSplitArrayByKeyword**. They take no arguments and write to the Debug Console. A program has no use for them. + +* TOC +{:toc} + +## Functions + +### DeArray +{: .no_toc } + +Copies the elements of an array into separate variables. + +Syntax: **ToolsArray.DeArray** ( *Arr* **,** *OutVars* ) + +*Arr* +: *required* A **Variant** that holds an array, such as the result of **Split**. It is not changed. + +*OutVars* +: *required* Any number of variables, in a **ParamArray**. The first element of *Arr* is assigned to the first variable, the second element to the second variable, and so on. An argument that is left out, as in `a, , c`, is skipped, and its variable is not changed. + +The copying stops at the last element of *Arr* or at the last variable, whichever comes first. The source says that an array whose lower bound is not 0 is not handled. + +```tb check_build check_run +Dim a As String +Dim c As String +ToolsArray.DeArray Split("x/y/z", "/"), a, , c +Debug.Print a & c +' Output: +' xz +``` + +### Extend +{: .no_toc } + +Unfinished. The source marks it with a note that means *to be continued*. + +Syntax: **ToolsArray.Extend** ( *Vars* ) **=** *Value* + +A property that is meant to assign the elements of the array *Value* to the elements of the array *Vars*. It assigns into the array *Vars* itself, so variables from which *Vars* was built, as in `Extend(Array(a, c)) = ...`, are not changed. + +### FindByteArray +{: .no_toc } + +Finds the position of one byte array inside another. + +Syntax: **ToolsArray.FindByteArray** ( *Arr* **,** *Keyword* ) **As Long** + +*Arr* +: *required* A **Byte** array, the bytes to search. + +*Keyword* +: *required* A **Byte** array, the bytes to find. + +Returns the position of the first place in *Arr* where all the bytes of *Keyword* follow one another, counted from the lower bound of *Arr*. Returns -1 if there is no such place. The comparison is exact, byte by byte. + +```tb check_build check_run +Dim Hay() As Byte +Dim Needle() As Byte +Hay = ToolsStr.ToBytes("Hello-World") + +Needle = ToolsStr.ToBytes("World") +Debug.Print ToolsArray.FindByteArray(Hay, Needle) +Needle = ToolsStr.ToBytes("xyz") +Debug.Print ToolsArray.FindByteArray(Hay, Needle) +' Output: +' 6 +' -1 +``` + +### GetArrayLength +{: .no_toc } + +Returns the number of elements of an array. + +Syntax: **ToolsArray.GetArrayLength** ( *Arr* ) **As Long** + +*Arr* +: *required* An array of **Variant**, such as the result of **Array**. An array of another type, such as the **String** array that **Split** returns, is not accepted by the parameter. + +Returns the upper bound less the lower bound, plus 1. An array that has not been dimensioned raises error 9. + +```tb check_build check_run +Debug.Print ToolsArray.GetArrayLength(Array(10, 20, 30)) +' Output: +' 3 +``` + +### GetIndexByValue +{: .no_toc } + +Finds the index of a value in an array. + +Syntax: **ToolsArray.GetIndexByValue** ( *Arr* **,** *Value* ) **As Long** + +*Arr* +: *required* A **Variant** that holds an array. + +*Value* +: *required* A **String**, the value to find. Each element is compared with it by the `=` operator. + +Returns the index of the first element that equals *Value*, in the numbering of the array, or -1 if there is none. + +```tb check_build check_run +Debug.Print ToolsArray.GetIndexByValue(Array("a", "b", "c"), "b") +Debug.Print ToolsArray.GetIndexByValue(Array("a", "b", "c"), "z") +' Output: +' 1 +' -1 +``` + +### IsArrayEmpty +{: .no_toc } + +Tests whether an array has no elements. + +Syntax: **ToolsArray.IsArrayEmpty** ( *Arr* ) **As Boolean** + +*Arr* +: *required* A **Variant** that holds an array. + +Returns **True** when the lower bound of *Arr* is greater than its upper bound, as in `Array()`, and also when the bounds cannot be read, as for an array that has not been dimensioned. + +```tb check_build check_run +Dim Empty1() As Long +Debug.Print ToolsArray.IsArrayEmpty(Empty1) +Debug.Print ToolsArray.IsArrayEmpty(Array()) +Debug.Print ToolsArray.IsArrayEmpty(Array(1, 2)) +' Output: +' True +' True +' False +``` + +### SliceByteArray +{: .no_toc } + +Copies a part of a **Byte** array into a new array. + +Syntax: **ToolsArray.SliceByteArray** ( *Arr* **,** *StartPos* [ **,** *EndPos* ] ) **As Byte()** + +*Arr* +: *required* A **Byte** array whose lower bound is 0. + +*StartPos* +: *required* A **Long**, the position of the first byte to copy. + +*EndPos* +: *optional* A **Long**, the position of the last byte to copy, which is included. The default of -1 means the end of the array. + +Returns a new **Byte** array, from 0, with the bytes from *StartPos* to *EndPos*. The positions are not checked against the array. The function copies with **RtlMoveMemory**, so a position outside the array makes it read memory that does not belong to the array. + +> [!WARNING] +> When *EndPos* is equal to *StartPos*, the function does not return the one byte. It returns an array with one element, holding 0, as it does when *EndPos* is less than *StartPos*; the test in the source is a strict "less than". [**SliceLongArray**](#slicelongarray) does not have this error. + +```tb check_build check_run +Dim Bytes() As Byte +Dim Part() As Byte +Bytes = ToolsStr.ToBytes("Hello-World") +Part = ToolsArray.SliceByteArray(Bytes, 2, 2) +Debug.Print UBound(Part) & " " & Part(0) +' Output: +' 0 0 +``` + +```tb check_build check_run +Dim Bytes() As Byte +Dim Part() As Byte +Bytes = ToolsStr.ToBytes("Hello-World") + +Part = ToolsArray.SliceByteArray(Bytes, 0, 4) +Debug.Print ToolsStr.ToString(Part) +Part = ToolsArray.SliceByteArray(Bytes, 6) +Debug.Print ToolsStr.ToString(Part) +' Output: +' Hello +' World +``` + +### SliceLongArray +{: .no_toc } + +Copies a part of a **Long** array into a new array. + +Syntax: **ToolsArray.SliceLongArray** ( *Arr* **,** *StartPos* [ **,** *EndPos* ] ) **As Long()** + +*Arr* +: *required* A **Long** array whose lower bound is 0. + +*StartPos* +: *required* A **Long**, the position of the first element to copy. + +*EndPos* +: *optional* A **Long**, the position of the last element to copy, which is included. The default of -1 means the end of the array. + +Returns a new **Long** array, from 0, with the elements from *StartPos* to *EndPos*. When *EndPos* is less than *StartPos*, the result is an array with one element holding 0. As for [**SliceByteArray**](#slicebytearray), the positions are not checked against the array. + +```tb check_build check_run +Dim Longs() As Long +Dim Part() As Long +Dim i As Long + +ReDim Longs(0 To 6) +For i = 0 To 6 + Longs(i) = (i + 1) * 10 +Next + +Part = ToolsArray.SliceLongArray(Longs, 2, 5) +Debug.Print UBound(Part) & " " & Part(0) & " " & Part(3) +' Output: +' 3 30 60 +``` + +### SplitByteArray +{: .no_toc } + +Splits a **Byte** array into two arrays at a position. + +Syntax: **ToolsArray.SplitByteArray** ( *Arr* **,** *position* [ **,** *Offset* ] ) **As Variant** + +*Arr* +: *required* A **Byte** array whose lower bound is 0. + +*position* +: *required* A **Long**, the number of bytes in the first part. + +*Offset* +: *optional* A **Long**, 0 by default, the number of bytes to leave out between the two parts: the second part starts at *position* + *Offset*. + +Returns a **Variant** array of two elements. Element 0 is a **Byte** array of the first *position* bytes, and element 1 is a **Byte** array of the bytes from *position* + *Offset* to the end. If *position* is at or past the end of *Arr*, the first part is *Arr* and the second part is an array with one element holding 0. The second part is also that array when *position* + *Offset* is at or past the end. + +```tb check_build check_run +Dim Bytes() As Byte +Dim Parts As Variant +Dim P() As Byte +Bytes = ToolsStr.ToBytes("one,two") + +Parts = ToolsArray.SplitByteArray(Bytes, 3, 1) +P = Parts(0) +Debug.Print ToolsStr.ToString(P) +P = Parts(1) +Debug.Print ToolsStr.ToString(P) + +Parts = ToolsArray.SplitByteArray(Bytes, 7) +P = Parts(1) +Debug.Print UBound(P) & " " & P(0) +' Output: +' one +' two +' 0 0 +``` + +A *position* of 0 raises the error *Unspecified error* (-2147467259), because the first part would be dimensioned with an upper bound of -1. + +### SplitByteArrayByKeyword +{: .no_toc } + +Splits a **Byte** array at each place where a keyword occurs. + +Syntax: **ToolsArray.SplitByteArrayByKeyword** ( *ByteArray* **,** *Keyword* [ **,** *SplitCount* ] ) **As Variant** + +*ByteArray* +: *required* A **Byte** array whose lower bound is 0. + +*Keyword* +: *required* A **Variant** that holds a **String**. It is converted to bytes with [**StringToByteArray**](#stringtobytearray), in the ANSI code page, and the bytes are searched for exactly. + +*SplitCount* +: *optional* A **Long**, 0 by default. When it is greater than 0, the array is split at no more than that many occurrences, and the rest of the array is the last part. + +Returns a **Variant** array, from 0, of **Byte** arrays: the parts between the occurrences of the keyword, in order. The keyword itself is in none of them. If the keyword does not occur, there is one part, a copy of the array. A keyword at the very end leaves a last part that is an array with one element holding 0, and not an empty array. A keyword at the very start raises the error of a *position* of 0 in [**SplitByteArray**](#splitbytearray), *Unspecified error* (-2147467259). + +```tb check_build check_run +Dim Bytes() As Byte +Dim Parts As Variant +Dim P() As Byte + +Bytes = ToolsStr.ToBytes("a,") +Parts = ToolsArray.SplitByteArrayByKeyword(Bytes, ",") +P = Parts(1) +Debug.Print (UBound(Parts) + 1) & " " & UBound(P) & " " & P(0) + +Bytes = ToolsStr.ToBytes(",a") +On Error Resume Next +Parts = ToolsArray.SplitByteArrayByKeyword(Bytes, ",") +Debug.Print Err.Number +' Output: +' 2 0 0 +' -2147467259 +``` + +```tb check_build check_run +Dim Data() As Byte +Dim Parts As Variant +Dim P() As Byte + +Data = ToolsStr.ToBytes("a,b,c") +Parts = ToolsArray.SplitByteArrayByKeyword(Data, ",", 1) +P = Parts(1) +Debug.Print ToolsStr.ToString(P) +' Output: +' b,c +``` + +### StringToByteArray +{: .no_toc } + +Converts a string to a byte array in the ANSI code page. + +Syntax: **ToolsArray.StringToByteArray** ( *St* ) **As Byte()** + +*St* +: *required* A **String**. + +Returns the bytes of *St* in the ANSI code page of the system, one byte for each character of a single-byte code page, with **StrConv** and **vbFromUnicode**. A character that the code page does not have is not kept as written. To get UTF-8 bytes, use [**ToolsUtf8.Encode**](ToolsUtf8#encode). + +```tb check_build check_run +Debug.Print UBound(ToolsArray.StringToByteArray("abc")) +' Output: +' 2 +``` + +## See Also + +- [ToolsStr](ToolsStr) module -- **ToBytes**, **ToString** and **ToHex**, which convert between strings and byte arrays +- [ToolsUtf8](ToolsUtf8) module -- UTF-8 conversion +- [Split](../../../Modules/Strings/Split) function -- splits a string +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsDic.md b/docs/Reference/Contributed/TBMANLIB/ToolsDic.md new file mode 100644 index 000000000..4a860ffde --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsDic.md @@ -0,0 +1,202 @@ +--- +title: ToolsDic +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsDic +has_toc: false +--- + +# ToolsDic module +{: .no_toc } + +Helpers for **Scripting.Dictionary** objects: reading and writing the `name=value&name=value` text of web forms, setting a value two levels deep, merging one dictionary into another, and copying one. A dictionary is passed as a `Scripting.Dictionary` parameter, so a program that names the type in its own code references the Microsoft Scripting Runtime. + +```tb check_build check_run +Dim Form As New Scripting.Dictionary +Debug.Print ToolsDic.FromWwwFormUrlencoded("name=Ann&city=Oslo&empty=&x= y ", Form) +Debug.Print Form("name") & "|" & Form("city") & "|" & Form("empty") & "|" & Form("x") +' Output: +' True +' Ann|Oslo||y +``` + +* TOC +{:toc} + +## Functions + +### DeepCopy +{: .no_toc } + +Copies a dictionary. + +Syntax: **ToolsDic.DeepCopy** ( *srcDic* ) **As Scripting.Dictionary** + +*srcDic* +: *required* A **Scripting.Dictionary**. It is not changed. + +Returns a new dictionary with the same keys and values. Only the first level is copied: where a value is an object, such as a dictionary or a collection, the copy holds the same object, not a copy of it. The source says that a recursive copy is still to be written. The copy has the default comparison mode, whatever the mode of the source. + +```tb check_build check_run +Dim A As New Scripting.Dictionary +Dim C As Scripting.Dictionary + +Set A("child") = New Scripting.Dictionary +A("child")("n") = 1 + +Set C = ToolsDic.DeepCopy(A) +C("child")("n") = 2 +Debug.Print A("child")("n") +' Output: +' 2 +``` + +### FromWwwFormUrlencoded +{: .no_toc } + +Reads the pairs of a `name=value&name=value` text into a dictionary. + +Syntax: **ToolsDic.FromWwwFormUrlencoded** ( *Content* **,** *Dic* ) **As Boolean** + +*Content* +: *required* A **String**, the text. It is split at each `&`, and each piece is split at its first `=`. + +*Dic* +: *required* A **Scripting.Dictionary** that receives the pairs. A name that it already holds gets the new value, and the other entries stay. + +The name and the value of each pair lose the spaces at their ends. A pair with an empty name is skipped. A pair with no `=` gets an empty value. A value that holds an `=` is cut at it: in `a=b=c` the value is `b`. The text is not percent-decoded, so `%20` and `+` stay as written, and a value that holds `&` cannot be expressed. The function always returns **True**. The source says that values that are arrays are still to be handled. + +### OverWrite +{: .no_toc } + +Copies values from one dictionary into another. + +Syntax: **ToolsDic.OverWrite** ( *DistDic* **,** *srcDic* [ **,** *OnlyKey* ] ) + +*DistDic* +: *required* A **Scripting.Dictionary**, the one that receives the values. + +*srcDic* +: *required* A **Scripting.Dictionary**, the one that supplies them. It is not changed. + +*OnlyKey* +: *optional* A **Boolean**, **True** by default. When it is **True**, only the keys that *DistDic* already has are written, so the structure of *DistDic* stays as it is. When it is **False**, every key of *srcDic* is written, and the missing ones are added. + +Where a value of *srcDic* is an object, the method calls itself for that key with the two objects, so the nested dictionary of *DistDic* is updated in place and not replaced. A value that is not an object is assigned. + +> [!NOTE] +> With *OnlyKey* set to **False**, a nested dictionary of *srcDic* whose key *DistDic* does not have makes the recursive call receive an empty value for the missing key, where it needs a dictionary. The call raises error 424, *Object required*, and *DistDic* keeps the key that was looked up, with the value **Empty**. + +```tb check_build check_run +Dim A As New Scripting.Dictionary +Dim B As New Scripting.Dictionary +Dim Inner As Scripting.Dictionary + +Set A("n") = New Scripting.Dictionary +A("n")("x") = 1 +A("n")("y") = 2 +Set Inner = A("n") +Set B("n") = New Scripting.Dictionary +B("n")("y") = 20 +B("n")("z") = 30 +ToolsDic.OverWrite A, B +Debug.Print Inner("y") & " " & Inner.Exists("z") + +Set B("m") = New Scripting.Dictionary +On Error Resume Next +ToolsDic.OverWrite A, B, False +Debug.Print Err.Number +Debug.Print A.Exists("m") & " " & IsEmpty(A("m")) +' Output: +' 20 False +' 424 +' True True +``` + +```tb check_build check_run +Dim A As New Scripting.Dictionary +Dim B As New Scripting.Dictionary +A("x") = 1 +A("y") = 2 +B("y") = 20 +B("z") = 30 + +ToolsDic.OverWrite A, B +Debug.Print A("x") & " " & A("y") & " " & A.Exists("z") + +ToolsDic.OverWrite A, B, False +Debug.Print A("z") +' Output: +' 1 20 False +' 30 +``` + +### ToWwwFormUrlencoded +{: .no_toc } + +Writes a dictionary as a `name=value&name=value` text. + +Syntax: **ToolsDic.ToWwwFormUrlencoded** ( *Dic* ) **As String** + +*Dic* +: *required* A **Scripting.Dictionary** of names and simple values. + +The function is meant to build the text in a JavaScript array made with [**ToolsJScript.NewArr**](ToolsJScript#newarr), and to turn the array into text with `&` in place of the commas. The values would not be percent-encoded, and a comma in a name or in a value would become `&`. + +> [!WARNING] +> In version 1.0.0.5 the function never returns a text. It adds the entries `a` with the value 1 and `bbb` with the value 2 to *Dic*, and then fails with error 91, *Object variable or With block variable not set*, because [**ToolsJScript.NewArr**](ToolsJScript#newarr) cannot create its array. The two entries stay in the dictionary. A second call with the same dictionary fails earlier, with error 457, because the keys `a` and `bbb` are already there. + +```tb check_build check_run +Dim Form As New Scripting.Dictionary +Form("name") = "Ann" + +On Error Resume Next +Debug.Print ToolsDic.ToWwwFormUrlencoded(Form) +Debug.Print Err.Number +Err.Clear +Debug.Print Form.Count + +Debug.Print ToolsDic.ToWwwFormUrlencoded(Form) +Debug.Print Err.Number +' Output: +' 91 +' 3 +' 457 +``` + +### TowLevelDicAssign +{: .no_toc } + +Sets a value in a dictionary that is held in a dictionary. + +Syntax: **ToolsDic.TowLevelDicAssign** ( *Dic* **,** *Lv1Name* **,** *Lv2Name* **,** *Value* ) + +*Dic* +: *required* A **Scripting.Dictionary** whose values are dictionaries. + +*Lv1Name* +: *required* A **String**, the key in *Dic* of the inner dictionary. The inner dictionary is created if *Dic* has no such key. + +*Lv2Name* +: *required* A **String**, the key in the inner dictionary. + +*Value* +: *required* A **Variant**, the value to store under *Lv2Name*. + +The name has *Tow* for *Two*. If the inner dictionary exists, the value replaces the value of *Lv2Name*, or adds it. If it does not, a new dictionary is made, it receives the value, and *Dic* receives the new dictionary. + +```tb check_build check_run +Dim D As New Scripting.Dictionary +ToolsDic.TowLevelDicAssign D, "a", "x", 1 +ToolsDic.TowLevelDicAssign D, "a", "y", 2 +Debug.Print D("a")("x") + D("a")("y") +Debug.Print D("a").Count +' Output: +' 3 +' 2 +``` + +## See Also + +- [ToolsJScript](ToolsJScript) module -- the JavaScript array that **ToWwwFormUrlencoded** uses +- [cJson](cJson) class -- keeps a JSON document in dictionaries +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsFso.md b/docs/Reference/Contributed/TBMANLIB/ToolsFso.md new file mode 100644 index 000000000..eb3459b9e --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsFso.md @@ -0,0 +1,131 @@ +--- +title: ToolsFso +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsFso +has_toc: false +--- + +# ToolsFso module +{: .no_toc } + +Helps with path names and folders: it completes a relative path with the folder of the program, tidies the slashes of a path, creates the missing folders of a path, and builds a new file name from an old one. It keeps one **Scripting.FileSystemObject**, [**Inst**](#inst), for its own use and for a program to use. + +```tb check_build check_run +Debug.Print ToolsFso.ClearSpan("C:/data//a", True) +Debug.Print ToolsFso.AutoCompleteFullPath("C:\data\\a.json", True) +Debug.Print ToolsFso.MakeNewFileFulPath("C:\data\a.json", "old") +' Output: +' C:\data\a +' C:\data\a.json +' a_old.json +``` + +[**cJson**](cJson) uses **AutoCompleteFullPath** and **AutoMakeDir** to find and prepare the file for its [**SaveTo**](cJson#saveto), and [**cCsv**](cCsv) uses **AutoMakeDir**. + +* TOC +{:toc} + +## Members + +### AutoCompleteFullPath +{: .no_toc } + +Completes a relative path with the folder of the program and tidies its slashes. + +Syntax: **ToolsFso.AutoCompleteFullPath** ( *Path* [ **,** *IsFile* ] ) **As String** + +*Path* +: *required* A **String**, a path. It is passed by reference and is not changed. + +*IsFile* +: *optional* A **Boolean**, **False** by default. Set it to **True** when *Path* names a file. When it is **False**, the result ends with a backslash, as the path of a folder does. + +If *Path* does not contain the two characters `:\` anywhere, it is taken to be relative: `App.Path`, a backslash and *Path* are joined, and the result goes through [**ClearSpan**](#clearspan). A path that contains `:\` goes through **ClearSpan** alone. In a debug run, `App.Path` is the folder of the project. + +> [!NOTE] +> Only `:\` marks a path as complete. A path with a drive letter written with forward slashes, such as `C:/data/a.json`, and a network path such as `\\server\share\a.json` do not contain it, so they are given the folder of the program in front of them and come out wrong. + +### AutoMakeDir +{: .no_toc } + +Creates the missing folders of a path and returns the path of the folder. + +Syntax: **ToolsFso.AutoMakeDir** ( *Path* [ **,** *IsFile* ] ) **As String** + +*Path* +: *required* A **String**, a folder path, or a file path when *IsFile* is **True**. It is used as written: a relative path is relative to the current folder of the program, and is not completed with `App.Path`. + +*IsFile* +: *optional* A **Boolean**, **False** by default. When it is **True**, *Path* names a file, and the folders that are created are those of its parent folder. When it is **False**, the whole of *Path* is taken to be a folder. + +All the folders of the path that do not exist are created, by the Windows function **MakeSureDirectoryPathExists**. The function returns the path of the folder after [**ClearSpan**](#clearspan), so it ends with a backslash. The result of the Windows function is not tested: if a folder cannot be created, no error is raised. That function takes ANSI text, so a folder name that has characters outside the system code page is not created as written. + +> [!WARNING] +> With *IsFile* left at **False**, the last name of the path becomes a folder: `AutoMakeDir("C:\data\log.txt")` creates a folder named `log.txt`. Pass **True** for a file. [**cCsv.SaveTo**](cCsv#saveto) calls the function without it. + +```tb check_build check_run +Dim Base As String +Base = Environ$("TEMP") & "\tbmanlib-docs-fso" + +Debug.Print ToolsFso.AutoMakeDir(Base & "\a\b\file.txt", True) = Base & "\a\b\" +Debug.Print Dir(Base & "\a\b", vbDirectory) = "b" + +RmDir Base & "\a\b" +RmDir Base & "\a" +RmDir Base +' Output: +' True +' True +``` + +### ClearSpan +{: .no_toc } + +Replaces the slashes of a path with single backslashes. + +Syntax: **ToolsFso.ClearSpan** ( *Path* [ **,** *IsFile* ] ) **As String** + +*Path* +: *required* A **String**, a path. It is passed by reference and is not changed. + +*IsFile* +: *optional* A **Boolean**, **False** by default. When it is **False**, a backslash is added at the end of *Path* first. + +The function makes four replacements, one after the other, on the whole string: each pair of backslashes becomes one, each pair of forward slashes becomes one backslash, each backslash followed by a forward slash becomes one backslash, and each remaining forward slash becomes a backslash. Each replacement is made once, from left to right, so a run of four backslashes becomes two, not one. A network path loses one of the two backslashes at its start. + +### Inst +{: .no_toc } + +The **Scripting.FileSystemObject** that the module uses. A field, declared `Public Inst As New Scripting.FileSystemObject`, created the first time it is used. It is shared by every caller. + +### MakeNewFileFulPath +{: .no_toc } + +Builds a file name from a file name and a suffix. + +Syntax: **ToolsFso.MakeNewFileFulPath** ( *FileSrc* **,** *AppendFix* [ **,** *JoinStr* ] ) **As String** + +*FileSrc* +: *required* A **String**, a file path. The file need not exist. + +*AppendFix* +: *required* A **String**, the text to add after the base name of the file. + +*JoinStr* +: *optional* A **String**, `_` by default. It is placed between the base name and *AppendFix*. + +Returns the base name of *FileSrc*, then *JoinStr*, then *AppendFix*, a period and the extension of *FileSrc*: `MakeNewFileFulPath("C:\data\a.json", "old")` returns `a_old.json`. + +> [!NOTE] +> Despite the word *FulPath* in the name, the result holds no folder. The function reads the folder of *FileSrc* and does not use it. Join the result to the folder with `&`. A file name with no extension gives a result that ends with a period. + +### Test +{: .no_toc } + +A procedure of the author's own tests. It writes the names of the files and folders under `D:\code\_html\easyweb\spa-3.1.8` to the Debug Console, and raises an error on a computer that has no such folder. A program has no use for it. + +## See Also + +- [ToolsStream](ToolsStream) module -- reads and writes the files that this module prepares +- [cJson](cJson) class -- **SaveTo** completes the file name with this module +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md b/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md new file mode 100644 index 000000000..ecf116ea6 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md @@ -0,0 +1,77 @@ +--- +title: ToolsJScript +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsJScript +has_toc: false +--- + +# ToolsJScript module +{: .no_toc } + +Is meant to create JavaScript arrays and objects with the Microsoft Script Control, so that a program can hand them to code that expects one. The module has two functions. [**ToolsDic.ToWwwFormUrlencoded**](ToolsDic#towwwformurlencoded) is the one place in the package that uses it. + +> [!NOTE] +> In version 1.0.0.5 both functions raise error 91, *Object variable or With block variable not set*, in a 32-bit program. The module creates the Script Control with `Dim MSSC As New MSScriptControl.ScriptControl`, and that statement does not create an object: the variable is **Nothing** when it is used, in the package and also in a program that references the Script Control itself. The control is created by `CreateObject("MSScriptControl.ScriptControl")`. +> +> The Script Control exists only as a 32-bit component. A project that embeds the package cannot be built for 64-bit programs at all; see [32-bit and 64-bit programs](.#32-bit-and-64-bit-programs). + +```tb check_build check_run +Dim Arr As Object +On Error Resume Next +Set Arr = ToolsJScript.NewArr() +Debug.Print Err.Number & " " & Err.Description +Err.Clear +Set Arr = ToolsJScript.NewObj() +Debug.Print Err.Number +' Output: +' 91 Object variable or With block variable not set +' 91 +``` + +What the module is meant to do is this. It creates one Script Control, shared by all callers, the first time one of its functions is called. It selects the language JScript and adds four methods to the built-in objects of the engine: `get_(x)` on every object and on every array, `has_(x)` on every object (**True** when the object has its own property named *x*), `Push(x)` on arrays, which calls the array's own `push`, and `Join(x)` on arrays, which calls `join`. The comments in the source say that `Push` and `Join` exist because the code editor changes the case of the names `push` and `join` that it knows, and that the two names with an underscore are slower than reading a property directly. The text of `Join` has no `return`, so `Join(x)` returns nothing; the array's own `join`, in lower case, returns the joined text. + +A program that needs a JavaScript array can create the control itself: + +```tb check_build check_run +Dim SC As Object +Dim Arr As Object +Set SC = CreateObject("MSScriptControl.ScriptControl") +SC.Language = "JScript" + +Set Arr = SC.Eval("[]") +Arr.push "x" +Arr.push "y" +Debug.Print Arr.length +Debug.Print Arr.join(",") +' Output: +' 2 +' x,y +``` + +## Functions + +### NewArr +{: .no_toc } + +Is meant to create an empty JavaScript array. + +Syntax: **ToolsJScript.NewArr** ( [ *Item* ] ) **As Object** + +*Item* +: *optional* Any number of values. They are accepted and ignored: the array would be empty whatever is passed. + +The function evaluates `[];` in the Script Control and returns the array. In version 1.0.0.5 it raises error 91 instead, as the note above says. + +### NewObj +{: .no_toc } + +Is meant to create an empty JavaScript object. + +Syntax: **ToolsJScript.NewObj** ( ) **As Object** + +The function evaluates `{};` and assigns the result to **NewArr**, the name of the other function, and not to its own result. In version 1.0.0.5 it raises error 91, as **NewArr** does. It would not work if the control existed: JScript reads `{};` as an empty block, not as an object, so a Script Control that evaluates `{};` returns no object, and the statement that uses the result fails with error 424, *Object required*. The text `({})` evaluates to an object. + +## See Also + +- [ToolsDic](ToolsDic) module -- **ToWwwFormUrlencoded** builds on **NewArr** +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md b/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md new file mode 100644 index 000000000..7da5ccd89 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md @@ -0,0 +1,120 @@ +--- +title: ToolsJsonVba +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsJsonVba +has_toc: false +--- + +# ToolsJsonVba module +{: .no_toc } + +The JSON parser and writer of the package, and its functions for UTC and ISO 8601 dates. [**cJson**](cJson) calls it for its [**Decode**](cJson#decode) and [**Encode**](cJson#encode), and a program can call it directly. + +The module is the code of **VBA-JSON** version 2.3.1, by Tim Hall (MIT licence, [github.com/VBA-tools/VBA-JSON](https://github.com/VBA-tools/VBA-JSON)), which the source says is based on **JSONLib** by Ryo Yokoyama (BSD licence, `code.google.com/p/vba-json`), together with the code of **VBA-UTC** version 1.0.6, also by Tim Hall (MIT licence, [github.com/VBA-tools/VBA-UTC](https://github.com/VBA-tools/VBA-UTC)), in one module. The licence notice of JSONLib is kept in the source. + +The [VBA_JSON](../VBA_JSON/) package of this documentation has the same code in its [**JsonConverter**](../VBA_JSON/JsonConverter/) module, and its pages describe every function in full. The two modules have the same code, line by line, apart from white space, the name of the module, the spelling `ERR.Raise` in one error call, and one line of **ConvertToJson**, which reads a dictionary entry as `JsonValue(json_Key)` where **JsonConverter** reads `JsonValue.Item(CStr(json_Key))`. One difference in what a program sees comes from the name `Dictionary`: in **ToolsJsonVba** it is the `Scripting.Dictionary` of the Microsoft Scripting Runtime, and in **JsonConverter** it is the **Dictionary** class of the VBA_JSON package. So **ParseJson** returns a `Scripting.Dictionary` for a JSON object here. Both modules make a **Collection** for a JSON array, and both write an object whose `TypeName` is `Dictionary` or `Collection`. + +Each entry below says what the function does in a sentence or two and refers to the page in the VBA_JSON section for the details. + +```tb check_build check_run +Dim d As Object +Set d = ToolsJsonVba.ParseJson("{""a"":1,""b"":[true,null]}") + +Debug.Print TypeName(d) +Debug.Print ToolsJsonVba.ConvertToJson(d) +' Output: +' Dictionary +' {"a":1,"b":[true,null]} +``` + +* TOC +{:toc} + +## Options + +### JsonOptions +{: .no_toc } + +A public variable of a type that is private to the module, with three **Boolean** fields. They change how the functions read and write JSON, and they apply to all callers, so they also change what [**cJson**](cJson) reads and writes. All three are **False** by default. [JsonOptions](../VBA_JSON/JsonConverter/#jsonoptions) in the VBA_JSON section describes them. + +| Field | Effect when **True** | +|:------|:---------------------| +| `UseDoubleForLargeNumbers` | **ParseJson** returns every number as a **Double**, also one of 16 or more characters, which it returns as a **String** when the field is **False**. **ConvertToJson** puts quotation marks around every **String**. | +| `AllowUnquotedKeys` | **ParseJson** accepts object keys without quotation marks. | +| `EscapeSolidus` | **ConvertToJson** writes `/` as `\/`. | + +```tb check_build check_run +Dim d As Object +Set d = ToolsJsonVba.ParseJson("{""url"":""a/b""}") + +Debug.Print ToolsJsonVba.ConvertToJson(d) +ToolsJsonVba.JsonOptions.EscapeSolidus = True +Debug.Print ToolsJsonVba.ConvertToJson(d) +ToolsJsonVba.JsonOptions.EscapeSolidus = False +' Output: +' {"url":"a/b"} +' {"url":"a\/b"} +``` + +## Functions + +### ConvertToIso +{: .no_toc } + +Converts a local date and time to an ISO 8601 text in UTC. + +Syntax: **ToolsJsonVba.ConvertToIso** ( *utc_LocalDate* ) **As String** + +Returns text such as `2024-06-03T07:00:00.000Z`: the date converted to UTC with the time zone of the system and written with a fixed `.000` for the milliseconds. See [ConvertToIso](../VBA_JSON/JsonConverter/ConvertToIso). + +### ConvertToJson +{: .no_toc } + +Converts a value to JSON text. + +Syntax: **ToolsJsonVba.ConvertToJson** ( *JsonValue* [ **,** *Whitespace* ] ) **As String** + +Accepts a **Scripting.Dictionary** (written as an object), a **Collection** or an array (written as an array), and a **String**, **Boolean**, number, **Date** or **Null**. Non-ASCII and control characters in strings are written as `\uXXXX`. The optional *Whitespace* turns on indentation. See [ConvertToJson](../VBA_JSON/JsonConverter/ConvertToJson). + +### ConvertToUtc +{: .no_toc } + +Converts a local date and time to UTC. + +Syntax: **ToolsJsonVba.ConvertToUtc** ( *utc_LocalDate* ) **As Date** + +Uses the time zone of the system, with its rules for daylight saving time. Raises error 10012 on failure. See [ConvertToUtc](../VBA_JSON/JsonConverter/ConvertToUtc). + +### ParseIso +{: .no_toc } + +Converts an ISO 8601 text to a local date and time. + +Syntax: **ToolsJsonVba.ParseIso** ( *utc_IsoString* ) **As Date** + +Reads a date, or a date and time, with or without a `Z` or an offset such as `+02:00`. Raises error 10013 on failure. See [ParseIso](../VBA_JSON/JsonConverter/ParseIso). + +### ParseJson +{: .no_toc } + +Converts JSON text to a **Scripting.Dictionary** or a **Collection**. + +Syntax: **ToolsJsonVba.ParseJson** ( *JsonString* ) **As Object** + +The text has to start with `{` or `[`. A JSON object becomes a **Scripting.Dictionary**, an array a **Collection** (numbered from 1), a string a **String**, a number a **Double** (a **String** when it has 16 or more characters), `true` and `false` **Boolean** values and `null` **Null**. Raises error 10001, with the source `JSONConverter`, for text that it cannot read. See [ParseJson](../VBA_JSON/JsonConverter/ParseJson). + +### ParseUtc +{: .no_toc } + +Converts a UTC date and time to local time. + +Syntax: **ToolsJsonVba.ParseUtc** ( *utc_UtcDate* ) **As Date** + +Uses the time zone of the system. Raises error 10011 on failure. See [ParseUtc](../VBA_JSON/JsonConverter/ParseUtc). + +## See Also + +- [JsonConverter](../VBA_JSON/JsonConverter/) module -- the same code in the VBA_JSON package, with a full page for each function +- [cJson](cJson) class -- a JSON document object built on this module +- [ToolsStr](ToolsStr) module -- **UnicodeEncode** and **UnicodeDecode** for the `\uXXXX` escapes +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsMath.md b/docs/Reference/Contributed/TBMANLIB/ToolsMath.md new file mode 100644 index 000000000..329bd5c0c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsMath.md @@ -0,0 +1,63 @@ +--- +title: ToolsMath +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsMath +has_toc: false +--- + +# ToolsMath module +{: .no_toc } + +Two number functions: **Ceil** rounds up to a chosen number of decimal places, and **GetRandRange** returns a random whole number in a range. The module needs no other component. [**ToolsStr.GetRandStr**](ToolsStr#getrandstr) uses **GetRandRange**. + +```tb check_build check_run +Debug.Print ToolsMath.Ceil(1.231, 2) +Debug.Print ToolsMath.Ceil(-1.5) +Debug.Print ToolsMath.Ceil(0.07, 2) +Debug.Print ToolsMath.Ceil(1234, -2) +Debug.Print ToolsMath.GetRandRange(3, 3) +' Output: +' 1.24 +' -1 +' 0.08 +' 1300 +' 3 +``` + +## Functions + +### Ceil +{: .no_toc } + +Rounds a number up, towards positive infinity, to a number of decimal places. + +Syntax: **ToolsMath.Ceil** ( *Num* [ **,** *Dot* ] ) **As Double** + +*Num* +: *required* A **Variant** that holds a number, or text that converts to one. + +*Dot* +: *optional* A **Long**, the number of decimal places to keep, 0 by default. A negative value rounds to tens, hundreds and so on: `Ceil(1234, -2)` is 1300. + +The result is the smallest number with *Dot* decimal places that is not less than *Num*: `Ceil(1.231, 2)` is 1.24 and `Ceil(-1.5)` is -1. The function multiplies *Num* by 10 to the power *Dot*, rounds up with **Int**, and divides again. The arithmetic is in binary floating point, so a number that has no exact binary form can multiply to a value a little above a whole number and round up one step too far: `Ceil(0.07, 2)` returns 0.08, because 0.07 multiplied by 100 is a little more than 7. + +### GetRandRange +{: .no_toc } + +Returns a random whole number from *a* to *b*, both included. + +Syntax: **ToolsMath.GetRandRange** ( *a* **,** *b* ) **As Long** + +*a* +: *required* A **Long**, the smallest value. It is passed by reference and is not changed. + +*b* +: *required* A **Long**, the largest value. It is passed by reference and is not changed. If it is less than *a*, the result is not in the range. + +Each call runs **Randomize** with no argument, which seeds the generator from the system timer, then returns `Int((b - a + 1) * Rnd() + a)`. The numbers come from the generator of the language and are not suitable for passwords, keys or any other use where they have to be unpredictable. + +## See Also + +- [ToolsStr](ToolsStr) module -- **GetRandStr** and **GetRandByte** use the generator +- [Randomize](../../../Modules/Math/Randomize) statement and [Rnd](../../../Modules/Math/Rnd) function -- the generator that **GetRandRange** uses +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsStr.md b/docs/Reference/Contributed/TBMANLIB/ToolsStr.md new file mode 100644 index 000000000..463a12431 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsStr.md @@ -0,0 +1,585 @@ +--- +title: ToolsStr +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsStr +has_toc: false +--- + +# ToolsStr module +{: .no_toc } + +String functions: trimming and testing, searching and cutting out a part, the conversions to `%XX` and `\uXXXX` escapes, conversion between strings and bytes, and random text and identifiers. Twenty-six functions, in five groups. Most use nothing but the language; [**GetGUID**](#getguid) uses the Windows object `Scriptlet.TypeLib`. + +```tb check_build check_run +Debug.Print ToolsStr.TrimEx(vbTab & " a b " & vbCrLf) +Debug.Print ToolsStr.SubStr("key = value ;", "=", ";") +Debug.Print ToolsStr.InsertSpan("abcdefg", "-", 3) +Debug.Print ToolsStr.UnicodeEncode("caf" & ChrW(233)) +' Output: +' a b +' value +' abc-def-g +' caf\u00E9 +``` + +A few functions keep a position or a count in an **Integer** variable: **TrimEx** and the functions built on it, **InsertSpan**, **PercentEncode**, **PercentDecode**, **UnicodeEncode** and **UnicodeDecode**. They raise error 6, *Overflow*, for a string of more than 32,767 characters. + +Most **String** parameters are passed by reference, as the language does by default, and the functions do not change them. The two exceptions are [**IsEmptyEx**](#isemptyex), which trims the variable that is passed to it, and [**MidEx**](#midex), which sets its *starindex* argument. + +* TOC +{:toc} + +## Testing and trimming + +### IsEmptyEx +{: .no_toc } + +Tests whether a string holds nothing but white space. + +Syntax: **ToolsStr.IsEmptyEx** ( *Text* ) **As Boolean** + +*Text* +: *required* A **String**. + +Returns **True** when *Text* has no characters left after [**TrimEx**](#trimex) removes the spaces, tabs, line breaks and other control characters at both ends. + +> [!WARNING] +> The parameter is passed by reference and the function assigns the trimmed text to it, so a variable that is passed is changed: after `IsEmptyEx(t)`, **t** holds the trimmed text. Pass an expression, such as `(t)`, to keep the variable as it is. + +```tb check_build check_run +Dim t As String +t = " x " +Debug.Print ToolsStr.IsEmptyEx(t) +Debug.Print "[" & t & "]" +Debug.Print ToolsStr.IsEmptyEx(vbTab & " ") +' Output: +' False +' [x] +' True +``` + +### IsString +{: .no_toc } + +Tests whether a value is a string. + +Syntax: **ToolsStr.IsString** ( *var* ) **As Boolean** + +*var* +: *required* A **Variant**. + +Returns **True** when `VarType(var)` is **vbString**. The text `"12"` is a string, and the number 12 is not. + +### LeftEx +{: .no_toc } + +Returns the first characters of a string, after removing the spaces at its ends. + +Syntax: **ToolsStr.LeftEx** ( *Text* **,** *Length* ) **As String** + +*Text* +: *required* A **Variant**. A value that is not a string is converted to one. + +*Length* +: *required* A **Long**, the number of characters. + +The spaces at both ends of *Text* are removed with **Trim** first. Tabs and line breaks stay. [**cJson.Decode**](cJson#decode) uses the function to see whether a text begins with `[`. + +### RightEx +{: .no_toc } + +Returns the last characters of a string, after removing the spaces at its ends. + +Syntax: **ToolsStr.RightEx** ( *Text* **,** *Length* ) **As String** + +*Text* +: *required* A **Variant**. A value that is not a string is converted to one. + +*Length* +: *required* A **Long**, the number of characters. + +The spaces at both ends of *Text* are removed with **Trim** first. Tabs and line breaks stay. + +### TrimEx +{: .no_toc } + +Removes white space and control characters from both ends of a string. + +Syntax: **ToolsStr.TrimEx** ( *Text* ) **As String** + +*Text* +: *required* A **String**. It is passed by reference and is not changed. + +Removes the characters space, tab, carriage return, line feed and every character with a code from 0 to 31, from the start and from the end of *Text*. Other characters that look like spaces, such as the non-breaking space, stay. + +## Searching and cutting + +### GetFirstChar +{: .no_toc } + +Returns the first visible character of a string. + +Syntax: **ToolsStr.GetFirstChar** ( *Txt* [ **,** *Length* ] ) **As String** + +*Txt* +: *required* A **String**. + +*Length* +: *optional* A **Long**, 1 by default. + +The function skips every character whose code is not from 33 to 126, so spaces, control characters and non-ASCII characters are skipped, and finds the first one that is. With a *Length* of 1 the result is that character. With a larger *Length* the result is that many characters from there, taken as they stand. If *Txt* holds no character with a code from 33 to 126, the function raises error 5. + +```tb check_build check_run +Debug.Print ToolsStr.GetFirstChar(" - abc") +Debug.Print ToolsStr.GetFirstChar(" - abc", 3) +' Output: +' - +' - a +``` + +### GetLastChar +{: .no_toc } + +Intended to return the last visible character of a string. + +Syntax: **ToolsStr.GetLastChar** ( *Txt* [ **,** *Length* ] ) **As String** + +*Txt* +: *required* A **String**. + +*Length* +: *optional* A **Long**, 1 by default. + +> [!WARNING] +> The code of this function is a copy of the code of [**GetFirstChar**](#getfirstchar): it searches from the start of the text, and returns the first visible character, not the last. `GetLastChar("abc")` returns `a`. + +```tb check_build check_run +Debug.Print ToolsStr.GetLastChar("abc") +' Output: +' a +``` + +### HasStr +{: .no_toc } + +Finds a string inside another, with the arguments in the order *what*, *where*. + +Syntax: **ToolsStr.HasStr** ( *FindStr* **,** *FullStr* [ **,** *StartPos* ] [ **,** *CompType* ] ) **As Long** + +*FindStr* +: *required* A **String**, the text to find. + +*FullStr* +: *required* A **String**, the text to search. + +*StartPos* +: *optional* A **Long**, the position at which the search starts, 1 by default. + +*CompType* +: *optional* A **VbCompareMethod**, **vbTextCompare** by default. + +Returns the position of the first match, counted from 1, or 0 when there is none: the result of [**InStr**](../../../Modules/Strings/InStr) with the arguments in a different order. Unlike **InStr**, the search ignores letter case unless *CompType* is **vbBinaryCompare**. + +```tb check_build check_run +Debug.Print ToolsStr.HasStr("lo", "Hello") +Debug.Print ToolsStr.HasStr("LO", "Hello") +Debug.Print ToolsStr.HasStr("LO", "Hello", 1, vbBinaryCompare) +' Output: +' 4 +' 4 +' 0 +``` + +### MidEx +{: .no_toc } + +Returns the text between two markers, without its first and last character. + +Syntax: **ToolsStr.MidEx** ( *FullStr* **,** *Lstr* **,** *Rstr* [ **,** *starindex* ] ) **As String** + +*FullStr* +: *required* A **String**, the text to search. + +*Lstr* +: *required* A **String**, the marker at the start. + +*Rstr* +: *required* A **String**, the marker at the end. + +*starindex* +: *optional* A **Long**, passed by reference. When it is not 0, the search for *Lstr* starts at that position and ignores letter case; a search from the start of *FullStr* is case-sensitive. When a match is found, the variable is set to the position after *Rstr*, so a loop that passes the same variable finds one match after another. + +The function finds the first *Lstr*, then the first *Rstr* after it. It returns an empty string if either is missing. Of the text between them, it removes the first and the last character, so the function suits a value that is enclosed in quotation marks: `MidEx("id=[""abc""]", "[", "]")` returns `abc`. If the text between has fewer than two characters, the call raises error 5. + +```tb check_build check_run +Dim Start As Long +Debug.Print ToolsStr.MidEx("id=[""abc""]", "[", "]") +Debug.Print "[" & ToolsStr.MidEx("abc", "[", "]") & "]" + +Debug.Print ToolsStr.MidEx("[""a""][""bc""]", "[", "]", Start) & " " & Start +Debug.Print ToolsStr.MidEx("[""a""][""bc""]", "[", "]", Start) & " " & Start + +On Error Resume Next +Debug.Print ToolsStr.MidEx("x[]y", "[", "]") +Debug.Print Err.Number +' Output: +' abc +' [] +' a 6 +' bc 12 +' 5 +``` + +### SliceString +{: .no_toc } + +Returns a part of a string, counted from 0. + +Syntax: **ToolsStr.SliceString** ( *Arr* **,** *StartPos* [ **,** *EndPos* ] ) **As String** + +*Arr* +: *required* A **String**. The parameter is called *Arr*. + +*StartPos* +: *required* A **Long**, the position of the first character, counting from 0. + +*EndPos* +: *optional* A **Long**, the position of the last character, which is included. The default of -1 means the end of the string. + +```tb check_build check_run +Debug.Print ToolsStr.SliceString("Hello World", 0, 4) +Debug.Print ToolsStr.SliceString("Hello World", 6) +' Output: +' Hello +' World +``` + +### SubStr +{: .no_toc } + +Returns the text between two markers, with the spaces at its ends removed. + +Syntax: **ToolsStr.SubStr** ( *Txt* **,** *txtF* [ **,** *txtE* ] [ **,** *RetInt* ] ) **As String** + +*Txt* +: *required* A **String**, the text to search. + +*txtF* +: *required* A **String**, the marker at the start. The search is case-sensitive. + +*txtE* +: *optional* A **String**, the marker at the end. If it is left out or empty, the text runs to the end of *Txt*. + +*RetInt* +: *optional* A **Boolean**, **False** by default. It chooses what the function returns when nothing is found. + +The function finds the first *txtF*, then the first *txtE* after it, and returns what is between them, with **Trim** applied. When *txtF* is not found, when *txtE* is not found, or when the text between is empty, it returns an empty string, or the text `0` if *RetInt* is **True**. + +```tb check_build check_run +Debug.Print ToolsStr.SubStr("key = value ;", "=", ";") +Debug.Print ToolsStr.SubStr("key = value", "=") +Debug.Print "[" & ToolsStr.SubStr("abc", "x") & "]" +Debug.Print ToolsStr.SubStr("abc", "x", , True) +' Output: +' value +' value +' [] +' 0 +``` + +## Changing text + +### InsertSpan +{: .no_toc } + +Inserts a separator after every group of characters. + +Syntax: **ToolsStr.InsertSpan** ( *inputStr* **,** *Span* **,** *SetpNum* [ **,** *HeadFoot* ] ) **As String** + +*inputStr* +: *required* A **String**. It is passed by reference and is not changed. + +*Span* +: *required* A **String**, the separator. + +*SetpNum* +: *required* A **Long**, the number of characters in each group. It has to be at least 1 and at most 32,767. + +*HeadFoot* +: *optional* A **Boolean**, **False** by default. When it is **True**, *Span* is also placed at the start and at the end of the result. + +The separator is placed between groups, and not after the last one: `InsertSpan("abcdefg", "-", 3)` returns `abc-def-g`, and with *HeadFoot* set it returns `-abc-def-g-`. + +### JoinStr +{: .no_toc } + +Joins strings with a separator. + +Syntax: **ToolsStr.JoinStr** ( *Span* **,** *Strings* ) + +*Span* +: *required* A **String**, the separator. It comes first, before the strings. + +*Strings* +: *required* Any number of values, in a **ParamArray**. + +Returns the values joined with *Span* between them, as **Join** does. + +```tb check_build check_run +Debug.Print ToolsStr.JoinStr("-", "a", "b", "c") +' Output: +' a-b-c +``` + +### PercentDecode +{: .no_toc } + +Replaces each `%XX` in a string with the character that has that code. + +Syntax: **ToolsStr.PercentDecode** ( *encodedStr* ) **As String** + +*encodedStr* +: *required* A **String**. It is passed by reference and is not changed. + +Each `%` and the two characters after it are read as a hexadecimal number, and replaced by **Chr** of the number. The characters are those of the ANSI code page, so the bytes of a UTF-8 text such as `%C3%A9` become two characters, not one. A `%` that is not followed by two hexadecimal digits raises error 13. + +### PercentEncode +{: .no_toc } + +Replaces each digit in a string with `%` and its code. + +Syntax: **ToolsStr.PercentEncode** ( *inputStr* ) **As String** + +*inputStr* +: *required* A **String**. It is passed by reference and is not changed. + +Despite its name, the function does not follow the rules of percent-encoding for web addresses. It replaces the digits `0` to `9` only, each with `%` and the two hexadecimal digits of its code, and leaves every other character as it is: `PercentEncode("A1b2")` returns `A%31b%32`. A space, a slash or a non-ASCII letter is not encoded. [**PercentDecode**](#percentdecode) turns the result back. + +```tb check_build check_run +Debug.Print ToolsStr.PercentEncode("A1b2") +Debug.Print ToolsStr.PercentDecode("A%31b%32") +' Output: +' A%31b%32 +' A1b2 +``` + +### UniVbCrLf +{: .no_toc } + +Changes every kind of line break in a string to carriage return and line feed. + +Syntax: **ToolsStr.UniVbCrLf** ( *Text* ) **As String** + +*Text* +: *required* A **String**. It is passed by reference and is not changed. + +An empty string is returned as it is. A string that has only carriage returns, or only the pairs carriage return and line feed, is converted as expected. + +> [!WARNING] +> A line feed that is not part of a pair becomes a carriage return and **two** line feeds. The function turns each line feed into a pair, and then turns the carriage return of that pair into a pair again. `"a" & vbLf & "b"` comes out as `"a" & vbCr & vbLf & vbLf & "b"`, five characters. +> +> The function also uses the three characters `@@@` as a marker while it works, so a text that holds `@@@` has it replaced by a line break. + +```tb check_build check_run +Dim s As String +Debug.Print Replace(ToolsStr.UniVbCrLf("a" & vbCr & "b"), vbCrLf, "|") +Debug.Print Replace(ToolsStr.UniVbCrLf("a" & vbCrLf & "b"), vbCrLf, "|") + +s = ToolsStr.UniVbCrLf("a" & vbLf & "b") +Debug.Print Len(s) & " " & Replace(Replace(s, vbCr, ""), vbLf, "") +Debug.Print Replace(ToolsStr.UniVbCrLf("x@@@y"), vbCrLf, "|") +' Output: +' a|b +' a|b +' 5 ab +' x|y +``` + +### UnicodeDecode +{: .no_toc } + +Replaces each `\uXXXX` in a string with the character it names. + +Syntax: **ToolsStr.UnicodeDecode** ( *Text* [ **,** *PreFix* ] ) **As String** + +*Text* +: *required* A **String**. It is passed by reference and is not changed. + +*PreFix* +: *optional* A **String**, `\u` by default. It has to be two characters long: the function compares two characters of the text with it and then skips six, whatever its length. + +The four characters after each prefix are read as a hexadecimal number, and the character with that code replaces the six. The letters in the number may be of either case. Four characters that are not hexadecimal digits raise error 13. The function does not know about backslashes before the prefix, so the text `\\u0041`, a backslash that is escaped, followed by `u0041`, is read from the second backslash. + +```tb check_build check_run +Debug.Print ToolsStr.UnicodeDecode("\u0041\u0042") +Debug.Print AscW(Right$(ToolsStr.UnicodeDecode("caf\u00E9"), 1)) +' Output: +' AB +' 233 +``` + +### UnicodeEncode +{: .no_toc } + +Replaces each character that is not in the ASCII range with `\uXXXX`. + +Syntax: **ToolsStr.UnicodeEncode** ( *inputString* [ **,** *PreFix* ] ) **As String** + +*inputString* +: *required* A **String**. + +*PreFix* +: *optional* A **String**, `\u` by default. + +Characters with a code from 0 to 127 stay as they are, the control characters included. Every other character becomes *PreFix* and the four hexadecimal digits of its code, with letters in upper case. A character that is made of two UTF-16 units, such as an emoji, becomes two such groups. + +## Bytes + +### LenBytes +{: .no_toc } + +Returns the number of elements of a byte array. + +Syntax: **ToolsStr.LenBytes** ( *inputArray* ) **As Long** + +*inputArray* +: *required* A **Byte** array. + +Returns the upper bound less the lower bound, plus 1. The comment in the source says *Unicode*, but the function counts the elements, whatever they hold. + +### ToBytes +{: .no_toc } + +Converts a string to a byte array in the ANSI code page. + +Syntax: **ToolsStr.ToBytes** ( *inputString* ) **As Byte()** + +*inputString* +: *required* A **String**. It is passed by reference and is not changed. + +Returns the bytes of the string in the ANSI code page of the system, with **StrConv** and **vbFromUnicode**, from 0. The comment in the source says *Unicode*; the result is not UTF-16 and not UTF-8. A character that the code page does not have is not kept as written. For UTF-8, use [**ToolsUtf8.Encode**](ToolsUtf8#encode). + +### ToHex +{: .no_toc } + +Writes the bytes of an array as hexadecimal digits. + +Syntax: **ToolsStr.ToHex** ( *inputArray* ) **As String** + +*inputArray* +: *required* A **Byte** array whose lower bound is 0. + +Returns the **Hex** of each element, joined with nothing between them. A byte below 16 gives one digit, and not two: `ToHex` of the bytes 10 and 11 is `AB`, the same as the byte 171 gives. The result cannot always be split back into bytes. + +```tb check_build check_run +Debug.Print ToolsStr.ToHex(ToolsStr.ToBytes("AB")) +Debug.Print ToolsStr.ToHex(ToolsStr.ToBytes(Chr$(10))) +' Output: +' 4142 +' A +``` + +### ToString +{: .no_toc } + +Converts a byte array in the ANSI code page to a string. + +Syntax: **ToolsStr.ToString** ( *inputArray* ) **As String** + +*inputArray* +: *required* A **Byte** array. It is passed by reference and is not changed. + +Returns the string, with **StrConv** and **vbUnicode**: each byte is read as a character of the ANSI code page. It is the reverse of [**ToBytes**](#tobytes). For UTF-8, use [**ToolsUtf8.Decode**](ToolsUtf8#decode). + +## Random text and identifiers + +### GetGUID +{: .no_toc } + +Returns a new GUID as text. + +Syntax: **ToolsStr.GetGUID** ( [ *isFull* ] ) **As String** + +*isFull* +: *optional* A **Boolean**, **False** by default. When it is **True**, the GUID is returned with the braces around it. + +The GUID is made by the Windows scripting object `Scriptlet.TypeLib`, which the function creates by name on each call. The result has the form `9A0F3C5E-1B2D-4C6E-8F70-123456789ABC`, 36 characters, or 38 with the braces, with the letters in upper case. + +```tb check_build check_run +Dim g As String +Dim h As String +g = ToolsStr.GetGUID() +h = ToolsStr.GetGUID(True) +Debug.Print Len(g) & " " & Len(h) +Debug.Print Left$(h, 1) & Right$(h, 1) +Debug.Print g = UCase$(g) +' Output: +' 36 38 +' {} +' True +``` + +### GetNewIds +{: .no_toc } + +Unfinished. Returns a fixed text. + +Syntax: **ToolsStr.GetNewIds** ( [ *Bath* ] ) **As String** + +*Bath* +: *optional* A **Long**. It is not used. + +The function returns the Chinese words for *under development*, followed by three periods, and no identifier. + +### GetRandByte +{: .no_toc } + +Returns ten random bytes. + +Syntax: **ToolsStr.GetRandByte** ( ) **As Byte()** + +Returns a **Byte** array from 0 to 9. The function takes no argument, so the number of bytes is always ten. It calls **Randomize** and fills the array with `Int(Rnd * 256)`. The bytes are not suitable for keys, passwords or tokens. + +```tb check_build check_run +Dim Bytes() As Byte +Bytes = ToolsStr.GetRandByte() +Debug.Print UBound(Bytes) +' Output: +' 9 +``` + +### GetRandStr +{: .no_toc } + +Returns a string of random characters. + +Syntax: **ToolsStr.GetRandStr** ( [ *Lens* ] [ **,** *Zuhe* ] ) **As String** + +*Lens* +: *optional* An **Integer**, the number of characters, 32 by default. A value below 1 is replaced by 8. + +*Zuhe* +: *optional* A **String** that says which characters can be used, `1aA` by default. It is a set of letters that stand for groups of characters: `1` for the digits, `a` for the lower-case letters `a` to `z`, `A` for the upper-case letters, and `@` for the 32 punctuation characters of ASCII. An empty string stands for all four groups. Other characters in the string are ignored; if no group is named, the result is an empty string. The name means *combination*. + +Each character is chosen with [**ToolsMath.GetRandRange**](ToolsMath#getrandrange), so the strings are not suitable for passwords, keys or tokens. + +```tb check_build check_run +Dim s As String +s = ToolsStr.GetRandStr(10, "1") +Debug.Print Len(s) +Debug.Print s Like "##########" +Debug.Print Len(ToolsStr.GetRandStr()) +Debug.Print Len(ToolsStr.GetRandStr(0)) +' Output: +' 10 +' True +' 32 +' 8 +``` + +## See Also + +- [ToolsArray](ToolsArray) module -- searching and cutting byte arrays +- [ToolsUtf8](ToolsUtf8) module -- UTF-8 conversion of strings and bytes +- [ToolsMath](ToolsMath) module -- the random numbers behind **GetRandStr** +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsStream.md b/docs/Reference/Contributed/TBMANLIB/ToolsStream.md new file mode 100644 index 000000000..f2a546a68 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsStream.md @@ -0,0 +1,204 @@ +--- +title: ToolsStream +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsStream +has_toc: false +--- + +# ToolsStream module +{: .no_toc } + +Reads and writes whole files as text in a chosen character set, or as an array of bytes. The functions use the ADODB.Stream object of the ADO 2.8 library, which is part of Windows. [**cJson**](cJson) uses the text functions for its [**LoadFrom**](cJson#loadfrom) and [**SaveTo**](cJson#saveto). + +All four functions work through one stream object, [**Inst**](#inst), that every caller in the program shares. That has two consequences, which the entries for the functions describe: a text function that fails leaves the stream open, and a binary function leaves it set to the binary type. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-stream.txt" + +ToolsStream.SaveFileAsText Path, "line 1" & vbCrLf & "line 2" +Debug.Print ToolsStream.LoadFileAsText(Path) = "line 1" & vbCrLf & "line 2" +Kill Path +' Output: +' True +``` + +* TOC +{:toc} + +## Members + +### Inst +{: .no_toc } + +The shared **ADODB.Stream** object that all the functions of the module use. A field, declared `Public Inst As New ADODB.Stream`, created the first time it is used. + +A program can use it, for example to close a stream that a failed call left open: `ToolsStream.Inst.Close`. The program needs no reference to the ADO library for that. + +### LastError +{: .no_toc } + +A **String** field. [**LoadFileAsBinary**](#loadfileasbinary) and [**SaveFileAsBinary**](#savefileasbinary) set it to an empty string when they start, and to the text of the error when they fail. The two text functions neither set it nor clear it. + +### LoadFileAsBinary +{: .no_toc } + +Reads a whole file into a byte array. + +Syntax: **ToolsStream.LoadFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** + +*Path* +: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. + +*OutData* +: *required* A dynamic **Byte** array. It receives the bytes of the file. + +Returns **True** when the file was read, and **False** when an error occurred; the text of the error is then in [**LastError**](#lasterror). The function closes the stream if it is open, sets the stream to the binary type and leaves it so. + +The text in **LastError** is the description of the error, then `#`, then the description a second time. The number of the error is not in it. + +```tb check_build check_run +Dim Bytes() As Byte +Debug.Print ToolsStream.LoadFileAsBinary(Environ$("TEMP") & "\tbmanlib-docs-missing.bin", Bytes) +Debug.Print ToolsStream.LastError +If ToolsStream.Inst.State <> 0 Then ToolsStream.Inst.Close +ToolsStream.Inst.Type = 2 +' Output: +' False +' File could not be opened.#File could not be opened. +``` + +### LoadFileAsText +{: .no_toc } + +Reads a whole file as text and returns it. + +Syntax: **ToolsStream.LoadFileAsText** ( *FileName* [ **,** *CharSet* ] ) **As String** + +*FileName* +: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. + +*CharSet* +: *optional* A **String**, the name of the character set in which the file is stored, `UTF-8` by default. Any name that the ADODB.Stream accepts can be used. A byte order mark at the start of a UTF-8 file is not returned as a character. + +The function opens the stream, sets its character set, loads the file and reads all the text from it. It does not set the type of the stream. It handles no error: a failure is raised to the caller as the error of the stream. + +> [!WARNING] +> In version 1.0.0.5, a failed text call leaves the shared stream open, and a binary call leaves it set to the binary type. Both break the calls that follow. +> +> When the file cannot be opened, the call raises error 3002, *File could not be opened*, and the call that closes the stream is never reached. Every later call to **LoadFileAsText** or [**SaveFileAsText**](#savefileastext) then fails in its first step, which opens a stream that is already open, with error 3705, *Operation is not allowed when the object is open*. The calls work again after `ToolsStream.Inst.Close`, or after a binary function, which closes an open stream first. [**cJson.LoadFrom**](cJson#loadfrom) and [**cJson.SaveTo**](cJson#saveto) are affected in the same way. +> +> A binary function sets the stream to the binary type, and the text functions do not set it back. After a call to [**LoadFileAsBinary**](#loadfileasbinary) or [**SaveFileAsBinary**](#savefileasbinary), a text function fails with error 3219, *Operation is not allowed in this context*. Setting the type back to text with `ToolsStream.Inst.Type = 2`, on the closed stream, makes the text functions work again. + +```tb check_build check_run +Dim Path As String +Dim Text As String +Dim Bytes() As Byte + +Path = Environ$("TEMP") & "\tbmanlib-docs-stream2.txt" +ToolsStream.SaveFileAsText Path, "hello" + +On Error Resume Next +Text = ToolsStream.LoadFileAsText(Environ$("TEMP") & "\tbmanlib-docs-missing.txt") +Debug.Print Err.Number & " " & Err.Description +Err.Clear +Text = ToolsStream.LoadFileAsText(Path) +Debug.Print Err.Number & " " & Err.Description +Err.Clear +ToolsStream.Inst.Close +Debug.Print ToolsStream.LoadFileAsText(Path) + +Bytes = ToolsStr.ToBytes("abc") +Debug.Print ToolsStream.SaveFileAsBinary(Path, Bytes) +Text = ToolsStream.LoadFileAsText(Path) +Debug.Print Err.Number & " " & Err.Description +Err.Clear +If ToolsStream.Inst.State <> 0 Then ToolsStream.Inst.Close +ToolsStream.Inst.Type = 2 +Debug.Print ToolsStream.LoadFileAsText(Path) +Kill Path +' Output: +' 3002 File could not be opened. +' 3705 Operation is not allowed when the object is open. +' hello +' True +' 3219 Operation is not allowed in this context. +' abc +``` + +### SaveFileAsBinary +{: .no_toc } + +Writes a byte array to a file. + +Syntax: **ToolsStream.SaveFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** + +*Path* +: *required* A **String**, the file to write. An existing file is replaced. The folder must exist. + +*OutData* +: *required* A **Byte** array whose bytes are written. The parameter is called *OutData*, as in [**LoadFileAsBinary**](#loadfileasbinary), though here it is the input. + +Returns **True** when the file was written, and **False** when an error occurred; [**LastError**](#lasterror) then holds the text of the error, in the form described for **LoadFileAsBinary**. The stream is closed first if it was open, set to the binary type, and left so. + +```tb check_build check_run +Dim Path As String +Dim Bytes() As Byte +Dim Back() As Byte + +Path = Environ$("TEMP") & "\tbmanlib-docs-stream.bin" +Bytes = ToolsStr.ToBytes("abc") + +Debug.Print ToolsStream.SaveFileAsBinary(Path, Bytes) +Debug.Print ToolsStream.LoadFileAsBinary(Path, Back) +Debug.Print UBound(Back) +Kill Path + +' The stream is binary now; return it to text for the text functions. +ToolsStream.Inst.Type = 2 +' Output: +' True +' True +' 2 +``` + +### SaveFileAsText +{: .no_toc } + +Writes a string to a file as text. + +Syntax: **ToolsStream.SaveFileAsText** ( *FileName* **,** *Data* [ **,** *CharSet* ] ) **As Boolean** + +*FileName* +: *required* A **String**, the file to write. An existing file is replaced. The folder must exist. + +*Data* +: *required* A **String**, the text to write. It is passed by reference. The function adds no line break to it. + +*CharSet* +: *optional* A **String**, the name of the character set, `UTF-8` by default. + +The function opens the stream, sets its character set, writes the text and saves the file. A character set of `UTF-8` writes the byte order mark EF BB BF at the start of the file. The folder of the file has to exist: when it does not, the call raises error 3004, *Write to file failed*. + +> [!WARNING] +> The function always returns **False**. Its result is never set, even when the file was written. A program cannot use the result to see whether the call worked, and has to handle the error that the stream raises. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-stream3.txt" +Debug.Print ToolsStream.SaveFileAsText(Path, "hello") +Debug.Print Dir(Path) = "tbmanlib-docs-stream3.txt" +Kill Path +' Output: +' False +' True +``` + +A failure is raised to the caller and the stream stays open; see the warning for [**LoadFileAsText**](#loadfileastext). + +## See Also + +- [cJson](cJson) class -- uses **LoadFileAsText** and **SaveFileAsText** +- [ToolsUtf8](ToolsUtf8) module -- converts between UTF-8 bytes and strings, without a stream +- [ToolsFso](ToolsFso) module -- folders and path names +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md b/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md new file mode 100644 index 000000000..fcc4ef7ed --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md @@ -0,0 +1,102 @@ +--- +title: ToolsUtf8 +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsUtf8 +has_toc: false +--- + +# ToolsUtf8 module +{: .no_toc } + +Converts between a **String** and an array of bytes in UTF-8, with the Windows functions **MultiByteToWideChar** and **WideCharToMultiByte** and the code page 65001. The module has three functions and needs no other component. + +```tb check_build check_run +Dim Bytes() As Byte +Bytes = ToolsUtf8.Encode("caf" & ChrW(233)) + +Debug.Print UBound(Bytes) + 1 +Debug.Print Hex$(Bytes(3)) & Hex$(Bytes(4)) +Debug.Print ToolsUtf8.Decode(Bytes) = "caf" & ChrW(233) +' Output: +' 5 +' C3A9 +' True +``` + +Both functions that read UTF-8 take a byte array that starts at index 0: they read from `Utf(0)` and take the count of bytes from `UBound`. + +* TOC +{:toc} + +## Functions + +### Decode +{: .no_toc } + +Converts UTF-8 bytes to a string. + +Syntax: **ToolsUtf8.Decode** ( *Utf* ) **As String** + +*Utf* +: *required* A **Byte** array that holds UTF-8 text. It is passed by reference and is not changed. + +Returns the text as a **String**. A byte order mark (EF BB BF) at the start of the bytes is not removed: it becomes the character U+FEFF at the start of the string. + +If *Utf* has not been dimensioned, the function returns an empty string. + +### DecodeToByteArray +{: .no_toc } + +Converts UTF-8 bytes to the bytes of a UTF-16 string. + +Syntax: **ToolsUtf8.DecodeToByteArray** ( *Utf* ) **As Byte()** + +*Utf* +: *required* A **Byte** array that holds UTF-8 text. It is passed by reference and is not changed. + +The conversion produces two bytes for each character of the text, but the array that the function returns is resized to a length in bytes equal to the number of characters, so it holds the first half of the converted bytes only. + +> [!WARNING] +> The result is cut off without an error. For the five UTF-8 bytes of the text `caf` followed by the letter e with an acute accent, which is four characters and eight UTF-16 bytes, the array has four bytes. To get the whole text as UTF-16 bytes, convert with [**Decode**](#decode) and assign the string to a **Byte** array. + +```tb check_build check_run +Dim Utf() As Byte +Dim Half() As Byte +Utf = ToolsUtf8.Encode("caf" & ChrW(233)) +Half = ToolsUtf8.DecodeToByteArray(Utf) +Debug.Print UBound(Half) + 1 +' Output: +' 4 +``` + +If *Utf* has not been dimensioned, the call raises error 9. + +### Encode +{: .no_toc } + +Converts a string to UTF-8 bytes. + +Syntax: **ToolsUtf8.Encode** ( *UCS* ) **As Byte()** + +*UCS* +: *required* A **String**. + +Returns a **Byte** array that starts at index 0 and holds the text in UTF-8, with no byte order mark and no terminating zero byte. + +For an empty string the function returns an array that has not been dimensioned, so `UBound` of the result raises error 9. + +```tb check_build check_run +Dim Utf() As Byte +Utf = ToolsUtf8.Encode("") +On Error Resume Next +Debug.Print UBound(Utf) +Debug.Print Err.Number +' Output: +' 9 +``` + +## See Also + +- [ToolsStream](ToolsStream) module -- reads and writes files in a chosen character set, UTF-8 by default +- [ToolsStr](ToolsStr) module -- **ToBytes** and **ToString**, which convert with the ANSI code page +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cCsv.md b/docs/Reference/Contributed/TBMANLIB/cCsv.md new file mode 100644 index 000000000..0db00e483 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cCsv.md @@ -0,0 +1,169 @@ +--- +title: cCsv +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cCsv +has_toc: false +--- + +# cCsv class +{: .no_toc } + +In version 1.0.0.5, a **cCsv** is a reader and writer of INI files, not of CSV files. It keeps the settings of an INI file in a two-level **Scripting.Dictionary**, [**Root**](#root): the sections are the keys of **Root**, and each item is a dictionary of the keys and values of that section. [**LoadFrom**](#loadfrom) fills **Root** from a file and [**SaveTo**](#saveto) writes **Root** to a file. + +The code is that of an INI tool: it adds the extension `.ini` to a file name, and its error messages name `iniTools`. The class reads and writes no comma-separated values. The package keeps one ready-made instance, [**TBMAN.Csv**](TBMAN#csv). + +```tb check_build +Dim Cfg As New cCsv +Set Cfg.Root = CreateObject("Scripting.Dictionary") +Cfg.Root.Add "Window", CreateObject("Scripting.Dictionary") +Cfg.Root("Window")("Width") = "800" + +If Not Cfg.SaveTo(Environ$("TEMP") & "\tbmanlib-docs-settings.ini") Then + Debug.Print Cfg.LastError +End If +``` + +* TOC +{:toc} + +## The file format + +The files are opened as ANSI text, in the code page of the system, not as Unicode or UTF-8. The text that **SaveTo** writes, and that **LoadFrom** reads, looks like this: + +``` +[Window] +Width=800 + +[Colors] +Back=white +``` + +- A section starts with a name in square brackets on a line of its own. **SaveTo** writes a blank line after each section. +- Each key has a line of its own, in the form `key=value`. **LoadFrom** splits a line at its first `=`, removes the spaces around the key and around the value, and removes one `"` at the start and one at the end of the value, if they are there. +- A blank line and a line that starts with a semicolon are skipped. A line that starts with `#` is not a comment: it is read as a key if it holds an `=`, and skipped if it does not. +- A line that has no `=` and is not a section is skipped. +- **LoadFrom** puts the keys that come before the first section under a section whose name is the empty string. + +## Properties + +### LastError +{: .no_toc } + +A **String**. The text of the last error that [**LoadFrom**](#loadfrom) or [**SaveTo**](#saveto) caught, or an empty string after a call that worked. + +After an error, the text is a Chinese label that names `iniTools.SaveTo` or `iniTools.LoadFrom` (not `cCsv`), then the error number, a `#` and the description. **LoadFrom** adds a colon and the path. When [**Root**](#root) has no entries, **SaveTo** sets the text to a Chinese sentence that means *there is no data to write*. + +### Root +{: .no_toc } + +The data, a **Scripting.Dictionary**: section names are the keys, and each item is a **Scripting.Dictionary** of the keys and values of the section. A field, declared `Public Root As Scripting.Dictionary` with no `New`, so it is **Nothing** until a program sets it: + +```tb check_build +Dim Cfg As New cCsv +Set Cfg.Root = CreateObject("Scripting.Dictionary") +``` + +While **Root** is **Nothing**, both methods catch the error and set [**LastError**](#lasterror). After a file is read, a value is read as `Cfg.Root("Window")("Width")`. [**SaveTo**](#saveto) writes each value as a string. + +## Methods + +### LoadFrom +{: .no_toc } + +Reads an INI file into [**Root**](#root). + +Syntax: *object*.**LoadFrom** ( *Path* ) **As cIni** + +*Path* +: *required* A **String**, the file to read. The extension `.ini` is added to a name that does not end with it, without regard to letter case, so `settings` and `settings.INI` name the same file. The path is used as written: a name without a folder is looked for in the current folder of the program. + +The values that the file holds are added to the dictionaries of [**Root**](#root) and replace the values of the same keys. Nothing that **Root** held before is removed. **Root** has to hold a dictionary before the call. + +The method is declared to return a [**cIni**](.#types-without-members), a class with no members, and its code assigns other values to its result: the first statement, `Set LoadFrom = Me`, assigns the **cCsv** itself, and the last, `LoadFrom = True`, assigns a **Boolean**. Neither is a **cIni**. The first assignment fails with the error *No such interface supported* (-2147467262), and because the error handler is switched on only after that statement, the error goes to the caller, with an empty source. The file is not read, [**LastError**](#lasterror) stays empty and **Root** is not changed. The call fails in this way every time. + +> [!NOTE] +> **LoadFrom** cannot read a file in version 1.0.0.5. + +```tb check_build check_run +Dim Cfg As New cCsv +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-read.ini" + +Open Path For Output As #1 +Print #1, "[Window]" +Print #1, "Width=800" +Close #1 + +Set Cfg.Root = CreateObject("Scripting.Dictionary") +On Error Resume Next +Cfg.LoadFrom Path +Debug.Print Err.Number +Debug.Print "[" & Cfg.LastError & "]" +Debug.Print Cfg.Root.Count +On Error GoTo 0 +Kill Path +' Output: +' -2147467262 +' [] +' 0 +``` + +### SaveTo +{: .no_toc } + +Writes [**Root**](#root) to an INI file. + +Syntax: *object*.**SaveTo** ( [ *Path* ] ) **As Boolean** + +*Path* +: *optional* A **String**, the file to write. The extension `.ini` is added to a name that does not end with it. A call without a path uses the name `.ini`. A file that exists is replaced. + +Returns **True** when the file was written. It returns **False** when **Root** is **Nothing** or has no entries, or when an error occurred; [**LastError**](#lasterror) then says why. + +The method first calls [**AutoMakeDir**](ToolsFso#automakedir) for *Path*, without telling it that the path names a file. **AutoMakeDir** then takes the whole path to be a folder and creates a folder with the name of the file, and the step that opens the file fails because that name is a folder. A file that exists is not affected by the call: no folder can be created with the name of a file, and the file is rewritten. + +> [!WARNING] +> In version 1.0.0.5, a first save of a new file returns **False** and leaves a folder that has the name of the file, `Path.ini`. [**LastError**](#lasterror) then holds error 70, *Permission denied*, which is the error of the step that opens the file. A save over a file that exists works, and replaces the file. + +```tb check_build check_run +Dim Cfg As New cCsv +Dim Path As String +Dim Text As String + +Path = Environ$("TEMP") & "\tbmanlib-docs-save" +Set Cfg.Root = CreateObject("Scripting.Dictionary") +Cfg.Root.Add "Window", CreateObject("Scripting.Dictionary") +Cfg.Root("Window")("Width") = "800" + +' A new file: the save fails and leaves a folder named Path.ini. +Debug.Print Cfg.SaveTo(Path) +Debug.Print InStr(Cfg.LastError, "70#Permission denied") > 0 +Debug.Print (GetAttr(Path & ".ini") And vbDirectory) <> 0 +CreateObject("Scripting.FileSystemObject").DeleteFolder Path & ".ini" + +' A file that exists: the save works. +Open Path & ".ini" For Output As #1 +Close #1 +Debug.Print Cfg.SaveTo(Path) +Open Path & ".ini" For Input As #1 +Do Until EOF(1) + Line Input #1, Text + Debug.Print "[" & Text & "]" +Loop +Close #1 +Kill Path & ".ini" +' Output: +' False +' True +' True +' True +' [[Window]] +' [Width=800] +' [] +``` + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.Csv** +- [ToolsFso](ToolsFso) module -- **AutoMakeDir**, which the method calls +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cJson.md b/docs/Reference/Contributed/TBMANLIB/cJson.md new file mode 100644 index 000000000..f2ec5f600 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cJson.md @@ -0,0 +1,397 @@ +--- +title: cJson +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cJson +has_toc: false +--- + +# cJson class +{: .no_toc } + +A **cJson** holds one JSON document. [**Decode**](#decode) reads a document from text and [**LoadFrom**](#loadfrom) reads one from a file. [**Item**](#item), [**Items**](#items), [**NewItem**](#newitem) and [**NewItems**](#newitems) read and build it, and [**Encode**](#encode) and [**SaveTo**](#saveto) write it as text or to a file. + +An object is created with `Dim J As New cJson`. The package also keeps one ready-made instance, [**TBMAN.Json**](TBMAN#json), which every part of a program shares. + +```tb check_build check_run +Dim J As New cJson +J.Decode "{""name"":""Ann"",""age"":31,""tags"":[""a"",""b""]}" + +Debug.Print J.Item("name") +Debug.Print J.Item("age") + 1 +Debug.Print J.Item("tags")(2) + +J.Item("age") = 32 +Debug.Print J.Encode() +' Output: +' Ann +' 32 +' b +' {"name":"Ann","age":32,"tags":["a","b"]} +``` + +* TOC +{:toc} + +## How the document is held + +The top level of a JSON text is an object or an array, and a **cJson** keeps each in a field of its own: + +- [**RootItem**](#rootitem) is a **Scripting.Dictionary**, which holds a JSON object. Its keys are the names of the object, in the order they were added, and letter case matters. +- [**RootItems**](#rootitems) is a **Collection**, which holds a JSON array. Its items are numbered from 1. + +Inside the document, a nested object is another **Scripting.Dictionary** and a nested array is another **Collection**. A JSON string is a **String**, a number is a **Double**, `true` and `false` are **Boolean** values and `null` is **Null**. A number whose text is 16 or more characters long (17 or more with a decimal point) is returned as a **String** instead, so that no digits are lost; [`ToolsJsonVba.JsonOptions`](ToolsJsonVba#jsonoptions) changes that. The parsing and writing are done by [**ToolsJsonVba**](ToolsJsonVba), the VBA-JSON code. + +A new **cJson** holds an empty object. Only one of the two fields is meant to hold data at a time. [**Root**](#root) returns **RootItems** when it has items and **RootItem** when it has none, and [**Encode**](#encode) with no argument writes **RootItem** when it has entries and **RootItems** only when it has none. + +## Properties + +### Item +{: .no_toc } + +Reads or writes the value stored under a name in [**RootItem**](#rootitem). + +Syntax: *object*.**Item** ( *Key* ) [ **=** *value* ] + +*Key* +: *required* A **String**, the name in the JSON object. + +Reading returns a **Variant**. For a name that is not in the object, the result is **Empty**; no error is raised and no entry is added. A nested object or array is returned as an object. + +Assigning a value stores it under the name, and replaces the value the name had. An object value, such as another **Scripting.Dictionary**, is stored with **Set**. Names are compared with letter case. + +```tb check_build check_run +Dim J As New cJson +Dim Home As New cJson +Home.Item("city") = "Oslo" + +J.Item("name") = "Ann" +Set J.Item("home") = Home.Root +Debug.Print J.Encode() +Debug.Print "[" & J.Item("missing") & "]" +' Output: +' {"name":"Ann","home":{"city":"Oslo"}} +' [] +``` + +### Items +{: .no_toc } + +Reads the item at a position in [**RootItems**](#rootitems). + +Syntax: *object*.**Items** ( *Index* ) + +*Index* +: *required* A **Long**, the position of the item, counted from 1. + +Returns a **Variant**, an object for a nested object or array. If *Index* is greater than the number of items, the result is **Empty**. A position of 0 or less is not checked, and the **Collection** raises its own error. + +The class declares a **Set** accessor for **Items**, which is meant to replace the item at a position with an object, and declares the accessor that would assign a value other than an object under the name `uuItems`, not `Items`. Neither works. `J.Items(1) = 5`, `Set J.Items(1) = New Collection` and `J.uuItems(1) = 5` all compile, and each raises the error *Unspecified error* (-2147467259) and leaves the array as it was. + +```tb check_build check_run +Dim J As New cJson +J.Decode "[1,2]" +On Error Resume Next +J.Items(1) = 5 +Debug.Print Err.Number +Err.Clear +Set J.Items(1) = New Collection +Debug.Print Err.Number +Debug.Print J.Encode() +Debug.Print J.Items(2) +' Output: +' -2147467259 +' -2147467259 +' [1,2] +' 2 +``` + +### LastError +{: .no_toc } + +A **String** that is meant to hold the text of the last error. [**Clear**](#clear), [**Decode**](#decode) and [**Encode**](#encode) set it to an empty string, and no member of the class stores a message in it: [**Decode**](#decode) raises its errors instead. The field stays empty unless a program assigns to it. + +### LastSuccess +{: .no_toc } + +Returns **True** when [**LastError**](#lasterror) is empty. Because nothing stores a message in **LastError**, it is always **True**. + +### Root +{: .no_toc } + +Returns the document as an object: [**RootItems**](#rootitems) if it has at least one item, otherwise [**RootItem**](#rootitem). + +Syntax: *object*.**Root** + +**Root** is the default member of the class, so `J("name")` is `J.Root("name")`. +### RootIsArray +{: .no_toc } + +Returns **True** when [**RootItems**](#rootitems) has at least one item. + +### RootIsEmpty +{: .no_toc } + +Returns **True** when both [**RootItems**](#rootitems) and [**RootItem**](#rootitem) are empty. + +### RootItem +{: .no_toc } + +The **Scripting.Dictionary** that holds a JSON object. A field, declared `Public RootItem As New Scripting.Dictionary`, so the dictionary is created the first time it is used. [**Decode**](#decode) replaces it when the text begins with `{`, and [**Clear**](#clear) releases it. After **Clear** the dictionary is created again the next time it is used, so `J.Item("a") = 1` works on a cleared object. + +### RootItems +{: .no_toc } + +The **Collection** that holds a JSON array. A field, declared `Public RootItems As New Collection`. [**Decode**](#decode) replaces it when the text begins with `[`, and [**NewItem**](#newitem) and [**NewItems**](#newitems) add to it. + +### RootText +{: .no_toc } + +A **String** field. It holds the text that [**Decode**](#decode) was given (with the spaces at both ends removed) or the text that [**Encode**](#encode) last produced. [**Clear**](#clear) empties it. + +### WhiteSpaceSet +{: .no_toc } + +A **Variant** field, empty by default. When it is set, [**Encode**](#encode) and [**SaveTo**](#saveto) use it as their *Whitespace* argument whenever the argument is left out. Set it to an **Integer** to indent by that many spaces for each level, or to a **String** to indent with the first character of the string. See [ConvertToJson](../VBA_JSON/JsonConverter/ConvertToJson) for the layout. + +## Methods + +### Clear +{: .no_toc } + +Empties the document. + +Syntax: *object*.**Clear** + +Sets [**RootText**](#roottext) and [**LastError**](#lasterror) to empty strings, and releases [**RootItem**](#rootitem) and [**RootItems**](#rootitems). + +Call **Clear** before [**Decode**](#decode) when the object held another document. **Decode** replaces only the field that matches the text it reads, so the array that an earlier call stored stays in **RootItems** after an object is decoded, and [**Root**](#root) keeps returning the array. + +### Decode +{: .no_toc } + +Reads a JSON text into the document. + +Syntax: *object*.**Decode** ( *Text* ) **As cJson** + +*Text* +: *required* A **String** that holds JSON. It is passed by reference. The spaces at both ends are removed first, and the result is stored in [**RootText**](#roottext). The text must then begin with `{` or `[`. + +Returns the object itself, so a call can be followed by a member: `J.Decode(Text).Item("name")`. + +A text that begins with `{` replaces [**RootItem**](#rootitem), and a text that begins with `[` replaces [**RootItems**](#rootitems). An empty text changes nothing in the document. + +> [!NOTE] +> Only spaces are removed from the start of the text before it is checked for `[`. A text that has a line break or a tab in front of its `[` is taken for an object, and the array that it holds cannot be stored in **RootItem**: the call raises the error *No such interface supported* (-2147467262), with the source `DECODE`. + +```tb check_build check_run +Dim J As New cJson +On Error Resume Next +J.Decode vbCrLf & "[1,2]" +Debug.Print Err.Number & " " & Err.Source +Err.Clear +J.Decode "[1,2]" +Debug.Print Err.Number & " " & J.Items(2) +' Output: +' -2147467262 DECODE +' 0 2 +``` + +If the text cannot be read, **Decode** raises the error again with the same number (10001 for a syntax error), the source `DECODE`, and the description of the parser followed by `#` and the whole text. The parser is that of [ParseJson](../VBA_JSON/JsonConverter/ParseJson): it carries out no check for commas between items, and it ignores text after the closing bracket of the value. + +```tb check_build check_run +Dim J As New cJson +On Error Resume Next +J.Decode "{""a"" 1}" +Debug.Print Err.Number & " " & Err.Source +' Output: +' 10001 DECODE +``` + +### Encode +{: .no_toc } + +Writes a document as JSON text. + +Syntax: *object*.**Encode** ( [ *Obj* ] [ **,** *Whitespace* ] [ **,** *FromUnicode* ] ) **As String** + +*Obj* +: *optional* The value to write: a **Scripting.Dictionary**, a **Collection** or another **cJson** (its **Root** is written). If it is left out, the object's own document is written. A value that is not an object is not converted: it is stored in [**RootText**](#roottext) as its text, with no quotation marks, and returned. An array is not an object either, and raises error 13, *Type mismatch*. + +*Whitespace* +: *optional* A **Variant**. An **Integer** indents each level by that many spaces, and a **String** indents with its first character, repeated once for each level. If it is left out, [**WhiteSpaceSet**](#whitespaceset) is used when it is set, and otherwise the text has no extra white space. + +*FromUnicode* +: *optional* A **Boolean**, **False** by default. See below. + +The text is built by [ConvertToJson](../VBA_JSON/JsonConverter/ConvertToJson) and also stored in [**RootText**](#roottext). Non-ASCII characters and control characters are written as `\uXXXX` escapes, so the text is ASCII. With *FromUnicode* set to **True**, every `\uXXXX` in the text is turned back into the character it names, and the result holds the characters themselves. + +If the value written has no items, nothing is converted and the function returns [**RootText**](#roottext) as it was: the text of the last call, or an empty string for a new or cleared object. It does not return `{}` or `[]`. + +> [!WARNING] +> With *FromUnicode* set to **True**, the conversion looks for the two characters `\u` anywhere in the text, without regard for the escape that comes before them. A value that holds a backslash followed by the letter `u`, such as `C:\users`, is written by the converter as `C:\\users`, and the conversion meets `\u` in it and tries to read `sers` as four hexadecimal digits. That raises error 13. +> +> Control characters other than tab, line feed, carriage return, backspace and form feed also come back as the raw characters, which are not valid in JSON text. + +```tb check_build check_run +Dim J As New cJson +J.Item("p") = "C:\users" +Debug.Print J.Encode() +On Error Resume Next +Debug.Print J.Encode(, , True) +Debug.Print Err.Number +On Error GoTo 0 + +J.Clear +J.Item("c") = Chr$(1) +Debug.Print AscW(Mid$(J.Encode(, , True), 7, 1)) +' Output: +' {"p":"C:\\users"} +' 13 +' 1 +``` + +```tb check_build check_run +Dim J As New cJson +J.Decode "{""a"":[1,2]}" +Debug.Print Replace(J.Encode(, 2), vbCrLf, "|") + +J.Clear +J.Item("word") = "caf" & ChrW(233) +Debug.Print J.Encode() +Debug.Print J.Encode(, , True) = "{""word"":""caf" & ChrW(233) & """}" +' Output: +' {| "a": [| 1,| 2| ]|} +' {"word":"caf\u00E9"} +' True +``` + +### LoadFrom +{: .no_toc } + +Reads a JSON file into the document. + +Syntax: *object*.**LoadFrom** ( *Path* [ **,** *CharSet* ] ) **As cJson** + +*Path* +: *required* A **String**, the file to read. It is used as written: a name without a folder is looked for in the current folder of the program. (This differs from [**SaveTo**](#saveto), which puts a name without a drive in the program's own folder.) + +*CharSet* +: *optional* A **String**, the name of the character set of the file, `UTF-8` by default. It is passed to an ADODB.Stream, so it can be any name that the stream accepts. A byte order mark at the start of a UTF-8 file is not part of the text. + +The file is read by [**ToolsStream.LoadFileAsText**](ToolsStream#loadfileastext) and the text goes to [**Decode**](#decode), so its rules and errors apply. Returns the object itself. + +> [!IMPORTANT] +> A file that cannot be opened raises the error of the ADODB.Stream and leaves the shared stream of [**ToolsStream**](ToolsStream) open. Every later call to **LoadFrom** or **SaveTo** then fails until the stream is closed with `ToolsStream.Inst.Close`. See [**LoadFileAsText**](ToolsStream#loadfileastext). + +### NewItem +{: .no_toc } + +Adds a new JSON object to the document and returns it for filling. + +Syntax: *object*.**NewItem** ( [ *ParentKey* ] ) **As cJson** + +*ParentKey* +: *optional* A **String**. If it is left out, the new object becomes the next item of [**RootItems**](#rootitems), an element of the array. If it is given, the new object is stored in [**RootItem**](#rootitem) under that name. + +Returns a new **cJson**. The object that it holds in its **RootItem** is the one that was added, so setting values on the returned object changes the document it was made for. + +```tb check_build check_run +Dim J As New cJson +Dim Home As cJson +J.Item("name") = "Ann" +Set Home = J.NewItem("home") +Home.Item("city") = "Oslo" +Debug.Print J.Encode() + +Dim List As New cJson +Dim Row As cJson +Set Row = List.NewItem() +Row.Item("id") = 1 +Set Row = List.NewItem() +Row.Item("id") = 2 +Debug.Print List.Encode() +' Output: +' {"name":"Ann","home":{"city":"Oslo"}} +' [{"id":1},{"id":2}] +``` + +### NewItems +{: .no_toc } + +Adds a new JSON array to the document and returns it for filling. + +Syntax: *object*.**NewItems** ( [ *ParentKey* ] ) **As cJson** + +*ParentKey* +: *optional* A **String**. If it is left out, the new array becomes the next item of [**RootItems**](#rootitems), an array inside an array. If it is given, the new array is stored in [**RootItem**](#rootitem) under that name. + +Returns a new **cJson** whose [**RootItems**](#rootitems) is the array that was added. Add items to it with `Add`, or with **NewItem** on the returned object. + +```tb check_build check_run +Dim J As New cJson +Dim Tags As cJson +Set Tags = J.NewItems("tags") +Tags.RootItems.Add "a" +Tags.RootItems.Add "b" +Debug.Print J.Encode() +' Output: +' {"tags":["a","b"]} +``` + +### SaveTo +{: .no_toc } + +Writes the document to a file and returns the text written. + +Syntax: *object*.**SaveTo** ( *FileName* [ **,** *CharSet* ] [ **,** *Whitespace* ] [ **,** *FromUnicode* ] ) **As String** + +*FileName* +: *required* A **String**, the file to write. A name that does not contain `:\` is taken to be relative and gets the folder of the program (`App.Path`) and a backslash in front. Forward slashes become backslashes, and the folders of the path are created if they are missing. See [**AutoCompleteFullPath**](ToolsFso#autocompletefullpath). A path with a drive that is written with forward slashes, such as `C:/data/a.json`, has no `:\` and is treated as relative. + +*CharSet* +: *optional* A **String**, the name of the character set, `UTF-8` by default. The file is written by [**ToolsStream.SaveFileAsText**](ToolsStream#savefileastext), which replaces a file that exists. + +*Whitespace* +: *optional* A **Variant**; as for [**Encode**](#encode). + +*FromUnicode* +: *optional* A **Boolean**; as for [**Encode**](#encode). With the default of **False** the file holds only ASCII characters, because non-ASCII characters are written as `\uXXXX`. + +The text is made by [**Encode**](#encode) of the object's [**Root**](#root), so the rules of that method apply, among them that an empty document writes the text of an earlier call. The function returns the text that it wrote to the file. A file written with the character set `UTF-8` begins with the byte order mark EF BB BF. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs\sample.json" + +Dim J As New cJson +Dim K As New cJson +J.Item("word") = "caf" & ChrW(233) +Debug.Print J.SaveTo(Path) + +K.LoadFrom Path +Debug.Print AscW(Right$(K.Item("word"), 1)) + +Kill Path +RmDir Environ$("TEMP") & "\tbmanlib-docs" +' Output: +' {"word":"caf\u00E9"} +' 233 +``` + +### ToArray +{: .no_toc } + +Returns the object of the document in an array. + +Syntax: *object*.**ToArray** ( ) **As Variant** + +If [**RootItem**](#rootitem) has entries, the result is an array of one element that holds that **Scripting.Dictionary**. In every other case, including a document held in [**RootItems**](#rootitems), the result is **Empty**. + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.Json** +- [ToolsJsonVba](ToolsJsonVba) module -- the parser and writer behind **Decode** and **Encode** +- [ToolsStream](ToolsStream) module -- reads and writes the files for **LoadFrom** and **SaveTo** +- [ToolsStr](ToolsStr) module -- the `\uXXXX` conversion of **Encode** +- [JsonConverter](../VBA_JSON/JsonConverter/) module -- the same parser and writer, in the VBA_JSON package +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cLayer.md b/docs/Reference/Contributed/TBMANLIB/cLayer.md new file mode 100644 index 000000000..1f8c3d2fc --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cLayer.md @@ -0,0 +1,36 @@ +--- +title: cLayer +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cLayer +has_toc: false +--- + +# cLayer class +{: .no_toc } + +A **cLayer** is meant to hold the user-interface helpers of the package. In version 1.0.0.5 it has one member, [**Toast**](#toast), which shows a message box. The package keeps one ready-made instance, [**TBMAN.Layer**](TBMAN#layer). + +```tb check_build +Dim Layer As New cLayer +Layer.Toast "The file was saved." +``` + +## Methods + +### Toast +{: .no_toc } + +Shows a message in a message box. + +Syntax: *object*.**Toast** ( *Msg* ) **As Long** + +*Msg* +: *required* A **String**, the text of the message. + +The method calls **MsgBox** with the text and no other argument. It is a message box in the usual sense: a dialog with an **OK** button, which stops the program until the user closes it, and not a notice that fades away by itself. The result is always 0, because the code does not set it. + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.Layer** +- [MsgBox](../../../Modules/Interaction/MsgBox) function -- the message box that **Toast** shows +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cLogs.md b/docs/Reference/Contributed/TBMANLIB/cLogs.md new file mode 100644 index 000000000..719d0093b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cLogs.md @@ -0,0 +1,294 @@ +--- +title: cLogs +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cLogs +has_toc: false +--- + +# cLogs class +{: .no_toc } + +A **cLogs** writes log lines. [**Save**](#save) appends a line to a log file, in a folder and a file name that follow a rule the program chooses, and can send the same line to a list box ([**FormView**](#formview)), to a debugging tool ([**DbgView**](#dbgview)) and to a web page ([**WebView**](#webview)). The level of each line and the level that the object is set to decide which lines are kept. + +An object is created with `Dim Logs As New cLogs`. The package also keeps one ready-made instance, [**TBMAN.Logs**](TBMAN#logs), which every part of a program shares. + +```tb check_build check_run +Dim Logs As New cLogs +Dim Line As String + +Logs.LogDir = Environ$("TEMP") & "\tbmanlib-docs-logs" +Logs.LogFileNameRule = EnumLogFileNameRule.byUser +Logs.LogSubDir = "app" +Logs.LogFileNameByUer = "demo.log" + +Line = Logs.Save("started") +Debug.Print Right$(Line, 14) +Debug.Print Dir(Logs.LogDir & "\app\demo.log") + +Logs.LogLevelControl = EnumLogLevel.Warn +Debug.Print "[" & Logs.Save("quiet", EnumLogLevel.Info) & "]" +Debug.Print Right$(Logs.Save("loud", EnumLogLevel.Danger), 13) + +CreateObject("Scripting.FileSystemObject").DeleteFolder Logs.LogDir +' Output: +' [INFO] started +' demo.log +' [] +' [DANGER] loud +``` + +The enumerations are declared inside the class. A program names their members with the name of the enumeration in front, as in the sample, or by the member alone, such as `Warn`. The name of the class cannot be put in front: `cLogs.EnumLogLevel.Warn` is refused with TB5079, *Unrecognized symbol 'cLogs'*. + +* TOC +{:toc} + +## Log lines + +A line is made of the time, the level and the text: `[10/12/2026 3:04:05 PM] [WARN] disk is almost full`. The time is the value of `Now` converted to text with the settings of the system, so its form changes from one computer to another. The level is `INFO`, `WARN`, `DANGER` or `ERROR`, the names of the first four members of [**EnumLogLevel**](#enumloglevel). [**Save**](#save), [**FormView**](#formview), [**WebView**](#webview) and [**DbgView**](#dbgview) all make the line the same way, and each returns it. + +A level is kept when it is equal to or greater than [**LogLevelControl**](#loglevelcontrol). Below that, the member does nothing and returns an empty string. + +The levels **Debugger** and **Custom** have no name for the line: a call with either of them raises error 9, *Subscript out of range*, because the table of names has only four entries. Nothing is written. + +```tb check_build check_run +Dim Logs As New cLogs +Dim Line As String +On Error Resume Next +Line = Logs.Save("x", EnumLogLevel.Debugger) +Debug.Print Err.Number +Err.Clear +Line = Logs.Save("x", EnumLogLevel.Custom) +Debug.Print Err.Number +' Output: +' 9 +' 9 +``` + +## Where a log file goes + +[**Save**](#save) builds the path from the properties below. `` is [**LogDir**](#logdir), or the folder of the program (`App.Path`) when **LogDir** is empty. `` is [**LogSubDir**](#logsubdir). + +| [**LogFileNameRule**](#logfilenamerule) | File | +|:---|:---| +| **ByDay** | `\logs\\\\.txt` | +| **ByMonth** | `\logs\\\.txt` | +| **byUser** | `\\` | + +The year, month and day come from `Now`. The folders that the path names are created if they are missing. For **byUser**, the file name may hold folders, but only the folder part made from `` and `` is created, so a missing folder inside **LogFileNameByUer** makes the call fail with the error *Unspecified error* (-2147467259). + +```tb check_build check_run +Dim Logs As New cLogs +Dim LogName As String + +Logs.LogDir = Environ$("TEMP") & "\tbmanlib-docs-logs2" +Logs.Save "hello" + +LogName = Format$(Now, "yyyyMMdd") & ".txt" +Debug.Print Dir(Logs.LogDir & "\logs\system\" & Format$(Now, "yyyy") & "\" & Format$(Now, "MM") & "\" & LogName) = LogName + +CreateObject("Scripting.FileSystemObject").DeleteFolder Logs.LogDir +' Output: +' True +``` + +Some paths come out with a doubled backslash, such as `\\logs\system`; Windows accepts the path as it is. The file is opened for appending, and each line is written with the **Print #** statement, so the file is ANSI text, in the code page of the system. A character that the code page does not have is not stored as written. On a system whose code page is Western, the letter *e* with an acute accent, `ChrW(233)`, is stored as the byte E9, and a Chinese character such as `ChrW(&H4E2D)` is stored as a question mark. + +## Properties + +### DebugPring +{: .no_toc } + +A **Boolean**, **False** by default. The name is spelled with the letters *Pring*. When it is **True** and `App.LogMode` is 0 (**vbLogAuto**), every line that is made is also printed to the Debug Console with **Debug.Print**, which in a debug run shows it there. + +### FormListBox +{: .no_toc } + +A [**ListBox**](../../VB/ListBox/) of a form, to which [**FormView**](#formview) adds the lines. A field, **Nothing** by default. Assign it with **Set**: `Set Logs.FormListBox = List1`. + +### FormListBoxMaxItem +{: .no_toc } + +A **Long**, the number of lines that [**FormView**](#formview) keeps in the list. The default is 0, and any value below 9 is replaced by 999 the first time a line is added: the property is then 999. + +### LogDir +{: .no_toc } + +A **String**, the folder under which [**Save**](#save) puts its files. When it is empty, the folder of the program is used. The folder need not exist. + +### LogFileNameByUer +{: .no_toc } + +A **String**, the file name for the rule **byUser**, with a path under the sub-folder if the program wants one. The name is spelled with the letters *Uer*. When it is empty, [**Save**](#save) sets it to `vb.txt`. + +### LogFileNameRule +{: .no_toc } + +An [**EnumLogFileNameRule**](#enumlogfilenamerule), the rule that decides the path of the file. The default is **None**, and [**Save**](#save) changes **None** to **ByDay** the first time it runs, so the property is **ByDay** afterwards. + +### LogLevelControl +{: .no_toc } + +An [**EnumLogLevel**](#enumloglevel), the lowest level that is kept. The default is **Info**, so every line is kept. A higher value means fewer lines. + +### LogSubDir +{: .no_toc } + +A **String**, a folder under the root that separates the logs of one part of the program from those of another. When it is empty, [**Save**](#save) sets it to `system`. + +### SendToDbgView +{: .no_toc } + +A **Boolean**, **False** by default. When it is **True**, [**Save**](#save) also calls [**DbgView**](#dbgview) for every line. The *ToDbgView* argument of **Save** has the same effect for one call. + +### SendToFormView +{: .no_toc } + +A **Boolean**, **False** by default. When it is **True**, [**Save**](#save) also calls [**FormView**](#formview) for every line. The *ToFormView* argument of **Save** has the same effect for one call. + +### SendToWebView +{: .no_toc } + +A **Boolean**, **False** by default. When it is **True**, [**Save**](#save) also calls [**WebView**](#webview) for every line. The *ToWebView* argument of **Save** has the same effect for one call. + +### WebUserCode +{: .no_toc } + +A **String**, the code that identifies the program's log on the web page. [**WebView**](#webview) sends nothing while it is empty. The class's comments say that the code can be any string made of letters and digits. + +## Methods + +### DbgView +{: .no_toc } + +Sends a log line to a debugging tool. + +Syntax: *object*.**DbgView** ( *LogStr* [ **,** *LogLevel* ] ) **As String** + +*LogStr* +: *required* A **String**, the text of the line. + +*LogLevel* +: *optional* An [**EnumLogLevel**](#enumloglevel), the level of the line. The default is **Info**. + +The line is passed to the Windows function **OutputDebugString**, with the text `[TBMAN-LOG]` and a full-width colon in front of it, so a tool that shows debugging output, such as DebugView, can filter the lines of the class by that prefix. Returns the line, or an empty string if its level is below [**LogLevelControl**](#loglevelcontrol). + +### FormView +{: .no_toc } + +Adds a log line to a list box. + +Syntax: *object*.**FormView** ( *LogStr* [ **,** *LogLevel* ] ) **As String** + +*LogStr* +: *required* A **String**, the text of the line. + +*LogLevel* +: *optional* An [**EnumLogLevel**](#enumloglevel), the level of the line. The default is **Info**. + +The line is added at the top of [**FormListBox**](#formlistbox), where it is the first item, and the oldest lines are removed so that the list holds at most [**FormListBoxMaxItem**](#formlistboxmaxitem) lines. If **FormListBox** is **Nothing**, nothing happens and the result is an empty string. Otherwise the result is the line, or an empty string if its level is below [**LogLevelControl**](#loglevelcontrol). + +```tb check_build +Dim Logs As New cLogs +Set Logs.FormListBox = List1 +Logs.FormListBoxMaxItem = 200 +Logs.FormView "Ready", EnumLogLevel.Info +``` + +### Save +{: .no_toc } + +Appends a log line to the log file, and returns the line. + +Syntax: *object*.**Save** ( *LogStr* [ **,** *LogLevel* ] [ **,** *ToFormView* ] [ **,** *ToDbgView* ] [ **,** *ToWebView* ] ) **As String** + +*LogStr* +: *required* A **String**, the text of the line. + +*LogLevel* +: *optional* An [**EnumLogLevel**](#enumloglevel), the level of the line. The default is **Info**. + +*ToFormView*, *ToDbgView*, *ToWebView* +: *optional* **Boolean** values, **False** by default. Each sends the line to [**FormView**](#formview), [**DbgView**](#dbgview) or [**WebView**](#webview) as well, for this call. The properties [**SendToFormView**](#sendtoformview), [**SendToDbgView**](#sendtodbgview) and [**SendToWebView**](#sendtowebview) do the same for every call. + +The method first sets [**LogSubDir**](#logsubdir), [**LogFileNameByUer**](#logfilenamebyuer) and [**LogFileNameRule**](#logfilenamerule) to their working values if they are empty, and that stays so even if the line is then dropped for its level. It makes the line, creates the folders and appends the line to the file as described in [Where a log file goes](#where-a-log-file-goes). Then it calls the three other members in the order **DbgView**, **WebView**, **FormView**, for each output that is switched on. A file error, such as a path that cannot be created, is raised to the caller. + +Returns the line, or an empty string if its level is below [**LogLevelControl**](#loglevelcontrol). + +> [!WARNING] +> The calls to the three other members do not pass the level of the line. They make the line again at the level **Info**. A line sent to a list box, a debugging tool or a web page is therefore labelled `INFO` whatever level **Save** was given, and when **LogLevelControl** is above **Info** it is not sent at all. The result of **Save** is the text that was made last: the `INFO` line, when one of the outputs sent it. + +```tb check_build check_run +Dim Logs As New cLogs +Dim Line As String +Logs.LogDir = Environ$("TEMP") & "\tbmanlib-docs-logs3" + +Line = Logs.Save("x", EnumLogLevel.Warn, , True) +Debug.Print InStr(Line, "[INFO] x") > 0 + +Logs.LogLevelControl = EnumLogLevel.Warn +Line = Logs.Save("x", EnumLogLevel.Warn, , True) +Debug.Print InStr(Line, "[WARN] x") > 0 + +CreateObject("Scripting.FileSystemObject").DeleteFolder Logs.LogDir +' Output: +' True +' True +``` + +### WebView +{: .no_toc } + +Sends a log line to a web page. + +Syntax: *object*.**WebView** ( *LogStr* [ **,** *LogLevel* ] ) **As String** + +*LogStr* +: *required* A **String**, the text of the line. + +*LogLevel* +: *optional* An [**EnumLogLevel**](#enumloglevel), the level of the line. The default is **Info**. + +If [**WebUserCode**](#webusercode) is empty, nothing happens and the result is an empty string. Otherwise the line is sent in an asynchronous HTTP POST request, made with the late-bound object `WinHttp.WinHttpRequest.5.1`, to `http://log.vb6.pro/submit?code=` followed by **WebUserCode**, with the body `log=` followed by the line. The call does not wait for the answer and does not report whether the request worked. The result is the line, or an empty string if its level is below [**LogLevelControl**](#loglevelcontrol). + +> [!IMPORTANT] +> The request uses `http`, not `https`, and goes to a web site that is not part of the package: the code and the text of every line can be read by any computer on the way. Neither the code nor the line is URL-encoded, so a `&`, an `=`, a `%` or a `+` in the line changes what the site receives. Do not send lines that hold private data. + +```tb check_build +Dim Logs As New cLogs +Logs.WebUserCode = "abc123" +Logs.WebView "service started", EnumLogLevel.Info +``` + +## Enumerations + +### EnumLogFileNameRule +{: .no_toc } + +How [**Save**](#save) names its files. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **None** | 0 | The default. **Save** changes it to **ByDay**. | +| **ByMonth** | 1 | One file for each month. | +| **ByDay** | 2 | One file for each day, in a folder for each month. | +| **byUser** | 3 | The file named by [**LogFileNameByUer**](#logfilenamebyuer). | + +### EnumLogLevel +{: .no_toc } + +The level of a log line. + +| Member | Value | Label in the line | +|:-------|------:|:------------------| +| **Info** | 0 | `INFO` | +| **Warn** | 1 | `WARN` | +| **Danger** | 2 | `DANGER` | +| **Error** | 3 | `ERROR` | +| **Debugger** | 4 | none: raises error 9 | +| **Custom** | 5 | none: raises error 9 | + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.Logs** +- [cTimeuse](cTimeuse) class -- measures the time a part of a program takes +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cTimeuse.md b/docs/Reference/Contributed/TBMANLIB/cTimeuse.md new file mode 100644 index 000000000..9db03141d --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cTimeuse.md @@ -0,0 +1,55 @@ +--- +title: cTimeuse +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cTimeuse +has_toc: false +--- + +# cTimeuse class +{: .no_toc } + +A **cTimeuse** is a stopwatch. [**Start**](#start) records the time, and [**Show**](#show) returns a message with the number of milliseconds since then. The class is declared as `cTimeuse`, with a lower-case *u*; twinBASIC does not distinguish the case of names. The package keeps one ready-made instance, named [**TBMAN.TimerUse**](TBMAN#timeruse). + +```tb check_build check_run +Dim Watch As New cTimeuse +Watch.Start + +' ... the work to be timed ... + +Debug.Print Watch.Show("took {*} ms") Like "took * ms" +' Output: +' True +``` + +The clock is the Windows function **GetTickCount**, the time since the computer started, in milliseconds. Its resolution is that of the system timer, which is 10 to 16 milliseconds, so a part of a program that takes less than that can show as 0. The value is held in a **Long**, which holds a little under 25 days of milliseconds as a positive number: a computer that has been running longer than that gives negative tick counts, and the difference of two counts across the change can overflow. + +## Methods + +### Show +{: .no_toc } + +Returns a message with the time since [**Start**](#start). + +Syntax: *object*.**Show** ( [ *Txt* ] ) **As Variant** + +*Txt* +: *optional* A **String**, the message. Every `{*}` in it is replaced by the number of milliseconds. The default is a Chinese message: the words for *time used*, a colon, `{*}` and the word for *milliseconds*. + +Returns a **String** in a **Variant**. The number is the tick count at the call less the tick count at **Start**. If **Start** has not been called, the number is the tick count itself: the milliseconds since the computer started. + +### Start +{: .no_toc } + +Records the time at which the measurement starts. + +Syntax: *object*.**Start** + +Stores the current tick count. Calling **Start** again starts a new measurement. + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.TimerUse** +- [cLogs](cLogs) class -- writes log lines +- [TwinTimerPackage](../TwinTimerPackage/) package -- a Win32 timer class that reports the elapsed time +- [Timer](../../../Modules/DateTime/Timer) function -- the seconds since midnight +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/index.md b/docs/Reference/Contributed/TBMANLIB/index.md new file mode 100644 index 000000000..d9e6a5298 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/index.md @@ -0,0 +1,125 @@ +--- +title: TBMANLIB Package +parent: Experimental Packages +has_toc: false +exclude_from_docs: + - cIni + - cTimer + - ToolsJsonUcs +indexed_from: 1.0.0.5 +permalink: /tB/Packages/Contributed/TBMANLIB/ +--- + +# TBMANLIB Package +{: .no_toc } + +**Author:** woeoio. **On GitHub:** [woeoio/tbman](https://github.com/woeoio/tbman), the repository of the TBMAN toolkit. Its first commit, of 7 December 2024, the day this version was published, holds the code of this package. The repository has been developed since, and its current code differs from version 1.0.0.5. + +The **TBMANLIB** contributed package is the library of **TBMAN**, a toolkit for twinBASIC programmers that grew out of the author's VB6 library *vbman*. It is a collection of small helpers rather than a library with one theme: a JSON document class and parser, a log writer, a stopwatch, an INI file class, a message-box helper, and modules of functions for strings, arrays and bytes, UTF-8, dictionaries, files and folders, numbers and JavaScript objects. The package's own description on TWINSERV is the single word *TBMAN*. + +```tb check_build check_run +Dim J As New cJson +Dim UserName As String +J.Decode "{""user"":{""name"":""Ann""},""scores"":[3,5]}" + +UserName = J.Item("user")("name") +Debug.Print UserName +Debug.Print J.Item("scores")(2) +Debug.Print ToolsStr.InsertSpan(UserName, "-", 1) +Debug.Print J.Encode() +' Output: +' Ann +' 5 +' A-n-n +' {"user":{"name":"Ann"},"scores":[3,5]} +``` + +* TOC +{:toc} + +## What the package offers + +- **JSON.** [**cJson**](cJson) holds a JSON document: it reads a text or a file, gives access to its objects and arrays, builds new ones, and writes them as text or to a file. The parser and writer are the code of VBA-JSON, in [**ToolsJsonVba**](ToolsJsonVba). +- **Logs.** [**cLogs**](cLogs) writes log lines to files in a folder tree by day or month, and can send each line to a list box, to a debugging tool or to a web page, with four levels. +- **Time.** [**cTimeuse**](cTimeuse) is a stopwatch with a millisecond count. +- **Messages.** [**cLayer**](cLayer) shows a message box. +- **INI files.** [**cCsv**](cCsv), in spite of its name, reads and writes INI files. +- **Text and bytes.** [**ToolsStr**](ToolsStr) trims, cuts and searches strings, makes `%XX` and `\uXXXX` escapes, converts between strings and bytes, and makes random strings and GUIDs. [**ToolsArray**](ToolsArray) finds, slices and splits byte arrays. [**ToolsUtf8**](ToolsUtf8) converts between UTF-8 bytes and strings. [**ToolsMath**](ToolsMath) rounds up and picks random numbers. +- **Files and folders.** [**ToolsFso**](ToolsFso) completes paths and creates folders. [**ToolsStream**](ToolsStream) reads and writes whole files as text in a chosen character set or as bytes. +- **Dictionaries and JavaScript.** [**ToolsDic**](ToolsDic) reads and writes form text, merges and copies `Scripting.Dictionary` objects. [**ToolsJScript**](ToolsJScript) creates JavaScript arrays and objects with the Microsoft Script Control. +- **Ready-made instances.** The module [**TBMAN**](TBMAN) has one shared instance of each class, so a program can write `TBMAN.Json` or `TBMAN.Logs` without `New`. + +The classes are created with `New`, as in `Dim J As New cJson`. The members of a module are called with the name of the module in front, as in `ToolsStr.TrimEx(...)`. The names of the package's own members keep the author's spelling, including the misspellings, such as **DebugPring**, **LogFileNameByUer** and **TowLevelDicAssign**; this documentation uses them as they are. + +## Components the package uses + +The package is written on top of components that are not part of twinBASIC. Its references are the Microsoft Scripting Runtime, the Microsoft ActiveX Data Objects 2.8 library and the Microsoft Script Control 1.0, and some members create other objects by name. + +| Component | Used by | +|:----------|:--------| +| Scripting Runtime: `Scripting.Dictionary` | **cJson** (`RootItem`), **cCsv** (`Root`), **ToolsDic**, **ToolsJsonVba** | +| Scripting Runtime: `Scripting.FileSystemObject` | **cCsv**, **ToolsFso** | +| ActiveX Data Objects: `ADODB.Stream` | **ToolsStream**, so **cJson.LoadFrom** and **cJson.SaveTo** | +| Microsoft Script Control: `MSScriptControl.ScriptControl` | **ToolsJScript**, so **ToolsDic.ToWwwFormUrlencoded** | +| `WinHttp.WinHttpRequest.5.1`, created by name | **cLogs.WebView** | +| `Scriptlet.TypeLib`, created by name | **ToolsStr.GetGUID** | +| `imagehlp.dll` (`MakeSureDirectoryPathExists`) | **cLogs.Save**, **ToolsFso.AutoMakeDir** | +| `kernel32.dll` (`GetTickCount`, `OutputDebugStringA`, `RtlMoveMemory`, `MultiByteToWideChar`, `WideCharToMultiByte`) | **cTimeuse**, **cLogs.DbgView**, **ToolsArray**, **ToolsUtf8** | + +The references of a package are not visible to the project that uses it. A program that names a type of these libraries in its own code, such as `Dim D As New Scripting.Dictionary`, adds the library to its own references; in a project that references only TBMANLIB the compiler refuses the name with TB5079, *Unrecognized datatype symbol 'Scripting'*. A program can call members that return those objects without naming the type, for example `J.Item("user")("name")`, and can create an object by name with **CreateObject**. + +### 32-bit and 64-bit programs + +The Microsoft Script Control exists only as a 32-bit component, and the package's reference to it has a path for 32-bit programs and none for 64-bit programs. + +> [!WARNING] +> In BETA 1005, a project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) crashes the compiler, again after each restart, until the IDE falls back to Safe Mode, even when the program uses nothing of the package. The cause is the reference to the Script Control: when that reference and the module that uses it are removed from a copy of the package, the compiler no longer crashes, and reports only the errors of the code that needed the reference. The package can be used in 32-bit programs only. + +The Script Control is used by **ToolsJScript** and, through it, by **ToolsDic.ToWwwFormUrlencoded**. Those members do not work in 32-bit programs either; see [ToolsJScript](ToolsJScript). The other components are available to 32-bit programs. + +## Defects of version 1.0.0.5 + +Part of the code is unfinished, and the source holds test code and notes to the author. Where a member does not do what its name or comment says, the page of the member says so. The ones that matter most are these: + +- [**cCsv.SaveTo**](cCsv#saveto) does not write a new file, and [**cCsv.LoadFrom**](cCsv#loadfrom) raises an error before it reads anything. +- [**ToolsDic.ToWwwFormUrlencoded**](ToolsDic#towwwformurlencoded) adds two entries to the dictionary it is given and then fails, and [**ToolsJScript.NewArr**](ToolsJScript#newarr) and **NewObj** raise error 91. +- [**cJson.Items**](cJson#items) cannot be assigned to, and [**cJson.Decode**](cJson#decode) rejects an array that follows a line break. +- [**ToolsStream.SaveFileAsText**](ToolsStream#savefileastext) always returns **False**, and a failed text call leaves the shared stream open, so that the next calls fail. +- [**ToolsStr.GetLastChar**](ToolsStr#getlastchar) returns the first character, [**UniVbCrLf**](ToolsStr#univbcrlf) doubles a line feed, and [**IsEmptyEx**](ToolsStr#isemptyex) trims the variable it is given. +- [**ToolsUtf8.DecodeToByteArray**](ToolsUtf8#decodetobytearray) returns half of the bytes. +- [**ToolsArray.SliceByteArray**](ToolsArray#slicebytearray) returns a wrong result when the start and the end are the same. +- [**cLogs.Save**](cLogs#save) sends the other outputs at the level **Info**, and the levels **Debugger** and **Custom** raise an error. + +## Classes + +- [cCsv](cCsv) -- reads and writes INI files into a two-level dictionary. +- [cJson](cJson) -- holds a JSON document: reads and writes text and files, and builds objects and arrays. +- [cLayer](cLayer) -- shows a message box. +- [cLogs](cLogs) -- writes log lines to files and to other outputs, with levels. +- [cTimeuse](cTimeuse) -- a stopwatch in milliseconds. + +## Modules + +- [TBMAN](TBMAN) -- shared instances of the classes, and a version text. +- [ToolsArray](ToolsArray) -- finds, slices and splits arrays of bytes and longs. +- [ToolsDic](ToolsDic) -- form text, merging and copying of dictionaries. +- [ToolsFso](ToolsFso) -- completes paths, tidies slashes and creates folders. +- [ToolsJScript](ToolsJScript) -- creates JavaScript arrays and objects with the Script Control. +- [ToolsJsonVba](ToolsJsonVba) -- the VBA-JSON parser and writer, and the VBA-UTC date functions. +- [ToolsMath](ToolsMath) -- rounds up, and picks random whole numbers. +- [ToolsStr](ToolsStr) -- trims, searches, escapes and converts strings; random text and GUIDs. +- [ToolsStream](ToolsStream) -- reads and writes whole files as text or bytes. +- [ToolsUtf8](ToolsUtf8) -- converts between UTF-8 bytes and strings. + +## Types without members +{: #types-without-members } + +Three types of the package have no members, and have no page: **cIni**, **cTimer** and **ToolsJsonUcs**. Each holds only the comment *add your member fields and procedures here*, or, for **ToolsJsonUcs**, *add your procedures here*. **cIni** is the type of [**TBMAN.Ini**](TBMAN#ini) and the declared result of [**cCsv.LoadFrom**](cCsv#loadfrom). + +## See Also + +- [VBA_JSON](../VBA_JSON/) package -- the same JSON code as a module of its own +- [TBMANLIB_Dialog](../TBMANLIB_Dialog/) package -- the Open, Save As and Browse For Folder dialogs, by the same author +- [TBMANLIB_RES](../TBMANLIB_RES/) package -- resources embedded in an executable, by the same author +- [TBMANLIB_Xml](../TBMANLIB_Xml/) package -- XML documents, by the same author +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 2aaca4fe1..c9aee6bfe 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -13,6 +13,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. +- [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.2 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. diff --git a/docs/_book.yml b/docs/_book.yml index b9461f1ee..6d673501f 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -599,6 +599,13 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB Package + subtitle: A toolkit of helpers for JSON, logs, INI files, strings, bytes, files and dictionaries + landing_page: /tB/Packages/Contributed/TBMANLIB/ + page: /tB/Packages/Contributed/TBMANLIB/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Aes Package subtitle: AES encryption of text and byte arrays, through the Windows Crypto API and CNG landing_page: /tB/Packages/Contributed/TBMANLIB_Aes/ diff --git a/indexer/snapshots/contributed/TBMANLIB/api.json b/indexer/snapshots/contributed/TBMANLIB/api.json new file mode 100644 index 000000000..d6aad3431 --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB/api.json @@ -0,0 +1,484 @@ +{ + "format": 3, + "package": "TBMANLIB", + "project": "TBMANLIB", + "version": "1.0.0.5", + "types": [ + { + "name": "TBMAN", + "kind": "module", + "vis": "public", + "file": "TBMAN.twin", + "signature": "Module TBMAN", + "members": [ + {"name":"Csv","kind":"field","vis":"public","signature":"Public Csv As New TBMANLIB.cCsv"}, + {"name":"Ini","kind":"field","vis":"public","signature":"Public Ini As New TBMANLIB.cIni"}, + {"name":"Json","kind":"field","vis":"public","signature":"Public Json As New TBMANLIB.cJson"}, + {"name":"Layer","kind":"field","vis":"public","signature":"Public Layer As New TBMANLIB.cLayer"}, + {"name":"Logs","kind":"field","vis":"public","signature":"Public Logs As New TBMANLIB.cLogs"}, + {"name":"TimerUse","kind":"field","vis":"public","signature":"Public TimerUse As New TBMANLIB.cTimeuse"}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As String","returnType":"String","accessor":"Get"} + ] + }, + { + "name": "ToolsArray", + "kind": "module", + "vis": "public", + "file": "Tools/Array/ToolsArray.twin", + "signature": "Module ToolsArray", + "members": [ + {"name":"Command1_Click","kind":"sub","vis":"private","signature":"Private Sub Command1_Click()"}, + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" (Destination As Any, Source As Any, ByVal length As Long)"}, + {"name":"DeArray","kind":"sub","vis":"public","signature":"Public Sub DeArray(Arr As Variant, ParamArray OutVars())"}, + {"name":"Extend","kind":"property","vis":"public","signature":"Public Property Let Extend(Vars As Variant, Value As Variant)","accessor":"Let"}, + {"name":"FindByteArray","kind":"function","vis":"public","signature":"Public Function FindByteArray(ByRef Arr() As Byte, ByRef Keyword() As Byte) As Long","returnType":"Long"}, + {"name":"GetArrayLength","kind":"function","vis":"public","signature":"Public Function GetArrayLength(Arr() As Variant) As Long","returnType":"Long"}, + {"name":"GetIndexByValue","kind":"function","vis":"public","signature":"Public Function GetIndexByValue(Arr As Variant, Value As String) As Long","returnType":"Long"}, + {"name":"IsArrayEmpty","kind":"function","vis":"public","signature":"Public Function IsArrayEmpty(Arr As Variant) As Boolean","returnType":"Boolean"}, + {"name":"SliceByteArray","kind":"function","vis":"public","signature":"Function SliceByteArray(ByRef Arr() As Byte, ByVal StartPos As Long, Optional ByVal EndPos As Long = -1) As Byte()","returnType":"Byte()"}, + {"name":"SliceLongArray","kind":"function","vis":"public","signature":"Function SliceLongArray(ByRef Arr() As Long, ByVal StartPos As Long, Optional ByVal EndPos As Long = -1) As Long()","returnType":"Long()"}, + {"name":"SplitByteArray","kind":"function","vis":"public","signature":"Public Function SplitByteArray(ByRef Arr() As Byte, ByVal position As Long, Optional Offset As Long) As Variant","returnType":"Variant"}, + {"name":"SplitByteArrayByKeyword","kind":"function","vis":"public","signature":"Function SplitByteArrayByKeyword(ByRef ByteArray() As Byte, ByVal Keyword As Variant, Optional ByVal SplitCount As Long) As Variant","returnType":"Variant"}, + {"name":"SplitLongToBytes","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SplitLongToBytes Lib \"msvbvm60\" Alias \"#644\" (ByVal lngNum As Long) As longByteType","returnType":"longByteType"}, + {"name":"StringToByteArray","kind":"function","vis":"public","signature":"Public Function StringToByteArray(ByVal St As String) As Byte()","returnType":"Byte()"}, + {"name":"Test","kind":"sub","vis":"public","signature":"Sub Test()"}, + {"name":"TestSliceArray","kind":"sub","vis":"public","signature":"Sub TestSliceArray()"}, + {"name":"TestSliceArray2","kind":"sub","vis":"public","signature":"Public Sub TestSliceArray2()"}, + {"name":"TestSplitArrayByKeyword","kind":"sub","vis":"public","signature":"Sub TestSplitArrayByKeyword()"} + ] + }, + { + "name": "longByteType", + "kind": "type", + "in": "ToolsArray", + "vis": "private", + "public": false, + "file": "Tools/Array/ToolsArray.twin", + "signature": "Private Type longByteType", + "fields": [ + {"name":"a1","vis":"public","signature":"a1 As Byte"}, + {"name":"a2","vis":"public","signature":"a2 As Byte"}, + {"name":"a3","vis":"public","signature":"a3 As Byte"}, + {"name":"a4","vis":"public","signature":"a4 As Byte"} + ], + "members": [] + }, + { + "name": "longType", + "kind": "type", + "in": "ToolsArray", + "vis": "private", + "public": false, + "file": "Tools/Array/ToolsArray.twin", + "signature": "Private Type longType", + "fields": [ + {"name":"v","vis":"public","signature":"v As Long"} + ], + "members": [] + }, + { + "name": "ToolsDic", + "kind": "module", + "vis": "public", + "file": "Tools/Dic/ToolsDic.twin", + "signature": "Module ToolsDic", + "members": [ + {"name":"DeepCopy","kind":"function","vis":"public","signature":"Public Function DeepCopy(srcDic As Scripting.Dictionary) As Scripting.Dictionary","returnType":"Scripting.Dictionary"}, + {"name":"FromWwwFormUrlencoded","kind":"function","vis":"public","signature":"Public Function FromWwwFormUrlencoded(Content As String, Dic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"OverWrite","kind":"sub","vis":"public","signature":"Public Sub OverWrite(DistDic As Scripting.Dictionary, srcDic As Scripting.Dictionary, Optional OnlyKey As Boolean = True)"}, + {"name":"ToWwwFormUrlencoded","kind":"function","vis":"public","signature":"Public Function ToWwwFormUrlencoded(Dic As Scripting.Dictionary) As String","returnType":"String"}, + {"name":"TowLevelDicAssign","kind":"sub","vis":"public","signature":"Public Sub TowLevelDicAssign(Dic As Scripting.Dictionary, Lv1Name As String, Lv2Name As String, Value As Variant)"} + ] + }, + { + "name": "ToolsFso", + "kind": "module", + "vis": "public", + "file": "Tools/FSO/ToolsFso.twin", + "signature": "Module ToolsFso", + "members": [ + {"name":"AutoCompleteFullPath","kind":"function","vis":"public","signature":"Public Function AutoCompleteFullPath(Path As String, Optional IsFile As Boolean) As String","returnType":"String"}, + {"name":"AutoMakeDir","kind":"function","vis":"public","signature":"Public Function AutoMakeDir(ByVal Path As String, Optional IsFile As Boolean) As String","returnType":"String"}, + {"name":"ClearSpan","kind":"function","vis":"public","signature":"Public Function ClearSpan(Path As String, Optional IsFile As Boolean) As String","returnType":"String"}, + {"name":"Inst","kind":"field","vis":"public","signature":"Public Inst As New Scripting.FileSystemObject"}, + {"name":"MakeNewFileFulPath","kind":"function","vis":"public","signature":"Public Function MakeNewFileFulPath(FileSrc As String, AppendFix As String, Optional JoinStr As String = \"_\") As String","returnType":"String"}, + {"name":"MakeSureDirectoryPathExists","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MakeSureDirectoryPathExists Lib \"imagehlp.dll\" (ByVal DirPath As String) As Long","returnType":"Long"}, + {"name":"Test","kind":"sub","vis":"public","signature":"Public Sub Test()"} + ] + }, + { + "name": "ToolsJScript", + "kind": "module", + "vis": "public", + "file": "Tools/JScript/ToolsJScript.twin", + "signature": "Module ToolsJScript", + "members": [ + {"name":"Init","kind":"sub","vis":"private","signature":"Private Sub Init()"}, + {"name":"IsInit","kind":"field","vis":"private","signature":"Dim IsInit As Boolean"}, + {"name":"MSSC","kind":"field","vis":"private","signature":"Dim MSSC As New MSScriptControl.ScriptControl"}, + {"name":"NewArr","kind":"function","vis":"public","signature":"Public Function NewArr(ParamArray Item() As Variant) As Object","returnType":"Object"}, + {"name":"NewObj","kind":"function","vis":"public","signature":"Public Function NewObj() As Object","returnType":"Object"} + ] + }, + { + "name": "ToolsJsonUcs", + "kind": "module", + "vis": "public", + "file": "Libs/Json/inc/ToolsJsonUcs.twin", + "signature": "Module ToolsJsonUcs", + "members": [] + }, + { + "name": "ToolsJsonVba", + "kind": "module", + "vis": "public", + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Module ToolsJsonVba", + "members": [ + {"name":"ConvertToIso","kind":"function","vis":"public","signature":"Public Function ConvertToIso(utc_LocalDate As Date) As String","returnType":"String"}, + {"name":"ConvertToJson","kind":"function","vis":"public","signature":"Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitespace As Variant, Optional ByVal json_CurrentIndentation As Long = 0) As String","returnType":"String"}, + {"name":"ConvertToUtc","kind":"function","vis":"public","signature":"Public Function ConvertToUtc(utc_LocalDate As Date) As Date","returnType":"Date"}, + {"name":"JsonOptions","kind":"field","vis":"public","signature":"Public JsonOptions As json_Options"}, + {"name":"ParseIso","kind":"function","vis":"public","signature":"Public Function ParseIso(utc_IsoString As String) As Date","returnType":"Date"}, + {"name":"ParseJson","kind":"function","vis":"public","signature":"Public Function ParseJson(ByVal JsonString As String) As Object","returnType":"Object"}, + {"name":"ParseUtc","kind":"function","vis":"public","signature":"Public Function ParseUtc(utc_UtcDate As Date) As Date","returnType":"Date"}, + {"name":"json_BufferAppend","kind":"sub","vis":"private","signature":"Private Sub json_BufferAppend(ByRef json_Buffer As String, ByRef json_Append As Variant, ByRef json_BufferPosition As Long, ByRef json_BufferLength As Long)"}, + {"name":"json_BufferToString","kind":"function","vis":"private","signature":"Private Function json_BufferToString(ByRef json_Buffer As String, ByVal json_BufferPosition As Long) As String","returnType":"String"}, + {"name":"json_Encode","kind":"function","vis":"private","signature":"Private Function json_Encode(ByVal json_Text As Variant) As String","returnType":"String"}, + {"name":"json_IsUndefined","kind":"function","vis":"private","signature":"Private Function json_IsUndefined(ByVal json_Value As Variant) As Boolean","returnType":"Boolean"}, + {"name":"json_ParseArray","kind":"function","vis":"private","signature":"Private Function json_ParseArray(json_String As String, ByRef json_Index As Long) As Collection","returnType":"Collection"}, + {"name":"json_ParseErrorMessage","kind":"function","vis":"private","signature":"Private Function json_ParseErrorMessage(json_String As String, ByRef json_Index As Long, ErrorMessage As String) As Variant","returnType":"Variant"}, + {"name":"json_ParseKey","kind":"function","vis":"private","signature":"Private Function json_ParseKey(json_String As String, ByRef json_Index As Long) As String","returnType":"String"}, + {"name":"json_ParseNumber","kind":"function","vis":"private","signature":"Private Function json_ParseNumber(json_String As String, ByRef json_Index As Long) As Variant","returnType":"Variant"}, + {"name":"json_ParseObject","kind":"function","vis":"private","signature":"Private Function json_ParseObject(json_String As String, ByRef json_Index As Long) As Dictionary","returnType":"Dictionary"}, + {"name":"json_ParseString","kind":"function","vis":"private","signature":"Private Function json_ParseString(json_String As String, ByRef json_Index As Long) As String","returnType":"String"}, + {"name":"json_ParseValue","kind":"function","vis":"private","signature":"Private Function json_ParseValue(json_String As String, ByRef json_Index As Long) As Variant","returnType":"Variant"}, + {"name":"json_Peek","kind":"function","vis":"private","signature":"Private Function json_Peek(json_String As String, ByVal json_Index As Long, Optional json_NumberOfCharacters As Long = 1) As String","returnType":"String"}, + {"name":"json_SkipSpaces","kind":"sub","vis":"private","signature":"Private Sub json_SkipSpaces(json_String As String, ByRef json_Index As Long)"}, + {"name":"json_StringIsLargeNumber","kind":"function","vis":"private","signature":"Private Function json_StringIsLargeNumber(json_String As Variant) As Boolean","returnType":"Boolean"}, + {"name":"utc_ConvertDate","kind":"function","vis":"private","signature":"Private Function utc_ConvertDate(utc_Value As Date, Optional utc_ConvertToUtc As Boolean = False) As Date","returnType":"Date"}, + {"name":"utc_DateToSystemTime","kind":"function","vis":"private","signature":"Private Function utc_DateToSystemTime(utc_Value As Date) As utc_SYSTEMTIME","returnType":"utc_SYSTEMTIME"}, + {"name":"utc_ExecuteInShell","kind":"function","vis":"private","signature":"Private Function utc_ExecuteInShell(utc_ShellCommand As String) As utc_ShellResult","returnType":"utc_ShellResult"}, + {"name":"utc_GetTimeZoneInformation","kind":"function","vis":"private","declare":true,"returnType":"Long","signatures":[{"cond":"Not (Mac) And VBA7","signature":"Private Declare PtrSafe Function utc_GetTimeZoneInformation Lib \"kernel32\" Alias \"GetTimeZoneInformation\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long"},{"cond":"Not (Mac) And Not (VBA7)","signature":"Private Declare Function utc_GetTimeZoneInformation Lib \"kernel32\" Alias \"GetTimeZoneInformation\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long"}]}, + {"name":"utc_SystemTimeToDate","kind":"function","vis":"private","signature":"Private Function utc_SystemTimeToDate(utc_Value As utc_SYSTEMTIME) As Date","returnType":"Date"}, + {"name":"utc_SystemTimeToTzSpecificLocalTime","kind":"function","vis":"private","declare":true,"returnType":"Long","signatures":[{"cond":"Not (Mac) And VBA7","signature":"Private Declare PtrSafe Function utc_SystemTimeToTzSpecificLocalTime Lib \"kernel32\" Alias \"SystemTimeToTzSpecificLocalTime\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long"},{"cond":"Not (Mac) And Not (VBA7)","signature":"Private Declare Function utc_SystemTimeToTzSpecificLocalTime Lib \"kernel32\" Alias \"SystemTimeToTzSpecificLocalTime\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long"}]}, + {"name":"utc_TzSpecificLocalTimeToSystemTime","kind":"function","vis":"private","declare":true,"returnType":"Long","signatures":[{"cond":"Not (Mac) And VBA7","signature":"Private Declare PtrSafe Function utc_TzSpecificLocalTimeToSystemTime Lib \"kernel32\" Alias \"TzSpecificLocalTimeToSystemTime\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long"},{"cond":"Not (Mac) And Not (VBA7)","signature":"Private Declare Function utc_TzSpecificLocalTimeToSystemTime Lib \"kernel32\" Alias \"TzSpecificLocalTimeToSystemTime\" (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long"}]}, + {"name":"utc_feof","kind":"function","vis":"private","declare":true,"signatures":[{"cond":"Mac And VBA7","signature":"Private Declare PtrSafe Function utc_feof Lib \"/usr/lib/libc.dylib\" Alias \"feof\" (ByVal utc_File As LongPtr) As LongPtr","returnType":"LongPtr"},{"cond":"Mac And Not (VBA7)","signature":"Private Declare Function utc_feof Lib \"libc.dylib\" Alias \"feof\" (ByVal utc_File As Long) As Long","returnType":"Long"}]}, + {"name":"utc_fread","kind":"function","vis":"private","declare":true,"signatures":[{"cond":"Mac And VBA7","signature":"Private Declare PtrSafe Function utc_fread Lib \"/usr/lib/libc.dylib\" Alias \"fread\" (ByVal utc_Buffer As String, ByVal utc_Size As LongPtr, ByVal utc_Number As LongPtr, ByVal utc_File As LongPtr) As LongPtr","returnType":"LongPtr"},{"cond":"Mac And Not (VBA7)","signature":"Private Declare Function utc_fread Lib \"libc.dylib\" Alias \"fread\" (ByVal utc_Buffer As String, ByVal utc_Size As Long, ByVal utc_Number As Long, ByVal utc_File As Long) As Long","returnType":"Long"}]}, + {"name":"utc_pclose","kind":"function","vis":"private","declare":true,"signatures":[{"cond":"Mac And VBA7","signature":"Private Declare PtrSafe Function utc_pclose Lib \"/usr/lib/libc.dylib\" Alias \"pclose\" (ByVal utc_File As LongPtr) As LongPtr","returnType":"LongPtr"},{"cond":"Mac And Not (VBA7)","signature":"Private Declare Function utc_pclose Lib \"libc.dylib\" Alias \"pclose\" (ByVal utc_File As Long) As Long","returnType":"Long"}]}, + {"name":"utc_popen","kind":"function","vis":"private","declare":true,"signatures":[{"cond":"Mac And VBA7","signature":"Private Declare PtrSafe Function utc_popen Lib \"/usr/lib/libc.dylib\" Alias \"popen\" (ByVal utc_Command As String, ByVal utc_Mode As String) As LongPtr","returnType":"LongPtr"},{"cond":"Mac And Not (VBA7)","signature":"Private Declare Function utc_popen Lib \"libc.dylib\" Alias \"popen\" (ByVal utc_Command As String, ByVal utc_Mode As String) As Long","returnType":"Long"}]} + ] + }, + { + "name": "json_Options", + "kind": "type", + "in": "ToolsJsonVba", + "vis": "private", + "public": false, + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Private Type json_Options", + "fields": [ + {"name":"UseDoubleForLargeNumbers","vis":"public","signature":"UseDoubleForLargeNumbers As Boolean"}, + {"name":"AllowUnquotedKeys","vis":"public","signature":"AllowUnquotedKeys As Boolean"}, + {"name":"EscapeSolidus","vis":"public","signature":"EscapeSolidus As Boolean"} + ], + "members": [] + }, + { + "name": "utc_SYSTEMTIME", + "kind": "type", + "in": "ToolsJsonVba", + "vis": "private", + "public": false, + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Private Type utc_SYSTEMTIME", + "fields": [ + {"name":"utc_wYear","vis":"public","signature":"utc_wYear As Integer"}, + {"name":"utc_wMonth","vis":"public","signature":"utc_wMonth As Integer"}, + {"name":"utc_wDayOfWeek","vis":"public","signature":"utc_wDayOfWeek As Integer"}, + {"name":"utc_wDay","vis":"public","signature":"utc_wDay As Integer"}, + {"name":"utc_wHour","vis":"public","signature":"utc_wHour As Integer"}, + {"name":"utc_wMinute","vis":"public","signature":"utc_wMinute As Integer"}, + {"name":"utc_wSecond","vis":"public","signature":"utc_wSecond As Integer"}, + {"name":"utc_wMilliseconds","vis":"public","signature":"utc_wMilliseconds As Integer"} + ], + "members": [] + }, + { + "name": "utc_ShellResult", + "kind": "type", + "in": "ToolsJsonVba", + "vis": "private", + "public": false, + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Private Type utc_ShellResult", + "fields": [ + {"name":"utc_Output","vis":"public","signature":"utc_Output As String"}, + {"name":"utc_ExitCode","vis":"public","signature":"utc_ExitCode As LongPtr"} + ], + "members": [] + }, + { + "name": "utc_ShellResult", + "kind": "type", + "in": "ToolsJsonVba", + "vis": "private", + "public": false, + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Private Type utc_ShellResult", + "fields": [ + {"name":"utc_Output","vis":"public","signature":"utc_Output As String"}, + {"name":"utc_ExitCode","vis":"public","signature":"utc_ExitCode As Long"} + ], + "members": [] + }, + { + "name": "utc_TIME_ZONE_INFORMATION", + "kind": "type", + "in": "ToolsJsonVba", + "vis": "private", + "public": false, + "file": "Libs/Json/inc/ToolsJsonVba.twin", + "signature": "Private Type utc_TIME_ZONE_INFORMATION", + "fields": [ + {"name":"utc_Bias","vis":"public","signature":"utc_Bias As Long"}, + {"name":"utc_StandardName","vis":"public","signature":"utc_StandardName(0 To 31) As Integer"}, + {"name":"utc_StandardDate","vis":"public","signature":"utc_StandardDate As utc_SYSTEMTIME"}, + {"name":"utc_StandardBias","vis":"public","signature":"utc_StandardBias As Long"}, + {"name":"utc_DaylightName","vis":"public","signature":"utc_DaylightName(0 To 31) As Integer"}, + {"name":"utc_DaylightDate","vis":"public","signature":"utc_DaylightDate As utc_SYSTEMTIME"}, + {"name":"utc_DaylightBias","vis":"public","signature":"utc_DaylightBias As Long"} + ], + "members": [] + }, + { + "name": "ToolsMath", + "kind": "module", + "vis": "public", + "file": "Tools/Math/ToolsMath.twin", + "signature": "Module ToolsMath", + "members": [ + {"name":"Ceil","kind":"function","vis":"public","signature":"Public Function Ceil(ByVal Num As Variant, Optional ByVal Dot As Long) As Double","returnType":"Double"}, + {"name":"GetRandRange","kind":"function","vis":"public","signature":"Public Function GetRandRange(a As Long, b As Long) As Long","returnType":"Long"} + ] + }, + { + "name": "ToolsStr", + "kind": "module", + "vis": "public", + "file": "Tools/String/ToolsStr.twin", + "signature": "Module ToolsStr", + "members": [ + {"name":"GetFirstChar","kind":"function","vis":"public","signature":"Public Function GetFirstChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, + {"name":"GetGUID","kind":"function","vis":"public","signature":"Public Function GetGUID(Optional isFull As Boolean) As String","returnType":"String"}, + {"name":"GetLastChar","kind":"function","vis":"public","signature":"Public Function GetLastChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, + {"name":"GetNewIds","kind":"function","vis":"public","signature":"Public Function GetNewIds(Optional ByVal Bath As Long) As String","returnType":"String"}, + {"name":"GetRandByte","kind":"function","vis":"public","signature":"Public Function GetRandByte() As Byte()","returnType":"Byte()"}, + {"name":"GetRandStr","kind":"function","vis":"public","signature":"Public Function GetRandStr(Optional ByVal Lens As Integer = 32, Optional Zuhe As String = \"1aA\") As String","returnType":"String"}, + {"name":"HasStr","kind":"function","vis":"public","signature":"Public Function HasStr(FindStr As String, FullStr As String, Optional StartPos As Long = 1, Optional CompType As VbCompareMethod = vbTextCompare) As Long","returnType":"Long"}, + {"name":"InsertSpan","kind":"function","vis":"public","signature":"Public Function InsertSpan(ByRef inputStr As String, ByVal Span As String, ByVal SetpNum As Long, Optional HeadFoot As Boolean) As String","returnType":"String"}, + {"name":"IsEmptyEx","kind":"function","vis":"public","signature":"Public Function IsEmptyEx(Text As String) As Boolean","returnType":"Boolean"}, + {"name":"IsString","kind":"function","vis":"public","signature":"Public Function IsString(var As Variant) As Boolean","returnType":"Boolean"}, + {"name":"JoinStr","kind":"function","vis":"public","signature":"Public Function JoinStr(Span As String, ParamArray Strings() As Variant) As String","returnType":"String"}, + {"name":"LeftEx","kind":"function","vis":"public","signature":"Public Function LeftEx(Text As Variant, Length As Long) As String","returnType":"String"}, + {"name":"LenBytes","kind":"function","vis":"public","signature":"Public Function LenBytes(inputArray() As Byte) As Long","returnType":"Long"}, + {"name":"MidEx","kind":"function","vis":"public","signature":"Public Function MidEx(FullStr As String, Lstr As String, Rstr As String, Optional starindex As Long) As String","returnType":"String"}, + {"name":"PercentDecode","kind":"function","vis":"public","signature":"Function PercentDecode(encodedStr As String) As String","returnType":"String"}, + {"name":"PercentEncode","kind":"function","vis":"public","signature":"Public Function PercentEncode(inputStr As String) As String","returnType":"String"}, + {"name":"RightEx","kind":"function","vis":"public","signature":"Public Function RightEx(Text As Variant, Length As Long) As String","returnType":"String"}, + {"name":"SliceString","kind":"function","vis":"public","signature":"Public Function SliceString(ByVal Arr As String, ByVal StartPos As Long, Optional ByVal EndPos As Long = -1) As String","returnType":"String"}, + {"name":"SubStr","kind":"function","vis":"public","signature":"Public Function SubStr(ByVal Txt As String, ByVal txtF$, Optional ByVal txtE$, Optional RetInt As Boolean) As String","returnType":"String"}, + {"name":"ToBytes","kind":"function","vis":"public","signature":"Public Function ToBytes(inputString As String) As Byte()","returnType":"Byte()"}, + {"name":"ToHex","kind":"function","vis":"public","signature":"Public Function ToHex(inputArray() As Byte) As String","returnType":"String"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(inputArray() As Byte) As String","returnType":"String"}, + {"name":"TrimEx","kind":"function","vis":"public","signature":"Public Function TrimEx(ByRef Text As String) As String","returnType":"String"}, + {"name":"UniVbCrLf","kind":"function","vis":"public","signature":"Public Function UniVbCrLf(Text As String) As String","returnType":"String"}, + {"name":"UnicodeDecode","kind":"function","vis":"public","signature":"Public Function UnicodeDecode(Text As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"}, + {"name":"UnicodeEncode","kind":"function","vis":"public","signature":"Public Function UnicodeEncode(ByVal inputString As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"} + ] + }, + { + "name": "ToolsStream", + "kind": "module", + "vis": "public", + "file": "Tools/FSO/ToolsStream.twin", + "signature": "Module ToolsStream", + "members": [ + {"name":"Inst","kind":"field","vis":"public","signature":"Public Inst As New ADODB.Stream"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"LoadFileAsBinary","kind":"function","vis":"public","signature":"Public Function LoadFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"LoadFileAsText","kind":"function","vis":"public","signature":"Public Function LoadFileAsText(ByVal FileName As String, Optional CharSet As String = \"UTF-8\") As String","returnType":"String"}, + {"name":"SaveFileAsBinary","kind":"function","vis":"public","signature":"Public Function SaveFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"SaveFileAsText","kind":"function","vis":"public","signature":"Public Function SaveFileAsText(ByVal FileName As String, Data As String, Optional CharSet As String = \"UTF-8\") As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "ToolsUtf8", + "kind": "module", + "vis": "public", + "file": "Tools/CharSet/ToolsUtf8.twin", + "signature": "Module ToolsUtf8", + "members": [ + {"name":"CP_UTF8","kind":"const","vis":"private","signature":"Private Const CP_UTF8 = 65001"}, + {"name":"Decode","kind":"function","vis":"public","signature":"Public Function Decode(ByRef Utf() As Byte) As String","returnType":"String"}, + {"name":"DecodeToByteArray","kind":"function","vis":"public","signature":"Public Function DecodeToByteArray(ByRef Utf() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"Encode","kind":"function","vis":"public","signature":"Public Function Encode(ByVal UCS As String) As Byte()","returnType":"Byte()"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32\" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cchMultiByte As LongPtr, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function WideCharToMultiByte Lib \"kernel32\" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As LongPtr, ByRef lpMultiByteStr As Any, ByVal cchMultiByte As Long, ByVal lpDefaultChar As String, ByVal lpUsedDefaultChar As LongPtr) As Long","returnType":"Long"} + ] + }, + { + "name": "cCsv", + "kind": "class", + "vis": "public", + "file": "Libs/FileSystem/Csv/cCsv.twin", + "signature": "Class cCsv", + "members": [ + {"name":"FileStream","kind":"field","vis":"private","signature":"Dim FileStream As Scripting.TextStream"}, + {"name":"FixFileExtName","kind":"sub","vis":"private","signature":"Private Sub FixFileExtName(Path As String)"}, + {"name":"Fso","kind":"field","vis":"private","signature":"Dim Fso As New Scripting.FileSystemObject"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal Path As String) As cIni","returnType":"cIni"}, + {"name":"Root","kind":"field","vis":"public","signature":"Public Root As Scripting.Dictionary"}, + {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo(Optional ByVal Path As String) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "cIni", + "kind": "class", + "vis": "public", + "file": "Libs/FileSystem/Ini/cIni.twin", + "signature": "Class cIni", + "members": [] + }, + { + "name": "cJson", + "kind": "class", + "vis": "public", + "file": "Libs/Json/cJson.twin", + "signature": "Class cJson", + "members": [ + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"Decode","kind":"function","vis":"public","signature":"Public Function Decode(ByRef Text As String) As cJson","returnType":"cJson"}, + {"name":"Encode","kind":"function","vis":"public","signature":"Public Function Encode(Optional Obj As Variant, Optional Whitespace As Variant, Optional FromUnicode As Boolean) As String","returnType":"String"}, + {"name":"Fso","kind":"field","vis":"private","signature":"Dim Fso As New Scripting.FileSystemObject"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal Key As String) As Variant","returnType":"Variant","accessor":"Get"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal Key As String, Dat As Variant)","accessor":"Set"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal Key As String, Dat As Variant)","accessor":"Let"}, + {"name":"Items","kind":"property","vis":"public","signature":"Public Property Get Items(ByVal Index As Long) As Variant","returnType":"Variant","accessor":"Get"}, + {"name":"Items","kind":"property","vis":"public","signature":"Public Property Set Items(ByVal Index As Long, Dat As Variant)","accessor":"Set"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"LastSuccess","kind":"property","vis":"public","signature":"Public Property Get LastSuccess() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal Path As String, Optional CharSet As String = \"UTF-8\") As cJson","returnType":"cJson"}, + {"name":"NewItem","kind":"function","vis":"public","signature":"Public Function NewItem(Optional ParentKey As String) As cJson","returnType":"cJson"}, + {"name":"NewItems","kind":"function","vis":"public","signature":"Public Function NewItems(Optional ParentKey As String) As cJson","returnType":"cJson"}, + {"name":"Root","kind":"property","vis":"public","signature":"Public Property Get Root() As Object","returnType":"Object","accessor":"Get"}, + {"name":"RootIsArray","kind":"property","vis":"public","signature":"Public Property Get RootIsArray() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RootIsEmpty","kind":"property","vis":"public","signature":"Public Property Get RootIsEmpty() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RootItem","kind":"field","vis":"public","signature":"Public RootItem As New Scripting.Dictionary"}, + {"name":"RootItems","kind":"field","vis":"public","signature":"Public RootItems As New Collection"}, + {"name":"RootText","kind":"field","vis":"public","signature":"Public RootText As String"}, + {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo( ByVal FileName As String, Optional CharSet As String = \"UTF-8\", Optional Whitespace As Variant, Optional FromUnicode As Boolean) As String","returnType":"String"}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant","returnType":"Variant"}, + {"name":"WhiteSpaceSet","kind":"field","vis":"public","signature":"Public WhiteSpaceSet As Variant"}, + {"name":"uuItems","kind":"property","vis":"public","signature":"Public Property Let uuItems(ByVal Index As Long, Dat As Variant)","accessor":"Let"} + ] + }, + { + "name": "cLayer", + "kind": "class", + "vis": "public", + "file": "Libs/Layer/cLayer.twin", + "signature": "Class cLayer", + "members": [ + {"name":"Toast","kind":"function","vis":"public","signature":"Public Function Toast(ByVal Msg As String) As Long","returnType":"Long"} + ] + }, + { + "name": "cLogs", + "kind": "class", + "vis": "public", + "file": "Libs/Logs/cLogs.twin", + "signature": "Class cLogs", + "members": [ + {"name":"DbgView","kind":"function","vis":"public","signature":"Public Function DbgView(LogStr As String, Optional LogLevel As EnumLogLevel) As String","returnType":"String"}, + {"name":"DebugPring","kind":"field","vis":"public","signature":"Public DebugPring As Boolean"}, + {"name":"FileNumber","kind":"field","vis":"private","signature":"Dim FileNumber As Variant"}, + {"name":"FormListBox","kind":"field","vis":"public","signature":"Public FormListBox As VB.ListBox"}, + {"name":"FormListBoxMaxItem","kind":"field","vis":"public","signature":"Public FormListBoxMaxItem As Long"}, + {"name":"FormView","kind":"function","vis":"public","signature":"Public Function FormView(LogStr As String, Optional LogLevel As EnumLogLevel) As String","returnType":"String"}, + {"name":"LevelData","kind":"field","vis":"private","signature":"Dim LevelData As Variant"}, + {"name":"LogData","kind":"field","vis":"private","signature":"Dim LogData As String"}, + {"name":"LogDir","kind":"field","vis":"public","signature":"Public LogDir As String"}, + {"name":"LogFileNameByUer","kind":"field","vis":"public","signature":"Public LogFileNameByUer As String"}, + {"name":"LogFileNameRule","kind":"field","vis":"public","signature":"Public LogFileNameRule As EnumLogFileNameRule"}, + {"name":"LogLevelControl","kind":"field","vis":"public","signature":"Public LogLevelControl As EnumLogLevel"}, + {"name":"LogSubDir","kind":"field","vis":"public","signature":"Public LogSubDir As String"}, + {"name":"MakeLogData","kind":"function","vis":"private","signature":"Private Function MakeLogData(LogStr As String, LogLevel As EnumLogLevel) As Boolean","returnType":"Boolean"}, + {"name":"MakeSureDirectoryPathExists","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MakeSureDirectoryPathExists Lib \"imagehlp.dll\" (ByVal DirPath As String) As Long","returnType":"Long"}, + {"name":"OutputDebugString","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub OutputDebugString Lib \"kernel32\" Alias \"OutputDebugStringA\" (ByVal lpOutputString As String)"}, + {"name":"Save","kind":"function","vis":"public","signature":"Public Function Save(ByVal LogStr As String, Optional LogLevel As EnumLogLevel, Optional ToFormView As Boolean, Optional ToDbgView As Boolean, Optional ToWebView As Boolean) As String","returnType":"String"}, + {"name":"SendToDbgView","kind":"field","vis":"public","signature":"Public SendToDbgView As Boolean"}, + {"name":"SendToFormView","kind":"field","vis":"public","signature":"Public SendToFormView As Boolean"}, + {"name":"SendToWebView","kind":"field","vis":"public","signature":"Public SendToWebView As Boolean"}, + {"name":"WEB_URL","kind":"const","vis":"private","signature":"Private Const WEB_URL As String = \"http://log.vb6.pro/submit\""}, + {"name":"WebUserCode","kind":"field","vis":"public","signature":"Public WebUserCode As String"}, + {"name":"WebView","kind":"function","vis":"public","signature":"Public Function WebView(LogStr As String, Optional LogLevel As EnumLogLevel) As String","returnType":"String"} + ] + }, + { + "name": "EnumLogFileNameRule", + "kind": "enum", + "in": "cLogs", + "vis": "public", + "file": "Libs/Logs/cLogs.twin", + "signature": "Enum EnumLogFileNameRule", + "values": [ + {"name":"None","value":"0"}, + {"name":"ByMonth","value":"1"}, + {"name":"ByDay","value":"2"}, + {"name":"byUser","value":"3"} + ] + }, + { + "name": "EnumLogLevel", + "kind": "enum", + "in": "cLogs", + "vis": "public", + "file": "Libs/Logs/cLogs.twin", + "signature": "Enum EnumLogLevel", + "values": [ + {"name":"Info","value":"0"}, + {"name":"Warn","value":"1"}, + {"name":"Danger","value":"2"}, + {"name":"Error","value":"3"}, + {"name":"Debugger","value":"4"}, + {"name":"Custom","value":"5"} + ] + }, + { + "name": "cTimer", + "kind": "class", + "vis": "public", + "file": "Libs/DateTime/cTimer.twin", + "signature": "Class cTimer", + "members": [] + }, + { + "name": "cTimeuse", + "kind": "class", + "vis": "public", + "file": "Libs/DateTime/cTimeUse.twin", + "signature": "Class cTimeuse", + "members": [ + {"name":"GetTickCount","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetTickCount Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"Show","kind":"function","vis":"public","signature":"Public Function Show(Optional Txt As String = \"耗时:{*} 毫秒\") As Variant","returnType":"Variant"}, + {"name":"Start","kind":"sub","vis":"public","signature":"Public Sub Start()"}, + {"name":"startTime","kind":"field","vis":"private","signature":"Dim startTime As Long"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-TBMANLIB/Settings b/test/example-projects/contributed-TBMANLIB/Settings new file mode 100644 index 000000000..0970338be --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB/Settings @@ -0,0 +1,85 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{EC8B1351-80F1-46CA-8634-DEC5271616AA}", + "licence": "MIT", + "name": "[IMPORTED] TBMAN", + "path32": "/Packages/TBMANLIB", + "path64": "/Packages/TBMANLIB", + "symbolId": "TBMANLIB", + "versionBuild": "5", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + }, + { + "autoCtlExt": true, + "id": "{420B2830-E718-11CF-893D-00A0C9054228}", + "lcid": 0, + "name": "Microsoft Scripting Runtime", + "path32": "C:\\Windows\\SysWOW64\\scrrun.dll", + "path64": "C:\\Windows\\System32\\scrrun.dll", + "symbolId": "Scripting", + "versionMajor": 1, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 950feb34350ee8c5e9659b4979d10f926cc9b95b Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 15:11:45 +0200 Subject: [PATCH 10/15] ArrayListLib: a contributed section of six pages (ArrayList, ListRange, Comparer, Enumerator, the interfaces), from its source alone and listed under Experimental Packages: version 1.4.0.134 does not compile in BETA 1005 (TB5001) --- WIP.ExamplesBuild.md | 2 +- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../Contributed/ArrayListLib/ArrayList.md | 750 ++++++++++++++++++ .../Contributed/ArrayListLib/Comparer.md | 81 ++ .../Contributed/ArrayListLib/Enumerator.md | 220 +++++ .../Contributed/ArrayListLib/Interfaces.md | 448 +++++++++++ .../Contributed/ArrayListLib/ListRange.md | 311 ++++++++ .../Contributed/ArrayListLib/index.md | 95 +++ docs/Reference/Experimental.md | 1 + docs/_book.yml | 7 + .../contributed/ArrayListLib/api.json | 450 +++++++++++ .../contributed-ArrayListLib/Settings | 74 ++ 13 files changed, 2440 insertions(+), 3 deletions(-) create mode 100644 docs/Reference/Contributed/ArrayListLib/ArrayList.md create mode 100644 docs/Reference/Contributed/ArrayListLib/Comparer.md create mode 100644 docs/Reference/Contributed/ArrayListLib/Enumerator.md create mode 100644 docs/Reference/Contributed/ArrayListLib/Interfaces.md create mode 100644 docs/Reference/Contributed/ArrayListLib/ListRange.md create mode 100644 docs/Reference/Contributed/ArrayListLib/index.md create mode 100644 indexer/snapshots/contributed/ArrayListLib/api.json create mode 100644 test/example-projects/contributed-ArrayListLib/Settings diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index d888534e4..d76af6cae 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -334,7 +334,7 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em until `contributed-WinReg` referenced WinDevLib (at the version WinReg embeds, 8.8.0.507) and the Scripting Runtime itself. The WinReg landing page tells the reader the same. - **An error inside an embedded package is the package's, and fails nothing** - (`splitPackageFaults`). BigNumberLib 0.0.0.0 and WinReg 0.0.0.0 do not compile on BETA + (`splitPackageFaults`). BigNumberLib 0.0.0.0, WinReg 0.0.0.0 and ArrayListLib 1.4.0.134 do not compile on BETA 1005: each passes a pointer to a `ByRef` structure parameter without `ByVal` (TB5001). The template-alone build finds those rows; the run prints them as a note naming the package rather than as a template FAIL, every other template row still fails, and the samples are diff --git a/WIP.md b/WIP.md index d5b8ac09c..4f71b67e4 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): BigNumberLib and WinReg do not compile on BETA 1005, xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-one **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index ea0d368e2..4cd73d368 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1187, + "pages": 1193, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/ArrayListLib/ArrayList.md b/docs/Reference/Contributed/ArrayListLib/ArrayList.md new file mode 100644 index 000000000..021db6591 --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/ArrayList.md @@ -0,0 +1,750 @@ +--- +title: ArrayList +parent: ArrayListLib Package +permalink: /tB/Packages/Contributed/ArrayListLib/ArrayList +has_toc: false +--- + +# ArrayList class +{: .no_toc } + +An **ArrayList** is a list of **Variant** values that grows as items are added. It has the members of the .NET class `System.Collections.ArrayList` which a COM client can call, and it is written in twinBASIC, so it needs no .NET. + +Items are numbered from 0. An item can be any value: a number, a string, an array or an object, and an object is stored as a reference. A list is created with **New**, as the class is not predeclared. [**Item**](#item) is the default member, so `list(0)` is the first item, and **For Each** goes through the items in order. + +```tb check_build +Dim list As New ArrayList +list.Add "pear" +list.Add "apple" +list.Add "fig" +list.Sort + +Dim v As Variant +For Each v In list + Debug.Print v +Next +Debug.Print list.Count +Debug.Print list.IndexOf("fig") +``` + +**ArrayList** implements [**IArrayList**](Interfaces#iarraylist), which extends [**IListRange**](Interfaces#ilistrange), [**IList**](Interfaces#ilist), [**ICollection**](Interfaces#icollection) and [**IEnumerable**](Interfaces#ienumerable) in turn, and also implements [**ICloneable**](Interfaces#icloneable). A variable can be declared with the class or with any of these interfaces; an interface offers a smaller set of members. The class has one member that no interface has: assigning to [**Count**](#count). + +* TOC +{:toc} + +## Items and equality + +[**Add**](#add), [**Insert**](#insert) and the assignment to [**Item**](#item) store a value as it is. An object is stored as a reference to the object, and an array is copied. + +[**Contains**](#contains), [**IndexOf**](#indexof), [**LastIndexOf**](#lastindexof) and [**Remove**](#remove) look for an item that matches the value they are given. Two values match when they have the same subtype and are equal: + +- A number matches a number of the same type. An **Integer** 1 does not match a **Long** 1, and neither matches a **Double** 1. +- A string matches a string with the same characters. The letter case counts. +- An object matches the same object, not an object that merely looks alike. +- **Null** matches **Null**. + +```tb check_build +Dim list As New ArrayList +list.Add 1 +list.Add "1" +Debug.Print list.Contains(1) +Debug.Print list.Contains(1&) +Debug.Print list.Contains("1") +Debug.Print list.IndexOf("1") +``` + +## Capacity and growth + +The list keeps its items in an internal array. A new list has room for 2 items, and [**Capacity**](#capacity) returns the number of items the array holds. When an item is added to a full list, the capacity becomes twice the old capacity plus 1: 2, 5, 11, 23 and so on. [**Clear**](#clear) returns the capacity to 2, and [**Clone**](#clone) gives its copy a capacity equal to the number of items. Assigning to **Capacity** sets it exactly. + +```tb check_build +Dim list As New ArrayList +Debug.Print list.Capacity +list.Add "a" +list.Add "b" +Debug.Print list.Capacity +list.Add "c" +Debug.Print list.Capacity +list.Capacity = 100 +Debug.Print list.Capacity +list.Clear +Debug.Print list.Capacity +``` + +## Enumerating + +**For Each** goes through the items from the first to the last, with a **Variant** as its variable. [**GetEnumerator**](#getenumerator) is the member that **For Each** calls; a program can call it with arguments to go through a part of the list, backwards, or in steps. [**Items**](#items), a hidden member, returns the items as an array. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c", "d", "e") + +Dim v As Variant +For Each v In list.GetEnumerator(1, 3) + Debug.Print v +Next +``` + +The enumerator reads the array of the list in place, and nothing checks that the list is unchanged while **For Each** runs. The documentation of the .NET class says that its enumerator is invalidated by a change to the list and raises an **InvalidOperationException**. A program should not add or remove items inside the loop. + +## Ranges + +[**GetRange**](#getrange) returns a [**ListRange**](ListRange), a view of a run of items, not a copy. A change made through the view changes the list. A change made to the list invalidates the view, and its next use raises an error. See [ListRange](ListRange). + +## Sorting + +[**Sort**](#sort) is a quicksort, so items that compare equal can change places. Without a comparer, items are compared with the `<` operator on **Variant** values. With a comparer, an object that implements [**IComparer**](Interfaces#icomparer), the sign of the number that **Compare** returns decides the order. The [**Comparer**](Comparer) class is a ready-made comparer. [**BinarySearch**](#binarysearch) needs the items in the order that the comparer, or the `<` operator, gives. + +## Errors + +| Number | Text | Raised by | +|--------|------|-----------| +| 9 | Subscript out of range | [**Item**](#item) and [**RemoveAt**](#removeat) with an index outside the list; [**RemoveRange**](#removerange), [**Reverse**](#reverse), [**GetRange**](#getrange), [**IndexOf**](#indexof), [**LastIndexOf**](#lastindexof) and [**SetRange**](#setrange) with a run that does not lie in the list; [**Count**](#count) with a negative value; [**CopyTo**](#copyto) with an index outside the array | +| 13 | Type mismatch | [**CopyTo**](#copyto), [**InsertRange**](#insertrange), [**AddRange**](#addrange) and [**SetRange**](#setrange) with something that is neither an array nor an object | +| 6 | Overflow | [**CopyTo**](#copyto) with an array that is too small | +| 438 | Object doesn't support this property or method | [**InsertRange**](#insertrange), [**AddRange**](#addrange) and [**SetRange**](#setrange) with an object that has no **Count** property | + +The numbers are raised with the standard text of the run time. + +## Differences from the .NET class + +- **Count.** The .NET property is read only. Here it can be assigned. +- **Sort.** Without a comparer, strings are compared by their characters, so an upper-case letter comes before a lower-case one. +- **Checks.** [**Insert**](#insert), [**InsertRange**](#insertrange), [**Sort**](#sort) and [**BinarySearch**](#binarysearch) do not check their index and count against the list, and the assignment to [**Capacity**](#capacity) does not check its value against **Count**. The documentation of the .NET class lists an **ArgumentOutOfRangeException** for an **Insert** index outside the list and for a **Capacity** below **Count**. +- **Changes during a loop.** See [Enumerating](#enumerating). +- **Parameters.** The count parameters are named *GetCount*, not *count*, and a named argument has to use that name. + +## Properties + +### Capacity +{: .no_toc } + +The number of items the list has room for. **Long**, read/write. + +Syntax: *object*.**Capacity** [ **=** *value* ] + +*value* +: A **Long**, at least [**Count**](#count). + +A new list has a capacity of 2. The list grows by itself when an item is added to a full list, so setting the capacity is needed only to avoid repeated growth when the final size is known. The value is used exactly. + +The assignment does not check *value* against [**Count**](#count). + +### Count +{: .no_toc } + +The number of items in the list. **Long**, read/write. + +Syntax: *object*.**Count** [ **=** *value* ] + +*value* +: A **Long**, 0 or more. A negative value raises error 9. + +Assigning a value larger than the current count makes the positions up to that count part of the list. They hold what the internal array holds there: **Empty** in an array that the assignment had to enlarge, or the items left by an earlier, smaller count. + +Assigning a smaller value ends the list at that count, but does not clear or release the items that follow: an object among them stays referenced until the position is written again. See [Known problems](./#known-problems). + +The assignment is a member of the class only; the interfaces have **Count** as a read-only property. The .NET class has no assignment. + +```tb check_build +Dim list As New ArrayList +list.Count = 3 +list(1) = "middle" +Debug.Print list.Count +Debug.Print IsEmpty(list(0)) +Debug.Print list(1) +``` + +### Item +{: .no_toc } + +The item at a position. **Variant**, read/write, and the default member. + +Syntax: [ **Set** ] *object*.**Item** ( *index* ) [ **=** *value* ] + +*index* +: A **Long** from 0 to **Count** - 1. A value outside the list raises error 9. + +*value* +: The new item. Use **Set** to store an object reference. + +Writing replaces the item. The previous value, and the reference if it was an object, is released. + +```tb check_build +Dim list As New ArrayList +list.Add "a" +list.Add "b" +list(0) = "first" +list.Item(1) = "second" +Debug.Print list(0) & " " & list(1) +Set list(1) = New Collection +Debug.Print TypeName(list(1)) +``` + +## Methods + +### Add +{: .no_toc } + +Adds an item at the end of the list and returns its index. + +Syntax: *object*.**Add** ( *value* ) **As Long** + +*value* +: *required* The item: any **Variant**, an object included. + +Returns the index of the new item, which is the value of [**Count**](#count) before the call. + +```tb check_build +Dim list As New ArrayList +Debug.Print list.Add("a") +Debug.Print list.Add("b") +Debug.Print list.Count +``` + +### AddRange +{: .no_toc } + +Adds the items of an array or of another collection at the end of the list. + +Syntax: *object*.**AddRange** ( *Target* ) + +*Target* +: *required* An array, or an object. See [**InsertRange**](#insertrange) for the kinds of *Target*. + +**AddRange** is the same as [**InsertRange**](#insertrange) at index **Count**. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b") +Dim more As New Collection +more.Add "c" +more.Add "d" +list.AddRange more +Debug.Print list.Count +Debug.Print list(3) +``` + +### BinarySearch +{: .no_toc } + +Searches a sorted run of items and returns the index of a value. + +Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* ] ) **As Long** + +*Index* +: *required* A **Long**: the index of the first item to search. + +*GetCount* +: *required* A **Long**: the number of items to search. + +*Value* +: *required* The value to look for. + +*Comparer* +: *optional* An object that implements [**IComparer**](Interfaces#icomparer). Without it, the items are compared with the `<` operator, and equality with `=`. + +Returns the index of a matching item. When there is none, the result is negative: **Not** *result* is the index of the first item that is larger than *Value*, or **Count** if there is none. When several items match, the result is the index of any one of them. + +The items from *Index* must be sorted in the order that *Comparer*, or the `<` operator, gives. The method does not check *Index* and *GetCount* against the list. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(10, 20, 30, 40) +Debug.Print list.BinarySearch(0, list.Count, 30) +Dim pos As Long = list.BinarySearch(0, list.Count, 25) +Debug.Print pos +Debug.Print Not pos +``` + +### Clear +{: .no_toc } + +Removes all items from the list. + +Syntax: *object*.**Clear** ( ) + +The objects among the items are released. The capacity returns to 2. + +### Clone +{: .no_toc } + +Returns a copy of the list. + +Syntax: *object*.**Clone** ( ) **As Variant** + +Returns a **Variant** that holds a new **ArrayList** with the same items. The copy is shallow: an object among the items is shared with the copy, not duplicated. **Clone** is the member of [**ICloneable**](Interfaces#icloneable). + +```tb check_build +Dim a As New ArrayList +a.AddRange Array(1, 2, 3) +Dim b As ArrayList +Set b = a.Clone() +a.Add 4 +Debug.Print a.Count +Debug.Print b.Count +``` + +### Contains +{: .no_toc } + +Returns whether the list holds an item that matches a value. + +Syntax: *object*.**Contains** ( *Value* ) **As Boolean** + +*Value* +: *required* The value to look for. See [Items and equality](#items-and-equality) for what matches. + +### CopyTo +{: .no_toc } + +Copies the items to an array, or over the items of another list. + +Syntax: *object*.**CopyTo** ( *Target*, *Index* ) + +*Target* +: *required* An array of any type, or an object that implements [**IListRange**](Interfaces#ilistrange), such as an **ArrayList**. The items are assigned one by one to the elements of an array of another type than **Variant**. + +*Index* +: *required* A **Long**: the index in *Target* of the first item copied. + +The array is filled in place; it keeps its size. When *Target* is a list, its items from *Index* are replaced and its count does not change, as for [**SetRange**](#setrange). + +Errors: error 9 when *Index* is below 0 or above the last index of an array; error 13 when *Target* is not an array and not a list; error 6, *Overflow*, when `UBound(Target) - Index` is less than **Count**. The last check is one element too strict: an array that has exactly **Count** elements from *Index* is refused. See [Known problems](./#known-problems). + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("x", "y", "z") +Dim target(0 To 4) As Variant +list.CopyTo target, 1 +Debug.Print target(1) & target(2) & target(3) +Debug.Print IsEmpty(target(0)) +``` + +### GetEnumerator +{: .no_toc } + +Returns the enumerator that **For Each** uses, optionally for a part of the list, in steps, or backwards. + +Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** + +*Index* +: *optional* A **Long**: the index of the first item. The default is 0. + +*GetCount* +: *optional* The number of items in the part to go through. The default is all the items from *Index*. + +*GetStep* +: *optional* A **Long**: how far the enumerator moves for each item. The default is 1. A negative value goes backwards, starting at the last item of the part. + +*ThisEnumerator* +: *optional* An [**IEnumerator**](Interfaces#ienumerator) variable that receives the enumerator, so that the loop can change its position, step or size. See [**Enumerator**](Enumerator). + +**For Each** calls this method without arguments. Calling it with arguments inside the loop header goes through part of the list. The method is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The result is an [**Enumerator**](Enumerator) object. + +The method does not check *GetCount* against the number of items. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c", "d", "e") + +Dim v As Variant +For Each v In list.GetEnumerator(, , -1) + Debug.Print v +Next +Debug.Print "--" +For Each v In list.GetEnumerator(0, , 2) + Debug.Print v +Next +``` + +### GetRange +{: .no_toc } + +Returns a view of a run of items. + +Syntax: *object*.**GetRange** ( *Index*, *GetCount* ) **As IListRange** + +*Index* +: *required* A **Long**: the index of the first item of the run. + +*GetCount* +: *required* A **Long**: the number of items in the run. + +Returns a [**ListRange**](ListRange), as an [**IListRange**](Interfaces#ilistrange). The view shares the items of the list, so changing an item in the view changes the list. Error 9 is raised when the run does not lie in the list. + +The type **IListRange** has no **Reverse**, **LastIndexOf**, **Capacity**, **Clone** or **Items**; the type **IArrayList** has them. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(10, 20, 30, 40, 50) +Dim r As IListRange = list.GetRange(1, 3) +r.Add 35 +Debug.Print r.Count +Debug.Print list.Count +Debug.Print list(4) +list.Add 60 +On Error Resume Next +Debug.Print r.Count +Debug.Print Err.Number +Debug.Print Err.Description +``` + +### IndexOf +{: .no_toc } + +Returns the index of the first item that matches a value, or -1. + +Syntax: *object*.**IndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** + +*Value* +: *required* The value to look for. See [Items and equality](#items-and-equality). + +*Index* +: *optional* A **Long**: the index where the search starts. The default is 0. + +*GetCount* +: *optional* The number of items to search. The default is all the items from *Index*. + +Returns -1 when no item matches. Error 9 is raised when *Index* is above **Count**, *GetCount* is negative, or the run does not fit in the list. + +### Insert +{: .no_toc } + +Inserts an item at a position and moves the items from there on up by one. + +Syntax: *object*.**Insert** ( *Index*, *Value* ) + +*Index* +: *required* A **Long** from 0 to **Count**. With **Count**, the item is added at the end. + +*Value* +: *required* The item. + +*Index* is not checked against **Count**. The method moves **Count** - *Index* items up to make room, so the number is negative for a value above **Count**. See [Known problems](./#known-problems). + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "c") +list.Insert 1, "b" +list.Insert 0, "start" +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +### InsertRange +{: .no_toc } + +Inserts the items of an array or of another collection at a position. + +Syntax: *object*.**InsertRange** ( *Index*, *Target* ) + +*Index* +: *required* A **Long** from 0 to **Count**. The items are inserted in front of the item that is at *Index* now. As for [**Insert**](#insert), the value is not checked. + +*Target* +: *required* The items to insert: an array or an object, as described below. + +*Target* is one of: + +- an array of any type. The items of an array of **Variant** are copied; the others are read one by one; +- an **ArrayList** or a [**ListRange**](ListRange), or any other object that implements [**IListRange**](Interfaces#ilistrange); +- any other object that has a **Count** property and that **For Each** can go through, such as a **Collection**. + +Error 13 is raised when *Target* is neither an array nor an object. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(1, 2) +list.InsertRange 1, Array("a", "b") +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +### LastIndexOf +{: .no_toc } + +Returns the index of the last item that matches a value, or -1. + +Syntax: *object*.**LastIndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** + +*Value* +: *required* The value to look for. + +*Index* +: *optional* The index where the search starts, going towards the start of the list. The default is the last item. + +*GetCount* +: *optional* The number of items to search, counted back from *Index*. The default is all the items from *Index* down to the first. + +Returns -1 when no item matches. Error 9 is raised when *Index* is **Count** or more, or *GetCount* is more than *Index* + 1. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "a", "c") +Debug.Print list.LastIndexOf("a") +Debug.Print list.LastIndexOf("a", 1) +Debug.Print list.LastIndexOf("a", 3, 1) +``` + +### Remove +{: .no_toc } + +Removes the first item that matches a value. + +Syntax: *object*.**Remove** ( *Value* ) + +*Value* +: *required* The value to remove. See [Items and equality](#items-and-equality). + +Nothing happens, and no error is raised, when no item matches. + +### RemoveAt +{: .no_toc } + +Removes the item at a position and moves the later items down by one. + +Syntax: *object*.**RemoveAt** ( *Index* ) + +*Index* +: *required* A **Long** from 0 to **Count** - 1. A value outside the list raises error 9. + +### RemoveRange +{: .no_toc } + +Removes a run of items. + +Syntax: *object*.**RemoveRange** ( *Index*, *GetCount* ) + +*Index* +: *required* A **Long**: the index of the first item to remove. + +*GetCount* +: *required* A **Long**: the number of items to remove. + +Error 9 is raised when *Index* or *GetCount* is negative, or the run does not lie in the list. + +### Reverse +{: .no_toc } + +Reverses the order of the items, or of a run of them. + +Syntax: *object*.**Reverse** ( [ *Index* ] [ , *GetCount* ] ) + +*Index* +: *optional* A **Long**: the index of the first item of the run. The default is 0. + +*GetCount* +: *optional* The number of items in the run. The default is all the items from *Index*. + +Error 9 is raised when the run does not lie in the list. + +The loop runs for *i* from 0 up to and including half of *GetCount*, rounded down, and swaps item *Index* + *i* with item *Index* + *GetCount* - 1 - *i*. For an even *GetCount* the last pass swaps the middle pair a second time, so those two items keep their order; for a *GetCount* of 0 the loop still runs once. See [Known problems](./#known-problems). + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c", "d", "e") +list.Reverse +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +### SetRange +{: .no_toc } + +Replaces the items from a position with the items of an array or of another collection. + +Syntax: *object*.**SetRange** ( *Index*, *Target* ) + +*Index* +: *required* A **Long**: the index of the first item to replace. + +*Target* +: *required* An array, or an object. The kinds are those of [**InsertRange**](#insertrange). + +The count of the list does not change. Error 9 is raised when the items of *Target* do not fit in the list from *Index* on, and error 13 when *Target* is neither an array nor an object. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(1, 2, 3, 4) +list.SetRange 1, Array("x", "y") +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +### Sort +{: .no_toc } + +Sorts the items, or a run of them. + +Syntax: *object*.**Sort** ( [ *Index* ] [ , *GetCount* ] [ , *Comparer* ] ) + +*Index* +: *optional* A **Long**: the index of the first item of the run. The default is 0. + +*GetCount* +: *optional* The number of items in the run. The default is all the items from *Index*. + +*Comparer* +: *optional* An object that implements [**IComparer**](Interfaces#icomparer). Without it, the items are compared with the `<` operator. + +The order is ascending. To sort in the other direction, give a comparer that reverses its result, or call [**Reverse**](#reverse) afterwards. The method does not check *Index* and *GetCount* against the list. + +Items of different kinds, such as numbers and strings, are put in the order that the `<` operator gives for **Variant** values. + +```tb check_build projname=arraylist-sort slot=file +Class LengthComparer + Implements IComparer + + Public Function Compare(X As Variant, Y As Variant) As Long Implements IComparer.Compare + Return Len(X) - Len(Y) + End Function +End Class +``` + +```tb check_build projname=arraylist-sort +Dim list As New ArrayList +list.AddRange Array("elderberry", "fig", "banana", "kiwi", "apple") +list.Sort 0, , New LengthComparer + +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +### ToArray +{: .no_toc } + +Returns the items as an array. + +Syntax: *object*.**ToArray** ( ) **As Variant()** + +Returns a new array of **Variant**, with the lower bound 0. The array is a copy; an object among the items is shared with it. A list with no items gives an array with no elements, with the upper bound -1. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c") +Dim a() As Variant +a = list.ToArray() +Debug.Print LBound(a) & " " & UBound(a) +list.Clear +a = list.ToArray() +Debug.Print UBound(a) +``` + +## Hidden members + +These members do not appear in IntelliSense. They can be called all the same. COM has no overloaded methods, so the members with a number in their name, from **BinarySearch_2** to **Sort_2**, stand for overloads of the .NET class; the README of the package gives this reason. The others are properties that a list has to offer for compatibility, and the two that serve the package itself, **Items** and **Version**. + +### BinarySearch_2 +{: .no_toc } + +Searches the whole list with the `<` operator. The same as **BinarySearch** (0, **Count**, *Value*). + +Syntax: *object*.**BinarySearch_2** ( *Value* ) **As Long** + +### BinarySearch_3 +{: .no_toc } + +Searches the whole list with a comparer. The same as **BinarySearch** (0, **Count**, *Value*, *Comparer*). + +Syntax: *object*.**BinarySearch_3** ( *Value*, *Comparer* ) **As Long** + +### IndexOf_2 +{: .no_toc } + +The same as **IndexOf** with all three arguments. + +Syntax: *object*.**IndexOf_2** ( *Value*, *Index*, *GetCount* ) **As Long** + +### IndexOf_3 +{: .no_toc } + +The same as **IndexOf** with the value only: searches the whole list. + +Syntax: *object*.**IndexOf_3** ( *Value* ) **As Long** + +### IsFixedSize +{: .no_toc } + +Always **False**. **Boolean**, read-only. A list can always grow. + +### IsReadOnly +{: .no_toc } + +Always **False**. **Boolean**, read-only. A list can always be changed. + +### IsSynchronized +{: .no_toc } + +Always **False**. **Boolean**, read-only. A list does not protect itself against use from several threads. + +### Items +{: .no_toc } + +Returns the items as an array that shares the memory of the list. + +Syntax: *object*.**Items** ( ) **As Variant()** + +The array is not a copy, and its elements are not counted as references to the objects they hold. It is meant for one use only, as the subject of **For Each**: + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(3, 4, 5) +Dim v As Variant +For Each v In list.Items + Debug.Print v +Next +``` + +The comment in the source warns against assigning the result to a variable: the variable would release the objects when it goes out of scope, and the list would release them again. The array descriptor is kept in a static variable of the method. + +### LastIndexOf_2 +{: .no_toc } + +The same as **LastIndexOf** with the value and the index. + +Syntax: *object*.**LastIndexOf_2** ( *Value*, *Index* ) **As Long** + +Searches from *Index* back to the first item. + +### LastIndexOf_3 +{: .no_toc } + +The same as **LastIndexOf** with all three arguments. + +Syntax: *object*.**LastIndexOf_3** ( *Value*, *Index*, *GetCount* ) **As Long** + +### Sort_2 +{: .no_toc } + +Sorts the whole list with a comparer. The same as **Sort** 0, , *Comparer*. + +Syntax: *object*.**Sort_2** ( *Comparer* ) + +### SyncRoot +{: .no_toc } + +Always **Nothing**. **Variant**, read-only. The .NET class returns an object here. + +### Version +{: .no_toc } + +A number that changes each time the list changes. **Long**, read-only. A [**ListRange**](ListRange) compares it with the value it saw when it was created, to find out whether the list has changed since. It changes for the assignment to **Item** and **Count**, and for **Add**, **AddRange**, **Clear**, **Insert**, **InsertRange**, **Remove**, **RemoveAt**, **RemoveRange**, **Reverse**, **SetRange** and **Sort**. + +## See Also + +- [ListRange](ListRange) class -- the view that **GetRange** returns +- [Enumerator](Enumerator) class -- the enumerator of **For Each** +- [Comparer](Comparer) class -- a comparer for **Sort** and **BinarySearch** +- [IArrayList](Interfaces#iarraylist) interface -- the members of the list as an interface +- [ArrayListLib package](./) -- overview diff --git a/docs/Reference/Contributed/ArrayListLib/Comparer.md b/docs/Reference/Contributed/ArrayListLib/Comparer.md new file mode 100644 index 000000000..3177d9fde --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/Comparer.md @@ -0,0 +1,81 @@ +--- +title: Comparer +parent: ArrayListLib Package +permalink: /tB/Packages/Contributed/ArrayListLib/Comparer +has_toc: false +--- + +# Comparer class +{: .no_toc } + +A **Comparer** compares two values and tells which of them is smaller. It implements [**IComparer**](Interfaces#icomparer), so it can be given to [**Sort**](ArrayList#sort) and [**BinarySearch**](ArrayList#binarysearch) of an [**ArrayList**](ArrayList) in place of a comparer that the program writes. + +The class is predeclared: `Comparer.Compare(a, b)` can be called without creating an object, and `Comparer` is itself a value that can be passed as a comparer. + +```tb check_build +Debug.Print Comparer.Compare(1, 2) +Debug.Print Comparer.Compare(5, 5) +Debug.Print Comparer.Compare("pear", "apple") +Debug.Print Comparer.Compare(#1/2/2026#, #1/1/2026#) +``` + +[**Sort**](ArrayList#sort) without a comparer compares items with the `<` operator, which needs no help from a **Comparer** for numbers and strings. A **Comparer** is useful for the other kinds of value (an array or an object), and as the base of a comparer that changes the result, such as one that sorts in descending order. + +## How two values are compared + +**Compare** takes the first rule that applies: + +1. It subtracts the second value from the first and takes the sign of the difference ([**Sgn**](../../../Modules/Math/Sgn)). This is the comparison of numbers and of dates. +2. When the subtraction raises an error, two values that are not objects are compared with the `<` and `>` operators. The comparison of strings is by their characters, so an upper-case letter is smaller than a lower-case one. +3. When one of the two values is an array, the first element of the array stands for it: **Compare** compares `X(0)` with `Y(0)`, or with the other value when it is not an array. +4. When the first value is an object, the result is that of its **CompareTo** method called with the second value. The result is returned as the object gives it, not reduced to -1, 0 or 1. +5. When the comparison of rule 2, 3 or 4 raises an error, the result is 0: the two values count as equal. This is the result for an object with no **CompareTo**, and for two values that cannot be compared with each other. + +## Writing a comparer + +A comparer is any object that implements [**IComparer**](Interfaces#icomparer). Its **Compare** returns a negative number when the first value goes before the second, zero when they are equal and a positive number when the first goes after. A comparer that gives the result of a **Comparer** the other way round sorts in descending order: + +```tb check_build projname=comparer-descending slot=file +Class DescendingComparer + Implements IComparer + + Public Function Compare(X As Variant, Y As Variant) As Long Implements IComparer.Compare + Return Comparer.Compare(Y, X) + End Function +End Class +``` + +```tb check_build projname=comparer-descending +Dim list As New ArrayList +list.AddRange Array(3, 1, 2) +list.Sort 0, , New DescendingComparer + +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +## Methods + +### Compare +{: .no_toc } + +Compares two values. + +Syntax: *object*.**Compare** ( *X*, *Y* ) **As Long** + +*X* +: *required* The first value. + +*Y* +: *required* The second value. + +Returns a negative number when *X* is smaller than *Y*, 0 when they are equal and a positive number when *X* is greater. For values that are not objects the result is -1, 0 or 1. See [How two values are compared](#how-two-values-are-compared) for the rules. + +## See Also + +- [IComparer](Interfaces#icomparer) interface -- the interface that **Comparer** implements +- [Sort](ArrayList#sort) method of **ArrayList** +- [BinarySearch](ArrayList#binarysearch) method of **ArrayList** +- [ArrayListLib package](./) -- overview diff --git a/docs/Reference/Contributed/ArrayListLib/Enumerator.md b/docs/Reference/Contributed/ArrayListLib/Enumerator.md new file mode 100644 index 000000000..dffb0e728 --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/Enumerator.md @@ -0,0 +1,220 @@ +--- +title: Enumerator +parent: ArrayListLib Package +permalink: /tB/Packages/Contributed/ArrayListLib/Enumerator +has_toc: false +--- + +# Enumerator class +{: .no_toc } + +An **Enumerator** goes through a run of the items of an array of **Variant** for [**For Each**](../../../Core/For-Each-Next), starting at any item, in steps of any size and in either direction. [**GetEnumerator**](ArrayList#getenumerator) of an [**ArrayList**](ArrayList) returns one over the items of the list. The class is public, so a program can also use it over any array of **Variant**. + +An **Enumerator** implements [**IEnumerator**](Interfaces#ienumerator), which extends [**IEnumVARIANT**](Interfaces#ienumvariant), the interface that **For Each** calls. It reads the memory of the array directly, without a copy. + +```tb check_build +Dim a() As Variant +a = Array("x", "y", "z") + +Dim v As Variant +For Each v In New Enumerator(a, 0, 3, -1) + Debug.Print v +Next +``` + +* TOC +{:toc} + +## Position, step and size + +The enumerator covers a part of the array: *GetCount* items from *Index*. Its position, [**CurrentIndex**](#currentindex), counts from the first item of that part, which is 0 whatever *Index* is. [**CurrentStep**](#currentstep) is how far the position moves for each item; a negative step goes backwards, and the position then starts at the last item of the part. The loop ends when the position leaves the part, which is [**CurrentSize**](#currentsize) items long. + +The properties can be changed inside the loop, through the **IEnumerator** variable that **GetEnumerator** or the constructor fills in. A change takes effect at the next item. Assigning **CurrentIndex** makes the next item the one at that index; assigning **CurrentStep** moves the next item by the new step from the present position. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c", "d", "e") + +Dim e As IEnumerator +Dim v As Variant +For Each v In list.GetEnumerator(, , , e) + Debug.Print v + If v = "b" Then e.CurrentIndex = 3 +Next +``` + +**Next** adds the step to the position before it reads an item, except for the first item, where the step to add is 0. **Reset** sets the position but not that step. After an item has been returned, the step is the enumerator's own, so the first **Next** after a **Reset** moves past the first item. See [Known problems](./#known-problems). + +Nothing checks that the array is unchanged during the loop. See [Enumerating](ArrayList#enumerating). + +## Constructors + +### New +{: .no_toc } + +Creates an enumerator. + +Syntax: **New Enumerator** [ ( *TargetArray* [ , *Index* [ , *GetCount* [ , *GetStep* [ , *ThisEnumerator* ] ] ] ] ) ] + +*TargetArray* +: *required* An array of **Variant**, passed by reference. The enumerator reads its memory in place. + +*Index* +: *optional* A **Long**: the index in the array of the first item of the part. The default is 0. + +*GetCount* +: *optional* The number of items in the part. The default is all the items from *Index* to the end of the array. + +*GetStep* +: *optional* A **Long**: how far the position moves for each item. The default is 1. A negative value goes backwards. + +*ThisEnumerator* +: *optional* An [**IEnumerator**](Interfaces#ienumerator) variable that receives the new enumerator. + +Without arguments, the enumerator has no array; call [**Bind**](#bind) to give it one. With arguments, it is bound by **Bind** at once. + +## Properties + +### CurrentIndex +{: .no_toc } + +The position of the item that was last returned, counted from the first item of the part. **Long**, read/write. + +Syntax: *object*.**CurrentIndex** [ **=** *value* ] + +*value* +: A **Long**. The next item is the one at this index. A value outside the part ends the loop. + +Before the first item is returned, the position is 0, or the last item of the part for a negative step. + +### CurrentSize +{: .no_toc } + +The number of items in the part that is enumerated. **Long**, read/write. + +Syntax: *object*.**CurrentSize** [ **=** *value* ] + +*value* +: A **Long**. Use it with care, and only to shorten the part of a larger array: the value is not checked, so one larger than the array reads past its items. + +### CurrentStep +{: .no_toc } + +How far the position moves for each item. **Long**, read/write. + +Syntax: *object*.**CurrentStep** [ **=** *value* ] + +*value* +: A **Long**. A negative value goes backwards. The next item is the one at the present position plus *value*. + +A step of 0 returns the same item for ever, so a loop with it does not end. + +## Methods + +### Bind +{: .no_toc } + +Gives the enumerator a part of an array to go through. + +Syntax: *object*.**Bind** ( *TargetArray*, *Index*, *GetCount*, *GetStep* ) + +*TargetArray* +: *required* An array of **Variant**, passed by reference. + +*Index* +: *required* A **Long**: the index in the array of the first item of the part. + +*GetCount* +: *required* A **Long**: the number of items in the part. + +*GetStep* +: *required* A **Long**: how far the position moves for each item. + +The enumerator reads the memory of the array in place, so the array must not be resized or erased while the enumerator is in use. For a negative *GetStep*, the position is set to the last item of the part. For a positive one, **Bind** leaves the position and the step that **Next** adds as they are. + +### GetEnumerator +{: .no_toc } + +Returns the enumerator itself. + +Syntax: *object*.**GetEnumerator** ( ) **As stdole.IUnknown** + +The method is marked [**[Enumerator]**](../../../Core/Attributes#enumerator), so **For Each** can be used on an **Enumerator** object, as in the first example. + +## Members of IEnumerator + +The class implements these members privately. They are reached through an [**IEnumerator**](Interfaces#ienumerator) or [**IEnumVARIANT**](Interfaces#ienumvariant) variable, such as the one that *ThisEnumerator* fills in. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array(10, 20) + +Dim e As IEnumerator +list.GetEnumerator 0, , 1, e + +Dim v As Variant +Dim fetched As Long +e.Next 1, v, fetched +Debug.Print v & " " & fetched +e.Next 1, v, fetched +Debug.Print v & " " & fetched +e.Next 1, v, fetched +Debug.Print fetched +``` + +### Clone +{: .no_toc } + +Creates another enumerator over the same part. + +Syntax: *object*.**Clone** ( *ppenum* ) + +*ppenum* +: *required* An **IEnumVARIANT** variable that receives the new enumerator. + +The new enumerator has the same items and step and starts at the beginning of the part, not at the position of the original. + +### Next +{: .no_toc } + +Returns the next item. + +Syntax: *object*.**Next** ( *celt*, *rgvar*, *pceltFetched* ) + +*celt* +: *required* A **Long**, which has to be 1. Any other value fails with the error code `E_INVALIDARG` (&H80070057). + +*rgvar* +: *required* A **Variant** that receives the item. An object is stored as a reference. + +*pceltFetched* +: *required* A **Long** that receives 1 when an item was returned and 0 when the part is finished. + +Returns `S_OK` when it returned an item. At the end of the part it sets *pceltFetched* to 0 and returns `S_FALSE`, which **For Each** reads as the end of the loop. + +### Reset +{: .no_toc } + +Goes back to the start of the part. + +Syntax: *object*.**Reset** ( ) + +The position becomes 0, or the last item of the part for a negative step. **For Each** calls **Reset** before the first **Next**. The step that **Next** adds is not cleared: see [Position, step and size](#position-step-and-size). + +### Skip +{: .no_toc } + +Moves past items without returning them. + +Syntax: *object*.**Skip** ( *celt* ) + +*celt* +: *required* A **Long**: the number of items to move past. The position moves by *celt* times the step. A position above the size of the part becomes the size plus 1, and one below 0 becomes -1. + +## See Also + +- [ArrayList](ArrayList) class -- its **GetEnumerator** returns an **Enumerator** +- [IEnumerator](Interfaces#ienumerator) interface -- the position, step and size properties +- [IEnumVARIANT](Interfaces#ienumvariant) interface -- the methods that **For Each** calls +- [Custom Enumerators](../../../../Features/Language/Custom-Enumerators) -- how a class provides the enumerator that **For Each** uses +- [ArrayListLib package](./) -- overview diff --git a/docs/Reference/Contributed/ArrayListLib/Interfaces.md b/docs/Reference/Contributed/ArrayListLib/Interfaces.md new file mode 100644 index 000000000..76519d8a8 --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/Interfaces.md @@ -0,0 +1,448 @@ +--- +title: ArrayListLib Interfaces +parent: ArrayListLib Package +permalink: /tB/Packages/Contributed/ArrayListLib/Interfaces +has_toc: false +--- + +# ArrayListLib interfaces +{: .no_toc } + +The package declares nine interfaces. Six have the name and the role of an interface of .NET: **ICloneable**, **ICollection**, **IComparer**, **IEnumerable**, **IEnumerator** and **IList**. **IListRange** and **IArrayList** are the package's own, and **IEnumVARIANT** is the interface of COM that **For Each** uses. Each one adds members to the one it extends: + +``` +IEnumerable + ICollection + IList + IListRange + IArrayList (also implements ICloneable) +IEnumVARIANT + IEnumerator +IComparer +``` + +[**ArrayList**](ArrayList) and [**ListRange**](ListRange) implement **IArrayList**, so a variable of any of these types can hold a list. A function that only reads items can take an **IList**; a function that sorts or takes parts can take an **IListRange**. [**Enumerator**](Enumerator) implements **IEnumerator**, and [**Comparer**](Comparer) implements **IComparer**. + +```tb check_build +Dim items As IList = New ArrayList +items.Add "a" +items.Add "b" +Debug.Print items.Count +Debug.Print items.Item(1) +Dim v As Variant +For Each v In items + Debug.Print v +Next +``` + +The members of the interfaces that a list implements are described once, on the page of [ArrayList](ArrayList); the entries below give the syntax and point there. + +* TOC +{:toc} + +## IEnumerable + +The base of the list interfaces: an object that **For Each** can go through. Interface identifier `F078EB73-3FA5-47F4-8003-520AEA1CE315`. + +### GetEnumerator +{: #ienumerable-getenumerator .no_toc } + +Returns the enumerator that **For Each** uses. + +Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** + +The member is marked [**[Enumerator]**](../../../Core/Attributes#enumerator). The parameters are those of [**GetEnumerator**](ArrayList#getenumerator) of **ArrayList**; here *ThisEnumerator* is declared **As Any**, an untyped reference. + +## ICollection + +Extends **IEnumerable**. The base interface of all collections. Interface identifier `B2E5C8F5-2847-450B-873B-F4BE7621F627`. + +### Count +{: #icollection-count .no_toc } + +The number of items. **Long**, read-only. + +Syntax: *object*.**Count** + +See [**Count**](ArrayList#count). + +### CopyTo +{: #icollection-copyto .no_toc } + +Copies the items to an array or over the items of another list. + +Syntax: *object*.**CopyTo** ( *Target*, *Index* ) + +See [**CopyTo**](ArrayList#copyto). + +### IsSynchronized +{: #icollection-issynchronized .no_toc } + +Whether access to the collection is safe from several threads. **Boolean**, read-only. Always **False** in the package. + +Syntax: *object*.**IsSynchronized** + +### SyncRoot +{: #icollection-syncroot .no_toc } + +An object to lock for access from several threads. **Variant**, read-only. Always **Nothing** in the package. + +Syntax: *object*.**SyncRoot** + +## IList + +Extends **ICollection**. The base interface of all lists. Interface identifier `BC694131-C923-4813-9D34-C79C5E2F6980`. + +### Add +{: #ilist-add .no_toc } + +Adds an item at the end of the list and returns its index. + +Syntax: *object*.**Add** ( *Value* ) **As Long** + +See [**Add**](ArrayList#add). + +### Clear +{: #ilist-clear .no_toc } + +Removes all items. + +Syntax: *object*.**Clear** ( ) + +See [**Clear**](ArrayList#clear). + +### Contains +{: #ilist-contains .no_toc } + +Returns whether the list holds an item that matches a value. + +Syntax: *object*.**Contains** ( *Value* ) **As Boolean** + +See [**Contains**](ArrayList#contains). + +### IndexOf +{: #ilist-indexof .no_toc } + +Returns the index of the first item that matches a value, or -1. + +Syntax: *object*.**IndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** + +See [**IndexOf**](ArrayList#indexof). + +### Insert +{: #ilist-insert .no_toc } + +Inserts an item at a position. + +Syntax: *object*.**Insert** ( *Index*, *Value* ) + +See [**Insert**](ArrayList#insert). + +### IsFixedSize +{: #ilist-isfixedsize .no_toc } + +Whether the list has a fixed size. **Boolean**, read-only. Always **False** in the package. + +Syntax: *object*.**IsFixedSize** + +### IsReadOnly +{: #ilist-isreadonly .no_toc } + +Whether the list cannot be changed. **Boolean**, read-only. Always **False** in the package. + +Syntax: *object*.**IsReadOnly** + +### Item +{: #ilist-item .no_toc } + +The item at a position. **Variant**, read/write. + +Syntax: [ **Set** ] *object*.**Item** ( *Index* ) [ **=** *Value* ] + +See [**Item**](ArrayList#item). The interface does not mark **Item** as the default member; the classes do. + +### Remove +{: #ilist-remove .no_toc } + +Removes the first item that matches a value. + +Syntax: *object*.**Remove** ( *Value* ) + +See [**Remove**](ArrayList#remove). + +### RemoveAt +{: #ilist-removeat .no_toc } + +Removes the item at a position. + +Syntax: *object*.**RemoveAt** ( *Index* ) + +See [**RemoveAt**](ArrayList#removeat). + +## IListRange + +Extends **IList**. A list with operations on runs of items. It is the type that [**GetRange**](ArrayList#getrange) returns. Interface identifier `480A22F9-91B0-483A-8A33-11634FCF644A`. + +### AddRange +{: #ilistrange-addrange .no_toc } + +Adds the items of an array or collection at the end. + +Syntax: *object*.**AddRange** ( *Target* ) + +See [**AddRange**](ArrayList#addrange). + +### BinarySearch +{: #ilistrange-binarysearch .no_toc } + +Searches a sorted run of items. + +Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* ] ) **As Long** + +See [**BinarySearch**](ArrayList#binarysearch). + +### GetAddressOfItemAt +{: #ilistrange-getaddressofitemat .no_toc } + +Returns the address in memory of an item. Hidden. + +Syntax: *object*.**GetAddressOfItemAt** ( *Index* ) **As LongPtr** + +The package uses it to copy runs of items between lists. The address stays valid only until the list changes, and a program has no use for it. + +### GetRange +{: #ilistrange-getrange .no_toc } + +Returns a view of a run of items. + +Syntax: *object*.**GetRange** ( *Index*, *GetCount* ) **As IListRange** + +See [**GetRange**](ArrayList#getrange). + +### InsertRange +{: #ilistrange-insertrange .no_toc } + +Inserts the items of an array or collection at a position. + +Syntax: *object*.**InsertRange** ( *Index*, *Target* ) + +See [**InsertRange**](ArrayList#insertrange). + +### RemoveRange +{: #ilistrange-removerange .no_toc } + +Removes a run of items. + +Syntax: *object*.**RemoveRange** ( *Index*, *GetCount* ) + +See [**RemoveRange**](ArrayList#removerange). + +### SetRange +{: #ilistrange-setrange .no_toc } + +Replaces items with the items of an array or collection. + +Syntax: *object*.**SetRange** ( *Index*, *Target* ) + +See [**SetRange**](ArrayList#setrange). + +### Sort +{: #ilistrange-sort .no_toc } + +Sorts the items, or a run of them. + +Syntax: *object*.**Sort** ( [ *Index* ] [ , *GetCount* ] [ , *Comparer* ] ) + +See [**Sort**](ArrayList#sort). + +### ToArray +{: #ilistrange-toarray .no_toc } + +Returns the items as an array. + +Syntax: *object*.**ToArray** ( ) **As Variant()** + +See [**ToArray**](ArrayList#toarray). + +## IArrayList + +Extends **IListRange** and implements **ICloneable**. The interface of a whole list. Interface identifier `1043D433-27BB-40F7-9255-50F4724FAB94`. + +### Capacity +{: #iarraylist-capacity .no_toc } + +The number of items the list has room for. **Long**, read/write. + +Syntax: *object*.**Capacity** [ **=** *value* ] + +See [**Capacity**](ArrayList#capacity). + +### Clone +{: #iarraylist-clone .no_toc } + +Returns a copy of the list. + +Syntax: *object*.**Clone** ( ) **As Variant** + +The member of [**ICloneable**](#icloneable) again; see [**Clone**](ArrayList#clone). + +### CloneTo +{: #iarraylist-cloneto .no_toc } + +Fills the list with copies of items of another list. Hidden. + +Syntax: *object*.**CloneTo** ( *Target*, *Source*, *Index*, *GetCount* ) + +[**Clone**](ArrayList#clone) calls it on the new list. The list it is called on takes copies of *GetCount* items of *Source*, from *Index*. *Target* is not used. + +### Items +{: #iarraylist-items .no_toc } + +Returns the items as an array that shares the memory of the list. Hidden. + +Syntax: *object*.**Items** ( ) **As Variant()** + +See [**Items**](ArrayList#items). + +### LastIndexOf +{: #iarraylist-lastindexof .no_toc } + +Returns the index of the last item that matches a value, or -1. + +Syntax: *object*.**LastIndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** + +See [**LastIndexOf**](ArrayList#lastindexof). + +### Reverse +{: #iarraylist-reverse .no_toc } + +Reverses the order of the items, or of a run of them. + +Syntax: *object*.**Reverse** ( [ *Index* ] [ , *GetCount* ] ) + +See [**Reverse**](ArrayList#reverse). + +### Version +{: #iarraylist-version .no_toc } + +A number that changes each time the list changes. **Long**, read-only. Hidden. + +Syntax: *object*.**Version** + +See [**Version**](ArrayList#version). + +## ICloneable + +An object that can make a copy of itself. **IArrayList** implements it. Interface identifier `8ABD76D4-42F5-4920-927A-BC766E00CE46`. + +### Clone +{: #icloneable-clone .no_toc } + +Returns a copy of the object. Whether the copy is shallow or deep is up to the class. + +Syntax: *object*.**Clone** ( ) **As Variant** + +## IEnumVARIANT + +The interface that **For Each** calls to go through the items of an object, one at a time. The package declares it as a public interface, with **Long** counts, because the declaration in **stdole** cannot be implemented in twinBASIC. Interface identifier `00020404-0000-0000-C000-000000000046`. See [IEnumVARIANT](../../../../Reference/COM-Interfaces/IEnumVARIANT) for the contract of the four methods, and [Enumerator](Enumerator) for the class that implements them. + +### Clone +{: #ienumvariant-clone .no_toc } + +Creates another enumerator over the same items. + +Syntax: *object*.**Clone** ( *ppenum* ) + +*ppenum* +: *required* An **IEnumVARIANT** variable that receives the new enumerator. + +### Next +{: #ienumvariant-next .no_toc } + +Returns the next item. + +Syntax: *object*.**Next** ( *celt*, *rgvar*, *pceltFetched* ) + +*celt* +: *required* A **Long**: the number of items asked for. + +*rgvar* +: *required* A **Variant** that receives the item. + +*pceltFetched* +: *required* A **Long** that receives the number of items returned. + +### Reset +{: #ienumvariant-reset .no_toc } + +Goes back to the first item. + +Syntax: *object*.**Reset** ( ) + +### Skip +{: #ienumvariant-skip .no_toc } + +Moves past items without returning them. + +Syntax: *object*.**Skip** ( *celt* ) + +*celt* +: *required* A **Long**: the number of items to move past. + +## IEnumerator + +Extends **IEnumVARIANT**. An enumerator whose position, step and size can be read and changed. Interface identifier `8E42D737-24C2-4D3F-8903-4B30E9383C69`. It is the type of the *ThisEnumerator* argument of **GetEnumerator**. + +### CurrentIndex +{: #ienumerator-currentindex .no_toc } + +The position of the item last returned. **Long**, read/write. + +Syntax: *object*.**CurrentIndex** [ **=** *value* ] + +See [**CurrentIndex**](Enumerator#currentindex). + +### CurrentSize +{: #ienumerator-currentsize .no_toc } + +The number of items in the part that is enumerated. **Long**, read/write. + +Syntax: *object*.**CurrentSize** [ **=** *value* ] + +See [**CurrentSize**](Enumerator#currentsize). + +### CurrentStep +{: #ienumerator-currentstep .no_toc } + +How far the position moves for each item. **Long**, read/write. + +Syntax: *object*.**CurrentStep** [ **=** *value* ] + +See [**CurrentStep**](Enumerator#currentstep). + +## IComparer + +An object that compares two values. [**Sort**](ArrayList#sort) and [**BinarySearch**](ArrayList#binarysearch) take one. A class implements it with `Implements IComparer`, as the examples on the page of [**Comparer**](Comparer) show. Interface identifier `D60ABA2A-1176-4515-9B4C-B0312BA5FE7F`. + +### Compare +{: #icomparer-compare .no_toc } + +Compares two values. + +Syntax: *object*.**Compare** ( *X*, *Y* ) **As Long** + +*X* +: *required* The first value. + +*Y* +: *required* The second value. + +Returns a negative number when *X* goes before *Y*, 0 when they are equal and a positive number when *X* goes after *Y*. Only the sign of the number matters to the list. + +## See Also + +- [ArrayList](ArrayList) class -- implements **IArrayList** +- [ListRange](ListRange) class -- implements **IArrayList** over a run of a list +- [Enumerator](Enumerator) class -- implements **IEnumerator** +- [Comparer](Comparer) class -- implements **IComparer** +- [ArrayListLib package](./) -- overview diff --git a/docs/Reference/Contributed/ArrayListLib/ListRange.md b/docs/Reference/Contributed/ArrayListLib/ListRange.md new file mode 100644 index 000000000..5dba13c0f --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/ListRange.md @@ -0,0 +1,311 @@ +--- +title: ListRange +parent: ArrayListLib Package +permalink: /tB/Packages/Contributed/ArrayListLib/ListRange +has_toc: false +--- + +# ListRange class +{: .no_toc } + +A **ListRange** is a view of a run of items in an [**ArrayList**](ArrayList). It shows those items under indexes that start at 0, and it does not copy them: a change made through the view changes the list. [**GetRange**](ArrayList#getrange) of an **ArrayList** (or of another **ListRange**) returns one. + +A range is made by **GetRange**; the class is marked as not creatable. **GetRange** returns the range as an [**IListRange**](Interfaces#ilistrange), which has no **Reverse**, **LastIndexOf**, **Capacity**, **Clone** or **Items**. A range implements [**IArrayList**](Interfaces#iarraylist) and the class **ArrayList** as well; the type **IArrayList** has all the members of a range. + +```tb check_build +Dim list As New ArrayList +list.AddRange Array("a", "b", "c", "d", "e") + +Dim r As IArrayList = list.GetRange(1, 3) +r.Reverse +Debug.Print r.Item(0) & r.Item(1) & r.Item(2) +r.RemoveAt 0 +Debug.Print list.Count + +Dim v As Variant +For Each v In list + Debug.Print v +Next +``` + +* TOC +{:toc} + +## When a range stops being valid + +A range keeps the version number that the list had when the range was made. Each call of the range first compares that number with the list's. A change that the range makes itself keeps the numbers equal, with the two exceptions below. Any other change to the list --- [**Add**](ArrayList#add), [**Insert**](ArrayList#insert), [**RemoveAt**](ArrayList#removeat), [**Sort**](ArrayList#sort) or an assignment to an item, for example --- makes them differ, and the next call of the range raises error -2146233079 (`vbObjectError + 988425`) with the text *This ArrayList range is no longer valid.* Make a new range with **GetRange** after such a change. + +The number is the value that the documentation of the .NET class **InvalidOperationException** gives for its HRESULT, `COR_E_INVALIDOPERATION` (`&H80131509`). + +Two changes made through the range itself make it invalid, because they count differently in the range and in the list. An assignment to an item (`r.Item(0) = x`, or with **Set**) adds 1 to the range's version number and nothing to the list's. [**SetRange**](#setrange) of a range, with a list or a range as its argument, adds 1 to the range's number and 2 to the list's. See [Known problems](./#known-problems). + +A change made through one range adds to the version number of the list, so every other range of the same list is invalid afterwards. + +## Properties + +### Capacity +{: .no_toc } + +The capacity of the list. **Long**, read/write. + +Syntax: *object*.**Capacity** [ **=** *value* ] + +Returns the [**Capacity**](ArrayList#capacity) of the list. An assignment is accepted and has no effect. + +### Count +{: .no_toc } + +The number of items in the range. **Long**, read-only. + +Syntax: *object*.**Count** + +Raises the error of an invalid range when the list has changed. A range cannot be resized by assigning to **Count**. + +### Item +{: .no_toc } + +The item at a position in the range. **Variant**, read/write, and the default member. + +Syntax: [ **Set** ] *object*.**Item** ( *index* ) [ **=** *value* ] + +*index* +: A **Long** from 0 to **Count** - 1 of the range. A value outside the range raises error 9. + +See [**Item**](ArrayList#item) of **ArrayList**. An assignment makes the range invalid, as described above. + +## Methods + +### Add +{: .no_toc } + +Adds an item at the end of the range and returns its index in the range. + +Syntax: *object*.**Add** ( *value* ) **As Long** + +The item is inserted in the list just after the last item of the range, and the range grows by one. See [**Add**](ArrayList#add). + +### AddRange +{: .no_toc } + +Adds the items of an array or of another collection at the end of the range. + +Syntax: *object*.**AddRange** ( *Target* ) + +The items are inserted in the list after the last item of the range, and the range grows by their number. See [**AddRange**](ArrayList#addrange). + +### BinarySearch +{: .no_toc } + +Searches a sorted run of the range and returns the index of a value, counted from the start of the range. + +Syntax: *object*.**BinarySearch** ( *Index*, *GetCount*, *Value* [ , *Comparer* ] ) **As Long** + +As [**BinarySearch**](ArrayList#binarysearch) of **ArrayList**, with *Index* counted from the start of the range and the result too, including the complement of the index that is returned when there is no match. Error 9 is raised when the run does not lie in the range. + +### Clear +{: .no_toc } + +Removes the items of the range from the list. + +Syntax: *object*.**Clear** ( ) + +The items are removed from the list, and the range is left with no items. The range stays valid. + +### Clone +{: .no_toc } + +Returns a copy of the items of the range. + +Syntax: *object*.**Clone** ( ) **As Variant** + +Returns a **Variant** that holds a new **ArrayList** with the items of the range, as [**Clone**](ArrayList#clone) of an **ArrayList** does. +### Contains +{: .no_toc } + +Returns whether the range holds an item that matches a value. + +Syntax: *object*.**Contains** ( *Value* ) **As Boolean** + +As [**Contains**](ArrayList#contains), over the items of the range. + +### CopyTo +{: .no_toc } + +Copies the items of the range to an array, or over the items of another list. + +Syntax: *object*.**CopyTo** ( *Target*, *Index* ) + +As [**CopyTo**](ArrayList#copyto), for the items of the range, including the check that refuses an array with exactly the room needed. + +### GetEnumerator +{: .no_toc } + +Returns the enumerator that **For Each** uses over the items of the range. + +Syntax: *object*.**GetEnumerator** ( [ *Index* ] [ , *GetCount* ] [ , *GetStep* ] [ , *ThisEnumerator* ] ) **As stdole.IUnknown** + +As [**GetEnumerator**](ArrayList#getenumerator), with *Index* counted from the start of the range. + +### GetRange +{: .no_toc } + +Returns a view of a run of items of the range. + +Syntax: *object*.**GetRange** ( *Index*, *GetCount* ) **As IListRange** + +*Index* is counted from the start of the range. Error 9 is raised when the run does not lie in the range. A range of a range is a view of the same items of the list; a change through either makes the other invalid. + +### IndexOf +{: .no_toc } + +Returns the index in the range of the first item that matches a value, or -1. + +Syntax: *object*.**IndexOf** ( *Value* [ , *Index* [ , *GetCount* ] ] ) **As Long** + +As [**IndexOf**](ArrayList#indexof), with *Index* and the result counted from the start of the range. Error 9 is raised when *Index* is outside the range or the run does not fit in it. + +### Insert +{: .no_toc } + +Inserts an item at a position of the range. + +Syntax: *object*.**Insert** ( *Index*, *Value* ) + +*Index* is counted from the start of the range, and the item is inserted in the list at that position. The range grows by one. *Index* is not checked against the range. + +### InsertRange +{: .no_toc } + +Inserts the items of an array or of another collection at a position of the range. + +Syntax: *object*.**InsertRange** ( *Index*, *Target* ) + +As [**InsertRange**](ArrayList#insertrange), with *Index* counted from the start of the range. The range grows by the number of items. + +### LastIndexOf +{: .no_toc } + +Returns the index in the range of the last item that matches a value, or -1. + +Syntax: *object*.**LastIndexOf** ( *Value*, *Index*, *GetCount* ) **As Long** + +As [**LastIndexOf**](ArrayList#lastindexof), with *Index* and the result counted from the start of the range. On a range, *Index* and *GetCount* are declared without **Optional**. + +### Remove +{: .no_toc } + +Removes the first item of the range that matches a value. + +Syntax: *object*.**Remove** ( *Value* ) + +As [**Remove**](ArrayList#remove). The item is removed from the list, and the range shrinks by one. + +### RemoveAt +{: .no_toc } + +Removes the item at a position of the range. + +Syntax: *object*.**RemoveAt** ( *Index* ) + +*Index* is counted from the start of the range; a value outside the range raises error 9. The item is removed from the list, and the range shrinks by one. + +### RemoveRange +{: .no_toc } + +Removes a run of items of the range. + +Syntax: *object*.**RemoveRange** ( *Index*, *GetCount* ) + +*Index* is counted from the start of the range. Error 9 is raised when the run does not lie in the range. The items are removed from the list, and the range shrinks by their number. + +### Reverse +{: .no_toc } + +Reverses the order of the items of the range, or of a run of them. + +Syntax: *object*.**Reverse** ( *Index*, *GetCount* ) + +As [**Reverse**](ArrayList#reverse), with *Index* counted from the start of the range, with the same loop. On a range, both arguments are declared without **Optional**. + +### SetRange +{: .no_toc } + +Replaces items of the range with the items of an array or of another collection. + +Syntax: *object*.**SetRange** ( *Index*, *Target* ) + +As [**SetRange**](ArrayList#setrange), with *Index* counted from the start of the range. Error 9 is raised when *Index* is outside the range. The check of the number of items is made against the list, not the range, so a *Target* longer than the rest of the range replaces items of the list after the range. + +### Sort +{: .no_toc } + +Sorts the items of the range, or a run of them. + +Syntax: *object*.**Sort** ( [ *Index* ] [ , *GetCount* ] [ , *Comparer* ] ) + +As [**Sort**](ArrayList#sort), with *Index* counted from the start of the range. Unlike the list, the range checks that the run lies in it and raises error 9 otherwise. + +### ToArray +{: .no_toc } + +Returns the items of the range as an array. + +Syntax: *object*.**ToArray** ( ) **As Variant()** + +As [**ToArray**](ArrayList#toarray), for the items of the range. + +## Hidden members + +### Bind +{: .no_toc } + +Makes the range a view of a run of a list. + +Syntax: *object*.**Bind** ( *TargetList* [ , *Index* [ , *GetCount* ] ] ) **As ListRange** + +*TargetList* +: *required* An [**IArrayList**](Interfaces#iarraylist): the list or range to look at. + +*Index* +: *optional* A **Long**: the index in *TargetList* of the first item. The default is 0. + +*GetCount* +: *optional* The number of items. The default is all the items from *Index*. + +Returns the range itself. **GetRange** calls **Bind**; a program has no reason to. + +### IndexOf_2, IndexOf_3, LastIndexOf_2, LastIndexOf_3, BinarySearch_2, BinarySearch_3, Sort_2 +{: .no_toc } + +The hidden overloads of [ArrayList](ArrayList#hidden-members), over the items of the range, with the same arguments. + +### IsFixedSize, IsReadOnly, IsSynchronized +{: .no_toc } + +Always **False**, as for [**ArrayList**](ArrayList#isfixedsize). + +### Items +{: .no_toc } + +Returns the items of the range as an array that shares their memory. + +Syntax: *object*.**Items** ( ) **As Variant()** + +As [**Items**](ArrayList#items): use the result only as the subject of **For Each**. + +### SyncRoot +{: .no_toc } + +Always **Nothing**, as for [**ArrayList**](ArrayList#syncroot). + +### Version +{: .no_toc } + +The version number that the range expects the list to have. **Long**, read-only. + +## See Also + +- [ArrayList](ArrayList) class -- the list; its **GetRange** returns a range +- [IListRange](Interfaces#ilistrange) interface -- the type that **GetRange** returns +- [IArrayList](Interfaces#iarraylist) interface -- all the members of a range +- [ArrayListLib package](./) -- overview diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md new file mode 100644 index 000000000..c2898f062 --- /dev/null +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -0,0 +1,95 @@ +--- +title: ArrayListLib Package +parent: Experimental Packages +has_toc: false +exclude_from_docs: + - LibMemory + - LibMemoryEx +indexed_from: 1.4.0.134 +permalink: /tB/Packages/Contributed/ArrayListLib/ +--- + +# ArrayListLib Package +{: .no_toc } + +**Author:** R.Beltran. **On GitHub:** [Theadd/ArrayList](https://github.com/Theadd/ArrayList), in the folder *ArrayListLib*. + +The **ArrayListLib** contributed package is a list class for twinBASIC, in place of the `ArrayList` class of .NET's `mscorlib` library. A VBA or VB6 program that needs a list which grows as items are added can use that class through `CreateObject("System.Collections.ArrayList")`. [**ArrayList**](ArrayList) has the members of that class which a COM client can call, under the same names, and needs no .NET Framework. + +This section documents version 1.4.0.134, which is the version on TWINSERV, published under the Unlicense. The repository is now at version 1.6, which has members that this section does not describe, such as a **BaseIndex** property and a **ToString** method; the sources of version 1.4 are in the history of the repository. + +> [!NOTE] +> Version 1.4.0.134 does not compile in twinBASIC BETA 1005, so a project that references it does not build. **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** and in the two quicksort procedures that **Sort** calls (error TB5001, in `ArrayList.twin` at lines 236, 519 and 536). + +```tb check_build +Dim scores As New ArrayList +scores.AddRange Array(72, 91, 65, 88) +scores.Sort + +Dim total As Long +Dim v As Variant +For Each v In scores + total += v +Next + +Debug.Print "lowest " & scores(0) & ", highest " & scores(scores.Count - 1) +Debug.Print "average " & total / scores.Count +``` + +## How it differs from the .NET class + +- **Creation.** A list is created with **New**, as in `Dim list As New ArrayList`, in place of `CreateObject`. +- **Members left out.** The package has none of the static members of the .NET class (**Adapter**, **FixedSize**, **ReadOnly**, **Repeat** and **Synchronized**), nor its **TrimToSize** method, and **ToArray** has no *Type* argument. +- **Overloads.** The README of the package says that COM has no overloaded methods, and that the hidden members with a number in their name, such as `IndexOf_2`, stand for the overloads of the .NET class. See [Hidden members](ArrayList#hidden-members). +- **Additions.** [**Count**](ArrayList#count) can be assigned, [**GetEnumerator**](ArrayList#getenumerator) takes a start, a count and a step so that **For Each** can go through part of a list or backwards, and the methods that take a collection also take any array. +- **Errors.** An index outside the list raises error 9, *Subscript out of range*, and an argument of the wrong kind error 13 or 6. The documentation of the .NET class lists an **ArgumentOutOfRangeException** for an index outside the list. +- **Changes during a loop.** A **For Each** over a list that changes while the loop runs is not detected. The documentation of the .NET class says that its enumerator is invalidated by such a change. See [Enumerating](ArrayList#enumerating). + +The page [ArrayList](ArrayList#differences-from-the-net-class) lists the remaining differences. + +## Classes and interfaces + +- [ArrayList](ArrayList) -- the list: items added, inserted, found, removed, sorted and enumerated + - [Capacity](ArrayList#capacity) -- returns or sets the number of items the list has room for + - [Count](ArrayList#count) -- returns or sets the number of items + - [Item](ArrayList#item) -- returns or sets one item (the default member) + - [Add](ArrayList#add), [AddRange](ArrayList#addrange), [Insert](ArrayList#insert), [InsertRange](ArrayList#insertrange) -- add items + - [Remove](ArrayList#remove), [RemoveAt](ArrayList#removeat), [RemoveRange](ArrayList#removerange), [Clear](ArrayList#clear) -- remove items + - [Contains](ArrayList#contains), [IndexOf](ArrayList#indexof), [LastIndexOf](ArrayList#lastindexof), [BinarySearch](ArrayList#binarysearch) -- find items + - [Sort](ArrayList#sort), [Reverse](ArrayList#reverse) -- reorder items + - [GetRange](ArrayList#getrange), [SetRange](ArrayList#setrange), [CopyTo](ArrayList#copyto), [ToArray](ArrayList#toarray), [Clone](ArrayList#clone) -- parts and copies + - [GetEnumerator](ArrayList#getenumerator) -- the enumerator that **For Each** uses +- [ListRange](ListRange) -- a view of a run of items in an ArrayList, returned by **GetRange** +- [Enumerator](Enumerator) -- the enumerator of **For Each**, with a start, a count and a step +- [Comparer](Comparer) -- compares two values for **Sort** and **BinarySearch** +- [Interfaces](Interfaces) -- the nine interfaces: **ICloneable**, **ICollection**, **IComparer**, **IEnumVARIANT**, **IEnumerable**, **IEnumerator**, **IList**, **IListRange** and **IArrayList** + +## Name clashes + +The package declares the names **ArrayList**, **Comparer**, **Enumerator**, **ListRange**, **ICloneable**, **ICollection**, **IComparer**, **IEnumVARIANT**, **IEnumerable**, **IEnumerator**, **IList**, **IListRange** and **IArrayList**, and its two memory modules declare more (see below). A name that another library or the project declares as well can be written with the name of the package in front, as in `ArrayListLib.ArrayList`. + +## Known problems + +These faults follow from the code of version 1.4.0.134. The package cannot be built in BETA 1005 (see the note above), so none of them has been run. + +- **Reverse.** The loop runs for *i* from 0 up to and including half of the count, rounded down, and swaps the items at *i* and at the count - 1 - *i* from the start of the run. For an even count the last pass swaps the middle pair a second time, so those two items keep their order. For a run of no items the loop still runs once. +- **CopyTo.** An array is refused with error 6 when `UBound(Target) - Index` is less than **Count**, so an array with exactly **Count** elements from *Index* is refused. The same check is in **ListRange**. +- **Insert and InsertRange.** *Index* is not checked against **Count**, and the number of items the method moves is **Count** - *Index*. +- **Sort and BinarySearch.** On an **ArrayList**, *Index* and *GetCount* are not checked against the list. A **ListRange** checks them. +- **Capacity.** The assignment does not check its value against **Count**. +- **Count.** A smaller value ends the list without clearing the items cut off. A larger value within the capacity makes those items part of the list again. +- **ListRange.** An assignment to an item through a range, and **SetRange** of a range with a list as its argument, make the range invalid, because they add to the version number of the range and of the list by different amounts. **SetRange** of a range checks the number of items against the list and not the range. +- **Enumerator.** **Reset** does not clear the step that **Next** adds, so the first **Next** after it moves past the first item once an item has been returned. **Clone** starts at the beginning of the part and not at the position of the original. +- **For Each.** A change to the list during the loop is not detected. + +## Memory modules + +The package includes two modules, **LibMemory** and **LibMemoryEx**, which the list classes use to read and write memory directly: the array descriptor type **SAFEARRAY_1D** and its constants, the procedures **VarPtrArr** and **ArrPtr** that return the address of an array, and **ZeroMemory**, **MemMoveEx**, **VariantArrayClone** and **ReassignArrayTo**, which clear, move, copy and hand over blocks of memory. **LibMemory** is the *VBA-MemoryTools* code of Ion Cristian Buse, under the MIT licence. A project that references the package can call them, but this section does not document them. + +## See Also + +- [Experimental Packages](../../Experimental/) -- the packages from TWINSERV that are not yet ready for use in a program +- [Contributed Packages](../) -- the other contributed packages +- [Collection](../../../Modules/Collection/) class -- the list that twinBASIC provides, with items found by key +- [For Each...Next](../../../Core/For-Each-Next) statement +- [Custom Enumerators](../../../../Features/Language/Custom-Enumerators) -- how a class provides the enumerator that **For Each** uses diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index c9aee6bfe..d691fe0a4 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -12,6 +12,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. +- [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.4.0.134 does not compile in twinBASIC BETA 1005. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.2 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. diff --git a/docs/_book.yml b/docs/_book.yml index 6d673501f..446d6a89a 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -592,6 +592,13 @@ parts: landing_is_target: true outline_closed: true + - title: ArrayListLib Package + subtitle: A list class in place of the ArrayList of .NET + landing_page: /tB/Packages/Contributed/ArrayListLib/ + page: /tB/Packages/Contributed/ArrayListLib/ + landing_is_target: true + outline_closed: true + - title: BigNumberLib Package subtitle: Arbitrary-precision signed integer arithmetic landing_page: /tB/Packages/Contributed/BigNumberLib/ diff --git a/indexer/snapshots/contributed/ArrayListLib/api.json b/indexer/snapshots/contributed/ArrayListLib/api.json new file mode 100644 index 000000000..d317f9aac --- /dev/null +++ b/indexer/snapshots/contributed/ArrayListLib/api.json @@ -0,0 +1,450 @@ +{ + "format": 3, + "package": "ArrayListLib", + "project": "ArrayListLib", + "version": "1.4.0.134", + "types": [ + { + "name": "ArrayList", + "kind": "class", + "vis": "public", + "file": "ArrayList.twin", + "signature": "Public Class ArrayList", + "implements": ["IArrayList"], + "attrs": [{"name":"ClassId","args":"\"E88D5510-C8E5-433D-8DA5-712D8586E0C9\""},{"name":"COMCreatable","args":"True"}], + "members": [ + {"name":"Add","kind":"function","vis":"public","signature":"Public Function Add(Value As Variant) As Long Implements IArrayList.Add","returnType":"Long","description":"Adds an item to the list. The return value is the position the new element was inserted in."}, + {"name":"AddRange","kind":"sub","vis":"public","signature":"Public Sub AddRange(Target As Variant) Implements IArrayList.AddRange"}, + {"name":"Assign","kind":"sub","vis":"private","signature":"Private Sub Assign(ByRef Target As Variant, ByRef Value As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"BinarySearch","kind":"function","vis":"public","signature":"Public Function BinarySearch(ByVal Index As Long, ByVal GetCount As Long, Value As Variant, Optional ByRef Comparer As IComparer = Nothing) As Long Implements IArrayList.BinarySearch","returnType":"Long","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}],"description":"Searches a section of a sorted list. Returns the index of the given value in the list. If not found, returns a negative integer. Use the bitwise operator (Not) to get the index of the first element larger than this one, if any."}, + {"name":"BinarySearch_2","kind":"function","vis":"public","signature":"Public Function BinarySearch_2(ByRef Value As Variant) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"BinarySearch_3","kind":"function","vis":"public","signature":"Public Function BinarySearch_3(ByRef Value As Variant, ByRef Comparer As IComparer) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"C2IArrayList","kind":"function","vis":"private","signature":"Private Function C2IArrayList(Value As Variant) As IArrayList","returnType":"IArrayList"}, + {"name":"C2IListRange","kind":"function","vis":"private","signature":"Private Function C2IListRange(Value As Variant) As IListRange","returnType":"IListRange"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Get Capacity() As Long Implements IArrayList.Capacity","returnType":"Long","accessor":"Get"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Let Capacity(Value As Long) Implements IArrayList.Capacity","accessor":"Let"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear() Implements IArrayList.Clear","description":"Removes all items from the list."}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements IArrayList.Clone","returnType":"Variant","description":"Creates a shallow copy of this ArrayList."}, + {"name":"CloneTo","kind":"sub","vis":"friend","signature":"Friend Sub CloneTo(ByVal Target As IArrayList, Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long) Implements IArrayList.CloneTo"}, + {"name":"Contains","kind":"function","vis":"public","signature":"Public Function Contains(Value As Variant) As Boolean Implements IArrayList.Contains","returnType":"Boolean","description":"Returns whether the list contains a particular item."}, + {"name":"CopyTo","kind":"sub","vis":"public","signature":"Public Sub CopyTo(Target As Variant, Index As Long) Implements IArrayList.CopyTo","description":"Copies this ArrayList to another array at specified index, the other array must be of a compatible array type but not necessarily the same type. It also accepts other lists implementing IListRange as target."}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long Implements IArrayList.Count","returnType":"Long","accessor":"Get"}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Let Count(ByVal Value As Long)","accessor":"Let"}, + {"name":"GetAddressOfItemAt","kind":"function","vis":"friend","signature":"Friend Function GetAddressOfItemAt(Index As Long) As LongPtr Implements IArrayList.GetAddressOfItemAt","returnType":"LongPtr"}, + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"GetRange","kind":"function","vis":"public","signature":"Public Function GetRange(ByVal Index As Long, ByVal GetCount As Long) As IListRange Implements IArrayList.GetRange","returnType":"IListRange"}, + {"name":"GrowCapacity","kind":"sub","vis":"private","signature":"Private Sub GrowCapacity(ByVal Increment As Long)"}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long Implements IArrayList.IndexOf","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, + {"name":"IndexOf_2","kind":"function","vis":"public","signature":"Public Function IndexOf_2(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IndexOf_3","kind":"function","vis":"public","signature":"Public Function IndexOf_3(ByRef Value As Variant) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal Index As Long, Value As Variant) Implements IArrayList.Insert","description":"Inserts value into the list at position Index. Index must be non-negative and less than or equal to the number of elements in the list. If Index equals the number of items in the list, then value is appended to the end."}, + {"name":"InsertListTo","kind":"sub","vis":"private","signature":"Private Sub InsertListTo(ByVal Index As Long, ByVal GetCount As Long, ByVal Target As IListRange)"}, + {"name":"InsertRange","kind":"sub","vis":"public","signature":"Public Sub InsertRange(ByVal Index As Long, Target As Variant) Implements IArrayList.InsertRange"}, + {"name":"IsContainedInMemoryRange","kind":"function","vis":"private","signature":"Private Function IsContainedInMemoryRange(TargetList As IListRange, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Boolean","returnType":"Boolean"}, + {"name":"IsFixedSize","kind":"property","vis":"public","signature":"Public Property Get IsFixedSize() As Boolean Implements IArrayList.IsFixedSize","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IsReadOnly","kind":"property","vis":"public","signature":"Public Property Get IsReadOnly() As Boolean Implements IArrayList.IsReadOnly","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IsSynchronized","kind":"property","vis":"public","signature":"Public Property Get IsSynchronized() As Boolean Implements IArrayList.IsSynchronized","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal Index As Long) As Variant Implements IArrayList.Item","returnType":"Variant","accessor":"Get","attrs":[{"name":"DefaultMember"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal Index As Long, Value As Variant) Implements IArrayList.Item","accessor":"Let"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal Index As Long, Value As Variant) Implements IArrayList.Item","accessor":"Set"}, + {"name":"Items","kind":"function","vis":"public","signature":"Public Function Items() As Variant() Implements IArrayList.Items","returnType":"Variant()","hidden":true,"attrs":[{"name":"Hidden"}],"description":"CAUTION: Limit usage only in For Each In .Items calls, do NOT assign the return value of .Items to another variable unless you know what you're doing."}, + {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Public Function LastIndexOf(ByRef Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long Implements IArrayList.LastIndexOf","returnType":"Long","description":"Returns the last index of a particular item. Returns -1 if the item isn't in the list."}, + {"name":"LastIndexOf_2","kind":"function","vis":"public","signature":"Public Function LastIndexOf_2(ByRef Value As Variant, ByVal Index As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"LastIndexOf_3","kind":"function","vis":"public","signature":"Public Function LastIndexOf_3(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"QuickSort","kind":"sub","vis":"private","signature":"Private Sub QuickSort(l As Long, r As Long, ByRef Comparer As IComparer)"}, + {"name":"QuickSortV2","kind":"sub","vis":"private","signature":"Private Sub QuickSortV2(l As Long, r As Long)"}, + {"name":"Remove","kind":"sub","vis":"public","signature":"Public Sub Remove(Value As Variant) Implements IArrayList.Remove","description":"Removes an item from the list."}, + {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal Index As Long) Implements IArrayList.RemoveAt","description":"Removes the item at Index position."}, + {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Public Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long) Implements IArrayList.RemoveRange"}, + {"name":"ReserveSpaceForInsert","kind":"sub","vis":"private","signature":"Private Sub ReserveSpaceForInsert(ByVal Index As Long, ByVal GetCount As Long)"}, + {"name":"ReserveSpaceForInsertOfOneAtZero","kind":"sub","vis":"private","signature":"Private Sub ReserveSpaceForInsertOfOneAtZero()"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) Implements IArrayList.Reverse"}, + {"name":"SetRange","kind":"sub","vis":"public","signature":"Public Sub SetRange(ByVal Index As Long, Target As Variant) Implements IArrayList.SetRange"}, + {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements IArrayList.Sort","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"Sort_2","kind":"sub","vis":"public","signature":"Public Sub Sort_2(ByRef Comparer As IComparer)","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Swap","kind":"sub","vis":"private","signature":"Private Sub Swap(ByRef A As Variant, ByRef B As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"SyncRoot","kind":"property","vis":"public","signature":"Public Property Get SyncRoot() As Variant Implements IArrayList.SyncRoot","returnType":"Variant","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant() Implements IArrayList.ToArray","returnType":"Variant()"}, + {"name":"VSwap","kind":"sub","vis":"private","signature":"Private Sub VSwap(ByRef A As UDTVariantB, ByRef B As UDTVariantB)"}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As Long Implements IArrayList.Version","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"pIndex","kind":"field","vis":"private","signature":"Private pIndex As Long"}, + {"name":"pItems","kind":"field","vis":"private","signature":"Private pItems() As Variant"}, + {"name":"pVersion","kind":"field","vis":"private","signature":"Private pVersion As Long"} + ] + }, + { + "name": "UDTVariantB", + "kind": "type", + "in": "ArrayList", + "vis": "private", + "public": false, + "file": "ArrayList.twin", + "signature": "Private Type UDTVariantB", + "fields": [ + {"name":"bytes","vis":"public","signature":"bytes(0 To 23) As Byte"} + ], + "members": [] + }, + { + "name": "UDTVariantB", + "kind": "type", + "in": "ArrayList", + "vis": "private", + "public": false, + "file": "ArrayList.twin", + "signature": "Private Type UDTVariantB", + "fields": [ + {"name":"bytes","vis":"public","signature":"bytes(0 To 15) As Byte"} + ], + "members": [] + }, + { + "name": "Comparer", + "kind": "class", + "vis": "public", + "file": "Comparer.twin", + "signature": "Public Class Comparer", + "implements": ["IComparer"], + "attrs": [{"name":"ClassId","args":"\"65B491CB-89D6-44D4-91EC-A29A6EAC9A9C\""},{"name":"PredeclaredId"}], + "members": [ + {"name":"Compare","kind":"function","vis":"public","signature":"Public Function Compare(X As Variant, Y As Variant) As Long Implements IComparer.Compare","returnType":"Long"} + ] + }, + { + "name": "Enumerator", + "kind": "class", + "vis": "public", + "file": "Enumerator.twin", + "signature": "Public Class Enumerator", + "implements": ["IEnumerator"], + "attrs": [{"name":"ClassId","args":"\"5892CEA6-ADE3-4532-B478-1F6122F3537C\""},{"name":"COMCreatable","args":"True"}], + "members": [ + {"name":"Assign","kind":"sub","vis":"private","signature":"Private Sub Assign(ByRef Target As Variant, ByRef Value As Variant)","attrs":[{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"Bind","kind":"sub","vis":"public","signature":"Public Sub Bind(ByRef TargetArray() As Variant, ByVal Index As Long, ByVal GetCount As Long, ByVal GetStep As Long)"}, + {"name":"CIndex","kind":"field","vis":"private","signature":"Private CIndex As Long"}, + {"name":"CStep","kind":"field","vis":"private","signature":"Private CStep As Long"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clone","kind":"sub","vis":"private","signature":"Private Sub Clone(ByRef ppenum As IEnumVARIANT) Implements IEnumerator.Clone"}, + {"name":"CurrentIndex","kind":"property","vis":"public","signature":"Public Property Get CurrentIndex() As Long Implements IEnumerator.CurrentIndex","returnType":"Long","accessor":"Get","description":"The Index of the current item being enumerated. If you manually change it, the next item being enumerated will be the one at the specified index."}, + {"name":"CurrentIndex","kind":"property","vis":"public","signature":"Public Property Let CurrentIndex(Value As Long) Implements IEnumerator.CurrentIndex","accessor":"Let"}, + {"name":"CurrentSize","kind":"property","vis":"public","signature":"Public Property Get CurrentSize() As Long Implements IEnumerator.CurrentSize","returnType":"Long","accessor":"Get","description":"It's the size of the list, array, or section of it, which is being enumerated. Use with caution and only when enumerating a section of a bigger collection. This won't check for out of bounds."}, + {"name":"CurrentSize","kind":"property","vis":"public","signature":"Public Property Let CurrentSize(Value As Long) Implements IEnumerator.CurrentSize","accessor":"Let"}, + {"name":"CurrentStep","kind":"property","vis":"public","signature":"Public Property Get CurrentStep() As Long Implements IEnumerator.CurrentStep","returnType":"Long","accessor":"Get","description":"Allows to change the iteration steps count and it's direction."}, + {"name":"CurrentStep","kind":"property","vis":"public","signature":"Public Property Let CurrentStep(Value As Long) Implements IEnumerator.CurrentStep","accessor":"Let"}, + {"name":"E_INVALIDARGS","kind":"const","vis":"private","signature":"Private Const E_INVALIDARGS As Long = &H80070057"}, + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator() As stdole.IUnknown","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"Items","kind":"field","vis":"private","signature":"Private Items() As Variant"}, + {"name":"MaxValue","kind":"field","vis":"private","signature":"Private MaxValue As Long"}, + {"name":"NStep","kind":"field","vis":"private","signature":"Private NStep As Long"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByRef TargetArray() As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator)"}, + {"name":"Next","kind":"sub","vis":"private","signature":"Private Sub Next(ByVal celt As Long, ByRef rgvar As Variant, ByRef pceltFetched As Long) Implements IEnumerator.Next","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"Reset","kind":"sub","vis":"private","signature":"Private Sub Reset() Implements IEnumerator.Reset"}, + {"name":"S_FALSE","kind":"const","vis":"private","signature":"Private Const S_FALSE As Long = 1"}, + {"name":"S_OK","kind":"const","vis":"private","signature":"Private Const S_OK As Long = 0"}, + {"name":"Skip","kind":"sub","vis":"private","signature":"Private Sub Skip(ByVal celt As Long) Implements IEnumerator.Skip"} + ] + }, + { + "name": "IArrayList", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface IArrayList Extends IListRange", + "extends": "IListRange", + "implements": ["ICloneable"], + "attrs": [{"name":"InterfaceId","args":"\"1043D433-27BB-40F7-9255-50F4724FAB94\""}], + "members": [ + {"name":"Capacity","kind":"property","vis":"public","signature":"Property Get Capacity() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Property Let Capacity(Value As Long)","accessor":"Let"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Function Clone() As Variant Implements ICloneable.Clone","returnType":"Variant"}, + {"name":"CloneTo","kind":"sub","vis":"public","signature":"Sub CloneTo(ByVal Target As IArrayList, ByRef Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long)","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Items","kind":"function","vis":"public","signature":"Function Items() As Variant()","returnType":"Variant()","hidden":true,"attrs":[{"name":"Hidden"}],"description":"CAUTION: Limit usage only in For Each In .Items calls, do NOT assign the return value of .Items to another variable unless you know what you're doing."}, + {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Function LastIndexOf(ByRef Value As Variant, Optional ByVal Index As Variant, Optional ByVal GetCount As Variant) As Long","returnType":"Long"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Sub Reverse(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant)"}, + {"name":"Version","kind":"property","vis":"public","signature":"Property Get Version() As Long","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]} + ] + }, + { + "name": "ICloneable", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface ICloneable", + "attrs": [{"name":"InterfaceId","args":"\"8ABD76D4-42F5-4920-927A-BC766E00CE46\""}], + "description": "Make a new object which is a copy of the object instanced. This object may be either deep copy or a shallow copy depending on the implementation of clone.", + "members": [ + {"name":"Clone","kind":"function","vis":"public","signature":"Function Clone() As Variant","returnType":"Variant"} + ] + }, + { + "name": "ICollection", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface ICollection Extends IEnumerable", + "extends": "IEnumerable", + "attrs": [{"name":"InterfaceId","args":"\"B2E5C8F5-2847-450B-873B-F4BE7621F627\""}], + "description": "Base interface for all collections", + "members": [ + {"name":"CopyTo","kind":"sub","vis":"public","signature":"Sub CopyTo(Target As Variant, Index As Long)"}, + {"name":"Count","kind":"property","vis":"public","signature":"Property Get Count() As Long","returnType":"Long","accessor":"Get"}, + {"name":"IsSynchronized","kind":"property","vis":"public","signature":"Property Get IsSynchronized() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"SyncRoot","kind":"property","vis":"public","signature":"Property Get SyncRoot() As Variant","returnType":"Variant","accessor":"Get"} + ] + }, + { + "name": "IComparer", + "kind": "interface", + "vis": "public", + "file": "Comparer.twin", + "signature": "Public Interface IComparer", + "attrs": [{"name":"InterfaceId","args":"\"D60ABA2A-1176-4515-9B4C-B0312BA5FE7F\""}], + "members": [ + {"name":"Compare","kind":"function","vis":"public","signature":"Function Compare(X As Variant, Y As Variant) As Long","returnType":"Long"} + ] + }, + { + "name": "IEnumVARIANT", + "kind": "interface", + "vis": "public", + "file": "Enumerator.twin", + "signature": "Public Interface IEnumVARIANT Extends stdole.IUnknown", + "extends": "stdole.IUnknown", + "attrs": [{"name":"InterfaceId","args":"\"00020404-0000-0000-C000-000000000046\""},{"name":"COMExtensible","args":"True"}], + "members": [ + {"name":"Clone","kind":"sub","vis":"public","signature":"Sub Clone(ByRef ppenum As IEnumVARIANT)"}, + {"name":"Next","kind":"sub","vis":"public","signature":"Sub Next(ByVal celt As Long, ByRef rgvar As Variant, ByRef pceltFetched As Long)"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Sub Reset()"}, + {"name":"Skip","kind":"sub","vis":"public","signature":"Sub Skip(ByVal celt As Long)"} + ] + }, + { + "name": "IEnumerable", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface IEnumerable", + "attrs": [{"name":"InterfaceId","args":"\"F078EB73-3FA5-47F4-8003-520AEA1CE315\""}], + "members": [ + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As Any) As stdole.IUnknown","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]} + ] + }, + { + "name": "IEnumerator", + "kind": "interface", + "vis": "public", + "file": "Enumerator.twin", + "signature": "Public Interface IEnumerator Extends IEnumVARIANT", + "extends": "IEnumVARIANT", + "attrs": [{"name":"InterfaceId","args":"\"8E42D737-24C2-4D3F-8903-4B30E9383C69\""},{"name":"COMExtensible","args":"True"}], + "members": [ + {"name":"CurrentIndex","kind":"property","vis":"public","signature":"Property Get CurrentIndex() As Long","returnType":"Long","accessor":"Get","description":"The Index of the current item being enumerated. If you manually change it, the next item being enumerated will be the one at the specified index."}, + {"name":"CurrentIndex","kind":"property","vis":"public","signature":"Property Let CurrentIndex(Value As Long)","accessor":"Let"}, + {"name":"CurrentSize","kind":"property","vis":"public","signature":"Property Get CurrentSize() As Long","returnType":"Long","accessor":"Get","description":"It's the size of the list, array, or section of it, which is being enumerated. Use with caution and only when enumerating a section of a bigger collection. This won't check for out of bounds."}, + {"name":"CurrentSize","kind":"property","vis":"public","signature":"Property Let CurrentSize(Value As Long)","accessor":"Let"}, + {"name":"CurrentStep","kind":"property","vis":"public","signature":"Property Get CurrentStep() As Long","returnType":"Long","accessor":"Get","description":"Allows to change the iteration steps count and it's direction."}, + {"name":"CurrentStep","kind":"property","vis":"public","signature":"Property Let CurrentStep(Value As Long)","accessor":"Let"} + ] + }, + { + "name": "IList", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface IList Extends ICollection", + "extends": "ICollection", + "attrs": [{"name":"InterfaceId","args":"\"BC694131-C923-4813-9D34-C79C5E2F6980\""}], + "description": "Base interface for all lists.", + "members": [ + {"name":"Add","kind":"function","vis":"public","signature":"Function Add(Value As Variant) As Long","returnType":"Long","description":"Adds an item to the list. The return value is the position the new element was inserted in."}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Sub Clear()","description":"Removes all items from the list."}, + {"name":"Contains","kind":"function","vis":"public","signature":"Function Contains(Value As Variant) As Boolean","returnType":"Boolean","description":"Returns whether the list contains a particular item."}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long","returnType":"Long","description":"Returns the index of a particular item. Returns -1 if the item isn't in the list."}, + {"name":"Insert","kind":"sub","vis":"public","signature":"Sub Insert(ByVal Index As Long, Value As Variant)","description":"Inserts value into the list at position Index. Index must be non-negative and less than or equal to the number of elements in the list. If Index equals the number of items in the list, then value is appended to the end."}, + {"name":"IsFixedSize","kind":"property","vis":"public","signature":"Property Get IsFixedSize() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IsReadOnly","kind":"property","vis":"public","signature":"Property Get IsReadOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Item","kind":"property","vis":"public","signature":"Property Get Item(ByVal Index As Long) As Variant","returnType":"Variant","accessor":"Get","description":"The Item property provides methods to read and edit entries in the List."}, + {"name":"Item","kind":"property","vis":"public","signature":"Property Let Item(ByVal Index As Long, Value As Variant)","accessor":"Let"}, + {"name":"Item","kind":"property","vis":"public","signature":"Property Set Item(ByVal Index As Long, Value As Variant)","accessor":"Set"}, + {"name":"Remove","kind":"sub","vis":"public","signature":"Sub Remove(Value As Variant)","description":"Removes an item from the list."}, + {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Sub RemoveAt(ByVal Index As Long)","description":"Removes the item at position index."} + ] + }, + { + "name": "IListRange", + "kind": "interface", + "vis": "public", + "file": "IArrayList.twin", + "signature": "Public Interface IListRange Extends IList", + "extends": "IList", + "attrs": [{"name":"InterfaceId","args":"\"480A22F9-91B0-483A-8A33-11634FCF644A\""}], + "members": [ + {"name":"AddRange","kind":"sub","vis":"public","signature":"Sub AddRange(Target As Variant)"}, + {"name":"BinarySearch","kind":"function","vis":"public","signature":"Function BinarySearch(ByVal Index As Long, ByVal GetCount As Long, ByRef Value As Variant, Optional ByRef Comparer As IComparer = Nothing) As Long","returnType":"Long"}, + {"name":"GetAddressOfItemAt","kind":"function","vis":"public","signature":"Function GetAddressOfItemAt(Index As Long) As LongPtr","returnType":"LongPtr","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"GetRange","kind":"function","vis":"public","signature":"Function GetRange(ByVal Index As Long, ByVal GetCount As Long) As IListRange","returnType":"IListRange"}, + {"name":"InsertRange","kind":"sub","vis":"public","signature":"Sub InsertRange(ByVal Index As Long, Target As Variant)"}, + {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long)","description":"Removes a range of elements from the list."}, + {"name":"SetRange","kind":"sub","vis":"public","signature":"Sub SetRange(ByVal Index As Long, Target As Variant)"}, + {"name":"Sort","kind":"sub","vis":"public","signature":"Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByRef Comparer As IComparer = Nothing)","description":"Sorts the elements in a section of this list. The sort compares the elements to each other using the given IComparer interface."}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Function ToArray() As Variant()","returnType":"Variant()"} + ] + }, + { + "name": "LibMemory", + "kind": "module", + "vis": "public", + "file": "LibMemory.twin", + "signature": "Module LibMemory", + "members": [ + {"name":"ArrPtr","kind":"function","vis":"public","signature":"Public Function ArrPtr(ByRef arr As Variant) As LongPtr","returnType":"LongPtr"}, + {"name":"INT_SIZE","kind":"const","vis":"public","signature":"Public Const INT_SIZE As Long = 2"}, + {"name":"PTR_SIZE","kind":"const","vis":"public","signatures":[{"cond":"Win64","signature":"Public Const PTR_SIZE As Long = 8"},{"cond":"Not (Win64)","signature":"Public Const PTR_SIZE As Long = 4"}]}, + {"name":"VARIANT_SIZE","kind":"const","vis":"public","signatures":[{"cond":"Win64","signature":"Public Const VARIANT_SIZE As Long = 24"},{"cond":"Not (Win64)","signature":"Public Const VARIANT_SIZE As Long = 16"}]}, + {"name":"VT_BYREF","kind":"const","vis":"public","signature":"Public Const VT_BYREF As Long = &H4000"}, + {"name":"VarPtrArr","kind":"function","vis":"public","signature":"Public Function VarPtrArr(ByRef arr As Variant) As LongPtr","returnType":"LongPtr"} + ] + }, + { + "name": "SAFEARRAYBOUND", + "kind": "type", + "in": "LibMemory", + "vis": "public", + "file": "LibMemory.twin", + "signature": "Public Type SAFEARRAYBOUND", + "fields": [ + {"name":"cElements","vis":"public","signature":"cElements As Long"}, + {"name":"lLbound","vis":"public","signature":"lLbound As Long"} + ], + "members": [] + }, + { + "name": "SAFEARRAY_1D", + "kind": "type", + "in": "LibMemory", + "vis": "public", + "file": "LibMemory.twin", + "signature": "Public Type SAFEARRAY_1D", + "fields": [ + {"name":"cDims","vis":"public","signature":"cDims As Integer"}, + {"name":"fFeatures","vis":"public","signature":"fFeatures As Integer"}, + {"name":"cbElements","vis":"public","signature":"cbElements As Long"}, + {"name":"cLocks","vis":"public","signature":"cLocks As Long"}, + {"name":"dummyPadding","vis":"public","signature":"dummyPadding As Long"}, + {"name":"pvData","vis":"public","signature":"pvData As LongLong"}, + {"name":"pvData","vis":"public","signature":"pvData As Long"}, + {"name":"rgsabound0","vis":"public","signature":"rgsabound0 As SAFEARRAYBOUND"} + ], + "members": [] + }, + { + "name": "LibMemoryEx", + "kind": "module", + "vis": "public", + "file": "LibMemoryEx.twin", + "signature": "Public Module LibMemoryEx", + "members": [ + {"name":"FADF_AUTO","kind":"const","vis":"public","signature":"Public Const FADF_AUTO As Long = &H1"}, + {"name":"FADF_EMBEDDED","kind":"const","vis":"public","signature":"Public Const FADF_EMBEDDED As Long = &H4"}, + {"name":"FADF_FIXEDSIZE","kind":"const","vis":"public","signature":"Public Const FADF_FIXEDSIZE As Long = &H10"}, + {"name":"FADF_HAVEVARTYPE","kind":"const","vis":"public","signature":"Public Const FADF_HAVEVARTYPE As Long = &H80"}, + {"name":"FADF_VARIANT","kind":"const","vis":"public","signature":"Public Const FADF_VARIANT As Long = &H800"}, + {"name":"FillMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub FillMemory Lib \"kernel32\" Alias \"RtlFillMemory\" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)"}, + {"name":"LSetByVal","kind":"sub","vis":"private","signature":"Private Sub LSetByVal(ByRef Target As UDTStringPointer, ByVal Value As String)"}, + {"name":"MemMoveBSTR","kind":"sub","vis":"private","signature":"Private Sub MemMoveBSTR(ByRef Target As LongPtr, ByRef Source As LongPtr, ByVal ByteCount As Long)"}, + {"name":"MemMoveEx","kind":"sub","vis":"public","signature":"Public Sub MemMoveEx(ByVal Target As LongPtr, ByVal Source As LongPtr, ByVal ByteCount As LongPtr)"}, + {"name":"ReassignArrayTo","kind":"sub","vis":"public","signature":"Public Sub ReassignArrayTo(ByRef Destination As Variant, ByRef Source As Variant)"}, + {"name":"RtlMoveMemory","kind":"sub","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Sub RtlMoveMemory Lib \"kernel32\" (ByVal lpDest As LongPtr, ByVal lpSource As LongPtr, ByVal Size As LongPtr)"}, + {"name":"SAFEARRAY_CELEMENTS_OFFSET","kind":"const","vis":"public","signature":"Public Const SAFEARRAY_CELEMENTS_OFFSET As Long = VARIANT_SIZE / INT_SIZE + 4 + PTR_SIZE"}, + {"name":"SAFEARRAY_LLBOUND_OFFSET","kind":"const","vis":"public","signature":"Public Const SAFEARRAY_LLBOUND_OFFSET As Long = VARIANT_SIZE / INT_SIZE + 8 + PTR_SIZE"}, + {"name":"SAFEARRAY_PVDATA_OFFSET","kind":"const","vis":"public","signature":"Public Const SAFEARRAY_PVDATA_OFFSET As Long = 8 + PTR_SIZE"}, + {"name":"SafeArrayCopyData","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SafeArrayCopyData Lib \"oleaut32\" (ByRef psaSource As Any, ByRef psaTarget As Any) As Long","returnType":"Long"}, + {"name":"VariantArrayClone","kind":"sub","vis":"public","signature":"Public Sub VariantArrayClone(ByVal DestinationAddress As LongPtr, ByVal SourceAddress As LongPtr, ByVal GetCount As Long, Optional ByVal ArrayElementSize As Long = VARIANT_SIZE)"}, + {"name":"ZeroMemory","kind":"sub","vis":"public","signature":"Public Sub ZeroMemory(ByVal TargetAddress As LongPtr, ByVal ByteCount As Long)"} + ] + }, + { + "name": "UDTStringPointer", + "kind": "type", + "in": "LibMemoryEx", + "vis": "public", + "file": "LibMemoryEx.twin", + "signature": "Type UDTStringPointer", + "fields": [ + {"name":"Value","vis":"public","signature":"Value As String"} + ], + "members": [] + }, + { + "name": "ListRange", + "kind": "class", + "vis": "public", + "file": "ListRange.twin", + "signature": "Public Class ListRange", + "implements": ["IArrayList","ArrayList"], + "attrs": [{"name":"ClassId","args":"\"0A102E9C-9E68-411A-A4B8-E6128FC07721\""},{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Add","kind":"function","vis":"public","signature":"Public Function Add(Value As Variant) As Long Implements ArrayList.Add, IArrayList.Add","returnType":"Long"}, + {"name":"AddRange","kind":"sub","vis":"public","signature":"Public Sub AddRange(Target As Variant) Implements ArrayList.AddRange, IArrayList.AddRange"}, + {"name":"ArrayList_Count","kind":"property","vis":"private","signature":"Private Property Let ArrayList_Count(ByVal Value As Long) Implements ArrayList.Count","accessor":"Let"}, + {"name":"Assign","kind":"sub","vis":"private","signature":"Private Sub Assign(ByRef Target As Variant, ByRef Value As Variant)","attrs":[{"name":"ArrayBoundsChecks","args":"False"},{"name":"IntegerOverflowChecks","args":"False"}]}, + {"name":"BinarySearch","kind":"function","vis":"public","signature":"Public Function BinarySearch(ByVal Index As Long, ByVal GetCount As Long, Value As Variant, Optional ByRef Comparer As IComparer = Nothing) As Long Implements ArrayList.BinarySearch, IArrayList.BinarySearch","returnType":"Long"}, + {"name":"BinarySearch_2","kind":"function","vis":"public","signature":"Public Function BinarySearch_2(ByRef Value As Variant) As Long Implements ArrayList.BinarySearch_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"BinarySearch_3","kind":"function","vis":"public","signature":"Public Function BinarySearch_3(ByRef Value As Variant, ByRef Comparer As IComparer) As Long Implements ArrayList.BinarySearch_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Bind","kind":"function","vis":"public","signature":"Public Function Bind(TargetList As IArrayList, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As ListRange","returnType":"ListRange","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"C2IArrayList","kind":"function","vis":"private","signature":"Private Function C2IArrayList(Value As Variant) As IArrayList","returnType":"IArrayList"}, + {"name":"C2IListRange","kind":"function","vis":"private","signature":"Private Function C2IListRange(Value As Variant) As IListRange","returnType":"IListRange"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Get Capacity() As Long Implements ArrayList.Capacity, IArrayList.Capacity","returnType":"Long","accessor":"Get"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Let Capacity(Value As Long) Implements ArrayList.Capacity, IArrayList.Capacity","accessor":"Let"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear() Implements ArrayList.Clear, IArrayList.Clear"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As Variant Implements ArrayList.Clone, IArrayList.Clone","returnType":"Variant"}, + {"name":"CloneTo","kind":"sub","vis":"private","signature":"Private Sub CloneTo(ByVal Target As IArrayList, Source As IArrayList, ByVal Index As Long, ByVal GetCount As Long) Implements ArrayList.CloneTo, IArrayList.CloneTo"}, + {"name":"Contains","kind":"function","vis":"public","signature":"Public Function Contains(Value As Variant) As Boolean Implements ArrayList.Contains, IArrayList.Contains","returnType":"Boolean"}, + {"name":"CopyTo","kind":"sub","vis":"public","signature":"Public Sub CopyTo(Target As Variant, Index As Long) Implements ArrayList.CopyTo, IArrayList.CopyTo"}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long Implements ArrayList.Count, IArrayList.Count","returnType":"Long","accessor":"Get"}, + {"name":"Count","kind":"property","vis":"private","signature":"Private Property Let Count(ByVal Value As Long)","accessor":"Let"}, + {"name":"GetAddressOfItemAt","kind":"function","vis":"private","signature":"Private Function GetAddressOfItemAt(Index As Long) As LongPtr Implements ArrayList.GetAddressOfItemAt, IArrayList.GetAddressOfItemAt","returnType":"LongPtr"}, + {"name":"GetEnumerator","kind":"function","vis":"public","signature":"Public Function GetEnumerator(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional ByVal GetStep As Long = 1, Optional ByRef ThisEnumerator As IEnumerator) As stdole.IUnknown Implements ArrayList.GetEnumerator, IArrayList.GetEnumerator","returnType":"stdole.IUnknown","attrs":[{"name":"Enumerator"}]}, + {"name":"GetRange","kind":"function","vis":"public","signature":"Public Function GetRange(ByVal Index As Long, ByVal GetCount As Long) As IListRange Implements ArrayList.GetRange, IArrayList.GetRange","returnType":"IListRange"}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(Value As Variant, Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant) As Long Implements ArrayList.IndexOf, IArrayList.IndexOf","returnType":"Long"}, + {"name":"IndexOf_2","kind":"function","vis":"public","signature":"Public Function IndexOf_2(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long Implements ArrayList.IndexOf_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IndexOf_3","kind":"function","vis":"public","signature":"Public Function IndexOf_3(ByRef Value As Variant) As Long Implements ArrayList.IndexOf_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal Index As Long, Value As Variant) Implements ArrayList.Insert, IArrayList.Insert"}, + {"name":"InsertRange","kind":"sub","vis":"public","signature":"Public Sub InsertRange(ByVal Index As Long, Target As Variant) Implements ArrayList.InsertRange, IArrayList.InsertRange"}, + {"name":"InternalUpdateRange","kind":"sub","vis":"private","signature":"Private Sub InternalUpdateRange()"}, + {"name":"IsFixedSize","kind":"property","vis":"public","signature":"Public Property Get IsFixedSize() As Boolean Implements ArrayList.IsFixedSize, IArrayList.IsFixedSize","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IsReadOnly","kind":"property","vis":"public","signature":"Public Property Get IsReadOnly() As Boolean Implements ArrayList.IsReadOnly, IArrayList.IsReadOnly","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"IsSynchronized","kind":"property","vis":"public","signature":"Public Property Get IsSynchronized() As Boolean Implements ArrayList.IsSynchronized, IArrayList.IsSynchronized","returnType":"Boolean","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal Index As Long) As Variant Implements ArrayList.Item, IArrayList.Item","returnType":"Variant","accessor":"Get","attrs":[{"name":"DefaultMember"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal Index As Long, Value As Variant) Implements ArrayList.Item, IArrayList.Item","accessor":"Let"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal Index As Long, Value As Variant) Implements ArrayList.Item, IArrayList.Item","accessor":"Set"}, + {"name":"Items","kind":"function","vis":"public","signature":"Public Function Items() As Variant() Implements ArrayList.Items, IArrayList.Items","returnType":"Variant()","hidden":true,"attrs":[{"name":"Hidden"}],"description":"CAUTION: Limit usage only in For Each In .Items calls, do NOT assign the return value of .Items to another variable unless you know what you're doing."}, + {"name":"LastIndexOf","kind":"function","vis":"public","signature":"Public Function LastIndexOf(Value As Variant, ByVal Index As Variant, ByVal GetCount As Variant) As Long Implements ArrayList.LastIndexOf, IArrayList.LastIndexOf","returnType":"Long"}, + {"name":"LastIndexOf_2","kind":"function","vis":"public","signature":"Public Function LastIndexOf_2(ByRef Value As Variant, ByVal Index As Long) As Long Implements ArrayList.LastIndexOf_2","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"LastIndexOf_3","kind":"function","vis":"public","signature":"Public Function LastIndexOf_3(ByRef Value As Variant, ByVal Index As Long, ByVal GetCount As Long) As Long Implements ArrayList.LastIndexOf_3","returnType":"Long","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New() Implements ArrayList.New"}, + {"name":"Remove","kind":"sub","vis":"public","signature":"Public Sub Remove(Value As Variant) Implements ArrayList.Remove, IArrayList.Remove"}, + {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal Index As Long) Implements ArrayList.RemoveAt, IArrayList.RemoveAt"}, + {"name":"RemoveRange","kind":"sub","vis":"public","signature":"Public Sub RemoveRange(ByVal Index As Long, ByVal GetCount As Long) Implements ArrayList.RemoveRange, IArrayList.RemoveRange"}, + {"name":"Reverse","kind":"sub","vis":"public","signature":"Public Sub Reverse(ByVal Index As Long, ByVal GetCount As Variant) Implements ArrayList.Reverse, IArrayList.Reverse"}, + {"name":"SetRange","kind":"sub","vis":"public","signature":"Public Sub SetRange(ByVal Index As Long, Target As Variant) Implements ArrayList.SetRange, IArrayList.SetRange"}, + {"name":"Sort","kind":"sub","vis":"public","signature":"Public Sub Sort(Optional ByVal Index As Long = 0, Optional ByVal GetCount As Variant, Optional Comparer As IComparer = Nothing) Implements ArrayList.Sort, IArrayList.Sort"}, + {"name":"Sort_2","kind":"sub","vis":"public","signature":"Public Sub Sort_2(ByRef Comparer As IComparer) Implements ArrayList.Sort_2","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"SyncRoot","kind":"property","vis":"public","signature":"Public Property Get SyncRoot() As Variant Implements ArrayList.SyncRoot, IArrayList.SyncRoot","returnType":"Variant","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant() Implements ArrayList.ToArray, IArrayList.ToArray","returnType":"Variant()"}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As Long Implements ArrayList.Version, IArrayList.Version","returnType":"Long","accessor":"Get","hidden":true,"attrs":[{"name":"Hidden"}]}, + {"name":"pBaseIndex","kind":"field","vis":"private","signature":"Private pBaseIndex As Long"}, + {"name":"pIndex","kind":"field","vis":"private","signature":"Private pIndex As Long"}, + {"name":"pItems","kind":"field","vis":"private","signature":"Private pItems() As Variant"}, + {"name":"pList","kind":"field","vis":"private","signature":"Private pList As IArrayList"}, + {"name":"pVersion","kind":"field","vis":"private","signature":"Private pVersion As Long"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-ArrayListLib/Settings b/test/example-projects/contributed-ArrayListLib/Settings new file mode 100644 index 000000000..9fb889ac2 --- /dev/null +++ b/test/example-projects/contributed-ArrayListLib/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{8647345A-521F-4728-84BC-0D28FA583254}", + "licence": "Unlicense", + "name": "[IMPORTED] mscorlib's ArrayList drop-in replacement", + "path32": "/Packages/ArrayListLib", + "path64": "/Packages/ArrayListLib", + "symbolId": "ArrayListLib", + "versionBuild": "134", + "versionMajor": "1", + "versionMinor": "4", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 0d34f8a294f6e03fde58739e196fd6158f00e267 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 15:48:46 +0200 Subject: [PATCH 11/15] tbFileSysTools: a contributed section of ten pages (FileSystemTools, FileSystemObject, Drive, File, Folder, TextStream and the three collections), with its snapshot, sample template and book chapter; its differences from the Scripting Runtime's FSO and its known problems each shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 1 + .../Contributed/tbFileSysTools/Drive.md | 193 +++ .../Contributed/tbFileSysTools/Drives.md | 80 + .../Contributed/tbFileSysTools/File.md | 387 +++++ .../tbFileSysTools/FileSystemObject.md | 382 +++++ .../tbFileSysTools/FileSystemTools.md | 1487 +++++++++++++++++ .../Contributed/tbFileSysTools/Files.md | 94 ++ .../Contributed/tbFileSysTools/Folder.md | 329 ++++ .../Contributed/tbFileSysTools/Folders.md | 69 + .../Contributed/tbFileSysTools/TextStream.md | 312 ++++ .../Contributed/tbFileSysTools/index.md | 248 +++ docs/_book.yml | 7 + .../contributed/tbFileSysTools/api.json | 1485 ++++++++++++++++ .../contributed-tbFileSysTools/Settings | 74 + 16 files changed, 5150 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/tbFileSysTools/Drive.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/Drives.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/File.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/Files.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/Folder.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/Folders.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/TextStream.md create mode 100644 docs/Reference/Contributed/tbFileSysTools/index.md create mode 100644 indexer/snapshots/contributed/tbFileSysTools/api.json create mode 100644 test/example-projects/contributed-tbFileSysTools/Settings diff --git a/WIP.md b/WIP.md index 4f71b67e4..7f333705e 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-one **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-two **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 4cd73d368..80477181a 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1193, + "pages": 1203, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index 67237507c..0f0b5f091 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -17,6 +17,7 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**Fmt**](Fmt/) -- **StringFormat**, a string built from a format string and values, in the style of C#'s **String.Format** - [**Generics**](Generics/) -- tuples, read-only objects that hold one to seven values of declared types - [**OLEGuids**](OLEGuids/) -- OLE and COM interfaces and structures in twinBASIC source, in place of `OLEGuids.tlb` +- [**tbFileSysTools**](tbFileSysTools/) -- files, folders, drives and text streams, in place of the Scripting Runtime's **FileSystemObject** - [**TBMANLIB_Dialog**](TBMANLIB_Dialog/) -- the **cDialog** class: the Open, Save and folder dialogs, with file filters and path helpers - [**TBMANLIB_ListBoxHScroll**](TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox** - [**TBMANLIB_RES**](TBMANLIB_RES/) -- resources embedded in an executable, as bytes, strings, hex, Base64 or pictures diff --git a/docs/Reference/Contributed/tbFileSysTools/Drive.md b/docs/Reference/Contributed/tbFileSysTools/Drive.md new file mode 100644 index 000000000..966d47ddd --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/Drive.md @@ -0,0 +1,193 @@ +--- +title: Drive +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/Drive +has_toc: false +--- + +# Drive class +{: .no_toc } + +A **Drive** represents a disk drive or a network share: its type, label, file system, serial number and free space, and its root folder. It has the members of the **Drive** of the Scripting Runtime, and adds [**SerialNumberHex**](#serialnumberhex) and [**UnsignedSerialNumber**](#unsignedserialnumber). + +A **Drive** is not created with **New**. [**GetDrive**](FileSystemTools#getdrive) returns one, [**GetDrives**](FileSystemTools#getdrives) returns a [**Drives**](Drives) collection of them, and so do [**File.Drive**](File#drive) and [**Folder.Drive**](Folder#drive). + +```tb check_build check_run +Dim d As Drive +Set d = GetDrive(Left$(GetSpecialFolder(sfWindowsFolder), 2)) +Debug.Print d.Path = Left$(GetSpecialFolder(sfWindowsFolder), 2) ' True +Debug.Print d.IsReady ' True +Debug.Print d.DriveType = DriveTypeConst.Fixed ' True +Debug.Print d.TotalSize > d.FreeSpace ' True +Debug.Print Len(d.SerialNumberHex) ' 9 +``` + +```tb check_build +Dim d As Drive +For Each d In GetDrives() + If d.IsReady Then + Debug.Print d.Path & " " & d.VolumeName & " " & d.FreeSpace + End If +Next +``` + +## A Drive asks Windows each time + +A **Drive** keeps only its root, such as `C:` or `\\server\share`. Every property asks Windows again, because free space, readiness and media can change, so there is no method to refresh it. Reading the label, the file system and the serial number makes three calls, although one would give all three. + +**Drives that are not ready.** A drive with no media, such as an empty optical drive or a card reader with no card, cannot be queried. The size, label, file system and serial number properties raise error 71 (*Disk not ready*) for it. Test [**IsReady**](#isready) first when that can happen. + +**Letter and share.** A **Drive** made from a network path has no letter and a share name, and a local drive has a letter and no share name: + +| The drive | DriveLetter | ShareName | +|---|---|---| +| a local drive, `C:` | `C` | an empty string | +| a share given by its network path | an empty string | `\\server\share` | + +## DriveTypeConst + +The type of a drive, for [**DriveType**](#drivetype). The enumeration is declared in the class, and its members are global: they are written `Fixed` or `DriveTypeConst.Fixed`. `Drive.Fixed` and `Drive.DriveTypeConst.Fixed` do not compile. Its values are those of the Scripting Runtime. They are one less than the numbers that Windows gives the same types, except for **UnknownType**. + +| Member | Value | Meaning | +|---|---|---| +| UnknownType | 0 | The type is not known, or the drive does not exist. | +| Removable | 1 | A removable disk, such as a USB disk or a floppy disk. | +| Fixed | 2 | A fixed disk. | +| Remote | 3 | A network drive. | +| CDRom | 4 | An optical drive. | +| RamDisk | 5 | A RAM disk. | + +In a project that references the Scripting Runtime as well, these names compile unqualified whichever library is listed first; see [Using the package with the Scripting Runtime](./#using-the-package-with-the-scripting-runtime). + +## Properties + +### AvailableSpace +{: .no_toc } + +The free bytes that the current user can use. + +Syntax: *object*.**AvailableSpace** **As LongLong** + +Counts the disk quota of the user, so on a volume with quotas it can be less than [**FreeSpace**](#freespace). This is the number to check before a write. + +### DriveLetter +{: .no_toc } + +The letter of the drive. + +Syntax: *object*.**DriveLetter** **As String** + +One uppercase letter. An empty string for a **Drive** that was made from a network path. + +### DriveType +{: .no_toc } + +The type of the drive. + +Syntax: *object*.**DriveType** **As DriveTypeConst** + +A [**DriveTypeConst**](#drivetypeconst) member. A drive that Windows cannot classify is **UnknownType**. + +### FileSystem +{: .no_toc } + +The name of the file system. + +Syntax: *object*.**FileSystem** **As String** + +`NTFS`, `FAT32`, `exFAT`, and so on. + +### FreeSpace +{: .no_toc } + +The free bytes on the volume. + +Syntax: *object*.**FreeSpace** **As LongLong** + +The free space of the whole volume, which a disk quota does not reduce; compare [**AvailableSpace**](#availablespace). + +### IsReady +{: .no_toc } + +Whether the drive can be queried. + +Syntax: *object*.**IsReady** **As Boolean** + +**True** when Windows answers a request for the free space of the drive. This raises no error, so it is the test to make before reading a size, a label or a serial number. + +### Path +{: .no_toc } + +The path of the drive. This is the default member, so an object used where a string is wanted gives its path. + +Syntax: *object*.**Path** **As String** + +The letter and a colon, `C:`, or `\\server\share`. There is no backslash at the end. + +### RootFolder +{: .no_toc } + +The root folder of the drive. + +Syntax: *object*.**RootFolder** **As Folder** + +Returns the [**Folder**](Folder) for the root of the drive, `C:\`. + +### SerialNumber +{: .no_toc } + +The serial number of the volume, as a signed **Long**. + +Syntax: *object*.**SerialNumber** **As Long** + +The serial number is an unsigned 32-bit value, and a **Long** is signed, so a number with its highest bit set is negative. [**SerialNumberHex**](#serialnumberhex) gives the usual form, and [**UnsignedSerialNumber**](#unsignedserialnumber) the number. + +### SerialNumberHex +{: .no_toc } + +The serial number of the volume, in the form that Windows Explorer shows. + +Syntax: *object*.**SerialNumberHex** **As String** + +Eight hexadecimal digits with a hyphen in the middle, `D4A1-B2C3`. + +### ShareName +{: .no_toc } + +The network path of the drive. + +Syntax: *object*.**ShareName** **As String** + +For a **Drive** made from a network path, the path itself. An empty string for a local drive. For a drive letter that is mapped to a network path, the property asks Windows for that path. + +### TotalSize +{: .no_toc } + +The size of the volume, in bytes. + +Syntax: *object*.**TotalSize** **As LongLong** + +### UnsignedSerialNumber +{: .no_toc } + +The serial number of the volume, as an unsigned number. + +Syntax: *object*.**UnsignedSerialNumber** **As LongLong** + +A value from 0 to 4,294,967,295. + +### VolumeName +{: .no_toc } + +The label of the volume. + +Syntax: *object*.**VolumeName** [ **=** *value* ] + +Reading returns the label, which is an empty string when the volume has none. Setting it changes the label of the volume. + +## See Also + +- [Drives](Drives) class -- the collection that **GetDrives** returns +- [Folder](Folder) class -- the root folder of a drive +- [FileSystemTools](FileSystemTools#getdrive) module -- **GetDrive**, **GetDrives** and **DriveExists** +- [ChDrive](../../../Modules/FileSystem/ChDrive) statement -- the VBA statement that changes the current drive diff --git a/docs/Reference/Contributed/tbFileSysTools/Drives.md b/docs/Reference/Contributed/tbFileSysTools/Drives.md new file mode 100644 index 000000000..f23c0a589 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/Drives.md @@ -0,0 +1,80 @@ +--- +title: Drives +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/Drives +has_toc: false +--- + +# Drives class +{: .no_toc } + +A **Drives** is a read-only collection of [**Drive**](Drive) objects: the logical drives of the computer. It counts them, returns one by letter or by position, and is enumerated with **For Each**. + +A **Drives** is not created with **New**. [**GetDrives**](FileSystemTools#getdrives) returns one, and [**FileSystemObject.Drives**](FileSystemObject#drives) is the same call. It holds the drives that existed when it was made: a USB disk that is plugged in afterwards is not in it, so call **GetDrives** again. Each **Drive** in it asks Windows when it is used. Drives that are not ready, such as an optical drive with no disc, are in the collection; check [**IsReady**](Drive#isready) before reading the size of one. + +```tb check_build +Dim ds As Drives, d As Drive +Set ds = GetDrives() +Debug.Print ds.Count +For Each d In ds + Debug.Print d.DriveLetter, d.IsReady +Next +``` + +```tb check_build check_run +Dim ds As Drives +Set ds = GetDrives() +Debug.Print ds.Count > 0 ' True +Debug.Print ds.Item("C").Path ' C: +Debug.Print ds(1).Path = ds.Item(1).Path ' True +``` + +```tb check_build check_run +Dim ds As Drives, d As Drive +Set ds = GetDrives() + +On Error Resume Next +Set d = ds("C:") +Debug.Print Err.Number ' 5 +Err.Clear +Set d = ds(0) +Debug.Print Err.Number ' 9 +Err.Clear +On Error GoTo 0 +``` + +## Properties + +### Count +{: .no_toc } + +The number of drives in the collection. + +Syntax: *object*.**Count** **As Long** + +### Item +{: .no_toc } + +One drive of the collection. This is the default member. + +Syntax: *object*.**Item** ( *letterOrIndex* ) **As Drive** + +*letterOrIndex* +: *required* A **Variant**: the drive letter alone, such as `"C"`, without a colon, or the position of the drive, counted from 1. + +The drives are in the order of their letters, and the letter is not case-sensitive. A letter that is not in the collection raises error 5, and so does `"C:"`, a letter with a colon. A position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). + +The Scripting Runtime accepts `"C:"`, raises error 68 (*Device unavailable*) for a letter that is not in use, and raises error 5 for a position. + +### NewEnum +{: .no_toc } + +The enumerator that **For Each** uses. It is hidden, and is not called from code. + +Syntax: *object*.**NewEnum** **As IUnknown** + +## See Also + +- [Drive](Drive) class -- the type of its items +- [FileSystemTools](FileSystemTools#getdrives) module -- **GetDrives** returns this collection +- [Files](Files) class and [Folders](Folders) class -- the other two collections diff --git a/docs/Reference/Contributed/tbFileSysTools/File.md b/docs/Reference/Contributed/tbFileSysTools/File.md new file mode 100644 index 000000000..73a6fec56 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/File.md @@ -0,0 +1,387 @@ +--- +title: File +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/File +has_toc: false +--- + +# File class +{: .no_toc } + +A **File** represents one file on disk: its name, size, dates and attributes, and operations to copy, move, rename and delete it, to open it as a [**TextStream**](TextStream) and to read its content. It has the members of the **File** of the Scripting Runtime, and adds [**Encoding**](#encoding), [**LineEnding**](#lineending), [**Normalize**](#normalize), [**Refresh**](#refresh), [**HasAttribute**](#hasattribute), [**SetAttribute**](#setattribute), [**ToString**](#tostring), [**ToStream**](#tostream), [**ExtensionName**](#extensionname) and [**Version**](#version). + +A **File** is not created with **New**. [**GetFile**](FileSystemTools#getfile) returns one, and so does the [**Files**](Files) collection of a [**Folder**](Folder#files). + +```tb check_build check_run +Dim work As String, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "hello", BuildPath(work, "Report.TXT") + +Set f = GetFile(BuildPath(work, "report.txt")) +Debug.Print f.Name ' Report.TXT +Debug.Print f.ExtensionName ' TXT +Debug.Print f.Size ' 5 +Debug.Print f.HasAttribute(faReadOnly) ' False + +f.SetAttribute faReadOnly, True +Debug.Print f.HasAttribute(faReadOnly) ' True +f.SetAttribute faReadOnly, False + +f.Name = "renamed.txt" +Debug.Print f.Name ' renamed.txt + +f.Delete +On Error Resume Next +Dim n As LongLong +n = f.Size +Debug.Print Err.Number ' 53 + +DeleteFolder work +``` + +## A File reads the disk each time + +A **File** keeps only the path of the file. Each member that reads a property looks at the file again, so the values are never out of date, and a file that was deleted or renamed by something else raises error 53 (*File not found*) at the next use. The name is returned with the letter case that the file system stores, whatever case was used to get the object. + +The price is that every read costs a call to Windows. Read a value once and keep it, rather than reading it in a loop. These cost more than the rest: + +- [**Encoding**](#encoding) reads the first 64 KB of the file. +- [**LineEnding**](#lineending) reads the whole file. +- [**Version**](#version) reads the version resource of the file. +- [**Type**](#type) asks the Windows shell. + +The members that change the file ([**Move**](#move), [**Rename**](#rename), [**Name**](#name) and [**Delete**](#delete)) are those of [**FileSystemTools**](FileSystemTools), with the object's path as the source: their errors are described there. **Move** and **Rename** point the object at the new path, so it stays usable. [**Copy**](#copy) leaves the object as it is and does not return the copy; call [**GetFile**](FileSystemTools#getfile) on the destination for that. After **Delete**, every member that reads raises error 53. + +## Properties + +### Attributes +{: .no_toc } + +The attributes of the file. + +Syntax: *object*.**Attributes** [ **=** *value* ] + +Reading returns a [**FileAttribute**](FileSystemTools#fileattribute) combination of every attribute that the file system reports, including those that cannot be set, such as **faCompressed** and **faReparsePoint**. Setting replaces the settable attributes with *value*; the other bits are dropped without an error, so reading the value and writing it back changes nothing. **faNormal** clears all settable attributes, also when it is combined with others. **faNoChange** does nothing. To change one attribute and leave the rest, use [**SetAttribute**](#setattribute). + +Raises error 53 when the file does not exist. + +### DateCreated +{: .no_toc } + +The date and time at which the file was created. + +Syntax: *object*.**DateCreated** **As Date** + +The value is in local time and has a resolution of one second. It is the date 0 (30 December 1899) when the file system does not hold a value. + +### DateLastAccessed +{: .no_toc } + +The date and time at which the file was last read or written. + +Syntax: *object*.**DateLastAccessed** **As Date** + +Local time, to the second. Windows can be set to update this value late or not at all. It is the date 0 when the file system holds no value. + +### DateLastModified +{: .no_toc } + +The date and time at which the content of the file was last changed. + +Syntax: *object*.**DateLastModified** **As Date** + +Local time, to the second. + +### Drive +{: .no_toc } + +The drive that holds the file. + +Syntax: *object*.**Drive** **As Drive** + +Returns the [**Drive**](Drive) of the path, for a network path the share. + +### Encoding +{: .no_toc } + +The text encoding of the file. + +Syntax: *object*.**Encoding** [ ( *sampleBytes* ) ] **As TextEncoding** + +*sampleBytes* +: *optional* A **Long**, the number of bytes at the start of the file to examine. Default: 65536. + +The same as [**GetFileEncoding**](FileSystemTools#getfileencoding): a [**TextEncoding**](FileSystemTools#textencoding) member, negative when the file starts with a BOM, and **encUnknown** for an empty or binary file. The file is read on each call. + +### ExtensionName +{: .no_toc } + +The extension of the file name. + +Syntax: *object*.**ExtensionName** **As String** + +What follows the last dot of the name, without the dot, with the letter case that the file system stores. An empty string when the name has no dot. See [**GetExtensionName**](FileSystemTools#getextensionname). + +### HasAttribute +{: .no_toc } + +Whether an attribute is set. + +Syntax: *object*.**HasAttribute** ( *attr* ) **As Boolean** + +*attr* +: *required* A [**FileAttribute**](FileSystemTools#fileattribute) member, any attribute including those that cannot be set. + +**True** when the attribute is set. The test is for any bit: with a combination such as `faHidden Or faSystem` it is **True** when either is set. Test one attribute at a time for "all of them". + +### LineEnding +{: .no_toc } + +The line-ending style of the file. + +Syntax: *object*.**LineEnding** **As NewlineType** + +The same as [**GetFileLineEnding**](FileSystemTools#getfilelineending) with the encoding of [**Encoding**](#encoding): a [**NewlineType**](FileSystemTools#newlinetype) member. The whole file is read on each call, so assign the value once. Raises error 5 for a binary file and error 7 for a file of more than 2 GB. + +### Name +{: .no_toc } + +The name of the file. + +Syntax: *object*.**Name** [ **=** *newName* ] + +The name, without its folder, with the letter case that the file system stores. Setting it renames the file as [**Rename**](#rename) does. + +### ParentFolder +{: .no_toc } + +The folder that holds the file. + +Syntax: *object*.**ParentFolder** **As Folder** + +Returns the [**Folder**](Folder). A file always has one. + +### Path +{: .no_toc } + +The full path of the file. This is the default member, so an object used where a string is wanted gives its path. + +Syntax: *object*.**Path** **As String** + +The folder part has the spelling that was used to get the object, and the name has the letter case that the file system stores. + +### ShortName +{: .no_toc } + +The 8.3 short name of the file. + +Syntax: *object*.**ShortName** **As String** + +The short name, or the ordinary name when the file has none. Compare the result with [**Name**](#name) to see whether a short name was found. + +### ShortPath +{: .no_toc } + +The 8.3 short path of the file. + +Syntax: *object*.**ShortPath** **As String** + +The short path, or the full path unchanged when none is available, for the reasons given for [**ShortName**](#shortname). + +### Size +{: .no_toc } + +The size of the file, in bytes. + +Syntax: *object*.**Size** **As LongLong** + +Exact for a file of any size, 4 GB and more included. + +### Type +{: .no_toc } + +The type description of the file. + +Syntax: *object*.**Type** **As String** + +What [**GetFileType**](FileSystemTools#getfiletype) returns, such as `Application` for an `.exe` file. The Scripting Runtime's **Type** returns the same string. + +### Version +{: .no_toc } + +The version of an executable file. + +Syntax: *object*.**Version** [ ( *source* ) ] **As String** + +*source* +: *optional* A [**FileVersionSource**](FileSystemTools#fileversionsource) member. Default: **fvFixed**. + +The same as [**GetFileVersion**](FileSystemTools#getfileversion): an empty string for a file that has no version resource, which is most files. + +## Methods + +### Copy +{: .no_toc } + +Copies the file. + +Syntax: *object*.**Copy** ( *destination* [, *overwrite* ] ) + +*destination* +: *required* A **String**, the new file or an existing folder. + +*overwrite* +: *optional* A **Boolean**. **True** (default) replaces an existing file. + +See [**CopyFile**](FileSystemTools#copyfile) for the rules and errors. + +### Delete +{: .no_toc } + +Deletes the file. + +Syntax: *object*.**Delete** ( [ *force* ] ) + +*force* +: *optional* A **Boolean**. **True** clears the read-only, hidden and system attributes first. Default: **False**. + +See [**DeleteFile**](FileSystemTools#deletefile). The object cannot be used afterwards: its members raise error 53. + +### Move +{: .no_toc } + +Moves the file. + +Syntax: *object*.**Move** ( *destination* ) + +*destination* +: *required* A **String**, the new full name of the file or an existing folder. + +See [**MoveFile**](FileSystemTools#movefile) for the rules and errors. The object then names the file at its new place. + +### Normalize +{: .no_toc } + +Rewrites a text file with a chosen encoding and line ending. + +Syntax: *object*.**Normalize** ( [ *toEncoding* [, *toLineEnding* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** + +The arguments, their defaults and the result are those of [**NormalizeTextFile**](FileSystemTools#normalizetextfile), where *toLineEnding* is called *toNewLine*: **encUtf8**, **nlWindows**, **encAutoDetect**, **faNoChange** and **True**. Returns **True** when the file was changed. The object reads the file again afterwards. + +### OpenAsTextStream +{: .no_toc } + +Opens the file as a text stream. + +Syntax: *object*.**OpenAsTextStream** ( [ *ioMode* [, *encoding* [, *writeModeLineEnding* ] ] ] ) **As TextStream** + +*ioMode* +: *optional* An [**IOMode**](FileSystemTools#iomode) member. Default: **ForReading**. + +*encoding* +: *optional* A [**TextEncoding**](FileSystemTools#textencoding) member. Default: **encAutoDetect**. + +*writeModeLineEnding* +: *optional* A [**NewlineType**](FileSystemTools#newlinetype) member for [**WriteLine**](TextStream#writeline). Default: **nlWindows**. + +Returns a [**TextStream**](TextStream); the rules are those of [**OpenTextFile**](FileSystemTools#opentextfile) with *create* **False**. + +> [!WARNING] +> **ForWriting** empties the file when the stream is opened. Use **ForAppending** to add to it. + +### Refresh +{: .no_toc } + +Reads the attributes, size and dates again. + +Syntax: *object*.**Refresh** + +Every member that reads a property already does this, so the method is needed only to test that the file still exists. Raises error 53 when it does not. + +### Rename +{: .no_toc } + +Renames the file. + +Syntax: *object*.**Rename** ( *newName* ) + +*newName* +: *required* A **String**, a bare name or a path to the same folder. + +See [**Rename**](FileSystemTools#rename) for the rules and errors. The object then names the file under its new name. + +### SetAttribute +{: .no_toc } + +Sets or clears one attribute. + +Syntax: *object*.**SetAttribute** ( *attr*, *state* ) + +*attr* +: *required* A [**FileAttribute**](FileSystemTools#fileattribute) member. Only a settable attribute has an effect: **faReadOnly**, **faHidden**, **faSystem**, **faArchive**, **faTemporary** and **faNotContentIndexed**. + +*state* +: *required* A **Boolean**. **True** sets the attribute and **False** clears it. The other attributes stay as they are. + +> [!WARNING] +> In version 1.1.1.0, **SetAttribute** sets nothing, and raises no error, on a file that has no attribute at all, because [**Attributes**](#attributes) then reports **faNormal**, and **faNormal** wins over the attribute being set. A new file has the archive attribute, so it is a file from which the program cleared every attribute that this applies to. To give such a file an attribute, assign [**Attributes**](#attributes). The same method of a [**Folder**](Folder#setattribute) is not affected. + +```tb check_build check_run +Dim work As String, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "x", BuildPath(work, "a.txt") +Set f = GetFile(BuildPath(work, "a.txt")) + +Debug.Print f.Attributes ' 32 +f.SetAttribute faHidden, True +Debug.Print f.Attributes ' 34 +f.SetAttribute faHidden, False +f.SetAttribute faArchive, False +Debug.Print f.Attributes ' 128 +f.SetAttribute faHidden, True ' sets nothing +Debug.Print f.Attributes ' 128 +f.Attributes = faHidden ' this works +Debug.Print f.Attributes ' 2 + +DeleteFolder work, True +``` + +### ToStream +{: .no_toc } + +Reads the file into a byte array. + +Syntax: *object*.**ToStream** ( [ *maxBytesToRead* ] ) **As Byte()** + +*maxBytesToRead* +: *optional* A **Long**. A value of 0 or more reads at most that many bytes from the start. Default: -1, the whole file. + +See [**ReadStream**](FileSystemTools#readstream). The array is not dimensioned for an empty file; test it with [**IsArrayInitialized**](../../../Modules/Information/IsArrayInitialized). + +### ToString +{: .no_toc } + +Reads the file as text. + +Syntax: *object*.**ToString** ( [ *encoding* [, *lineEnding* ] ] ) **As String** + +*encoding* +: *optional* A [**TextEncoding**](FileSystemTools#textencoding) member. Default: **encAutoDetect**. + +*lineEnding* +: *optional* A [**NewlineType**](FileSystemTools#newlinetype) member to convert the line endings to. Default: **nlNoChange**. + +See [**TextFileToString**](FileSystemTools#textfiletostring). Raises error 5 for a binary file and error 7 for a file of more than 2 GB. The text is read in one piece; for a large file, open a stream with [**OpenAsTextStream**](#openastextstream). + +## See Also + +- [Folder](Folder) class -- the folder that holds a file, and the collection of its files +- [Files](Files) class -- the collection of the files of a folder +- [FileSystemTools](FileSystemTools#getfile) module -- **GetFile** returns a **File** +- [TextStream](TextStream) class -- the stream that **OpenAsTextStream** returns +- [FileLen](../../../Modules/FileSystem/FileLen) function -- the VBA function that returns the size of a file +- [GetAttr](../../../Modules/FileSystem/GetAttr) function -- the VBA function that returns the attributes of a file +- [SetAttr](../../../Modules/FileSystem/SetAttr) statement -- the VBA statement that sets the attributes of a file +- [FileDateTime](../../../Modules/FileSystem/FileDateTime) function -- the VBA function that returns the date of a file diff --git a/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md b/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md new file mode 100644 index 000000000..83333048c --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/FileSystemObject.md @@ -0,0 +1,382 @@ +--- +title: FileSystemObject +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/FileSystemObject +has_toc: false +--- + +# FileSystemObject class +{: .no_toc } + +**FileSystemObject** is a class whose members are the procedures of the [**FileSystemTools**](FileSystemTools) module, for code written for the **FileSystemObject** of the Scripting Runtime and for hosts that need an object and not a module. Each member calls the procedure of the same name in the module, with the same defaults, so the entries of [**FileSystemTools**](FileSystemTools) describe them in full. This page gives each member's syntax and says where it differs from the module. + +Code written in twinBASIC can call the module directly, with no object. The class exists so that a line such as `Dim fso As New FileSystemObject` and the calls that follow it keep working when the Scripting Runtime is replaced. + +```tb check_build check_run +Dim fso As New FileSystemObject +Dim work As String +work = fso.BuildPath(Environ$("TEMP"), fso.GetTempName("fst", "")) +fso.CreateFolder work + +Dim ts As TextStream +Set ts = fso.CreateTextFile(fso.BuildPath(work, "a.txt")) +ts.WriteLine "hello" +ts.Close + +Debug.Print fso.FileExists(fso.BuildPath(work, "a.txt")) ' True +Debug.Print fso.GetFile(fso.BuildPath(work, "a.txt")).Size ' 7 +Debug.Print fso.Drives.Count > 0 ' True + +fso.DeleteFolder work +``` + +## Differences from the module + +- Some arguments have the names that the Scripting Runtime gives them, so named arguments in ported code keep working: *fileSpec*, *folderSpec* and *driveSpec* where the module has *filePath*, *folderPath* and *drivePath*; *path* in **CreateFolder**; *fileName* in **CreateTextFile**, **GetFileVersion** and **OpenTextFile**. The syntax lines below show the names of the class. +- [**MoveFile**](#movefile), [**MoveFolder**](#movefolder) and [**Rename**](#rename) are procedures that return nothing, as in the Scripting Runtime. The module's return the full path of the result. +- [**Drives**](#drives) is the module's [**GetDrives**](FileSystemTools#getdrives). +- Nothing else differs. + +## Porting code from the Scripting Runtime + +Replace `CreateObject("Scripting.FileSystemObject")` with `New FileSystemObject`, and check these calls. The package differs from the Scripting Runtime in them, and a program compiles with the old code but does not do the same: + +- [**GetSpecialFolder**](#getspecialfolder) returns the path as a **String**, not a **Folder**. Code that reads `.Path` from the result does not compile (the sample after this list). +- The third argument of [**CreateTextFile**](#createtextfile) and of [**Folder.CreateTextFile**](Folder#createtextfile) is an encoding. The Scripting Runtime's *Unicode* **Boolean** **True** is -1 here, which writes UTF-8, and not UTF-16. The default is UTF-8 without a BOM, where the Scripting Runtime writes ANSI. +- The fourth argument of [**OpenTextFile**](#opentextfile) and the second of [**File.OpenAsTextStream**](File#openastextstream) are an encoding, not a **Tristate**. **TristateTrue** is -1, which is **encAutoDetect**. +- The other differences, such as the added members and the changed defaults, are listed under [Differences from the Scripting Runtime](./#differences-from-the-scripting-runtime). + +```tb check_build expect-error=TB5027 +Dim fso As New FileSystemObject +Debug.Print fso.GetSpecialFolder(sfTemporaryFolder).Path +``` + +## Files and folders + +### CopyFile +{: .no_toc } + +Syntax: *object*.**CopyFile** ( *source*, *destination* [, *overwrite* ] ) + +See [**CopyFile**](FileSystemTools#copyfile). + +### CopyFolder +{: .no_toc } + +Syntax: *object*.**CopyFolder** ( *source*, *destination* [, *overwrite* ] ) + +See [**CopyFolder**](FileSystemTools#copyfolder). + +### CreateFolder +{: .no_toc } + +Syntax: *object*.**CreateFolder** ( *path* [, *createIntermediates* ] ) **As Folder** + +*path* is *folderPath* in the module. See [**CreateFolder**](FileSystemTools#createfolder). + +### DeleteFile +{: .no_toc } + +Syntax: *object*.**DeleteFile** ( *fileSpec* [, *force* [, *ignoreMissing* ] ] ) + +*fileSpec* is *filePath* in the module. See [**DeleteFile**](FileSystemTools#deletefile). + +### DeleteFolder +{: .no_toc } + +Syntax: *object*.**DeleteFolder** ( *folderSpec* [, *force* [, *ignoreMissing* ] ] ) + +*folderSpec* is *folderPath* in the module. See [**DeleteFolder**](FileSystemTools#deletefolder), including its warning about links. + +### GetFilePaths +{: .no_toc } + +Syntax: *object*.**GetFilePaths** ( *folderPath* [, *pattern* [, *includeHidden* [, *includeSystem* [, *recursive* ] ] ] ] ) **As Collection** + +See [**GetFilePaths**](FileSystemTools#getfilepaths). + +### MoveFile +{: .no_toc } + +Syntax: *object*.**MoveFile** ( *source*, *destination* ) + +A procedure, with no result. See [**MoveFile**](FileSystemTools#movefile). + +### MoveFolder +{: .no_toc } + +Syntax: *object*.**MoveFolder** ( *source*, *destination* ) + +A procedure, with no result. See [**MoveFolder**](FileSystemTools#movefolder). + +### Rename +{: .no_toc } + +Syntax: *object*.**Rename** ( *source*, *newName* ) + +A procedure, with no result. See [**Rename**](FileSystemTools#rename). + +## Paths + +### BuildPath +{: .no_toc } + +Syntax: *object*.**BuildPath** ( *path*, *name* ) **As String** + +See [**BuildPath**](FileSystemTools#buildpath). + +### CleanFileName +{: .no_toc } + +Syntax: *object*.**CleanFileName** ( *fileName* [, *replaceChar* ] ) **As String** + +See [**CleanFileName**](FileSystemTools#cleanfilename). + +### GetAbsolutePathName +{: .no_toc } + +Syntax: *object*.**GetAbsolutePathName** ( *path* [, *baseDirectory* [, *useLongPathPrefix* ] ] ) **As String** + +See [**GetAbsolutePathName**](FileSystemTools#getabsolutepathname). + +### GetBaseName +{: .no_toc } + +Syntax: *object*.**GetBaseName** ( *path* ) **As String** + +See [**GetBaseName**](FileSystemTools#getbasename). + +### GetDriveName +{: .no_toc } + +Syntax: *object*.**GetDriveName** ( *path* ) **As String** + +See [**GetDriveName**](FileSystemTools#getdrivename). + +### GetExtensionName +{: .no_toc } + +Syntax: *object*.**GetExtensionName** ( *path* ) **As String** + +See [**GetExtensionName**](FileSystemTools#getextensionname). + +### GetFileName +{: .no_toc } + +Syntax: *object*.**GetFileName** ( *path* ) **As String** + +See [**GetFileName**](FileSystemTools#getfilename). + +### GetParentFolderName +{: .no_toc } + +Syntax: *object*.**GetParentFolderName** ( *path* ) **As String** + +See [**GetParentFolderName**](FileSystemTools#getparentfoldername). + +### GetRelativePath +{: .no_toc } + +Syntax: *object*.**GetRelativePath** ( *basePath*, *targetPath* [, *basePathIsFolder* [, *trimLeadingDotSlash* ] ] ) **As String** + +See [**GetRelativePath**](FileSystemTools#getrelativepath). + +### GetTempName +{: .no_toc } + +Syntax: *object*.**GetTempName** ( [ *prefix* [, *extension* [, *inFolder* ] ] ] ) **As String** + +See [**GetTempName**](FileSystemTools#gettempname). + +## Existence + +### DriveExists +{: .no_toc } + +Syntax: *object*.**DriveExists** ( *driveSpec* ) **As Boolean** + +*driveSpec* is *drivePath* in the module. See [**DriveExists**](FileSystemTools#driveexists). + +### FileExists +{: .no_toc } + +Syntax: *object*.**FileExists** ( *fileSpec* ) **As Boolean** + +*fileSpec* is *filePath* in the module. See [**FileExists**](FileSystemTools#fileexists). + +### FolderExists +{: .no_toc } + +Syntax: *object*.**FolderExists** ( *folderSpec* ) **As Boolean** + +*folderSpec* is *folderPath* in the module. See [**FolderExists**](FileSystemTools#folderexists). + +## Current directory and special folders + +### GetCurrentDir +{: .no_toc } + +Syntax: *object*.**GetCurrentDir** ( ) **As String** + +See [**GetCurrentDir**](FileSystemTools#getcurrentdir). + +### GetSpecialFolder +{: .no_toc } + +Syntax: *object*.**GetSpecialFolder** ( *specialFolder* ) **As String** + +Returns a path, not a **Folder** object as the Scripting Runtime does. See [**GetSpecialFolder**](FileSystemTools#getspecialfolder). + +### SetCurrentDir +{: .no_toc } + +Syntax: *object*.**SetCurrentDir** ( *folderPath* ) + +See [**SetCurrentDir**](FileSystemTools#setcurrentdir). + +## File information + +### GetFileEncoding +{: .no_toc } + +Syntax: *object*.**GetFileEncoding** ( *filePath* [, *sampleBytes* ] ) **As TextEncoding** + +See [**GetFileEncoding**](FileSystemTools#getfileencoding). + +### GetFileLineEnding +{: .no_toc } + +Syntax: *object*.**GetFileLineEnding** ( *filePath* [, *encoding* ] ) **As NewlineType** + +See [**GetFileLineEnding**](FileSystemTools#getfilelineending). + +### GetFileType +{: .no_toc } + +Syntax: *object*.**GetFileType** ( *filePath* ) **As String** + +See [**GetFileType**](FileSystemTools#getfiletype). + +### GetFileVersion +{: .no_toc } + +Syntax: *object*.**GetFileVersion** ( *fileName* [, *source* ] ) **As String** + +*fileName* is *filePath* in the module. See [**GetFileVersion**](FileSystemTools#getfileversion). + +## Objects + +### Drives +{: .no_toc } + +Syntax: *object*.**Drives** ( ) **As Drives** + +Returns the [**Drives**](Drives) collection of all drives. It is a function, called with or without parentheses: `fso.Drives.Count` and `For Each d In fso.Drives` both work. In the module it is [**GetDrives**](FileSystemTools#getdrives). + +### GetDrive +{: .no_toc } + +Syntax: *object*.**GetDrive** ( *driveSpec* ) **As Drive** + +*driveSpec* is *drivePath* in the module. See [**GetDrive**](FileSystemTools#getdrive). + +### GetFile +{: .no_toc } + +Syntax: *object*.**GetFile** ( *filePath* ) **As File** + +See [**GetFile**](FileSystemTools#getfile). + +### GetFolder +{: .no_toc } + +Syntax: *object*.**GetFolder** ( *folderPath* ) **As Folder** + +See [**GetFolder**](FileSystemTools#getfolder). + +## Whole files + +### ArrayToTextFile +{: .no_toc } + +Syntax: *object*.**ArrayToTextFile** ( *lines*(), *filePath* [, *encoding* [, *lineEnding* ] ] ) + +See [**ArrayToTextFile**](FileSystemTools#arraytotextfile). + +### MergeTextFiles +{: .no_toc } + +Syntax: *object*.**MergeTextFiles** ( *firstFilePath*, *secondFilePath* [, *outputFilePath* [, *insertBlankLineBetween* [, *firstFileEncoding* [, *secondFileEncoding* ] ] ] ] ) + +See [**MergeTextFiles**](FileSystemTools#mergetextfiles). + +### NormalizeTextFile +{: .no_toc } + +Syntax: *object*.**NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** + +See [**NormalizeTextFile**](FileSystemTools#normalizetextfile). + +### ReadStream +{: .no_toc } + +Syntax: *object*.**ReadStream** ( *filePath*, *stream*() [, *maxBytesToRead* ] ) + +See [**ReadStream**](FileSystemTools#readstream). + +### StringToTextFile +{: .no_toc } + +Syntax: *object*.**StringToTextFile** ( *text*, *filePath* [, *encoding* [, *outLineEnding* ] ] ) + +See [**StringToTextFile**](FileSystemTools#stringtotextfile). + +### TextFileToArray +{: .no_toc } + +Syntax: *object*.**TextFileToArray** ( *filePath* [, *encoding* ] ) **As String()** + +See [**TextFileToArray**](FileSystemTools#textfiletoarray). + +### TextFileToString +{: .no_toc } + +Syntax: *object*.**TextFileToString** ( *filePath* [, *encoding* [, *outLineEnding* ] ] ) **As String** + +See [**TextFileToString**](FileSystemTools#textfiletostring). + +### WriteStream +{: .no_toc } + +Syntax: *object*.**WriteStream** ( *stream*(), *filePath* ) + +See [**WriteStream**](FileSystemTools#writestream). + +## Text streams + +### CreateTextFile +{: .no_toc } + +Syntax: *object*.**CreateTextFile** ( *fileName* [, *overwrite* [, *encoding* [, *lineEnding* ] ] ] ) **As TextStream** + +*fileName* is *filePath* in the module. The third argument is an encoding, not the Scripting Runtime's *Unicode* **Boolean**. See [**CreateTextFile**](FileSystemTools#createtextfile). + +### GetStandardStream +{: .no_toc } + +Syntax: *object*.**GetStandardStream** ( *standardStreamType* [, *unicode* ] ) **As TextStream** + +See [**GetStandardStream**](FileSystemTools#getstandardstream). + +### OpenTextFile +{: .no_toc } + +Syntax: *object*.**OpenTextFile** ( *fileName* [, *ioMode* [, *create* [, *encoding* [, *writeModeLineEnding* ] ] ] ] ) **As TextStream** + +*fileName* is *filePath* in the module. The fourth argument is an encoding, not a **Tristate**. See [**OpenTextFile**](FileSystemTools#opentextfile). + +## See Also + +- [FileSystemTools](FileSystemTools) module -- the procedures in full, and the enumerations +- [File](File) class, [Folder](Folder) class, [Drive](Drive) class and [TextStream](TextStream) class -- the objects that the members return +- [tbFileSysTools](./) package -- how the package differs from the Scripting Runtime's **FileSystemObject** diff --git a/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md b/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md new file mode 100644 index 000000000..fe3f182ad --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/FileSystemTools.md @@ -0,0 +1,1487 @@ +--- +title: FileSystemTools +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/FileSystemTools +has_toc: false +--- + +# FileSystemTools module +{: .no_toc } + +**FileSystemTools** is the standard module that holds the code of the **tbFileSysTools** package: path functions, file and folder operations, whole-file text and byte input and output, and the functions that return [**File**](File), [**Folder**](Folder), [**Drive**](Drive) and [**TextStream**](TextStream) objects. A project that references the package calls its procedures by name, with no object to create. The [**FileSystemObject**](FileSystemObject) class has the same procedures as members of a COM object, for code that was written for the Scripting Runtime. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +Dim path As String +path = BuildPath(work, "notes.txt") + +' Write a string as UTF-8 with a byte-order mark, then ask what the file holds. +StringToTextFile "caf" & ChrW$(233) & vbCrLf & "ok", path, encUtf8Bom +Debug.Print GetFileEncoding(path) = encUtf8Bom ' True +Debug.Print GetFileLineEnding(path) = nlWindows ' True +Debug.Print Len(TextFileToString(path)) ' 8 + +DeleteFolder work +``` + +The procedures, by purpose: + +- Paths: [BuildPath](#buildpath), [CleanFileName](#cleanfilename), [GetAbsolutePathName](#getabsolutepathname), [GetBaseName](#getbasename), [GetDriveName](#getdrivename), [GetExtensionName](#getextensionname), [GetFileName](#getfilename), [GetParentFolderName](#getparentfoldername), [GetRelativePath](#getrelativepath), [GetTempName](#gettempname) +- Existence: [DriveExists](#driveexists), [FileExists](#fileexists), [FolderExists](#folderexists) +- Files and folders: [CopyFile](#copyfile), [CopyFolder](#copyfolder), [CreateFolder](#createfolder), [DeleteFile](#deletefile), [DeleteFolder](#deletefolder), [GetFilePaths](#getfilepaths), [MoveFile](#movefile), [MoveFolder](#movefolder), [Rename](#rename) +- Current directory and special folders: [GetCurrentDir](#getcurrentdir), [GetSpecialFolder](#getspecialfolder), [SetCurrentDir](#setcurrentdir) +- File information: [GetFileEncoding](#getfileencoding), [GetFileLineEnding](#getfilelineending), [GetFileType](#getfiletype), [GetFileVersion](#getfileversion) +- Objects: [GetDrive](#getdrive), [GetDrives](#getdrives), [GetFile](#getfile), [GetFolder](#getfolder) +- Whole files: [ArrayToTextFile](#arraytotextfile), [MergeTextFiles](#mergetextfiles), [NormalizeTextFile](#normalizetextfile), [ReadStream](#readstream), [StringToTextFile](#stringtotextfile), [TextFileToArray](#textfiletoarray), [TextFileToString](#textfiletostring), [WriteStream](#writestream) +- Text streams: [CreateTextFile](#createtextfile), [GetStandardStream](#getstandardstream), [OpenTextFile](#opentextfile) + +The enumerations that the procedures take are described [at the end of the page](#enumerations): [TextEncoding](#textencoding), [NewlineType](#newlinetype), [IOMode](#iomode), [SpecialFolderConst](#specialfolderconst), [FileAttribute](#fileattribute), [FileVersionSource](#fileversionsource) and [StandardStreamTypes](#standardstreamtypes). + +## Conventions + +These rules apply to every procedure on the page. Each entry states only what departs from them. + +**Paths.** + +- A forward slash in a path is read as a backslash. +- A relative path is resolved against the current directory of the process, which [**GetCurrentDir**](#getcurrentdir) returns. A procedure that compares two paths, or uses one path more than once, resolves it first, so `a.txt` and `.\a.txt` name the same file to it. +- A path longer than 260 characters works in most procedures, among them [**CreateFolder**](#createfolder), [**FileExists**](#fileexists), [**StringToTextFile**](#stringtotextfile), [**TextFileToString**](#textfiletostring), [**CopyFile**](#copyfile), [**MoveFile**](#movefile), [**Rename**](#rename), [**DeleteFile**](#deletefile), [**DeleteFolder**](#deletefolder), [**GetFile**](#getfile) and [**GetFilePaths**](#getfilepaths). The Scripting Runtime does not accept such a path: its **FileExists** and **FolderExists** return **False** for it, **GetFile** raises error 53 and **OpenTextFile** raises error 76. +- These stop at the limit. [**GetFileVersion**](#getfileversion) returns an empty string, and [**File.ShortPath**](File#shortpath) returns the path as it is. [**SetCurrentDir**](#setcurrentdir) raises error 75. A path with a wildcard in it is not found: [**DeleteFile**](#deletefile) and [**CopyFile**](#copyfile) raise error 53 for `deep\*.dll`, where `deep` is a folder with a long path. [**Folder.Files**](Folder#files), [**Folder.SubFolders**](Folder#subfolders) and [**Folder.Size**](Folder#size) raise error 76 for a folder whose own path has 258 characters or more. [**GetRelativePath**](#getrelativepath) can return a wrong path. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +' A folder 12 levels deep, with a path of more than 300 characters. +Dim deep As String, i As Long +deep = work +For i = 1 To 12 + deep = BuildPath(deep, String$(25, "d")) +Next +CreateFolder deep, True + +Dim path As String +path = BuildPath(deep, "note.txt") +StringToTextFile "hello", path +Debug.Print Len(path) > 300 ' True +Debug.Print FileExists(path) ' True +Debug.Print TextFileToString(path) ' hello + +DeleteFolder work +``` + +**Wildcards.** [**CopyFile**](#copyfile), [**CopyFolder**](#copyfolder), [**DeleteFile**](#deletefile), [**DeleteFolder**](#deletefolder), [**MoveFile**](#movefile) and [**MoveFolder**](#movefolder) accept `*` and `?` in the last component of the path that names what to act on. A wildcard in a destination is error 5. A pattern that matches nothing is an error, as described for each procedure. A failure part-way through a wildcard operation leaves the earlier matches changed; the sample under [**DeleteFile**](#deletefile) shows it. + +> [!WARNING] +> Windows matches a pattern against the 8.3 short name of a file as well as against its name. On a volume that creates short names, `*.txt` also matches `a.txtx`, whose short name ends in `.TXT`. In version 1.1.1.0 [**GetFilePaths**](#getfilepaths) lists such a file for `*.txt`, and [**DeleteFile**](#deletefile) deletes it. The Scripting Runtime does the same. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "1", BuildPath(work, "a.txt") +StringToTextFile "2", BuildPath(work, "a.txtx") + +' The 8.3 name of a.txtx is different from its name, and it ends in .TXT. +Debug.Print GetFile(BuildPath(work, "a.txtx")).ShortName <> "a.txtx" ' True +Debug.Print GetFilePaths(work, "*.txt").Count ' 2 +DeleteFile BuildPath(work, "*.txt") +Debug.Print GetFilePaths(work).Count ' 0 + +DeleteFolder work +``` + +**Errors.** A procedure raises a run-time error with the number VB6 uses for the same failure. The error numbers, and the Windows failures that map to them, are in [Errors](./#errors), which also lists the errors that version 1.1.1.0 raises where the Scripting Runtime raises another. **Err.Source** is the name of the operation, such as `CopyFile`, and **Err.Description** names the path; the errors of [**OpenTextFile**](#opentextfile) and its relatives have an empty **Err.Source** and only the standard text of the number. + +**Encodings.** A text procedure takes a [**TextEncoding**](#textencoding) member. Its value is a Windows code page; a negative value is the same code page with a byte-order mark (BOM). On reading, **encAutoDetect** means: find the encoding from the first bytes of the file. On writing, **encAutoDetect** means **encUtf8**. A BOM that the file holds always decides the encoding on reading, whatever the argument says. + +### Junctions and symbolic links +{: .no_toc } + +A directory junction or symbolic link is a folder that stands for another folder. In version 1.1.1.0, [**CopyFolder**](#copyfolder), [**DeleteFolder**](#deletefolder) and [**GetFilePaths**](#getfilepaths) with *recursive* enter one like an ordinary folder. [**Folder.Size**](Folder#size) does not: it skips the link, so each file is counted once, where it is stored. The Scripting Runtime enters a link when it adds up the size, so it counts a target twice when the target is in the same tree. + +The sample builds a tree with a link to one of its own folders, and a second tree with a link to a folder outside it. **DeleteFolder** of the second tree deletes the file in the outside folder. + +```tb check_build check_run +Dim work As String, tree As String, sh As Object +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +tree = BuildPath(work, "tree") +CreateFolder BuildPath(tree, "sub"), True +StringToTextFile String$(500, "x"), BuildPath(tree, "file1.txt") +StringToTextFile String$(1000, "y"), BuildPath(tree, "sub\file2.txt") +Set sh = CreateObject("WScript.Shell") +sh.Run "cmd /c mklink /J """ & BuildPath(tree, "link") & """ """ & BuildPath(tree, "sub") & """ >nul", 0, True + +Debug.Print GetFolder(tree).Size ' 1500 +Debug.Print CreateObject("Scripting.FileSystemObject").GetFolder(tree).Size ' 2500 +Debug.Print GetFilePaths(tree, , , , True).Count ' 3 +CopyFolder tree, BuildPath(work, "copy") +Debug.Print FileExists(BuildPath(work, "copy\link\file2.txt")) ' True +sh.Run "cmd /c rmdir """ & BuildPath(tree, "link") & """", 0, True + +Dim target As String, tree2 As String +target = BuildPath(work, "target") +tree2 = BuildPath(work, "tree2") +CreateFolder target +CreateFolder tree2 +StringToTextFile "keep me", BuildPath(target, "keep.txt") +sh.Run "cmd /c mklink /J """ & BuildPath(tree2, "link") & """ """ & target & """ >nul", 0, True +DeleteFolder tree2, True +Debug.Print FolderExists(tree2) ' False +Debug.Print FolderExists(target) ' True +Debug.Print FileExists(BuildPath(target, "keep.txt")) ' False + +DeleteFolder work +``` + +## Paths + +### BuildPath +{: .no_toc } + +Joins a folder path and a name. + +Syntax: **BuildPath** ( *path*, *name* ) **As String** + +*path* +: *required* A **String** holding a folder path. It can be empty. + +*name* +: *required* A **String** holding the name of a file or folder, or a relative path. + +Returns *path* and *name* joined by one backslash. Nothing is read from the disk, and neither string has to name anything that exists. + +- An empty *path* returns *name*, and an empty *name* returns *path*. +- A backslash that ends *path* or starts *name* is not doubled. +- A *path* that ends in a colon, a drive letter such as `C:`, is joined without a backslash: `BuildPath("C:", "foo")` returns `C:foo`, as the Scripting Runtime does. +- A forward slash in either argument becomes a backslash: `BuildPath("C:\a", "b/c")` returns `C:\a\b\c`. The Scripting Runtime returns `C:\a\b/c`. + +### CleanFileName +{: .no_toc } + +Removes from a file name the characters that Windows does not allow. + +Syntax: **CleanFileName** ( *fileName* [, *replaceChar* ] ) **As String** + +*fileName* +: *required* A **String** holding a file name. It is a name, not a path: the path separators are among the characters that are removed. + +*replaceChar* +: *optional* A **String** that takes the place of each removed character. Default: an empty string, which deletes them. + +Returns the name after these steps, in this order: + +1. Each of the characters `\ / : * ? " < > |` and each control character (codes 0 to 31) is replaced by *replaceChar*. +2. Spaces and dots at the end, and spaces at the start, are removed. This includes any that *replaceChar* put there. Dots at the start stay. +3. If the part of the name before its first dot is a reserved device name (**CON**, **PRN**, **AUX**, **NUL**, **COM0** to **COM9** and **LPT0** to **LPT9**, in any letter case, trailing spaces ignored), *replaceChar* is added to that part, or an underscore when *replaceChar* is empty. Windows treats such a name as a device even with an extension. + +The length of the name is not checked. + +```tb check_build check_run +Debug.Print CleanFileName("report: Q1/Q2?.txt") ' report Q1Q2.txt +Debug.Print CleanFileName("report: Q1/Q2?.txt", "_") ' report_ Q1_Q2_.txt +Debug.Print CleanFileName("NUL.txt") ' NUL_.txt +Debug.Print CleanFileName("notes. ") ' notes +Debug.Print CleanFileName("com3") ' com3_ +Debug.Print CleanFileName("CON ") ' CON_ +Debug.Print CleanFileName("Aux.tar.gz", "-") ' Aux-.tar.gz +Debug.Print CleanFileName(".gitignore") ' .gitignore +``` + +### GetAbsolutePathName +{: .no_toc } + +Returns the full path for a path specification. + +Syntax: **GetAbsolutePathName** ( *path* [, *baseDirectory* [, *useLongPathPrefix* ] ] ) **As String** + +*path* +: *required* A **String** holding an absolute or relative path. The path does not have to exist. + +*baseDirectory* +: *optional* A **String** holding the folder against which a relative *path* is resolved. Default: an empty string, which means the current directory. An absolute *path* ignores it. A *path* of the form `C:name` (a drive without a backslash) uses it only when *baseDirectory* is on the same drive. + +*useLongPathPrefix* +: *optional* A **Boolean**. When **True**, the result starts with `\\?\`, or with `\\?\UNC\` for a network path. Default: **False**. + +Returns the path with `.` and `..` resolved and forward slashes turned into backslashes. The work is done on the text, and the disk is not read. A backslash at the end is removed, except for a drive root, which keeps it and gets an uppercase drive letter: `c:\` becomes `C:\`. The rest of the path keeps the letter case it was given. + +Raises error 52 when Windows cannot form a full path from the text. An empty string is such a case: `GetAbsolutePathName("")` raises error 52, where the Scripting Runtime returns the current directory. `GetAbsolutePathName(".")` returns the current directory in both. + +```tb check_build check_run +Debug.Print GetAbsolutePathName("a\..\b", "C:\base") ' C:\base\b +Debug.Print GetAbsolutePathName("\x", "D:\base\deep") ' D:\x +Debug.Print GetAbsolutePathName("C:name", "C:\base") ' C:\base\name +Debug.Print GetAbsolutePathName("c:\") ' C:\ +Debug.Print GetAbsolutePathName("C:\Temp\") ' C:\Temp +Debug.Print GetAbsolutePathName("C:/a/./b/../c") ' C:\a\c +Debug.Print GetAbsolutePathName("C:\x", , True) ' \\?\C:\x +Debug.Print GetAbsolutePathName("\\srv\sh\f", , True) ' \\?\UNC\srv\sh\f +``` + +### GetBaseName +{: .no_toc } + +Returns the name of the last component of a path, without its extension. + +Syntax: **GetBaseName** ( *path* ) **As String** + +*path* +: *required* A **String** holding a path. It does not have to exist. + +The extension is what follows the last dot of the name. A name without a dot is returned whole, and a name that starts with its only dot has an empty base name: `GetBaseName("C:\work\.gitignore")` returns an empty string, and [**GetExtensionName**](#getextensionname) returns `gitignore`. The work is done on the text. + +### GetDriveName +{: .no_toc } + +Returns the drive part of a path. + +Syntax: **GetDriveName** ( *path* ) **As String** + +*path* +: *required* A **String** holding a path. + +Returns a drive letter and a colon, `C:`, for a path that starts with one, and `\\server\share` for a network path. Neither has a trailing backslash. Returns an empty string for any other path, and for a network path that names a server but no share. + +### GetExtensionName +{: .no_toc } + +Returns the extension of the last component of a path. + +Syntax: **GetExtensionName** ( *path* ) **As String** + +*path* +: *required* A **String** holding a path. + +Returns what follows the last dot of the name, without the dot, or an empty string when the name has no dot. See [**GetBaseName**](#getbasename). + +### GetFileName +{: .no_toc } + +Returns the last component of a path. + +Syntax: **GetFileName** ( *path* ) **As String** + +*path* +: *required* A **String** holding a path. + +Returns the name after the last backslash. Separators at the end of the path are ignored: `C:\work\data\` returns `data`. The drive part is dropped: `C:name` returns `name`. A drive root, `C:\`, and a network root, `\\server` or `\\server\share`, return an empty string. The work is done on the text. + +### GetParentFolderName +{: .no_toc } + +Returns the path of the folder that holds the last component of a path. + +Syntax: **GetParentFolderName** ( *path* ) **As String** + +*path* +: *required* A **String** holding a path. + +Returns the text before the last separator. The parent of a folder directly under a drive root is the root, with its backslash: `C:\work` returns `C:\`. Returns an empty string for a name with no separator, for a drive root, and for a network root (`\\server` or `\\server\share`). The work is done on the text. + +```tb check_build check_run +Dim p As String +p = "C:\Projects\App\docs\readme.final.txt" +Debug.Print GetFileName(p) ' readme.final.txt +Debug.Print GetBaseName(p) ' readme.final +Debug.Print GetExtensionName(p) ' txt +Debug.Print GetParentFolderName(p) ' C:\Projects\App\docs +Debug.Print GetDriveName(p) ' C: +Debug.Print BuildPath("C:\Projects", "App") ' C:\Projects\App +Debug.Print BuildPath("C:\Projects\", "\App") ' C:\Projects\App +``` + +Roots and names without a folder: + +```tb check_build check_run +Debug.Print GetFileName("C:\work\data\") ' data +Debug.Print GetFileName("C:name") ' name +Debug.Print GetFileName("C:\") = "" ' True +Debug.Print GetFileName("\\server\share") = "" ' True +Debug.Print GetParentFolderName("C:\work") ' C:\ +Debug.Print GetParentFolderName("C:\") = "" ' True +Debug.Print GetParentFolderName("\\srv\sh\dir") ' \\srv\sh +Debug.Print GetParentFolderName("\\srv\sh") = "" ' True +Debug.Print GetDriveName("\\srv\sh\x") ' \\srv\sh +Debug.Print GetDriveName("\\srv") = "" ' True +Debug.Print GetBaseName("C:\work\.gitignore") = "" ' True +Debug.Print GetExtensionName("C:\work\.gitignore") ' gitignore +``` + +### GetRelativePath +{: .no_toc } + +Returns the path that leads from one path to another. + +Syntax: **GetRelativePath** ( *basePath*, *targetPath* [, *basePathIsFolder* [, *trimLeadingDotSlash* ] ] ) **As String** + +*basePath* +: *required* A **String** holding the path to start from. + +*targetPath* +: *required* A **String** holding the path to reach. + +*basePathIsFolder* +: *optional* A **Boolean**. **True** (default) treats *basePath* as a folder. **False** treats it as a file when it exists as one, and the relative path starts from the folder that holds it. A *basePath* that does not exist is treated as a file when this is **False**. + +*trimLeadingDotSlash* +: *optional* A **Boolean**. Windows starts a path that goes down with `.\`. **True** (default) removes it, so the result is `src` and not `.\src`. **False** keeps it. + +Both paths are first made absolute with [**GetAbsolutePathName**](#getabsolutepathname). A *targetPath* that exists as a folder is treated as one; any other, one that does not exist included, is treated as a file. So `GetRelativePath("C:\a", "C:\a")` returns `..\a` when the folder `C:\a` does not exist. + +Returns the absolute *targetPath* when there is no relative path, for example when the two paths are on different drives, and when only *targetPath* is longer than 260 characters. + +> [!WARNING] +> In version 1.1.1.0, when *basePath* is 260 characters or longer, the result can be a relative path that leads to the wrong place, and no error is raised. For a target `x\y.txt` below such a base, the result starts with `..\` and the last name of the base, instead of being `x\y.txt`. A base path of 258 characters or fewer gives the right result. + +```tb check_build check_run +Debug.Print GetRelativePath("C:\Projects\App", "C:\Projects\Lib\core.txt") ' ..\Lib\core.txt +Debug.Print GetRelativePath("C:\Projects\App", "C:\Projects\App\src") ' src +Debug.Print GetRelativePath("C:\Projects\App", "D:\Data") ' D:\Data +Debug.Print GetRelativePath("C:\a\b", "C:\a\b\c\d.txt", True, False) ' .\c\d.txt +Debug.Print GetRelativePath("C:\a\b\f.txt", "C:\a\c.txt", False) ' ..\c.txt + +' The same relative path below a short base and below a base of 264 characters. +Dim shortBase As String, longBase As String +shortBase = "C:\" & String$(100, "a") +longBase = "C:\" & String$(130, "a") & "\" & String$(130, "b") +Debug.Print GetRelativePath(shortBase, shortBase & "\x\y.txt") ' x\y.txt +Debug.Print GetRelativePath(longBase, longBase & "\x\y.txt") = "x\y.txt" ' False +``` + +### GetTempName +{: .no_toc } + +Returns a random name for a temporary file or folder. + +Syntax: **GetTempName** ( [ *prefix* [, *extension* [, *inFolder* ] ] ] ) **As String** + +*prefix* +: *optional* A **String** put before the random part. Default: an empty string. + +*extension* +: *optional* A **String** put after the random part, with its dot. Default: `.tmp`. An empty string gives a name without an extension. + +*inFolder* +: *optional* A **String** naming a folder. The name returned does not exist in that folder at the time of the call. Default: an empty string, which does not check. + +Returns *prefix*, eight random uppercase letters and digits, and *extension*: with the defaults, twelve characters such as `K3F9Q2ZA.tmp`. The Scripting Runtime returns `rad` and five hexadecimal digits, such as `rad15093.tmp`. The result is a name, not a path, and nothing is created. The check against *inFolder* does not reserve the name, which another process can take before the caller creates it. + +## Existence + +### DriveExists +{: .no_toc } + +Returns whether a drive exists. + +Syntax: **DriveExists** ( *drivePath* ) **As Boolean** + +*drivePath* +: *required* A **String** that starts with a drive letter: `C`, `C:`, `C:\` or a longer path on that drive. + +Returns **True** when Windows has a drive with that letter. Returns **False** for a letter that is not in use, for any string that is not of one of those forms, and for a network path, which is not a lettered drive. + +### FileExists +{: .no_toc } + +Returns whether a file exists. + +Syntax: **FileExists** ( *filePath* ) **As Boolean** + +*filePath* +: *required* A **String** holding a path. + +Returns **True** when the path exists and is not a folder. Hidden and system files count. Returns **False** for an empty string, for a folder, and for a path that holds a wildcard. + +### FolderExists +{: .no_toc } + +Returns whether a folder exists. + +Syntax: **FolderExists** ( *folderPath* ) **As Boolean** + +*folderPath* +: *required* A **String** holding a path. + +Returns **True** when the path exists and is a folder, a drive root included. Returns **False** for a file. + +## Files and folders + +### CopyFile +{: .no_toc } + +Copies one or more files. + +Syntax: **CopyFile** ( *source*, *destination* [, *overwrite* ] ) + +*source* +: *required* A **String** naming the file to copy. The last component can hold the wildcards `*` and `?`, which match files only. + +*destination* +: *required* A **String** naming the new file, or a folder. An existing folder, or a path that ends in a backslash, is a folder: the copy keeps the name of the source. A wildcard in *destination* is error 5. + +*overwrite* +: *optional* A **Boolean**. **True** (default) replaces an existing file. **False** raises error 58 when the destination file exists. + +With a wildcard in *source*, the destination is a folder that must already exist, and each matching file is copied into it. The parent folder of a single destination file must exist too. A destination that is an existing folder gets the copy with or without a backslash at the end. The Scripting Runtime raises error 70 when the folder has no backslash. + +Errors: + +- 53 when the source file does not exist, or a wildcard matches no file (a pattern that matches only folders counts as no match). +- 76 when the folder of *destination* does not exist. +- 58 when the destination file exists and *overwrite* is **False**. +- 70 when Windows refuses: a destination file that is read-only, and a *source* that is a folder. + +The Scripting Runtime raises the same numbers for a missing source, an existing destination with *overwrite* **False**, and a read-only destination. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder BuildPath(work, "backup"), True +StringToTextFile "a", BuildPath(work, "a.txt") +StringToTextFile "b", BuildPath(work, "b.txt") +StringToTextFile "c", BuildPath(work, "c.log") + +CopyFile BuildPath(work, "*.txt"), BuildPath(work, "backup") +Debug.Print GetFilePaths(BuildPath(work, "backup")).Count ' 2 +DeleteFile BuildPath(work, "*.txt") +Debug.Print GetFilePaths(work).Count ' 1 +DeleteFile BuildPath(work, "*.txt"), ignoreMissing:=True ' nothing matches: no error + +DeleteFolder work +``` + +### CopyFolder +{: .no_toc } + +Copies a folder and everything in it. + +Syntax: **CopyFolder** ( *source*, *destination* [, *overwrite* ] ) + +*source* +: *required* A **String** naming the folder to copy. The last component can hold the wildcards `*` and `?`, which match folders only. + +*destination* +: *required* A **String** naming a folder. A wildcard in it is error 5. + +*overwrite* +: *optional* A **Boolean**. **True** (default) replaces files that already exist in the destination. **False** raises error 58 at the first file that exists, and the files copied before it stay. + +When *destination* is an existing folder, the source is copied into it, as a subfolder with the name of the source, whether or not *destination* ends in a backslash. Otherwise *destination* is the new folder, and its parent must exist. Subfolders that already exist in the destination are merged. With a wildcard in *source*, *destination* must be an existing folder, and each matching folder is copied into it. + +The Scripting Runtime does this only when *destination* ends in a backslash. Without one, it copies the contents of the source straight into the existing folder: `CopyFolder "src", "dst"` puts `f1.txt` in `dst` with the Scripting Runtime, and in `dst\src` here. + +Errors: + +- 76 when the source folder, a wildcard match, or the parent of the new folder does not exist. +- 5 when *destination* is the source or lies inside it, which would copy the folder into itself. +- 58 when a file exists and *overwrite* is **False**. + +> [!WARNING] +> In version 1.1.1.0, a directory junction or symbolic link inside the source is copied as if it were an ordinary folder, so its target is copied under the name of the link. See [Junctions and symbolic links](#junctions-and-symbolic-links). + +### CreateFolder +{: .no_toc } + +Creates a folder. + +Syntax: **CreateFolder** ( *folderPath* [, *createIntermediates* ] ) **As Folder** + +*folderPath* +: *required* A **String** naming the folder to create. + +*createIntermediates* +: *optional* A **Boolean**. **False** (default) creates only the last folder, so its parent must exist. **True** also creates the missing folders above it. + +Returns the new [**Folder**](Folder). + +Errors: + +- 58 when a folder, or a file, with that path exists. +- 76 when the parent does not exist and *createIntermediates* is **False**. +- 52 when the last component holds a character that is not allowed in a name, the wildcards `*` and `?` among them. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +On Error Resume Next +CreateFolder work ' the folder exists now +Debug.Print Err.Number ' 58 +Err.Clear +CreateFolder BuildPath(work, "a\b\c") ' the folders above are missing +Debug.Print Err.Number ' 76 +Err.Clear +On Error GoTo 0 + +CreateFolder BuildPath(work, "a\b\c"), True +Debug.Print FolderExists(BuildPath(work, "a\b")) ' True + +DeleteFolder work +``` + +### DeleteFile +{: .no_toc } + +Deletes one or more files. + +Syntax: **DeleteFile** ( *filePath* [, *force* [, *ignoreMissing* ] ] ) + +*filePath* +: *required* A **String** naming the file to delete. The last component can hold the wildcards `*` and `?`, which match files only. + +*force* +: *optional* A **Boolean**. **True** clears the read-only, hidden and system attributes of a file before deleting it. **False** (default) leaves them, and a read-only file cannot be deleted. + +*ignoreMissing* +: *optional* A **Boolean**. **True** does nothing when the path or the pattern matches no file. **False** (default) raises error 53. + +A path that names a folder is error 53, whatever *ignoreMissing* is. A failure on one file of a wildcard stops the deletion, and the files before it stay deleted. + +Errors: 53 when no file matches, a folder is named, or the folder of the path does not exist; 70 for a read-only file without *force*. The Scripting Runtime raises 53 and 70 in these cases too. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "a", BuildPath(work, "a.txt") +StringToTextFile "b", BuildPath(work, "b.txt") +StringToTextFile "c", BuildPath(work, "c.txt") +SetAttr BuildPath(work, "b.txt"), vbReadOnly + +On Error Resume Next +DeleteFile BuildPath(work, "*.txt") ' stops at the read-only file +Debug.Print Err.Number ' 70 +Err.Clear +On Error GoTo 0 +Debug.Print FileExists(BuildPath(work, "a.txt")) ' False +Debug.Print FileExists(BuildPath(work, "c.txt")) ' True + +DeleteFile BuildPath(work, "*.txt"), force:=True +Debug.Print GetFilePaths(work).Count ' 0 +DeleteFolder work +``` + +### DeleteFolder +{: .no_toc } + +Deletes a folder and everything in it. + +Syntax: **DeleteFolder** ( *folderPath* [, *force* [, *ignoreMissing* ] ] ) + +*folderPath* +: *required* A **String** naming the folder to delete. The last component can hold the wildcards `*` and `?`, which match folders only. + +*force* +: *optional* A **Boolean**. **True** clears the read-only, hidden and system attributes as the contents are deleted. **False** (default) leaves them, and a read-only file stops the folder from being removed. + +*ignoreMissing* +: *optional* A **Boolean**. **True** does nothing when the path or the pattern matches no folder. **False** (default) raises error 76. + +The contents are deleted first, and an item that cannot be deleted is skipped without an error. The folder itself is removed last; when it is not empty by then, the failure is reported. + +Errors: 76 when no folder matches or a file is named; 75 when the folder is not empty because something in it could not be deleted, such as a read-only file when *force* is **False** (the message ends with the Windows code 145). The folder and the file stay. The Scripting Runtime raises error 70 in that case. + +> [!WARNING] +> In version 1.1.1.0, a directory junction or symbolic link inside the folder is entered like an ordinary folder, and **DeleteFolder** deletes the files in its target, not only the link. A tree that holds a link to a folder outside it loses the contents of that folder. The Scripting Runtime does the same. Remove such links before calling **DeleteFolder**. See [Junctions and symbolic links](#junctions-and-symbolic-links). + +### GetFilePaths +{: .no_toc } + +Returns the paths of the files in a folder. + +Syntax: **GetFilePaths** ( *folderPath* [, *pattern* [, *includeHidden* [, *includeSystem* [, *recursive* ] ] ] ] ) **As Collection** + +*folderPath* +: *required* A **String** naming the folder. + +*pattern* +: *optional* A **String**, a file name that can hold the wildcards `*` and `?`. Default: `*`. The pattern applies to the files of every folder that is searched, and not to the folders. + +*includeHidden* +: *optional* A **Boolean**. **True** includes hidden files. Default: **False**. + +*includeSystem* +: *optional* A **Boolean**. **True** includes system files. Default: **False**. A file that is both hidden and system needs both. + +*recursive* +: *optional* A **Boolean**. **True** also searches the subfolders, to any depth. Default: **False**. + +Returns a **Collection** of **String** values, the full path of each file, starting at item 1. Folders are not in it. The order is the one in which Windows lists the folder. An empty result is an empty **Collection**. Hidden and system subfolders are searched only when *includeHidden* and *includeSystem* allow them. + +Raises error 76 when *folderPath* is not a folder, and error 70 when a folder cannot be read. A junction or symbolic link in the tree is entered like a folder, so the files below its target are listed again under the link; see [Junctions and symbolic links](#junctions-and-symbolic-links). + +Unlike [**Folder.Files**](Folder#files), the result leaves out hidden and system files unless they are asked for. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder BuildPath(work, "sub"), True +StringToTextFile "a", BuildPath(work, "a.txt") +StringToTextFile "b", BuildPath(work, "sub\b.txt") +StringToTextFile "c", BuildPath(work, "sub\c.md") + +Debug.Print GetFilePaths(work, "*.txt").Count ' 1 +Debug.Print GetFilePaths(work, "*.txt", recursive:=True).Count ' 2 + +' A hidden file is left out unless it is asked for. +GetFile(BuildPath(work, "a.txt")).SetAttribute faHidden, True +Debug.Print GetFilePaths(work, "*.txt").Count ' 0 +Debug.Print GetFilePaths(work, "*.txt", includeHidden:=True).Count ' 1 +Debug.Print GetFolder(work).Files.Count ' 1 + +DeleteFolder work +``` + +### MoveFile +{: .no_toc } + +Moves one or more files. + +Syntax: **MoveFile** ( *source*, *destination* ) **As String** + +*source* +: *required* A **String** naming the file to move. The last component can hold the wildcards `*` and `?`, which match files only. + +*destination* +: *required* A **String**. An existing folder, or a path that ends in a backslash, is a folder: the file keeps its name in it. Any other path is the new full name of the file. A wildcard in it is error 5. + +Returns the full path of the moved file. With a wildcard in *source*, it returns the full path of the destination folder, which must already exist. + +A file is never replaced: when the destination file exists, error 58 is raised. A destination that is an existing folder gets the file with or without a backslash at the end. The Scripting Runtime raises error 58 when the folder has no backslash. + +Errors: 53 when a wildcard matches no file, or matches only folders; 76 when the destination folder of a wildcard move does not exist; 58 when the destination file exists. + +> [!NOTE] +> A *source* without a wildcard that names a folder is moved too, with no error: `MoveFile "C:\old", "C:\new"` renames the folder. The Scripting Runtime raises error 53. + +### MoveFolder +{: .no_toc } + +Moves one or more folders. + +Syntax: **MoveFolder** ( *source*, *destination* ) **As String** + +*source* +: *required* A **String** naming the folder to move. The last component can hold the wildcards `*` and `?`, which match folders only. + +*destination* +: *required* A **String**. An existing folder, or a path that ends in a backslash, is a folder: the source is moved into it and keeps its name. Any other path is the new full name of the folder. A wildcard in it is error 5. + +Returns the full path of the moved folder, or the full path of the destination folder when *source* holds a wildcard. + +A destination that is an existing folder gets the source with or without a backslash at the end. The Scripting Runtime raises error 58 when the folder has no backslash. + +Errors: 76 when *source* names no folder, or a wildcard matches no folder; 5 for a wildcard in *destination*. + +### Rename +{: .no_toc } + +Renames a file or a folder in place. + +Syntax: **Rename** ( *source*, *newName* ) **As String** + +*source* +: *required* A **String** naming the file or folder. + +*newName* +: *required* A **String**, a bare name or a path that resolves to the folder that holds *source*. Wildcards are not allowed. + +Returns the full path of the renamed item. + +*newName* cannot move the item to another folder. A change of letter case alone is allowed. + +Errors: 53 when *source* does not exist (for a folder as well as a file); 58 when *newName* is the name of another item in that folder; 5 for a wildcard, and for a *newName* that leads to another folder. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "x", BuildPath(work, "old.txt") + +Dim newPath As String +newPath = Rename(BuildPath(work, "old.txt"), "new.txt") +Debug.Print GetFileName(newPath) ' new.txt +Debug.Print FileExists(BuildPath(work, "old.txt")) ' False + +DeleteFolder work +``` + +## Current directory and special folders + +### GetCurrentDir +{: .no_toc } + +Returns the current directory of the process. + +Syntax: **GetCurrentDir** ( ) **As String** + +Returns the path without a trailing backslash, except for a drive root (`C:\`). It is the folder against which a relative path is resolved. After [**ChDir**](../../../Modules/FileSystem/ChDir), **GetCurrentDir** returns the new directory; after [**SetCurrentDir**](#setcurrentdir), **CurDir$** does. + +### GetSpecialFolder +{: .no_toc } + +Returns the path of a well-known Windows folder. + +Syntax: **GetSpecialFolder** ( *specialFolder* ) **As String** + +*specialFolder* +: *required* A [**SpecialFolderConst**](#specialfolderconst) member. + +Returns the path without a trailing backslash. **sfTemporaryFolder** (and its FSO name **TemporaryFolder**) returns the value of the `TEMP` environment variable; every other member is asked from Windows by its known-folder identifier. Raises error 5 for a value that is not a member. A program built for 32 bits gets the `Program Files (x86)` folder for both **sfProgramFiles** and **sfProgramFilesX86**, and the `System32` path for **sfSystemFolder**; **sfSystemX86Folder** is `SysWOW64`. The letter case of the result can differ from that of the Scripting Runtime: `C:\WINDOWS` against `C:\Windows`. + +> [!NOTE] +> Scripting.FileSystemObject returns a **Folder** object from **GetSpecialFolder**. This procedure returns the path as a **String**. Use [**GetFolder**](#getfolder) on the result to get the object. + +```tb check_build check_run +Debug.Print StrComp(GetSpecialFolder(sfWindowsFolder), Environ$("windir"), vbTextCompare) ' 0 +Debug.Print GetSpecialFolder(TemporaryFolder) = Environ$("TEMP") ' True +Debug.Print FolderExists(GetSpecialFolder(sfLocalAppData)) ' True +``` + +### SetCurrentDir +{: .no_toc } + +Sets the current directory of the process. + +Syntax: **SetCurrentDir** ( *folderPath* ) + +*folderPath* +: *required* A **String** naming an existing folder. Windows limits the path to 260 characters here. + +Relative paths given to the procedures of the package, and to the file statements of VBA, are resolved against the new directory: after `SetCurrentDir work`, `Open "x.txt" For Output` creates the file in `work`. + +Errors: 53 when the folder does not exist, 76 when the path names a file, and 75 when the path is longer than 260 characters. + +## File information + +### GetFileEncoding +{: .no_toc } + +Returns the text encoding of a file. + +Syntax: **GetFileEncoding** ( *filePath* [, *sampleBytes* ] ) **As TextEncoding** + +*filePath* +: *required* A **String** naming the file. + +*sampleBytes* +: *optional* A **Long**, the number of bytes at the start of the file to examine. Default: 65536. + +Returns a [**TextEncoding**](#textencoding) member. A negative value means that the file starts with a BOM. A file that is empty, or looks like binary data, returns **encUnknown**. The decision is taken in this order: + +1. A BOM at the start: UTF-8, UTF-16, UTF-32 (little or big endian), GB18030 and UTF-7 each have one. +2. A pattern of zero bytes that points to UTF-16 or UTF-32. +3. UTF-8, when the first 8,192 bytes are valid UTF-8. Plain ASCII is valid UTF-8, so it is reported as **encUtf8**, and so is ASCII text that holds a zero byte. +4. Binary data, when the sample is not valid UTF-8 and holds a zero byte, or more than 2% control characters other than tab, line feed, carriage return and form feed. The second test is made only for a sample of 64 bytes or more. +5. Otherwise, **encSystemAnsi**. + +The result is an estimate, as every guess about the encoding of a text file is. A file in UTF-16, UTF-32 or UTF-8, with or without a BOM, is found right; so is a Latin-1 file when the system code page is Latin-1. Text in another code page without a BOM is not: GB18030 text with an accented letter is reported as **encSystemAnsi**, and UTF-7 or Big5 text that is pure ASCII as **encUtf8**. A sample that ends in the first byte of a multi-byte sequence is judged without that byte, so a Latin-1 file whose only accented letter is its last byte is reported as **encUtf8**. Pass the encoding to the procedure that reads the file when it is known. + +Raises error 5 when the file does not exist; see [Errors](./#errors). + +### GetFileLineEnding +{: .no_toc } + +Returns the line-ending style of a text file. + +Syntax: **GetFileLineEnding** ( *filePath* [, *encoding* ] ) **As NewlineType** + +*filePath* +: *required* A **String** naming the file. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encAutoDetect**. + +Reads the whole file and returns a [**NewlineType**](#newlinetype) member: **nlWindows** (CR LF), **nlUnix** (LF) or **nlMacintosh** (CR) when the file uses one style, **nlMixed** when it uses more than one, and **nlUnknown** when it has no line ending. **nlMixed** and **nlUnknown** are both 0, so they cannot be told apart by their value. + +Raises error 5 for a binary file, and error 7 for a file too large for one **String** (over 2 GB). It raises the same error as [**GetFileEncoding**](#getfileencoding) for a missing file. + +### GetFileType +{: .no_toc } + +Returns the description of the type of a file or folder. + +Syntax: **GetFileType** ( *filePath* ) **As String** + +*filePath* +: *required* A **String** naming a file or folder. + +Returns the type name that Windows Explorer shows, in the language of the system: `Application` for an `.exe` file, `File folder` for a folder, and for other files the description registered for the extension, or `TXT File` for a `.txt` file when none is registered. The Scripting Runtime's `File.Type` and `Folder.Type` return the same strings. When the shell cannot give a name, including when the path does not exist, the result is the extension in uppercase and the word `File` (`TXT File`), or just `File` when there is no extension. + +### GetFileVersion +{: .no_toc } + +Returns the version of an executable file. + +Syntax: **GetFileVersion** ( *filePath* [, *source* ] ) **As String** + +*filePath* +: *required* A **String** naming the file. A path longer than 260 characters gives an empty string. + +*source* +: *optional* A [**FileVersionSource**](#fileversionsource) member. Default: **fvFixed**. + +Returns the binary version as four numbers separated by dots, such as `10.0.19041.7725`, with **fvFixed**, the same string as the Scripting Runtime. **fvFileString** returns the `FileVersion` text that the author of the file wrote into it, and **fvProductString** the `ProductVersion` text; both return the binary version when the file has no such text. Returns an empty string when the file has no version resource, and when the file does not exist. The Scripting Runtime raises an error for a file that does not exist (the Windows code `0x80070002`, as the number -2147024894). + +```tb check_build check_run +Dim kernel As String +kernel = BuildPath(GetSpecialFolder(sfSystemFolder), "kernel32.dll") +Debug.Print Len(GetFileVersion(kernel)) > 0 ' True +Debug.Print Len(GetFileVersion(kernel, fvFileString)) > 0 ' True +``` + +## Objects + +### GetDrive +{: .no_toc } + +Returns a [**Drive**](Drive) object. + +Syntax: **GetDrive** ( *drivePath* ) **As Drive** + +*drivePath* +: *required* A **String** that starts with a drive letter (`C`, `C:`, `C:\`, or a longer path on that drive), or the path of a network share (`\\server\share`, or a longer path). + +Returns the drive, with its letter in uppercase. A path on a drive is reduced to the drive. Raises error 68 when the string is of none of those forms, and when a lettered drive does not exist. A network share is not checked until a property of the **Drive** needs it. + +### GetDrives +{: .no_toc } + +Returns a [**Drives**](Drives) collection of every drive. + +Syntax: **GetDrives** ( ) **As Drives** + +Returns the logical drives that Windows has, in the order of their letters. Check [**Drive.IsReady**](Drive#isready) before reading the size of one. The collection is a snapshot, and a drive that appears later is not in it. + +### GetFile +{: .no_toc } + +Returns a [**File**](File) object for an existing file. + +Syntax: **GetFile** ( *filePath* ) **As File** + +*filePath* +: *required* A **String** naming the file. + +Returns the object for the full path of the file. Its **Name** has the letter case that the file system stores. Raises error 53 when the file does not exist, or when the path is a folder. + +### GetFolder +{: .no_toc } + +Returns a [**Folder**](Folder) object for an existing folder. + +Syntax: **GetFolder** ( *folderPath* ) **As Folder** + +*folderPath* +: *required* A **String** naming the folder. A drive root is allowed. + +Returns the object for the full path of the folder. Its **Name** has the letter case that the file system stores. Raises error 76 when the folder does not exist, or when the path is a file. + +## Whole files + +### ArrayToTextFile +{: .no_toc } + +Writes the elements of a **String** array to a text file, one per line. + +Syntax: **ArrayToTextFile** ( *lines*(), *filePath* [, *encoding* [, *lineEnding* ] ] ) + +*lines* +: *required* A **String** array. Its elements are joined with the line ending. No line ending follows the last element, so a final empty element is what makes the file end with one. + +*filePath* +: *required* A **String** naming the file. An existing file is replaced. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encUtf8**. A BOM is written only for the members with **Bom** in their name. **encAutoDetect** is read as **encUtf8**. + +*lineEnding* +: *optional* A [**NewlineType**](#newlinetype) member. Default: **nlWindows**. **nlNoChange** is read as **nlWindows**, because a line ending is needed to join the lines. + +The file is written as [**WriteStream**](#writestream) writes it, read-only files included; see the warning there. [**TextFileToArray**](#textfiletoarray) reads the result back into the same array. An array that is not dimensioned writes an empty file. + +Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)); the errors of [**WriteStream**](#writestream). + +### MergeTextFiles +{: .no_toc } + +Joins two text files into one. + +Syntax: **MergeTextFiles** ( *firstFilePath*, *secondFilePath* [, *outputFilePath* [, *insertBlankLineBetween* [, *firstFileEncoding* [, *secondFileEncoding* ] ] ] ] ) + +*firstFilePath* +: *required* A **String** naming the file whose text comes first. + +*secondFilePath* +: *required* A **String** naming the file whose text follows. + +*outputFilePath* +: *optional* A **String** naming the result. Default: an empty string, which replaces *firstFilePath*. The result can also be either input file. + +*insertBlankLineBetween* +: *optional* A **Boolean**. **True** puts an empty line between the two texts. Default: **False**. + +*firstFileEncoding* +: *optional* A [**TextEncoding**](#textencoding) member for the first file. Default: **encAutoDetect**. + +*secondFileEncoding* +: *optional* A [**TextEncoding**](#textencoding) member for the second file. Default: **encAutoDetect**. + +The result has the encoding of the first file, with its BOM if it has one, and the line ending of the first file; a first file with mixed line endings, or none, gives CR LF. The lines of the second file are written in that encoding and with that line ending: a UTF-16 file with a BOM followed by a UTF-8 file with LF line endings gives a UTF-16 file with a BOM and CR LF line endings. + +Every line is written with a line ending, the last one too, so the result ends with a line ending even when the files did not. + +When the result is one of the inputs, the new text is written to a file beside it and swapped in once it is complete, and the input stays as it was after an error. When it is a separate file and an error occurs, the part that was written stays. + +Errors: 5 when the first file does not exist or either file is binary; 53 when the second file does not exist; 52 for an output name with a character above U+7FFF (see [Errors](./#errors)). + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +Dim fileA As String, fileB As String, fileOut As String +fileA = BuildPath(work, "a.txt") +fileB = BuildPath(work, "b.txt") +fileOut = BuildPath(work, "all.txt") +StringToTextFile "a1" & vbCrLf & "a2", fileA +StringToTextFile "b1" & vbLf & "b2", fileB + +MergeTextFiles fileA, fileB, fileOut, True +Debug.Print Replace(TextFileToString(fileOut), vbCrLf, "|") ' a1|a2||b1|b2| + +DeleteFolder work +``` + +### NormalizeTextFile +{: .no_toc } + +Rewrites a text file in place with a chosen encoding and line ending. + +Syntax: **NormalizeTextFile** ( *filePath* [, *toEncoding* [, *toNewLine* [, *fromEncoding* [, *toAttributes* [, *skipIfUnchanged* ] ] ] ] ] ) **As Boolean** + +*filePath* +: *required* A **String** naming the file. + +*toEncoding* +: *optional* A [**TextEncoding**](#textencoding) member for the new file. Default: **encUtf8**, with no BOM; a file that starts with a BOM loses it. **encAutoDetect** is read as **encUtf8**. + +*toNewLine* +: *optional* A [**NewlineType**](#newlinetype) member. Default: **nlWindows**. **nlNoChange** keeps the line endings as they are. + +*fromEncoding* +: *optional* A [**TextEncoding**](#textencoding) member for the file as it is now. Default: **encAutoDetect**. + +*toAttributes* +: *optional* A [**FileAttribute**](#fileattribute) combination to give the file. Only the settable attributes count. Default: **faNoChange**, which keeps the attributes of the file. + +*skipIfUnchanged* +: *optional* A **Boolean**. **True** (default) leaves the file alone when the text would be written back with exactly the bytes it has. **False** always writes it. + +Returns **True** when the file was changed, a change of attributes alone included, and **False** when it was left as it was. A second call with the same arguments returns **False**. + +A binary file is refused with error 5; this includes a file that *fromEncoding* names when it is not UTF-16 or UTF-32 and holds binary data. An empty file stays empty, or becomes a BOM alone when *toEncoding* has one. The new file is written beside the old one and swapped in, as [**WriteStream**](#writestream) does. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +Dim path As String +path = BuildPath(work, "script.txt") +StringToTextFile "one" & vbLf & "two" & vbLf, path +Debug.Print GetFileLineEnding(path) = nlUnix ' True +Debug.Print NormalizeTextFile(path) ' True +Debug.Print GetFileLineEnding(path) = nlWindows ' True +Debug.Print NormalizeTextFile(path) ' False + +' The attributes of the file are kept, or set when toAttributes is given. +SetAttr path, vbHidden +Debug.Print NormalizeTextFile(path, , nlUnix) ' True +Debug.Print GetAttr(path) ' 2 +Debug.Print NormalizeTextFile(path, , nlUnix, , faReadOnly) ' True +Debug.Print GetAttr(path) ' 1 +SetAttr path, vbNormal + +' A binary file is refused. +Dim bin() As Byte +ReDim bin(0 To 2) +bin(0) = 233: bin(1) = 0: bin(2) = 97 +WriteStream bin, BuildPath(work, "data.bin") +On Error Resume Next +NormalizeTextFile BuildPath(work, "data.bin") +Debug.Print Err.Number ' 5 +Err.Clear + +DeleteFolder work, True +``` + +### ReadStream +{: .no_toc } + +Reads a file into a byte array. + +Syntax: **ReadStream** ( *filePath*, *stream*() [, *maxBytesToRead* ] ) + +*filePath* +: *required* A **String** naming the file. + +*stream* +: *required* A **Byte** array that receives the bytes. It is dimensioned from 0. + +*maxBytesToRead* +: *optional* A **Long**. A value of 0 or more reads at most that many bytes from the start of the file. Default: -1, the whole file. + +An empty file, and a *maxBytesToRead* of 0, leave *stream* as it was: an array that already has elements keeps them, and one that has none stays without. Test the array with [**IsArrayInitialized**](../../../Modules/Information/IsArrayInitialized) before indexing it. + +Raises error 7 when the whole file is longer than 2,147,483,647 bytes; a head of any size can be read from a larger file. Raises error 5 (*file not found!*) when the file does not exist; see [Errors](./#errors). + +### StringToTextFile +{: .no_toc } + +Writes a string to a text file. + +Syntax: **StringToTextFile** ( *text*, *filePath* [, *encoding* [, *outLineEnding* ] ] ) + +*text* +: *required* A **String** with the text to write. + +*filePath* +: *required* A **String** naming the file. An existing file is replaced. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encUtf8**. A BOM is written only for the members with **Bom** in their name. **encAutoDetect** is read as **encUtf8**. + +*outLineEnding* +: *optional* A [**NewlineType**](#newlinetype) member. A style other than **nlNoChange** converts every line ending in *text*, CR LF, LF or CR, to it. Default: **nlNoChange**, which writes *text* as it is. + +An empty *text* creates an empty file, or one that holds only the BOM. The file is written as [**WriteStream**](#writestream) writes it, and an existing read-only file is replaced without an error; see the warning there. + +For a code page with a limited character set, a character that the code page does not have is replaced by the substitute character of that code page, without an error: the Greek letter pi written with **encLatin1** becomes the byte 63, a question mark. An unpaired surrogate in *text* raises error 5 when the encoding is UTF-8. + +Errors: 52 when the last component of *filePath* holds `<`, `>`, `:`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)); the errors of [**WriteStream**](#writestream). + +```tb check_build check_run +Dim work As String, path As String, b() As Byte +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "t.txt") + +StringToTextFile ChrW$(960), path, encLatin1 +ReadStream path, b +Debug.Print b(0) ' 63 + +StringToTextFile "a" & vbCr & "b" & vbLf, path, , nlUnix +ReadStream path, b +Debug.Print UBound(b) + 1 ' 4 + +On Error Resume Next +StringToTextFile ChrW$(&HD800&) & "x", path, encUtf8 +Debug.Print Err.Number ' 5 +Err.Clear + +DeleteFolder work +``` + +### TextFileToArray +{: .no_toc } + +Reads a text file into an array of lines. + +Syntax: **TextFileToArray** ( *filePath* [, *encoding* ] ) **As String()** + +*filePath* +: *required* A **String** naming the file. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encAutoDetect**. A BOM in the file decides over it. + +Returns a **String** array, dimensioned from 0, with one element per line. CR LF, LF and CR all end a line, also in one file. The line endings are not in the elements. A line ending at the end of the file gives a last, empty element. An empty file returns an array that is not dimensioned; test it with [**IsArrayInitialized**](../../../Modules/Information/IsArrayInitialized). A BOM is removed. + +Raises error 5 for a binary file when the encoding is detected, and the error of [**GetFileEncoding**](#getfileencoding) for a missing file. + +```tb check_build check_run +Dim work As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +Dim path As String, lines() As String, back() As String +path = BuildPath(work, "lines.txt") +lines = Split("first,second,third", ",") +ArrayToTextFile lines, path, encUtf8, nlUnix +Debug.Print GetFileLineEnding(path) = nlUnix ' True + +back = TextFileToArray(path) +Debug.Print UBound(back) - LBound(back) + 1 ' 3 +Debug.Print back(2) ' third + +DeleteFolder work +``` + +### TextFileToString +{: .no_toc } + +Reads a text file into a string. + +Syntax: **TextFileToString** ( *filePath* [, *encoding* [, *outLineEnding* ] ] ) **As String** + +*filePath* +: *required* A **String** naming the file. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encAutoDetect**, which finds the encoding as [**GetFileEncoding**](#getfileencoding) does. An explicit encoding is used as it stands, unless the file starts with a BOM, which decides over it. A BOM member (**encUtf8Bom**) for a file without a BOM reads the file with the same code page. + +*outLineEnding* +: *optional* A [**NewlineType**](#newlinetype) member. A style other than **nlNoChange** converts every line ending in the result to it. Default: **nlNoChange**. + +Returns the text, without the BOM. An empty file returns an empty string. Bytes that are not valid in the encoding are replaced by a substitute character, without an error. + +Raises error 5 for a file that looks binary when the encoding is detected (an explicit encoding is not checked), error 7 for a file of more than 2 GB, and the error of [**GetFileEncoding**](#getfileencoding) for a missing file. + +### WriteStream +{: .no_toc } + +Writes a byte array to a file. + +Syntax: **WriteStream** ( *stream*(), *filePath* ) + +*stream* +: *required* A **Byte** array. An empty array, or one that is not dimensioned, creates an empty file. + +*filePath* +: *required* A **String** naming the file. An existing file is replaced. + +The bytes are first written to a new file in the same folder, `.~XXXXXXXX.tmp`, which is then swapped in for the target. A failure part-way therefore leaves the old file whole. The folder must allow a new file to be created in it. + +> [!WARNING] +> In version 1.1.1.0, **WriteStream**, [**StringToTextFile**](#stringtotextfile) and [**ArrayToTextFile**](#arraytotextfile) replace a read-only file without an error, and the new file is not read-only. The attributes of the old file are lost. [**CreateTextFile**](#createtextfile) and the Scripting Runtime's **CreateTextFile** raise error 70 for the same file. + +Errors: an unspecified error (the number -2147467259) when the folder does not exist, and error 70 when Windows refuses; see [Errors](./#errors) for the other Windows failures. + +```tb check_build check_run +Dim work As String, path As String, ts As TextStream, b() As Byte, lines() As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "protected.txt") +ReDim b(0 To 0) +b(0) = 65 +lines = Split("a,b", ",") + +StringToTextFile "old", path +SetAttr path, vbReadOnly +StringToTextFile "new", path ' no error +Debug.Print TextFileToString(path) ' new +Debug.Print GetAttr(path) And vbReadOnly ' 0 + +SetAttr path, vbReadOnly +ArrayToTextFile lines, path +Debug.Print GetAttr(path) And vbReadOnly ' 0 + +SetAttr path, vbReadOnly +WriteStream b, path +Debug.Print GetAttr(path) And vbReadOnly ' 0 + +SetAttr path, vbReadOnly +On Error Resume Next +Set ts = CreateTextFile(path) +Debug.Print Err.Number ' 70 +Err.Clear +WriteStream b, BuildPath(work, "missing\x.bin") +Debug.Print Err.Number ' -2147467259 +Err.Clear +On Error GoTo 0 + +DeleteFolder work, True +``` + +## Text streams + +### CreateTextFile +{: .no_toc } + +Creates a text file and returns a [**TextStream**](TextStream) open for writing. + +Syntax: **CreateTextFile** ( *filePath* [, *overwrite* [, *encoding* [, *lineEnding* ] ] ] ) **As TextStream** + +*filePath* +: *required* A **String** naming the file. + +*overwrite* +: *optional* A **Boolean**. **True** (default) empties an existing file. **False** raises error 58 when the file exists. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encUtf8**, without a BOM. A member with **Bom** in its name writes the BOM when the file is created. **encAutoDetect** is read as **encUtf8**. + +*lineEnding* +: *optional* A [**NewlineType**](#newlinetype) member that [**WriteLine**](TextStream#writeline) and [**WriteBlankLines**](TextStream#writeblanklines) use. Default: **nlWindows**. Anything that is not **nlUnix** or **nlMacintosh** is CR LF. + +The file is written in place, with no swap file, and an existing read-only file raises error 70. The errors are those of [**OpenTextFile**](#opentextfile) for **ForWriting**, and error 52 for a name that has `<`, `>`, `"`, `|`, `?`, `*` or a control character, and in version 1.1.1.0 a character above U+7FFF (see [Errors](./#errors)). + +> [!IMPORTANT] +> The third argument of **Scripting.FileSystemObject.CreateTextFile** is a **Boolean** *Unicode*, and this procedure's third argument is an *encoding*. A **Boolean** converts to a number: **True** is -1, which is **encAutoDetect**, and on writing that means UTF-8, not UTF-16. **False** is 0, **encSystemAnsi**. Code ported from the Scripting Runtime that passes **True** needs **encUtf16** or **encUtf16Bom** here. + +```tb check_build check_run +Dim work As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +' In code written for the Scripting Runtime, the last True meant Unicode. +Set ts = CreateTextFile(BuildPath(work, "a.txt"), True, True) +Debug.Print ts.Encoding = encUtf8 ' True +ts.Close + +Set ts = CreateTextFile(BuildPath(work, "b.txt"), True, encUtf16Bom) +Debug.Print ts.Encoding = encUtf16Bom ' True +ts.Close + +DeleteFolder work +``` + +### GetStandardStream +{: .no_toc } + +Returns a [**TextStream**](TextStream) for the standard input, output or error stream of the process. + +Syntax: **GetStandardStream** ( *standardStreamType* [, *unicode* ] ) **As TextStream** + +*standardStreamType* +: *required* A [**StandardStreamTypes**](#standardstreamtypes) member. + +*unicode* +: *optional* A **Boolean**. **True** reads and writes UTF-16 text, without a BOM. **False** (default) uses the system ANSI code page. + +Standard input can be read, and standard output and error can be written. The stream does not own the handle, and [**Close**](TextStream#close) does not close it. Reading waits for the other end. Line endings written are CR LF. For these streams [**Encoding**](TextStream#encoding) returns **encSystemAnsi**, also when *unicode* is **True**, and [**IsStreaming**](TextStream#isstreaming) returns **False**. + +### OpenTextFile +{: .no_toc } + +Opens a text file and returns a [**TextStream**](TextStream). + +Syntax: **OpenTextFile** ( *filePath* [, *ioMode* [, *create* [, *encoding* [, *writeModeLineEnding* ] ] ] ] ) **As TextStream** + +*filePath* +: *required* A **String** naming the file. + +*ioMode* +: *optional* An [**IOMode**](#iomode) member. Default: **ForReading**. + +*create* +: *optional* A **Boolean**. **True** creates the file when it does not exist, for **ForWriting** and **ForAppending**. Default: **False**. + +*encoding* +: *optional* A [**TextEncoding**](#textencoding) member. Default: **encAutoDetect**. See below. + +*writeModeLineEnding* +: *optional* A [**NewlineType**](#newlinetype) member that [**WriteLine**](TextStream#writeline) and [**WriteBlankLines**](TextStream#writeblanklines) use. Default: **nlWindows**. Anything that is not **nlUnix** or **nlMacintosh** is CR LF. + +What the mode does: + +- **ForReading** requires the file. The encoding is found from the start of the file: a BOM first, then *encoding* when it names one, then the same guess as [**GetFileEncoding**](#getfileencoding). A file that looks binary is refused with error 5 when the encoding is detected. An empty file is reported as UTF-8. +- **ForWriting** empties the file when it is opened, for an encoding that is written in pieces; a stream that holds the whole file (UTF-7) replaces the file when it is closed or flushed. *encoding* gives the encoding, and **encAutoDetect** means UTF-8. A BOM is written for a member with **Bom** in its name. While the stream is open, other streams can read the file, and cannot open it for writing or appending (error 70). A stream open for reading does not stop another from opening the file for writing. +- **ForAppending** adds to the end. A file that exists keeps its encoding and BOM, found as for reading: *encoding* is used only when the file has no BOM, and is not checked against the text in the file, so a wrong one mixes encodings in the file. A BOM is not written again, and none is written to an existing file that is empty. The line ending for **WriteLine** is the one the first 64 KB of the file use, and *writeModeLineEnding* is used only for an empty file, a file without a line ending and a file with mixed ones. A file that does not exist is created like a new file under **ForWriting**, with the BOM of the encoding. + +Errors: 53 when the file does not exist for **ForReading**, or for the other modes with *create* **False**; 76 when the folder does not exist; 52 for a name that is not valid when *create* is **True**; 5 for an unknown *ioMode*, and for a binary file as above; 70 when another stream has the file open for writing. The error has an empty **Err.Source** and the standard text of its number, `File not found` for 53, without the path. + +> [!IMPORTANT] +> The fourth argument of **Scripting.FileSystemObject.OpenTextFile** is a **Tristate** *Format*, and this procedure's is an *encoding*. **TristateTrue** is -1, which is **encAutoDetect**: on writing that means UTF-8, not UTF-16. The default also differs: a file opened for writing here is UTF-8 without a BOM, where the Scripting Runtime writes ANSI. + +This sample appends to a file that has a BOM and LF line endings. The new line gets the line ending of the file, and no second BOM is written: + +```tb check_build check_run +Dim work As String, path As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "log.txt") +StringToTextFile "a" & vbLf & "b" & vbLf, path, encUtf8Bom + +Set ts = OpenTextFile(path, ForAppending) +ts.WriteLine "c" +ts.Close + +Debug.Print GetFileLineEnding(path) = nlUnix ' True +Debug.Print GetFileEncoding(path) = encUtf8Bom ' True +Debug.Print Len(TextFileToString(path)) ' 6 + +DeleteFolder work +``` + +## Enumerations + +The enumerations are declared in the module and are global: **encUtf8**, **ForReading** and the other members are used without a prefix. [**DriveTypeConst**](Drive#drivetypeconst) belongs to the [**Drive**](Drive) class. + +### TextEncoding +{: .no_toc } + +An encoding of text in a file. The value is a Windows code page. A negative value is the same code page with a byte-order mark (BOM): it is written when a file is created with it, and expected when a file is read with it. A code page that has no member is used by its number: + +```tb check_build check_run +Dim work As String, path As String, b() As Byte +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "sjis.txt") + +' Shift-JIS is code page 932. Hiragana A, U+3042, is the two bytes 82 A0. +StringToTextFile ChrW$(&H3042&), path, 932 +ReadStream path, b +Debug.Print Hex$(b(0)) & " " & Hex$(b(1)) ' 82 A0 +Debug.Print AscW(TextFileToString(path, 932)) ' 12354 + +DeleteFolder work +``` + +| Member | Value | Meaning | +|---|---|---| +| encUnknown | -1 | As a result: the encoding could not be found (the file is empty or binary). Same value as **encAutoDetect**. | +| encAutoDetect | -1 | As an argument for reading: find the encoding. As an argument for writing: **encUtf8**. | +| encSystemAnsi | 0 | The ANSI code page of the system. | +| encGB2312 | 936 | Simplified Chinese, GBK. | +| encBig5 | 950 | Traditional Chinese. | +| encUtf16 | 1200 | UTF-16, little endian, no BOM. | +| encUtf16Be | 1201 | UTF-16, big endian, no BOM. | +| encUtf32 | 12000 | UTF-32, little endian, no BOM. | +| encUtf32Be | 12001 | UTF-32, big endian, no BOM. | +| encUsAscii | 20127 | US-ASCII. | +| encLatin1 | 28591 | ISO 8859-1. | +| encLatin9 | 28605 | ISO 8859-15. | +| encGB18030 | 54936 | GB18030, no BOM. | +| encUtf7 | 65000 | UTF-7, no BOM. | +| encUtf8 | 65001 | UTF-8, no BOM. | +| encUtf8Bom | -65001 | UTF-8 with the BOM EF BB BF. | +| encUtf16Bom | -1200 | UTF-16, little endian, with the BOM FF FE. | +| encUtf16BeBom | -1201 | UTF-16, big endian, with the BOM FE FF. | +| encUtf32Bom | -12000 | UTF-32, little endian, with the BOM FF FE 00 00. | +| encUtf32BeBom | -12001 | UTF-32, big endian, with the BOM 00 00 FE FF. | +| encUtf7Bom | -65000 | UTF-7 with the signature `+/v8-`. | +| encGB18030Bom | -54936 | GB18030 with the BOM 84 31 95 33. | + +Only these seven negative members have a BOM. A BOM member given for reading a file that has no BOM reads it with the code page alone. + +### NewlineType +{: .no_toc } + +A line-ending style. + +| Member | Value | Meaning | +|---|---|---| +| nlUnknown | 0 | As a result: the text has no line ending. | +| nlMixed | 0 | As a result: the text has more than one style. | +| nlNoChange | 0 | As an argument: leave the line endings as they are. | +| nlWindows | 1 | Carriage return and line feed (`vbCrLf`). | +| nlUnix | 2 | Line feed (`vbLf`). | +| nlMacintosh | 3 | Carriage return (`vbCr`). | + +### IOMode +{: .no_toc } + +How [**OpenTextFile**](#opentextfile) opens a file. The values are those of the Scripting Runtime. + +| Member | Value | Meaning | +|---|---|---| +| ForReading | 1 | Read. The file must exist. | +| ForWriting | 2 | Write, from an empty file. | +| ForAppending | 8 | Write at the end of the file. | + +### SpecialFolderConst +{: .no_toc } + +A well-known folder, for [**GetSpecialFolder**](#getspecialfolder). The first three members have the values of the Scripting Runtime, and **WindowsFolder**, **SystemFolder** and **TemporaryFolder** are names for them that match it. The rest are known folders of Windows. + +| Member | Value | Folder | +|---|---|---| +| sfWindowsFolder, WindowsFolder | 0 | The Windows folder. | +| sfSystemFolder, SystemFolder | 1 | The `System32` folder. | +| sfTemporaryFolder, TemporaryFolder | 2 | The folder named by the `TEMP` environment variable. | +| sfSystemX86Folder | 3 | The 32-bit system folder (`SysWOW64` on 64-bit Windows). | +| sfProgramFiles | 4 | Program Files. | +| sfProgramFilesX86 | 5 | Program Files (x86). | +| sfProgramData | 6 | ProgramData. | +| sfUserProfile | 7 | The profile folder of the current user. | +| sfDesktop | 8 | The desktop of the current user. | +| sfDocuments | 9 | Documents of the current user. | +| sfDownloads | 10 | Downloads of the current user. | +| sfPictures | 11 | Pictures of the current user. | +| sfMusic | 12 | Music of the current user. | +| sfVideos | 13 | Videos of the current user. | +| sfLocalAppData | 14 | Local application data (`AppData\Local`). | +| sfRoamingAppData | 15 | Roaming application data (`AppData\Roaming`). | +| sfPublicDesktop | 16 | The desktop shared by all users. | +| sfPublicDocuments | 17 | Documents shared by all users. | +| sfPublicDownloads | 18 | Downloads shared by all users. | +| sfPublicPictures | 19 | Pictures shared by all users. | +| sfPublicMusic | 20 | Music shared by all users. | +| sfPublicVideos | 21 | Videos shared by all users. | +| sfFonts | 22 | Fonts. | +| sfStartup | 23 | The Startup folder of the current user. | +| sfCommonStartup | 24 | The Startup folder of all users. | + +### FileAttribute +{: .no_toc } + +The attributes of a file or folder, for [**File.Attributes**](File#attributes), [**Folder.Attributes**](Folder#attributes) and [**NormalizeTextFile**](#normalizetextfile). The values are those of Windows. Combine them with **Or**. + +Reading an attribute set returns every attribute that the file system reports. Writing applies only the settable ones, listed first; the others are dropped without an error. **faNormal** means no attributes, and it wins over any other member it is combined with: `faNormal Or faReadOnly` clears every settable attribute. The FSO values *Volume* (8) and *Alias* (1024) are not members: the label of a volume is [**Drive.VolumeName**](Drive#volumename), and an alias is **faReparsePoint**. + +| Member | Value | Meaning | +|---|---|---| +| faNoChange | -1 | As an argument: keep the attributes. | +| faNormal, Normal | 128 | No attributes. Settable. | +| faReadOnly, ReadOnly | 1 | Read-only. Settable. | +| faHidden, Hidden | 2 | Hidden. Settable. | +| faSystem, System | 4 | System file. Settable. | +| faArchive, Archive | 32 | Changed since the last backup. Settable. | +| faTemporary | 256 | Temporary file. Settable. | +| faNotContentIndexed | 8192 | Not indexed by the content indexing service. Settable. | +| faDirectory, Directory | 16 | A folder. | +| faCompressed, Compressed | 2048 | Compressed. | +| faEncrypted | 16384 | Encrypted. | +| faReparsePoint | 1024 | A reparse point: a junction, a symbolic link or a cloud placeholder. | +| faSparseFile | 512 | A sparse file. | +| faOffline | 4096 | The content is not available locally. | +| faIntegrityStream | 32768 | A checksummed stream (NTFS, ReFS). | +| faVirtual | 65536 | Reserved by Windows. | +| faNoScrubData | 131072 | Excluded from the integrity scrubber. | +| faEA | 262144 | Has extended attributes. Same value as **faRecallOnOpen**. | +| faRecallOnOpen | 262144 | A cloud file recalled when it is opened. | +| faPinned | 524288 | A cloud file kept on this computer. | +| faUnpinned | 1048576 | A cloud file that may be removed from this computer. | +| faRecallOnDataAccess | 4194304 | A cloud file recalled when its data is read. | +| faStrictlySequential | 536870912 | Meant for sequential access. | + +### FileVersionSource +{: .no_toc } + +Which version a file reports, for [**GetFileVersion**](#getfileversion). + +| Member | Value | Meaning | +|---|---|---| +| fvFixed | 0 | The binary version of the file, four numbers. | +| fvFileString | 1 | The `FileVersion` text of the file. | +| fvProductString | 2 | The `ProductVersion` text of the file. | + +### StandardStreamTypes +{: .no_toc } + +A standard stream of the process, for [**GetStandardStream**](#getstandardstream). The values are those of the Scripting Runtime. + +| Member | Value | Meaning | +|---|---|---| +| StdIn | 0 | Standard input. | +| StdOut | 1 | Standard output. | +| StdErr | 2 | Standard error. | + +## See Also + +- [FileSystemObject](FileSystemObject) class -- the same procedures as the members of a COM object +- [File](File) class -- a file, returned by **GetFile** +- [Folder](Folder) class -- a folder, returned by **GetFolder** +- [Drive](Drive) class -- a drive, returned by **GetDrive** +- [TextStream](TextStream) class -- the text stream returned by **OpenTextFile** and **CreateTextFile** +- [FileCopy](../../../Modules/FileSystem/FileCopy) function -- the VBA statement that copies one file +- [Kill](../../../Modules/FileSystem/Kill) function -- the VBA statement that deletes files +- [Open](../../../Core/Open) statement -- the VBA statement that opens a file for input and output diff --git a/docs/Reference/Contributed/tbFileSysTools/Files.md b/docs/Reference/Contributed/tbFileSysTools/Files.md new file mode 100644 index 000000000..b1f8d343d --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/Files.md @@ -0,0 +1,94 @@ +--- +title: Files +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/Files +has_toc: false +--- + +# Files class +{: .no_toc } + +A **Files** is a read-only collection of [**File**](File) objects: the files in one folder. It counts them, returns one by name or by position, and is enumerated with **For Each**. + +A **Files** is not created with **New**. [**Folder.Files**](Folder#files) returns one. It holds the files that the folder had when the property was read; files created or deleted afterwards are not added to it or removed from it. Each **File** in it reads the disk when it is used, so a file that was deleted after the collection was made raises error 53 when it is touched. Hidden and system files are in the collection. + +```tb check_build check_run +Dim work As String, fs As Files, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "hello", BuildPath(work, "a.txt") +StringToTextFile "hello world", BuildPath(work, "b.txt") + +Set fs = GetFolder(work).Files +Debug.Print fs.Count ' 2 +Debug.Print fs.Item("b.txt").Size ' 11 +Debug.Print fs(1).Name ' a.txt +Debug.Print fs("A.TXT").Name ' a.txt +For Each f In fs + Debug.Print f.Name +Next +' Output: +' 2 +' 11 +' a.txt +' a.txt +' a.txt +' b.txt + +DeleteFolder work +``` + +```tb check_build check_run +Dim work As String, fs As Files, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +StringToTextFile "a", BuildPath(work, "a.txt") +Set fs = GetFolder(work).Files + +On Error Resume Next +Set f = fs("missing.txt") +Debug.Print Err.Number ' 5 +Err.Clear +Set f = fs(2) +Debug.Print Err.Number ' 9 +Err.Clear +On Error GoTo 0 + +DeleteFolder work +``` + +## Properties + +### Count +{: .no_toc } + +The number of files in the collection. + +Syntax: *object*.**Count** **As Long** + +### Item +{: .no_toc } + +One file of the collection. This is the default member. + +Syntax: *object*.**Item** ( *nameOrIndex* ) **As File** + +*nameOrIndex* +: *required* A **Variant**: the name of the file with its extension, such as `"readme.txt"`, in any letter case, or the position of the file, counted from 1. + +The files are in the order in which Windows lists the folder. A name that is not in the collection raises error 5, and a position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). + +The Scripting Runtime takes a name only: it raises error 53 (*File not found*) for a name that is not in the collection, and error 5 for a position. + +### NewEnum +{: .no_toc } + +The enumerator that **For Each** uses. It is hidden, and is not called from code. + +Syntax: *object*.**NewEnum** **As IUnknown** + +## See Also + +- [Folder](Folder#files) class -- **Files** returns this collection +- [File](File) class -- the type of its items +- [Folders](Folders) class and [Drives](Drives) class -- the other two collections diff --git a/docs/Reference/Contributed/tbFileSysTools/Folder.md b/docs/Reference/Contributed/tbFileSysTools/Folder.md new file mode 100644 index 000000000..8ab6f0a36 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/Folder.md @@ -0,0 +1,329 @@ +--- +title: Folder +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/Folder +has_toc: false +--- + +# Folder class +{: .no_toc } + +A **Folder** represents one folder on disk: its name, dates, attributes and total size, the files and subfolders in it, and operations to copy, move, rename and delete it. It has the members of the **Folder** of the Scripting Runtime, and adds [**HasAttribute**](#hasattribute), [**SetAttribute**](#setattribute), [**Refresh**](#refresh) and [**Rename**](#rename). + +A **Folder** is not created with **New**. [**GetFolder**](FileSystemTools#getfolder) and [**CreateFolder**](FileSystemTools#createfolder) return one, and so do [**SubFolders**](#subfolders), [**ParentFolder**](#parentfolder) and [**Drive.RootFolder**](Drive#rootfolder). + +```tb check_build check_run +Dim work As String, fd As Folder, f As File +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder BuildPath(work, "sub"), True +StringToTextFile "hello", BuildPath(work, "a.txt") +StringToTextFile "hello world", BuildPath(work, "b.txt") +StringToTextFile "abc", BuildPath(work, "sub\c.txt") + +Set fd = GetFolder(work) +Debug.Print fd.Files.Count +Debug.Print fd.SubFolders.Count +Debug.Print fd.Size +For Each f In fd.Files + Debug.Print f.Name & " " & f.Size +Next +' Output: +' 2 +' 1 +' 19 +' a.txt 5 +' b.txt 11 + +DeleteFolder work +``` + +## A Folder reads the disk each time + +A **Folder** keeps only the path of the folder. Each member that reads a property looks at the folder again, so the values are never out of date, and a folder that was deleted or renamed by something else raises error 76 (*Path not found*) at the next use. The name is returned with the letter case that the file system stores. [**Rename**](#rename) and the [**Name**](#name) setter raise error 53 instead for a folder that is gone. + +The price is that every read costs a call to Windows. Read a value once and keep it. [**Size**](#size) costs the most: it goes through the whole tree on every read. + +[**Files**](#files) and [**SubFolders**](#subfolders) return the members of the folder at the moment of the call. The collection does not follow later changes, so the property is read again to see them. The objects in it are live, as every **File** and **Folder** is. + +The members that change the folder ([**Move**](#move), [**Rename**](#rename), [**Name**](#name) and [**Delete**](#delete)) are those of [**FileSystemTools**](FileSystemTools), with the object's path as the source: their errors are described there. **Move** and **Rename** point the object at the new path, so it stays usable. [**Copy**](#copy) leaves the object as it is and does not return the copy. After **Delete**, every member that reads raises error 76. + +## Properties + +### Attributes +{: .no_toc } + +The attributes of the folder. + +Syntax: *object*.**Attributes** [ **=** *value* ] + +Reading returns a [**FileAttribute**](FileSystemTools#fileattribute) combination of every attribute that the file system reports, **faDirectory** among them. Setting replaces the settable attributes with *value*; the other bits are dropped without an error. **faNormal** clears all settable attributes, also when it is combined with others. **faNoChange** does nothing. To change one attribute and leave the rest, use [**SetAttribute**](#setattribute). + +Raises error 76 when the folder does not exist, and error 70 when Windows refuses the change. + +### DateCreated +{: .no_toc } + +The date and time at which the folder was created. + +Syntax: *object*.**DateCreated** **As Date** + +Local time, to the second. It is the date 0 (30 December 1899) when the file system holds no value. + +### DateLastAccessed +{: .no_toc } + +The date and time at which the folder was last accessed. + +Syntax: *object*.**DateLastAccessed** **As Date** + +Local time, to the second. Windows can be set to update this value late or not at all. + +### DateLastModified +{: .no_toc } + +The date and time at which the folder was last changed. + +Syntax: *object*.**DateLastModified** **As Date** + +Local time, to the second. + +### Drive +{: .no_toc } + +The drive that holds the folder. + +Syntax: *object*.**Drive** **As Drive** + +Returns the [**Drive**](Drive) of the path, for a network path the share. + +### Files +{: .no_toc } + +The files in the folder. + +Syntax: *object*.**Files** **As Files** + +Returns a [**Files**](Files) collection of the files directly in the folder. Hidden and system files are in it, which differs from [**GetFilePaths**](FileSystemTools#getfilepaths). The folder is read when the property is read, and the collection does not change afterwards. Raises error 70 when the folder cannot be read, as the Scripting Runtime does. + +> [!NOTE] +> In version 1.1.1.0, **Files**, [**SubFolders**](#subfolders) and [**Size**](#size) raise error 76 (*Path not found*) for a folder whose path has 258 characters or more, although the folder exists and its [**Name**](#name) can be read. The same members work for a folder with 256 characters. + +```tb check_build check_run +Dim work As String, deep As String, n As Long +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +deep = work +Do While Len(deep) < 270 + deep = BuildPath(deep, String$(25, "d")) +Loop +CreateFolder deep, True + +Debug.Print Len(GetFolder(deep).Name) ' 25 +On Error Resume Next +n = GetFolder(deep).Files.Count +Debug.Print Err.Number ' 76 +Err.Clear +On Error GoTo 0 + +DeleteFolder work +``` + +### HasAttribute +{: .no_toc } + +Whether an attribute is set. + +Syntax: *object*.**HasAttribute** ( *attr* ) **As Boolean** + +*attr* +: *required* A [**FileAttribute**](FileSystemTools#fileattribute) member, any attribute including those that cannot be set. + +**True** when the attribute is set. The test is for any bit: with a combination such as `faHidden Or faSystem` it is **True** when either is set. + +### IsRootFolder +{: .no_toc } + +Whether the folder is the root of its drive. + +Syntax: *object*.**IsRootFolder** **As Boolean** + +**True** for `C:\`, and for the root of a network share, `\\server\share`. + +### Name +{: .no_toc } + +The name of the folder. + +Syntax: *object*.**Name** [ **=** *newName* ] + +The name, without the folder above it, with the letter case that the file system stores. It is an empty string for a drive root. Setting it renames the folder as [**Rename**](#rename) does. + +### ParentFolder +{: .no_toc } + +The folder that holds this folder. + +Syntax: *object*.**ParentFolder** **As Folder** + +Returns the [**Folder**](Folder) above, or **Nothing** for a drive root. This is not an error. + +### Path +{: .no_toc } + +The full path of the folder. This is the default member, so an object used where a string is wanted gives its path. + +Syntax: *object*.**Path** **As String** + +The part above the last name has the spelling that was used to get the object, and the last name has the letter case that the file system stores. + +### ShortName +{: .no_toc } + +The 8.3 short name of the folder. + +Syntax: *object*.**ShortName** **As String** + +The short name, or the ordinary name when the folder has none. Compare the result with [**Name**](#name) to see whether a short name was found. + +### ShortPath +{: .no_toc } + +The 8.3 short path of the folder. + +Syntax: *object*.**ShortPath** **As String** + +The short path, or the full path unchanged when none is available, for the reasons given for [**ShortName**](#shortname). + +### Size +{: .no_toc } + +The total size of the files in the folder and in all folders below it, in bytes. + +Syntax: *object*.**Size** **As LongLong** + +The tree is read again on every call, which takes time for a large tree. Hidden and system files are counted. A directory junction or symbolic link is not entered, so each file is counted once, where it is stored. The Scripting Runtime enters a link, and counts the files below its target twice when the link leads to a folder in the same tree: for a folder that holds 1,500 bytes, with a junction to one of its subfolders, **Size** returns 1,500 here and 2,500 with the Scripting Runtime (the sample under [Junctions and symbolic links](FileSystemTools#junctions-and-symbolic-links) shows it). + +Raises error 70 when a folder in the tree cannot be read, as the Scripting Runtime does. Raises error 76 when the folder, or a folder below it, has a path of 258 characters or more; see [Files](#files). + +### SubFolders +{: .no_toc } + +The folders directly in the folder. + +Syntax: *object*.**SubFolders** **As Folders** + +Returns a [**Folders**](Folders) collection of the folders directly below this one. Hidden and system folders are in it. The folder is read when the property is read, and the collection does not change afterwards. Raises error 70 when the folder cannot be read. + +### Type +{: .no_toc } + +The type description of the folder. + +Syntax: *object*.**Type** **As String** + +What the Windows shell calls a folder, `File folder` in an English system, the same string as the Scripting Runtime. When the shell cannot answer, the text is `File folder`. + +## Methods + +### Copy +{: .no_toc } + +Copies the folder and its contents. + +Syntax: *object*.**Copy** ( *destination* [, *overwrite* ] ) + +*destination* +: *required* A **String**, the new folder or an existing folder to copy into. + +*overwrite* +: *optional* A **Boolean**. **True** (default) replaces existing files. + +See [**CopyFolder**](FileSystemTools#copyfolder) for the rules and errors, including what happens to a link inside the folder. + +### CreateTextFile +{: .no_toc } + +Creates a text file in the folder. + +Syntax: *object*.**CreateTextFile** ( *fileName* [, *overwrite* [, *encoding* [, *lineEnding* ] ] ] ) **As TextStream** + +*fileName* +: *required* A **String**, the name of the file, or a relative path below the folder. + +*overwrite* +: *optional* A **Boolean**. **True** (default) empties an existing file. **False** raises error 58 when it exists. + +*encoding* +: *optional* A [**TextEncoding**](FileSystemTools#textencoding) member. Default: **encUtf8**, without a BOM. + +*lineEnding* +: *optional* A [**NewlineType**](FileSystemTools#newlinetype) member for [**WriteLine**](TextStream#writeline). Default: **nlWindows**. + +Returns a [**TextStream**](TextStream) open for writing, as [**CreateTextFile**](FileSystemTools#createtextfile) does for the path of the folder and *fileName* joined. The third argument is an encoding and not the Boolean *Unicode* of the Scripting Runtime; see the note at that procedure. + +### Delete +{: .no_toc } + +Deletes the folder and its contents. + +Syntax: *object*.**Delete** ( [ *force* ] ) + +*force* +: *optional* A **Boolean**. **True** clears the read-only, hidden and system attributes as the contents are deleted. Default: **False**. + +See [**DeleteFolder**](FileSystemTools#deletefolder), including its warning about links. The object cannot be used afterwards: its members raise error 76. + +### Move +{: .no_toc } + +Moves the folder. + +Syntax: *object*.**Move** ( *destination* ) + +*destination* +: *required* A **String**, the new full name of the folder or an existing folder to move into. + +See [**MoveFolder**](FileSystemTools#movefolder) for the rules and errors. The object then names the folder at its new place. + +### Refresh +{: .no_toc } + +Reads the attributes and dates again. + +Syntax: *object*.**Refresh** + +Every member that reads a property already does this, so the method is needed only to test that the folder still exists. Raises error 76 when it does not. + +### Rename +{: .no_toc } + +Renames the folder. + +Syntax: *object*.**Rename** ( *newName* ) + +*newName* +: *required* A **String**, a bare name or a path to the same parent folder. + +See [**Rename**](FileSystemTools#rename) for the rules and errors. The object then names the folder under its new name. + +### SetAttribute +{: .no_toc } + +Sets or clears one attribute. + +Syntax: *object*.**SetAttribute** ( *attr*, *state* ) + +*attr* +: *required* A [**FileAttribute**](FileSystemTools#fileattribute) member. Only a settable attribute has an effect: **faReadOnly**, **faHidden**, **faSystem**, **faArchive**, **faTemporary** and **faNotContentIndexed**. + +*state* +: *required* A **Boolean**. **True** sets the attribute and **False** clears it. The other attributes stay as they are. + +## See Also + +- [File](File) class -- a file, and the members of the **Files** collection +- [Files](Files) class and [Folders](Folders) class -- the collections that **Files** and **SubFolders** return +- [Drive](Drive) class -- the drive that holds a folder +- [FileSystemTools](FileSystemTools#getfolder) module -- **GetFolder** and **CreateFolder** return a **Folder** +- [MkDir](../../../Modules/FileSystem/MkDir) statement -- the VBA statement that creates a folder +- [RmDir](../../../Modules/FileSystem/RmDir) statement -- the VBA statement that removes an empty folder +- [Dir](../../../Modules/FileSystem/Dir) function -- the VBA function that lists the files of a folder diff --git a/docs/Reference/Contributed/tbFileSysTools/Folders.md b/docs/Reference/Contributed/tbFileSysTools/Folders.md new file mode 100644 index 000000000..49e614316 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/Folders.md @@ -0,0 +1,69 @@ +--- +title: Folders +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/Folders +has_toc: false +--- + +# Folders class +{: .no_toc } + +A **Folders** is a read-only collection of [**Folder**](Folder) objects: the folders directly below one folder. It counts them, returns one by name or by position, and is enumerated with **For Each**. + +A **Folders** is not created with **New**. [**Folder.SubFolders**](Folder#subfolders) returns one. It holds the folders that existed when the property was read, and does not follow later changes. Each **Folder** in it reads the disk when it is used, so a folder that was deleted after the collection was made raises error 76 when it is touched. Hidden and system folders are in the collection. Only the first level is in it: to go deeper, read **SubFolders** of each item in turn. + +```tb check_build check_run +Dim work As String, fs As Folders, fd As Folder +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder BuildPath(work, "alpha"), True +CreateFolder BuildPath(work, "beta") + +Set fs = GetFolder(work).SubFolders +Debug.Print fs.Count ' 2 +Debug.Print fs("beta").Name ' beta +For Each fd In fs + Debug.Print fd.Name +Next +' Output: +' 2 +' beta +' alpha +' beta + +DeleteFolder work +``` + +## Properties + +### Count +{: .no_toc } + +The number of folders in the collection. + +Syntax: *object*.**Count** **As Long** + +### Item +{: .no_toc } + +One folder of the collection. This is the default member. + +Syntax: *object*.**Item** ( *nameOrIndex* ) **As Folder** + +*nameOrIndex* +: *required* A **Variant**: the name of the folder, such as `"bin"`, in any letter case, or the position of the folder, counted from 1. + +The folders are in the order in which Windows lists the folder that holds them. A name that is not in the collection raises error 5, and a position below 1 or above [**Count**](#count) raises error 9 (*Subscript out of range*). + +The Scripting Runtime takes a name only: it raises error 76 (*Path not found*) for a name that is not in the collection, and error 5 for a position. It also has an **Add** method, which creates a folder; this collection has none, and [**CreateFolder**](FileSystemTools#createfolder) does that work. + +### NewEnum +{: .no_toc } + +The enumerator that **For Each** uses. It is hidden, and is not called from code. + +Syntax: *object*.**NewEnum** **As IUnknown** + +## See Also + +- [Folder](Folder#subfolders) class -- **SubFolders** returns this collection +- [Files](Files) class and [Drives](Drives) class -- the other two collections diff --git a/docs/Reference/Contributed/tbFileSysTools/TextStream.md b/docs/Reference/Contributed/tbFileSysTools/TextStream.md new file mode 100644 index 000000000..deb8c5127 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/TextStream.md @@ -0,0 +1,312 @@ +--- +title: TextStream +parent: tbFileSysTools Package +permalink: /tB/Packages/Contributed/tbFileSysTools/TextStream +has_toc: false +--- + +# TextStream class +{: .no_toc } + +A **TextStream** reads text from a file, or writes text to one, a piece at a time. It converts between the encoding of the file and the strings of the program, so the code sees text and not bytes, and the file can be of any size. It has the members of the **TextStream** of the Scripting Runtime, and adds [**Flush**](#flush), [**Encoding**](#encoding) and [**IsStreaming**](#isstreaming). + +A **TextStream** is not created with **New**. These return one: + +- [**OpenTextFile**](FileSystemTools#opentextfile) and [**CreateTextFile**](FileSystemTools#createtextfile), and the same members of [**FileSystemObject**](FileSystemObject); +- [**File.OpenAsTextStream**](File#openastextstream) and [**Folder.CreateTextFile**](Folder#createtextfile); +- [**GetStandardStream**](FileSystemTools#getstandardstream), for the standard input, output and error streams. + +```tb check_build check_run +Dim work As String, path As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "log.txt") + +Dim ts As TextStream +Set ts = CreateTextFile(path, True, encUtf16Bom) +ts.WriteLine "alpha" +ts.WriteLine "beta" +ts.Close +Debug.Print GetFile(path).Size ' 28 + +Set ts = OpenTextFile(path) +Debug.Print ts.Encoding = encUtf16Bom ' True +Debug.Print ts.ReadLine ' alpha +Debug.Print ts.Line ' 2 +Debug.Print ts.ReadLine ' beta +Debug.Print ts.AtEndOfStream ' True +ts.Close + +DeleteFolder work +``` + +The members, by purpose: [AtEndOfLine](#atendofline), [AtEndOfStream](#atendofstream), [Read](#read), [ReadAll](#readall), [ReadLine](#readline), [Skip](#skip) and [SkipLine](#skipline) read; [Write](#write), [WriteBlankLines](#writeblanklines), [WriteLine](#writeline) and [Flush](#flush) write; [Close](#close) ends the stream; [Column](#column), [Line](#line), [Encoding](#encoding) and [IsStreaming](#isstreaming) describe it. + +## Reading and writing + +A stream is open for reading or for writing, as the [**IOMode**](FileSystemTools#iomode) of [**OpenTextFile**](FileSystemTools#opentextfile) says; **ForAppending** is a way to write. A read member on a stream open for writing, and a write member on a stream open for reading, raises error 54 (*Stream is not open for reading* or *for writing*). + +**Encoding.** For reading, the encoding is found when the stream is opened: a BOM in the file first, then the *encoding* that was asked for, then the same guess as [**GetFileEncoding**](FileSystemTools#getfileencoding). The BOM is skipped, and is not part of the text. For writing, the *encoding* given when the stream was opened is used, and **encAutoDetect** means UTF-8. A BOM is written once, when a file is created or emptied, by an encoding with **Bom** in its name; text added to an existing file never gets another one. [**Encoding**](#encoding) returns the result. + +**Line endings.** The reading members accept CR LF, LF and CR as line endings, also mixed in one file. [**WriteLine**](#writeline) and [**WriteBlankLines**](#writeblanklines) write the line ending of the stream: the one given when it was opened, or for **ForAppending** to a file that has text, the one that the start of the file uses. [**Write**](#write) writes its text unchanged. + +**Large files.** [**ReadLine**](#readline), [**Read**](#read), [**Write**](#write) and appending have no limit on the size of the file. [**ReadAll**](#readall) is limited by the size of a **String**: it raises error 7 when more than 2 GB remain. The same limit applies to [**TextFileToString**](FileSystemTools#textfiletostring) and to [**ReadStream**](FileSystemTools#readstream) of a whole file, which raise error 7 for a file of 2,200,000,000 bytes; [**File.Size**](File#size) and [**Folder.Size**](Folder#size) return such a size exactly. + +```tb check_build +Dim work As String, big As String, ts As TextStream, b() As Byte +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +big = BuildPath(work, "big.dat") + +' A file of 2,200,000,000 bytes: it needs that much free space. +CreateObject("WScript.Shell").Run "cmd /c fsutil file createnew """ & big & """ 2200000000 >nul", 0, True +Debug.Print GetFile(big).Size ' 2200000000 + +Set ts = OpenTextFile(big, ForReading, , encLatin1) +Debug.Print Len(ts.Read(5)) ' 5 +ts.Close + +Set ts = OpenTextFile(big, ForAppending, , encLatin1) +ts.WriteLine "x" +ts.Close +Debug.Print GetFile(big).Size ' 2200000003 + +ReadStream big, b, 10 +Debug.Print UBound(b) + 1 ' 10 + +DeleteFolder work +``` + +**Two modes.** Most encodings, UTF-8 among them, are read and written in pieces of 64 KB, and [**IsStreaming**](#isstreaming) is **True**. UTF-7 cannot be cut into pieces. A stream in UTF-7 holds the whole file in memory and [**IsStreaming**](#isstreaming) is **False**. A stream of this kind that is open for writing does not empty the file when it is opened: the file is replaced when the stream is closed or flushed. Every member works in both modes. + +**Position.** [**Line**](#line) and [**Column**](#column) follow the characters that are read or written. Reading a line moves past its line ending. + +**Standard streams.** A stream returned by [**GetStandardStream**](FileSystemTools#getstandardstream) has the reading members when it is the standard input, and the writing members when it is the standard output or error. The other direction raises error 54. [**Close**](#close) ends the object and leaves the handle of the process open. [**Encoding**](#encoding) returns **encSystemAnsi** and [**IsStreaming**](#isstreaming) returns **False** for these streams. + +## Errors + +- 52 -- the stream is closed. +- 54 -- the member does not fit the direction of the stream. +- 62 -- reading at or past the end of the stream. +- 5 -- an invalid argument, such as a negative count for [**Read**](#read). +- 7 -- [**ReadAll**](#readall) of more than 2 GB. + +The Scripting Runtime raises 54 and 62 for the same mistakes. For a closed stream it raises 91 (*Object variable not set*), not 52. + +## Properties + +### AtEndOfLine +{: .no_toc } + +Whether the next character to read is a line ending. + +Syntax: *object*.**AtEndOfLine** **As Boolean** + +**True** when the next character is a carriage return or a line feed. **False** at the end of the stream, and for any other character. Nothing is consumed. Raises error 54 on a stream open for writing. + +### AtEndOfStream +{: .no_toc } + +Whether the stream has no more text to read. + +Syntax: *object*.**AtEndOfStream** **As Boolean** + +**True** when everything has been read. The check may read ahead in the file, but it consumes nothing, and it can raise the errors of a read. Raises error 54 on a stream open for writing. On a standard input, it waits until the next input arrives or the input ends. + +### Column +{: .no_toc } + +The column of the next character to read or write. + +Syntax: *object*.**Column** **As Long** + +The first column is 1. Columns count UTF-16 code units, so a character outside the Basic Multilingual Plane, which takes two, advances the column by two. Reading and writing both advance the column. It is 1 after a line ending. + +### Encoding +{: .no_toc } + +The encoding of the stream. + +Syntax: *object*.**Encoding** **As TextEncoding** + +A [**TextEncoding**](FileSystemTools#textencoding) member. For a stream open for reading it is the encoding that was found; for one open for writing, the encoding that was asked for, with **encAutoDetect** read as **encUtf8**. A negative value means that the file has a BOM. An empty file that is opened for reading reports **encUtf8** when no encoding was given. For a standard stream, also one made with *unicode* **True**, the property returns **encSystemAnsi**. + +### IsStreaming +{: .no_toc } + +Whether the stream works in pieces or holds the whole file. + +Syntax: *object*.**IsStreaming** **As Boolean** + +**True** for the encodings that can be read and written in pieces, which is nearly all. **False** when the stream holds the whole file in memory, as described under Two modes above. A standard stream returns **False**. + +### Line +{: .no_toc } + +The line of the next character to read or write. + +Syntax: *object*.**Line** **As Long** + +The first line is 1. Each line ending that is read or written adds 1; CR LF counts once. Writing counts as well: after `Write "abc" & vbCrLf & "de"`, **Line** is 2 and [**Column**](#column) is 3. A CR and an LF written by two separate calls count as two line endings. + +```tb check_build check_run +Dim work As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +Set ts = CreateTextFile(BuildPath(work, "pos.txt")) +ts.Write "abc" & vbCrLf & "de" +Debug.Print ts.Line & "," & ts.Column ' 2,3 +ts.Close + +DeleteFolder work +``` + +## Methods + +### Close +{: .no_toc } + +Ends the stream. + +Syntax: *object*.**Close** + +Writes the text that is still waiting to be written, and closes the file. Calling it again does nothing. A reading or writing member called afterwards raises error 52; [**Line**](#line), [**Column**](#column), [**Encoding**](#encoding) and [**IsStreaming**](#isstreaming) still answer. A stream that is released without **Close** is closed when the object is destroyed, with its waiting text written: after `ts.Write "x"` and `Set ts = Nothing` the file is one byte long. A program should still call **Close**. + +```tb check_build check_run +Dim work As String, path As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "x.txt") + +Set ts = CreateTextFile(path) +ts.Write "x" +Set ts = Nothing +Debug.Print GetFile(path).Size ' 1 + +DeleteFolder work +``` + +### Flush +{: .no_toc } + +Writes the waiting text to the file. + +Syntax: *object*.**Flush** + +For a stream that works in pieces, the text that is held back is encoded and handed to Windows, and the file stays open. This does not wait for the disk to confirm it. For a stream that holds the whole file, the whole file is written again. For a stream open for reading, nothing happens. Raises error 52 when the stream is closed. + +### Read +{: .no_toc } + +Reads a number of characters. + +Syntax: *object*.**Read** ( *characters* ) **As String** + +*characters* +: *required* A **Long**, the number of characters to read. Characters count UTF-16 code units. A value of 0 returns an empty string. A negative value is error 5. + +Returns that many characters, or all that remain when fewer are left. Line endings are returned as they are in the file. Raises error 62 when the stream is already at its end. + +### ReadAll +{: .no_toc } + +Reads everything that is left. + +Syntax: *object*.**ReadAll** **As String** + +Returns the rest of the stream, which is an empty string at the end of the stream, and for an empty file. The Scripting Runtime raises error 62 there. The text is decoded in one step and kept in memory, so a very large file is better read with [**ReadLine**](#readline). Raises error 7, without reading anything, when the rest is more than 2 GB. + +```tb check_build check_run +Dim work As String, path As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "empty.txt") +StringToTextFile "", path + +Set ts = OpenTextFile(path) +Debug.Print "[" & ts.ReadAll & "]" ' [] +ts.Skip 3 ' nothing happens at the end + +On Error Resume Next +ts.SkipLine +Debug.Print Err.Number ' 62 +Err.Clear +On Error GoTo 0 +ts.Close + +DeleteFolder work +``` + +### ReadLine +{: .no_toc } + +Reads one line. + +Syntax: *object*.**ReadLine** **As String** + +Returns the text up to the next line ending, without the line ending. CR LF, LF and CR each end a line. The last line is returned also when no line ending follows it. Raises error 62 when the stream is already at its end. + +### Skip +{: .no_toc } + +Skips a number of characters. + +Syntax: *object*.**Skip** ( *characters* ) + +*characters* +: *required* A **Long**, the number of characters to skip. A value of 0 or less does nothing. + +Does nothing at the end of the stream, where [**SkipLine**](#skipline) raises error 62. The Scripting Runtime raises error 62 for **Skip** at the end too. + +### SkipLine +{: .no_toc } + +Skips one line. + +Syntax: *object*.**SkipLine** + +Skips the text and the line ending of the next line. Raises error 62 when the stream is already at its end. + +### Write +{: .no_toc } + +Writes text. + +Syntax: *object*.**Write** ( *text* ) + +*text* +: *required* A **String**. It is written as it is, and no line ending is added. + +A stream that works in pieces holds the text back until 65,536 characters have collected, or until [**Flush**](#flush) or [**Close**](#close); a stream that holds the whole file writes it at **Flush** or **Close**. A surrogate pair is never cut between two pieces. For a code page that cannot hold a character, the character is replaced by the substitute of the code page, without an error; UTF-8 and GB18030 raise error 5 for an unpaired surrogate. + +### WriteBlankLines +{: .no_toc } + +Writes empty lines. + +Syntax: *object*.**WriteBlankLines** ( *lines* ) + +*lines* +: *required* A **Long**, the number of line endings to write. + +### WriteLine +{: .no_toc } + +Writes text and a line ending. + +Syntax: *object*.**WriteLine** ( [ *text* ] ) + +*text* +: *optional* A **String** to write before the line ending. Default: an empty string, which writes a line ending alone. + +The line ending is the one of the stream: see Line endings above. + +## See Also + +- [FileSystemTools](FileSystemTools) module -- [OpenTextFile](FileSystemTools#opentextfile), [CreateTextFile](FileSystemTools#createtextfile) and [GetStandardStream](FileSystemTools#getstandardstream) create streams +- [File](File#openastextstream) class -- **OpenAsTextStream** opens a file as a stream +- [Folder](Folder#createtextfile) class -- **CreateTextFile** creates a file in a folder and returns a stream +- [Open](../../../Core/Open) statement -- the VBA statement that opens a file for input and output +- [Line Input #](../../../Core/Line-Input) statement -- the VBA statement that reads a line from a file +- [Print #](../../../Core/Print) statement -- the VBA statement that writes text to a file diff --git a/docs/Reference/Contributed/tbFileSysTools/index.md b/docs/Reference/Contributed/tbFileSysTools/index.md new file mode 100644 index 000000000..5f4dadf30 --- /dev/null +++ b/docs/Reference/Contributed/tbFileSysTools/index.md @@ -0,0 +1,248 @@ +--- +title: tbFileSysTools Package +parent: Contributed Packages +has_toc: false +exclude_from_docs: + - FSTShared + - TextCodec + - WinAPI +indexed_from: 1.1.1.0 +permalink: /tB/Packages/Contributed/tbFileSysTools/ +--- + +# tbFileSysTools Package +{: .no_toc } + +**Author:** GCuser99. **On GitHub:** [GCuser99/tbFileSysTools](https://github.com/GCuser99/tbFileSysTools), in the folder *Package*. + +The **tbFileSysTools** contributed package, listed in the Available Packages dialog as *File System Tools - Modern Replacement for FSO*, reads and changes files, folders and drives, as the **FileSystemObject** of the Scripting Runtime does, and is written in twinBASIC. It reads and writes text files in any Windows code page, with detection of the encoding and handling of byte-order marks (BOMs), detects and converts line endings, streams files of any size, and works with paths longer than 260 characters. + +It has two ways in. The [**FileSystemTools**](FileSystemTools) module holds the code, as procedures that a project calls by name, with no object to create. The [**FileSystemObject**](FileSystemObject) class has the same procedures as members of an object, for code written for the Scripting Runtime. The objects that both return, [**File**](File), [**Folder**](Folder), [**Drive**](Drive) and [**TextStream**](TextStream), and the collections [**Files**](Files), [**Folders**](Folders) and [**Drives**](Drives), have the members of the Scripting Runtime's objects of the same names, and some more. + +```tb check_build check_run +Dim work As String, path As String +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work +path = BuildPath(work, "notes.txt") + +' Write a string as UTF-8 with a byte-order mark, then ask what the file holds. +StringToTextFile "caf" & ChrW$(233) & vbCrLf & "ok", path, encUtf8Bom +Debug.Print GetFileEncoding(path) = encUtf8Bom ' True +Debug.Print GetFileLineEnding(path) = nlWindows ' True +Debug.Print Len(TextFileToString(path)) ' 8 + +' Read it line by line; the encoding is found from the BOM. +Dim ts As TextStream +Set ts = OpenTextFile(path) +Do Until ts.AtEndOfStream + Debug.Print Len(ts.ReadLine) +Loop +ts.Close +' Output: +' True +' True +' 8 +' 4 +' 2 + +DeleteFolder work +``` + +> [!NOTE] +> This section documents version 1.1.1.0, the version on the package server, published under the MIT licence. The GitHub repository has moved on: its README describes version 1.8.0.0, and its change log lists changes from 1.2.0.0 on. Some members that the README names, such as **FindInTextFiles**, **FileContainsText** and **IsFileLocked**, do not exist in 1.1.1.0. The change log also records that later versions removed the *useLongPathPrefix* argument of **GetAbsolutePathName** and the *skipIfUnchanged* argument of **NormalizeTextFile**, which this version has, and that they changed some of the behaviour described on these pages. + +The public types of the package are the module and the eight classes on these pages, and the enumerations that the module declares. Three private modules, **FSTShared** (shared file code and the error mapping), **TextCodec** (encoding, decoding and detection) and **WinAPI** (Windows declarations), are not part of the interface and are not described. + +## Differences from the Scripting Runtime + +A call that works with the Scripting Runtime's **FileSystemObject** keeps its name here. These are the differences. + +**Added members.** + +- Whole files: [**StringToTextFile**](FileSystemTools#stringtotextfile), [**TextFileToString**](FileSystemTools#textfiletostring), [**ArrayToTextFile**](FileSystemTools#arraytotextfile), [**TextFileToArray**](FileSystemTools#textfiletoarray), [**ReadStream**](FileSystemTools#readstream), [**WriteStream**](FileSystemTools#writestream), [**NormalizeTextFile**](FileSystemTools#normalizetextfile) and [**MergeTextFiles**](FileSystemTools#mergetextfiles). +- Detection: [**GetFileEncoding**](FileSystemTools#getfileencoding) and [**GetFileLineEnding**](FileSystemTools#getfilelineending). +- Paths and directories: [**CleanFileName**](FileSystemTools#cleanfilename), [**GetRelativePath**](FileSystemTools#getrelativepath), [**GetFilePaths**](FileSystemTools#getfilepaths), [**GetFileType**](FileSystemTools#getfiletype), [**GetCurrentDir**](FileSystemTools#getcurrentdir), [**SetCurrentDir**](FileSystemTools#setcurrentdir) and [**Rename**](FileSystemTools#rename). +- On [**File**](File): **Encoding**, **ExtensionName**, **HasAttribute**, **LineEnding**, **Normalize**, **Refresh**, **Rename**, **SetAttribute**, **ToStream**, **ToString** and **Version**. On [**Folder**](Folder): **HasAttribute**, **Refresh**, **Rename** and **SetAttribute**. On [**Drive**](Drive): **SerialNumberHex** and **UnsignedSerialNumber**. On [**TextStream**](TextStream): **Encoding**, **Flush** and **IsStreaming**. +- [**Files**](Files), [**Folders**](Folders) and [**Drives**](Drives) return an item by position as well as by name. +- Enumerations for the encodings, line endings and 25 special folders. + +**Members with more arguments.** [**CreateFolder**](FileSystemTools#createfolder) can create the folders above the new one. [**DeleteFile**](FileSystemTools#deletefile) and [**DeleteFolder**](FileSystemTools#deletefolder) can ignore a missing target. [**GetAbsolutePathName**](FileSystemTools#getabsolutepathname) takes a base directory. [**GetTempName**](FileSystemTools#gettempname) takes a prefix, an extension and a folder to check. + +**Missing members.** One: the **Add** method of the **Folders** collection, which creates a folder. [**CreateFolder**](FileSystemTools#createfolder) does that. Every other member of the Scripting Runtime's **FileSystemObject**, **File**, **Folder**, **Drive** and **TextStream** is here, and so are **Count**, **Item** and the enumerator of the three collections. **Drives** of the class [**FileSystemObject**](FileSystemObject#drives) is a function, called with or without parentheses. + +**Arguments that mean something else.** The Scripting Runtime selects the encoding of a text file with a **Boolean** (*Unicode*, in **CreateTextFile**) or a **Tristate** (*Format*, in **OpenTextFile** and **OpenAsTextStream**). Here the argument is a [**TextEncoding**](FileSystemTools#textencoding). Code that passes **True** or **TristateTrue**, which is -1, gets **encAutoDetect**, and for writing that is UTF-8, not UTF-16. The defaults also differ: a text file is written in UTF-8 without a BOM, where the Scripting Runtime writes ANSI, and a file opened for reading has its encoding detected, where the Scripting Runtime reads ANSI. + +**Results that differ.** + +- [**GetSpecialFolder**](FileSystemTools#getspecialfolder) returns the path as a **String**, where the Scripting Runtime returns a **Folder**. It accepts 25 folders, and not only the Windows, System and Temporary folders. +- [**FileAttribute**](FileSystemTools#fileattribute) has no *Volume* (8) and no *Alias* (1024). The label of a volume is [**Drive.VolumeName**](Drive#volumename), and an alias is **faReparsePoint**. [**File**](File#setattribute) and [**Folder**](Folder#setattribute) add **SetAttribute** and **HasAttribute**, which change and test one attribute. +- [**Folder.Size**](Folder#size) does not enter directory junctions and symbolic links. The Scripting Runtime does, and counts the target of a link twice when it is in the same tree: 2,500 bytes there for a folder that holds 1,500. +- [**BuildPath**](FileSystemTools#buildpath) and [**GetParentFolderName**](FileSystemTools#getparentfoldername) turn `/` into `\`. `BuildPath("C:\a", "b/c")` returns `C:\a\b\c` and the Scripting Runtime `C:\a\b/c`; `GetParentFolderName("C:/a/b.txt")` returns `C:\a` and the Scripting Runtime `C:/a`. +- [**GetAbsolutePathName**](FileSystemTools#getabsolutepathname) raises error 52 for an empty string, where the Scripting Runtime returns the current directory. +- [**GetDrive**](FileSystemTools#getdrive) accepts a full path and reduces it to its drive: `GetDrive("C:\Windows")` is `C:`. The Scripting Runtime raises error 5. +- [**CopyFile**](FileSystemTools#copyfile), [**CopyFolder**](FileSystemTools#copyfolder), [**MoveFile**](FileSystemTools#movefile) and [**MoveFolder**](FileSystemTools#movefolder) put the source inside an existing destination folder, whether or not the destination ends in a backslash. The Scripting Runtime does so only with a backslash. Without one it raises error 70 for **CopyFile**, 58 for **MoveFile** and **MoveFolder**, and **CopyFolder** copies the contents of the source straight into the folder. +- [**MoveFile**](FileSystemTools#movefile) moves a folder when *source* names one. The Scripting Runtime raises error 53. +- [**DeleteFolder**](FileSystemTools#deletefolder) of a folder that holds a read-only file raises error 75 without *force*; the Scripting Runtime raises 70. +- [**GetFileVersion**](FileSystemTools#getfileversion) returns an empty string for a file that does not exist. The Scripting Runtime raises an error. +- At the end of a stream, [**ReadAll**](TextStream#readall) returns an empty string and [**Skip**](TextStream#skip) does nothing, where the Scripting Runtime raises error 62. A closed stream raises 52 here and 91 there. +- [**Files.Item**](Files#item), [**Folders.Item**](Folders#item) and [**Drives.Item**](Drives#item) take a position as well as a name. A name that is not in the collection raises error 5; the Scripting Runtime raises 53, 76 and 68 for the three. `Drives("C:")` raises error 5, and the Scripting Runtime accepts it. +- [**GetTempName**](FileSystemTools#gettempname) returns eight random letters and digits and `.tmp`. The Scripting Runtime returns `rad` and five hexadecimal digits. +- A path longer than 260 characters works in most procedures. The Scripting Runtime does not accept it: **FileExists** and **FolderExists** return **False** for it, **GetFile** raises error 53 and **OpenTextFile** raises 76. +- [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile) and [**WriteStream**](FileSystemTools#writestream) replace a read-only file without an error. [**CreateTextFile**](FileSystemTools#createtextfile) raises error 70 for it, as the Scripting Runtime's does. + +**The same.** For these failures the two raise the same error numbers: a missing file (53) or folder (76) from **GetFile**, **GetFolder**, **CopyFile**, **DeleteFile**, **DeleteFolder** and **OpenTextFile**; an existing folder or destination (58) from **CreateFolder**, **CopyFile**, **MoveFile** and **CreateTextFile**; a missing parent folder (76) from **CreateFolder**; a drive that does not exist (68) from **GetDrive**; a read-only destination or file (70) from **CopyFile**, **DeleteFile** and **CreateTextFile**; an unreadable folder (70) from **Files**, **SubFolders** and **Size**; the wrong direction of a stream (54) and a read at the end (62) from **TextStream**. Both enter a junction or symbolic link when they delete a folder, and both match a pattern against the 8.3 name of a file too. + +## Using the package with the Scripting Runtime + +The package declares types that have the same names as those of the Scripting Runtime library: **FileSystemObject**, **File**, **Folder**, **Drive**, **Files**, **Folders**, **Drives**, **TextStream**, and the enumerations **FileAttribute**, **IOMode**, **SpecialFolderConst**, **StandardStreamTypes** and **DriveTypeConst** with their members, such as **ForReading**, **Normal** and **Fixed**. In a project that references both libraries, a bare name is found in the library that is listed first among the references, and no error is reported for the other. With the package first, `Dim fso As New FileSystemObject` creates the package's class, and `Dim f As File` is the package's type. With the Scripting Runtime first, they are the Scripting Runtime's: `f.Encoding` then fails to compile (TB5027, *Unrecognized member 'Encoding' on type 'IFile'*), and `fso.BuildPath("C:\a", "b/c")` returns `C:\a\b/c`. The enumeration members (**ForReading**, **Normal**, **Fixed**) compile in either order, and their values are the same in both libraries. + +Write the name of the library before the type to be sure which one is meant. Both forms compile in either order: + +```tb check_build +Dim fso As New tbFileSysTools.FileSystemObject +Dim f As tbFileSysTools.File +Set f = fso.GetFile(fso.BuildPath(fso.GetSpecialFolder(sfSystemFolder), "kernel32.dll")) +Debug.Print f.Size +``` + +The other ways out are to remove the reference to the Scripting Runtime, or to list **tbFileSysTools** above it in the References dialog. The procedures of the module (**FileExists**, **GetFile**, **CopyFile** and the rest) do not collide with anything in the Scripting Runtime, which has no module-level procedures. + +## Errors + +The procedures raise the error numbers that VB6 uses for the same failures. **Err.Source** names the operation, such as `CopyFile`, and **Err.Description** names the path. The errors of [**OpenTextFile**](FileSystemTools#opentextfile), [**CreateTextFile**](FileSystemTools#createtextfile) and the other procedures that open a stream have an empty **Err.Source** and only the standard text of their number, without the path. Each entry says which numbers it raises. + +- 5 -- *Invalid procedure call or argument*: a wildcard in a destination, a folder copied into itself, a text procedure given a binary file, a value that is not a member of an enumeration, a name that is not in a collection, and the *file not found!* of [**ReadStream**](FileSystemTools#readstream). +- 7 -- *Out of memory*: a whole file that is too large for one array or **String** (more than 2 GB). +- 9 -- *Subscript out of range*: a position outside a collection. +- 52 -- *Bad file name or number*: a name with a character that Windows does not allow, and a closed [**TextStream**](TextStream). +- 53 -- *File not found*. +- 54 -- *Bad file mode*: a read on a stream open for writing, or the reverse. +- 58 -- *File already exists*. The text says *File* also when a folder is in the way. +- 62 -- *Input past end of file*. +- 68 -- *Device unavailable*: a drive letter that is not in use. +- 70 -- *Permission denied*: access is refused, or the file is in use. +- 71 -- *Disk not ready*: a drive with no media. +- 75 -- *Path/File access error*: any other Windows failure. The text ends with the Windows code, such as 145 for a folder that is not empty and 206 for a path that is too long. +- 76 -- *Path not found*. + +Windows reports a failure with its own code, and the package maps it to one of those numbers: *file not found* (2) to 53, *path not found* (3) and *bad directory name* (267) to 76, *file exists* (80 and 183) to 58, *access denied* and the sharing violations (5, 32 and 33) to 70, and *invalid name* (123) to 52. + +> [!NOTE] +> Version 1.1.1.0 raises some errors that the Scripting Runtime does not raise, or other numbers. The sample shows the first three. +> +> - A file name with a character above U+7FFF, such as the Chinese character U+8A9E, the Hangul letter U+AC00 or an emoji, raises error 52 in [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile), [**CreateTextFile**](FileSystemTools#createtextfile), [**OpenTextFile**](FileSystemTools#opentextfile) with *create* **True**, the output file of [**MergeTextFiles**](FileSystemTools#mergetextfiles) and [**Folder.CreateTextFile**](Folder#createtextfile). The Scripting Runtime accepts the names, and so do [**WriteStream**](FileSystemTools#writestream) and [**CreateFolder**](FileSystemTools#createfolder). +> - A missing file raises error 5 and not 53 in [**TextFileToString**](FileSystemTools#textfiletostring), [**TextFileToArray**](FileSystemTools#textfiletoarray), [**GetFileEncoding**](FileSystemTools#getfileencoding), [**GetFileLineEnding**](FileSystemTools#getfilelineending), [**NormalizeTextFile**](FileSystemTools#normalizetextfile), [**ReadStream**](FileSystemTools#readstream) and the first file of [**MergeTextFiles**](FileSystemTools#mergetextfiles). [**OpenTextFile**](FileSystemTools#opentextfile) and [**GetFile**](FileSystemTools#getfile) raise 53. +> - A folder that does not exist raises the unspecified error -2147467259, not 76, in [**StringToTextFile**](FileSystemTools#stringtotextfile), [**ArrayToTextFile**](FileSystemTools#arraytotextfile) and [**WriteStream**](FileSystemTools#writestream). [**CreateTextFile**](FileSystemTools#createtextfile) and [**OpenTextFile**](FileSystemTools#opentextfile) raise 76. +> - [**Folder.Files**](Folder#files), [**SubFolders**](Folder#subfolders) and [**Size**](Folder#size) raise error 76 for a folder whose path has 258 characters or more. + +```tb check_build check_run +Dim work As String, s As String, ts As TextStream +work = BuildPath(Environ$("TEMP"), GetTempName("fst", "")) +CreateFolder work + +On Error Resume Next +StringToTextFile "x", BuildPath(work, ChrW$(&H8A9E&) & ".txt") +Debug.Print Err.Number ' 52 +Err.Clear +StringToTextFile "x", BuildPath(work, ChrW$(&H65E5&) & ".txt") +Debug.Print Err.Number ' 0 +s = TextFileToString(BuildPath(work, "missing.txt")) +Debug.Print Err.Number ' 5 +Err.Clear +Set ts = OpenTextFile(BuildPath(work, "missing.txt")) +Debug.Print Err.Number ' 53 +Err.Clear +StringToTextFile "x", BuildPath(work, "nofolder\x.txt") +Debug.Print Err.Number ' -2147467259 +Err.Clear +On Error GoTo 0 + +DeleteFolder work +``` + +Test with [**FileExists**](FileSystemTools#fileexists) first when the number of a missing-file error matters. + +## Classes and modules + +- [FileSystemTools](FileSystemTools) -- the module that holds the procedures, and the enumerations + - [ArrayToTextFile](FileSystemTools#arraytotextfile) -- writes the elements of a String array to a text file, one per line + - [BuildPath](FileSystemTools#buildpath) -- joins a folder path and a name + - [CleanFileName](FileSystemTools#cleanfilename) -- removes from a file name the characters that Windows does not allow + - [CopyFile](FileSystemTools#copyfile) -- copies one or more files + - [CopyFolder](FileSystemTools#copyfolder) -- copies a folder and everything in it + - [CreateFolder](FileSystemTools#createfolder) -- creates a folder + - [CreateTextFile](FileSystemTools#createtextfile) -- creates a text file and returns a TextStream open for writing + - [DeleteFile](FileSystemTools#deletefile) -- deletes one or more files + - [DeleteFolder](FileSystemTools#deletefolder) -- deletes a folder and everything in it + - [DriveExists](FileSystemTools#driveexists) -- returns whether a drive exists + - [FileExists](FileSystemTools#fileexists) -- returns whether a file exists + - [FolderExists](FileSystemTools#folderexists) -- returns whether a folder exists + - [GetAbsolutePathName](FileSystemTools#getabsolutepathname) -- returns the full path for a path specification + - [GetBaseName](FileSystemTools#getbasename) -- returns the last component of a path without its extension + - [GetCurrentDir](FileSystemTools#getcurrentdir) -- returns the current directory + - [GetDrive](FileSystemTools#getdrive) -- returns a Drive object + - [GetDriveName](FileSystemTools#getdrivename) -- returns the drive part of a path + - [GetDrives](FileSystemTools#getdrives) -- returns a Drives collection of every drive + - [GetExtensionName](FileSystemTools#getextensionname) -- returns the extension of the last component of a path + - [GetFile](FileSystemTools#getfile) -- returns a File object + - [GetFileEncoding](FileSystemTools#getfileencoding) -- returns the text encoding of a file + - [GetFileLineEnding](FileSystemTools#getfilelineending) -- returns the line-ending style of a text file + - [GetFileName](FileSystemTools#getfilename) -- returns the last component of a path + - [GetFilePaths](FileSystemTools#getfilepaths) -- returns the paths of the files in a folder + - [GetFileType](FileSystemTools#getfiletype) -- returns the type description of a file or folder + - [GetFileVersion](FileSystemTools#getfileversion) -- returns the version of an executable file + - [GetFolder](FileSystemTools#getfolder) -- returns a Folder object + - [GetParentFolderName](FileSystemTools#getparentfoldername) -- returns the path of the folder that holds the last component of a path + - [GetRelativePath](FileSystemTools#getrelativepath) -- returns the path that leads from one path to another + - [GetSpecialFolder](FileSystemTools#getspecialfolder) -- returns the path of a well-known Windows folder + - [GetStandardStream](FileSystemTools#getstandardstream) -- returns a TextStream for standard input, output or error + - [GetTempName](FileSystemTools#gettempname) -- returns a random name for a temporary file or folder + - [MergeTextFiles](FileSystemTools#mergetextfiles) -- joins two text files into one + - [MoveFile](FileSystemTools#movefile) -- moves one or more files + - [MoveFolder](FileSystemTools#movefolder) -- moves one or more folders + - [NormalizeTextFile](FileSystemTools#normalizetextfile) -- rewrites a text file with a chosen encoding and line ending + - [OpenTextFile](FileSystemTools#opentextfile) -- opens a text file and returns a TextStream + - [ReadStream](FileSystemTools#readstream) -- reads a file into a byte array + - [Rename](FileSystemTools#rename) -- renames a file or folder in place + - [SetCurrentDir](FileSystemTools#setcurrentdir) -- sets the current directory + - [StringToTextFile](FileSystemTools#stringtotextfile) -- writes a string to a text file + - [TextFileToArray](FileSystemTools#textfiletoarray) -- reads a text file into an array of lines + - [TextFileToString](FileSystemTools#textfiletostring) -- reads a text file into a string + - [WriteStream](FileSystemTools#writestream) -- writes a byte array to a file + - [TextEncoding](FileSystemTools#textencoding), [NewlineType](FileSystemTools#newlinetype), [IOMode](FileSystemTools#iomode), [SpecialFolderConst](FileSystemTools#specialfolderconst), [FileAttribute](FileSystemTools#fileattribute), [FileVersionSource](FileSystemTools#fileversionsource) and [StandardStreamTypes](FileSystemTools#standardstreamtypes) -- the enumerations +- [FileSystemObject](FileSystemObject) -- a class with the procedures of the module as members, for code written for the Scripting Runtime +- [Drive](Drive) -- a disk drive or a network share + - [AvailableSpace](Drive#availablespace) -- returns the free bytes that the current user can use + - [DriveLetter](Drive#driveletter) -- returns the letter of the drive + - [DriveType](Drive#drivetype) -- returns the type of the drive + - [FileSystem](Drive#filesystem) -- returns the name of the file system + - [FreeSpace](Drive#freespace) -- returns the free bytes on the volume + - [IsReady](Drive#isready) -- returns whether the drive can be queried + - [Path](Drive#path) -- returns the path of the drive (default member) + - [RootFolder](Drive#rootfolder) -- returns the root folder of the drive + - [SerialNumber](Drive#serialnumber) -- returns the serial number as a signed Long + - [SerialNumberHex](Drive#serialnumberhex) -- returns the serial number in the form that Explorer shows + - [ShareName](Drive#sharename) -- returns the network path of the drive + - [TotalSize](Drive#totalsize) -- returns the size of the volume + - [UnsignedSerialNumber](Drive#unsignedserialnumber) -- returns the serial number as an unsigned number + - [VolumeName](Drive#volumename) -- returns or sets the label of the volume +- [Drives](Drives) -- the collection of the drives +- [File](File) -- a file + - [Attributes](File#attributes), [DateCreated](File#datecreated), [DateLastAccessed](File#datelastaccessed), [DateLastModified](File#datelastmodified), [Drive](File#drive), [Encoding](File#encoding), [ExtensionName](File#extensionname), [HasAttribute](File#hasattribute), [LineEnding](File#lineending), [Name](File#name), [ParentFolder](File#parentfolder), [Path](File#path), [ShortName](File#shortname), [ShortPath](File#shortpath), [Size](File#size), [Type](File#type), [Version](File#version) -- the properties + - [Copy](File#copy), [Delete](File#delete), [Move](File#move), [Normalize](File#normalize), [OpenAsTextStream](File#openastextstream), [Refresh](File#refresh), [Rename](File#rename), [SetAttribute](File#setattribute), [ToStream](File#tostream), [ToString](File#tostring) -- the methods +- [Files](Files) -- the collection of the files of a folder +- [Folder](Folder) -- a folder + - [Attributes](Folder#attributes), [DateCreated](Folder#datecreated), [DateLastAccessed](Folder#datelastaccessed), [DateLastModified](Folder#datelastmodified), [Drive](Folder#drive), [Files](Folder#files), [HasAttribute](Folder#hasattribute), [IsRootFolder](Folder#isrootfolder), [Name](Folder#name), [ParentFolder](Folder#parentfolder), [Path](Folder#path), [ShortName](Folder#shortname), [ShortPath](Folder#shortpath), [Size](Folder#size), [SubFolders](Folder#subfolders), [Type](Folder#type) -- the properties + - [Copy](Folder#copy), [CreateTextFile](Folder#createtextfile), [Delete](Folder#delete), [Move](Folder#move), [Refresh](Folder#refresh), [Rename](Folder#rename), [SetAttribute](Folder#setattribute) -- the methods +- [Folders](Folders) -- the collection of the folders of a folder +- [TextStream](TextStream) -- reads or writes a text file a piece at a time + - [AtEndOfLine](TextStream#atendofline), [AtEndOfStream](TextStream#atendofstream), [Column](TextStream#column), [Encoding](TextStream#encoding), [IsStreaming](TextStream#isstreaming), [Line](TextStream#line) -- the properties + - [Close](TextStream#close), [Flush](TextStream#flush), [Read](TextStream#read), [ReadAll](TextStream#readall), [ReadLine](TextStream#readline), [Skip](TextStream#skip), [SkipLine](TextStream#skipline), [Write](TextStream#write), [WriteBlankLines](TextStream#writeblanklines), [WriteLine](TextStream#writeline) -- the methods + +## See Also + +- [Open](../../../Core/Open) statement -- the VBA statement that opens a file for input and output +- [FileSystem](../../../Modules/FileSystem/) module -- the VBA functions and statements for files and folders: **FileCopy**, **Kill**, **MkDir**, **Dir** and the rest +- [FileProps](../FileProps/) package -- the Windows shell properties of a file +- [TBMANLIB_Dialog](../TBMANLIB_Dialog/) package -- the Open, Save and folder dialogs, with path helpers +- [tbRegExp](../tbRegExp/) package -- regular expressions, by the same author diff --git a/docs/_book.yml b/docs/_book.yml index 446d6a89a..6519e89f6 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -523,6 +523,13 @@ parts: landing_is_target: true outline_closed: true + - title: tbFileSysTools Package + subtitle: Files, folders, drives and text streams, in place of the Scripting Runtime's FileSystemObject + landing_page: /tB/Packages/Contributed/tbFileSysTools/ + page: /tB/Packages/Contributed/tbFileSysTools/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Dialog Package subtitle: Open, Save and folder dialogs in a class, with file filters and path helpers landing_page: /tB/Packages/Contributed/TBMANLIB_Dialog/ diff --git a/indexer/snapshots/contributed/tbFileSysTools/api.json b/indexer/snapshots/contributed/tbFileSysTools/api.json new file mode 100644 index 000000000..8422896ff --- /dev/null +++ b/indexer/snapshots/contributed/tbFileSysTools/api.json @@ -0,0 +1,1485 @@ +{ + "format": 3, + "package": "tbFileSysTools", + "project": "tbFileSysTools", + "version": "1.1.1.0", + "types": [ + { + "name": "Drive", + "kind": "class", + "vis": "public", + "file": "Drive.twin", + "signature": "Public Class Drive", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "Provides access to the properties of a particular disk drive or network share", + "members": [ + {"name":"AvailableSpace","kind":"property","vis":"public","signature":"Public Property Get AvailableSpace() As LongLong","returnType":"LongLong","accessor":"Get","description":"Free bytes available to the calling user (honours quotas). Raises error 71 if the drive is not ready — check IsReady first."}, + {"name":"DeviceExists","kind":"function","vis":"private","signature":"Private Function DeviceExists() As Boolean","returnType":"Boolean"}, + {"name":"DriveLetter","kind":"property","vis":"public","signature":"Public Property Get DriveLetter() As String","returnType":"String","accessor":"Get","description":"Returns one uppercase letter that identifies the local drive or a network share."}, + {"name":"DriveType","kind":"property","vis":"public","signature":"Public Property Get DriveType() As DriveTypeConst","returnType":"DriveTypeConst","accessor":"Get","description":"Returns the type of a specified drive."}, + {"name":"ERROR_BAD_NETPATH","kind":"const","vis":"private","signature":"Private Const ERROR_BAD_NETPATH As Long = 53"}, + {"name":"ERROR_FILE_NOT_FOUND","kind":"const","vis":"private","signature":"Private Const ERROR_FILE_NOT_FOUND As Long = 2"}, + {"name":"ERROR_INVALID_DRIVE","kind":"const","vis":"private","signature":"Private Const ERROR_INVALID_DRIVE As Long = 15"}, + {"name":"ERROR_NOT_READY","kind":"const","vis":"private","signature":"Private Const ERROR_NOT_READY As Long = 21"}, + {"name":"ERROR_PATH_NOT_FOUND","kind":"const","vis":"private","signature":"Private Const ERROR_PATH_NOT_FOUND As Long = 3"}, + {"name":"FileSystem","kind":"property","vis":"public","signature":"Public Property Get FileSystem() As String","returnType":"String","accessor":"Get","description":"The file system name, e.g. NTFS, FAT32, exFAT. Raises error 71 if the drive is not ready."}, + {"name":"FreeSpace","kind":"property","vis":"public","signature":"Public Property Get FreeSpace() As LongLong","returnType":"LongLong","accessor":"Get","description":"Free bytes on the volume. Raises error 71 if the drive is not ready — check IsReady first."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal root As String)"}, + {"name":"IsReady","kind":"property","vis":"public","signature":"Public Property Get IsReady() As Boolean","returnType":"Boolean","accessor":"Get","description":"True if the drive is ready (media present, queryable). Never raises — check this before reading any size or volume property."}, + {"name":"Path","kind":"property","vis":"public","signature":"Public Property Get Path() As String","returnType":"String","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns an uppercase letter followed by a colon that indicates the path name for a specified drive."}, + {"name":"QueryDiskSpace","kind":"sub","vis":"private","signature":"Private Sub QueryDiskSpace(ByRef availToUser As LongLong, ByRef total As LongLong, ByRef free As LongLong, ByVal member As String)"}, + {"name":"QueryVolumeInfo","kind":"sub","vis":"private","signature":"Private Sub QueryVolumeInfo(ByRef label As String, ByRef fsName As String, ByRef serial As Long, ByVal member As String)"}, + {"name":"RootFolder","kind":"property","vis":"public","signature":"Public Property Get RootFolder() As Folder","returnType":"Folder","accessor":"Get","description":"Returns a Folder object that represents the root folder of a specified drive."}, + {"name":"RootPath","kind":"function","vis":"private","signature":"Private Function RootPath() As String","returnType":"String"}, + {"name":"SerialNumber","kind":"property","vis":"public","signature":"Public Property Get SerialNumber() As Long","returnType":"Long","accessor":"Get","description":"Returns the serial number of a specified drive, as a signed Long (FSO parity — a serial with its high bit set reports as a negative number). See SerialNumberHex for the conventional display form."}, + {"name":"SerialNumberHex","kind":"property","vis":"public","signature":"Public Property Get SerialNumberHex() As String","returnType":"String","accessor":"Get","description":"The drive's serial number in the conventional Explorer/`vol` display form, e.g. \"D4A1-B2C3\"."}, + {"name":"ShareName","kind":"property","vis":"public","signature":"Public Property Get ShareName() As String","returnType":"String","accessor":"Get","description":"The UNC path for this drive: the share itself if the object was built from a UNC, the resolved target if it is a mapped network drive, or an empty string for a local drive."}, + {"name":"TotalSize","kind":"property","vis":"public","signature":"Public Property Get TotalSize() As LongLong","returnType":"LongLong","accessor":"Get","description":"Returns the total size of a specified drive or network share. Raises error 71 if the drive is not ready — check IsReady first."}, + {"name":"UnsignedSerialNumber","kind":"property","vis":"public","signature":"Public Property Get UnsignedSerialNumber() As LongLong","returnType":"LongLong","accessor":"Get","description":"The drive's serial number as an unsigned 32-bit value (0 to 4294967295)."}, + {"name":"VOL_BUF","kind":"const","vis":"private","signature":"Private Const VOL_BUF As Long = 261"}, + {"name":"VolumeName","kind":"property","vis":"public","signature":"Public Property Get VolumeName() As String","returnType":"String","accessor":"Get","description":"Sets/Gets the volume label. The setter raises on failure — 71 if the drive is not ready, 70 if you lack permission (labelling requires write access to the volume root)."}, + {"name":"VolumeName","kind":"property","vis":"public","signature":"Public Property Let VolumeName(ByVal value As String)","accessor":"Let"}, + {"name":"mRoot","kind":"field","vis":"private","signature":"Private mRoot As String"} + ] + }, + { + "name": "DriveTypeConst", + "kind": "enum", + "in": "Drive", + "vis": "public", + "file": "Drive.twin", + "signature": "Public Enum DriveTypeConst", + "values": [ + {"name":"UnknownType","value":"0"}, + {"name":"Removable","value":"1"}, + {"name":"Fixed","value":"2"}, + {"name":"Remote","value":"3"}, + {"name":"CDRom","value":"4"}, + {"name":"RamDisk","value":"5"} + ] + }, + { + "name": "Drives", + "kind": "class", + "vis": "public", + "file": "Drives.twin", + "signature": "Public Class Drives", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "A read-only collection of Drive objects, enumerable with For Each.", + "members": [ + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Returns the number of Drive objects available."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal items As Collection)"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal letterOrIndex As Variant) As [Drive]","returnType":"[Drive]","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns a specific Drive object by name or index."}, + {"name":"NewEnum","kind":"function","vis":"public","signature":"Public Function NewEnum() As IUnknown","returnType":"IUnknown","hidden":true,"attrs":[{"name":"Enumerator"},{"name":"Hidden","args":"True"}]}, + {"name":"mItems","kind":"field","vis":"private","signature":"Private mItems As Collection"} + ] + }, + { + "name": "FSTShared", + "kind": "module", + "vis": "private", + "public": false, + "file": "FSTShared.twin", + "signature": "Private Module FSTShared", + "members": [ + {"name":"AddKeyed","kind":"sub","vis":"public","signature":"Public Sub AddKeyed(ByRef c As Collection, ByVal item As Variant, ByVal key As String)"}, + {"name":"AddLongPathPrefix","kind":"function","vis":"public","signature":"Public Function AddLongPathPrefix(ByVal p As String) As String","returnType":"String"}, + {"name":"ApiPath","kind":"function","vis":"public","signature":"Public Function ApiPath(ByVal canonicalAbsPath As String) As String","returnType":"String"}, + {"name":"EnsureTrailingBackslash","kind":"function","vis":"public","signature":"Public Function EnsureTrailingBackslash(ByVal s As String) As String","returnType":"String"}, + {"name":"FileTimeToDate","kind":"function","vis":"public","signature":"Public Function FileTimeToDate(ByRef ft As FILETIME) As Date","returnType":"Date"}, + {"name":"FindDataFileName","kind":"function","vis":"public","signature":"Public Function FindDataFileName(ByRef ffd As WIN32_FIND_DATAW) As String","returnType":"String"}, + {"name":"GetShortPath","kind":"function","vis":"public","signature":"Public Function GetShortPath(ByVal path As String) As String","returnType":"String"}, + {"name":"IncludeByAttr","kind":"function","vis":"public","signature":"Public Function IncludeByAttr(ByVal attr As FILE_ATTRIBUTES, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"IsEmptySearch","kind":"function","vis":"public","signature":"Public Function IsEmptySearch(ByVal errCode As Long, ByVal sourceName As String, ByVal context As String) As Boolean","returnType":"Boolean"}, + {"name":"IsUNC","kind":"function","vis":"private","signature":"Private Function IsUNC(ByVal p As String) As Boolean","returnType":"Boolean"}, + {"name":"Max","kind":"function","vis":"public","signature":"Public Function Max(ParamArray numberList() As Variant) As Variant","returnType":"Variant"}, + {"name":"Min","kind":"function","vis":"public","signature":"Public Function Min(ParamArray numberList() As Variant) As Variant","returnType":"Variant"}, + {"name":"RaiseFromLastError","kind":"sub","vis":"public","signature":"Public Sub RaiseFromLastError(ByVal errCode As Long, ByVal sourceName As String, ByVal context As String, Optional ByVal pathIsDrive As Boolean = False)"}, + {"name":"SETTABLE_ATTRS","kind":"const","vis":"public","signature":"Public Const SETTABLE_ATTRS As Long = FileAttribute.faReadOnly Or FileAttribute.faHidden Or FileAttribute.faSystem Or FileAttribute.faArchive Or FileAttribute.faTemporary Or FileAttribute.faNotContentIndexed Or FileAttribute.faNormal"}, + {"name":"SanitizeAttrs","kind":"function","vis":"public","signature":"Public Function SanitizeAttrs(ByVal value As FileAttribute) As FileAttribute","returnType":"FileAttribute"}, + {"name":"SetPathAttributes","kind":"function","vis":"public","signature":"Public Function SetPathAttributes(ByVal path As String, ByVal attrs As Long) As Long","returnType":"Long"}, + {"name":"SizeFromHL","kind":"function","vis":"public","signature":"Public Function SizeFromHL(ByVal high As Long, ByVal low As Long) As LongLong","returnType":"LongLong"}, + {"name":"StatPath","kind":"function","vis":"public","signature":"Public Function StatPath(ByVal path As String, ByRef attrs As Long, ByRef size As LongLong, ByRef created As Date, ByRef modified As Date, ByRef accessed As Date, ByRef leafName As String) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "File", + "kind": "class", + "vis": "public", + "file": "File.twin", + "signature": "Public Class File", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "An FSO-style File object. Obtain via FileSystem.GetFile or Folder.Files; Not New-able directly.", + "members": [ + {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Get Attributes() As FileAttribute","returnType":"FileAttribute","accessor":"Get","description":"Gets or sets the file's attributes on disk (ReadOnly/Hidden/System/Archive); combine with Or. NoChange is ignored — use Normal to clear all attributes."}, + {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Let Attributes(ByVal value As FileAttribute)","accessor":"Let"}, + {"name":"Copy","kind":"sub","vis":"public","signature":"Public Sub Copy(ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies the file to the destination (overwrites by default)."}, + {"name":"DateCreated","kind":"property","vis":"public","signature":"Public Property Get DateCreated() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was created."}, + {"name":"DateLastAccessed","kind":"property","vis":"public","signature":"Public Property Get DateLastAccessed() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was last accessed."}, + {"name":"DateLastModified","kind":"property","vis":"public","signature":"Public Property Get DateLastModified() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified file was last modified."}, + {"name":"Delete","kind":"sub","vis":"public","signature":"Public Sub Delete(Optional ByVal force As Boolean = False)","description":"Deletes the file."}, + {"name":"Drive","kind":"property","vis":"public","signature":"Public Property Get Drive() As Drive","returnType":"Drive","accessor":"Get","description":"Returns the drive letter of the drive where a specified file or folder resides."}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding(Optional ByVal sampleBytes As Long = 65536&) As TextEncoding","returnType":"TextEncoding","accessor":"Get","description":"Detects a file's text encoding from a head sample: BOM sniff, then UTF-16/32 and UTF-8 heuristics. Returns a signed TextEncoding (negative = BOM present); encUnknown for an empty/unreadable file."}, + {"name":"EnsureCurrent","kind":"sub","vis":"private","signature":"Private Sub EnsureCurrent()"}, + {"name":"ExtensionName","kind":"property","vis":"public","signature":"Public Property Get ExtensionName() As String","returnType":"String","accessor":"Get","description":"Returns a string containing the extension name."}, + {"name":"HasAttribute","kind":"property","vis":"public","signature":"Public Property Get HasAttribute(ByVal attr As FileAttribute) As Boolean","returnType":"Boolean","accessor":"Get","description":"True if the specified attribute is set."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal path As String, ByVal attrs As Long, ByVal size As LongLong, ByVal created As Date, ByVal modified As Date, ByVal accessed As Date)"}, + {"name":"LineEnding","kind":"property","vis":"public","signature":"Public Property Get LineEnding() As NewlineType","returnType":"NewlineType","accessor":"Get","description":"The file's line-ending style on disk (nlWindows / nlUnix / nlMacintosh / nlMixed / nlUnknown). Reads file content on each access."}, + {"name":"Move","kind":"sub","vis":"public","signature":"Public Sub Move(ByVal destination As String)","description":"Moves the file, then re-points this object at the new location."}, + {"name":"Name","kind":"property","vis":"public","signature":"Public Property Get Name() As String","returnType":"String","accessor":"Get","description":"Gets/Sets the name of the file. If setting, The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, + {"name":"Name","kind":"property","vis":"public","signature":"Public Property Let Name(ByVal newName As String)","accessor":"Let"}, + {"name":"Normalize","kind":"function","vis":"public","signature":"Public Function Normalize(Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toLineEnding As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites this file in place: target encoding + newline style (source auto-detected), attributes preserved by default. Returns True if modified."}, + {"name":"OpenAsTextStream","kind":"function","vis":"public","signature":"Public Function OpenAsTextStream(Optional ByVal ioMode As IOMode = ForReading, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens the file and returns a streaming TextStream."}, + {"name":"ParentFolder","kind":"property","vis":"public","signature":"Public Property Get ParentFolder() As Folder","returnType":"Folder","accessor":"Get","description":"Returns the folder object for the parent of the specified file."}, + {"name":"Path","kind":"property","vis":"public","signature":"Public Property Get Path() As String","returnType":"String","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns the path for a specified file."}, + {"name":"Refresh","kind":"sub","vis":"public","signature":"Public Sub Refresh()","description":"Re-reads attributes, size and dates from disk. Raises error 53 if the file no longer exists."}, + {"name":"Rename","kind":"sub","vis":"public","signature":"Public Sub Rename(ByVal newName As String)","description":"Renames the file. The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, + {"name":"SetAttribute","kind":"sub","vis":"public","signature":"Public Sub SetAttribute(ByVal attr As FileAttribute, ByVal state As Boolean)","description":"Sets or clears a single attribute, leaving the others untouched. Only settable attributes have any effect (ReadOnly, Hidden, System, Archive, Temporary, NotContentIndexed)."}, + {"name":"ShortName","kind":"property","vis":"public","signature":"Public Property Get ShortName() As String","returnType":"String","accessor":"Get","description":"Returns the file's 8.3 short name, or the normal file name unchanged when no short name is available - which includes volumes with 8.3 name generation disabled and paths longer than 260 characters. Compare the result with Name to tell whether shortening actually occurred."}, + {"name":"ShortPath","kind":"property","vis":"public","signature":"Public Property Get ShortPath() As String","returnType":"String","accessor":"Get","description":"Returns the file's 8.3 short path, or the full path unchanged when no short name is available - which includes volumes with 8.3 name generation disabled and paths longer than 260 characters. Compare the result with the full path to tell whether shortening actually occurred."}, + {"name":"Size","kind":"property","vis":"public","signature":"Public Property Get Size() As LongLong","returnType":"LongLong","accessor":"Get","description":"Returns the size, in bytes, of a specified file."}, + {"name":"ToStream","kind":"function","vis":"public","signature":"Public Function ToStream(Optional ByVal maxBytesToRead As Long = -1) As Byte()","returnType":"Byte()","description":"Reads the file specified into a byte array."}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal lineEnding As NewlineType = nlNoChange) As String","returnType":"String","description":"Reads the entire text file to a string. With encAutoDetect (default) the encoding is detected; otherwise the given encoding is used (a byte-order mark still overrides it). A leading BOM is stripped either way."}, + {"name":"Type","kind":"property","vis":"public","signature":"Public Property Get Type() As String","returnType":"String","accessor":"Get","description":"Returns the shell's type description for a file or folder (e.g. \"Text Document\", \"Application\", \"File Folder\"), the same string Explorer and Scripting.FileSystemObject's File.Type show. Falls back to \" File\" or \"File\" when the shell is unavailable, and for paths longer than 260 characters, which the shell APIs do not support."}, + {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version(Optional ByVal source As FileVersionSource = fvFixed) As String","returnType":"String","accessor":"Get","description":"Returns the file's version. By default (fvFixed) returns the binary version as major.minor.build.revision, fvFileString returns the authored StringFileInfo FileVersion string and fvProductString returns the ProductVersion string; both fall back to the fixed version when the file has no string block. Returns an empty string when the file carries no version resource, and also for paths longer than 260 characters, which the version-resource APIs do not support."}, + {"name":"mAccessed","kind":"field","vis":"private","signature":"Private mAccessed As Date"}, + {"name":"mAttrs","kind":"field","vis":"private","signature":"Private mAttrs As Long"}, + {"name":"mCreated","kind":"field","vis":"private","signature":"Private mCreated As Date"}, + {"name":"mModified","kind":"field","vis":"private","signature":"Private mModified As Date"}, + {"name":"mPath","kind":"field","vis":"private","signature":"Private mPath As String"}, + {"name":"mSize","kind":"field","vis":"private","signature":"Private mSize As LongLong"} + ] + }, + { + "name": "FileSystemObject", + "kind": "class", + "vis": "public", + "file": "FileSystemObject.twin", + "signature": "Public Class FileSystemObject", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "This class replaces or improves on most functionality of the FileSystemObject, adding reading and writing on non-UTF16 encoded files.", + "members": [ + {"name":"ArrayToTextFile","kind":"sub","vis":"public","signature":"Public Sub ArrayToTextFile(ByRef lines() As String, ByVal filePath As String, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal lineEnding As NewlineType = nlWindows)","description":"Writes an array of lines to a file in the specified encoding (a BOM is emitted only for BOM-bearing members such as encUtf8Bom). Overwrites any existing file."}, + {"name":"BuildPath","kind":"function","vis":"public","signature":"Public Function BuildPath(ByVal path As String, ByVal name As String) As String","returnType":"String","description":"Combines a folder path and the name of a folder or file and returns the combination with valid path separators."}, + {"name":"CleanFileName","kind":"function","vis":"public","signature":"Public Function CleanFileName(ByVal fileName As String, Optional ByVal replaceChar As String = \"\") As String","returnType":"String","description":"Cleans a file name (not a path) of items that are invalid in Windows: the reserved characters \\ / : * ? \" < > |, control characters, leading/trailing spaces, trailing dots, and reserved device names such as CON, PRN, NUL, COM0-9 and LPT0-9."}, + {"name":"CopyFile","kind":"sub","vis":"public","signature":"Public Sub CopyFile(ByVal source As String, ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies a file to the destination path specified. Wildcards are supported in the last component of source."}, + {"name":"CopyFolder","kind":"sub","vis":"public","signature":"Public Sub CopyFolder(ByVal source As String, ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies a folder to the destination path specified."}, + {"name":"CreateFolder","kind":"function","vis":"public","signature":"Public Function CreateFolder(ByVal path As String, Optional ByVal createIntermediates As Boolean = False) As Folder","returnType":"Folder","description":"Creates a folder and returns the new Folder. Errors if it already exists. By default only the leaf is created; pass createIntermediates:=True to also create any missing intermediate folders."}, + {"name":"CreateTextFile","kind":"function","vis":"public","signature":"Public Function CreateTextFile(ByVal fileName As String, Optional ByVal overwrite As Boolean = True, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal lineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Creates (or overwrites) a text file and returns a TextStream open for writing."}, + {"name":"DeleteFile","kind":"sub","vis":"public","signature":"Public Sub DeleteFile(ByVal fileSpec As String, Optional ByVal force As Boolean = False, Optional ByVal ignoreMissing As Boolean = False)","description":"Deletes the specified file (wildcards supported). force clears a read-only attribute first. ignoreMissing (default False) suppresses the file-not-found error when the path or wildcard pattern matches nothing; it does not suppress other failures such as the path being a directory or the file being locked."}, + {"name":"DeleteFolder","kind":"sub","vis":"public","signature":"Public Sub DeleteFolder(ByVal folderSpec As String, Optional ByVal force As Boolean = False, Optional ByVal ignoreMissing As Boolean = False)","description":"Deletes a folder and its contents (wildcards supported). force clears read-only attributes as it goes. ignoreMissing (default False) suppresses the path-not-found error when the folder or wildcard pattern matches nothing; it does not suppress other failures such as the path being a file or the folder being locked or non-empty."}, + {"name":"DriveExists","kind":"function","vis":"public","signature":"Public Function DriveExists(ByVal driveSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified drive exists; False if it does not."}, + {"name":"Drives","kind":"function","vis":"public","signature":"Public Function Drives() As Drives","returnType":"Drives","description":"Returns a Drives collection of all logical drives present on the system."}, + {"name":"FileExists","kind":"function","vis":"public","signature":"Public Function FileExists(ByVal fileSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified file exists; False if it does not."}, + {"name":"FolderExists","kind":"function","vis":"public","signature":"Public Function FolderExists(ByVal folderSpec As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified folder exists; False if it does not."}, + {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\", Optional ByVal useLongPathPrefix As Boolean = False) As String","returnType":"String","description":"Returns a complete and unambiguous path from a provided path specification."}, + {"name":"GetBaseName","kind":"function","vis":"public","signature":"Public Function GetBaseName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the base name of the last component, less any file extension, in a path."}, + {"name":"GetCurrentDir","kind":"function","vis":"public","signature":"Public Function GetCurrentDir() As String","returnType":"String","description":"Returns the current directory."}, + {"name":"GetDrive","kind":"function","vis":"public","signature":"Public Function GetDrive(ByVal driveSpec As String) As Drive","returnType":"Drive","description":"Returns a Drive object for a drive spec: \"C\", \"C:\", \"C:\\\", a UNC share, or any path on the drive."}, + {"name":"GetDriveName","kind":"function","vis":"public","signature":"Public Function GetDriveName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the drive for a specified path."}, + {"name":"GetExtensionName","kind":"function","vis":"public","signature":"Public Function GetExtensionName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the extension name for the last component in a path."}, + {"name":"GetFile","kind":"function","vis":"public","signature":"Public Function GetFile(ByVal filePath As String) As File","returnType":"File","description":"Returns a File object for an existing file."}, + {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String, Optional ByVal sampleBytes As Long = 65536) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, + {"name":"GetFileLineEnding","kind":"function","vis":"public","signature":"Public Function GetFileLineEnding(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As NewlineType","returnType":"NewlineType","description":"Reads a text file and reports its line-ending style: nlWindows / nlUnix / nlMacintosh for a consistent file, nlMixed if it contains more than one, or nlUnknown if it has none. Reads the whole file, so the result (including mixed) is exact."}, + {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal path As String) As String","returnType":"String","description":"Returns the last component of a specified path that is not part of the drive specification."}, + {"name":"GetFilePaths","kind":"function","vis":"public","signature":"Public Function GetFilePaths(ByVal folderPath As String, Optional ByVal pattern As String = \"*\", Optional ByVal includeHidden As Boolean = False, Optional ByVal includeSystem As Boolean = False, Optional ByVal recursive As Boolean = False) As Collection","returnType":"Collection","description":"Returns a Collection of full file paths in the specified folder. Supports a wildcard pattern, optional recursion into subfolders, inclusion of hidden/system files."}, + {"name":"GetFileType","kind":"function","vis":"public","signature":"Public Function GetFileType(ByVal filePath As String) As String","returnType":"String","description":"Returns the shell's type description for a file or folder (e.g. \"Text Document\", \"Application\", \"File Folder\"), the same string Explorer and Scripting.FileSystemObject's File.Type show. Falls back to \" File\" or \"File\" when the shell is unavailable, and for paths longer than 260 characters, which the shell APIs do not support."}, + {"name":"GetFileVersion","kind":"function","vis":"public","signature":"Public Function GetFileVersion(ByVal fileName As String, Optional ByVal source As FileVersionSource = fvFixed) As String","returnType":"String","description":"Returns the file's version. By default (fvFixed) returns the binary version as major.minor.build.revision, fvFileString returns the authored StringFileInfo FileVersion string and fvProductString returns the ProductVersion string; both fall back to the fixed version when the file has no string block. Returns an empty string when the file carries no version resource, and also for paths longer than 260 characters, which the version-resource APIs do not support."}, + {"name":"GetFolder","kind":"function","vis":"public","signature":"Public Function GetFolder(ByVal folderPath As String) As Folder","returnType":"Folder","description":"Returns a Folder object for an existing folder."}, + {"name":"GetParentFolderName","kind":"function","vis":"public","signature":"Public Function GetParentFolderName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the parent folder of the last component in a specified path."}, + {"name":"GetRelativePath","kind":"function","vis":"public","signature":"Public Function GetRelativePath(ByVal basePath As String, ByVal targetPath As String, Optional ByVal basePathIsFolder As Boolean = True, Optional ByVal trimLeadingDotSlash As Boolean = True) As String","returnType":"String","description":"Returns a relative path from a base path to a target path. The base path is treated as a folder by default. Relative inputs are resolved against the current directory. Returns the absolute target if no relative path exists (e.g. different drives)."}, + {"name":"GetSpecialFolder","kind":"function","vis":"public","signature":"Public Function GetSpecialFolder(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String","description":"Returns the absolute path to the requested known folder. The returned path does NOT include a trailing backslash."}, + {"name":"GetStandardStream","kind":"function","vis":"public","signature":"Public Function GetStandardStream(ByVal standardStreamType As StandardStreamTypes, Optional ByVal unicode As Boolean = False) As TextStream","returnType":"TextStream","description":"Returns a TextStream over one of the process's standard streams. unicode:=True gives UTF-16LE (no BOM); default is the system ANSI code page. Raises if no handle is available (e.g. no console)."}, + {"name":"GetTempName","kind":"function","vis":"public","signature":"Public Function GetTempName(Optional ByVal prefix As String, Optional ByVal extension As String = \".tmp\", Optional ByVal inFolder As String = \"\") As String","returnType":"String","description":"Returns a randomly generated temporary file or folder name. If inFolder is supplied, the returned name is one that does not already exist in that folder at the time of the call."}, + {"name":"MergeTextFiles","kind":"sub","vis":"public","signature":"Public Sub MergeTextFiles(ByVal firstFilePath As String, ByVal secondFilePath As String, Optional ByVal outputFilePath As String, Optional ByVal insertBlankLineBetween As Boolean = False, Optional ByVal firstFileEncoding As TextEncoding = encAutoDetect, Optional ByVal secondFileEncoding As TextEncoding = encAutoDetect)","description":"Merges two text files in the order specified. If outputFilePath is omitted, the first file is overwritten in place. The second file's line endings are converted to match the first file's. Streams line by line, so file size is not limited."}, + {"name":"MoveFile","kind":"sub","vis":"public","signature":"Public Sub MoveFile(ByVal source As String, ByVal destination As String)","description":"Moves one or more files from one location to another."}, + {"name":"MoveFolder","kind":"sub","vis":"public","signature":"Public Sub MoveFolder(ByVal source As String, ByVal destination As String)","description":"Moves a folder from one location to another."}, + {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, + {"name":"OpenTextFile","kind":"function","vis":"public","signature":"Public Function OpenTextFile(ByVal fileName As String, Optional ByVal ioMode As IOMode = ForReading, Optional ByVal create As Boolean = False, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens a text file and returns a streaming TextStream. For reading, omit encoding (encAutoDetect) to detect it from a head sample. For writing/appending, encoding selects the output encoding (default UTF-8, no BOM). Encodings whose chunk boundaries cannot be managed (UTF-7, unclassifiable code pages) transparently fall back to a buffered path."}, + {"name":"ReadStream","kind":"sub","vis":"public","signature":"Public Sub ReadStream(ByVal filePath As String, ByRef stream() As Byte, Optional ByVal maxBytesToRead As Long = -1)","description":"Reads the file specified into a byte array."}, + {"name":"Rename","kind":"sub","vis":"public","signature":"Public Sub Rename(ByVal source As String, ByVal newName As String)","description":"Renames a file or folder in place. The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, + {"name":"SetCurrentDir","kind":"sub","vis":"public","signature":"Public Sub SetCurrentDir(ByVal folderPath As String)","description":"Sets the current directory."}, + {"name":"StringToTextFile","kind":"sub","vis":"public","signature":"Public Sub StringToTextFile(ByVal text As String, ByVal filePath As String, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal outLineEnding As NewlineType = nlNoChange)","description":"Writes a string to a file in the specified encoding (a BOM is emitted only for BOM-bearing members such as encUtf8Bom). Overwrites any existing file."}, + {"name":"TextFileToArray","kind":"function","vis":"public","signature":"Public Function TextFileToArray(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As String()","returnType":"String()","description":"Reads a text file into an array of lines (mixed CR/LF/CRLF handled). Encoding auto-detected by default. A trailing newline yields a final empty element. An empty file returns an unallocated array."}, + {"name":"TextFileToString","kind":"function","vis":"public","signature":"Public Function TextFileToString(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String","description":"Reads an entire text file to a string. With encAutoDetect (default) the encoding is detected, and a binary file raises; with an explicit encoding that encoding is used as-is (a byte-order mark still overrides it). A leading BOM is stripped. An empty file returns an empty string. Optionally normalizes line endings."}, + {"name":"WriteStream","kind":"sub","vis":"public","signature":"Public Sub WriteStream(ByRef stream() As Byte, ByVal filePath As String)","description":"Writes a byte array to the file specified."} + ] + }, + { + "name": "FileSystemTools", + "kind": "module", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Module FileSystemTools", + "members": [ + {"name":"ArrayToTextFile","kind":"sub","vis":"public","signature":"Public Sub ArrayToTextFile(ByRef lines() As String, ByVal filePath As String, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal lineEnding As NewlineType = nlWindows)","description":"Writes an array of lines to a file in the specified encoding (a BOM is emitted only for BOM-bearing members such as encUtf8Bom). Overwrites any existing file."}, + {"name":"BuildPath","kind":"function","vis":"public","signature":"Public Function BuildPath(ByVal path As String, ByVal name As String) As String","returnType":"String","description":"Combines a folder path and the name of a folder or file and returns the combination with valid path separators."}, + {"name":"CleanFileName","kind":"function","vis":"public","signature":"Public Function CleanFileName(ByVal fileName As String, Optional ByVal replaceChar As String = \"\") As String","returnType":"String","description":"Cleans a file name (not a path) of items that are invalid in Windows: the reserved characters \\ / : * ? \" < > |, control characters, leading/trailing spaces, trailing dots, and reserved device names such as CON, PRN, NUL, COM0-9 and LPT0-9."}, + {"name":"CollectFiles","kind":"sub","vis":"private","signature":"Private Sub CollectFiles(ByVal folder As String, ByVal pattern As String, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean, ByVal recursive As Boolean, ByRef result As Collection)"}, + {"name":"CombineLexical","kind":"function","vis":"private","signature":"Private Function CombineLexical(ByVal baseDir As String, ByVal relPath As String) As String","returnType":"String"}, + {"name":"CopyFile","kind":"sub","vis":"public","signature":"Public Sub CopyFile(ByVal source As String, ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies a file to the destination path specified. Wildcards are supported in the last component of source."}, + {"name":"CopyFileWildcard","kind":"sub","vis":"private","signature":"Private Sub CopyFileWildcard(ByVal sourcePattern As String, ByVal destination As String, ByVal overwriteFiles As Boolean)"}, + {"name":"CopyFolder","kind":"sub","vis":"public","signature":"Public Sub CopyFolder(ByVal source As String, ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies a folder to the destination path specified. Wildcards are supported in the last component of source."}, + {"name":"CopyFolderRecursive","kind":"sub","vis":"private","signature":"Private Sub CopyFolderRecursive(ByVal source As String, ByVal destination As String, ByVal overwriteFiles As Boolean)"}, + {"name":"CopyFolderWildcard","kind":"sub","vis":"private","signature":"Private Sub CopyFolderWildcard(ByVal sourcePattern As String, ByVal destination As String, ByVal overwriteFiles As Boolean)"}, + {"name":"CopyOneFile","kind":"sub","vis":"private","signature":"Private Sub CopyOneFile(ByVal source As String, ByVal destination As String, ByVal overwriteFiles As Boolean)"}, + {"name":"CopyOneFolder","kind":"sub","vis":"private","signature":"Private Sub CopyOneFolder(ByVal source As String, ByVal destination As String, ByVal overwrite As Boolean)"}, + {"name":"CreateDir","kind":"sub","vis":"private","signature":"Private Sub CreateDir(ByVal path As String, ByVal sourceName As String)"}, + {"name":"CreateFolder","kind":"function","vis":"public","signature":"Public Function CreateFolder(ByVal folderPath As String, Optional ByVal createIntermediates As Boolean = False) As Folder","returnType":"Folder","description":"Creates a folder and returns the new Folder. Errors if it already exists. By default only the leaf is created; pass createIntermediates:=True to also create any missing intermediate folders."}, + {"name":"CreateTextFile","kind":"function","vis":"public","signature":"Public Function CreateTextFile(ByVal filePath As String, Optional ByVal overwrite As Boolean = True, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal lineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Creates (or overwrites) a text file and returns a TextStream open for writing."}, + {"name":"DeleteFile","kind":"sub","vis":"public","signature":"Public Sub DeleteFile(ByVal filePath As String, Optional ByVal force As Boolean = False, Optional ByVal ignoreMissing As Boolean = False)","description":"Deletes the specified file (wildcards supported). force clears a read-only attribute first. ignoreMissing (default False) suppresses the file-not-found error when the path or wildcard pattern matches nothing; it does not suppress other failures such as the path being a directory or the file being locked."}, + {"name":"DeleteFileWildcard","kind":"sub","vis":"private","signature":"Private Sub DeleteFileWildcard(ByVal pattern As String, ByVal force As Boolean, ByVal ignoreMissing As Boolean)"}, + {"name":"DeleteFolder","kind":"sub","vis":"public","signature":"Public Sub DeleteFolder(ByVal folderPath As String, Optional ByVal force As Boolean = False, Optional ByVal ignoreMissing As Boolean = False)","description":"Deletes a folder and its contents (wildcards supported). force clears read-only attributes as it goes. ignoreMissing (default False) suppresses the path-not-found error when the folder or wildcard pattern matches nothing; it does not suppress other failures such as the path being a file or the folder being locked or non-empty."}, + {"name":"DeleteFolderRecursive","kind":"sub","vis":"private","signature":"Private Sub DeleteFolderRecursive(ByVal folder As String, ByVal force As Boolean)"}, + {"name":"DeleteFolderWildcard","kind":"sub","vis":"private","signature":"Private Sub DeleteFolderWildcard(ByVal pattern As String, ByVal force As Boolean, ByVal ignoreMissing As Boolean)"}, + {"name":"DeleteOneFile","kind":"sub","vis":"private","signature":"Private Sub DeleteOneFile(ByVal filePath As String, ByVal force As Boolean, ByVal ignoreMissing As Boolean)"}, + {"name":"DeleteOneFolder","kind":"sub","vis":"private","signature":"Private Sub DeleteOneFolder(ByVal folderPath As String, ByVal force As Boolean, ByVal ignoreMissing As Boolean)"}, + {"name":"DriveExists","kind":"function","vis":"public","signature":"Public Function DriveExists(ByVal drivePath As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified drive exists; False if it does not."}, + {"name":"ERR_NOT_TEXT","kind":"const","vis":"private","signature":"Private Const ERR_NOT_TEXT As Long = 5"}, + {"name":"EnsureParentExists","kind":"sub","vis":"private","signature":"Private Sub EnsureParentExists(ByVal path As String)"}, + {"name":"EnumerateLevel","kind":"sub","vis":"private","signature":"Private Sub EnumerateLevel(ByVal folder As String, ByVal pattern As String, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean, ByRef result As Collection)"}, + {"name":"EnumerateSubFolders","kind":"function","vis":"private","signature":"Private Function EnumerateSubFolders(ByVal folder As String, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean) As Collection","returnType":"Collection"}, + {"name":"FileExists","kind":"function","vis":"public","signature":"Public Function FileExists(ByVal filePath As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified file exists; False if it does not."}, + {"name":"FolderExists","kind":"function","vis":"public","signature":"Public Function FolderExists(ByVal folderPath As String) As Boolean","returnType":"Boolean","description":"Returns True if a specified folder exists; False if it does not."}, + {"name":"GetAbsolutePathName","kind":"function","vis":"public","signature":"Public Function GetAbsolutePathName(ByVal path As String, Optional ByVal baseDirectory As String = \"\", Optional ByVal useLongPathPrefix As Boolean = False) As String","returnType":"String","description":"Returns a complete and unambiguous path from a provided path specification."}, + {"name":"GetBaseName","kind":"function","vis":"public","signature":"Public Function GetBaseName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the base name of the last component, less any file extension, in a path."}, + {"name":"GetCurrentDir","kind":"function","vis":"public","signature":"Public Function GetCurrentDir() As String","returnType":"String","description":"Returns the current directory."}, + {"name":"GetDrive","kind":"function","vis":"public","signature":"Public Function GetDrive(ByVal drivePath As String) As Drive","returnType":"Drive","description":"Returns a Drive object for a drive spec: \"C\", \"C:\", \"C:\\\", a UNC share, or any path on the drive."}, + {"name":"GetDriveName","kind":"function","vis":"public","signature":"Public Function GetDriveName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the drive for a specified path."}, + {"name":"GetDrives","kind":"function","vis":"public","signature":"Public Function GetDrives() As Drives","returnType":"Drives","description":"Returns a Drives collection of all logical drives present on the system."}, + {"name":"GetExtensionName","kind":"function","vis":"public","signature":"Public Function GetExtensionName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the extension name for the last component in a path."}, + {"name":"GetFile","kind":"function","vis":"public","signature":"Public Function GetFile(ByVal filePath As String) As File","returnType":"File","description":"Returns a File object for an existing file."}, + {"name":"GetFileEncoding","kind":"function","vis":"public","signature":"Public Function GetFileEncoding(ByVal filePath As String, Optional ByVal sampleBytes As Long = 65536) As TextEncoding","returnType":"TextEncoding","description":"Detects a file's text encoding from a head sample (default 64 KB): BOM sniff first, then UTF-16/32 and UTF-8 heuristics, else system ANSI. Returns a signed TextEncoding (negative = BOM present), or encUnknown if the file is empty or looks binary. Raises if the file cannot be read."}, + {"name":"GetFileLineEnding","kind":"function","vis":"public","signature":"Public Function GetFileLineEnding(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As NewlineType","returnType":"NewlineType","description":"Reads a text file and reports its line-ending style: nlWindows / nlUnix / nlMacintosh for a consistent file, nlMixed if it contains more than one, or nlUnknown if it has none. Reads the whole file, so the result (including mixed) is exact."}, + {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal path As String) As String","returnType":"String","description":"Returns the last component of a specified path that is not part of the drive specification."}, + {"name":"GetFilePaths","kind":"function","vis":"public","signature":"Public Function GetFilePaths(ByVal folderPath As String, Optional ByVal pattern As String = \"*\", Optional ByVal includeHidden As Boolean = False, Optional ByVal includeSystem As Boolean = False, Optional ByVal recursive As Boolean = False) As Collection","returnType":"Collection","description":"Returns a Collection of full file paths in the specified folder. Supports a wildcard pattern, optional recursion into subfolders, inclusion of hidden/system files."}, + {"name":"GetFileType","kind":"function","vis":"public","signature":"Public Function GetFileType(ByVal filePath As String) As String","returnType":"String","description":"Returns the shell's type description for a file or folder (e.g. \"Text Document\", \"Application\", \"File Folder\"), the same string Explorer and Scripting.FileSystemObject's File.Type show. Falls back to \" File\" or \"File\" when the shell is unavailable, and for paths longer than 260 characters, which the shell APIs do not support."}, + {"name":"GetFileVersion","kind":"function","vis":"public","signature":"Public Function GetFileVersion(ByVal filePath As String, Optional ByVal source As FileVersionSource = fvFixed) As String","returnType":"String","description":"Returns the file's version. By default (fvFixed) returns the binary version as major.minor.build.revision, fvFileString returns the authored StringFileInfo FileVersion string and fvProductString returns the ProductVersion string; both fall back to the fixed version when the file has no string block. Returns an empty string when the file carries no version resource, and also for paths longer than 260 characters, which the version-resource APIs do not support."}, + {"name":"GetFolder","kind":"function","vis":"public","signature":"Public Function GetFolder(ByVal folderPath As String) As Folder","returnType":"Folder","description":"Returns a Folder object for an existing folder."}, + {"name":"GetParentFolderName","kind":"function","vis":"public","signature":"Public Function GetParentFolderName(ByVal path As String) As String","returnType":"String","description":"Returns a string containing the name of the parent folder of the last component in a specified path."}, + {"name":"GetRelativePath","kind":"function","vis":"public","signature":"Public Function GetRelativePath(ByVal basePath As String, ByVal targetPath As String, Optional ByVal basePathIsFolder As Boolean = True, Optional ByVal trimLeadingDotSlash As Boolean = True) As String","returnType":"String","description":"Returns a relative path from a base path to a target path. The base path is treated as a folder by default. Relative inputs are resolved against the current directory. Returns the absolute target if no relative path exists (e.g. different drives)."}, + {"name":"GetSpecialFolder","kind":"function","vis":"public","signature":"Public Function GetSpecialFolder(ByVal specialFolder As SpecialFolderConst) As String","returnType":"String","description":"Returns the absolute path to the requested known folder. The returned path does NOT include a trailing backslash."}, + {"name":"GetStandardStream","kind":"function","vis":"public","signature":"Public Function GetStandardStream(ByVal standardStreamType As StandardStreamTypes, Optional ByVal unicode As Boolean = False) As TextStream","returnType":"TextStream","description":"Returns a TextStream over one of the process's standard streams. unicode:=True gives UTF-16LE (no BOM); default is the system ANSI code page. Raises if no handle is available (e.g. no console)."}, + {"name":"GetTempName","kind":"function","vis":"public","signature":"Public Function GetTempName(Optional ByVal prefix As String, Optional ByVal extension As String = \".tmp\", Optional ByVal inFolder As String = \"\") As String","returnType":"String","description":"Returns a randomly generated temporary file or folder name. If inFolder is supplied, the returned name is one that does not already exist in that folder at the time of the call."}, + {"name":"GuardWriteName","kind":"sub","vis":"private","signature":"Private Sub GuardWriteName(ByVal path As String, ByVal opName As String)"}, + {"name":"GuidFromString","kind":"function","vis":"private","signature":"Private Function GuidFromString(ByVal s As String) As UUID","returnType":"UUID"}, + {"name":"HasWildcards","kind":"function","vis":"private","signature":"Private Function HasWildcards(ByVal s As String) As Boolean","returnType":"Boolean"}, + {"name":"IsDriveRelative","kind":"function","vis":"private","signature":"Private Function IsDriveRelative(ByVal p As String) As Boolean","returnType":"Boolean"}, + {"name":"IsFolderPathOrExistingFolder","kind":"function","vis":"private","signature":"Private Function IsFolderPathOrExistingFolder(ByVal path As String) As Boolean","returnType":"Boolean"}, + {"name":"IsReservedDeviceName","kind":"function","vis":"private","signature":"Private Function IsReservedDeviceName(ByVal name As String) As Boolean","returnType":"Boolean"}, + {"name":"IsUncRoot","kind":"function","vis":"private","signature":"Private Function IsUncRoot(ByVal s As String) As Boolean","returnType":"Boolean"}, + {"name":"KFID_CommonStartup_STR","kind":"const","vis":"private","signature":"Private Const KFID_CommonStartup_STR As String = \"{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}\""}, + {"name":"KFID_Desktop_STR","kind":"const","vis":"private","signature":"Private Const KFID_Desktop_STR As String = \"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}\""}, + {"name":"KFID_Documents_STR","kind":"const","vis":"private","signature":"Private Const KFID_Documents_STR As String = \"{FDD39AD0-238F-46AF-ADB4-6C85480369C7}\""}, + {"name":"KFID_Downloads_STR","kind":"const","vis":"private","signature":"Private Const KFID_Downloads_STR As String = \"{374DE290-123F-4565-9164-39C4925E467B}\""}, + {"name":"KFID_Fonts_STR","kind":"const","vis":"private","signature":"Private Const KFID_Fonts_STR As String = \"{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}\""}, + {"name":"KFID_LocalAppData_STR","kind":"const","vis":"private","signature":"Private Const KFID_LocalAppData_STR As String = \"{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}\""}, + {"name":"KFID_Music_STR","kind":"const","vis":"private","signature":"Private Const KFID_Music_STR As String = \"{4BD8D571-6D19-48D3-BE97-422220080E43}\""}, + {"name":"KFID_Pictures_STR","kind":"const","vis":"private","signature":"Private Const KFID_Pictures_STR As String = \"{33E28130-4E1E-4676-835A-98395C3BC3BB}\""}, + {"name":"KFID_Profile_STR","kind":"const","vis":"private","signature":"Private Const KFID_Profile_STR As String = \"{5E6C858F-0E22-4760-9AFE-EA3317B67173}\""}, + {"name":"KFID_ProgramData_STR","kind":"const","vis":"private","signature":"Private Const KFID_ProgramData_STR As String = \"{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}\""}, + {"name":"KFID_ProgramFilesX86_STR","kind":"const","vis":"private","signature":"Private Const KFID_ProgramFilesX86_STR As String = \"{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}\""}, + {"name":"KFID_ProgramFiles_STR","kind":"const","vis":"private","signature":"Private Const KFID_ProgramFiles_STR As String = \"{905E63B6-C1BF-494E-B29C-65B732D3D21A}\""}, + {"name":"KFID_PublicDesktop_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicDesktop_STR As String = \"{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}\""}, + {"name":"KFID_PublicDocuments_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicDocuments_STR As String = \"{ED4824AF-DCE4-45A8-81E2-FC7965083634}\""}, + {"name":"KFID_PublicDownloads_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicDownloads_STR As String = \"{3D644C9B-1FB8-4F30-9B45-F670235F79C0}\""}, + {"name":"KFID_PublicMusic_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicMusic_STR As String = \"{3214FAB5-9757-4298-BB61-92A9DEAA44FF}\""}, + {"name":"KFID_PublicPictures_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicPictures_STR As String = \"{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}\""}, + {"name":"KFID_PublicVideos_STR","kind":"const","vis":"private","signature":"Private Const KFID_PublicVideos_STR As String = \"{2400183A-6185-49FB-A2D8-4A392A602BA3}\""}, + {"name":"KFID_RoamingAppData_STR","kind":"const","vis":"private","signature":"Private Const KFID_RoamingAppData_STR As String = \"{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}\""}, + {"name":"KFID_Startup_STR","kind":"const","vis":"private","signature":"Private Const KFID_Startup_STR As String = \"{B97D20BB-F46A-4C97-BA10-5E3608430854}\""}, + {"name":"KFID_SystemX86_STR","kind":"const","vis":"private","signature":"Private Const KFID_SystemX86_STR As String = \"{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}\""}, + {"name":"KFID_System_STR","kind":"const","vis":"private","signature":"Private Const KFID_System_STR As String = \"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\""}, + {"name":"KFID_Videos_STR","kind":"const","vis":"private","signature":"Private Const KFID_Videos_STR As String = \"{18989B1D-99B5-455B-841C-AB7C74E4DDFC}\""}, + {"name":"KFID_Windows_STR","kind":"const","vis":"private","signature":"Private Const KFID_Windows_STR As String = \"{F38BF404-1D43-42F2-9305-67DE0B28FC23}\""}, + {"name":"LastSep","kind":"function","vis":"private","signature":"Private Function LastSep(ByVal s As String) As Long","returnType":"Long"}, + {"name":"MergeTextFiles","kind":"sub","vis":"public","signature":"Public Sub MergeTextFiles(ByVal firstFilePath As String, ByVal secondFilePath As String, Optional ByVal outputFilePath As String, Optional ByVal insertBlankLineBetween As Boolean = False, Optional ByVal firstFileEncoding As TextEncoding = encAutoDetect, Optional ByVal secondFileEncoding As TextEncoding = encAutoDetect)","description":"Merges two text files in the order specified. If outputFilePath is omitted, the first file is overwritten in place. The second file's line endings are converted to match the first file's. Streams line by line, so file size is not limited."}, + {"name":"MoveFile","kind":"function","vis":"public","signature":"Public Function MoveFile(ByVal source As String, ByVal destination As String) As String","returnType":"String","description":"Moves a file to the destination and returns its resolved absolute path. If the destination is an existing folder (or ends in \"\\\"), the file keeps its name inside that folder; otherwise the destination is the new full path. Wildcards are supported in the last component of source."}, + {"name":"MoveFolder","kind":"function","vis":"public","signature":"Public Function MoveFolder(ByVal source As String, ByVal destination As String) As String","returnType":"String","description":"Moves a folder to the destination and returns its resolved absolute path. If the destination is an existing folder (or ends in \"\\\"), the source folder is moved inside it, keeping its own name; otherwise the destination is the new full path. Wildcards are supported in the last component of source."}, + {"name":"MoveOneFile","kind":"function","vis":"private","signature":"Private Function MoveOneFile(ByVal source As String, ByVal destination As String) As String","returnType":"String"}, + {"name":"MoveOneFolder","kind":"function","vis":"private","signature":"Private Function MoveOneFolder(ByVal source As String, ByVal destination As String) As String","returnType":"String"}, + {"name":"MoveWildcard","kind":"function","vis":"private","signature":"Private Function MoveWildcard(ByVal sourcePattern As String, ByVal destination As String, ByVal wantFolders As Boolean) As String","returnType":"String"}, + {"name":"NormalizeTextFile","kind":"function","vis":"public","signature":"Public Function NormalizeTextFile(ByVal filePath As String, Optional ByVal toEncoding As TextEncoding = encUtf8, Optional ByVal toNewLine As NewlineType = nlWindows, Optional ByVal fromEncoding As TextEncoding = encAutoDetect, Optional ByVal toAttributes As FileAttribute = faNoChange, Optional ByVal skipIfUnchanged As Boolean = True) As Boolean","returnType":"Boolean","description":"Rewrites a text file in place in the target encoding and newline style (source auto-detected). toAttributes enforces a target attribute set (settable bits only); NoChange (the default) preserves the file's current attributes. Skips the write when content is already in target form. Refuses binary files. Returns True if the file was modified."}, + {"name":"OpenTextFile","kind":"function","vis":"public","signature":"Public Function OpenTextFile(ByVal filePath As String, Optional ByVal ioMode As IOMode = ForReading, Optional ByVal create As Boolean = False, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal writeModeLineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Opens a text file and returns a streaming TextStream. For reading, omit encoding (encAutoDetect) to detect it from a head sample. For writing/appending, encoding selects the output encoding (default UTF-8, no BOM). Encodings whose chunk boundaries cannot be managed (UTF-7, unclassifiable code pages) transparently fall back to a buffered path."}, + {"name":"PathExists","kind":"function","vis":"private","signature":"Private Function PathExists(ByVal path As String) As Boolean","returnType":"Boolean"}, + {"name":"PeekWord","kind":"function","vis":"private","signature":"Private Function PeekWord(ByVal p As LongPtr) As Integer","returnType":"Integer"}, + {"name":"PtrToStringW","kind":"function","vis":"private","signature":"Private Function PtrToStringW(ByVal pStr As LongPtr, ByVal charLen As Long) As String","returnType":"String"}, + {"name":"ReadStream","kind":"sub","vis":"public","signature":"Public Sub ReadStream(ByVal filePath As String, ByRef stream() As Byte, Optional ByVal maxBytesToRead As Long = -1)","description":"Reads the file specified into a byte array."}, + {"name":"ReadStringFileInfo","kind":"function","vis":"private","signature":"Private Function ReadStringFileInfo(ByRef buffer() As Byte, ByVal fieldName As String) As String","returnType":"String"}, + {"name":"Rename","kind":"function","vis":"public","signature":"Public Function Rename(ByVal source As String, ByVal newName As String) As String","returnType":"String","description":"Renames a file or folder in place and returns the resolved absolute path of the renamed item. The new name may be a bare name or a path resolving to the same folder as the source (case-only changes are permitted); it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, + {"name":"ResolveAttrForRelative","kind":"function","vis":"private","signature":"Private Function ResolveAttrForRelative(ByVal path As String) As FILE_ATTRIBUTES","returnType":"FILE_ATTRIBUTES"}, + {"name":"SetCurrentDir","kind":"sub","vis":"public","signature":"Public Sub SetCurrentDir(ByVal folderPath As String)","description":"Sets the current directory."}, + {"name":"StringToTextFile","kind":"sub","vis":"public","signature":"Public Sub StringToTextFile(ByVal text As String, ByVal filePath As String, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal outLineEnding As NewlineType = nlNoChange)","description":"Writes a string to a file in the specified encoding (a BOM is emitted only for BOM-bearing members such as encUtf8Bom). Overwrites any existing file."}, + {"name":"TextFileToArray","kind":"function","vis":"public","signature":"Public Function TextFileToArray(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect) As String()","returnType":"String()","description":"Reads a text file into an array of lines (mixed CR/LF/CRLF handled). Encoding auto-detected by default. A trailing newline yields a final empty element. An empty file returns an unallocated array."}, + {"name":"TextFileToString","kind":"function","vis":"public","signature":"Public Function TextFileToString(ByVal filePath As String, Optional ByVal encoding As TextEncoding = encAutoDetect, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String","description":"Reads an entire text file to a string. With encAutoDetect (default) the encoding is detected, and a binary file raises; with an explicit encoding that encoding is used as-is (a byte-order mark still overrides it). A leading BOM is stripped. An empty file returns an empty string. Optionally normalizes line endings."}, + {"name":"WriteStream","kind":"sub","vis":"public","signature":"Public Sub WriteStream(ByRef stream() As Byte, ByVal filePath As String)","description":"Writes a byte array to the file specified."} + ] + }, + { + "name": "FileAttribute", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum FileAttribute", + "values": [ + {"name":"faNoChange","value":"-1"}, + {"name":"faNormal","value":"&H00000080"}, + {"name":"faReadOnly","value":"&H00000001"}, + {"name":"faHidden","value":"&H00000002"}, + {"name":"faSystem","value":"&H00000004"}, + {"name":"faArchive","value":"&H00000020"}, + {"name":"faTemporary","value":"&H00000100"}, + {"name":"faNotContentIndexed","value":"&H00002000"}, + {"name":"faDirectory","value":"&H00000010"}, + {"name":"faCompressed","value":"&H00000800"}, + {"name":"faEncrypted","value":"&H00004000"}, + {"name":"faReparsePoint","value":"&H00000400"}, + {"name":"faSparseFile","value":"&H00000200"}, + {"name":"faOffline","value":"&H00001000"}, + {"name":"faIntegrityStream","value":"&H00008000&"}, + {"name":"faVirtual","value":"&H00010000"}, + {"name":"faNoScrubData","value":"&H00020000"}, + {"name":"faEA","value":"&H00040000"}, + {"name":"faRecallOnOpen","value":"&H00040000"}, + {"name":"faPinned","value":"&H00080000"}, + {"name":"faUnpinned","value":"&H00100000"}, + {"name":"faRecallOnDataAccess","value":"&H00400000"}, + {"name":"faStrictlySequential","value":"&H20000000"}, + {"name":"Normal","value":"faNormal"}, + {"name":"ReadOnly","value":"faReadOnly"}, + {"name":"Hidden","value":"faHidden"}, + {"name":"System","value":"faSystem"}, + {"name":"Directory","value":"faDirectory"}, + {"name":"Archive","value":"faArchive"}, + {"name":"Compressed","value":"faCompressed"} + ] + }, + { + "name": "FileVersionSource", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum FileVersionSource", + "values": [ + {"name":"fvFixed","value":"0"}, + {"name":"fvFileString","value":"1"}, + {"name":"fvProductString","value":"2"} + ] + }, + { + "name": "IOMode", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum IOMode", + "values": [ + {"name":"ForReading","value":"1"}, + {"name":"ForWriting","value":"2"}, + {"name":"ForAppending","value":"8"} + ] + }, + { + "name": "NewlineType", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum NewlineType", + "values": [ + {"name":"nlUnknown","value":"0"}, + {"name":"nlMixed","value":"0"}, + {"name":"nlNoChange","value":"0"}, + {"name":"nlWindows","value":"1"}, + {"name":"nlUnix","value":"2"}, + {"name":"nlMacintosh","value":"3"} + ] + }, + { + "name": "SpecialFolderConst", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum SpecialFolderConst", + "values": [ + {"name":"sfWindowsFolder","value":"0"}, + {"name":"sfSystemFolder","value":"1"}, + {"name":"sfTemporaryFolder","value":"2"}, + {"name":"sfSystemX86Folder","value":"3"}, + {"name":"sfProgramFiles","value":"4"}, + {"name":"sfProgramFilesX86","value":"5"}, + {"name":"sfProgramData","value":"6"}, + {"name":"sfUserProfile","value":"7"}, + {"name":"sfDesktop","value":"8"}, + {"name":"sfDocuments","value":"9"}, + {"name":"sfDownloads","value":"10"}, + {"name":"sfPictures","value":"11"}, + {"name":"sfMusic","value":"12"}, + {"name":"sfVideos","value":"13"}, + {"name":"sfLocalAppData","value":"14"}, + {"name":"sfRoamingAppData","value":"15"}, + {"name":"sfPublicDesktop","value":"16"}, + {"name":"sfPublicDocuments","value":"17"}, + {"name":"sfPublicDownloads","value":"18"}, + {"name":"sfPublicPictures","value":"19"}, + {"name":"sfPublicMusic","value":"20"}, + {"name":"sfPublicVideos","value":"21"}, + {"name":"sfFonts","value":"22"}, + {"name":"sfStartup","value":"23"}, + {"name":"sfCommonStartup","value":"24"}, + {"name":"WindowsFolder","value":"sfWindowsFolder"}, + {"name":"SystemFolder","value":"sfSystemFolder"}, + {"name":"TemporaryFolder","value":"sfTemporaryFolder"} + ] + }, + { + "name": "StandardStreamTypes", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum StandardStreamTypes", + "values": [ + {"name":"StdIn","value":"0"}, + {"name":"StdOut","value":"1"}, + {"name":"StdErr","value":"2"} + ] + }, + { + "name": "TextEncoding", + "kind": "enum", + "in": "FileSystemTools", + "vis": "public", + "file": "FileSystemTools.twin", + "signature": "Public Enum TextEncoding", + "values": [ + {"name":"encUnknown","value":"-1"}, + {"name":"encAutoDetect","value":"-1"}, + {"name":"encSystemAnsi","value":"0"}, + {"name":"encGB2312","value":"936"}, + {"name":"encBig5","value":"950"}, + {"name":"encUtf16","value":"1200"}, + {"name":"encUtf16Be","value":"1201"}, + {"name":"encUtf32","value":"12000"}, + {"name":"encUtf32Be","value":"12001"}, + {"name":"encUsAscii","value":"20127"}, + {"name":"encLatin1","value":"28591"}, + {"name":"encLatin9","value":"28605"}, + {"name":"encGB18030","value":"54936"}, + {"name":"encUtf7","value":"65000"}, + {"name":"encUtf8","value":"65001"}, + {"name":"encUtf8Bom","value":"-encUtf8"}, + {"name":"encUtf16Bom","value":"-encUtf16"}, + {"name":"encUtf16BeBom","value":"-encUtf16Be"}, + {"name":"encUtf32Bom","value":"-encUtf32"}, + {"name":"encUtf32BeBom","value":"-encUtf32Be"}, + {"name":"encUtf7Bom","value":"-encUtf7"}, + {"name":"encGB18030Bom","value":"-encGB18030"} + ] + }, + { + "name": "Files", + "kind": "class", + "vis": "public", + "file": "Files.twin", + "signature": "Public Class Files", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "A read-only collection of File objects, enumerable with For Each.", + "members": [ + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Returns the number of File objects available."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal items As Collection)"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal nameOrIndex As Variant) As File","returnType":"File","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns a specific File object by name or index."}, + {"name":"NewEnum","kind":"function","vis":"public","signature":"Public Function NewEnum() As IUnknown","returnType":"IUnknown","hidden":true,"attrs":[{"name":"Enumerator"},{"name":"Hidden","args":"True"}]}, + {"name":"mItems","kind":"field","vis":"private","signature":"Private mItems As Collection"} + ] + }, + { + "name": "Folder", + "kind": "class", + "vis": "public", + "file": "Folder.twin", + "signature": "Public Class Folder", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "An FSO-style Folder object. Obtain via FileSystem.GetFolder or Folder.SubFolders; Not New-able directly.", + "members": [ + {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Get Attributes() As FileAttribute","returnType":"FileAttribute","accessor":"Get","description":"Sets or returns the attributes of a specified folder."}, + {"name":"Attributes","kind":"property","vis":"public","signature":"Public Property Let Attributes(ByVal value As FileAttribute)","accessor":"Let"}, + {"name":"Copy","kind":"sub","vis":"public","signature":"Public Sub Copy(ByVal destination As String, Optional ByVal overwrite As Boolean = True)","description":"Copies this folder (and contents) to the destination."}, + {"name":"CreateTextFile","kind":"function","vis":"public","signature":"Public Function CreateTextFile(ByVal fileName As String, Optional ByVal overwrite As Boolean = True, Optional ByVal encoding As TextEncoding = encUtf8, Optional ByVal lineEnding As NewlineType = nlWindows) As TextStream","returnType":"TextStream","description":"Creates (or overwrites) a text file in this folder and returns a TextStream open for writing."}, + {"name":"DateCreated","kind":"property","vis":"public","signature":"Public Property Get DateCreated() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified folder was created."}, + {"name":"DateLastAccessed","kind":"property","vis":"public","signature":"Public Property Get DateLastAccessed() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified folder was last accessed."}, + {"name":"DateLastModified","kind":"property","vis":"public","signature":"Public Property Get DateLastModified() As Date","returnType":"Date","accessor":"Get","description":"Returns the date and time when a specified folder was last modified."}, + {"name":"Delete","kind":"sub","vis":"public","signature":"Public Sub Delete(Optional ByVal force As Boolean = False)","description":"Deletes this folder and its contents."}, + {"name":"Drive","kind":"property","vis":"public","signature":"Public Property Get Drive() As Drive","returnType":"Drive","accessor":"Get","description":"Returns the drive letter of the drive where the specified folder resides."}, + {"name":"EnsureCurrent","kind":"sub","vis":"private","signature":"Private Sub EnsureCurrent()"}, + {"name":"EnumChildren","kind":"function","vis":"private","signature":"Private Function EnumChildren(ByVal folderPath As String, ByVal wantFolders As Boolean, ByVal includeHidden As Boolean, ByVal includeSystem As Boolean) As Collection","returnType":"Collection"}, + {"name":"Files","kind":"property","vis":"public","signature":"Public Property Get Files() As Files","returnType":"Files","accessor":"Get","description":"A Files collection of every file in this folder (including hidden/system, FSO-style)."}, + {"name":"FolderSize","kind":"function","vis":"private","signature":"Private Function FolderSize(ByVal folderPath As String) As LongLong","returnType":"LongLong"}, + {"name":"HasAttribute","kind":"property","vis":"public","signature":"Public Property Get HasAttribute(ByVal attr As FileAttribute) As Boolean","returnType":"Boolean","accessor":"Get","description":"True if the specified attribute is set."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal path As String, ByVal attrs As Long, ByVal created As Date, ByVal modified As Date, ByVal accessed As Date)"}, + {"name":"IsRootFolder","kind":"property","vis":"public","signature":"Public Property Get IsRootFolder() As Boolean","returnType":"Boolean","accessor":"Get","description":"True if this folder is the root of its drive."}, + {"name":"MakeFileObject","kind":"function","vis":"private","signature":"Private Function MakeFileObject(ByVal path As String, ByVal attrs As Long, ByVal size As LongLong, ByVal created As Date, ByVal modified As Date, ByVal accessed As Date) As File","returnType":"File"}, + {"name":"MakeFolderObject","kind":"function","vis":"private","signature":"Private Function MakeFolderObject(ByVal path As String, ByVal attrs As Long, ByVal created As Date, ByVal modified As Date, ByVal accessed As Date) As Folder","returnType":"Folder"}, + {"name":"Move","kind":"sub","vis":"public","signature":"Public Sub Move(ByVal destination As String)","description":"Moves this folder, then re-points this object at the new location."}, + {"name":"Name","kind":"property","vis":"public","signature":"Public Property Get Name() As String","returnType":"String","accessor":"Get","description":"Sets or returns the name of a specified folder."}, + {"name":"Name","kind":"property","vis":"public","signature":"Public Property Let Name(ByVal newName As String)","accessor":"Let"}, + {"name":"ParentFolder","kind":"property","vis":"public","signature":"Public Property Get ParentFolder() As Folder","returnType":"Folder","accessor":"Get","description":"The parent Folder, or Nothing for a root folder."}, + {"name":"Path","kind":"property","vis":"public","signature":"Public Property Get Path() As String","returnType":"String","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns the path for a specified folder."}, + {"name":"Refresh","kind":"sub","vis":"public","signature":"Public Sub Refresh()","description":"Re-reads attributes and dates from disk. Raises error 76 if the folder no longer exists."}, + {"name":"Rename","kind":"sub","vis":"public","signature":"Public Sub Rename(ByVal newName As String)","description":"Renames the folder. The new name must be a bare name, or a path that resolves to the same folder as the source — it cannot move the item to a different folder. Wildcards are not permitted, and the destination must not already exist."}, + {"name":"SetAttribute","kind":"sub","vis":"public","signature":"Public Sub SetAttribute(ByVal attr As FileAttribute, ByVal state As Boolean)","description":"Sets or clears a single attribute, leaving the others untouched. Only settable attributes have any effect (ReadOnly, Hidden, System, Archive, Temporary, NotContentIndexed)."}, + {"name":"ShortName","kind":"property","vis":"public","signature":"Public Property Get ShortName() As String","returnType":"String","accessor":"Get","description":"Returns the folder's 8.3 short name, or the normal folder name unchanged when no short name is available - which includes volumes with 8.3 name generation disabled and paths longer than 260 characters. Compare the result with Name to tell whether shortening actually occurred."}, + {"name":"ShortPath","kind":"property","vis":"public","signature":"Public Property Get ShortPath() As String","returnType":"String","accessor":"Get","description":"Returns the folder's 8.3 short path, or the full path unchanged when no short name is available - which includes volumes with 8.3 name generation disabled and paths longer than 260 characters. Compare the result with Path to tell whether shortening actually occurred."}, + {"name":"Size","kind":"property","vis":"public","signature":"Public Property Get Size() As LongLong","returnType":"LongLong","accessor":"Get","description":"Total size in bytes of all files in this folder and its subfolders. Directory reparse points (junctions, symlinks) are NOT followed - this deviates from Scripting.FileSystemObject, which follows them and can therefore double-count or recurse infinitely. Computed on demand: it walks the whole subtree on every read."}, + {"name":"SubFolders","kind":"property","vis":"public","signature":"Public Property Get SubFolders() As Folders","returnType":"Folders","accessor":"Get","description":"A Folders collection of every immediate subfolder."}, + {"name":"Type","kind":"property","vis":"public","signature":"Public Property Get Type() As String","returnType":"String","accessor":"Get","description":"Returns the shell's type description for the folder (e.g. \"File folder\"), the same string Explorer and Scripting.FileSystemObject's Folder.Type show. Falls back to a generic description when the shell is unavailable, and for paths longer than 260 characters, which the shell APIs do not support."}, + {"name":"mAccessed","kind":"field","vis":"private","signature":"Private mAccessed As Date"}, + {"name":"mAttrs","kind":"field","vis":"private","signature":"Private mAttrs As Long"}, + {"name":"mCreated","kind":"field","vis":"private","signature":"Private mCreated As Date"}, + {"name":"mModified","kind":"field","vis":"private","signature":"Private mModified As Date"}, + {"name":"mPath","kind":"field","vis":"private","signature":"Private mPath As String"} + ] + }, + { + "name": "Folders", + "kind": "class", + "vis": "public", + "file": "Folders.twin", + "signature": "Public Class Folders", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "A read-only collection of Folder objects, enumerable with For Each.", + "members": [ + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Returns the number of Folder objects available."}, + {"name":"Internal_Init","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Init(ByVal items As Collection)"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal nameOrIndex As Variant) As Folder","returnType":"Folder","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns a specific Folder object by name or index."}, + {"name":"NewEnum","kind":"function","vis":"public","signature":"Public Function NewEnum() As IUnknown","returnType":"IUnknown","hidden":true,"attrs":[{"name":"Enumerator"},{"name":"Hidden","args":"True"}]}, + {"name":"mItems","kind":"field","vis":"private","signature":"Private mItems As Collection"} + ] + }, + { + "name": "TextCodec", + "kind": "module", + "vis": "private", + "public": false, + "file": "TextCodec.twin", + "signature": "Private Module TextCodec", + "members": [ + {"name":"BomBytes","kind":"function","vis":"public","signature":"Public Function BomBytes(ByVal enc As TextEncoding) As Byte()","returnType":"Byte()"}, + {"name":"BomLen","kind":"function","vis":"public","signature":"Public Function BomLen(ByVal enc As TextEncoding) As Long","returnType":"Long"}, + {"name":"BytesEqual","kind":"function","vis":"public","signature":"Public Function BytesEqual(ByRef a() As Byte, ByRef b() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"BytesToHexString","kind":"function","vis":"private","signature":"Private Function BytesToHexString(bytes() As Byte) As String","returnType":"String"}, + {"name":"ChangeNewLine","kind":"function","vis":"public","signature":"Public Function ChangeNewLine(ByRef text As String, ByVal outLineEnding As NewlineType) As String","returnType":"String"}, + {"name":"CodePageOf","kind":"function","vis":"public","signature":"Public Function CodePageOf(ByVal enc As TextEncoding) As Long","returnType":"Long"}, + {"name":"ConcatBytes","kind":"function","vis":"public","signature":"Public Function ConcatBytes(ByRef a() As Byte, ByRef b() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"ConvertFromUTF16","kind":"function","vis":"private","signature":"Private Function ConvertFromUTF16(ByRef text As String, ByVal toCodePage As Long) As Byte()","returnType":"Byte()"}, + {"name":"ConvertToUTF16","kind":"function","vis":"private","signature":"Private Function ConvertToUTF16(ByRef stream() As Byte, ByVal fromCodePage As Long) As String","returnType":"String"}, + {"name":"ConvertUTF16toUTF32","kind":"function","vis":"private","signature":"Private Function ConvertUTF16toUTF32(utf16str As String) As Byte()","returnType":"Byte()"}, + {"name":"ConvertUTF32toUTF16","kind":"function","vis":"private","signature":"Private Function ConvertUTF32toUTF16(utf32Bytes() As Byte) As String","returnType":"String"}, + {"name":"CountSub","kind":"function","vis":"private","signature":"Private Function CountSub(ByVal s As String, ByVal find As String) As Long","returnType":"Long"}, + {"name":"CountUTF16UnitsFromUTF32","kind":"function","vis":"private","signature":"Private Function CountUTF16UnitsFromUTF32(utf32Bytes() As Byte) As Long","returnType":"Long"}, + {"name":"CountUTF32UnitsFromUTF16","kind":"function","vis":"private","signature":"Private Function CountUTF32UnitsFromUTF16(utf16Bytes() As Byte) As Long","returnType":"Long"}, + {"name":"Decode","kind":"function","vis":"public","signature":"Public Function Decode(ByRef stream() As Byte, ByVal enc As TextEncoding, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String"}, + {"name":"DecodeBytes","kind":"function","vis":"public","signature":"Public Function DecodeBytes(ByRef stream() As Byte, ByVal codePage As Long, Optional ByVal outLineEnding As NewlineType = nlNoChange) As String","returnType":"String"}, + {"name":"Detect","kind":"function","vis":"public","signature":"Public Function Detect(ByRef stream() As Byte, Optional ByVal seed As TextEncoding = encAutoDetect) As TextEncoding","returnType":"TextEncoding"}, + {"name":"DetectNewlineType","kind":"function","vis":"public","signature":"Public Function DetectNewlineType(ByVal text As String) As NewlineType","returnType":"NewlineType"}, + {"name":"DetectUTF1632","kind":"function","vis":"private","signature":"Private Function DetectUTF1632(stream() As Byte, Optional ByVal maxBytesToScan As Long = 2000, Optional ByVal maxNullsToFind As Long = 100) As TextEncoding","returnType":"TextEncoding"}, + {"name":"Encode","kind":"function","vis":"public","signature":"Public Function Encode(ByVal text As String, ByVal enc As TextEncoding) As Byte()","returnType":"Byte()"}, + {"name":"EncodeBytes","kind":"function","vis":"public","signature":"Public Function EncodeBytes(ByVal text As String, ByVal codePage As Long) As Byte()","returnType":"Byte()"}, + {"name":"GetNewLine","kind":"function","vis":"public","signature":"Public Function GetNewLine(ByVal nlType As NewlineType) As String","returnType":"String"}, + {"name":"HALF_BASE","kind":"const","vis":"private","signature":"Private Const HALF_BASE As Long = &H10000"}, + {"name":"HasBom","kind":"function","vis":"private","signature":"Private Function HasBom(ByVal enc As TextEncoding) As Boolean","returnType":"Boolean"}, + {"name":"IsValidUTF8","kind":"function","vis":"private","signature":"Private Function IsValidUTF8(stream() As Byte, Optional ByVal maxBytesToScan As Long = 8192) As Boolean","returnType":"Boolean"}, + {"name":"IsWideText","kind":"function","vis":"public","signature":"Public Function IsWideText(ByVal enc As TextEncoding) As Boolean","returnType":"Boolean"}, + {"name":"LooksLikeBinary","kind":"function","vis":"public","signature":"Public Function LooksLikeBinary(ByRef raw() As Byte, Optional ByVal maxBytesToScan As Long = 8192) As Boolean","returnType":"Boolean"}, + {"name":"SafeCountB","kind":"function","vis":"public","signature":"Public Function SafeCountB(ByRef b() As Byte) As Long","returnType":"Long"}, + {"name":"SliceBytes","kind":"function","vis":"public","signature":"Public Function SliceBytes(ByRef b() As Byte, ByVal startIdx As Long, ByVal endIdx As Long) As Byte()","returnType":"Byte()"}, + {"name":"SniffBom","kind":"function","vis":"private","signature":"Private Function SniffBom(ByRef stream() As Byte) As TextEncoding","returnType":"TextEncoding"}, + {"name":"SwapByteOrder16","kind":"sub","vis":"private","signature":"Private Sub SwapByteOrder16(ByRef stream() As Byte)"}, + {"name":"SwapByteOrder32","kind":"sub","vis":"private","signature":"Private Sub SwapByteOrder32(ByRef stream() As Byte)"}, + {"name":"UNI_MAX_BMP","kind":"const","vis":"private","signature":"Private Const UNI_MAX_BMP As Long = &HFFFF&"}, + {"name":"UNI_SUR_HIGH_END","kind":"const","vis":"private","signature":"Private Const UNI_SUR_HIGH_END As Long = &HDBFF&"}, + {"name":"UNI_SUR_HIGH_START","kind":"const","vis":"private","signature":"Private Const UNI_SUR_HIGH_START As Long = &HD800&"}, + {"name":"UNI_SUR_LOW_END","kind":"const","vis":"private","signature":"Private Const UNI_SUR_LOW_END As Long = &HDFFF&"}, + {"name":"UNI_SUR_LOW_START","kind":"const","vis":"private","signature":"Private Const UNI_SUR_LOW_START As Long = &HDC00&"} + ] + }, + { + "name": "TextStream", + "kind": "class", + "vis": "public", + "file": "TextStream.twin", + "signature": "Public Class TextStream", + "attrs": [{"name":"Hidden","args":"False"}], + "description": "A streaming text reader/writer returned by FileSystem.OpenTextFile, supporting auto-detected and explicit encodings with large-file streaming.", + "members": [ + {"name":"AdvancePosition","kind":"sub","vis":"private","signature":"Private Sub AdvancePosition(ByRef consumed As String)"}, + {"name":"AtEndOfLine","kind":"property","vis":"public","signature":"Public Property Get AtEndOfLine() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when the next character to read is a line terminator (CR or LF)."}, + {"name":"AtEndOfStream","kind":"property","vis":"public","signature":"Public Property Get AtEndOfStream() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when the read cursor is at end of stream."}, + {"name":"Available","kind":"function","vis":"private","signature":"Private Function Available() As Long","returnType":"Long"}, + {"name":"CREATE_ALWAYS","kind":"const","vis":"private","signature":"Private Const CREATE_ALWAYS As Long = 2"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Classify","kind":"function","vis":"private","signature":"Private Function Classify(ByVal codePage As Long, ByRef kind As DecodeKind, ByRef leadRanges() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"Close","kind":"sub","vis":"public","signature":"Public Sub Close()","description":"Flushes pending writes and closes the stream. Further operations raise an error."}, + {"name":"Column","kind":"property","vis":"public","signature":"Public Property Get Column() As Long","returnType":"Long","accessor":"Get","description":"Current 1-based column number."}, + {"name":"CompletePrefixLen","kind":"function","vis":"private","signature":"Private Function CompletePrefixLen(ByRef b() As Byte, ByVal total As Long, ByVal kind As DecodeKind, ByRef leadRanges() As Byte) As Long","returnType":"Long"}, + {"name":"CountOf","kind":"function","vis":"private","signature":"Private Function CountOf(ByRef s As String, ByVal find As String) As Long","returnType":"Long"}, + {"name":"DbcsPrefixLen","kind":"function","vis":"private","signature":"Private Function DbcsPrefixLen(ByRef b() As Byte, ByVal total As Long, ByRef leadRanges() As Byte) As Long","returnType":"Long"}, + {"name":"ERR_NOT_TEXT","kind":"const","vis":"private","signature":"Private Const ERR_NOT_TEXT As Long = 5"}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding() As TextEncoding","returnType":"TextEncoding","accessor":"Get","description":"The resolved encoding of the stream (auto-detected for reads when not specified). BOM-bearing encodings report as their negative (e.g. encUtf8Bom)."}, + {"name":"EnsureChars","kind":"sub","vis":"private","signature":"Private Sub EnsureChars(ByVal n As Long)"}, + {"name":"EnsureOpen","kind":"sub","vis":"private","signature":"Private Sub EnsureOpen()"}, + {"name":"EnsureReadable","kind":"sub","vis":"private","signature":"Private Sub EnsureReadable()"}, + {"name":"EnsureWritable","kind":"sub","vis":"private","signature":"Private Sub EnsureWritable()"}, + {"name":"FILE_APPEND_DATA","kind":"const","vis":"private","signature":"Private Const FILE_APPEND_DATA As Long = &H4"}, + {"name":"FILE_SHARE_READ","kind":"const","vis":"private","signature":"Private Const FILE_SHARE_READ As Long = &H1"}, + {"name":"FillSome","kind":"function","vis":"private","signature":"Private Function FillSome() As Boolean","returnType":"Boolean"}, + {"name":"Flush","kind":"sub","vis":"public","signature":"Public Sub Flush()","description":"Commits pending writes to disk. In streaming mode this encodes and writes the buffered text now; the file stays open."}, + {"name":"FlushBuffered","kind":"sub","vis":"private","signature":"Private Sub FlushBuffered()"}, + {"name":"FlushWriteChunk","kind":"sub","vis":"private","signature":"Private Sub FlushWriteChunk(ByVal flushAll As Boolean)"}, + {"name":"GENERIC_WRITE","kind":"const","vis":"private","signature":"Private Const GENERIC_WRITE As Long = &H40000000"}, + {"name":"Gb18030PrefixLen","kind":"function","vis":"private","signature":"Private Function Gb18030PrefixLen(ByRef b() As Byte, ByVal total As Long) As Long","returnType":"Long"}, + {"name":"GuardCreateName","kind":"sub","vis":"private","signature":"Private Sub GuardCreateName(ByVal path As String, ByVal opName As String)"}, + {"name":"Init","kind":"sub","vis":"friend","signature":"Friend Sub Init(ByVal filePath As String, ByVal ioMode As IOMode, ByVal create As Boolean, ByVal format As TextEncoding, ByVal lineEnding As NewlineType)"}, + {"name":"InitAppending","kind":"sub","vis":"private","signature":"Private Sub InitAppending(ByVal format As TextEncoding, ByVal create As Boolean)"}, + {"name":"InitReading","kind":"sub","vis":"private","signature":"Private Sub InitReading(ByVal format As TextEncoding)"}, + {"name":"InitStdStream","kind":"sub","vis":"friend","signature":"Friend Sub InitStdStream(ByVal handle As LongPtr, ByVal isInput As Boolean, ByVal wide As Boolean)"}, + {"name":"InitWriting","kind":"sub","vis":"private","signature":"Private Sub InitWriting(ByVal format As TextEncoding, ByVal create As Boolean)"}, + {"name":"IsCharCR","kind":"function","vis":"private","signature":"Private Function IsCharCR(ByRef text As String, ByVal pos As Long) As Boolean","returnType":"Boolean"}, + {"name":"IsDbcsLead","kind":"function","vis":"private","signature":"Private Function IsDbcsLead(ByVal bb As Long, ByRef leadRanges() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"IsStreaming","kind":"property","vis":"public","signature":"Public Property Get IsStreaming() As Boolean","returnType":"Boolean","accessor":"Get","description":"True if the stream uses byte-streaming; False if it fell back to a buffered (whole-file) path."}, + {"name":"Line","kind":"property","vis":"public","signature":"Public Property Get Line() As Long","returnType":"Long","accessor":"Get","description":"Current 1-based line number."}, + {"name":"NextLineBreakIn","kind":"function","vis":"private","signature":"Private Function NextLineBreakIn(ByRef text As String, ByVal startPos As Long, ByRef breakLen As Long) As Long","returnType":"Long"}, + {"name":"OPEN_EXISTING","kind":"const","vis":"private","signature":"Private Const OPEN_EXISTING As Long = 3"}, + {"name":"OpenReadStream","kind":"sub","vis":"private","signature":"Private Sub OpenReadStream(ByVal bomLen As Long)"}, + {"name":"OpenWriteAppend","kind":"sub","vis":"private","signature":"Private Sub OpenWriteAppend()"}, + {"name":"OpenWriteFresh","kind":"sub","vis":"private","signature":"Private Sub OpenWriteFresh()"}, + {"name":"Read","kind":"function","vis":"public","signature":"Public Function Read(ByVal characters As Long) As String","returnType":"String","description":"Reads the specified number of characters and returns the resulting string."}, + {"name":"ReadAll","kind":"function","vis":"public","signature":"Public Function ReadAll() As String","returnType":"String","description":"Reads the entire remaining stream and returns the resulting string. In streaming mode this reads and decodes the remainder in a single pass, but still holds the whole remainder in memory; prefer ReadLine for very large files."}, + {"name":"ReadLine","kind":"function","vis":"public","signature":"Public Function ReadLine() As String","returnType":"String","description":"Reads an entire line (up to, but not including, the newline) and returns the resulting string."}, + {"name":"STREAM_CHUNK","kind":"const","vis":"private","signature":"Private Const STREAM_CHUNK As Long = 65536"}, + {"name":"Skip","kind":"sub","vis":"public","signature":"Public Sub Skip(ByVal characters As Long)","description":"Skips the specified number of characters when reading."}, + {"name":"SkipLine","kind":"sub","vis":"public","signature":"Public Sub SkipLine()","description":"Skips the next line when reading."}, + {"name":"SourceExhausted","kind":"function","vis":"private","signature":"Private Function SourceExhausted() As Boolean","returnType":"Boolean"}, + {"name":"StdAtEnd","kind":"function","vis":"private","signature":"Private Function StdAtEnd() As Boolean","returnType":"Boolean"}, + {"name":"StdAtEndOfLine","kind":"function","vis":"private","signature":"Private Function StdAtEndOfLine() As Boolean","returnType":"Boolean"}, + {"name":"StdEnsureInput","kind":"sub","vis":"private","signature":"Private Sub StdEnsureInput()"}, + {"name":"StdFill","kind":"sub","vis":"private","signature":"Private Sub StdFill(ByVal wantBytes As Long)"}, + {"name":"StdReadAll","kind":"function","vis":"private","signature":"Private Function StdReadAll() As String","returnType":"String"}, + {"name":"StdReadChars","kind":"function","vis":"private","signature":"Private Function StdReadChars(ByVal n As Long) As String","returnType":"String"}, + {"name":"StdReadLine","kind":"function","vis":"private","signature":"Private Function StdReadLine() As String","returnType":"String"}, + {"name":"StdWriteString","kind":"sub","vis":"private","signature":"Private Sub StdWriteString(ByVal text As String)"}, + {"name":"Utf16PrefixLen","kind":"function","vis":"private","signature":"Private Function Utf16PrefixLen(ByRef b() As Byte, ByVal total As Long, ByVal isLE As Boolean) As Long","returnType":"Long"}, + {"name":"Utf8PrefixLen","kind":"function","vis":"private","signature":"Private Function Utf8PrefixLen(ByRef b() As Byte, ByVal total As Long) As Long","returnType":"Long"}, + {"name":"Write","kind":"sub","vis":"public","signature":"Public Sub Write(ByVal text As String)","description":"Writes the specified string to the stream (no trailing newline)."}, + {"name":"WriteBlankLines","kind":"sub","vis":"public","signature":"Public Sub WriteBlankLines(ByVal lines As Long)","description":"Writes the specified number of blank lines to the stream."}, + {"name":"WriteBytes","kind":"sub","vis":"private","signature":"Private Sub WriteBytes(ByRef bytes() As Byte)"}, + {"name":"WriteLine","kind":"sub","vis":"public","signature":"Public Sub WriteLine(Optional ByVal text As String = \"\")","description":"Writes the specified string followed by the stream's newline."}, + {"name":"mBuffer","kind":"field","vis":"private","signature":"Private mBuffer As String"}, + {"name":"mBytesRemaining","kind":"field","vis":"private","signature":"Private mBytesRemaining As LongLong"}, + {"name":"mCarry","kind":"field","vis":"private","signature":"Private mCarry() As Byte"}, + {"name":"mClosed","kind":"field","vis":"private","signature":"Private mClosed As Boolean"}, + {"name":"mColumn","kind":"field","vis":"private","signature":"Private mColumn As Long"}, + {"name":"mCp","kind":"field","vis":"private","signature":"Private mCp As Long"}, + {"name":"mDecPos","kind":"field","vis":"private","signature":"Private mDecPos As Long"}, + {"name":"mDecoded","kind":"field","vis":"private","signature":"Private mDecoded As String"}, + {"name":"mDirty","kind":"field","vis":"private","signature":"Private mDirty As Boolean"}, + {"name":"mEnc","kind":"field","vis":"private","signature":"Private mEnc As TextEncoding"}, + {"name":"mFile","kind":"field","vis":"private","signature":"Private mFile As Long"}, + {"name":"mHandle","kind":"field","vis":"private","signature":"Private mHandle As LongPtr"}, + {"name":"mIsStd","kind":"field","vis":"private","signature":"Private mIsStd As Boolean"}, + {"name":"mKind","kind":"field","vis":"private","signature":"Private mKind As DecodeKind"}, + {"name":"mLeadRanges","kind":"field","vis":"private","signature":"Private mLeadRanges() As Byte"}, + {"name":"mLine","kind":"field","vis":"private","signature":"Private mLine As Long"}, + {"name":"mLineEnding","kind":"field","vis":"private","signature":"Private mLineEnding As String"}, + {"name":"mMode","kind":"field","vis":"private","signature":"Private mMode As IOMode"}, + {"name":"mPath","kind":"field","vis":"private","signature":"Private mPath As String"}, + {"name":"mPos","kind":"field","vis":"private","signature":"Private mPos As Long"}, + {"name":"mStdCp","kind":"field","vis":"private","signature":"Private mStdCp As Long"}, + {"name":"mStdEof","kind":"field","vis":"private","signature":"Private mStdEof As Boolean"}, + {"name":"mStdHandle","kind":"field","vis":"private","signature":"Private mStdHandle As LongPtr"}, + {"name":"mStdInBuf","kind":"field","vis":"private","signature":"Private mStdInBuf As String"}, + {"name":"mStdInput","kind":"field","vis":"private","signature":"Private mStdInput As Boolean"}, + {"name":"mStdKind","kind":"field","vis":"private","signature":"Private mStdKind As DecodeKind"}, + {"name":"mStdLead","kind":"field","vis":"private","signature":"Private mStdLead() As Byte"}, + {"name":"mStdRaw","kind":"field","vis":"private","signature":"Private mStdRaw() As Byte"}, + {"name":"mStreaming","kind":"field","vis":"private","signature":"Private mStreaming As Boolean"}, + {"name":"mWriteBuf","kind":"field","vis":"private","signature":"Private mWriteBuf As String"} + ] + }, + { + "name": "DecodeKind", + "kind": "enum", + "in": "TextStream", + "vis": "private", + "public": false, + "file": "TextStream.twin", + "signature": "Private Enum DecodeKind", + "values": [ + {"name":"dkSingle"}, + {"name":"dkUtf8"}, + {"name":"dkUtf16LE"}, + {"name":"dkUtf16BE"}, + {"name":"dkUtf32"}, + {"name":"dkDBCS"}, + {"name":"dkGB18030"} + ] + }, + { + "name": "WinAPI", + "kind": "module", + "vis": "private", + "public": false, + "file": "WinAPI.twin", + "signature": "Private Module WinAPI", + "members": [ + {"name":"CLSIDFromString","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CLSIDFromString Lib \"ole32\" (ByVal lpsz As LongPtr, ByRef lpclsid As UUID) As Long","returnType":"Long"}, + {"name":"CloseHandle","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CloseHandle Lib \"kernel32\" (ByVal hObject As LongPtr) As BOOL","returnType":"BOOL"}, + {"name":"CoTaskMemFree","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub CoTaskMemFree Lib \"ole32\" (ByVal pv As LongPtr)"}, + {"name":"CopyFileExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CopyFileExW Lib \"kernel32\" (ByVal lpExistingFileName As LongPtr, ByVal lpNewFileName As LongPtr, ByVal lpProgresRoutine As LongPtr, lpData As Any, pbCancel As BOOL, ByVal dwCopyFlags As CopyFileFlags) As BOOL","returnType":"BOOL"}, + {"name":"CopyMemory","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" (Destination As Any, Source As Any, ByVal Length As LongPtr)"}, + {"name":"CreateDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CreateDirectoryW Lib \"kernel32\" (ByVal lpPathName As LongPtr, lpSecurityAttributes As SECURITY_ATTRIBUTES) As BOOL","returnType":"BOOL"}, + {"name":"CreateFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CreateFileW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal dwDesiredAccess As Long, ByVal dwShareMode As FileShareMode, lpSecurityAttributes As SECURITY_ATTRIBUTES, ByVal dwCreationDisposition As CreateFileDisposition, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"DeleteFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function DeleteFileW Lib \"kernel32\" (ByVal lpFileName As LongPtr) As BOOL","returnType":"BOOL"}, + {"name":"ERROR_ACCESS_DENIED","kind":"const","vis":"public","signature":"Public Const ERROR_ACCESS_DENIED As Long = 5"}, + {"name":"ERROR_ALREADY_EXISTS","kind":"const","vis":"public","signature":"Public Const ERROR_ALREADY_EXISTS As Long = 183"}, + {"name":"ERROR_BAD_NETPATH","kind":"const","vis":"public","signature":"Public Const ERROR_BAD_NETPATH As Long = 53"}, + {"name":"ERROR_CONNECTION_UNAVAIL","kind":"const","vis":"public","signature":"Public Const ERROR_CONNECTION_UNAVAIL As Long = 1201"}, + {"name":"ERROR_DIRECTORY","kind":"const","vis":"public","signature":"Public Const ERROR_DIRECTORY As Long = 267"}, + {"name":"ERROR_DISK_FULL","kind":"const","vis":"public","signature":"Public Const ERROR_DISK_FULL As Long = 112"}, + {"name":"ERROR_FILE_EXISTS","kind":"const","vis":"public","signature":"Public Const ERROR_FILE_EXISTS As Long = 80"}, + {"name":"ERROR_FILE_NOT_FOUND","kind":"const","vis":"public","signature":"Public Const ERROR_FILE_NOT_FOUND As Long = 2"}, + {"name":"ERROR_HANDLE_DISK_FULL","kind":"const","vis":"public","signature":"Public Const ERROR_HANDLE_DISK_FULL As Long = 39"}, + {"name":"ERROR_INVALID_DRIVE","kind":"const","vis":"public","signature":"Public Const ERROR_INVALID_DRIVE As Long = 15"}, + {"name":"ERROR_INVALID_NAME","kind":"const","vis":"public","signature":"Public Const ERROR_INVALID_NAME As Long = 123"}, + {"name":"ERROR_INVALID_PARAMETER","kind":"const","vis":"public","signature":"Public Const ERROR_INVALID_PARAMETER As Long = &H57"}, + {"name":"ERROR_LOCK_VIOLATION","kind":"const","vis":"public","signature":"Public Const ERROR_LOCK_VIOLATION As Long = 33"}, + {"name":"ERROR_MORE_DATA","kind":"const","vis":"public","signature":"Public Const ERROR_MORE_DATA As Long = 234"}, + {"name":"ERROR_NOT_CONNECTED","kind":"const","vis":"public","signature":"Public Const ERROR_NOT_CONNECTED As Long = 2250"}, + {"name":"ERROR_NOT_READY","kind":"const","vis":"public","signature":"Public Const ERROR_NOT_READY As Long = 21"}, + {"name":"ERROR_NOT_SAME_DEVICE","kind":"const","vis":"public","signature":"Public Const ERROR_NOT_SAME_DEVICE As Long = 17"}, + {"name":"ERROR_NO_MORE_FILES","kind":"const","vis":"public","signature":"Public Const ERROR_NO_MORE_FILES As Long = 18"}, + {"name":"ERROR_NO_MORE_ITEMS","kind":"const","vis":"public","signature":"Public Const ERROR_NO_MORE_ITEMS As Long = 259"}, + {"name":"ERROR_NO_UNICODE_TRANSLATION","kind":"const","vis":"public","signature":"Public Const ERROR_NO_UNICODE_TRANSLATION As Long = &H459"}, + {"name":"ERROR_PATH_NOT_FOUND","kind":"const","vis":"public","signature":"Public Const ERROR_PATH_NOT_FOUND As Long = 3"}, + {"name":"ERROR_SHARING_VIOLATION","kind":"const","vis":"public","signature":"Public Const ERROR_SHARING_VIOLATION As Long = 32"}, + {"name":"ERROR_SUCCESS","kind":"const","vis":"public","signature":"Public Const ERROR_SUCCESS As Long = 0"}, + {"name":"FileTimeToLocalFileTime","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FileTimeToLocalFileTime Lib \"kernel32\" (lpFileTime As FILETIME, lpLocalFileTime As FILETIME) As BOOL","returnType":"BOOL"}, + {"name":"FileTimeToSystemTime","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FileTimeToSystemTime Lib \"kernel32\" (lpFileTime As FILETIME, lpSystemTime As SYSTEMTIME) As BOOL","returnType":"BOOL"}, + {"name":"FindClose","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindClose Lib \"kernel32\" (ByVal hFindFile As LongPtr) As Long","returnType":"Long"}, + {"name":"FindFirstFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindFirstFileW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByRef lpFindFileData As WIN32_FIND_DATAW) As LongPtr","returnType":"LongPtr"}, + {"name":"FindNextFileW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FindNextFileW Lib \"kernel32\" (ByVal hFindFile As LongPtr, ByRef lpFindFileData As WIN32_FIND_DATAW) As Long","returnType":"Long"}, + {"name":"GetACP","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetACP Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetCPInfo","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetCPInfo Lib \"kernel32\" (ByVal codePage As Long, ByRef lpCPInfo As CPINFO) As Long","returnType":"Long"}, + {"name":"GetCurrentDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetCurrentDirectoryW Lib \"kernel32\" (ByVal nBufferLength As Long, ByVal lpBuffer As LongPtr) As Long","returnType":"Long"}, + {"name":"GetDiskFreeSpaceExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetDiskFreeSpaceExW Lib \"kernel32\" (ByVal lpDirectoryName As LongPtr, ByRef lpFreeBytesAvailable As LongLong, ByRef lpTotalNumberOfBytes As LongLong, ByRef lpTotalNumberOfFreeBytes As LongLong) As Long","returnType":"Long"}, + {"name":"GetDriveTypeW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetDriveTypeW Lib \"kernel32\" (ByVal lpRootPathName As LongPtr) As Long","returnType":"Long"}, + {"name":"GetFileAttributesExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetFileAttributesExW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal fInfoLevelId As GET_FILEEX_INFO_LEVELS, ByRef lpFileInformation As Any) As BOOL","returnType":"BOOL"}, + {"name":"GetFileAttributesW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetFileAttributesW Lib \"kernel32\" (ByVal lpFileName As LongPtr) As FILE_ATTRIBUTES","returnType":"FILE_ATTRIBUTES"}, + {"name":"GetFileVersionInfoSizeW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetFileVersionInfoSizeW Lib \"version.dll\" (ByVal lptstrFilename As LongPtr, lpdwHandle As Long) As Long","returnType":"Long"}, + {"name":"GetFileVersionInfoW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetFileVersionInfoW Lib \"version.dll\" (ByVal lptstrFilename As LongPtr, ByVal dwhandle As Long, ByVal dwLen As Long, lpData As Any) As BOOL","returnType":"BOOL"}, + {"name":"GetFullPathNameW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetFullPathNameW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal nBufferLength As Long, ByVal lpBuffer As LongPtr, lpFilePart As LongPtr) As Long","returnType":"Long"}, + {"name":"GetLastError","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetLastError Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetLogicalDrives","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetLogicalDrives Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetShortPathNameW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetShortPathNameW Lib \"kernel32\" (ByVal lpszLongPath As LongPtr, ByVal lpszShortPath As LongPtr, ByVal cchBuffer As Long) As Long","returnType":"Long"}, + {"name":"GetStdHandle","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetStdHandle Lib \"kernel32\" (ByVal nStdHandle As STD_HANDLES) As LongPtr","returnType":"LongPtr"}, + {"name":"GetVolumeInformationW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetVolumeInformationW Lib \"kernel32\" (ByVal lpRootPathName As LongPtr, ByVal lpVolumeNameBuffer As LongPtr, ByVal nVolumeNameSize As Long, ByRef lpVolumeSerialNumber As Long, ByRef lpMaximumComponentLength As Long, ByRef lpFileSystemFlags As Long, ByVal lpFileSystemNameBuffer As LongPtr, ByVal nFileSystemNameSize As Long) As Long","returnType":"Long"}, + {"name":"INVALID_HANDLE_VALUE","kind":"const","vis":"public","signature":"Public Const INVALID_HANDLE_VALUE As LongPtr = -1"}, + {"name":"LPWSTRtoStr","kind":"function","vis":"public","signature":"Public Function LPWSTRtoStr(lPtr As LongPtr, Optional ByVal fFree As Boolean = True) As String","returnType":"String"}, + {"name":"MAX_PATH","kind":"const","vis":"public","signature":"Public Const MAX_PATH As Long = 260"}, + {"name":"MoveFileExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function MoveFileExW Lib \"kernel32\" (ByVal lpExistingFileName As LongPtr, ByVal lpNewFileName As LongPtr, ByVal dwFlags As MoveFileExFlags) As BOOL","returnType":"BOOL"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32\" (ByVal CodePage As Long, ByVal dwFlags As MBWC_Flags, ByVal lpMultiByteStr As LongPtr, ByVal cchMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"PathRelativePathToW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function PathRelativePathToW Lib \"shlwapi\" (ByVal pszPath As LongPtr, ByVal pszFrom As LongPtr, ByVal dwAttrFrom As FILE_ATTRIBUTES, ByVal pszTo As LongPtr, ByVal dwAttrTo As FILE_ATTRIBUTES) As BOOL","returnType":"BOOL"}, + {"name":"ReadFile","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function ReadFile Lib \"kernel32\" (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As OVERLAPPED) As BOOL","returnType":"BOOL"}, + {"name":"RemoveDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RemoveDirectoryW Lib \"kernel32\" (ByVal lpPathName As LongPtr) As BOOL","returnType":"BOOL"}, + {"name":"SHGetFileInfoW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SHGetFileInfoW Lib \"shell32\" (ByVal pszPath As Any, ByVal dwFileAttributes As Long, psfi As SHFILEINFOW, ByVal cbFileInfo As Long, ByVal uFlags As SHGFI_flags) As LongPtr","returnType":"LongPtr"}, + {"name":"SHGetKnownFolderPath","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SHGetKnownFolderPath Lib \"shell32\" (rfid As UUID, ByVal dwFlags As KNOWN_FOLDER_FLAG, ByVal hToken As LongPtr, ppszPath As LongPtr) As Long","returnType":"Long"}, + {"name":"SetCurrentDirectoryW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetCurrentDirectoryW Lib \"kernel32\" (ByVal lpPathName As LongPtr) As BOOL","returnType":"BOOL"}, + {"name":"SetFileAttributesW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetFileAttributesW Lib \"kernel32\" (ByVal lpFileName As LongPtr, ByVal dwFileAttributes As FILE_ATTRIBUTES) As BOOL","returnType":"BOOL"}, + {"name":"SetLastError","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub SetLastError Lib \"kernel32\" (ByVal dwErrCode As Long)"}, + {"name":"SetVolumeLabelW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetVolumeLabelW Lib \"kernel32\" (ByVal lpRootPathName As LongPtr, ByVal lpVolumeName As LongPtr) As Long","returnType":"Long"}, + {"name":"SysReAllocStringW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SysReAllocStringW Lib \"oleaut32\" Alias \"SysReAllocString\" (ByVal pBSTR As LongPtr, Optional ByVal pszStrPtr As LongPtr) As Long","returnType":"Long"}, + {"name":"VerQueryValueW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function VerQueryValueW Lib \"version.dll\" (ByRef pBlock As Any, ByVal lpSubBlock As LongPtr, ByRef lplpBuffer As LongPtr, ByRef puLen As Long) As BOOL","returnType":"BOOL"}, + {"name":"WNetGetConnectionW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function WNetGetConnectionW Lib \"mpr.dll\" (ByVal lpLocalName As LongPtr, ByVal lpRemoteName As LongPtr, lpnLength As Long) As Long","returnType":"Long"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function WideCharToMultiByte Lib \"kernel32\" (ByVal CodePage As Long, ByVal dwFlags As WCMB_Flags, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, Optional ByVal lpDefaultChar As LongPtr, Optional lpUsedDefaultChar As BOOL) As Long","returnType":"Long"}, + {"name":"WriteFile","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function WriteFile Lib \"kernel32\" (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As OVERLAPPED) As BOOL","returnType":"BOOL"}, + {"name":"lstrlenW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function lstrlenW Lib \"kernel32\" (lpString As Any) As Long","returnType":"Long"} + ] + }, + { + "name": "BOOL", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum BOOL", + "values": [ + {"name":"CFALSE"}, + {"name":"CTRUE"} + ] + }, + { + "name": "CPINFO", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type CPINFO", + "fields": [ + {"name":"MaxCharSize","vis":"public","signature":"MaxCharSize As Long"}, + {"name":"DefaultChar","vis":"public","signature":"DefaultChar(0 To 1) As Byte"}, + {"name":"LeadByte","vis":"public","signature":"LeadByte(0 To 11) As Byte"} + ], + "members": [] + }, + { + "name": "CopyFileFlags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum CopyFileFlags", + "values": [ + {"name":"COPY_FILE_FAIL_IF_EXISTS","value":"&H00000001"} + ] + }, + { + "name": "CreateFileDisposition", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum CreateFileDisposition", + "values": [ + {"name":"CREATE_NEW","value":"1"}, + {"name":"CREATE_ALWAYS","value":"2"}, + {"name":"OPEN_EXISTING","value":"3"}, + {"name":"OPEN_ALWAYS","value":"4"}, + {"name":"TRUNCATE_EXISTING","value":"5"} + ] + }, + { + "name": "DROPEFFECTS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum DROPEFFECTS", + "values": [ + {"name":"DROPEFFECT_NONE","value":"0"}, + {"name":"DROPEFFECT_COPY","value":"1"}, + {"name":"DROPEFFECT_MOVE","value":"2"}, + {"name":"DROPEFFECT_LINK","value":"4"}, + {"name":"DROPEFFECT_SCROLL","value":"&H80000000"} + ] + }, + { + "name": "FILETIME", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type FILETIME", + "fields": [ + {"name":"dwLowDateTime","vis":"public","signature":"dwLowDateTime As Long"}, + {"name":"dwHighDateTime","vis":"public","signature":"dwHighDateTime As Long"} + ], + "members": [] + }, + { + "name": "FILE_ATTRIBUTES", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum FILE_ATTRIBUTES", + "values": [ + {"name":"INVALID_FILE_ATTRIBUTES","value":"-1"}, + {"name":"FILE_ATTRIBUTE_READONLY","value":"&H00000001"}, + {"name":"FILE_ATTRIBUTE_HIDDEN","value":"&H00000002"}, + {"name":"FILE_ATTRIBUTE_SYSTEM","value":"&H00000004"}, + {"name":"FILE_ATTRIBUTE_DIRECTORY","value":"&H00000010"}, + {"name":"FILE_ATTRIBUTE_ARCHIVE","value":"&H00000020"}, + {"name":"FILE_ATTRIBUTE_DEVICE","value":"&H00000040"}, + {"name":"FILE_ATTRIBUTE_NORMAL","value":"&H00000080"}, + {"name":"FILE_ATTRIBUTE_TEMPORARY","value":"&H00000100"}, + {"name":"FILE_ATTRIBUTE_SPARSE_FILE","value":"&H00000200"}, + {"name":"FILE_ATTRIBUTE_REPARSE_POINT","value":"&H00000400"}, + {"name":"FILE_ATTRIBUTE_COMPRESSED","value":"&H00000800"}, + {"name":"FILE_ATTRIBUTE_OFFLINE","value":"&H00001000"}, + {"name":"FILE_ATTRIBUTE_NOT_CONTENT_INDEXED","value":"&H00002000"}, + {"name":"FILE_ATTRIBUTE_ENCRYPTED","value":"&H00004000"}, + {"name":"FILE_ATTRIBUTE_INTEGRITY_STREAM","value":"&H00008000&"}, + {"name":"FILE_ATTRIBUTE_VIRTUAL","value":"&H00010000"}, + {"name":"FILE_ATTRIBUTE_NO_SCRUB_DATA","value":"&H00020000"}, + {"name":"FILE_ATTRIBUTE_EA","value":"&H00040000"}, + {"name":"FILE_ATTRIBUTE_RECALL_ON_OPEN","value":"&H00040000"}, + {"name":"FILE_ATTRIBUTE_PINNED","value":"&H000080000"}, + {"name":"FILE_ATTRIBUTE_UNPINNED","value":"&H00100000"}, + {"name":"FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS","value":"&H400000"}, + {"name":"FILE_ATTRIBUTE_STRICTLY_SEQUENTIAL","value":"&H20000000"} + ] + }, + { + "name": "FileShareMode", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum FileShareMode", + "values": [ + {"name":"FILE_SHARE_READ","value":"&H00000001"}, + {"name":"FILE_SHARE_WRITE","value":"&H00000002"}, + {"name":"FILE_SHARE_DELETE","value":"&H00000004"} + ] + }, + { + "name": "GET_FILEEX_INFO_LEVELS", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum GET_FILEEX_INFO_LEVELS", + "values": [ + {"name":"GetFileExInfoStandard","value":"0"}, + {"name":"GetFileExMaxInfoLevel","value":"1"} + ] + }, + { + "name": "KNOWN_FOLDER_FLAG", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum KNOWN_FOLDER_FLAG", + "values": [ + {"name":"KF_FLAG_DEFAULT","value":"&H00000000"}, + {"name":"KF_FLAG_SIMPLE_IDLIST","value":"&H00000100"}, + {"name":"KF_FLAG_NOT_PARENT_RELATIVE","value":"&H00000200"}, + {"name":"KF_FLAG_DEFAULT_PATH","value":"&H00000400"}, + {"name":"KF_FLAG_INIT","value":"&H00000800"}, + {"name":"KF_FLAG_NO_ALIAS","value":"&H00001000"}, + {"name":"KF_FLAG_DONT_UNEXPAND","value":"&H00002000"}, + {"name":"KF_FLAG_DONT_VERIFY","value":"&H00004000"}, + {"name":"KF_FLAG_CREATE","value":"&H00008000&"}, + {"name":"KF_FLAG_NO_APPCONTAINER_REDIRECTION","value":"&H00010000"}, + {"name":"KF_FLAG_NO_PACKAGE_REDIRECTION","value":"&H00010000"}, + {"name":"KF_FLAG_FORCE_PACKAGE_REDIRECTION","value":"&H00020000"}, + {"name":"KF_FLAG_FORCE_APPCONTAINER_REDIRECTION","value":"&H00020000"}, + {"name":"KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET","value":"&H00040000"}, + {"name":"KF_FLAG_FORCE_APP_DATA_REDIRECTION","value":"&H00080000"}, + {"name":"KF_FLAG_ALIAS_ONLY","value":"&H80000000"} + ] + }, + { + "name": "MBWC_Flags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum MBWC_Flags", + "values": [ + {"name":"MB_PRECOMPOSED","value":"&H1"}, + {"name":"MB_COMPOSITE","value":"&H2"}, + {"name":"MB_USEGLYPHCHARS","value":"&H4"}, + {"name":"MB_ERR_INVALID_CHARS","value":"&H8"} + ] + }, + { + "name": "MoveFileExFlags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum MoveFileExFlags", + "values": [ + {"name":"MOVEFILE_REPLACE_EXISTING","value":"&H00000001"}, + {"name":"MOVEFILE_COPY_ALLOWED","value":"&H00000002"} + ] + }, + { + "name": "OVERLAPPED", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type OVERLAPPED", + "fields": [ + {"name":"Internal","vis":"public","signature":"Internal As LongPtr"}, + {"name":"InternalHigh","vis":"public","signature":"InternalHigh As LongPtr"}, + {"name":"OffsetsOrPtr","vis":"public","signature":"OffsetsOrPtr As LongLong"}, + {"name":"OffsetOrPtr","vis":"public","signature":"OffsetOrPtr As Long"}, + {"name":"OffsetHigh","vis":"public","signature":"OffsetHigh As Long"}, + {"name":"hEvent","vis":"public","signature":"hEvent As LongPtr"} + ], + "members": [] + }, + { + "name": "REGSAM", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum REGSAM", + "values": [ + {"name":"KEY_QUERY_VALUE","value":"(&H0001)"}, + {"name":"KEY_SET_VALUE","value":"(&H0002)"}, + {"name":"KEY_CREATE_SUB_KEY","value":"(&H0004)"}, + {"name":"KEY_ENUMERATE_SUB_KEYS","value":"(&H0008)"}, + {"name":"KEY_NOTIFY","value":"(&H0010)"}, + {"name":"KEY_CREATE_LINK","value":"(&H0020)"}, + {"name":"KEY_WOW64_32KEY","value":"(&H0200)"}, + {"name":"KEY_WOW64_64KEY","value":"(&H0100)"}, + {"name":"KEY_WOW64_RES","value":"(&H0300)"}, + {"name":"KEY_READ","value":"((STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY) And (Not SYNCHRONIZE))"}, + {"name":"KEY_WRITE","value":"((STANDARD_RIGHTS_WRITE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY) And (Not SYNCHRONIZE))"}, + {"name":"KEY_EXECUTE","value":"((KEY_READ) And (Not SYNCHRONIZE))"}, + {"name":"KEY_ALL_ACCESS","value":"((STANDARD_RIGHTS_ALL Or KEY_QUERY_VALUE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY Or KEY_CREATE_LINK) And (Not SYNCHRONIZE))"} + ] + }, + { + "name": "REGTYPES", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum REGTYPES", + "values": [ + {"name":"REG_NONE","value":"0"}, + {"name":"REG_SZ","value":"1"}, + {"name":"REG_EXPAND_SZ","value":"2"}, + {"name":"REG_BINARY","value":"3"}, + {"name":"REG_DWORD","value":"4"}, + {"name":"REG_DWORD_BIG_ENDIAN","value":"5"}, + {"name":"REG_DWORD_LITTLE_ENDIAN","value":"4"}, + {"name":"REG_LINK","value":"6"}, + {"name":"REG_MULTI_SZ","value":"7"}, + {"name":"REG_RESOURCE_LIST","value":"8"}, + {"name":"REG_FULL_RESOURCE_DESCRIPTOR","value":"9"}, + {"name":"REG_RESOURCE_REQUIREMENTS_LIST","value":"&HA"}, + {"name":"REG_QWORD","value":"&HB"}, + {"name":"REG_QWORD_LITTLE_ENDIAN","value":"&HB"} + ] + }, + { + "name": "RegKeyspace", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum RegKeyspace", + "values": [ + {"name":"HKEY_CLASSES_ROOT","value":"&H80000000"}, + {"name":"HKEY_CURRENT_USER","value":"&H80000001"}, + {"name":"HKEY_LOCAL_MACHINE","value":"&H80000002"}, + {"name":"HKEY_USERS","value":"&H80000003"}, + {"name":"HKEY_PERFORMANCE_DATA","value":"&H80000004"}, + {"name":"HKEY_PERFORMANCE_TEXT","value":"&H80000050"}, + {"name":"HKEY_PERFORMANCE_NLSTEXT","value":"&H80000060"}, + {"name":"HKEY_CURRENT_CONFIG","value":"&H80000005"}, + {"name":"HKEY_DYN_DATA","value":"&H80000006"}, + {"name":"HKEY_CURRENT_USER_LOCAL_SETTINGS","value":"&H80000007"} + ] + }, + { + "name": "RegOptions", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum RegOptions", + "values": [ + {"name":"REG_OPTION_RESERVED","value":"(&H00000000)"}, + {"name":"REG_OPTION_NON_VOLATILE","value":"(&H00000000)"}, + {"name":"REG_OPTION_VOLATILE","value":"(&H00000001)"}, + {"name":"REG_OPTION_CREATE_LINK","value":"(&H00000002)"}, + {"name":"REG_OPTION_BACKUP_RESTORE","value":"(&H00000004)"}, + {"name":"REG_OPTION_OPEN_LINK","value":"(&H00000008)"}, + {"name":"REG_OPTION_DONT_VIRTUALIZE","value":"(&H00000010)"}, + {"name":"REG_LEGAL_OPTION","value":"(REG_OPTION_RESERVED Or REG_OPTION_NON_VOLATILE Or REG_OPTION_VOLATILE Or REG_OPTION_CREATE_LINK Or REG_OPTION_BACKUP_RESTORE Or REG_OPTION_OPEN_LINK Or REG_OPTION_DONT_VIRTUALIZE)"}, + {"name":"REG_OPEN_LEGAL_OPTION","value":"(REG_OPTION_RESERVED Or REG_OPTION_BACKUP_RESTORE Or REG_OPTION_OPEN_LINK Or REG_OPTION_DONT_VIRTUALIZE)"} + ] + }, + { + "name": "SECURITY_ATTRIBUTES", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type SECURITY_ATTRIBUTES", + "fields": [ + {"name":"nLength","vis":"public","signature":"nLength As Long"}, + {"name":"lpSecurityDescriptor","vis":"public","signature":"lpSecurityDescriptor As LongPtr"}, + {"name":"bInheritHandle","vis":"public","signature":"bInheritHandle As Long"} + ], + "members": [] + }, + { + "name": "SFGAOF", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum SFGAOF", + "values": [ + {"name":"SFGAO_CANCOPY","value":"DROPEFFECT_COPY"}, + {"name":"SFGAO_CANMOVE","value":"DROPEFFECT_MOVE"}, + {"name":"SFGAO_CANLINK","value":"DROPEFFECT_LINK"}, + {"name":"SFGAO_STORAGE","value":"&H00000008"}, + {"name":"SFGAO_CANRENAME","value":"&H00000010"}, + {"name":"SFGAO_CANDELETE","value":"&H00000020"}, + {"name":"SFGAO_HASPROPSHEET","value":"&H00000040"}, + {"name":"SFGAO_DROPTARGET","value":"&H00000100"}, + {"name":"SFGAO_CAPABILITYMASK","value":"&H00000177"}, + {"name":"SFGAO_PLACEHOLDER","value":"&H00000800"}, + {"name":"SFGAO_SYSTEM","value":"&H00001000"}, + {"name":"SFGAO_ENCRYPTED","value":"&H00002000"}, + {"name":"SFGAO_ISSLOW","value":"&H00004000"}, + {"name":"SFGAO_GHOSTED","value":"&H00008000&"}, + {"name":"SFGAO_LINK","value":"&H00010000"}, + {"name":"SFGAO_SHARE","value":"&H00020000"}, + {"name":"SFGAO_READONLY","value":"&H00040000"}, + {"name":"SFGAO_HIDDEN","value":"&H00080000"}, + {"name":"SFGAO_DISPLAYATTRMASK","value":"&H000FC000"}, + {"name":"SFGAO_FILESYSANCESTOR","value":"&H10000000"}, + {"name":"SFGAO_FOLDER","value":"&H20000000"}, + {"name":"SFGAO_FILESYSTEM","value":"&H40000000"}, + {"name":"SFGAO_HASSUBFOLDER","value":"&H80000000"}, + {"name":"SFGAO_CONTENTSMASK","value":"&H80000000"}, + {"name":"SFGAO_VALIDATE","value":"&H01000000"}, + {"name":"SFGAO_REMOVABLE","value":"&H02000000"}, + {"name":"SFGAO_COMPRESSED","value":"&H04000000"}, + {"name":"SFGAO_BROWSABLE","value":"&H08000000"}, + {"name":"SFGAO_NONENUMERATED","value":"&H00100000"}, + {"name":"SFGAO_NEWCONTENT","value":"&H00200000"}, + {"name":"SFGAO_CANMONIKER","value":"&H00400000"}, + {"name":"SFGAO_HASSTORAGE","value":"&H00400000"}, + {"name":"SFGAO_STREAM","value":"&H00400000"}, + {"name":"SFGAO_STORAGEANCESTOR","value":"&H00800000"}, + {"name":"SFGAO_STORAGECAPMASK","value":"&H70C50008"}, + {"name":"SFGAO_PKEYSFGAOMASK","value":"&H81044000"} + ] + }, + { + "name": "SHFILEINFOW", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type SHFILEINFOW", + "fields": [ + {"name":"hIcon","vis":"public","signature":"hIcon As LongPtr"}, + {"name":"iIcon","vis":"public","signature":"iIcon As Long"}, + {"name":"dwAttributes","vis":"public","signature":"dwAttributes As SFGAOF"}, + {"name":"szDisplayName","vis":"public","signature":"szDisplayName(MAX_PATH - 1) As Integer"}, + {"name":"szTypeName","vis":"public","signature":"szTypeName(79) As Integer"} + ], + "members": [] + }, + { + "name": "SHGFI_flags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum SHGFI_flags", + "values": [ + {"name":"SHGFI_LARGEICON","value":"&H0"}, + {"name":"SHGFI_SMALLICON","value":"&H1"}, + {"name":"SHGFI_OPENICON","value":"&H2"}, + {"name":"SHGFI_SHELLICONSIZE","value":"&H4"}, + {"name":"SHGFI_PIDL","value":"&H8"}, + {"name":"SHGFI_USEFILEATTRIBUTES","value":"&H10"}, + {"name":"SHGFI_ADDOVERLAYS","value":"&H20"}, + {"name":"SHGFI_OVERLAYINDEX","value":"&H40"}, + {"name":"SHGFI_ICON","value":"&H100"}, + {"name":"SHGFI_DISPLAYNAME","value":"&H200"}, + {"name":"SHGFI_TYPENAME","value":"&H400"}, + {"name":"SHGFI_ATTRIBUTES","value":"&H800"}, + {"name":"SHGFI_ICONLOCATION","value":"&H1000"}, + {"name":"SHGFI_EXETYPE","value":"&H2000"}, + {"name":"SHGFI_SYSICONINDEX","value":"&H4000"}, + {"name":"SHGFI_LINKOVERLAY","value":"&H8000&"}, + {"name":"SHGFI_SELECTED","value":"&H10000"}, + {"name":"SHGFI_ATTR_SPECIFIED","value":"&H20000"} + ] + }, + { + "name": "STD_HANDLES", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum STD_HANDLES", + "values": [ + {"name":"STD_INPUT_HANDLE","value":"-10"}, + {"name":"STD_OUTPUT_HANDLE","value":"-11"}, + {"name":"STD_ERROR_HANDLE","value":"-12"} + ] + }, + { + "name": "SYSTEMTIME", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type SYSTEMTIME", + "fields": [ + {"name":"wYear","vis":"public","signature":"wYear As Integer"}, + {"name":"wMonth","vis":"public","signature":"wMonth As Integer"}, + {"name":"wDayOfWeek","vis":"public","signature":"wDayOfWeek As Integer"}, + {"name":"wDay","vis":"public","signature":"wDay As Integer"}, + {"name":"wHour","vis":"public","signature":"wHour As Integer"}, + {"name":"wMinute","vis":"public","signature":"wMinute As Integer"}, + {"name":"wSecond","vis":"public","signature":"wSecond As Integer"}, + {"name":"wMilliseconds","vis":"public","signature":"wMilliseconds As Integer"} + ], + "members": [] + }, + { + "name": "StandardAccessTypes", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum StandardAccessTypes", + "values": [ + {"name":"DELETE","value":"(&H00010000)"}, + {"name":"READ_CONTROL","value":"(&H00020000)"}, + {"name":"WRITE_DAC","value":"(&H00040000)"}, + {"name":"WRITE_OWNER","value":"(&H00080000)"}, + {"name":"SYNCHRONIZE","value":"(&H00100000)"}, + {"name":"STANDARD_RIGHTS_REQUIRED","value":"(&H000F0000)"}, + {"name":"STANDARD_RIGHTS_READ","value":"(READ_CONTROL)"}, + {"name":"STANDARD_RIGHTS_WRITE","value":"(READ_CONTROL)"}, + {"name":"STANDARD_RIGHTS_EXECUTE","value":"(READ_CONTROL)"}, + {"name":"STANDARD_RIGHTS_ALL","value":"(&H001F0000)"}, + {"name":"SPECIFIC_RIGHTS_ALL","value":"(&H0000FFFF&)"}, + {"name":"ACCESS_SYSTEM_SECURITY","value":"(&H01000000)"}, + {"name":"MAXIMUM_ALLOWED","value":"(&H02000000)"} + ] + }, + { + "name": "UUID", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type UUID", + "fields": [ + {"name":"Data1","vis":"public","signature":"Data1 As Long"}, + {"name":"Data2","vis":"public","signature":"Data2 As Integer"}, + {"name":"Data3","vis":"public","signature":"Data3 As Integer"}, + {"name":"Data4","vis":"public","signature":"Data4(0 To 7) As Byte"} + ], + "members": [] + }, + { + "name": "VS_FIXEDFILEINFO", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type VS_FIXEDFILEINFO", + "fields": [ + {"name":"dwSignature","vis":"public","signature":"dwSignature As Long"}, + {"name":"dwStrucVersionl","vis":"public","signature":"dwStrucVersionl As Integer"}, + {"name":"dwStrucVersionh","vis":"public","signature":"dwStrucVersionh As Integer"}, + {"name":"dwFileVersionMSl","vis":"public","signature":"dwFileVersionMSl As Integer"}, + {"name":"dwFileVersionMSh","vis":"public","signature":"dwFileVersionMSh As Integer"}, + {"name":"dwFileVersionLSl","vis":"public","signature":"dwFileVersionLSl As Integer"}, + {"name":"dwFileVersionLSh","vis":"public","signature":"dwFileVersionLSh As Integer"}, + {"name":"dwProductVersionMSl","vis":"public","signature":"dwProductVersionMSl As Integer"}, + {"name":"dwProductVersionMSh","vis":"public","signature":"dwProductVersionMSh As Integer"}, + {"name":"dwProductVersionLSl","vis":"public","signature":"dwProductVersionLSl As Integer"}, + {"name":"dwProductVersionLSh","vis":"public","signature":"dwProductVersionLSh As Integer"}, + {"name":"dwFileFlagsMask","vis":"public","signature":"dwFileFlagsMask As VersionFileFlags"}, + {"name":"dwFileFlags","vis":"public","signature":"dwFileFlags As VersionFileFlags"}, + {"name":"dwFileOS","vis":"public","signature":"dwFileOS As VersionOSFlags"}, + {"name":"dwFileType","vis":"public","signature":"dwFileType As VersionFileTypes"}, + {"name":"dwFileSubtype","vis":"public","signature":"dwFileSubtype As VersionFileSubTypes"}, + {"name":"dwFileDateMS","vis":"public","signature":"dwFileDateMS As Long"}, + {"name":"dwFileDateLS","vis":"public","signature":"dwFileDateLS As Long"} + ], + "members": [] + }, + { + "name": "VS_FIXEDFILEINFO_P", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type VS_FIXEDFILEINFO_P", + "fields": [ + {"name":"dwSignature","vis":"public","signature":"dwSignature As Long"}, + {"name":"dwStrucVersion","vis":"public","signature":"dwStrucVersion As Long"}, + {"name":"dwFileVersionMS","vis":"public","signature":"dwFileVersionMS As Long"}, + {"name":"dwFileVersionLS","vis":"public","signature":"dwFileVersionLS As Long"}, + {"name":"dwProductVersionMS","vis":"public","signature":"dwProductVersionMS As Long"}, + {"name":"dwProductVersionLS","vis":"public","signature":"dwProductVersionLS As Long"}, + {"name":"dwFileFlagsMask","vis":"public","signature":"dwFileFlagsMask As VersionFileFlags"}, + {"name":"dwFileFlags","vis":"public","signature":"dwFileFlags As VersionFileFlags"}, + {"name":"dwFileOS","vis":"public","signature":"dwFileOS As VersionOSFlags"}, + {"name":"dwFileType","vis":"public","signature":"dwFileType As VersionFileTypes"}, + {"name":"dwFileSubtype","vis":"public","signature":"dwFileSubtype As VersionFileSubTypes"}, + {"name":"dwFileDateMS","vis":"public","signature":"dwFileDateMS As Long"}, + {"name":"dwFileDateLS","vis":"public","signature":"dwFileDateLS As Long"} + ], + "members": [] + }, + { + "name": "VersionFileFlags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum VersionFileFlags", + "values": [ + {"name":"VS_FF_DEBUG","value":"&H00000001"}, + {"name":"VS_FF_PRERELEASE","value":"&H00000002"}, + {"name":"VS_FF_PATCHED","value":"&H00000004"}, + {"name":"VS_FF_PRIVATEBUILD","value":"&H00000008"}, + {"name":"VS_FF_INFOINFERRED","value":"&H00000010"}, + {"name":"VS_FF_SPECIALBUILD","value":"&H00000020"} + ] + }, + { + "name": "VersionFileSubTypes", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum VersionFileSubTypes", + "values": [ + {"name":"VFT2_UNKNOWN","value":"&H00000000"}, + {"name":"VFT2_DRV_PRINTER","value":"&H00000001"}, + {"name":"VFT2_DRV_KEYBOARD","value":"&H00000002"}, + {"name":"VFT2_DRV_LANGUAGE","value":"&H00000003"}, + {"name":"VFT2_DRV_DISPLAY","value":"&H00000004"}, + {"name":"VFT2_DRV_MOUSE","value":"&H00000005"}, + {"name":"VFT2_DRV_NETWORK","value":"&H00000006"}, + {"name":"VFT2_DRV_SYSTEM","value":"&H00000007"}, + {"name":"VFT2_DRV_INSTALLABLE","value":"&H00000008"}, + {"name":"VFT2_DRV_SOUND","value":"&H00000009"}, + {"name":"VFT2_DRV_COMM","value":"&H0000000A"}, + {"name":"VFT2_DRV_INPUTMETHOD","value":"&H0000000B"}, + {"name":"VFT2_DRV_VERSIONED_PRINTER","value":"&H0000000C"}, + {"name":"VFT2_FONT_RASTER","value":"&H00000001"}, + {"name":"VFT2_FONT_VECTOR","value":"&H00000002"}, + {"name":"VFT2_FONT_TRUETYPE","value":"&H00000003"} + ] + }, + { + "name": "VersionFileTypes", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum VersionFileTypes", + "values": [ + {"name":"VFT_UNKNOWN","value":"&H00000000"}, + {"name":"VFT_APP","value":"&H00000001"}, + {"name":"VFT_DLL","value":"&H00000002"}, + {"name":"VFT_DRV","value":"&H00000003"}, + {"name":"VFT_FONT","value":"&H00000004"}, + {"name":"VFT_VXD","value":"&H00000005"}, + {"name":"VFT_STATIC_LIB","value":"&H00000007"} + ] + }, + { + "name": "VersionOSFlags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum VersionOSFlags", + "values": [ + {"name":"VOS_UNKNOWN","value":"&H00000000"}, + {"name":"VOS_DOS","value":"&H00010000"}, + {"name":"VOS_OS216","value":"&H00020000"}, + {"name":"VOS_OS232","value":"&H00030000"}, + {"name":"VOS_NT","value":"&H00040000"}, + {"name":"VOS_WINCE","value":"&H00050000"}, + {"name":"VOS__BASE","value":"&H00000000"}, + {"name":"VOS__WINDOWS16","value":"&H00000001"}, + {"name":"VOS__PM16","value":"&H00000002"}, + {"name":"VOS__PM32","value":"&H00000003"}, + {"name":"VOS__WINDOWS32","value":"&H00000004"}, + {"name":"VOS_DOS_WINDOWS16","value":"&H00010001"}, + {"name":"VOS_DOS_WINDOWS32","value":"&H00010004"}, + {"name":"VOS_OS216_PM16","value":"&H00020002"}, + {"name":"VOS_OS232_PM32","value":"&H00030003"}, + {"name":"VOS_NT_WINDOWS32","value":"&H00040004"} + ] + }, + { + "name": "WCMB_Flags", + "kind": "enum", + "in": "WinAPI", + "vis": "public", + "file": "WinAPI.twin", + "signature": "Public Enum WCMB_Flags", + "values": [ + {"name":"WC_COMPOSITECHECK","value":"&H00000200"}, + {"name":"WC_DISCARDNS","value":"&H00000010"}, + {"name":"WC_SEPCHARS","value":"&H00000020"}, + {"name":"WC_DEFAULTCHAR","value":"&H00000040"}, + {"name":"WC_ERR_INVALID_CHARS","value":"&H00000080"}, + {"name":"WC_NO_BEST_FIT_CHARS","value":"&H00000400"} + ] + }, + { + "name": "WIN32_FILE_ATTRIBUTE_DATA", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type WIN32_FILE_ATTRIBUTE_DATA", + "fields": [ + {"name":"dwFileAttributes","vis":"public","signature":"dwFileAttributes As FILE_ATTRIBUTES"}, + {"name":"ftCreationTime","vis":"public","signature":"ftCreationTime As FILETIME"}, + {"name":"ftLastAccessTime","vis":"public","signature":"ftLastAccessTime As FILETIME"}, + {"name":"ftLastWriteTime","vis":"public","signature":"ftLastWriteTime As FILETIME"}, + {"name":"nFileSizeHigh","vis":"public","signature":"nFileSizeHigh As Long"}, + {"name":"nFileSizeLow","vis":"public","signature":"nFileSizeLow As Long"} + ], + "members": [] + }, + { + "name": "WIN32_FIND_DATAW", + "kind": "type", + "in": "WinAPI", + "vis": "public", + "public": false, + "file": "WinAPI.twin", + "signature": "Public Type WIN32_FIND_DATAW", + "fields": [ + {"name":"dwFileAttributes","vis":"public","signature":"dwFileAttributes As FILE_ATTRIBUTES"}, + {"name":"ftCreationTime","vis":"public","signature":"ftCreationTime As FILETIME"}, + {"name":"ftLastAccessTime","vis":"public","signature":"ftLastAccessTime As FILETIME"}, + {"name":"ftLastWriteTime","vis":"public","signature":"ftLastWriteTime As FILETIME"}, + {"name":"nFileSizeHigh","vis":"public","signature":"nFileSizeHigh As Long"}, + {"name":"nFileSizeLow","vis":"public","signature":"nFileSizeLow As Long"}, + {"name":"dwReserved0","vis":"public","signature":"dwReserved0 As Long"}, + {"name":"dwReserved1","vis":"public","signature":"dwReserved1 As Long"}, + {"name":"cFileName","vis":"public","signature":"cFileName(0 To (MAX_PATH - 1)) As Integer"}, + {"name":"cAlternateFileName","vis":"public","signature":"cAlternateFileName(0 To 13) As Integer"} + ], + "members": [] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-tbFileSysTools/Settings b/test/example-projects/contributed-tbFileSysTools/Settings new file mode 100644 index 000000000..be608964d --- /dev/null +++ b/test/example-projects/contributed-tbFileSysTools/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{1901ECFE-40F4-46F8-8160-B9AD2E5E306F}", + "licence": "MIT", + "name": "[IMPORTED] File System Tools - Modern Replacement for FSO", + "path32": "/Packages/tbFileSysTools", + "path64": "/Packages/tbFileSysTools", + "symbolId": "tbFileSysTools", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "1", + "versionRevision": "1" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From bd23e80c341972d7fda918add47fbebdfba037e1 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 16:26:31 +0200 Subject: [PATCH 12/15] Four twinBASIC defects queued with reproducers, found documenting contributed packages: a Private Sub New does not make construction private; ReDim with bounds in the wrong order raises -2147467259 (VB6: 9); a variable typed as an ActiveX control's class cannot hold it; a Script Control reference crashes a 64-bit compile. Their callouts on ReDim, Classes and Modules, tsPwGen and TBMANLIB --- BUGS-TO-REPORT.md | 235 ++++++++++++++++++ .../activex-control-class-variable.twinproj | Bin 0 -> 8308 bytes .../activex-control-class-variable/repro.json | 25 ++ .../src/Settings | 111 +++++++++ .../src/Sources/Startup.twin | 119 +++++++++ .../vb6/Holder.cls | 16 ++ .../vb6/Module1.bas | 121 +++++++++ .../vb6/Probe.vbp | 14 ++ .../private-constructor-callable.twinproj | Bin 0 -> 6792 bytes bugs/private-constructor-callable/repro.json | 7 + .../src/Packages/CtorLib/Settings | 35 +++ .../src/Packages/CtorLib/Sources/Locked.twin | 12 + .../private-constructor-callable/src/Settings | 74 ++++++ .../src/Sources/Classes.twin | 65 +++++ .../src/Sources/Startup.twin | 42 ++++ ...dim-empty-range-unspecified-error.twinproj | Bin 0 -> 4663 bytes .../repro.json | 33 +++ .../src/Settings | 61 +++++ .../src/Sources/Startup.twin | 69 +++++ .../vb6/Module1.bas | 78 ++++++ .../vb6/Probe.vbp | 7 + .../repro.json | 7 + ...ipt-control-reference-win64-crash.twinproj | Bin 0 -> 2772 bytes .../src/Settings | 71 ++++++ .../src/Sources/Startup.twin | 10 + docs/Features/Advanced/Classes-and-Modules.md | 3 + .../Contributed/TBMANLIB/ToolsJScript.md | 2 +- docs/Reference/Contributed/TBMANLIB/index.md | 2 +- .../Contributed/tsPwGen/PwGenOptions.md | 2 +- docs/Reference/Core/ReDim.md | 3 + 30 files changed, 1221 insertions(+), 3 deletions(-) create mode 100644 bugs/activex-control-class-variable/activex-control-class-variable.twinproj create mode 100644 bugs/activex-control-class-variable/repro.json create mode 100644 bugs/activex-control-class-variable/src/Settings create mode 100644 bugs/activex-control-class-variable/src/Sources/Startup.twin create mode 100644 bugs/activex-control-class-variable/vb6/Holder.cls create mode 100644 bugs/activex-control-class-variable/vb6/Module1.bas create mode 100644 bugs/activex-control-class-variable/vb6/Probe.vbp create mode 100644 bugs/private-constructor-callable/private-constructor-callable.twinproj create mode 100644 bugs/private-constructor-callable/repro.json create mode 100644 bugs/private-constructor-callable/src/Packages/CtorLib/Settings create mode 100644 bugs/private-constructor-callable/src/Packages/CtorLib/Sources/Locked.twin create mode 100644 bugs/private-constructor-callable/src/Settings create mode 100644 bugs/private-constructor-callable/src/Sources/Classes.twin create mode 100644 bugs/private-constructor-callable/src/Sources/Startup.twin create mode 100644 bugs/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj create mode 100644 bugs/redim-empty-range-unspecified-error/repro.json create mode 100644 bugs/redim-empty-range-unspecified-error/src/Settings create mode 100644 bugs/redim-empty-range-unspecified-error/src/Sources/Startup.twin create mode 100644 bugs/redim-empty-range-unspecified-error/vb6/Module1.bas create mode 100644 bugs/redim-empty-range-unspecified-error/vb6/Probe.vbp create mode 100644 bugs/script-control-reference-win64-crash/repro.json create mode 100644 bugs/script-control-reference-win64-crash/script-control-reference-win64-crash.twinproj create mode 100644 bugs/script-control-reference-win64-crash/src/Settings create mode 100644 bugs/script-control-reference-win64-crash/src/Sources/Startup.twin diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index 67fced789..584230c04 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -3691,3 +3691,238 @@ Severity: high. The date is wrong and nothing signals it. A program that steps b +--- + +## `ReDim` with an upper bound below the lower bound raises *Unspecified error* (-2147467259), where VB6 raises error 9, *Subscript out of range* + +**Describe the bug** +`ReDim` of a dynamic array with an upper bound below its lower bound, such as `ReDim a(0 To -1)`, `ReDim a(1 To 0)` or `ReDim a(5 To 2)`, raises error -2147467259, *Unspecified error* (E_FAIL). VB6 raises error 9, *Subscript out of range*, for each of them. The error is the same for every element type and for `ReDim Preserve`, and a failed `ReDim Preserve` leaves the array as it was. A program that handles error 9, or tests `Err.Number = 9`, around a `ReDim` does not recognise this one. Observed in the IDE, with the error of each statement written to the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `redim-empty-range-unspecified-error.twinproj` (attached as `redim-empty-range-unspecified-error.zip`). Its one source file, `Startup.twin`, tries 24 `ReDim` statements, each followed by a line that writes the error; the first three are enough: + ``` + Module Startup + + Public Sub Main() + Dim a() As Long + On Error Resume Next + ReDim a(0 To -1) + Debug.Print Err.Number & vbTab & Err.Description + Err.Clear + ReDim a(5 To 2) + Debug.Print Err.Number & vbTab & Err.Description + Err.Clear + ReDim a(0 To 0) + Debug.Print Err.Number & vbTab & Err.Description + End Sub + + End Module + ``` +2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. +3. See `-2147467259 Unspecified error` twice, then `0`. VB6 (the same statements, attached as `redim-empty-range-unspecified-error-vb6.zip`, which writes each result to `out.txt`) gives `9 Subscript out of range` twice, then `0`. + +What decides it, each tried in the project above: +- Every range whose upper bound is below its lower bound raises the error: `(0 To -1)`, `(1 To 0)`, `(5 To 2)`, `(-3 To -5)`; `(-1)`, which is `0 To -1`, and `(n)` with `n = -1`; two dimensions, `(0, 0 To -1)` and `(1 To 0, 0 To 1)`. VB6 raises error 9 for each. +- The element type makes no difference: **Long**, **String**, a user-defined type, **Object**, **Variant** (an array of Variant, and a **Variant** variable, empty or holding an array), and `ReDim s(5 To 2) As String`. +- `ReDim Preserve` raises it too, for `(0 To -1)` of a **Long** array, a **String** array and a **Variant** variable that holds an array, and for `(5 To 2)` of a **Long** array. After the failed `ReDim Preserve a(0 To -1)`, `UBound(a)` is still 2, in both. +- A range of one element or more is not affected: `(0 To 0)`, `(2 To 2)`, `(1 To 2)` and `(0)` raise nothing, in both. +- A 64-bit program, and an exe built from the project, give the same errors. +- With LLVM (the project compiled with `+llvm`) the results differ, and differ from VB6: a range that ends one below its start (`0 To -1`, `1 To 0`, `(-1)`, `(n)`, `(0, 0 To -1)`, `(1 To 0, 0 To 1)`, and the same for **String**, a user-defined type, **Object** and **Variant**) makes an empty array and raises nothing, so `UBound(a)` is `-1`; a range further below (`5 To 2`, `-3 To -5`) raises -2147024882, *Application-defined or object-defined error*; and `ReDim Preserve a(5 To 2)` raises error 9, as VB6 does. + +**Expected behavior** +Error 9, *Subscript out of range*, for each of these statements, as in VB6, whether the code is compiled with LLVM or not. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997, 995 and 957 (not tried with LLVM there). Not tried: the run-time error panel for an unhandled error, and the exe built with LLVM. + +Severity: low. An error is raised either way, and an unhandled one stops the program either way; only its number and text differ, so code that tests for error 9 around a `ReDim` (a size computed from data that can be zero or negative) does not recognise it. With LLVM the statement can make an empty array where VB6 raises an error, which a program written for VB6 does not expect. + + + +--- + +## A project that references the Microsoft Script Control crashes the compiler when it is built for 64-bit programs + +**Describe the bug** +A project with a reference to *Microsoft Script Control 1.0* (`msscript.ocx`) crashes the compiler when its target is win64, although no code uses the reference. The IDE restarts the compiler on the same project (`restarting from MEMORY [...]` in the DEBUG CONSOLE, four times), it crashes again each time, and the IDE ends in Safe Mode, so nothing in the project compiles. With the target win32 the same project compiles with no diagnostic. A reference to the Script Control in an embedded package does the same to every project that embeds the package: the contributed package TBMANLIB 1.0.0.5, which references it, takes down a project of one `Debug.Print`. Observed in the IDE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `script-control-reference-win64-crash.twinproj` (attached as `script-control-reference-win64-crash.zip`). Its code is one module that prints `hello`; the project's references, in `Settings`, include + ``` + { + "id": "{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}", + "lcid": 0, + "name": "Microsoft Script Control 1.0", + "path32": "C:\\Windows\\SysWOW64\\msscript.ocx", + "path64": "", + "symbolId": "MSScriptControl", + "versionMajor": 1, + "versionMinor": 0 + } + ``` + (the reference as TBMANLIB 1.0.0.5 holds it). +2. Choose **win64** in the target list of the toolbar, or let the IDE compile it for win64 in any way (`tbbuild --arch win64`). +3. See the DEBUG CONSOLE: `restarting from MEMORY []` four times, then the IDE in Safe Mode. With **win32** chosen the project compiles, 0 errors and 0 warnings. + +Each of these was a project of its own, one `Debug.Print "hello"` and the references of the project above, compiled for win64: + +| the project's extra reference | result | +|---|---| +| none | compiles, and builds | +| Script Control, `path64` empty (as above) | the compiler crashes | +| Script Control, `path64` the 32-bit file `C:\Windows\SysWOW64\msscript.ocx` | the compiler crashes | +| Script Control, `path64` a file that does not exist | the compiler crashes | +| Script Control in an embedded package of the project (`Packages/ScLib`, `path64` empty), none in the project | the compiler crashes | +| Common Dialog Control 6.0 (`comdlg32.ocx`), also 32-bit only, `path64` empty or the 32-bit file | compiles | +| Winsock Control 6.0 (`mswinsck.ocx`), also 32-bit only, `path64` empty or the 32-bit file | compiles | +| Microsoft Scripting Runtime, or ADODB 2.8, with `path64` empty | compiles, and builds | +| Microsoft Scripting Runtime with `path64` empty, in an embedded package | compiles, and builds | + +An empty `path64` is not the cause, then: the Script Control crashes the compiler whatever `path64` holds, and two other 32-bit-only controls do not. + +**Expected behavior** +A compiled project, or a diagnostic that names the reference and says that it has no 64-bit component, and a compiler that keeps running. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The compiler crashes on BETA 1005, 997, 995 and 987, and does not on 983, 975 and 957. Not tried: BETA 964, 973, 974 and 982, the builds between 983 and 987 (not installed), a `path64` that names a 64-bit build of the control (there is none on this machine), the project built with LLVM, and the exception text (the DEBUG CONSOLE of the crashing IDE holds the `restarting from MEMORY` lines only). + +Severity: high for a project that has the reference. It cannot be compiled for 64-bit programs at all, the IDE ends in Safe Mode, and nothing says which reference is the cause. No wrong code is produced. In a 32-bit program the reference is harmless. A 64-bit program cannot use the control, which exists only as a 32-bit component, so the workaround is to remove the reference. + + + +--- + +## A variable declared as the class of an ActiveX control cannot hold the control: `Set` raises *No such interface supported*, and every member of a `Dim ... As New` variable raises error 91 + +**Describe the bug** +An object variable whose declared type is the class of an ActiveX control, such as `MSScriptControl.ScriptControl`, never receives the object. `Set sc = New MSScriptControl.ScriptControl` and `Set sc = CreateObject("MSScriptControl.ScriptControl")` raise error -2147467262, *No such interface supported* (E_NOINTERFACE). `Dim sc As New MSScriptControl.ScriptControl` raises nothing at the declaration, `TypeName(sc)` is `ScriptControl` and `sc Is Nothing` is False, and then the first member that is used raises error 91, *Object variable or With block variable not set*. The control itself is created and has its interface: the same `New` and `CreateObject` give a working object when the variable is declared `As Object` or `As MSScriptControl.IScriptControl`, the control's default interface. The same happens with the Winsock and Common Dialog controls. The classes of libraries that are not controls (`Scripting.Dictionary`, `ADODB.Connection`) are not affected. VB6 creates every one of these. Observed in the IDE, with the error of each statement written to the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `activex-control-class-variable.twinproj` (attached as `activex-control-class-variable.zip`). It references Microsoft Script Control 1.0, Microsoft Winsock Control 6.0, Microsoft Common Dialog Control 6.0, Microsoft Scripting Runtime and ADO 2.8 (all 32-bit, so the project's target stays win32), and its module `Startup` runs 21 statements with a line after each that writes the error. Four of them: + ``` + Dim sc As New MSScriptControl.ScriptControl + sc.Language = "JScript" ' error 91 + Dim sc2 As MSScriptControl.ScriptControl + Set sc2 = New MSScriptControl.ScriptControl ' -2147467262, No such interface supported + Dim o As Object + Set o = New MSScriptControl.ScriptControl ' no error + Dim i As MSScriptControl.IScriptControl + Set i = New MSScriptControl.ScriptControl ' no error, and i.Eval("1 + 2") is 3 + ``` +2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. +3. See, for the statements of the project (each is one line in the console, with its error number and text): + + | statement | twinBASIC | VB6 | + |---|---|---| + | `Dim sc As New ScriptControl`, then `sc.Language = "JScript"` or `sc.Eval("1 + 2")` | 91 | no error, `Eval` is 3 | + | `Private ModuleSc As New ScriptControl` at module level, then `ModuleSc.Language = ...` | 91 | no error | + | `Public Sc As New ScriptControl` as a class field, then `h.Sc.Language = ...` | 91 | no error | + | `Set sc = New ScriptControl`, `sc` declared `As ScriptControl` | -2147467262, No such interface supported | no error | + | `Set sc = CreateObject("MSScriptControl.ScriptControl")`, `sc` declared `As ScriptControl` | -2147467262 | no error | + | `Set sc = o`, `o` an **Object** holding the control, `sc` declared `As ScriptControl` | -2147467262 | no error | + | `Set o = New ScriptControl`, and `Set o = CreateObject(...)`, `o` declared `As Object` | no error, `o.Eval("1 + 2")` is 3 | the same | + | `Set i = New ScriptControl`, `i` declared `As IScriptControl` | no error, `i.Eval("1 + 2")` is 3 | the same | + | `Set w = New MSWinsockLib.Winsock`, `w` declared `As Winsock`; the same with `MSComDlg.CommonDialog` | -2147467262 | no error | + | `Dim wa As New MSWinsockLib.Winsock`, then `wa.LocalPort` | 91 | no error | + | the same two classes, the variable declared `As Object` | no error | no error | + | `Dim d As New Scripting.Dictionary`, `Dim c As New ADODB.Connection`, then a member | no error | no error | + + VB6 is the same statements (attached as `activex-control-class-variable-vb6.zip`, which writes each result to `out.txt`). + +What decides it, each tried in the project above: +- The variable's declared type decides: a class that is an ActiveX control (the coclass has the `control` flag in its type library), as a variable's type, fails; `Object`, the default interface and a class that is not a control work. `Dim x As MSScriptControl.DScriptControlSource`, the events interface the class lists beside its default one, fails with the same error on `Set x = o`. +- `New` and `CreateObject` make no difference: `Set o = New ...` and `Set o = CreateObject(...)` both work for an `Object`. +- `Dim ... As New` hides the failure until a member is used; `Set` reports it at the statement. `TypeName` and `Is Nothing` do not show it. +- A built exe gives the same results as a run in the IDE (the statements written with `TbRun.Out` in a scratch copy). +- `Dim j As New MSScriptControl.IScriptControl` is refused with TB5121, *Unable to create instance of type 'IScriptControl'*, so the interface type is usable with `Set` only. + +**Expected behavior** +A variable declared as the class of an ActiveX control holds the control, as in VB6: `Set sc = New MSScriptControl.ScriptControl` and `Dim sc As New MSScriptControl.ScriptControl` give a usable object, and `sc.Eval("1 + 2")` is 3. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997, 995 and 957. The type libraries of the failing classes list two implemented interfaces flagged `default`: the dual interface (`IScriptControl`) and, flagged `default` and `source`, the events dispinterface (`DScriptControlSource`). `ADODB.Connection` lists the same pair and works, so the pair alone does not explain it; the three classes that fail are flagged `control` in their type libraries, and the two that work are not. + +Not tried: a control that is registered for 64-bit, and a form that hosts the control (a variable of the control's class on a form is the usual use). + +Severity: medium. A visible error is raised, never a wrong value, but it is raised at the first use with a message that points at an object that was never set, and a VB6 program that holds an ActiveX control's class in a typed variable, or declares one `As New`, must be changed to `As Object` or to the interface. The contributed package TBMANLIB 1.0.0.5 does this for the Script Control (`Dim MSSC As New MSScriptControl.ScriptControl`), and both functions of its **ToolsJScript** module fail with error 91 for that reason. + + + +--- + +## `Private Sub New` does not make construction private: `New` from another module, or from a project that references the class's package, compiles and runs the constructor + +**Describe the bug** +A class whose constructor is declared `Private Sub New()` can be created with `New` by code outside the class: another module of the same project, another class, and a project that references the package that holds the class. There is no diagnostic, and the private constructor runs. Other private members are enforced: a `Private Sub` of the same class is refused from another module with TB5027, *Unrecognized member*, in a project and in a package, so the constructor is the one private member that the compiler lets anybody call. A class that wants its objects created only by its own code, such as a factory method that sets the object up after `New`, cannot say so. Observed in the IDE's diagnostics, and in a run. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `private-constructor-callable.twinproj` (attached as `private-constructor-callable.zip`). It embeds a package, `CtorLib` (in `Packages/CtorLib`), with one class: + ``` + [COMCreatable(False)] + Public Class Locked + + Public V As Long + + Private Sub New() + V = 5 + End Sub + + End Class + ``` + The project has the same in a class of its own, `ZPriv`, in `Classes.twin`, and its module `Startup` creates both: + ``` + Module Startup + + Public Sub Main() + Dim a As ZPriv = New ZPriv + Debug.Print a.V + Dim j As Locked = New Locked + Debug.Print j.V + End Sub + + End Module + ``` +2. Let the IDE compile it (it does so on opening): 0 errors, 0 warnings. +3. Run the project in the IDE (F5): the DEBUG CONSOLE shows `5` and `5`, the value the private constructor sets. + +What decides it, each tried in the project above (`Classes.twin` and `Startup.twin` hold all of them, 11 lines of output): +- Every way of creating the object compiles and runs the constructor: `Dim a As ZPriv = New ZPriv`, `Dim b As New ZPriv`, `Set c = New ZPriv`. +- A constructor with parameters is the same: `Private Sub New(ByVal x As Long)` and `New ZPrivArgs(3)`. +- `Private Class ZPrivateClass` with `Private Sub New()` is created with `New` from another module too. +- `New` from another class of the project (`Set Make = New ZPriv` in a method of `ZCaller`) compiles. +- From the package: `Dim j As Locked = New Locked` and `Dim k As New Locked` compile, and the constructor runs. +- `Friend Sub New` also compiles (`New ZFriend`), and so does `New` of a package class whose constructor is `Friend Sub New`; and a `Friend Sub` that is an ordinary method of a package class can be called from the project. Whether `Friend` should stop that is not part of this entry. +- Not affected: `New` inside the class itself, a factory method on the predeclared object (`ZFactory.Create`, which does `Set Create = New ZFactory`), which has to keep working; and `Public Sub New`, the control. + +**Expected behavior** +A diagnostic for `New ZPriv`, `Dim ... As New ZPriv` and `New Locked` outside the class, as for a private member that is used from outside; `New` inside the class compiles and runs as it does now. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same on BETA 997, 995 and 957. + +Not tried: a `Private Sub New` in a class of a package built from the project's own `Packages` folder compared with one installed from a `.twinpack` file, and `New` through `CreateObject` (the class is `[COMCreatable(False)]` here). + +Severity: low. The compiler accepts code that the class's author meant to forbid, and nothing else goes wrong. For a class that relies on it, such as a settings object that is meant to be created only from presets, an object made with `New` is in the state that the constructor leaves, which the class never intended anybody to use. + + + diff --git a/bugs/activex-control-class-variable/activex-control-class-variable.twinproj b/bugs/activex-control-class-variable/activex-control-class-variable.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..441ed9d5ee6082d9ffccfe1e1bbff29d82f50bc2 GIT binary patch literal 8308 zcmcgxNpIuG6?RWnJMkfhIVFdJ(3rLnQ=lkW8_)+X(!_~3v>f*UtxiCZWtr0yOJI>+ zVEE_c$0ffa?^SUrYO~cdi4K%l%Uizp)vKzfKmF@>|9P|X$DN%W(@TOo`e3stiCJie zF6Y;79JrH^LT~x`HT?W$=UaH5%;qdksDJf1r>DUrhS8Ll19x_Qg)iUi?8Eac;2sS_ zH=-=9j6rcQ=C|;?biFU`v?}5^vU?Z4K2Xlm*sJz`Bl~gKXu0pjhVU#3nZ&&Ru~8*mEH)I1)oxM3-@rzQ5zpbK|C=Ft&8^ z;AG^*!92+bb|{TNeFeziDZm`&RFa-TCYc)_zCd2m-AzD9PFH~Y7R);m{l&TnxQN4f-icHV5-v5rBQuH? zaw&u4#9W5M-W0#AXn=kx)ngP`CC7XyE7SmhKsDJgNTN(*@A0uHbk z1FRJIP+yEaBKbv%B?);?Fx8#I!aCrd;sHjO)T)+N2-mMn1UWT`q$0XB={*6_G5)2O(n!ncPtn+QE(q6n-zTe5J5Os#bvOi%qx?S?N<^h�yWXBXzzBKEg zw$Xx3Os?orY^aKVL4NIU!9w=6z$jQtV{%CqI~TMGJeU%jg&bFztN6&)@ew(F+JN5$jP1EgJmv|Eg8^eExA-mS)poaeTehRtFCu4Uh0B@lknFqmlf>8MZl4i@&Z

?$6F^mjMQ;399a>ySOy|O}U~xnd+|DcaFu!j4KB>92Bhn}*BRzc> zpQ<;DGOim<=)+8qd6WwPQFxC~E-#*9iS!W1JSYU<(6BtJw68Q}t&4q9x?3s%L$YMI z5d8dV4maU5H;B}XC$55u?(FDUX%XqNY^h|=vRUDXY*dWEpYoEhzlQ~%H=@`C>n?!^ zbF~)q%YQ(r>S=#(x8||vCYYwNxE9xg2u>hy)vfj6mM3c)ymo}+<}t&vOx>N&uYx3` zIATK~tvYFv2BClHCbu~2?USru;rsJ=Y+>Jc{PyWmYQRh9^FQA{nXz{pB%m^l>2v+< z(=Z6>;31(A2l73ykdKFXln>R7$aiC(41O3?hxug{0SA7d;FO<2ovr_KSH;wl*w;dRo_Mw=~Pv zJC511yQbqTU>O1aIQP}TWphjigNHfPrGjLYF)_WNF^vFd==}t2+C}Q^?$$CZbzL{~ zR#VsDNA|GuU)ZGUt+uoh=7gS@rK7v>nwxuZIv{46u$dsTfD3x(08tYGs@eVV81^Ke z-ShEy^vFM6d~CPIW1jd>7BT8cI8o=cTdU?kfw5_FL#2#ur)3O1O zu5Nb*9jnvL68!qqSpt$B+6zS5b)6iHF54?s_gY*aXK?S!Hn?P3;gXXHq6m*CmW6qYuk4rqWXnPwo)xfc zk5?<9y%=`y#t~j{V1zh4)x=+N;;bq3?bhHwK5?h{l)VR2ICfDytracN4O3nfk)J+; j$>L~M1J)wZ>+sL>-=n}|KF#kMc_`(1Yk%)qUcmkf`vxCM literal 0 HcmV?d00001 diff --git a/bugs/activex-control-class-variable/repro.json b/bugs/activex-control-class-variable/repro.json new file mode 100644 index 000000000..22ac53eb6 --- /dev/null +++ b/bugs/activex-control-class-variable/repro.json @@ -0,0 +1,25 @@ +{ + "mode": "run", + "expect": { + "exit": 0, + "output": [ + "^Dim sc As New ScriptControl: sc\\.Language = \"JScript\"\\s+91\\s+Object variable or With block variable not set$", + "^Dim sc As New ScriptControl: sc\\.Eval\\(\"1 \\+ 2\"\\) =\\s+91\\s+Object variable or With block variable not set$", + "^Private ModuleSc As New ScriptControl \\(module level\\): ModuleSc\\.Language\\s+91\\s+Object variable or With block variable not set$", + "^Public Sc As New ScriptControl \\(class field\\): h\\.Sc\\.Language\\s+91\\s+Object variable or With block variable not set$", + "^Dim sc As ScriptControl: Set sc = New ScriptControl\\s+-2147467262\\s+No such interface supported$", + "^Dim sc As ScriptControl: Set sc = CreateObject\\(\"MSScriptControl\\.ScriptControl\"\\)\\s+-2147467262\\s+No such interface supported$", + "^Dim o As Object: Set o = New ScriptControl\\s+0\\s*$", + "^Dim sc As ScriptControl: Set sc = o\\s+-2147467262\\s+No such interface supported$", + "^Object variable: o\\.Eval\\(\"1 \\+ 2\"\\) = 3\\s+0\\s*$", + "^IScriptControl variable: i\\.Eval\\(\"1 \\+ 2\"\\) = 3\\s+0\\s*$", + "^Dim w As Winsock: Set w = New Winsock\\s+-2147467262\\s+No such interface supported$", + "^Dim wa As New Winsock: wa\\.LocalPort\\s+91\\s+Object variable or With block variable not set$", + "^Dim wo As Object: Set wo = New Winsock\\s+0\\s*$", + "^Dim cd As CommonDialog: Set cd = New CommonDialog\\s+-2147467262\\s+No such interface supported$", + "^Dim cdo As Object: Set cdo = New CommonDialog\\s+0\\s*$", + "^Dim d As New Scripting\\.Dictionary: d\\.Add, d\\.Count = 1\\s+0\\s*$", + "^Dim c As New ADODB\\.Connection: ConnectionString = x\\s+0\\s*$" + ] + } +} diff --git a/bugs/activex-control-class-variable/src/Settings b/bugs/activex-control-class-variable/src/Settings new file mode 100644 index 000000000..6a8ce28b0 --- /dev/null +++ b/bugs/activex-control-class-variable/src/Settings @@ -0,0 +1,111 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "ActivexControlClassVariable", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A variable declared as the class of an ActiveX control cannot hold the control: Set raises No such interface supported, and Dim As New leaves an unusable object", + "project.exportPathIsV2": true, + "project.id": "{D2EA1E0C-5243-4B29-8D64-BC07DA4C8ADD}", + "project.name": "ActivexControlClassVariable", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}", + "lcid": 0, + "name": "Microsoft Script Control 1.0", + "path32": "C:\\Windows\\SysWOW64\\msscript.ocx", + "path64": "", + "symbolId": "MSScriptControl", + "versionMajor": 1, + "versionMinor": 0 + }, + { + "id": "{248DD890-BB45-11CF-9ABC-0080C7E7B78D}", + "lcid": 0, + "name": "Microsoft Winsock Control 6.0 (SP6)", + "path32": "C:\\Windows\\SysWOW64\\mswinsck.ocx", + "path64": "", + "symbolId": "MSWinsockLib", + "versionMajor": 1, + "versionMinor": 0 + }, + { + "id": "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}", + "lcid": 0, + "name": "Microsoft Common Dialog Control 6.0 (SP6)", + "path32": "C:\\Windows\\SysWOW64\\comdlg32.ocx", + "path64": "", + "symbolId": "MSComDlg", + "versionMajor": 1, + "versionMinor": 2 + }, + { + "id": "{420B2830-E718-11CF-893D-00A0C9054228}", + "lcid": 0, + "name": "Microsoft Scripting Runtime", + "path32": "C:\\Windows\\SysWOW64\\scrrun.dll", + "path64": "C:\\Windows\\System32\\scrrun.dll", + "symbolId": "Scripting", + "versionMajor": 1, + "versionMinor": 0 + }, + { + "id": "{2A75196C-D9EB-4129-B803-931327F72D5C}", + "lcid": 0, + "name": "Microsoft ActiveX Data Objects 2.8 Library", + "path32": "C:\\Program Files (x86)\\Common Files\\System\\ado\\msado28.tlb", + "path64": "C:\\Program Files\\Common Files\\System\\ado\\msado28.tlb", + "symbolId": "ADODB", + "versionMajor": 2, + "versionMinor": 8 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/activex-control-class-variable/src/Sources/Startup.twin b/bugs/activex-control-class-variable/src/Sources/Startup.twin new file mode 100644 index 000000000..040f797bc --- /dev/null +++ b/bugs/activex-control-class-variable/src/Sources/Startup.twin @@ -0,0 +1,119 @@ +' A variable declared as the class of an ActiveX control cannot hold the control: +' Set raises -2147467262, No such interface supported, and Dim As New leaves an object +' that raises error 91 on every use. The same statements work in VB6, and work in +' twinBASIC for a variable declared As Object, for one declared as the control's default +' interface (IScriptControl), and for the classes of libraries that are not controls. +Class Holder + Public Sc As New MSScriptControl.ScriptControl +End Class + +Module Startup + + Private ModuleSc As New MSScriptControl.ScriptControl + + Private Sub Show(ByVal label As String) + Debug.Print label & vbTab & Err.Number & vbTab & Err.Description + Err.Clear + End Sub + + ' Dim As New: the declaration raises nothing, the variable is not Nothing, and its + ' members raise error 91 + Private Sub AsNew() + On Error Resume Next + Dim sc As New MSScriptControl.ScriptControl + Dim t As String + Dim isNothing As Boolean + Dim r As Variant + t = TypeName(sc) + Show "Dim sc As New ScriptControl: TypeName(sc) = " & t + isNothing = (sc Is Nothing) + Show "Dim sc As New ScriptControl: sc Is Nothing = " & isNothing + sc.Language = "JScript" + Show "Dim sc As New ScriptControl: sc.Language = ""JScript""" + r = sc.Eval("1 + 2") + Show "Dim sc As New ScriptControl: sc.Eval(""1 + 2"") = " & r + ModuleSc.Language = "JScript" + Show "Private ModuleSc As New ScriptControl (module level): ModuleSc.Language" + Dim h As New Holder + h.Sc.Language = "JScript" + Show "Public Sc As New ScriptControl (class field): h.Sc.Language" + End Sub + + ' Set: the variable's type is the class + Private Sub SetTyped() + On Error Resume Next + Dim sc As MSScriptControl.ScriptControl + Dim o As Object + Set sc = New MSScriptControl.ScriptControl + Show "Dim sc As ScriptControl: Set sc = New ScriptControl" + Set sc = CreateObject("MSScriptControl.ScriptControl") + Show "Dim sc As ScriptControl: Set sc = CreateObject(""MSScriptControl.ScriptControl"")" + Set o = New MSScriptControl.ScriptControl + Show "Dim o As Object: Set o = New ScriptControl" + Set sc = o + Show "Dim sc As ScriptControl: Set sc = o" + End Sub + + ' the variable's type is not the class + Private Sub SetOther() + On Error Resume Next + Dim o As Object + Dim i As MSScriptControl.IScriptControl + Dim r As Variant + Set o = CreateObject("MSScriptControl.ScriptControl") + Show "Dim o As Object: Set o = CreateObject(""MSScriptControl.ScriptControl"")" + o.Language = "JScript" + r = o.Eval("1 + 2") + Show "Object variable: o.Eval(""1 + 2"") = " & r + Set i = New MSScriptControl.ScriptControl + Show "Dim i As IScriptControl: Set i = New ScriptControl" + i.Language = "JScript" + r = i.Eval("1 + 2") + Show "IScriptControl variable: i.Eval(""1 + 2"") = " & r + End Sub + + ' two more ActiveX controls + Private Sub OtherControls() + On Error Resume Next + Dim w As MSWinsockLib.Winsock + Dim wo As Object + Dim wa As New MSWinsockLib.Winsock + Dim cd As MSComDlg.CommonDialog + Dim cdo As Object + Dim n As Long + Set w = New MSWinsockLib.Winsock + Show "Dim w As Winsock: Set w = New Winsock" + n = wa.LocalPort + Show "Dim wa As New Winsock: wa.LocalPort" + Set wo = New MSWinsockLib.Winsock + Show "Dim wo As Object: Set wo = New Winsock" + Set cd = New MSComDlg.CommonDialog + Show "Dim cd As CommonDialog: Set cd = New CommonDialog" + Set cdo = New MSComDlg.CommonDialog + Show "Dim cdo As Object: Set cdo = New CommonDialog" + End Sub + + ' classes of libraries that are not controls + Private Sub NotControls() + On Error Resume Next + Dim n As Long + Dim s As String + Dim d As New Scripting.Dictionary + Dim c As New ADODB.Connection + d.Add "a", 1 + n = d.Count + Show "Dim d As New Scripting.Dictionary: d.Add, d.Count = " & n + c.ConnectionString = "x" + s = c.ConnectionString + Show "Dim c As New ADODB.Connection: ConnectionString = " & s + End Sub + + Public Sub Main() + AsNew + SetTyped + SetOther + OtherControls + NotControls + End Sub + +End Module diff --git a/bugs/activex-control-class-variable/vb6/Holder.cls b/bugs/activex-control-class-variable/vb6/Holder.cls new file mode 100644 index 000000000..7690a7964 --- /dev/null +++ b/bugs/activex-control-class-variable/vb6/Holder.cls @@ -0,0 +1,16 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Holder" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Sc As New MSScriptControl.ScriptControl diff --git a/bugs/activex-control-class-variable/vb6/Module1.bas b/bugs/activex-control-class-variable/vb6/Module1.bas new file mode 100644 index 000000000..549c0017e --- /dev/null +++ b/bugs/activex-control-class-variable/vb6/Module1.bas @@ -0,0 +1,121 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The VB6 side of the reproducer: the same statements as src/Sources/Startup.twin, +' written to out.txt beside Probe.exe. Build and run it with: +' node scripts/bug_repro.mjs vb6 activex-control-class-variable + +Private ModuleSc As New MSScriptControl.ScriptControl + +Private Sub Show(ByVal label As String) + Print #9, label & vbTab & Err.Number & vbTab & Err.Description + Err.Clear +End Sub + +' Dim As New: the declaration raises nothing, the variable is not Nothing, and its +' members raise error 91 in twinBASIC +Private Sub AsNew() + On Error Resume Next + Dim sc As New MSScriptControl.ScriptControl + Dim t As String + Dim isNothing As Boolean + Dim r As Variant + t = TypeName(sc) + Show "Dim sc As New ScriptControl: TypeName(sc) = " & t + isNothing = (sc Is Nothing) + Show "Dim sc As New ScriptControl: sc Is Nothing = " & isNothing + sc.Language = "JScript" + Show "Dim sc As New ScriptControl: sc.Language = ""JScript""" + r = sc.Eval("1 + 2") + Show "Dim sc As New ScriptControl: sc.Eval(""1 + 2"") = " & r + ModuleSc.Language = "JScript" + Show "Private ModuleSc As New ScriptControl (module level): ModuleSc.Language" + Dim h As New Holder + h.Sc.Language = "JScript" + Show "Public Sc As New ScriptControl (class field): h.Sc.Language" +End Sub + +' Set: the variable's type is the class +Private Sub SetTyped() + On Error Resume Next + Dim sc As MSScriptControl.ScriptControl + Dim o As Object + Set sc = New MSScriptControl.ScriptControl + Show "Dim sc As ScriptControl: Set sc = New ScriptControl" + Set sc = CreateObject("MSScriptControl.ScriptControl") + Show "Dim sc As ScriptControl: Set sc = CreateObject(""MSScriptControl.ScriptControl"")" + Set o = New MSScriptControl.ScriptControl + Show "Dim o As Object: Set o = New ScriptControl" + Set sc = o + Show "Dim sc As ScriptControl: Set sc = o" +End Sub + +' the variable's type is not the class +Private Sub SetOther() + On Error Resume Next + Dim o As Object + Dim i As MSScriptControl.IScriptControl + Dim r As Variant + Set o = CreateObject("MSScriptControl.ScriptControl") + Show "Dim o As Object: Set o = CreateObject(""MSScriptControl.ScriptControl"")" + o.Language = "JScript" + r = o.Eval("1 + 2") + Show "Object variable: o.Eval(""1 + 2"") = " & r + Set i = New MSScriptControl.ScriptControl + Show "Dim i As IScriptControl: Set i = New ScriptControl" + i.Language = "JScript" + r = i.Eval("1 + 2") + Show "IScriptControl variable: i.Eval(""1 + 2"") = " & r +End Sub + +' two more ActiveX controls +Private Sub OtherControls() + On Error Resume Next + Dim w As MSWinsockLib.Winsock + Dim wo As Object + Dim wa As New MSWinsockLib.Winsock + Dim cd As MSComDlg.CommonDialog + Dim cdo As Object + Dim n As Long + Set w = New MSWinsockLib.Winsock + Show "Dim w As Winsock: Set w = New Winsock" + n = wa.LocalPort + Show "Dim wa As New Winsock: wa.LocalPort" + Set wo = New MSWinsockLib.Winsock + Show "Dim wo As Object: Set wo = New Winsock" + Set cd = New MSComDlg.CommonDialog + Show "Dim cd As CommonDialog: Set cd = New CommonDialog" + Set cdo = New MSComDlg.CommonDialog + Show "Dim cdo As Object: Set cdo = New CommonDialog" +End Sub + +' classes of libraries that are not controls +Private Sub NotControls() + On Error Resume Next + Dim n As Long + Dim s As String + Dim d As New Scripting.Dictionary + Dim c As New ADODB.Connection + d.Add "a", 1 + n = d.Count + Show "Dim d As New Scripting.Dictionary: d.Add, d.Count = " & n + c.ConnectionString = "x" + s = c.ConnectionString + Show "Dim c As New ADODB.Connection: ConnectionString = " & s +End Sub + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + AsNew + SetTyped + SetOther + OtherControls + NotControls + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/activex-control-class-variable/vb6/Probe.vbp b/bugs/activex-control-class-variable/vb6/Probe.vbp new file mode 100644 index 000000000..2ba577add --- /dev/null +++ b/bugs/activex-control-class-variable/vb6/Probe.vbp @@ -0,0 +1,14 @@ +Type=Exe +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Reference=*\G{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}#1.0#0#C:\Windows\SysWOW64\msscript.ocx#Microsoft Script Control 1.0 +Reference=*\G{248DD890-BB45-11CF-9ABC-0080C7E7B78D}#1.0#0#C:\Windows\SysWOW64\mswinsck.ocx#Microsoft Winsock Control 6.0 (SP6) +Reference=*\G{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.2#0#C:\Windows\SysWOW64\comdlg32.ocx#Microsoft Common Dialog Control 6.0 (SP6) +Reference=*\G{420B2830-E718-11CF-893D-00A0C9054228}#1.0#0#C:\Windows\SysWOW64\scrrun.dll#Microsoft Scripting Runtime +Reference=*\G{2A75196C-D9EB-4129-B803-931327F72D5C}#2.8#0#C:\Program Files (x86)\Common Files\System\ado\msado28.tlb#Microsoft ActiveX Data Objects 2.8 Library +Module=Module1; Module1.bas +Class=Holder; Holder.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/private-constructor-callable/private-constructor-callable.twinproj b/bugs/private-constructor-callable/private-constructor-callable.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..8cb1cbddac8a8c15d2522aa86ab8f8871f5fe18f GIT binary patch literal 6792 zcmcgwTW=!C6~?ohY+%D?gDDubffNO zY|G-0{``OA-A|8h|HbD>Ze2ke6t zQPcOrC|KK(ADEWwT2q(8Wb*qx3||%g1kd4emQdjUwjF!?b0-{P!1y!gHxu0c*_Id>vR3 z^Svtb7BpZ{SZR|=kIt;M3*%#0CQVbdR;z0kxfB=3^OKpMu9@qMt!RM@UOx-3;rnJX z>EhgE^7?r!?EKbR(#=0!KX(Fq!6GpHI-r~C>*oP;Y5y^zUI;2}w%OzLK=$%@lk0cG zx8siax6De1hIYVKxGP+H0WOk<03Bs1x2I3TF`4-RcXmKwzs}mGL53%Dkk#{vsrQ?m zT3u`O&J4{MoVK)Xt<%(6_5Nw^OgD{g<0Pv#_#ICYQjdn$8J->(0l)7urq9*yKdykD zMX(BBgr>bA59JLD*_62~dO}X)@4M$iGb1E9%dp_KR4jt$i^YoEkFM)=-Dv0<4CKR9 zBmAW6#wp(qkhnG{(m@M&Q_5eQ_ep0R`AdF^P&7=g!0U}V-&=b!dB;4*e+Va&tEceY z#k*5uG6^HccWJ#ExziNMsj-VBqRU2o7fJZEocivNdqyynygtwX&(Fwu?+3uKE|gMV zna98Cpy{Smyh|H3l-K1(FaX+GdRlRie0R<%?rFLQEAa3Z0zb5Vg$98i;M*-qFIE=J zo-aa-zxH}jLX6t~;EQgMTlg0&a1C9SSQ`n|_&-=SBLEY=5}|_y_@p%Ox(J{D5)z|BACIl--N=DcPN1 z9)6zu?$eFsl1CK|am}EUcJQg7v?hl1E@Q?UYjO^g8aq@BK zKvY)t5~Tl=^0it{`wX|i+Oshl5uO^*!z1+W4}-K^jO!4rd4H zbS+V2oJ*o(S0Y<$bGIoVg7d2IOM=%ix(WdcXOs!RCVO*EB2C7RyTCf>X;oJcfPRF$ zkw8besW^*F967R>m(C)44A7MyMBL}L1<}4eI$h7JFz-c>6z(N}6IaEgv_k%hBPE=3 z0HP^M;pZ782^a6=o?X}`)0iua@?7m$_k<(fAo~Sd*;>~ob}S1@3jsYX0N)QA zDU{Bb4EKvwg1ShLN8*HV)Rd%c;>4n1low68Xo=Sf7E%bmWymbFOL^jBo6uMkjPjyp z@h%mhr+2EpCDz`#RSv4DT`ICRnZ>eUl$Sjh+mtB&Z47-?0hxP*?g|R+JZ&s?zAbhk zwk&Rmv~C(8x2!9nNjg;Rlhe1!A{GUs{0?mna43&og*4u&i^5_DfZ@gZD#F{Rcb~!8 zHes{EmvpZ+cVOgpbGKFBsyCHj$ca^zg;1fyx-p~gL zoocsawWg2QAH#0KFucLalGnrO?aPYx%;=vP&3;eoHR?UhXtoTkTQ>}?*E2eLvwqU) z83t6eX-ZYfzSNPsYx6Y6Y|#xfMLcCt>3v?=EokDpTf8c5^M$vV{k+OMp?P`fEogSt z-eM+KB-hc@uygfU)yMdC!{@T58Jy_dvwBb0dS<7q8I3_-Yn?V~TCLVPX@L)oX8la~ z56u~xqUZ}`Lnw7HU^?G>5t=9iVy|vIJnvr;>>zc%>)#P;lK};;SoPWASAJP`irn<`~?F<2z4yzSHYeY7osTz)UvcuZ@TKMv>b1t0ll6x zsBA{$KPx+DS6|W(&@keCu?CbZyIo+`TJ=G<(b7&0v!fZkR#)p9&8F5lInnC_dAf$|=MAN|pzy-*KTlm5~igCG%y@sAa7_`lK`TEnisBQ!^tM znxWYrIw$N7Tuyq9dMZAR)xnM`XdhBySjXtdC_3H;C%zUv6$^)`NWlo*`)e#9Ktef kJ(Qg8>+$zYV^4gEJA3>!#qt&!6apY$+}tadHX`Hs4-aEUK>z>% literal 0 HcmV?d00001 diff --git a/bugs/private-constructor-callable/repro.json b/bugs/private-constructor-callable/repro.json new file mode 100644 index 000000000..0bb7a09f3 --- /dev/null +++ b/bugs/private-constructor-callable/repro.json @@ -0,0 +1,7 @@ +{ + "mode": "compile", + "expect": { + "exit": 0, + "noDiagnostics": true + } +} diff --git a/bugs/private-constructor-callable/src/Packages/CtorLib/Settings b/bugs/private-constructor-callable/src/Packages/CtorLib/Settings new file mode 100644 index 000000000..7aeef0e98 --- /dev/null +++ b/bugs/private-constructor-callable/src/Packages/CtorLib/Settings @@ -0,0 +1,35 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "CtorLib", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Package TWINPACK", + "project.description": "One class with a private constructor, for the reproducer", + "project.id": "{5C0E7A12-3D84-4F69-B1A7-92E6D80C4B35}", + "project.licence": "MIT", + "project.name": "CtorLib", + "project.optionExplicit": true, + "project.packageVisibility": "PUBLIC", + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.versionMajor": 1, + "project.versionMinor": 0, + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/private-constructor-callable/src/Packages/CtorLib/Sources/Locked.twin b/bugs/private-constructor-callable/src/Packages/CtorLib/Sources/Locked.twin new file mode 100644 index 000000000..917162e85 --- /dev/null +++ b/bugs/private-constructor-callable/src/Packages/CtorLib/Sources/Locked.twin @@ -0,0 +1,12 @@ +' A class whose constructor is private. A project that references the package +' can still create one with New. +[COMCreatable(False)] +Public Class Locked + + Public V As Long + + Private Sub New() + V = 5 + End Sub + +End Class diff --git a/bugs/private-constructor-callable/src/Settings b/bugs/private-constructor-callable/src/Settings new file mode 100644 index 000000000..5923cd955 --- /dev/null +++ b/bugs/private-constructor-callable/src/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "PrivateConstructorCallable", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A class whose constructor is Private Sub New can still be created with New from another module and from a project that references its package", + "project.exportPathIsV2": true, + "project.id": "{84E847ED-D32D-4794-B244-DD4A0725AD44}", + "project.name": "PrivateConstructorCallable", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{5C0E7A12-3D84-4F69-B1A7-92E6D80C4B35}", + "licence": "MIT", + "name": "[IMPORTED] CtorLib", + "path32": "/Packages/CtorLib", + "path64": "/Packages/CtorLib", + "symbolId": "CtorLib", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/private-constructor-callable/src/Sources/Classes.twin b/bugs/private-constructor-callable/src/Sources/Classes.twin new file mode 100644 index 000000000..1fc8c3ec7 --- /dev/null +++ b/bugs/private-constructor-callable/src/Sources/Classes.twin @@ -0,0 +1,65 @@ +' Classes whose constructor is not public. Each is created with New from another +' module (Startup.twin), and each compiles. + +Class ZPriv + Public V As Long + + Private Sub New() + V = 5 + End Sub +End Class + +[COMCreatable(False)] +Class ZPrivArgs + Public V As Long + + Private Sub New(ByVal x As Long) + V = x + End Sub +End Class + +Class ZFriend + Public V As Long + + Friend Sub New() + V = 7 + End Sub +End Class + +Private Class ZPrivateClass + Public V As Long + + Private Sub New() + V = 9 + End Sub +End Class + +' New inside the class itself: a factory on the predeclared object +[PredeclaredId] +Class ZFactory + Public V As Long + + Private Sub New() + V = 11 + End Sub + + Public Function Create() As ZFactory + Set Create = New ZFactory + End Function +End Class + +' the control: a public constructor +Class ZPublic + Public V As Long + + Public Sub New() + V = 13 + End Sub +End Class + +' New inside another class of the same project +Class ZCaller + Public Function Make() As ZPriv + Set Make = New ZPriv + End Function +End Class diff --git a/bugs/private-constructor-callable/src/Sources/Startup.twin b/bugs/private-constructor-callable/src/Sources/Startup.twin new file mode 100644 index 000000000..4868761f0 --- /dev/null +++ b/bugs/private-constructor-callable/src/Sources/Startup.twin @@ -0,0 +1,42 @@ +' A Private Sub New does not make construction private: New compiles in another module, +' and in a project that references the package of the class, and the constructor runs. +Module Startup + + Public Sub Main() + Dim a As ZPriv = New ZPriv + Debug.Print "Dim a As ZPriv = New ZPriv: a.V = " & a.V + + Dim b As New ZPriv + Debug.Print "Dim b As New ZPriv: b.V = " & b.V + + Dim c As ZPriv + Set c = New ZPriv + Debug.Print "Set c = New ZPriv: c.V = " & c.V + + Dim d As ZPrivArgs = New ZPrivArgs(3) + Debug.Print "Private Sub New(x): New ZPrivArgs(3): d.V = " & d.V + + Dim e As ZFriend = New ZFriend + Debug.Print "Friend Sub New: New ZFriend: e.V = " & e.V + + Dim f As ZPrivateClass = New ZPrivateClass + Debug.Print "Private Class: New ZPrivateClass: f.V = " & f.V + + Dim g As ZFactory = ZFactory.Create + Debug.Print "New inside the class: ZFactory.Create.V = " & g.V + + Dim h As ZPublic = New ZPublic + Debug.Print "Public Sub New: New ZPublic: h.V = " & h.V + + Dim caller As New ZCaller + Dim i As ZPriv = caller.Make + Debug.Print "New inside another class: i.V = " & i.V + + Dim j As Locked = New Locked + Debug.Print "Package class, Private Sub New: New Locked: j.V = " & j.V + + Dim k As New Locked + Debug.Print "Package class: Dim k As New Locked: k.V = " & k.V + End Sub + +End Module diff --git a/bugs/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj b/bugs/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..50571981006842b3c5c902a24077d6a1cb640741 GIT binary patch literal 4663 zcmcgwS#RSu5O%g_()zJ)1xgsf#sMtDhwQ`w`rvaBG>4I7lWo_FLbe#2TUit+%1IF9 z=k%?=tiPf&qG%mfvTcFZKpbf<&U~C14*mV_-~72%_@PiJTu_@Whf7aBTv+a$zIA<% z&e$!Z_D~2eKqCHp36HM|-@*6s(&Iu>`|81?Cu}M#!Kfb#77AbB%d0{WzE7DyqmE;_ zlm{spGzT(Y!}r*l{btS6CcaYaZTNRVeIAHen*U1iQS5S)``H%Iw+i1PS6V^_URB;P zH&jAJ{QLp7_s9huuqC-;azQMY1fE9)nexE3$&@<$j>rWi@PADe7W1i3D8fUGM%_Fx z+XszSr$Q3o6?gWOM1|ZfsG#J!*Cr`VkiQJ3{!B1W5+1hA*1G{ z6wICP$Myzv8qBNUfh!}a*JL%lvZmxUfkdgE1qJMRo+hrhT-G(`HG?Zju%6gr&vkAkjxeNeEN~j5?CT@g=<_x zPFrr46FHE}bn{&ckeqew+(pa>J-#-d>law$S$eLQ(RH&muEh8ThbHrOH-pGhY#2j&>8T!-8D6GkODp}_t znuM53euVfo<8U)exce2NWE09Ml(HHjbT6w_$TRrHjGWaJnvqC0x(pN4RbLcyQ zP=_ul)SQIk^IHeeA+HMN1(nriI!5K9viBv`jn%Z~ z3afDnJ#j6OKzs$s(Z#|j*DIt^AHJAq38xRe5Sw!mY>&M^AmbNOu# z54y6opIA8A>Pv)Jw-WM@NjToLFq(x{AHZL!5*>s{h!27|zD&FSXCddcojBs>`cG6A zFM#=M;l#!@+6mM?on~wG=kskXlH9NuE>-(8?n=QO{|MTCI1}J-z+I}_K?Ty)#+0Qv zxJ8L87nUTEgp8Y>@g7$bHq3N&@Lo~ z0(?~CP;m!mwykhpyrfb>)%wYC?(<(@6nrf1Y?lDOTQ(1bC1LWYG8Y;tlYZ%llm_(H z3LJQ@pqwW-9{yF!^RAe5C`x={9!@Jx1LoLcOD<4z_fd_Z`2IAR^pH21>^_dwDreS` zKK;7;*cGz{ldxs~al{-tyqDDV!P%!A`#86ueV7Ja+Y&Yzz8j{+?0C*nAwXbu z0ORt}za4H9ldK+NF|G0k*0eM99SnWDuMabLxPYOG(0=LCl6HuDmc2EQOzJGo?K0X;X_=&RMd?ISu~>T`}Apu+xO$H-8KI^-g2dYj%vb z+3y%C=_fZsl>=AKv(aoI_)W5TL;^I;=4kAb`1m9j3)KM6v!ym+ zB<4t2LA|JwD?Qi)IEWBpmI&NC8)6Q8ey>(M6XgqUw@de>!Y8-0ovk-Z|c~!Fzr}9ie z0AYB6VP|0m+&x83NffA-|K((Rsl@1hg<~YP%q4f)wmK)gnhAaohg2XiO0P^vhO-P; zz$T$cabsF$$1shSYMFzMYI{acb-b>v+K%3FO{>)%*w!;JVgOL$@J9crW<(Rg0`DOK z7DBc!WVAa$puGWN)CG&`b)`sE*L733EnS5{@o~$M5b3(z&Qk@SD3D&d*fUJaoX^iZ zwajv)S}9y*|Hg+p}59LmFXIdU?c=*~$*_#-NoK*vMM*6B$ZZio23B**hfB03f{I01-q zc|H0h7g5UuO0#`Xull95Ku#Wh@Gp5#0$SkwTwLN@h5rJ7CZakJ$1+|kfot^4Vc+Vh zaCJ5#7sT#w+2ZsO-Mt3+gp>a6+z|eK8+uzmVl}_<* z_kX#s9hR^3jdhw|2pSC@_MNIk-)Hc6TDkd3r)Ru<4Jt`a_}mI8pYGMPc&uFIO$KNz z{dfBOk#wH(1P)vINRX>Pnce#=7H=N#m)%Ffj8P`0H*&qY0?!+Hzn60YZy1I}yL*zv w3%WPm3wd)3Da+obuq@_ejWu{R{TdVTfP%M-ymIsR<|9N94XfGM=8uuT0UYS^#sB~S literal 0 HcmV?d00001 diff --git a/bugs/script-control-reference-win64-crash/src/Settings b/bugs/script-control-reference-win64-crash/src/Settings new file mode 100644 index 000000000..af6b12259 --- /dev/null +++ b/bugs/script-control-reference-win64-crash/src/Settings @@ -0,0 +1,71 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "ScriptControlReferenceWin64Crash", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A project that references the Microsoft Script Control crashes the compiler when built for 64-bit", + "project.exportPathIsV2": true, + "project.id": "{23271215-32D7-4E19-AE84-4A07C2358D43}", + "project.name": "ScriptControlReferenceWin64Crash", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}", + "lcid": 0, + "name": "Microsoft Script Control 1.0", + "path32": "C:\\Windows\\SysWOW64\\msscript.ocx", + "path64": "", + "symbolId": "MSScriptControl", + "versionMajor": 1, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/script-control-reference-win64-crash/src/Sources/Startup.twin b/bugs/script-control-reference-win64-crash/src/Sources/Startup.twin new file mode 100644 index 000000000..c0966d800 --- /dev/null +++ b/bugs/script-control-reference-win64-crash/src/Sources/Startup.twin @@ -0,0 +1,10 @@ +' The project references the Microsoft Script Control 1.0 (Settings, project.references), +' and its code uses nothing of it. Compiled for win32 it has no diagnostic; compiled for +' win64 it crashes the compiler. +Module Startup + + Public Sub Main() + Debug.Print "hello" + End Sub + +End Module diff --git a/docs/Features/Advanced/Classes-and-Modules.md b/docs/Features/Advanced/Classes-and-Modules.md index 3a9ce74aa..0df1da080 100644 --- a/docs/Features/Advanced/Classes-and-Modules.md +++ b/docs/Features/Advanced/Classes-and-Modules.md @@ -18,6 +18,9 @@ A class can have a `Sub New` constructor, and the constructor can take arguments - A class that has both a `Class_Initialize` and a `Sub New` with no parameters cannot be created with a plain `New`. The call matches both, and fails with TB5073. - A class whose only `Sub New` has required arguments, with no `Class_Initialize`, cannot be declared [`As New`](../../tB/Core/New), which creates the object without arguments. The declaration fails with TB5121. If every argument of the `Sub New` is `Optional`, the class can be declared `As New`: the object is created by calling `Sub New` with the default values. +> [!NOTE] +> A `Sub New` declared `Private` is meant to make construction private, so that only code inside the class creates objects with it. In BETA 1005 this is not enforced: `New MyClass` in another module of the project, or in a project that references a package with the class, compiles and runs the private constructor. + ### Example For example a class can have: diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md b/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md index ecf116ea6..c8f27b103 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md @@ -11,7 +11,7 @@ has_toc: false Is meant to create JavaScript arrays and objects with the Microsoft Script Control, so that a program can hand them to code that expects one. The module has two functions. [**ToolsDic.ToWwwFormUrlencoded**](ToolsDic#towwwformurlencoded) is the one place in the package that uses it. > [!NOTE] -> In version 1.0.0.5 both functions raise error 91, *Object variable or With block variable not set*, in a 32-bit program. The module creates the Script Control with `Dim MSSC As New MSScriptControl.ScriptControl`, and that statement does not create an object: the variable is **Nothing** when it is used, in the package and also in a program that references the Script Control itself. The control is created by `CreateObject("MSScriptControl.ScriptControl")`. +> In version 1.0.0.5 both functions raise error 91, *Object variable or With block variable not set*, in a 32-bit program. The module creates the Script Control with `Dim MSSC As New MSScriptControl.ScriptControl`. In BETA 1005 that statement creates no usable object, in the package and also in a program that references the Script Control itself: a variable declared with the type **ScriptControl** cannot hold the control, so `Set` raises error -2147467262, *No such interface supported*, and every member used on a `Dim ... As New` variable raises error 91. A variable declared **As Object**, or **As MSScriptControl.IScriptControl**, holds the control, so `CreateObject("MSScriptControl.ScriptControl")` works with either. VB6 creates the control with `As New`, with `Set` and with `CreateObject`, for a variable of any of these types. The same happens with the classes of other ActiveX controls, such as the Winsock and Common Dialog controls. > > The Script Control exists only as a 32-bit component. A project that embeds the package cannot be built for 64-bit programs at all; see [32-bit and 64-bit programs](.#32-bit-and-64-bit-programs). diff --git a/docs/Reference/Contributed/TBMANLIB/index.md b/docs/Reference/Contributed/TBMANLIB/index.md index d9e6a5298..c67fccf1e 100644 --- a/docs/Reference/Contributed/TBMANLIB/index.md +++ b/docs/Reference/Contributed/TBMANLIB/index.md @@ -73,7 +73,7 @@ The references of a package are not visible to the project that uses it. A progr The Microsoft Script Control exists only as a 32-bit component, and the package's reference to it has a path for 32-bit programs and none for 64-bit programs. > [!WARNING] -> In BETA 1005, a project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) crashes the compiler, again after each restart, until the IDE falls back to Safe Mode, even when the program uses nothing of the package. The cause is the reference to the Script Control: when that reference and the module that uses it are removed from a copy of the package, the compiler no longer crashes, and reports only the errors of the code that needed the reference. The package can be used in 32-bit programs only. +> In BETA 1005, a project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) crashes the compiler, again after each restart, until the IDE falls back to Safe Mode, even when the program uses nothing of the package. The cause is a defect of twinBASIC, not of the package: any project that references the Microsoft Script Control, directly or through a package, crashes the compiler in a 64-bit build, whatever path the reference gives for 64-bit programs. Other 32-bit-only controls, such as the Common Dialog control, do not. The package can be used in 32-bit programs only. The Script Control is used by **ToolsJScript** and, through it, by **ToolsDic.ToWwwFormUrlencoded**. Those members do not work in 32-bit programs either; see [ToolsJScript](ToolsJScript). The other components are available to 32-bit programs. diff --git a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md index 79120d290..ff854fa2c 100644 --- a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md +++ b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md @@ -43,7 +43,7 @@ A property of a recipe checks nothing when it is set. A value that cannot be use > ``` > [!NOTE] -> The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, but a project that references the package can still create an object with **New**. It is a recipe in which every property has the empty value of its type: **Length** 0, no class enabled, empty strings, **MinPerClass** 0, **Mode** **pgmRandom** and **RandomSource** **Nothing**. **Generate** returns an empty string for it, with the error `InvalidLength`, until the properties are set. The presets are the way to get the defaults. +> The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, so that only its presets create recipes. In BETA 1005 twinBASIC does not enforce a **Private** constructor, and a project that references the package can still create an object with **New**. It is a recipe in which every property has the empty value of its type: **Length** 0, no class enabled, empty strings, **MinPerClass** 0, **Mode** **pgmRandom** and **RandomSource** **Nothing**. **Generate** returns an empty string for it, with the error `InvalidLength`, until the properties are set. The presets are the way to get the defaults. > > ```tb check_build check_run > Dim o As PwGenOptions = New PwGenOptions diff --git a/docs/Reference/Core/ReDim.md b/docs/Reference/Core/ReDim.md index 4c81c015a..540fbcc1e 100644 --- a/docs/Reference/Core/ReDim.md +++ b/docs/Reference/Core/ReDim.md @@ -46,6 +46,9 @@ Similarly, with **Preserve**, the size of the array can only change by changing When an array becomes smaller than it was, data in the eliminated elements will be lost. +> [!NOTE] +> An upper bound below the lower bound, as in `ReDim X(0 To -1)`, raises an error. In BETA 1005 the error is -2147467259, *Unspecified error*; VB6 raises error 9, *Subscript out of range*. + When variables are initialized, a numeric variable is initialized to 0, a variable-length string is initialized to a zero-length string (""), and a fixed-length string is filled with zeros. **Variant** variables are initialized to **Empty**. Each element of a user-defined type variable is initialized as if it were a separate variable. A variable that refers to an object must be assigned an existing object by using the [**Set**](Set) statement before it can be used. Until it is assigned an object, the declared object variable has the special value **Nothing**, which indicates that it doesn't refer to any particular instance of an object. From 0a1df75804302fd7fb4d7b81d8c8521f394bcff9 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 17:25:46 +0200 Subject: [PATCH 13/15] TBMANLIB_Diagnostic: a contributed section of 15 pages (DiagnosticEngine, the collectors, the formatters and the helper classes), listed under Experimental Packages: its text report and Dictionary fail, and its List and file helpers give wrong results; each defect shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../TBMANLIB_Diagnostic/Collectors.md | 476 +++++++++ .../TBMANLIB_Diagnostic/DiagnosticEngine.md | 579 +++++++++++ .../TBMANLIB_Diagnostic/DiagnosticResult.md | 144 +++ .../TBMANLIB_Diagnostic/Dictionary.md | 164 ++++ .../TBMANLIB_Diagnostic/FileHelper.md | 371 +++++++ .../TBMANLIB_Diagnostic/FormatHelper.md | 293 ++++++ .../TBMANLIB_Diagnostic/FormatterFactory.md | 134 +++ .../TBMANLIB_Diagnostic/Formatters.md | 183 ++++ .../IDiagnosticCollector.md | 155 +++ .../TBMANLIB_Diagnostic/IOutputFormatter.md | 161 +++ .../TBMANLIB_Diagnostic/KeyValuePair.md | 81 ++ .../Contributed/TBMANLIB_Diagnostic/List.md | 300 ++++++ .../TBMANLIB_Diagnostic/RefreshMode.md | 51 + .../TBMANLIB_Diagnostic/StringBuilder.md | 175 ++++ .../Contributed/TBMANLIB_Diagnostic/index.md | 185 ++++ docs/Reference/Experimental.md | 1 + docs/_book.yml | 7 + .../contributed/TBMANLIB_Diagnostic/api.json | 923 ++++++++++++++++++ .../contributed-TBMANLIB_Diagnostic/Settings | 74 ++ 21 files changed, 4459 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticEngine.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticResult.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/IDiagnosticCollector.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/IOutputFormatter.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/KeyValuePair.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/RefreshMode.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/StringBuilder.md create mode 100644 docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md create mode 100644 indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json create mode 100644 test/example-projects/contributed-TBMANLIB_Diagnostic/Settings diff --git a/WIP.md b/WIP.md index 7f333705e..08be57a9e 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-two **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-three **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 80477181a..3c6787feb 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1203, + "pages": 1218, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md new file mode 100644 index 000000000..02f2c1a7b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Collectors.md @@ -0,0 +1,476 @@ +--- +title: TBMANLIB_Diagnostic Collectors +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/Collectors +has_toc: false +--- + +# Collectors +{: .no_toc } + +The package has eleven collector classes. Each one finds one kind of information about the computer or its processes, and returns it as a [**DiagnosticResult**](DiagnosticResult) whose items are pairs of text: a key, which is the label of a line of the report, and a value. All eleven implement [**IDiagnosticCollector**](IDiagnosticCollector), so a [**DiagnosticEngine**](DiagnosticEngine) runs them in the same way, and all eleven report that they need no administrator rights. This page describes what each collector reports, the Windows functions it calls, and what it does when the information is missing. + +```tb check_build check_run +Dim collectors(0 To 10) As IDiagnosticCollector +Set collectors(0) = New OSInfoCollector +Set collectors(1) = New CPUInfoCollector +Set collectors(2) = New LocaleCollector +Set collectors(3) = New EnvironmentCollector +Set collectors(4) = New RuntimesCollector +Set collectors(5) = New CPULoadCollector +Set collectors(6) = New MemoryInfoCollector +Set collectors(7) = New UptimeCollector +Set collectors(8) = New ProcessInfoCollector +Set collectors(9) = New DiskInfoCollector +Set collectors(10) = New NetworkInfoCollector + +Dim i As Long +For i = 0 To 10 + Debug.Print collectors(i).CategoryName & ": " & collectors(i).RefreshMode & " " & collectors(i).SupportsSummary +Next i +' Output: +' Operating System: 0 False +' CPU Info: 0 False +' Locale: 0 False +' Environment: 0 True +' Runtimes: 0 True +' CPU Load: 1 False +' Memory: 1 False +' Uptime: 1 False +' Processes: 1 True +' Disk: 2 False +' Network: 2 True +``` + +* TOC +{:toc} + +## Using the collectors + +### Registering them + +A program registers the collectors it wants in the engine. The results come back in the order of registration, which is the order of the report. This is the report of all eleven, in the order of the table below: + +```tb check_build +Dim engine As New DiagnosticEngine +engine.RegisterCollector New OSInfoCollector +engine.RegisterCollector New CPUInfoCollector +engine.RegisterCollector New LocaleCollector +engine.RegisterCollector New EnvironmentCollector +engine.RegisterCollector New RuntimesCollector +engine.RegisterCollector New CPULoadCollector +engine.RegisterCollector New MemoryInfoCollector +engine.RegisterCollector New UptimeCollector +engine.RegisterCollector New ProcessInfoCollector +engine.RegisterCollector New DiskInfoCollector +engine.RegisterCollector New NetworkInfoCollector + +Dim report As String +report = FormatterFactory.FormatResults(engine.RunAll(), FormatterFactory.CreateFormatter("json")) +Debug.Print report +``` + +| Class | Category name | [Mode](RefreshMode) | Summary | Collects | +|:------|:--------------|:--------------------|:--------|:---------| +| [**OSInfoCollector**](#osinfocollector) | `Operating System` | Static | no | Windows version, architecture, computer and user name | +| [**CPUInfoCollector**](#cpuinfocollector) | `CPU Info` | Static | no | architecture, processor count, name and frequency | +| [**LocaleCollector**](#localecollector) | `Locale` | Static | no | language, country, code pages, time zone | +| [**EnvironmentCollector**](#environmentcollector) | `Environment` | Static | yes | sixteen environment variables | +| [**RuntimesCollector**](#runtimescollector) | `Runtimes` | Static | yes | .NET Framework, .NET and Visual C++ runtimes | +| [**CPULoadCollector**](#cpuloadcollector) | `CPU Load` | Dynamic | no | the load of the processors | +| [**MemoryInfoCollector**](#memoryinfocollector) | `Memory` | Dynamic | no | physical, page file and virtual memory | +| [**UptimeCollector**](#uptimecollector) | `Uptime` | Dynamic | no | the time since the system started | +| [**ProcessInfoCollector**](#processinfocollector) | `Processes` | Dynamic | yes | the running processes | +| [**DiskInfoCollector**](#diskinfocollector) | `Disk` | SemiDynamic | no | fixed and removable drives | +| [**NetworkInfoCollector**](#networkinfocollector) | `Network` | SemiDynamic | yes | host name, adapters and addresses | + +The properties other than the category name, the mode and the summary flag are the same for all eleven. **RequiresElevation** is **False**. **Description** is a sentence in English; it is quoted in each section below. + +### Calling a collector directly + +A collector needs no engine. A variable of the interface type calls it by the names of [**IDiagnosticCollector**](IDiagnosticCollector). The classes also declare their implementing members as **Public**, so a variable of the class type reaches them by the long names, such as `IDiagnosticCollector_Collect`. The classes do not have the short names. + +```tb check_build check_run +Dim memory As New MemoryInfoCollector +Dim uptime As IDiagnosticCollector +Set uptime = New UptimeCollector + +Dim a As DiagnosticResult(Of KeyValuePair(Of String, String)) +Dim b As DiagnosticResult(Of KeyValuePair(Of String, String)) +Set a = memory.IDiagnosticCollector_Collect() +Set b = uptime.Collect() + +Dim i As Long +Dim keys As String +For i = 0 To a.ItemCount - 1 + keys = keys & IIf(i > 0, ", ", "") & a.Items.Item(i).Key +Next i +Debug.Print a.Category & ": " & keys + +keys = "" +For i = 0 To b.ItemCount - 1 + keys = keys & IIf(i > 0, ", ", "") & b.Items.Item(i).Key +Next i +Debug.Print b.Category & ": " & keys +' Output: +' Memory: Memory Load, Total Physical, Available Physical, Used Physical, Total Page File, Available Page File, Total Virtual, Available Virtual +' Uptime: Uptime, Total Days, Total Hours, Total Seconds, Boot Time +``` + +### The values are text + +Every value is a **String**. Numbers are formatted with the **Format** function, so the decimal separator is the one of the user's regional settings: a size is `1.5 GB` for one user and `1,5 GB` for another. Sizes use [**FormatBytes**](FormatHelper#formatbytes): units of 1024 bytes, named B, KB, MB, GB and TB, with one decimal place from 1 KB up. + +This sample prints every item of a few collectors, to show the shape of the values. The values depend on the computer: + +```tb check_build +Dim collectors(0 To 3) As IDiagnosticCollector +Set collectors(0) = New OSInfoCollector +Set collectors(1) = New CPUInfoCollector +Set collectors(2) = New MemoryInfoCollector +Set collectors(3) = New LocaleCollector + +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) +Dim i As Long, j As Long +For i = 0 To 3 + Set r = collectors(i).Collect() + Debug.Print "== " & r.Category + For j = 0 To r.ItemCount - 1 + Debug.Print r.Items.Item(j).Key & ": " & r.Items.Item(j).Value + Next j +Next i +``` + +### A collector that fails + +None of the collectors raises an error for information that is missing. When a Windows function reports a failure, the collector either leaves the item out or, for the four collectors that cannot do without the function (**CPULoadCollector**, **MemoryInfoCollector**, **ProcessInfoCollector** and **DiskInfoCollector**), returns a result with [**Success**](DiagnosticResult#success) set to **False** and a message in [**ErrorMessage**](DiagnosticResult#errormessage). Each section says which. A formatter prints a failed result as one line with the key `Error`. + +## OSInfoCollector +{: .no_toc } + +Category `Operating System`; mode **Static**; no summary. **Description**: `Windows operating system version and build information`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `Version` | The major and minor number of the version, such as `10.0`. | +| `Build` | The build number, such as `22631`. | +| `Name` | `Windows 11` for major version 10 with build 22000 or higher, `Windows 10` for lower builds. For major version 6, by the minor number: `Windows 8.1` (3), `Windows 8` (2), `Windows 7` (1) or `Windows Vista` (0). For major version 5: `Windows XP x64 / Server 2003` (2), `Windows XP` (1) or `Windows 2000` (0). `Windows` for any other number. | +| `Architecture` | The architecture of the computer, not of the program: `x64`, `ARM64`, `x86`, `ARM`, or `Unknown`. A 32-bit program on 64-bit Windows reports `x64`. | +| `Computer Name` | The NetBIOS name of the computer. | +| `User Name` | The name of the user who runs the program. | + +The name is derived from the numbers alone, so a version of Windows Server gets the name of the client version that has the same numbers, such as `Windows 10` for a server with build 17763. + +**Windows functions.** `RtlGetVersion` of `ntdll.dll`, which returns the real version and not the one that a compatibility setting of the program reports; `GetNativeSystemInfo` and `GetComputerNameW` of `kernel32.dll`; `GetUserNameW` of `advapi32.dll`. + +**When the information is missing.** When `RtlGetVersion` fails, the result has the one item `Version` with the value `Unknown`, and then the architecture. `Computer Name` and `User Name` are left out when their functions fail. The result keeps **Success** set to **True**. + +## CPUInfoCollector +{: .no_toc } + +Category `CPU Info`; mode **Static**; no summary. **Description**: `CPU architecture and processor count`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `Architecture` | `x64 (AMD64)`, `ARM64`, `x86 (Intel)`, `ARM`, `IA64 (Itanium)`, or `Unknown (` and the number from Windows, then `)`. | +| `Processor Count` | The number of logical processors that `GetNativeSystemInfo` reports. | +| `Processor Level` | The `wProcessorLevel` number, a decimal number whose meaning depends on the architecture. | +| `Processor Revision` | `0x` and the `wProcessorRevision` number in hexadecimal. | +| `Page Size` | The size of a memory page, written by [**FormatBytes**](FormatHelper#formatbytes) with no decimals. The text is `4. KB`, with a decimal separator after the number: see [Known problems](.#known-problems-in-version-0000). | +| `Processor Name` | The text of the registry value `ProcessorNameString`, without leading and trailing spaces. Left out when it is empty or cannot be read. | +| `Frequency` | The registry value `~MHz` and ` MHz`, such as `3600 MHz`. Left out when it cannot be read. | + +**Windows functions.** `GetNativeSystemInfo` of `kernel32.dll`. For the two registry values, `RegOpenKeyExW`, `RegQueryValueExW` and `RegCloseKey` of `advapi32.dll`, on `HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0`. Only the first processor is read, so the name and the frequency are those of processor 0. + +**When the information is missing.** `Processor Name` and `Frequency` are left out. The other five items are always there. + +## LocaleCollector +{: .no_toc } + +Category `Locale`; mode **Static**; no summary. **Description**: `Regional settings, code pages, and timezone`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `LCID` | The locale identifier of the user's default locale, as a decimal number, such as `1033`. | +| `Language Code` | The two-letter or three-letter ISO 639 code of the language, such as `en`. | +| `Country Code` | The ISO 3166 code of the country, such as `US`. | +| `Locale` | The two codes joined by a hyphen, such as `en-US`. Present only when both codes are. | +| `Language` | The English name of the language, such as `English`. | +| `Country` | The English name of the country, such as `United States`. | +| `ANSI Code Page` | `CP` and the number of the ANSI code page, such as `CP1252`. | +| `OEM Code Page` | `CP` and the number of the OEM code page, such as `CP437`. | +| `Timezone` | `UTC`, the sign, the hours and, when it is not a whole hour, a colon and two digits of minutes: `UTC+0`, `UTC+1`, `UTC-5`, `UTC+5:30`. | +| `Timezone Name` | The name of the time zone in standard time. Left out when empty. | + +`Timezone` is built from the **Bias** field of the Windows time-zone record, which is the offset of standard time. The **DaylightBias** field is not read, and neither is the value that tells whether daylight saving time is in effect, so the offset does not change in summer, and `Timezone Name` is the standard-time name all year. On a computer in Central European Summer Time, where the clock is at UTC+2, the items are `Timezone` = `UTC+1` and `Timezone Name` = `W. Europe Standard Time`. See [Known problems](.#known-problems-in-version-0000). + +**Windows functions.** All of `kernel32.dll`: `GetUserDefaultLCID`, `GetLocaleInfoW` with the four type constants for the ISO language and country codes and the English language and country names, `GetACP`, `GetOEMCP` and `GetTimeZoneInformation`. + +**When the information is missing.** `Language Code`, `Country Code`, `Language` and `Country` are left out when `GetLocaleInfoW` returns nothing, and then `Locale` is too if one of the two codes is missing. `LCID`, the two code pages and `Timezone` are always there. + +## EnvironmentCollector +{: .no_toc } + +Category `Environment`; mode **Static**; has a summary. **Description**: `System and user environment variables`. + +**Keys.** The full result has one item for each of these sixteen environment variables that is defined and not empty, in this order, with the name of the variable as the key and its value as the value: + +`PATH`, `TEMP`, `TMP`, `USERPROFILE`, `APPDATA`, `LOCALAPPDATA`, `PROGRAMFILES`, `PROGRAMFILES(X86)`, `WINDIR`, `SYSTEMROOT`, `COMPUTERNAME`, `USERNAME`, `HOMEDRIVE`, `HOMEPATH`, `PATHEXT` and `COMSPEC`. + +The value of `PATH` is cut to its first 200 characters when it is longer, and `...` is added. + +**Summary.** The summary result has [**IsSummary**](DiagnosticResult#issummary) set to **True** and the items for `PATH`, `TEMP` and `USERPROFILE`, when they are defined. The value of `PATH` is the number of its entries in the form `(24 entries)`, where an entry is a part between semicolons. + +**Windows functions.** None. The collector reads the variables with the **Environ$** function of the runtime library. + +**When the information is missing.** A variable that is not defined, or empty, has no item. A computer on which none of the variables is defined gives an empty result with **Success** set to **True**. + +The environment of a user often holds names and folders that the user may not want to publish; see the note on [what a report contains](.#what-a-report-contains). + +## RuntimesCollector +{: .no_toc } + +Category `Runtimes`; mode **Static**; has a summary. **Description**: `Installed .NET, VC++ and other runtimes`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `.NET Framework 4.x` | The version of .NET Framework 4, such as `4.8`, from the number in the registry value `Release`. A number below 378389 gives `4.x (Release ` and the number and `)`. | +| `.NET Framework 3.5` | `Installed`, when the registry value `Install` of version 3.5 is 1. | +| `.NET SDK` | The text of the registry value `Version` under the key `sharedhost`. | +| `VC++ Redist (x64)` | The values `Major` and `Minor` of the 64-bit Visual C++ runtime, joined by a period, such as `14.38`. Present when `Major` is above 0. | +| `VC++ Redist (x86)` | The same for the 32-bit runtime. | + +The version of .NET Framework 4 is found in this table from the top: it is the first row whose number is at most the value of `Release`. + +| `Release` at least | Version | +|-------------------:|:--------| +| 533320 | 4.8.1 | +| 528040 | 4.8 | +| 461808 | 4.7.2 | +| 461308 | 4.7.1 | +| 460798 | 4.7 | +| 394802 | 4.6.2 | +| 394254 | 4.6.1 | +| 393295 | 4.6 | +| 379893 | 4.5.2 | +| 378675 | 4.5.1 | +| 378389 | 4.5 | + +**Summary.** The summary result has [**IsSummary**](DiagnosticResult#issummary) set to **True** and three items, each present only when the full result has the item it comes from: `.NET Framework`, the version text of `.NET Framework 4.x`; `.NET`, the value of `.NET SDK`; and `VC++ Redist`, which is the fixed text `2015-2022` whenever `Major` of the 64-bit runtime is 14 or more, and not the version. + +**Windows functions.** `RegOpenKeyExW`, `RegQueryValueExW` and `RegCloseKey` of `advapi32.dll`, on keys of `HKEY_LOCAL_MACHINE` that are opened with `KEY_READ` and `KEY_WOW64_64KEY`, so a 32-bit program reads the registry of 64-bit programs. The keys are: + +- `SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full`, value `Release`; +- `SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5`, value `Install`; +- `SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedhost`, value `Version`; +- `SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64`, values `Major` and `Minor`; +- `SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86`, values `Major` and `Minor`. + +The full result labels the value of the key `sharedhost` as `.NET SDK`, and the summary labels the same value `.NET`. + +**When the information is missing.** Each item is left out when its key or value is not in the registry. A computer with none of them gives an empty result with **Success** set to **True**. + +## CPULoadCollector +{: .no_toc } + +Category `CPU Load`; mode **Dynamic**; no summary. **Description**: `Current CPU usage percentage`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `Usage` | The load as a percentage with one decimal place, such as `12.3%`. | +| `Usage Value` | The same number without the percent sign, such as `12.3`. | + +The collector measures the load between two calls. It keeps the idle, kernel and user times that `GetSystemTimes` reported the last time, and the load is `(1 - idle change / (kernel change + user change)) * 100`, limited to the range 0 to 100. The first call of a new object, and the first call after [**Reset**](#reset), has nothing to compare with and reports 0 (`0.0%`). A call in which the counters did not change also gives 0. A program that wants a reading keeps one object and calls it again after some time, as the engine does when it runs the same registered collector a second time. + +```tb check_build check_run +Dim cpu As New CPULoadCollector +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) +Set r = cpu.IDiagnosticCollector_Collect() +Debug.Print r.Items.Item(0).Key & " " & r.Items.Item(0).Value +Debug.Print r.Items.Item(1).Key & " " & r.Items.Item(1).Value + +' Load the processor for half a second, then read again. +Dim t As Single = Timer +Do While Timer < t + 0.5 +Loop +Set r = cpu.IDiagnosticCollector_Collect() +Dim pct As Double = Val(r.Items.Item(1).Value) +Debug.Print pct >= 0 And pct <= 100 +' Output: +' Usage 0.0% +' Usage Value 0.0 +' True +``` + +**Windows functions.** `GetSystemTimes` of `kernel32.dll`. The three times are 64-bit values that the collector builds from the two halves of each `FILETIME`, with the helper [**FileTimeToLongLong**](#the-winapi-helpers) of the **WinAPI** module. + +**When the information is missing.** When `GetSystemTimes` fails, the result has no items, **Success** set to **False** and the message `Failed to get system times`. + +### New +{: .no_toc } + +Creates a collector. + +Syntax: **New** **CPULoadCollector** + +The new collector has no earlier reading, so its first result reports 0. + +### Reset +{: .no_toc } + +Discards the earlier reading. + +Syntax: *collector*.**Reset** + +The next result reports 0 and starts a new measurement. It is a method of the class, not of the interface. + +## MemoryInfoCollector +{: .no_toc } + +Category `Memory`; mode **Dynamic**; no summary. **Description**: `Physical and virtual memory status`. + +**Keys.** The result has these eight items, in this order: + +| Key | Value | +|:----|:------| +| `Memory Load` | The percentage of physical memory in use, as a whole number and `%`, such as `54%`. | +| `Total Physical` | The physical memory, such as `15.9 GB`. | +| `Available Physical` | The physical memory that is available. | +| `Used Physical` | `Total Physical` less `Available Physical`. | +| `Total Page File` | The `ullTotalPageFile` value of Windows, which is the current limit for committed memory of the system or of the program, whichever is smaller. | +| `Available Page File` | The part of that limit that is available. | +| `Total Virtual` | The size of the user-mode address space of the program that runs the collector. | +| `Available Virtual` | The part of that address space that is free. | + +The sizes are formatted with [**FormatBytes**](FormatHelper#formatbytes). + +**Windows functions.** `GlobalMemoryStatusEx` of `kernel32.dll`. + +**When the information is missing.** When the function fails, the result has no items, **Success** set to **False** and the message `Failed to get memory status`. + +## UptimeCollector +{: .no_toc } + +Category `Uptime`; mode **Dynamic**; no summary. **Description**: `System uptime since last boot`. + +**Keys.** + +| Key | Value | +|:----|:------| +| `Uptime` | The time since the system started, in the form of [**FormatUptime**](FormatHelper#formatuptime): `3d 12h 45m 32s`, with the days, hours and minutes left out while they are 0. | +| `Total Days` | The whole number of days. | +| `Total Hours` | The whole number of hours. | +| `Total Seconds` | The whole number of seconds. | +| `Boot Time` | The local time at which the system started, as `yyyy-mm-dd hh:nn:ss`. | + +`Boot Time` is calculated: it is the current time less the uptime. + +**Windows functions.** `GetTickCount64` of `kernel32.dll`. + +**When the information is missing.** The collector has no failure path: the function returns a number in all cases. + +## ProcessInfoCollector +{: .no_toc } + +Category `Processes`; mode **Dynamic**; has a summary. **Description**: `Running processes information`. + +**Keys.** The full result starts with two items and then has one item for each running process, in the order in which Windows lists them: + +| Key | Value | +|:----|:------| +| `Total Processes` | The number of processes. | +| `Current PID` | The process identifier of the program that runs the collector. | +| `PID 4120` | The file name of the program, without a folder, and the number of its threads, such as `explorer.exe (Threads: 71)`. | + +The key of a process is `PID ` and its process identifier. The process with the identifier 0 is listed as `[System Process]`. + +**Summary.** The summary result has [**IsSummary**](DiagnosticResult#issummary) set to **True** and three items: `Total Processes`, `Total Threads` (the sum of the thread counts of all processes) and `Current PID`. + +**Windows functions.** `CreateToolhelp32Snapshot` with `TH32CS_SNAPPROCESS`, `Process32FirstW`, `Process32NextW` and `CloseHandle`, and `GetCurrentProcessId`, all of `kernel32.dll`. The snapshot lists the processes of all users, and needs no administrator rights. + +**When the information is missing.** When the snapshot cannot be made, the result has no items, **Success** set to **False** and the message `Failed to create process snapshot`. If `Process32FirstW` fails, the counts are 0. + +## DiskInfoCollector +{: .no_toc } + +Category `Disk`; mode **SemiDynamic**; no summary. **Description**: `Disk drives and space information`. + +**Keys.** The result has four items for each fixed or removable drive, in the order of the drive letters. For drive `C`: + +| Key | Value | +|:----|:------| +| `Drive C` | `Fixed` or `Removable`. | +| `C: Total` | The size of the volume, such as `465.3 GB`. | +| `C: Free` | The free space of the volume, from the field of `GetDiskFreeSpaceExW` that holds the total number of free bytes. | +| `C: Used` | The used space and, in parentheses, the percentage of the volume with one decimal place, such as `120.5 GB (25.9%)`. | + +Only the drives of the types `DRIVE_FIXED` and `DRIVE_REMOVABLE` are reported. Network drives, CD-ROM drives and RAM disks have no items, although the code has names for them. A drive whose space cannot be read, such as a card reader with no card, has no items either. + +**Windows functions.** `GetLogicalDrives`, `GetDriveTypeW` and `GetDiskFreeSpaceExW` of `kernel32.dll`. + +**When the information is missing.** When `GetLogicalDrives` returns 0, the result has no items, **Success** set to **False** and the message `Failed to get logical drives`. A drive that fails is skipped. + +## NetworkInfoCollector +{: .no_toc } + +Category `Network`; mode **SemiDynamic**; has a summary. **Description**: `Network adapters and configuration`. + +**Keys.** The full result starts with the host name and then lists the adapters, counted from 0: + +| Key | Value | +|:----|:------| +| `Host Name` | The host name of the computer. | +| `Adapter 0` | The description of the adapter, such as `Intel(R) Ethernet Connection`. | +| `IPAddress0` | The first IPv4 address of that adapter, such as `192.168.1.20`. Left out for an adapter whose address is `0.0.0.0`. | + +The keys of the next adapter are `Adapter 1` and `IPAddress1`, and so on. The key of the address has no space before the number, and the key of the adapter has one. An adapter with no description has no items. + +**Summary.** The summary result has [**IsSummary**](DiagnosticResult#issummary) set to **True** and two items: `Host Name` and `Primary IP`. The primary address is the first IPv4 address of the first adapter that: + +- has the type Ethernet or IEEE 802.11 (wireless); +- has no `virtual`, `vmware`, `hyper-v`, `bluetooth` or `software` in its description, in any letter case; and +- has an address that is not `0.0.0.0` or `127.0.0.1`. + +**Windows functions.** `WSAStartup`, `gethostname` and `WSACleanup` of `ws2_32.dll` for the host name; `GetAdaptersInfo` of `iphlpapi.dll`, which is called twice, once for the size of the buffer and once for the data; `RtlMoveMemory` of `kernel32.dll` to copy the records. The host name and the descriptions are converted from the ANSI code page. Only IPv4 addresses are reported, because `GetAdaptersInfo` returns no others. + +The collector reads the adapters one after the other in steps of 640 bytes, a fixed number that its source calls approximate. It tests the pointer that links the records of the list only for 0 and does not follow it. The size of an `IP_ADAPTER_INFO` record depends on the size of a pointer. In a 32-bit program the records are 640 bytes apart, and the collector lists every adapter. In a 64-bit program `LenB` of the package's `IP_ADAPTER_INFO` type is 696 and the records are 704 bytes apart, so the second step does not reach the second record: on a computer with several adapters the full result lists `Adapter 0` only, and the summary may have no `Primary IP`. See [Known problems](.#known-problems-in-version-0000). + +**When the information is missing.** The host name is left out when Winsock cannot start or `gethostname` fails. When there are no adapters, or `GetAdaptersInfo` fails, the full result has no adapter items and the summary has no `Primary IP`. The collector has no error result. + +## The WinAPI helpers + +The **WinAPI** module of the package declares the Windows functions that the collectors call, and a few helper procedures. The module **WinAPIStructures** declares the record types and constants they use. Neither has a page of its own. Two helpers matter to the results of the collectors. + +`WinAPI.FileTimeToLongLong` converts a `FILETIME` to a 64-bit number, and is what **CPULoadCollector** uses. It adds the high half multiplied by 2^32 to the low half, with the sign bit of the low half added back as 2147483648, so a low half with all bits set gives 4294967295: + +```tb check_build check_run +Dim ft As WinAPIStructures.FILETIME +ft.dwLowDateTime = -1 +Debug.Print WinAPI.FileTimeToLongLong(ft) +ft.dwHighDateTime = 3 +ft.dwLowDateTime = &H90000000 +Debug.Print WinAPI.FileTimeToLongLong(ft) +' Output: +' 4294967295 +' 15300820992 +``` + +`WinAPI.GetStringFromBuffer` builds a string from an array of **Integer** characters up to the first zero. It is used for the process name and the time-zone name. + +## See Also + +- [IDiagnosticCollector](IDiagnosticCollector) interface -- the contract that all of these implement +- [DiagnosticEngine](DiagnosticEngine) class -- runs the collectors +- [RefreshMode](RefreshMode) enumeration -- the modes of the table above +- [FormatHelper](FormatHelper) module -- the functions that format sizes, times and percentages +- [Formatters](Formatters) -- turn the results into text, JSON or HTML diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticEngine.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticEngine.md new file mode 100644 index 000000000..778bff095 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticEngine.md @@ -0,0 +1,579 @@ +--- +title: DiagnosticEngine +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/DiagnosticEngine +has_toc: false +--- + +# DiagnosticEngine class +{: .no_toc } + +A **DiagnosticEngine** keeps a list of collectors, runs them one after another, and returns what they collected. It has no collectors of its own: a program registers each one it wants, and the order of registration is the order of the results. It writes nothing to a screen or a file. Its results are read directly or passed to a [formatter](FormatterFactory#formatresults). + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New OSInfoCollector +engine.RegisterCollector New MemoryInfoCollector +engine.RegisterCollector New UptimeCollector + +Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Set results = engine.RunAll() + +Dim i As Long +For i = 0 To results.Count - 1 + Debug.Print results.Item(i).Category & ": " & results.Item(i).Success +Next i +' Output: +' Operating System: True +' Memory: True +' Uptime: True +``` + +The result of every collector has the type [**DiagnosticResult**](DiagnosticResult)(Of [**KeyValuePair**](KeyValuePair)(Of String, String)), so a list of results has the type `List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))`. The syntax lines on this page write the type out. The collectors are classes that implement [**IDiagnosticCollector**](IDiagnosticCollector); the package has [eleven of them](Collectors), and a program can add its own. + +* TOC +{:toc} + +## How a run proceeds + +[**RunAll**](#runall) takes the collectors in the order in which they were registered. For each one it does these steps: + +1. It raises [**CollectorStarted**](#collectorstarted). +2. It raises [**Progress**](#progress) with the share of collectors that are already done, and the text `Collecting: ` and the collector's category name. +3. It calls the collector's **Collect** method, or its **CollectSummary** method for a summary run, and adds the result to [**Results**](#results). +4. It raises [**CollectorCompleted**](#collectorcompleted). + +When the last collector is done, it raises **Progress** with 100 and the text `Complete`, and then [**AllCompleted**](#allcompleted). A run is synchronous. All of this happens inside the call, in the thread of the caller. + +Each notification exists twice, as an event and as a callback that a program sets with a property: [**OnCollectorStarted**](#oncollectorstarted), [**OnProgress**](#onprogress), [**OnCollectorCompleted**](#oncollectorcompleted) and [**OnError**](#onerror). The engine raises the event first and calls the callback second, and does both when a program uses both. The callbacks are [delegates](#delegates). **AllCompleted** has no callback. + +### A collector that raises an error + +A collector that raises a run-time error does not stop the run. The engine takes the text of the error (**Err.Description**), replaces the collector's result with a new, empty result for the same category that holds the text as its [**ErrorMessage**](DiagnosticResult#errormessage), and goes on with the next collector. The number and the source of the error are not kept. The engine raises [**Error**](#error) for it before **CollectorCompleted**, which then reports `success` as **False** and an `itemCount` of 0. [**RunCategory**](#runcategory) and [**RunByRefreshMode**](#runbyrefreshmode) handle an error in the same way. + +A collector that fails without raising an error, such as one whose Windows call returns an error code, returns its own result with **Success** set to **False**. The engine does not call **Error** for it. + +### The samples on this page + +The samples use two classes of the program. **StubCollector** is a collector that returns one item and can be told to fail. **RunWatcher** prints each event of an engine. + +```tb check_build slot=file projname=diag-engine +Class StubCollector + Implements IDiagnosticCollector + + Public Category As String + Public Fail As Boolean + + Private Property Get IDiagnosticCollector_CategoryName() As String + Return Category + End Property + + Private Property Get IDiagnosticCollector_Description() As String + Return "A collector for the samples" + End Property + + Private Property Get IDiagnosticCollector_RefreshMode() As RefreshMode + Return RefreshMode.Static + End Property + + Private Property Get IDiagnosticCollector_RequiresElevation() As Boolean + Return False + End Property + + Private Property Get IDiagnosticCollector_SupportsSummary() As Boolean + Return False + End Property + + Private Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String)) + If Fail Then Err.Raise 5, "StubCollector", "stub failure" + Dim result As DiagnosticResult(Of KeyValuePair(Of String, String)) + Set result = New DiagnosticResult(Of KeyValuePair(Of String, String))(Category) + result.AddItem New KeyValuePair(Of String, String)("Answer", "42") + Return result + End Function + + Private Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String)) + Return IDiagnosticCollector_Collect() + End Function +End Class +``` + +```tb check_build slot=file projname=diag-engine +Class RunWatcher + Private WithEvents m_Engine As DiagnosticEngine + + Public Sub Attach(ByVal engine As DiagnosticEngine) + Set m_Engine = engine + End Sub + + Private Sub m_Engine_CollectorStarted(ByVal category As String) + Debug.Print "started " & category + End Sub + + Private Sub m_Engine_Progress(ByVal percent As Long, ByVal message As String) + Debug.Print "progress " & percent & " " & message + End Sub + + Private Sub m_Engine_Error(ByVal category As String, ByVal errorMessage As String) + Debug.Print "error " & category & " " & errorMessage + End Sub + + Private Sub m_Engine_CollectorCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long) + Debug.Print "completed " & category & " " & success & " " & itemCount + End Sub + + Private Sub m_Engine_AllCompleted(ByVal results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))) + Debug.Print "all " & results.Count + End Sub +End Class +``` + +With one collector that fails between two that do not: + +```tb check_build check_run projname=diag-engine +Dim engine As New DiagnosticEngine +Dim watcher As New RunWatcher +watcher.Attach engine + +Dim alpha As New StubCollector +Dim beta As New StubCollector +Dim gamma As New StubCollector +alpha.Category = "Alpha" +beta.Category = "Beta" +beta.Fail = True +gamma.Category = "Gamma" +engine.RegisterCollector alpha +engine.RegisterCollector beta +engine.RegisterCollector gamma + +engine.RunAll +' Output: +' started Alpha +' progress 0 Collecting: Alpha +' completed Alpha True 1 +' started Beta +' progress 33 Collecting: Beta +' error Beta stub failure +' completed Beta False 0 +' started Gamma +' progress 67 Collecting: Gamma +' completed Gamma True 1 +' progress 100 Complete +' all 3 +``` + +## Properties + +### CollectorCount +{: .no_toc } + +The number of registered collectors. + +Syntax: *engine*.**CollectorCount** **As Long** + +Read-only. It is the **Count** of the list that [**Collectors**](#collectors) returns. + +### Collectors +{: .no_toc } + +The registered collectors, in the order of registration. + +Syntax: *engine*.**Collectors** **As List(Of IDiagnosticCollector)** + +Read-only. The property returns the list that the engine uses, not a copy. A collector added to the list directly is run, and the check for a duplicate category name that [**RegisterCollector**](#registercollector) makes is not made. A collector removed from it is no longer run. See [**List**](List) for the members of the list. Its index starts at 0. + +### IsRunning +{: .no_toc } + +Whether a run is in progress. + +Syntax: *engine*.**IsRunning** **As Boolean** + +Read-only. [**RunAll**](#runall) and [**RunByRefreshMode**](#runbyrefreshmode) set it to **True** when they start and to **False** when their loop over the collectors ends. It is therefore **True** in the handlers of **CollectorStarted**, **Error**, **CollectorCompleted** and the **Progress** events that come before a collector, and **False** in the last **Progress** event (100, `Complete`) and in **AllCompleted**. [**RunCategory**](#runcategory) sets it to **False** before it raises **CollectorCompleted**. A run that a handler starts sets it to **False** when that run ends, while the outer run goes on. + +### OnCollectorCompleted +{: .no_toc } + +A callback for the end of each collector. + +Syntax: *engine*.**OnCollectorCompleted** **=** **AddressOf** *procedure* + +Write-only. The value is a [**DiagnosticCompleteHandler**](#diagnosticcompletehandler). The engine calls it after it raises [**CollectorCompleted**](#collectorcompleted), with the same three values. + +### OnCollectorStarted +{: .no_toc } + +A callback for the start of each collector. + +Syntax: *engine*.**OnCollectorStarted** **=** **AddressOf** *procedure* + +Write-only. The value is a [**DiagnosticCompleteHandler**](#diagnosticcompletehandler), the same delegate type as for the end of a collector. The engine calls it after it raises [**CollectorStarted**](#collectorstarted), with the category name, `success` set to **True** and `itemCount` set to 0. + +### OnError +{: .no_toc } + +A callback for the error of a collector. + +Syntax: *engine*.**OnError** **=** **AddressOf** *procedure* + +Write-only. The value is a [**DiagnosticErrorHandler**](#diagnosticerrorhandler). The engine calls it after it raises [**Error**](#error), with the same two values. + +### OnProgress +{: .no_toc } + +A callback for the progress of a run. + +Syntax: *engine*.**OnProgress** **=** **AddressOf** *procedure* + +Write-only. The value is a [**DiagnosticProgressHandler**](#diagnosticprogresshandler). The engine calls it after it raises [**Progress**](#progress), with the same two values. + +The callbacks and [**Cancel**](#cancel) are shown in this example, which stops the run when it reaches the collector `Beta`. The module holds the engine in a variable of its own for the callback to reach. + +```tb check_build slot=module projname=diag-engine +Public CallbackEngine As DiagnosticEngine + +Public Sub StopAtBeta(ByVal percent As Long, ByVal message As String) + Debug.Print percent & " " & message + If message = "Collecting: Beta" Then CallbackEngine.Cancel +End Sub + +Public Sub ShowCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long) + Debug.Print "completed " & category & " " & itemCount +End Sub +``` + +```tb check_build check_run projname=diag-engine +Set CallbackEngine = New DiagnosticEngine +CallbackEngine.OnProgress = AddressOf StopAtBeta +CallbackEngine.OnCollectorCompleted = AddressOf ShowCompleted + +Dim names() As String = Split("Alpha,Beta,Gamma", ",") +Dim c As StubCollector +Dim i As Long +For i = 0 To 2 + Set c = New StubCollector + c.Category = names(i) + CallbackEngine.RegisterCollector c +Next i + +Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Set results = CallbackEngine.RunAll() +Debug.Print results.Count +' Output: +' 0 Collecting: Alpha +' completed Alpha 1 +' 33 Collecting: Beta +' completed Beta 1 +' 100 Complete +' 2 +``` + +### Results +{: .no_toc } + +The results of the last [**RunAll**](#runall). + +Syntax: *engine*.**Results** **As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))** + +Read-only. It is the list that **RunAll** fills and returns, not a copy of it. **RunAll** empties it when it starts, so a variable that holds the result of an earlier run is empty or refilled once the next run has started. [**RunCategory**](#runcategory) and [**RunByRefreshMode**](#runbyrefreshmode) do not add to it. Copy the items to a list of the program to keep them. + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New UptimeCollector + +Dim first As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Set first = engine.RunAll() +Debug.Print first Is engine.Results ' True +Debug.Print first.Count ' 1 + +engine.UnregisterCollector "Uptime" +engine.RunAll +Debug.Print first.Count ' 0 +' Output: +' True +' 1 +' 0 +``` + +## Methods + +### Cancel +{: .no_toc } + +Asks a run to stop before its next collector. + +Syntax: *engine*.**Cancel** + +Sets a flag that [**RunAll**](#runall) and [**RunByRefreshMode**](#runbyrefreshmode) read before they start each collector. The collector that is running finishes. A run is synchronous, so the call can come only from code that runs during the run, which is a handler of an event or a callback. The two methods clear the flag when they start, so a call that comes before a run has no effect. **RunAll** still raises **Progress** with 100 and `Complete` and then **AllCompleted** after a cancel, with the results of the collectors that ran. [**RunCategory**](#runcategory) does not read the flag. See the example under [**OnProgress**](#onprogress). + +### IsElevated +{: .no_toc } + +Whether the program runs with administrator rights. + +Syntax: *engine*.**IsElevated** **As Boolean** + +Calls `IsUserAnAdmin` of `shell32.dll` and returns **True** when the result is not zero. The engine does not use the value: it never reads the [**RequiresElevation**](IDiagnosticCollector#requireselevation) property of its collectors, and the collectors of the package all return **False** for it. + +### New +{: .no_toc } + +Creates an engine. + +Syntax: **New** **DiagnosticEngine** + +The engine starts with no collectors, no results, no callbacks and **IsRunning** set to **False**. + +### RegisterCollector +{: .no_toc } + +Adds a collector to the engine. + +Syntax: *engine*.**RegisterCollector** ( *collector* ) + +*collector* +: *required* An object that implements [**IDiagnosticCollector**](IDiagnosticCollector). + +The collector goes to the end of the list. Registering the same collector twice, or two collectors with the same [**CategoryName**](IDiagnosticCollector#categoryname), is an error. + +The method raises error 5 (*Invalid procedure call or argument*) in these cases, with the source `DiagnosticEngine.RegisterCollector`: + +- *collector* is **Nothing**. The text is `Collector cannot be Nothing`. +- A registered collector has the same category name. The text is `Collector already exists: ` and the name. The names are compared with the `=` operator, and the package has no **Option Compare** statement, so the comparison is binary and the letter case matters: `Alpha` and `ALPHA` are two names. + +```tb check_build check_run projname=diag-engine +Dim engine As New DiagnosticEngine +Dim a As New StubCollector +a.Category = "Alpha" +engine.RegisterCollector a + +On Error Resume Next +engine.RegisterCollector a +Debug.Print Err.Number & " " & Err.Source & ": " & Err.Description +Err.Clear +engine.RegisterCollector Nothing +Debug.Print Err.Number & " " & Err.Source & ": " & Err.Description +Debug.Print engine.CollectorCount +' Output: +' 5 DiagnosticEngine.RegisterCollector: Collector already exists: Alpha +' 5 DiagnosticEngine.RegisterCollector: Collector cannot be Nothing +' 1 +``` + +### RunAll +{: .no_toc } + +Runs every collector and returns the results. + +Syntax: *engine*.**RunAll** [ ( *useSummary* ) ] **As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))** + +*useSummary* +: *optional* A **Boolean**. When **True**, a collector whose **SupportsSummary** property is **True** is asked for **CollectSummary**, and the others for **Collect**. Default: **False**. + +Returns the list [**Results**](#results), which holds one result for each collector, in the order of registration. [How a run proceeds](#how-a-run-proceeds) describes the steps and the events. A run with no collector raises **Progress** with 100 and `Complete` and then **AllCompleted** with an empty list. + +A summary result has [**IsSummary**](DiagnosticResult#issummary) set to **True** when the collector sets it, as the built-in collectors that support a summary do. + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New CPUInfoCollector +engine.RegisterCollector New EnvironmentCollector + +Dim results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Set results = engine.RunAll(True) +Debug.Print results.Item(0).Category & " " & results.Item(0).IsSummary +Debug.Print results.Item(1).Category & " " & results.Item(1).IsSummary +' Output: +' CPU Info False +' Environment True +``` + +### RunByRefreshMode +{: .no_toc } + +Runs the collectors that have one refresh mode. + +Syntax: *engine*.**RunByRefreshMode** ( *mode* [ , *useSummary* ] ) **As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))** + +*mode* +: *required* A [**RefreshMode**](RefreshMode) member. Only the collectors whose **RefreshMode** property is equal to it run. + +*useSummary* +: *optional* A **Boolean**, as for [**RunAll**](#runall). Default: **False**. + +Returns a new list with one result for each collector that ran, in the order of registration. The list is not [**Results**](#results), which this method does not change. The method raises **CollectorStarted**, **CollectorCompleted** and **Error** for each collector, and neither **Progress** nor **AllCompleted**. It clears the flag of [**Cancel**](#cancel) when it starts and reads it before each collector, as **RunAll** does. + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New OSInfoCollector +engine.RegisterCollector New MemoryInfoCollector +engine.RegisterCollector New LocaleCollector +engine.RegisterCollector New UptimeCollector + +Dim changing As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Set changing = engine.RunByRefreshMode(RefreshMode.Dynamic) + +Dim i As Long +For i = 0 To changing.Count - 1 + Debug.Print changing.Item(i).Category +Next i +Debug.Print engine.Results.Count +' Output: +' Memory +' Uptime +' 0 +``` + +### RunCategory +{: .no_toc } + +Runs the collector with one category name. + +Syntax: *engine*.**RunCategory** ( *categoryName* [ , *useSummary* ] ) **As DiagnosticResult(Of KeyValuePair(Of String, String))** + +*categoryName* +: *required* A **String**, the [**CategoryName**](IDiagnosticCollector#categoryname) of a registered collector. The first collector with that name runs. + +*useSummary* +: *optional* A **Boolean**, as for [**RunAll**](#runall). Default: **False**. + +Returns the result of the collector. The result is not added to [**Results**](#results). The method raises **CollectorStarted**, **CollectorCompleted** and **Error**, and neither **Progress** nor **AllCompleted**. + +When no collector has the name, the method raises no error and no event. It returns a result for *categoryName* with **Success** set to **False** and the message `Collector not found: ` and the name. + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New UptimeCollector + +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) +Set r = engine.RunCategory("Uptime") +Debug.Print r.Category & " " & r.Success & " " & r.ItemCount + +Set r = engine.RunCategory("Nothing here") +Debug.Print r.Category & " " & r.Success & " " & r.ErrorMessage +' Output: +' Uptime True 5 +' Nothing here False Collector not found: Nothing here +``` + +### UnregisterCollector +{: .no_toc } + +Removes a collector from the engine. + +Syntax: *engine*.**UnregisterCollector** ( *categoryName* ) + +*categoryName* +: *required* A **String**, the category name of the collector to remove. + +Removes the first collector with that name. A name that no collector has is ignored without an error. The names are compared as in [**RegisterCollector**](#registercollector), so the letter case matters. + +## Events + +### AllCompleted +{: .no_toc } + +Raised when [**RunAll**](#runall) has run its last collector. + +Syntax: **Event** **AllCompleted** ( *results* ) + +*results* +: The [**Results**](#results) list, as a `List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))`. + +It follows the last **Progress** event. A run that was cancelled raises it too. There is no callback for it. + +### CollectorCompleted +{: .no_toc } + +Raised after a collector has returned its result. + +Syntax: **Event** **CollectorCompleted** ( *category*, *success*, *itemCount* ) + +*category* +: A **String**, the category name of the collector. + +*success* +: A **Boolean**, the **Success** property of the result. It is **False** when the collector raised an error, and when it reported a failure itself. + +*itemCount* +: A **Long**, the **ItemCount** property of the result. + +### CollectorStarted +{: .no_toc } + +Raised before a collector is run. + +Syntax: **Event** **CollectorStarted** ( *category* ) + +*category* +: A **String**, the category name of the collector. + +### Error +{: .no_toc } + +Raised when a collector raised a run-time error. + +Syntax: **Event** **Error** ( *category*, *errorMessage* ) + +*category* +: A **String**, the category name of the collector. + +*errorMessage* +: A **String**, the **Err.Description** of the error. + +It is raised before **CollectorCompleted** for the same collector. It is not raised for a result that a collector returns with **Success** set to **False**, or for a category name that no collector has. A handler is written as `m_Engine_Error` for a variable `m_Engine` declared with **WithEvents**. + +### Progress +{: .no_toc } + +Raised before each collector of a [**RunAll**](#runall), and at its end. + +Syntax: **Event** **Progress** ( *percent*, *message* ) + +*percent* +: A **Long**. Before a collector it is the number of collectors that are already done, times 100, divided by the number of collectors, and converted with **CLng**: with three collectors the values are 0, 33 and 67. At the end it is 100. + +*message* +: A **String**. Before a collector it is `Collecting: ` and the category name of the collector. At the end it is `Complete`. + +**RunCategory** and **RunByRefreshMode** do not raise it. + +## Delegates + +The three delegate types are declared in a module of the package, **DelegatesModule**, which has no page of its own. They are the types of the callback properties. A procedure for a callback is written with the same arguments, and passed with **AddressOf**. + +### DiagnosticCompleteHandler +{: .no_toc } + +The type of [**OnCollectorStarted**](#oncollectorstarted) and [**OnCollectorCompleted**](#oncollectorcompleted). + +Syntax: **Delegate Sub** **DiagnosticCompleteHandler** ( **ByVal** *category* **As String**, **ByVal** *success* **As Boolean**, **ByVal** *itemCount* **As Long** ) + +The arguments are those of [**CollectorCompleted**](#collectorcompleted). When the engine calls a procedure of this type for the start of a collector, *success* is **True** and *itemCount* is 0. + +### DiagnosticErrorHandler +{: .no_toc } + +The type of [**OnError**](#onerror). + +Syntax: **Delegate Sub** **DiagnosticErrorHandler** ( **ByVal** *category* **As String**, **ByVal** *errorMessage* **As String** ) + +The arguments are those of [**Error**](#error). + +### DiagnosticProgressHandler +{: .no_toc } + +The type of [**OnProgress**](#onprogress). + +Syntax: **Delegate Sub** **DiagnosticProgressHandler** ( **ByVal** *percent* **As Long**, **ByVal** *message* **As String** ) + +The arguments are those of [**Progress**](#progress). + +## See Also + +- [IDiagnosticCollector](IDiagnosticCollector) interface -- the contract of a collector +- [Collectors](Collectors) -- the eleven collectors of the package +- [DiagnosticResult](DiagnosticResult) class -- what a collector returns +- [RefreshMode](RefreshMode) enumeration -- the modes that **RunByRefreshMode** selects by +- [FormatterFactory](FormatterFactory) module -- turns a list of results into text +- [**On Error**](../../../Core/On-Error) statement -- handles the errors that **RegisterCollector** raises diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticResult.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticResult.md new file mode 100644 index 000000000..7bf2d3746 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/DiagnosticResult.md @@ -0,0 +1,144 @@ +--- +title: DiagnosticResult +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/DiagnosticResult +has_toc: false +--- + +# DiagnosticResult class +{: .no_toc } + +A **DiagnosticResult** holds what one collector found: the category name, the list of items, the time, and whether the collection worked. It is a generic class, **DiagnosticResult(Of T)**, where *T* is the type of an item. The engine, the collectors and the formatters all use `DiagnosticResult(Of KeyValuePair(Of String, String))`, so each item is a [**KeyValuePair**](KeyValuePair) of a key and a value that are both text. + +```tb check_build check_run +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Demo") +r.AddItem New KeyValuePair(Of String, String)("Answer", "42") + +Debug.Print r.Category & " " & r.Success & " " & r.ItemCount +Debug.Print r.Items.Item(0).Key & "=" & r.Items.Item(0).Value + +r.SetError "disk not ready" +Debug.Print r.Success & " " & r.ErrorMessage & " " & r.ItemCount +Debug.Print Abs(DateDiff("s", r.CollectedAt, Now)) < 5 +' Output: +' Demo True 1 +' Answer=42 +' False disk not ready 1 +' True +``` + +A program creates a result when it writes a collector of its own, see [**IDiagnosticCollector**](IDiagnosticCollector), or builds results by hand to give a formatter, see [**FormatterFactory.FormatResults**](FormatterFactory#formatresults). The built-in collectors create theirs with the category name as the argument, and add the items in the order in which they appear in a report. + +* TOC +{:toc} + +## Properties + +### Category +{: .no_toc } + +The category name of the result. + +Syntax: *result*.**Category** [ **=** *value* ] + +A **String**. It is the argument of the constructor, or an empty string for a result made without one. The formatters print it as the title of the group of items, so it should be the [**CategoryName**](IDiagnosticCollector#categoryname) of the collector. The engine does not check that it is. + +### CollectedAt +{: .no_toc } + +The time at which the result was created. + +Syntax: *result*.**CollectedAt** [ **=** *value* ] + +A **Date**, the local time of the constructor (**Now**). It does not change when items are added. A collector creates its result at the start of its work, so the value is the time at which the collection began. No formatter prints it. + +### ErrorMessage +{: .no_toc } + +The text of the failure. + +Syntax: *result*.**ErrorMessage** [ **=** *value* ] + +A **String**, empty for a result that did not fail. [**SetError**](#seterror) sets it. A formatter that finds **Success** set to **False** prints this text as the only item of the category, under the key `Error`. + +### IsSummary +{: .no_toc } + +Whether the result is the short form of a collection. + +Syntax: *result*.**IsSummary** [ **=** *value* ] + +A **Boolean**, **False** for a new result. The collectors that support a summary set it to **True** in the result of **CollectSummary**. Nothing reads it in the package, and no formatter prints it. A program can read it to tell the two forms apart. + +### ItemCount +{: .no_toc } + +The number of items. + +Syntax: *result*.**ItemCount** **As Long** + +Read-only. It is the **Count** of [**Items**](#items). + +### Items +{: .no_toc } + +The items of the result. + +Syntax: *result*.**Items** **As List(Of T)** + +Read-only. The property returns the list that the result uses, not a copy, so a program can change the list directly. Its index starts at 0, see [**List**](List). The list is empty when the result is new and when the collection failed before it added anything. + +### Success +{: .no_toc } + +Whether the collection worked. + +Syntax: *result*.**Success** [ **=** *value* ] + +A **Boolean**, **True** for a new result. [**SetError**](#seterror) sets it to **False**. The engine reports it as the *success* argument of [**CollectorCompleted**](DiagnosticEngine#collectorcompleted). + +## Methods + +### AddItem +{: .no_toc } + +Adds an item at the end of the list. + +Syntax: *result*.**AddItem** ( *item* ) + +*item* +: *required* A value of type *T*. + +The method does not check the item and does not look for a key that is already there. + +### New +{: .no_toc } + +Creates a result. + +Syntax: **New** **DiagnosticResult(Of T)** [ ( *categoryName* ) ] + +*categoryName* +: *optional* A **String**, the value of [**Category**](#category). Without it, **Category** is an empty string. + +The new result has no items, **Success** set to **True**, an empty **ErrorMessage**, **IsSummary** set to **False**, and **CollectedAt** set to the time of the call. + +### SetError +{: .no_toc } + +Marks the result as failed. + +Syntax: *result*.**SetError** ( *errMsg* ) + +*errMsg* +: *required* A **String**, the text for [**ErrorMessage**](#errormessage). + +Sets **Success** to **False** and **ErrorMessage** to *errMsg*. The items that are already in the list stay, and the formatters leave them out of the report. + +## See Also + +- [KeyValuePair](KeyValuePair) class -- the type of the items +- [List](List) class -- the type of **Items** +- [IDiagnosticCollector](IDiagnosticCollector) interface -- the functions that return a result +- [DiagnosticEngine](DiagnosticEngine) class -- runs collectors and returns lists of results diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md new file mode 100644 index 000000000..629ed893b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Dictionary.md @@ -0,0 +1,164 @@ +--- +title: TBMANLIB_Diagnostic Dictionary +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/Dictionary +has_toc: false +--- + +# Dictionary class +{: .no_toc } + +A collection of values that are found by a key. It is a generic class, **Dictionary(Of TKey, TValue)**, and is not the **Dictionary** of the Scripting Runtime. It keeps the keys in one [**List**](List) and the values in another, in the order in which they were added, and finds a key by looking at the keys one after the other, so it is meant for a few dozen items and not a large number. Nothing else in the package uses it. + +**In version 0.0.0.0 a dictionary cannot be created.** The constructor assigns the two lists to its fields without **Set**, and `New Dictionary(Of TKey, TValue)` raises run-time error 91, *Object variable or With block variable not set*. The variable keeps the value **Nothing**, so every member fails as well. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim ages As Dictionary(Of String, Long) +On Error Resume Next +Set ages = New Dictionary(Of String, Long) +Debug.Print Err.Number & ": " & Err.Description +Err.Clear + +Debug.Print ages Is Nothing +ages.Add "Ann", 31 +Debug.Print Err.Number +' Output: +' 91: Object variable or With block variable not set +' True +' 91 +``` + +The rest of this page describes the members as the source declares them. **Item** is a function, so a value would be read as `ages.Item("Bob")`. The class has no default member and no enumerator: there is no `ages("Bob")`, a value is set with [**SetItem**](#setitem), and the keys are visited with an index loop over [**Keys**](#keys). The keys are compared with the `=` operator. + +The name `Dictionary` is also the name of the class of the Scripting Runtime; see [Using the package with the Scripting Runtime](.#using-the-package-with-the-scripting-runtime). A program that needs a dictionary can use that class. + +* TOC +{:toc} + +## Properties + +### Count +{: .no_toc } + +The number of keys. + +Syntax: *dictionary*.**Count** **As Long** + +Read-only. + +### Keys +{: .no_toc } + +The keys, in the order in which they were added. + +Syntax: *dictionary*.**Keys** **As List(Of TKey)** + +Read-only. The property returns the list that the dictionary uses, not a copy. Changing the list directly, with **Add**, **Remove** or **Clear**, leaves the values as they were, and then the keys and the values no longer match. + +### Values +{: .no_toc } + +The values, in the order of the keys. + +Syntax: *dictionary*.**Values** **As List(Of TValue)** + +Read-only. The same remark as for [**Keys**](#keys) applies. The value at an index of this list belongs to the key at the same index of **Keys**. + +## Methods + +### Add +{: .no_toc } + +Adds a key and a value. + +Syntax: *dictionary*.**Add** ( *key*, *value* ) + +*key* +: *required* A value of type *TKey*. The key must not be there already. + +*value* +: *required* A value of type *TValue*. + +### Clear +{: .no_toc } + +Removes all keys and values. + +Syntax: *dictionary*.**Clear** + +### ContainsKey +{: .no_toc } + +Tests whether a key is there. + +Syntax: *dictionary*.**ContainsKey** ( *key* ) **As Boolean** + +*key* +: *required* A value of type *TKey*. + +### Item +{: .no_toc } + +Returns the value of a key. + +Syntax: *dictionary*.**Item** ( *key* ) **As TValue** + +*key* +: *required* A value of type *TKey*. The key must be there. [**TryGetValue**](#trygetvalue) does not need that. + +### New +{: .no_toc } + +Creates an empty dictionary. + +Syntax: **New** **Dictionary(Of TKey, TValue)** + +In version 0.0.0.0 it raises run-time error 91: see the start of this page. + +### Remove +{: .no_toc } + +Removes a key and its value. + +Syntax: *dictionary*.**Remove** ( *key* ) **As Boolean** + +*key* +: *required* A value of type *TKey*. + +Returns **True** when the key was there and is removed, and **False** when it was not. The keys after it move down by one place. + +### SetItem +{: .no_toc } + +Sets the value of a key, and adds the key if it is new. + +Syntax: *dictionary*.**SetItem** ( *key*, *value* ) + +*key* +: *required* A value of type *TKey*. + +*value* +: *required* A value of type *TValue*. + +Replaces the value of a key that is there, and keeps its place. A key that is not there goes to the end. + +### TryGetValue +{: .no_toc } + +Reads the value of a key when the key is there. + +Syntax: *dictionary*.**TryGetValue** ( *key*, *value* ) **As Boolean** + +*key* +: *required* A value of type *TKey*. + +*value* +: *required* A variable of type *TValue*, passed **ByRef**. It receives the value when the key is there, and is not changed when it is not. + +Returns **True** when the key was there. + +## See Also + +- [List](List) class -- holds the keys and the values +- [KeyValuePair](KeyValuePair) class -- one key with one value +- [Using the package with the Scripting Runtime](.#using-the-package-with-the-scripting-runtime) -- which **Dictionary** a name means diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md new file mode 100644 index 000000000..a3a240cb6 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FileHelper.md @@ -0,0 +1,371 @@ +--- +title: FileHelper +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/FileHelper +has_toc: false +--- + +# FileHelper module +{: .no_toc } + +Functions that write, append to and read a text file, test whether a file or folder exists, and take paths apart. A program uses them to save a report that a [formatter](Formatters) made. They call the file statements of the runtime library (**Open**, **Put**, **Get**, **Kill**, **MkDir**, **GetAttr**), and need no other library. + +The functions are called with the name of the module, as in `FileHelper.WriteFile(report, path)`. Three of them, **WriteFile**, **AppendFile** and **ReadFile**, return a **Variant** and never raise an error. **WriteFile** and **AppendFile** return **True**, and when something fails, a **String** that describes the failure; a program tells the two apart with **VarType**. **ReadFile** returns a **String** in both cases, the content of the file or the description of the failure, and the description starts with the text `Error `. A file that does not exist is not a failure for **ReadFile** and **GetFileSize**: they create it. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim folder As String = Environ$("TEMP") & "\diag-filehelper" +Dim path As String = folder & "\report.txt" +FileHelper.DeleteFile path + +Debug.Print FileHelper.WriteFile("Hello", path) +Debug.Print FileHelper.GetFileSize(path) +Debug.Print FileHelper.AppendFile(" world", path) +Debug.Print FileHelper.GetFileSize(path) +Debug.Print FileHelper.FileExists(path) +Debug.Print FileHelper.DirectoryExists(folder) +Debug.Print FileHelper.DeleteFile(path) +Debug.Print FileHelper.FileExists(path) +RmDir folder +' Output: +' True +' 8 +' True +' 14 +' True +' True +' True +' False +``` + +The files that **WriteFile** and **AppendFile** write are UTF-8 with a byte-order mark (the bytes `EF BB BF` at the start). The text is converted with **StrConv** and the constant **vbUTF8**. + +* TOC +{:toc} + +## Paths + +A path that is not absolute is taken as relative to the folder of the program, **App.Path**, and not to the current folder. [**GetFullPath**](#getfullpath) makes the full path, and the functions that open, test, read or delete a file call it first. [**EnsureDirectoryExists**](#ensuredirectoryexists) and the functions that take a path apart ([**GetDirectoryName**](#getdirectoryname), [**GetFileName**](#getfilename), [**GetFileNameWithoutExtension**](#getfilenamewithoutextension), [**GetExtension**](#getextension), [**IsAbsolutePath**](#isabsolutepath) and [**NormalizePath**](#normalizepath)) use the text as it is given. + +```tb check_build check_run +Debug.Print FileHelper.GetFullPath("x.txt") = App.Path & "\x.txt" +Debug.Print FileHelper.GetFullPath("sub\..\x.txt") = App.Path & "\x.txt" +Debug.Print FileHelper.GetFullPath("C:\a\..\b\x.txt") +' Output: +' True +' True +' C:\b\x.txt +``` + +## Methods + +### AppendFile +{: .no_toc } + +Adds text to the end of a file. + +Syntax: **FileHelper.AppendFile** ( *content*, *filePath* ) **As Variant** + +*content* +: *required* A **String**. + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Creates the file if it does not exist, and the folder that holds it when only that last folder is missing (see [**EnsureDirectoryExists**](#ensuredirectoryexists)). The file is written in UTF-8. The byte-order mark is written only when the file is empty or new. The content goes after the last byte of the file. + +Returns **True**, or a **String** of the form `Error ` and the number, a colon, the description, and ` (File: ` and the path as the caller gave it, and `)`. The function raises no error. + +### DeleteFile +{: .no_toc } + +Deletes a file. + +Syntax: **FileHelper.DeleteFile** ( *filePath* ) **As Boolean** + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Calls **Kill** and returns **True**. Returns **False** for any error, a file that does not exist included. The path may hold the wildcards `*` and `?` that **Kill** accepts, and then every file that matches is deleted. + +```tb check_build check_run +Dim folder As String = Environ$("TEMP") & "\diag-wild" +FileHelper.EnsureDirectoryExists folder +FileHelper.WriteFile "a", folder & "\a.tmpd" +FileHelper.WriteFile "b", folder & "\b.tmpd" + +Debug.Print FileHelper.DeleteFile(folder & "\*.tmpd") +Debug.Print FileHelper.FileExists(folder & "\a.tmpd") Or FileHelper.FileExists(folder & "\b.tmpd") +Debug.Print FileHelper.DeleteFile(folder & "\a.tmpd") +RmDir folder +' Output: +' True +' False +' False +``` + +### DirectoryExists +{: .no_toc } + +Tests whether a folder exists. + +Syntax: **FileHelper.DirectoryExists** ( *dirPath* ) **As Boolean** + +*dirPath* +: *required* A **String**, the path of the folder, absolute or relative to **App.Path**. + +Returns **True** when **GetAttr** succeeds and reports a folder. A file, or a path that does not exist, gives **False**. + +### EnsureDirectoryExists +{: .no_toc } + +Creates a folder. + +Syntax: **FileHelper.EnsureDirectoryExists** ( *path* ) + +*path* +: *required* A **String**, the path of the folder. + +Removes one trailing backslash and calls **MkDir**. It ignores every error, so a folder that exists is not an error, and neither is a folder that cannot be created. **MkDir** creates the last level only. When the folder above it does not exist, nothing is created and no error is reported: [**WriteFile**](#writefile) then returns the error text of **Open**, which is `Error -2147467259: Unspecified error` and the path. VB6 raises error 76, *Path not found*, for the same **Open** statement. + +```tb check_build check_run +Dim root As String = Environ$("TEMP") & "\diag-new" +Dim result As Variant = FileHelper.WriteFile("x", root & "\sub\a.txt") +Debug.Print VarType(result) = vbString +Debug.Print Left$(result, InStr(result, " (File: ") - 1) +result = FileHelper.AppendFile("x", root & "\sub\a.txt") +Debug.Print Left$(result, InStr(result, " (File: ") - 1) +Debug.Print FileHelper.DirectoryExists(root) +' Output: +' True +' Error -2147467259: Unspecified error +' Error -2147467259: Unspecified error +' False +``` + +### FileExists +{: .no_toc } + +Tests whether a file exists. + +Syntax: **FileHelper.FileExists** ( *filePath* ) **As Boolean** + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Returns **True** when **GetAttr** succeeds and the path is not a folder. A folder, or a path that does not exist, gives **False**. + +### GetDirectoryName +{: .no_toc } + +Returns the folder part of a path. + +Syntax: **FileHelper.GetDirectoryName** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Removes the spaces at both ends and one trailing slash or backslash, and returns the text before the last backslash, or, when there is none, before the last slash. A path with no separator gives an empty string. The root of a drive is returned without its backslash: `C:\a\b.txt` gives `C:\a`, and `C:\a\` and `C:\a.txt` give `C:`. + +### GetExtension +{: .no_toc } + +Returns the extension of a file name. + +Syntax: **FileHelper.GetExtension** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Returns the part of the file name from its last period, with the period: `.gz` for `b.tar.gz`. An empty string when the name has no period. A name that starts with a period, such as `.gitignore`, is all extension. + +### GetFileName +{: .no_toc } + +Returns the file name of a path. + +Syntax: **FileHelper.GetFileName** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Removes the spaces at both ends and returns the text after the last backslash, or, when there is none, after the last slash. A path with no separator is returned as it is. + +### GetFileNameWithoutExtension +{: .no_toc } + +Returns the file name without its extension. + +Syntax: **FileHelper.GetFileNameWithoutExtension** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Returns the file name of *path* up to its last period: `b.tar` for `b.tar.gz`. + +### GetFileSize +{: .no_toc } + +Returns the size of a file in bytes. + +Syntax: **FileHelper.GetFileSize** ( *filePath* ) **As LongLong** + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Opens the file in binary mode and returns the result of **LOF**. Returns -1 when an error occurs, such as a folder that does not exist. The function opens the file with `For Binary`, and that mode creates a file that does not exist, so a missing file in a folder that exists is not an error: it is created with no content and the function returns 0. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim path As String = Environ$("TEMP") & "\diag-missing.bin" +FileHelper.DeleteFile path +Debug.Print FileHelper.FileExists(path) +Debug.Print FileHelper.GetFileSize(path) +Debug.Print FileHelper.FileExists(path) +FileHelper.DeleteFile path +Debug.Print FileHelper.GetFileSize(Environ$("TEMP") & "\diag-no-folder\x.bin") +' Output: +' False +' 0 +' True +' -1 +``` + +### GetFullPath +{: .no_toc } + +Returns the full path for a path. + +Syntax: **FileHelper.GetFullPath** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Removes the spaces at both ends. An empty path becomes `.\`. A path that [**IsAbsolutePath**](#isabsolutepath) rejects gets **App.Path** and a backslash in front. The result is then passed through [**NormalizePath**](#normalizepath). + +### IsAbsolutePath +{: .no_toc } + +Tests whether a path is absolute. + +Syntax: **FileHelper.IsAbsolutePath** ( *path* ) **As Boolean** + +*path* +: *required* A **String**. + +Returns **True** when the second character, after the spaces at both ends are removed, is a colon, as in `C:\a`, or when the path starts with two backslashes, as in `\\server\share`. Any other path gives **False**, including `\a` (a path from the root of the current drive) and `//server/share` (with slashes). + +### NormalizePath +{: .no_toc } + +Cleans up the separators of a path. + +Syntax: **FileHelper.NormalizePath** ( *path* ) **As String** + +*path* +: *required* A **String**. + +Changes every slash to a backslash and every run of backslashes to one. It then goes through the parts between the backslashes: it removes the spaces at both ends of a part, drops an empty part and a part `.`, and lets a part `..` remove the part before it. A `..` with nothing before it is dropped. The parts are joined with a backslash, and the result has no backslash at the start or at the end. + +Because an empty first part is dropped, a path from the root, such as `\a\b`, loses its first backslash, and so does a UNC path: `\\server\share\f.txt` becomes `server\share\f.txt`, which is a relative path. [**GetFullPath**](#getfullpath) treats such a path as absolute and does not add **App.Path**, so a file in a UNC path is opened as the relative path `server\share\f.txt`. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Debug.Print FileHelper.NormalizePath("C:/a//b/./c/../d.txt") +Debug.Print FileHelper.GetDirectoryName("C:\a\b.txt") +Debug.Print FileHelper.GetDirectoryName("C:\a\") +Debug.Print FileHelper.GetFileName("C:\a\b.tar.gz") +Debug.Print FileHelper.GetFileNameWithoutExtension("C:\a\b.tar.gz") +Debug.Print FileHelper.GetExtension("C:\a\b.tar.gz") +Debug.Print FileHelper.IsAbsolutePath("C:\a") & " " & FileHelper.IsAbsolutePath("a\b") +Debug.Print FileHelper.NormalizePath("\\server\share\f.txt") +Debug.Print FileHelper.GetFullPath("\\server\share\f.txt") +' Output: +' C:\a\b\d.txt +' C:\a +' C: +' b.tar.gz +' b.tar +' .gz +' True False +' server\share\f.txt +' server\share\f.txt +``` + +### ReadFile +{: .no_toc } + +Reads a whole file as text. + +Syntax: **FileHelper.ReadFile** ( *filePath* ) **As Variant** + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Returns the content of the file as a **String**, and an empty string for an empty file. When an error occurs, such as a folder that does not exist, it returns an error text in the form that [**AppendFile**](#appendfile) describes. A file that does not exist is not an error: it is created with no content, as for [**GetFileSize**](#getfilesize), and the function returns an empty string, so a missing file and an empty file look the same. See [Known problems](.#known-problems-in-version-0000). + +The function reads the bytes of the file into a string as characters, and does not decode UTF-8 or remove a byte-order mark. A file that [**WriteFile**](#writefile) wrote therefore starts with the three characters of its byte-order mark, and a character above 127 is several characters. + +```tb check_build check_run +Dim path As String = Environ$("TEMP") & "\diag-readfile.txt" +FileHelper.DeleteFile path +FileHelper.WriteFile "caf" & ChrW$(233), path + +Dim text As Variant = FileHelper.ReadFile(path) +Debug.Print VarType(text) = vbString +Debug.Print Len(text) +FileHelper.DeleteFile path + +Debug.Print VarType(FileHelper.ReadFile(path)) = vbString +Debug.Print Len(FileHelper.ReadFile(path)) +Debug.Print FileHelper.FileExists(path) +FileHelper.DeleteFile path +' Output: +' True +' 8 +' True +' 0 +' True +``` + +### WriteFile +{: .no_toc } + +Writes text to a file. + +Syntax: **FileHelper.WriteFile** ( *content*, *filePath* ) **As Variant** + +*content* +: *required* A **String**. + +*filePath* +: *required* A **String**, the path of the file, absolute or relative to **App.Path**. + +Creates the file, and the folder that holds it when only that last folder is missing (see [**EnsureDirectoryExists**](#ensuredirectoryexists)). It writes the byte-order mark and then the text in UTF-8, from the start of the file. Returns **True**, or an error text as [**AppendFile**](#appendfile) describes. + +**WriteFile** opens the file in binary mode and does not shorten it. When the file exists and is longer than the new content, the bytes after the new content stay in the file. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim path As String = Environ$("TEMP") & "\diag-writefile.txt" +FileHelper.DeleteFile path + +FileHelper.WriteFile "0123456789", path +Debug.Print FileHelper.GetFileSize(path) +FileHelper.WriteFile "ab", path +Debug.Print FileHelper.GetFileSize(path) +FileHelper.DeleteFile path + +Debug.Print FileHelper.WriteFile("", path) +Debug.Print FileHelper.GetFileSize(path) +FileHelper.DeleteFile path +' Output: +' 13 +' 13 +' True +' 3 +``` + +An empty string is not an error: the file holds the three bytes of the byte-order mark. + +## See Also + +- [Formatters](Formatters) -- make the text that **WriteFile** saves +- [FormatterFactory](FormatterFactory) module -- **FormatResults** makes the text of a report +- [FormatHelper](FormatHelper) module -- the other helper functions of the package diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md new file mode 100644 index 000000000..929f35c98 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatHelper.md @@ -0,0 +1,293 @@ +--- +title: FormatHelper +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/FormatHelper +has_toc: false +--- + +# FormatHelper module +{: .no_toc } + +Functions that turn numbers, times and text into the strings of a report: sizes in bytes, durations, percentages, dates, padded and shortened text, and text that is escaped for JSON and for HTML. The collectors and the formatters of the package use them, and a program can call them for its own reports. + +The functions are called with the name of the module, as in `FormatHelper.FormatBytes(1536)`. The module has a function named **FormatDateTime**, which the runtime library has too. A call without the module name reaches the function of the runtime library, with one argument as well as with two, and no error is reported for the two names. Write `FormatHelper.FormatDateTime` for the one of the package: + +```tb check_build check_run +Dim d As Date = DateSerial(2026, 3, 15) + TimeSerial(14, 30, 0) +Debug.Print FormatDateTime(d, vbShortTime) +Debug.Print FormatHelper.FormatDateTime(d) +' Output: +' 14:30 +' 2026-03-15 14:30:00 +``` + +```tb check_build check_run +Debug.Print FormatHelper.FormatBytes(512) +Debug.Print FormatHelper.FormatBytes(1536) +Debug.Print FormatHelper.FormatBytes(1073741824, 2) +Debug.Print FormatHelper.FormatUptime(90061000) +Debug.Print FormatHelper.FormatPercent(1, 3, 1) +Debug.Print "[" & FormatHelper.PadLeft("42", 5) & "]" +Debug.Print FormatHelper.Truncate("A long sentence", 10) +' Output: +' 512 B +' 1.5 KB +' 1.00 GB +' 1d 1h 1m 1s +' 33.3% +' [ 42] +' A long ... +``` + +Functions that format a number or a date use the **Format** function of the runtime library. The decimal separator of a number, and the colon of a time, are those of the regional settings of the user: a size is `1.5 KB` in one setting and `1,5 KB` in another. + +* TOC +{:toc} + +## Methods + +### EscapeHtml +{: .no_toc } + +Escapes text for HTML. + +Syntax: **FormatHelper.EscapeHtml** ( *text* ) **As String** + +*text* +: *required* A **String**. + +Replaces `&` with `&`, `<` with `<`, `>` with `>`, `"` with `"` and `'` with `'`, and returns the result. The ampersand is replaced first, so the entities that the other replacements add stay as they are. + +```tb check_build check_run +Debug.Print FormatHelper.EscapeHtml("&") +' Output: +' <a href='x'>&</a> +``` + +### EscapeJson +{: .no_toc } + +Escapes text for a JSON string. + +Syntax: **FormatHelper.EscapeJson** ( *text* ) **As String** + +*text* +: *required* A **String**. + +Replaces the backslash with `\\`, the double quote with `\"`, a carriage return with `\r`, a line feed with `\n` and a tab with `\t`. The result is meant to go between double quotes. No other character is changed: the control characters with the codes 1 to 31 other than these three, which JSON requires to be escaped, are returned as they are. + +```tb check_build check_run +Debug.Print FormatHelper.EscapeJson("a\b" & vbTab & """q""") +Debug.Print Len(FormatHelper.EscapeJson(Chr$(1))) & " " & Asc(FormatHelper.EscapeJson(Chr$(1))) +' Output: +' a\\b\t\"q\" +' 1 1 +``` + +### FormatBytes +{: .no_toc } + +Formats a number of bytes with a unit. + +Syntax: **FormatHelper.FormatBytes** ( *bytes* [ , *decimals* ] ) **As String** + +*bytes* +: *required* A **LongLong**, the number of bytes. + +*decimals* +: *optional* A **Long**, the number of digits after the decimal separator. Default: 1. + +The units are 1024 times larger one after the other, and are written `KB`, `MB`, `GB` and `TB`. The function picks the largest unit that is at most the size, divides, and writes the number with *decimals* digits, a space and the unit: `1.5 KB`. A size below 1024 is written in whole bytes as `512 B`, whatever *decimals* is. A size of 1024 TB or more is shown in TB. The number is made with the **Format** function and a pattern of zeros, so the digits are rounded. A negative size is written in bytes. + +With *decimals* set to 0 the pattern is `0.`, and the decimal separator stays in the text: `4. KB` and not `4 KB`. The **Page Size** item of [**CPUInfoCollector**](Collectors#cpuinfocollector) is written this way. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Debug.Print FormatHelper.FormatBytes(4096, 0) +Debug.Print FormatHelper.FormatBytes(1048576, 0) +Debug.Print FormatHelper.FormatBytes(1536, 3) +Debug.Print FormatHelper.FormatBytes(-2048) +Debug.Print FormatHelper.FormatBytes(0) +' Output: +' 4. KB +' 1. MB +' 1.500 KB +' -2048 B +' 0 B +``` + +### FormatDateTime +{: .no_toc } + +Formats a date and time as text. + +Syntax: **FormatHelper.FormatDateTime** ( *dt* ) **As String** + +*dt* +: *required* A **Date**. + +Returns the form `yyyy-mm-dd hh:nn:ss` with a 24-hour clock, such as `2026-01-04 15:30:00`. + +### FormatDateTimeISO +{: .no_toc } + +Formats a date and time in the ISO 8601 form. + +Syntax: **FormatHelper.FormatDateTimeISO** ( *dt* ) **As String** + +*dt* +: *required* A **Date**. + +Returns the form `yyyy-mm-ddThh:nn:ss`, such as `2026-01-04T15:30:00`. It has no time zone and no fraction of a second. + +```tb check_build check_run +Dim d As Date = DateSerial(2026, 1, 4) + TimeSerial(15, 30, 45) +Debug.Print FormatHelper.FormatDateTime(d) +Debug.Print FormatHelper.FormatDateTimeISO(d) +Debug.Print FormatHelper.FormatTime(d) +' Output: +' 2026-01-04 15:30:45 +' 2026-01-04T15:30:45 +' 15:30:45 +``` + +### FormatPercent +{: .no_toc } + +Formats a share as a percentage. + +Syntax: **FormatHelper.FormatPercent** ( *value*, *total* [ , *decimals* ] ) **As String** + +*value* +: *required* A **Double**, the part. + +*total* +: *required* A **Double**, the whole. + +*decimals* +: *optional* A **Long**, the number of digits after the decimal separator. Default: 0. + +Returns *value* divided by *total*, times 100, and `%`. With *decimals* set to 0 the number is made with **CLng**, which rounds a .5 to the even number. When *total* is 0 the function returns `0%`, also when *decimals* is above 0. + +### FormatPercentValue +{: .no_toc } + +Formats a number that is already a percentage. + +Syntax: **FormatHelper.FormatPercentValue** ( *percent* [ , *decimals* ] ) **As String** + +*percent* +: *required* A **Double**, a number from 0 to 100. + +*decimals* +: *optional* A **Long**, the number of digits after the decimal separator. Default: 0. + +Returns *percent* and `%`, rounded in the same way as [**FormatPercent**](#formatpercent). + +```tb check_build check_run +Debug.Print FormatHelper.FormatPercent(1, 0, 1) +Debug.Print FormatHelper.FormatPercentValue(54.6) +Debug.Print FormatHelper.FormatPercentValue(2.5) +Debug.Print FormatHelper.FormatPercentValue(12.34, 1) +' Output: +' 0% +' 55% +' 2% +' 12.3% +``` + +### FormatTime +{: .no_toc } + +Formats the time of a date. + +Syntax: **FormatHelper.FormatTime** ( *dt* ) **As String** + +*dt* +: *required* A **Date**. + +Returns the form `hh:nn:ss` with a 24-hour clock, such as `15:30:45`. + +### FormatUptime +{: .no_toc } + +Formats a duration in milliseconds as days, hours, minutes and seconds. + +Syntax: **FormatHelper.FormatUptime** ( *milliseconds* ) **As String** + +*milliseconds* +: *required* A **LongLong**, the duration. + +Returns the duration as `3d 12h 45m 32s`. The days are left out while they are 0, the hours while they and the days are 0, and the minutes while all three are 0. The seconds are always there. Parts of a second are dropped. + +```tb check_build check_run +Debug.Print FormatHelper.FormatUptime(0) +Debug.Print FormatHelper.FormatUptime(59999) +Debug.Print FormatHelper.FormatUptime(3600000) +Debug.Print FormatHelper.FormatUptime(302732000) +' Output: +' 0s +' 59s +' 1h 0m 0s +' 3d 12h 5m 32s +``` + +### PadLeft +{: .no_toc } + +Pads text on the left, to a width. + +Syntax: **FormatHelper.PadLeft** ( *text*, *width* [ , *padChar* ] ) **As String** + +*text* +: *required* A **String**. + +*width* +: *required* A **Long**, the length to reach. + +*padChar* +: *optional* A **String**. Its first character repeats. Default: a space. + +Returns *text* with characters added on the left until it is *width* characters long. Text that is already that long, or longer, is returned as it is. + +### PadRight +{: .no_toc } + +Pads text on the right, to a width. + +Syntax: **FormatHelper.PadRight** ( *text*, *width* [ , *padChar* ] ) **As String** + +The arguments are those of [**PadLeft**](#padleft). The characters are added on the right. + +### Truncate +{: .no_toc } + +Shortens text to a length. + +Syntax: **FormatHelper.Truncate** ( *text*, *maxLength* [ , *ellipsis* ] ) **As String** + +*text* +: *required* A **String**. + +*maxLength* +: *required* A **Long**, the greatest length of the result. + +*ellipsis* +: *optional* A **String** that marks the cut. Default: `...`. + +Text of *maxLength* characters or fewer is returned as it is. A longer text is cut so that, with *ellipsis* at the end, it is *maxLength* characters long. When *maxLength* is not above the length of *ellipsis*, the result is the first *maxLength* characters of *ellipsis* alone. + +```tb check_build check_run +Debug.Print FormatHelper.PadRight("42", 5, ".") +Debug.Print FormatHelper.Truncate("abcdef", 3) +Debug.Print FormatHelper.Truncate("abcdef", 5, "~") +' Output: +' 42... +' ... +' abcd~ +``` + +## See Also + +- [Formatters](Formatters) -- use **EscapeJson**, **EscapeHtml** and the date formats +- [Collectors](Collectors) -- use **FormatBytes**, **FormatUptime** and the percentage formats +- [**Format**](../../../Modules/Strings/Format) function -- the function that makes the numbers and dates diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md new file mode 100644 index 000000000..651001087 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/FormatterFactory.md @@ -0,0 +1,134 @@ +--- +title: FormatterFactory +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/FormatterFactory +has_toc: false +--- + +# FormatterFactory module +{: .no_toc } + +Creates a [formatter](Formatters) from a name, and uses a formatter to turn a list of results into text. A formatter is an object that implements [**IOutputFormatter**](IOutputFormatter). The functions are called with the name of the module, as in `FormatterFactory.CreateFormatter("json")`. + +```tb check_build check_run +Dim f As IOutputFormatter +Dim fmtName As Variant +For Each fmtName In Array("text", "json", "html", "xml") + Set f = FormatterFactory.CreateFormatter(CStr(fmtName)) + Debug.Print fmtName & " -> " & f.FormatterName & " " & f.FileExtension +Next +' Output: +' text -> Text .txt +' json -> JSON .json +' html -> HTML .html +' xml -> Text .txt +``` + +The text formatter, which `text`, `txt` and every name that is not known give, raises run-time error 5 when it begins a report in version 0.0.0.0: see [TextFormatter](Formatters#textformatter). The names `json` and `html` work. + +* TOC +{:toc} + +## Methods + +### CreateFormatter +{: .no_toc } + +Creates a formatter from the name of a format. + +Syntax: **FormatterFactory.CreateFormatter** ( *formatName* ) **As** [**IOutputFormatter**](IOutputFormatter) + +*formatName* +: *required* A **String**. The letter case does not matter. `text` and `txt` give a [**TextFormatter**](Formatters#textformatter), `json` a [**JsonFormatter**](Formatters#jsonformatter), and `html` and `htm` an [**HtmlFormatter**](Formatters#htmlformatter). + +Returns a new formatter each time. Any other name, including an empty string, gives a **TextFormatter** without an error. In version 0.0.0.0 that formatter fails later, when a report begins (see [TextFormatter](Formatters#textformatter)), so a name that is not `json` or `html` makes no report. + +### CreateFormatterFromArg +{: .no_toc } + +Creates a formatter from a command-line argument. + +Syntax: **FormatterFactory.CreateFormatterFromArg** ( *arg* ) **As** [**IOutputFormatter**](IOutputFormatter) + +*arg* +: *required* A **String** such as `/json` or `-html`. The function converts it to lower case, removes one leading slash or hyphen, and passes the rest to [**CreateFormatter**](#createformatter). + +The arguments `json` and `/JSON` give a **JsonFormatter**. The argument `--json` has two hyphens and only one is removed, so the function returns a **TextFormatter** for it. + +```tb check_build check_run +Debug.Print FormatterFactory.CreateFormatterFromArg("/JSON").FormatterName +Debug.Print FormatterFactory.CreateFormatterFromArg("-html").FormatterName +Debug.Print FormatterFactory.CreateFormatterFromArg("--json").FormatterName +' Output: +' JSON +' HTML +' Text +``` + +### FormatResults +{: .no_toc } + +Formats a list of results with a formatter. + +Syntax: **FormatterFactory.FormatResults** ( *results*, *formatter* ) **As String** + +*results* +: *required* A `List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))`, such as the list that [**RunAll**](DiagnosticEngine#runall) returns. + +*formatter* +: *required* An object that implements [**IOutputFormatter**](IOutputFormatter). + +Returns the text that the formatter makes. The function calls the formatter in this order: + +1. **BeginFormat**. +2. For each result, in list order: **FormatCategory** with the [**Category**](DiagnosticResult#category) of the result; then **FormatItem** with the key and the value of each item, or, when [**Success**](DiagnosticResult#success) is **False**, one **FormatItem** with the key `Error` and the [**ErrorMessage**](DiagnosticResult#errormessage); then **EndCategory**. +3. **EndFormat**, whose return value is the return value of the function. + +The items of a failed result are not passed to the formatter. The function writes nothing to a file or a screen, and the same formatter can be used again, because its **BeginFormat** starts a new report. *results* or *formatter* set to **Nothing** raises error 91. + +```tb check_build check_run +Dim results As New List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) + +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Memory") +r.AddItem New KeyValuePair(Of String, String)("Answer", "42") +r.SetError "Failed to get memory status" +results.Add r + +Dim html As String +html = FormatterFactory.FormatResults(results, New HtmlFormatter) +Debug.Print InStr(html, "Error") > 0 +Debug.Print InStr(html, "Failed to get memory status") > 0 +Debug.Print InStr(html, "Answer") > 0 +' Output: +' True +' True +' False +``` + +### GetSupportedFormats +{: .no_toc } + +Returns the names of the formats. + +Syntax: **FormatterFactory.GetSupportedFormats** **As String** + +Returns the fixed text `text, json, html`. The short names `txt` and `htm` that [**CreateFormatter**](#createformatter) accepts are not in it. + +## Delegates + +### FormatterFactoryDelegate +{: .no_toc } + +A delegate type for a function that creates a formatter. + +Syntax: **Delegate Function** **FormatterFactoryDelegate** ( ) **As** [**IOutputFormatter**](IOutputFormatter) + +The type is declared in the package's module **DelegatesModule**, with the delegates of the [engine](DiagnosticEngine#delegates). Nothing in the package uses it. A program can use it as the type of a variable that holds a function which creates a formatter. + +## See Also + +- [IOutputFormatter](IOutputFormatter) interface -- the contract of a formatter +- [Formatters](Formatters) -- the text, JSON and HTML formatters +- [DiagnosticEngine](DiagnosticEngine) class -- makes the list of results +- [FileHelper](FileHelper) module -- writes the text to a file diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md new file mode 100644 index 000000000..0eb5a684c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/Formatters.md @@ -0,0 +1,183 @@ +--- +title: TBMANLIB_Diagnostic Formatters +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/Formatters +has_toc: false +--- + +# Formatters +{: .no_toc } + +The package has three formatters. Each one turns a list of diagnostic results into one string, in a format for reading (text), for programs (JSON) or for a browser (HTML). The text formatter fails in version 0.0.0.0, so a report is made with the JSON or the HTML formatter. They implement [**IOutputFormatter**](IOutputFormatter), so [**FormatterFactory.FormatResults**](FormatterFactory#formatresults) runs them in the same way, and [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) creates one from a name. + +| Class | Name | Extension | Output | +|:------|:-----|:----------|:-------| +| [**TextFormatter**](#textformatter) | `Text` | `.txt` | Meant to be a report in lines of 50 characters, with a box round the title and a frame round each group. In version 0.0.0.0 it raises error 5 and makes no report. | +| [**JsonFormatter**](#jsonformatter) | `JSON` | `.json` | One JSON object, with an object for each group, and every value a string. | +| [**HtmlFormatter**](#htmlformatter) | `HTML` | `.html` | A complete HTML document with its style sheet, and a table for each group. | + +The formatters have these properties in common: + +- They only build a string. They do not write a file or a screen. [**FileHelper**](FileHelper) writes the string to a file. +- The line breaks are **vbCrLf**. +- One object can make any number of reports, because **BeginFormat** discards what the last report left in it. +- Each report names the tool as `Twinbasic Diagnostic Tool v1.0`, or, in JSON, as `"toolVersion": "1.0"`. The text is fixed in the code, and is not the version of the package. +- The classes declare the members of the interface as **Public**, so a variable of the class type reaches them by the long names, such as `IOutputFormatter_FormatItem`. + +```tb check_build check_run +Dim f As IOutputFormatter +Set f = New JsonFormatter +f.BeginFormat +f.FormatCategory "CPU Info" +f.FormatItem "Processor Count", "8" +f.FormatItem "Path", "C:\Tools ""x""" +f.EndCategory + +Dim lines() As String +Dim i As Long +lines = Split(f.EndFormat(), vbCrLf) +For i = 3 To UBound(lines) - 1 + Debug.Print lines(i) +Next i +' Output: +' "toolVersion": "1.0", +' "categories": { +' "cPUInfo": { +' "processorCount": "8", +' "path": "C:\\Tools \"x\"" +' } +' } +' } +' } +``` + +* TOC +{:toc} + +## TextFormatter +{: .no_toc } + +Meant to write a report as text, in lines that are 50 characters wide, with a double-line box round the title and a frame round each group, drawn with the box-drawing characters of Unicode. **FormatterName** is `Text` and **FileExtension** is `.txt`. + +**In version 0.0.0.0 the formatter cannot make a report.** [**BeginFormat**](IOutputFormatter#beginformat) raises run-time error 5, *Invalid procedure call or argument*. The class builds the box with the **Chr$** function and the code points 9556 (`╔`), 9552 (`═`) and others above 255, and [**Chr$**](../../../Modules/Strings/Chr) accepts only 0 to 255. **ChrW$** accepts the code points. The error is the same in a project that sets `runtime.useUnicodeStandardLibrary` to **False**, in a 32-bit program and in a 64-bit program. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim f As IOutputFormatter +Set f = New TextFormatter +On Error Resume Next +f.BeginFormat +Debug.Print Err.Number & ": " & Err.Description +Err.Clear + +Dim s As String +s = Chr$(9556) +Debug.Print Err.Number +Err.Clear +s = ChrW$(9556) +Debug.Print Err.Number & " " & AscW(s) +' Output: +' 5: Invalid procedure call or argument +' 5 +' 0 9556 +``` + +Because [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) gives a **TextFormatter** for `text`, for `txt` and for every name it does not know, a report made with one of those names fails in the same way. Use `json` or `html`. + +The source builds the report from a title in a box, one block for each group (a header line, a line for each item as `│`, two spaces, the key, a colon, a space and the value, and a closing line), and a last line `Generated: ` and the time as `yyyy-mm-dd hh:nn:ss`. A program that wants a text report can write its own formatter: see [**IOutputFormatter**](IOutputFormatter). + +## JsonFormatter +{: .no_toc } + +Writes a report as one JSON object. **FormatterName** is `JSON` and **FileExtension** is `.json`. + +```json +{ + "diagnosticReport": { + "generatedAt": "2026-01-04T15:30:00", + "toolVersion": "1.0", + "categories": { + "cPUInfo": { + "processorCount": "8", + "path": "C:\\Tools \"x\"" + } + } + } +} +``` + +- `generatedAt` is the local time at which **BeginFormat** was called, in the form `yyyy-mm-ddThh:nn:ss`, without a time zone. +- Each group is a member of `categories`, and each item is a member of its group. The name of the group and the key of the item are changed into camel case, as described below, and **every value is a string**, the numbers too: `"processorCount": "8"`. +- A failed result has one member, `error`, with the message. +- A group with no items is an empty object. + +The camel-case rule changes the first character to lower case. The characters space, underscore, hyphen and colon are removed, and the character after each of them is changed to upper case. All the other characters stay as they are. The rule does not look at the rest of a word, so a name in capital letters keeps them: + +| In the report | In the JSON | +|:--------------|:------------| +| `Operating System` | `operatingSystem` | +| `Processor Count` | `processorCount` | +| `CPU Info` | `cPUInfo` | +| `C: Total` | `cTotal` | +| `PID 4120` | `pID4120` | +| `PATH` | `pATH` | +| `PROGRAMFILES(X86)` | `pROGRAMFILES(X86)` | +| `.NET Framework 4.x` | `.NETFramework4.x` | +| `VC++ Redist (x64)` | `vC++Redist(x64)` | +| `IPAddress0` | `iPAddress0` | + +Two keys that give the same name appear twice in the group; the formatter does not check. The names, the keys and the values are escaped for backslash, double quote, carriage return, line feed and tab, with [**EscapeJson**](FormatHelper#escapejson). Other control characters (code 1 to 31, except the three) are written as they are, and a value that holds one is not valid JSON. [**EscapeJson**](FormatHelper#escapejson) returns `Chr$(1)` as the one character it was given. + +```tb check_build check_run +Dim f As IOutputFormatter +Set f = New JsonFormatter +f.BeginFormat +f.FormatCategory "Operating System" +f.FormatItem "Computer Name", "PC-01" +f.FormatCategory "Disk" +f.FormatItem "C: Total", "465.3 GB" +Dim json As String = f.EndFormat() + +Debug.Print InStr(json, """operatingSystem"": {") > 0 +Debug.Print InStr(json, """computerName"": ""PC-01""") > 0 +Debug.Print InStr(json, """cTotal"": ""465.3 GB""") > 0 +' Output: +' True +' True +' True +``` + +## HtmlFormatter +{: .no_toc } + +Writes a report as a complete HTML document. **FormatterName** is `HTML` and **FileExtension** is `.html`. + +The document declares ``, the language `en` and the character set UTF-8. Its title and its first heading are `Twinbasic Diagnostic Report`, and a paragraph under it has the text `Generated: ` and the time at which **BeginFormat** was called, as `yyyy-mm-dd hh:nn:ss`. Each group is a `section` with the class `category`, an `h2` with the title, and a table of rows. A row has two cells, `td` with the class `key` and `td` with the class `value`. The document ends with a footer that names the tool. + +The style sheet is in the document. It sets a light and a dark colour scheme, which the browser selects with `prefers-color-scheme`, a layout that is at most 900 pixels wide, and a print style. The document has no script and loads nothing from another address. + +The category name, the keys and the values are escaped with [**EscapeHtml**](FormatHelper#escapehtml), which replaces `&`, `<`, `>`, `"` and `'`. A file that is saved from the report should be in UTF-8, which is the character set that the document declares. + +```tb check_build check_run +Dim f As IOutputFormatter +Set f = New HtmlFormatter +f.BeginFormat +f.FormatCategory "Environment" +f.FormatItem "PATH", "C:\Tools & " +f.EndCategory + +Dim html As String = f.EndFormat() +Debug.Print Left$(html, 15) +Debug.Print InStr(html, "

Environment

") > 0 +Debug.Print InStr(html, "C:\Tools & <x>") > 0 +' Output: +' +' True +' True +``` + +## See Also + +- [IOutputFormatter](IOutputFormatter) interface -- the contract that all three implement +- [FormatterFactory](FormatterFactory) module -- creates a formatter by name and runs it +- [FormatHelper](FormatHelper) module -- **EscapeJson**, **EscapeHtml** and the date formats +- [FileHelper](FileHelper) module -- saves a report as a UTF-8 file diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/IDiagnosticCollector.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/IDiagnosticCollector.md new file mode 100644 index 000000000..c424bdc88 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/IDiagnosticCollector.md @@ -0,0 +1,155 @@ +--- +title: IDiagnosticCollector +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/IDiagnosticCollector +has_toc: false +--- + +# IDiagnosticCollector interface +{: .no_toc } + +The contract of a collector: an object that finds one kind of information, such as the memory or the disks, and returns it as a [**DiagnosticResult**](DiagnosticResult). A [**DiagnosticEngine**](DiagnosticEngine) runs objects of this type. The eleven classes of [Collectors](Collectors) implement it, and a class of a program can implement it too. + +The interface has five properties that describe the collector and two methods that collect. + +```tb check_build slot=file projname=diag-collector +Class ToolCollector + Implements IDiagnosticCollector + + Private Property Get IDiagnosticCollector_CategoryName() As String + Return "Tool" + End Property + + Private Property Get IDiagnosticCollector_Description() As String + Return "Fixed values of the program" + End Property + + Private Property Get IDiagnosticCollector_RefreshMode() As RefreshMode + Return RefreshMode.Static + End Property + + Private Property Get IDiagnosticCollector_RequiresElevation() As Boolean + Return False + End Property + + Private Property Get IDiagnosticCollector_SupportsSummary() As Boolean + Return True + End Property + + Private Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String)) + Dim result As DiagnosticResult(Of KeyValuePair(Of String, String)) + Set result = New DiagnosticResult(Of KeyValuePair(Of String, String))("Tool") + result.AddItem New KeyValuePair(Of String, String)("Name", "Sample") + result.AddItem New KeyValuePair(Of String, String)("Version", "1.2") + Return result + End Function + + Private Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String)) + Dim result As DiagnosticResult(Of KeyValuePair(Of String, String)) + Set result = New DiagnosticResult(Of KeyValuePair(Of String, String))("Tool") + result.IsSummary = True + result.AddItem New KeyValuePair(Of String, String)("Version", "1.2") + Return result + End Function +End Class +``` + +```tb check_build check_run projname=diag-collector +Dim engine As New DiagnosticEngine +engine.RegisterCollector New ToolCollector + +Dim full As DiagnosticResult(Of KeyValuePair(Of String, String)) +Dim brief As DiagnosticResult(Of KeyValuePair(Of String, String)) +Set full = engine.RunCategory("Tool") +Set brief = engine.RunCategory("Tool", True) + +Debug.Print full.ItemCount & " " & full.IsSummary +Debug.Print brief.ItemCount & " " & brief.IsSummary +' Output: +' 2 False +' 1 True +``` + +A collector can be run on its own, without an engine; see [Calling a collector directly](Collectors#calling-a-collector-directly). + +* TOC +{:toc} + +## What the engine reads + +The engine reads **CategoryName**, **RefreshMode**, **SupportsSummary**, **Collect** and **CollectSummary**. It does not read **Description** or **RequiresElevation**; they are there for the program. The category name of a collector should be the same text as the **Category** of the result that it returns, because a formatter prints the **Category** of the result, and the engine finds a collector by its **CategoryName**. The engine does not check that they agree. + +## Properties + +### CategoryName +{: .no_toc } + +The name that identifies the collector. + +Syntax: *collector*.**CategoryName** **As String** + +Read-only. The engine refuses two collectors with the same name, finds a collector by it in [**RunCategory**](DiagnosticEngine#runcategory) and [**UnregisterCollector**](DiagnosticEngine#unregistercollector), and passes it to the events. The built-in collectors return fixed names such as `Operating System` and `CPU Info`; see [Collectors](Collectors). + +### Description +{: .no_toc } + +A sentence that says what the collector collects. + +Syntax: *collector*.**Description** **As String** + +Read-only. Nothing in the package reads it. + +### RefreshMode +{: .no_toc } + +How often the data of the collector changes. + +Syntax: *collector*.**RefreshMode** **As** [**RefreshMode**](RefreshMode) + +Read-only. [**RunByRefreshMode**](DiagnosticEngine#runbyrefreshmode) selects the collectors by it. + +### RequiresElevation +{: .no_toc } + +Whether the collector needs administrator rights. + +Syntax: *collector*.**RequiresElevation** **As Boolean** + +Read-only. The engine does not read it, and does not skip a collector that returns **True**. All the collectors of the package return **False**. A program can compare it with [**IsElevated**](DiagnosticEngine#iselevated). + +### SupportsSummary +{: .no_toc } + +Whether the collector has a short form of its data. + +Syntax: *collector*.**SupportsSummary** **As Boolean** + +Read-only. The engine asks a collector for **CollectSummary** only when this is **True** and the run is a summary run. A collector that returns **False** still has to implement **CollectSummary**; the collectors of the package return the result of **Collect** from it. + +## Methods + +### Collect +{: .no_toc } + +Collects the data and returns it. + +Syntax: *collector*.**Collect** **As DiagnosticResult(Of KeyValuePair(Of String, String))** + +Returns a result that holds the items in the order of the report. A collector reports a failure by [**SetError**](DiagnosticResult#seterror) on the result it returns, or by raising a run-time error, which the engine turns into the same kind of result, see [A collector that raises an error](DiagnosticEngine#a-collector-that-raises-an-error). It returns a result, never **Nothing**: the engine reads the result's **Success** and **ItemCount**, and a **Nothing** makes [**RunAll**](DiagnosticEngine#runall) raise run-time error 91 out of the engine. The run stops at that collector, and [**IsRunning**](DiagnosticEngine#isrunning) stays **True**. + +### CollectSummary +{: .no_toc } + +Collects the short form of the data. + +Syntax: *collector*.**CollectSummary** **As DiagnosticResult(Of KeyValuePair(Of String, String))** + +Returns a result with the most important items only. A collector that supports it sets [**IsSummary**](DiagnosticResult#issummary) to **True** on the result. + +## See Also + +- [DiagnosticEngine](DiagnosticEngine) class -- registers and runs collectors +- [DiagnosticResult](DiagnosticResult) class -- what a collector returns +- [RefreshMode](RefreshMode) enumeration -- the type of **RefreshMode** +- [Collectors](Collectors) -- the eleven collectors of the package +- [**Implements**](../../../Core/Implements) statement -- how a class takes on an interface diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/IOutputFormatter.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/IOutputFormatter.md new file mode 100644 index 000000000..ff91eb405 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/IOutputFormatter.md @@ -0,0 +1,161 @@ +--- +title: IOutputFormatter +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/IOutputFormatter +has_toc: false +--- + +# IOutputFormatter interface +{: .no_toc } + +The contract of a formatter: an object that turns a list of results into one string, group by group and item by item. [**FormatterFactory.FormatResults**](FormatterFactory#formatresults) calls a formatter in the order the interface suggests. The package has three formatters, for [text, JSON and HTML](Formatters), and a class of a program can implement the interface for another format. + +A formatter only converts. It does not write to a file or a screen, and it does not read the results itself: it is given the category names, the keys and the values as text, and returns the text of the whole report from its last method. + +```tb check_build slot=file projname=diag-formatter +Class CsvFormatter + Implements IOutputFormatter + + Private m_Text As String + Private m_Category As String + + Private Sub IOutputFormatter_BeginFormat() + m_Text = "category,key,value" & vbLf + End Sub + + Private Sub IOutputFormatter_FormatCategory(ByVal categoryName As String) + m_Category = categoryName + End Sub + + Private Sub IOutputFormatter_FormatItem(ByVal key As String, ByVal value As String) + m_Text = m_Text & m_Category & "," & key & "," & value & vbLf + End Sub + + Private Sub IOutputFormatter_EndCategory() + m_Category = "" + End Sub + + Private Function IOutputFormatter_EndFormat() As String + Return m_Text + End Function + + Private Property Get IOutputFormatter_FileExtension() As String + Return ".csv" + End Property + + Private Property Get IOutputFormatter_FormatterName() As String + Return "CSV" + End Property +End Class +``` + +```tb check_build check_run projname=diag-formatter +Dim results As New List(Of DiagnosticResult(Of KeyValuePair(Of String, String))) +Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) + +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Memory") +r.AddItem New KeyValuePair(Of String, String)("Memory Load", "54%") +r.AddItem New KeyValuePair(Of String, String)("Used Physical", "8 GB") +results.Add r + +Set r = New DiagnosticResult(Of KeyValuePair(Of String, String))("Uptime") +r.AddItem New KeyValuePair(Of String, String)("Total Days", "3") +results.Add r + +Dim lines() As String +Dim i As Long +lines = Split(FormatterFactory.FormatResults(results, New CsvFormatter), vbLf) +For i = 0 To UBound(lines) - 1 + Debug.Print lines(i) +Next i +' Output: +' category,key,value +' Memory,Memory Load,54% +' Memory,Used Physical,8 GB +' Uptime,Total Days,3 +``` + +* TOC +{:toc} + +## Order of the calls + +**BeginFormat** is called once, first. Then each group of items is one call of **FormatCategory**, one call of **FormatItem** for each item, and one call of **EndCategory**. **EndFormat** comes last and returns the text. The three formatters of the package also accept a call of **FormatCategory** or **EndFormat** while a group is still open, and close the group first, so a program that calls them directly may leave out **EndCategory**. A formatter should start a new report in **BeginFormat**, because one object is used for many reports. + +## Properties + +### FileExtension +{: .no_toc } + +The file extension for a report in this format. + +Syntax: *formatter*.**FileExtension** **As String** + +Read-only. It includes the period: `.txt`, `.json` and `.html` for the formatters of the package. A program uses it to name the file it writes. + +### FormatterName +{: .no_toc } + +The name of the format. + +Syntax: *formatter*.**FormatterName** **As String** + +Read-only. `Text`, `JSON` and `HTML` for the formatters of the package. Nothing in the package reads it. + +## Methods + +### BeginFormat +{: .no_toc } + +Starts a report. + +Syntax: *formatter*.**BeginFormat** + +The formatters of the package discard what an earlier report left in them, and write the start of the document. + +### EndCategory +{: .no_toc } + +Ends the group that **FormatCategory** started. + +Syntax: *formatter*.**EndCategory** + +The formatters of the package do nothing when no group is open. + +### EndFormat +{: .no_toc } + +Ends the report and returns it. + +Syntax: *formatter*.**EndFormat** **As String** + +Returns the whole text of the report, ending any group that is open. The line breaks in the text of the package's formatters are **vbCrLf**. + +### FormatCategory +{: .no_toc } + +Starts a group of items. + +Syntax: *formatter*.**FormatCategory** ( *categoryName* ) + +*categoryName* +: *required* A **String**, the title of the group. [**FormatResults**](FormatterFactory#formatresults) passes the **Category** of a result. + +### FormatItem +{: .no_toc } + +Adds one item to the group that is open. + +Syntax: *formatter*.**FormatItem** ( *key*, *value* ) + +*key* +: *required* A **String**, the label of the line. + +*value* +: *required* A **String**, the text of the line. + +## See Also + +- [Formatters](Formatters) -- the three formatters of the package +- [FormatterFactory](FormatterFactory) module -- creates a formatter and runs it over a list of results +- [DiagnosticResult](DiagnosticResult) class -- the source of the categories, keys and values diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/KeyValuePair.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/KeyValuePair.md new file mode 100644 index 000000000..42edccfb9 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/KeyValuePair.md @@ -0,0 +1,81 @@ +--- +title: KeyValuePair +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/KeyValuePair +has_toc: false +--- + +# KeyValuePair class +{: .no_toc } + +A **KeyValuePair** holds one key and one value. It is a generic class, **KeyValuePair(Of TKey, TValue)**. Every item of a diagnostic result is a `KeyValuePair(Of String, String)`: the key is the label of a line of the report, such as `Total Physical`, and the value is its text, such as `15.9 GB`. + +```tb check_build check_run +Dim kv As New KeyValuePair(Of String, Long) +kv.Key = "answer" +kv.Value = 42 +Debug.Print kv.ToString() + +Dim item As KeyValuePair(Of String, String) +Set item = New KeyValuePair(Of String, String)("Host Name", "PC-01") +Debug.Print item.Key & " / " & item.Value +Debug.Print item.ToString() +' Output: +' [answer: 42] +' Host Name / PC-01 +' [Host Name: PC-01] +``` + +* TOC +{:toc} + +## Properties + +### Key +{: .no_toc } + +The key. + +Syntax: *pair*.**Key** [ **=** *value* ] + +A value of type *TKey*. For a pair made without arguments it is the default of the type: an empty string, 0 or **Nothing**. + +### Value +{: .no_toc } + +The value. + +Syntax: *pair*.**Value** [ **=** *value* ] + +A value of type *TValue*. For a pair made without arguments it is the default of the type: an empty string, 0 or **Nothing**. + +## Methods + +### New +{: .no_toc } + +Creates a pair. + +Syntax: **New** **KeyValuePair(Of TKey, TValue)** [ ( *key*, *value* ) ] + +*key* +: *optional* The first value, for [**Key**](#key). + +*value* +: *optional* The second value, for [**Value**](#value). + +Both arguments are given together or not at all. Without them, the key and the value have the default of their types. + +### ToString +{: .no_toc } + +Returns the pair as text. + +Syntax: *pair*.**ToString** **As String** + +Returns `[`, the key, `: `, the value and `]`, each converted with **CStr**. The conversion raises an error for an object that cannot be converted to a string. + +## See Also + +- [DiagnosticResult](DiagnosticResult) class -- holds a list of pairs +- [Dictionary](Dictionary) class -- a collection of keys and values with look-up by key diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md new file mode 100644 index 000000000..87b10196e --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md @@ -0,0 +1,300 @@ +--- +title: List +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/List +has_toc: false +--- + +# List class +{: .no_toc } + +A list of values of one type, in the order in which they were added. It is a generic class, **List(Of T)**, and the index of its first item is 0. The engine uses it for the collectors and the results, a result uses it for its items, and a program can use it for any type. The class keeps its items in a **Collection** of the runtime library. + +```tb check_build check_run +Dim names As New List(Of String) +names.Add "Ann" +names.Add "Bob" +names.Add "Cy" +Debug.Print names.Count & " " & names.Item(0) & " " & names.IndexOf("Bob") + +names.RemoveAt 1 +Debug.Print names.Count & " " & names.Item(1) +Debug.Print names.Contains("Bob") +' Output: +' 3 Ann 1 +' 2 Cy +' False +``` + +**Item** is a function, so an item is read as `names.Item(0)`. The class has no default member and no enumerator: there is no `names(0)`, and the items are visited with an index loop from 0 to **Count** - 1. + +**Insert**, and **SetItem** with the index 0, put the item in the wrong place in version 0.0.0.0, and **SetItem** can lose an item. Both pass the position as the fourth argument of **Collection.Add**, which is *after*, where the code needs *before*. [**Insert**](#insert) puts the new item one place too late, and [**SetItem**](#setitem) with the index 0 puts it in the second place. See [Known problems](.#known-problems-in-version-0000). + +```tb check_build check_run +Dim text As String +Dim i As Long + +Dim a As New List(Of String) +a.Add "a" +a.Add "b" +a.Add "c" +a.Insert 1, "X" +For i = 0 To a.Count - 1 + text = text & a.Item(i) +Next i +Debug.Print text + +Dim b As New List(Of String) +b.Add "a" +b.Add "b" +b.Add "c" +b.Insert 0, "Y" +text = "" +For i = 0 To b.Count - 1 + text = text & b.Item(i) +Next i +Debug.Print text + +Dim c As New List(Of String) +c.Add "a" +c.Add "b" +c.Add "c" +c.SetItem 0, "Z" +text = "" +For i = 0 To c.Count - 1 + text = text & c.Item(i) +Next i +Debug.Print text +' Output: +' abXc +' aYbc +' bZc +``` + +The same position is an error when the list has too few items. **Insert** with the index 0 on an empty list raises run-time error 5, *Invalid procedure call or argument*. **SetItem** with the index 0 on a list of one item removes the item and then raises error 5, so the list is left empty and the item is lost: + +```tb check_build check_run +Dim one As New List(Of String) +one.Add "a" +On Error Resume Next +one.SetItem 0, "Z" +Debug.Print Err.Number & ": " & Err.Description +Err.Clear +Debug.Print one.Count + +Dim blank As New List(Of String) +blank.Insert 0, "Y" +Debug.Print Err.Number & ": " & Err.Description +Debug.Print blank.Count +' Output: +' 5: Automation error +' 0 +' 5: Invalid procedure call or argument +' 0 +``` + +* TOC +{:toc} + +## Properties + +### Capacity +{: .no_toc } + +The capacity of the list. + +Syntax: *list*.**Capacity** **As Long** + +Read-only. It is the same as [**Count**](#count): the **Collection** under the list has no capacity to set. + +### Count +{: .no_toc } + +The number of items. + +Syntax: *list*.**Count** **As Long** + +Read-only. + +## Methods + +### Add +{: .no_toc } + +Adds an item at the end. + +Syntax: *list*.**Add** ( *item* ) + +*item* +: *required* A value of type *T*. + +### Clear +{: .no_toc } + +Removes all items. + +Syntax: *list*.**Clear** + +### Contains +{: .no_toc } + +Tests whether the list holds a value. + +Syntax: *list*.**Contains** ( *item* ) **As Boolean** + +*item* +: *required* A value of type *T*. + +Returns **True** when an item is equal to *item*. The items are compared with the `=` operator, so the comparison is by value for text and numbers. For objects the operator compares what an object offers by default, and a class that has no default member makes it raise run-time error 438, *Object doesn't support this property or method*, as soon as the list holds an item to compare. **IndexOf** and **Remove**, which calls **IndexOf**, raise the same error. **Item**, **Add** and **RemoveAt** work with objects. + +```tb check_build slot=file projname=diag-list +Class Person + Public Name As String +End Class +``` + +```tb check_build check_run projname=diag-list +Dim people As New List(Of Person) +Dim ann As New Person +ann.Name = "Ann" +people.Add ann +Debug.Print people.Count & " " & people.Item(0).Name + +On Error Resume Next +Debug.Print people.Contains(ann) +Debug.Print Err.Number & ": " & Err.Description +Err.Clear +Debug.Print people.IndexOf(ann) +Debug.Print Err.Number +Err.Clear +Debug.Print people.Remove(ann) +Debug.Print Err.Number & " " & people.Count +' Output: +' 1 Ann +' 438: Object doesn't support this property or method +' 438 +' 438 1 +``` + +### IndexOf +{: .no_toc } + +Returns the position of a value. + +Syntax: *list*.**IndexOf** ( *item* ) **As Long** + +*item* +: *required* A value of type *T*. + +Returns the index of the first item that is equal to *item*, compared as in [**Contains**](#contains), or -1 when there is none. + +### Insert +{: .no_toc } + +Inserts an item at a position. + +Syntax: *list*.**Insert** ( *index*, *item* ) + +*index* +: *required* A **Long**, the position of the new item, from 0 to [**Count**](#count). With **Count** the item goes at the end. + +*item* +: *required* A value of type *T*. + +Raises error 9 with the source `List(Of T).Insert` and the text `Index out of range: ` and the index, when *index* is below 0 or above **Count**. See the start of the page for the position of the new item and for the empty list. + +### Item +{: .no_toc } + +Returns the item at a position. + +Syntax: *list*.**Item** ( *index* ) **As T** + +*index* +: *required* A **Long**, from 0 to [**Count**](#count) - 1. + +Raises error 9 with the source `List(Of T).Item` and the text `Index out of range: ` and the index, when *index* is outside the list. + +### New +{: .no_toc } + +Creates a list. + +Syntax: **New** **List(Of T)** [ ( *initialCapacity* ) ] + +*initialCapacity* +: *optional* A **Long**. It is accepted and not used. + +### Remove +{: .no_toc } + +Removes the first item with a value. + +Syntax: *list*.**Remove** ( *item* ) **As Boolean** + +*item* +: *required* A value of type *T*. + +Returns **True** when an item was found, with [**IndexOf**](#indexof), and removed. Returns **False** when none is equal to *item*. + +### RemoveAt +{: .no_toc } + +Removes the item at a position. + +Syntax: *list*.**RemoveAt** ( *index* ) + +*index* +: *required* A **Long**, from 0 to [**Count**](#count) - 1. + +The items after it move down by one place. Raises error 9 with the source `List(Of T).RemoveAt` and the text `Index out of range: ` and the index, when *index* is outside the list. + +### SetItem +{: .no_toc } + +Replaces the item at a position. + +Syntax: *list*.**SetItem** ( *index*, *item* ) + +*index* +: *required* A **Long**, from 0 to [**Count**](#count) - 1. + +*item* +: *required* A value of type *T*. + +Removes the item at *index* and adds *item* in its place. Raises error 9 with the source `List(Of T).SetItem` and the text `Index out of range: ` and the index, when *index* is outside the list. See the start of the page for the case of index 0. + +### ToArray +{: .no_toc } + +Returns the items as an array. + +Syntax: *list*.**ToArray** **As Variant()** + +Returns a **Variant** array whose first element has the index 0. For an empty list it returns an array that has no bounds, and **UBound** and **LBound** raise error 9 for it. + +```tb check_build check_run +Dim names As New List(Of String) +Dim items() As Variant +items = names.ToArray() +On Error Resume Next +Debug.Print UBound(items) +Debug.Print Err.Number +Err.Clear + +names.Add "a" +names.Add "b" +names.Add "c" +items = names.ToArray() +Debug.Print LBound(items) & " " & UBound(items) & " " & items(2) +' Output: +' 9 +' 0 2 c +``` + +## See Also + +- [Dictionary](Dictionary) class -- keys and values, built on two lists +- [DiagnosticResult](DiagnosticResult) class -- holds its items in a list +- [DiagnosticEngine](DiagnosticEngine) class -- holds its collectors in a list +- [Collection](../../../Modules/Collection/) class -- the class that the list keeps its items in diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/RefreshMode.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/RefreshMode.md new file mode 100644 index 000000000..1654b6479 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/RefreshMode.md @@ -0,0 +1,51 @@ +--- +title: RefreshMode +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/RefreshMode +has_toc: false +--- + +# RefreshMode enumeration +{: .no_toc } + +Says how often the data of a collector changes. A collector returns one member from its [**RefreshMode**](IDiagnosticCollector#refreshmode) property, and [**RunByRefreshMode**](DiagnosticEngine#runbyrefreshmode) runs the collectors that return one member. The enumeration is declared in a module of the package, **RefreshModeModule**, which has no page of its own; the members are written with the name of the enumeration, as in `RefreshMode.Static`. + +```tb check_build check_run +Dim c As IDiagnosticCollector +Set c = New DiskInfoCollector + +Select Case c.RefreshMode + Case RefreshMode.Static + Debug.Print "collected once" + Case RefreshMode.Dynamic + Debug.Print "changes all the time" + Case RefreshMode.SemiDynamic + Debug.Print "changes slowly" +End Select +Debug.Print CLng(RefreshMode.Static) & " " & CLng(RefreshMode.Dynamic) & " " & CLng(RefreshMode.SemiDynamic) +' Output: +' changes slowly +' 0 1 2 +``` + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **Static** | 0 | The data does not change while the program runs, such as the version of the operating system or the model of the processor. | +| **Dynamic** | 1 | The data changes all the time and is read again for each report, such as the load of the processor or the memory in use. | +| **SemiDynamic** | 2 | The data changes slowly, such as the free space of a disk or the network configuration. A program reads it again when it wants to. | + +The mode is a label. The package does not keep the result of a **Static** collector or skip it in a later run, and it does not schedule anything: [**RunAll**](DiagnosticEngine#runall) runs every collector each time it is called. A program that reads the **Static** data once and the **Dynamic** data every second calls **RunByRefreshMode** for each mode, or keeps the results of the first call itself. + +The collectors of the package have these modes: + +| Mode | Collectors | +|:-----|:-----------| +| **Static** | [**OSInfoCollector**](Collectors#osinfocollector), [**CPUInfoCollector**](Collectors#cpuinfocollector), [**LocaleCollector**](Collectors#localecollector), [**EnvironmentCollector**](Collectors#environmentcollector), [**RuntimesCollector**](Collectors#runtimescollector) | +| **Dynamic** | [**CPULoadCollector**](Collectors#cpuloadcollector), [**MemoryInfoCollector**](Collectors#memoryinfocollector), [**UptimeCollector**](Collectors#uptimecollector), [**ProcessInfoCollector**](Collectors#processinfocollector) | +| **SemiDynamic** | [**DiskInfoCollector**](Collectors#diskinfocollector), [**NetworkInfoCollector**](Collectors#networkinfocollector) | + +## See Also + +- [IDiagnosticCollector](IDiagnosticCollector) interface -- the **RefreshMode** property +- [DiagnosticEngine](DiagnosticEngine) class -- **RunByRefreshMode** +- [Collectors](Collectors) -- the mode of each collector of the package diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/StringBuilder.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/StringBuilder.md new file mode 100644 index 000000000..2d58c83ef --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/StringBuilder.md @@ -0,0 +1,175 @@ +--- +title: StringBuilder +parent: TBMANLIB_Diagnostic Package +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/StringBuilder +has_toc: false +--- + +# StringBuilder class +{: .no_toc } + +Builds a long string from many parts. The text is kept in a buffer that grows as needed, so appending does not copy the whole string each time. The three [formatters](Formatters) build their reports with it. Most of its methods return the builder, so the calls can be chained. + +```tb check_build check_run +Dim sb As New StringBuilder +sb.Append("Name: ").Append("Ann").AppendLine +sb.AppendFormat "{0} is {1}", "Ann", 31 +sb.AppendLine +sb.AppendRepeat "-", 5 + +Debug.Print Replace(sb.ToString(), vbCrLf, "|") +Debug.Print sb.Length +' Output: +' Name: Ann|Ann is 31|----- +' 27 +``` + +The text is a **String**, with the limits of one. Positions are counted from 0: the first character is at position 0. + +* TOC +{:toc} + +## Properties + +### Capacity +{: .no_toc } + +The size of the buffer, in characters. + +Syntax: *builder*.**Capacity** **As Long** + +Read-only. It is 256 for a builder made without an argument, and the argument, or 16 when that is less, for a builder made with one. When an append does not fit, the buffer becomes twice as large, or as large as the text needs when that is more. + +### Length +{: .no_toc } + +The number of characters in the text. + +Syntax: *builder*.**Length** [ **=** *value* ] + +Setting a value below 0 sets 0. A value below the current length cuts the text. A value above it makes the text longer and the buffer larger when needed; the new characters are those that the buffer holds at those places, which are spaces for a buffer that was never written there, and the characters of earlier text for a buffer that was cut or cleared. + +```tb check_build check_run +Dim sb As New StringBuilder +sb.Append "abc" +sb.Insert 1, "XY" +Debug.Print sb.ToString() +sb.Length = 2 +Debug.Print sb.ToString() +sb.Length = 5 +Debug.Print sb.ToString() + +Dim fresh As New StringBuilder +fresh.Append "ab" +fresh.Length = 4 +Debug.Print "[" & fresh.ToString() & "]" +' Output: +' aXYbc +' aX +' aXYbc +' [ab ] +``` + +## Methods + +### Append +{: .no_toc } + +Adds text at the end. + +Syntax: *builder*.**Append** ( *text* ) **As StringBuilder** + +*text* +: *required* A **String**. + +Returns the builder. An empty string changes nothing. + +### AppendFormat +{: .no_toc } + +Adds text in which numbered places are replaced by values. + +Syntax: *builder*.**AppendFormat** ( *format*, *args* ) **As StringBuilder** + +*format* +: *required* A **String** with the places `{0}`, `{1}` and so on. + +*args* +: *optional* Any number of values, each converted with **CStr**. The first one replaces `{0}`. + +Returns the builder. The function replaces the places one after the other, in the order of the values, with the **Replace** function, so it has no formats such as `{0:N2}`. A value that holds a place, such as `{1}`, is itself replaced when a later value has that number. + +### AppendLine +{: .no_toc } + +Adds text and a line break. + +Syntax: *builder*.**AppendLine** [ ( *text* ) ] **As StringBuilder** + +*text* +: *optional* A **String**. Default: an empty string. + +Returns the builder. The line break is **vbCrLf**. + +### AppendRepeat +{: .no_toc } + +Adds a character several times. + +Syntax: *builder*.**AppendRepeat** ( *char*, *count* ) **As StringBuilder** + +*char* +: *required* A **String**. Its first character is repeated. + +*count* +: *required* A **Long**. A value of 0 or less changes nothing. + +Returns the builder. + +### Clear +{: .no_toc } + +Empties the text. + +Syntax: *builder*.**Clear** + +Sets the length to 0. The buffer keeps its size and its contents. + +### Insert +{: .no_toc } + +Inserts text at a position. + +Syntax: *builder*.**Insert** ( *index*, *text* ) **As StringBuilder** + +*index* +: *required* A **Long**, from 0 to [**Length**](#length). The text goes before the character at that position. With **Length** it goes at the end. + +*text* +: *required* A **String**. + +Returns the builder. Raises error 9 with the source `StringBuilder.Insert` and the text `Index out of range`, when *index* is below 0 or above **Length**. + +### New +{: .no_toc } + +Creates a builder. + +Syntax: **New** **StringBuilder** [ ( *initialCapacity* ) ] + +*initialCapacity* +: *optional* A **Long**, the size of the buffer in characters. A value below 16 gives 16. Default: 256. + +### ToString +{: .no_toc } + +Returns the text. + +Syntax: *builder*.**ToString** **As String** + +Returns the text, or an empty string when the length is 0. + +## See Also + +- [Formatters](Formatters) -- build their reports with a **StringBuilder** +- [FormatHelper](FormatHelper) module -- functions for text, numbers and times diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md new file mode 100644 index 000000000..b53b80518 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md @@ -0,0 +1,185 @@ +--- +title: TBMANLIB_Diagnostic Package +parent: Experimental Packages +has_toc: false +exclude_from_docs: + - WinAPI + - WinAPIStructures + - DelegatesModule + - RefreshModeModule +indexed_from: 0.0.0.0 +permalink: /tB/Packages/Contributed/TBMANLIB_Diagnostic/ +--- + +# TBMANLIB_Diagnostic Package +{: .no_toc } + +**Author:** woeoio. + +The **TBMANLIB_Diagnostic** contributed package makes a diagnostic report of a Windows computer and of the processes that run on it, and writes the report as text, JSON or HTML. The package has three parts. A [**DiagnosticEngine**](DiagnosticEngine) runs *collectors*, classes that each find one kind of information (the operating system, the processor and its load, the memory, the disks, the network, the running processes, the regional settings, the environment variables, the installed runtimes and the uptime), and each returns a [**DiagnosticResult**](DiagnosticResult), a list of pairs of text. A *formatter* turns the results into one string. [**FileHelper**](FileHelper) can save the string as a UTF-8 file. The package is written in twinBASIC and calls the Windows API directly. It uses no other library, and none of its collectors asks for administrator rights. + +The package is the reusable part of woeoio's entry for [twinBASIC Monthly Challenge #1](../../../../Challenges/1), *Self-Contained Diagnostic Tool*. The entry is a console program that shows the values on a live screen, or writes a report when it is started with an argument such as `/json`. That program is not in the package; [**FormatterFactory.CreateFormatterFromArg**](FormatterFactory#createformatterfromarg) reads such an argument. + +This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence. The package compiles in BETA 1005, as a 32-bit and as a 64-bit program. The repository [woeoio/tbman](https://github.com/woeoio/tbman), the toolkit of the same author whose package is [**TBMANLIB**](../TBMANLIB/), does not hold the code of this package. + +The package has defects: the text formatter and the **Dictionary** class cannot be used, and several members give a wrong result without an error. [Known problems in version 0.0.0.0](#known-problems-in-version-0000) lists them. The sample below uses the parts that work. + +```tb check_build check_run +Dim engine As New DiagnosticEngine +engine.RegisterCollector New OSInfoCollector +engine.RegisterCollector New MemoryInfoCollector +engine.RegisterCollector New UptimeCollector + +Dim formatter As IOutputFormatter +Set formatter = FormatterFactory.CreateFormatter("json") + +Dim report As String +report = FormatterFactory.FormatResults(engine.RunAll(), formatter) + +Dim path As String +path = Environ$("TEMP") & "\diag-report" & formatter.FileExtension +Debug.Print FileHelper.WriteFile(report, path) +Debug.Print FileHelper.GetFileSize(path) > 0 +Debug.Print Left$(report, 1) +Debug.Print InStr(report, """memory"": {") > 0 +FileHelper.DeleteFile path +' Output: +' True +' True +' { +' True +``` + +* TOC +{:toc} + +## How a report is made + +1. **Register collectors.** A new [**DiagnosticEngine**](DiagnosticEngine) has none. A program registers an object of each collector class that it wants with [**RegisterCollector**](DiagnosticEngine#registercollector). The order of registration is the order of the report. The [eleven collectors](Collectors) of the package are classes with names such as **OSInfoCollector** and **MemoryInfoCollector**, and a program can write its own, see [**IDiagnosticCollector**](IDiagnosticCollector). +2. **Run them.** [**RunAll**](DiagnosticEngine#runall) runs every collector and returns a list of results. [**RunCategory**](DiagnosticEngine#runcategory) runs one by its name, and [**RunByRefreshMode**](DiagnosticEngine#runbyrefreshmode) runs those that change at one rate. The engine raises events and calls callbacks while it works, so a program can show the progress or cancel. +3. **Format.** [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) makes a [formatter](Formatters) from the name `text`, `json` or `html`, and [**FormatResults**](FormatterFactory#formatresults) returns the report as a string. A formatter only builds the string. In version 0.0.0.0 the text formatter fails, so a report is made from `json` or `html`. +4. **Save.** [**FileHelper.WriteFile**](FileHelper#writefile) writes the string, and the formatter's **FileExtension** property gives the extension for the file name. + +A result can also be read directly, without a formatter. Each [item](KeyValuePair) has a key and a value, both text: the key is the label of the line, such as `Total Physical`, and the value is its text, such as `15.9 GB`. + +## Summary reports + +[**RunAll**](DiagnosticEngine#runall) takes an argument that asks for the short form. Four collectors have one: [**EnvironmentCollector**](Collectors#environmentcollector), [**RuntimesCollector**](Collectors#runtimescollector), [**ProcessInfoCollector**](Collectors#processinfocollector) and [**NetworkInfoCollector**](Collectors#networkinfocollector). For the process list it gives the counts instead of every process, and for the environment only three variables. The other seven collectors return their full data also in a summary run. + +## What a report contains +{: #what-a-report-contains } + +> [!NOTE] +> A full report names the user and the computer, the folders in the environment variables such as `USERPROFILE` and `PATH`, every running program, the drives, and the names and IPv4 addresses of the network adapters. Read a report before it goes to someone else. + +## Failures + +A collector never ends a run. An error that a collector raises, and a failure that it reports itself, give a result with [**Success**](DiagnosticResult#success) set to **False** and a message, and the run goes on; see [A collector that raises an error](DiagnosticEngine#a-collector-that-raises-an-error). A formatter prints such a result as one line with the key `Error`. The functions of [**FileHelper**](FileHelper) that write and read return a text that describes the failure and do not raise an error, and [**List**](List), [**StringBuilder**](StringBuilder) and [**RegisterCollector**](DiagnosticEngine#registercollector) raise run-time errors, which their pages list. + +## The Windows functions and the registry + +The package declares the Windows functions it calls, in a module of its own, and uses no component that has to be installed. Its references are the OLE Automation library and the **App** object that every twinBASIC project has. These are the libraries and the functions the collectors and the helpers call: + +| Library | Functions | +|:--------|:----------| +| `kernel32.dll` | `GetNativeSystemInfo`, `GetComputerNameW`, `GlobalMemoryStatusEx`, `GetSystemTimes`, `GetTickCount64`, `GetLogicalDrives`, `GetDriveTypeW`, `GetDiskFreeSpaceExW`, `CreateToolhelp32Snapshot`, `Process32FirstW`, `Process32NextW`, `CloseHandle`, `GetCurrentProcessId`, `GetTimeZoneInformation`, `GetACP`, `GetOEMCP`, `GetUserDefaultLCID`, `GetLocaleInfoW`, `RtlMoveMemory` | +| `ntdll.dll` | `RtlGetVersion` | +| `advapi32.dll` | `GetUserNameW`, `RegOpenKeyExW`, `RegQueryValueExW`, `RegCloseKey` | +| `shell32.dll` | `IsUserAnAdmin` | +| `iphlpapi.dll` | `GetAdaptersInfo` | +| `ws2_32.dll` | `WSAStartup`, `gethostname`, `WSACleanup` | + +The registry is read, never written, with the access `KEY_READ`. The keys are named on the page of the collector that reads them: [CPUInfoCollector](Collectors#cpuinfocollector) and [RuntimesCollector](Collectors#runtimescollector). + +## Using the package with the Scripting Runtime + +The package declares a generic class named **Dictionary**, and the Scripting Runtime (`scrrun.dll`) has a class of that name. The package's class is written with its two type arguments, as in `Dictionary(Of String, Long)`, and the Scripting Runtime's has none. The name with type arguments is the package's class in a project that references both libraries, whichever is listed first. A name without type arguments depends on the order of the references: + +| Declaration | Scripting Runtime listed first | Package listed first | +|:------------|:-------------------------------|:---------------------| +| `Dim a As New Dictionary` | the class of the Scripting Runtime | error TB5136, *Unable to bind to generic class 'Dictionary'; no generic specifiers given* | +| `Dim b As Dictionary(Of String, Long)` | the class of the package | the class of the package | +| `Dim c As New Scripting.Dictionary` | the class of the Scripting Runtime | the class of the Scripting Runtime | +| `Dim d As New TBMANLIB_Diagnostic.Dictionary(Of String, Long)` | the class of the package | the class of the package | + +A project that wants the Scripting Runtime's class writes `Scripting.Dictionary`, which does not depend on the order. The package's class cannot be created in version 0.0.0.0 (see [**Dictionary**](Dictionary)), so a project has no reason to write its name. A project with only the package, which has no `Scripting` library, shows the first row of the table: + +```tb check_build expect-error=TB5136 +Dim a As New Dictionary +``` + +```tb check_build +Dim b As Dictionary(Of String, Long) +Dim d As TBMANLIB_Diagnostic.Dictionary(Of String, Long) +``` + +The modules of the package are public, so a project that references it can use their names without the name of the module: `GetTickCount64`, `FILETIME` and `KEY_READ` of the **WinAPI** modules are visible, and so is `FileTimeToLongLong`. Where a name is declared in the project as well, such as its own `Declare` of `CloseHandle`, its own `Type FILETIME` or its own `Const KEY_READ`, written **Private** or **Public**, the project compiles and uses its own declaration. A call of **FormatDateTime** without the module name reaches the function of the runtime library, not the one of [**FormatHelper**](FormatHelper#formatdatetime). Write the name of the module before a name of the package that another declaration has too, as in `WinAPIStructures.KEY_READ` and `FormatHelper.FormatDateTime`. + +## Types that have no page + +The package has four modules whose contents a program has no need for: + +- **WinAPI** declares the Windows functions that the package calls, eight console and registry functions that it does not call (`GetStdHandle`, `SetConsoleCursorPosition`, `SetConsoleTextAttribute`, `GetConsoleScreenBufferInfo`, `SetConsoleCtrlHandler`, `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute` and `RegEnumKeyExW`), and four helper procedures: `MakeCOORD`, `FileTimeToLongLong`, `GetStringFromBuffer` and `GetStringFromByteBuffer`. [The WinAPI helpers](Collectors#the-winapi-helpers) describes the two that matter to the results. +- **WinAPIStructures** declares the record types and the constants of those functions: `OSVERSIONINFOEXW`, `SYSTEM_INFO`, `MEMORYSTATUSEX`, `FILETIME`, `PROCESSENTRY32W`, `IP_ADDR_STRING`, `IP_ADAPTER_INFO`, `TIME_ZONE_INFORMATION`, `SYSTEMTIME`, `COORD`, `SMALL_RECT` and `CONSOLE_SCREEN_BUFFER_INFO`. +- **DelegatesModule** declares the four delegate types. Three are the types of the callbacks of the engine, see [Delegates](DiagnosticEngine#delegates), and the fourth is [**FormatterFactoryDelegate**](FormatterFactory#formatterfactorydelegate). +- **RefreshModeModule** holds only the enumeration [**RefreshMode**](RefreshMode). + +## Known problems in version 0.0.0.0 + +> [!WARNING] +> Version 0.0.0.0 of the package gives a wrong result without an error, or loses data, in these cases. +> +> - **[List.Insert](List#insert) puts the new item one place too late**, and **[List.SetItem](List#setitem) with the index 0 puts it in the second place.** `SetItem` with the index 0 on a list of one item removes the item and then raises error 5, so the list is empty afterwards. +> - **[FileHelper.WriteFile](FileHelper#writefile) does not shorten an existing file.** The bytes after the new content stay: writing `ab` over a file of 13 bytes leaves a file of 13 bytes. +> - **[ReadFile](FileHelper#readfile) and [GetFileSize](FileHelper#getfilesize) create a file that does not exist.** **ReadFile** returns an empty string and **GetFileSize** returns 0, as for an empty file, so a missing file is not told from an empty one. +> - **ReadFile does not decode UTF-8 or remove the byte-order mark that WriteFile writes.** The text `café` written with **WriteFile** is read back as 8 characters. +> - **[NormalizePath](FileHelper#normalizepath) and [GetFullPath](FileHelper#getfullpath) turn a UNC path into a relative one.** `\\server\share\f.txt` becomes `server\share\f.txt`, and the file functions then open a file of that relative name. +> - **[EscapeJson](FormatHelper#escapejson) leaves the control characters 1 to 31, other than line feed, carriage return and tab, unescaped**, so [**JsonFormatter**](Formatters#jsonformatter) can write JSON that a parser rejects. +> - **[FormatBytes](FormatHelper#formatbytes) with no decimals leaves the decimal separator in the text**, as in `4. KB`. The `Page Size` item of [**CPUInfoCollector**](Collectors#cpuinfocollector) is written this way. +> - **[LocaleCollector](Collectors#localecollector) reports the offset of standard time as `Timezone` all year.** In summer time the item is behind the clock by the daylight saving offset: `UTC+1` where the clock is at UTC+2, as in Central European Summer Time. +> - **[NetworkInfoCollector](Collectors#networkinfocollector) lists the first adapter only in a 64-bit program.** It reads the adapters in steps of 640 bytes, and the records are 704 bytes apart in a 64-bit program. The summary may then have no `Primary IP`. A 32-bit program lists every adapter. + +> [!NOTE] +> Version 0.0.0.0 has these other problems. Each raises an error. +> +> - **A [Dictionary](Dictionary) cannot be created.** `New Dictionary(Of TKey, TValue)` raises run-time error 91, *Object variable or With block variable not set*. +> - **The [TextFormatter](Formatters#textformatter) cannot make a report.** Its **BeginFormat** raises error 5, *Invalid procedure call or argument*, because it builds the box with **Chr$** and code points above 255. [**FormatterFactory.CreateFormatter**](FormatterFactory#createformatter) gives this formatter for `text`, for `txt` and for every name it does not know. The names `json` and `html` work. +> - **[List.Contains](List#contains), [IndexOf](List#indexof) and [Remove](List#remove) raise error 438**, *Object doesn't support this property or method*, for a list of objects of a class that has no default member. +> - **[List.Insert](List#insert) with the index 0 raises error 5 on an empty list.** +> - **[WriteFile](FileHelper#writefile) and [AppendFile](FileHelper#appendfile) make only the last folder of a path.** For a path whose parent folder is missing they return the error text `Error -2147467259: Unspecified error` and the path. The **Open** statement raises error 76, *Path not found*, for it in VB6. + +Two behaviours follow from the code and can look like defects. [**CPULoadCollector**](Collectors#cpuloadcollector) reports 0 for its first reading, because it measures between two calls. [**JsonFormatter**](Formatters#jsonformatter) writes every value as a string and turns `PATH` into `pATH`. + +## Pages + +**Engine and results** + +- [DiagnosticEngine](DiagnosticEngine) -- registers collectors, runs them, and raises events and callbacks +- [DiagnosticResult](DiagnosticResult) -- the items, the time and the success of one collection +- [KeyValuePair](KeyValuePair) -- one key with one value +- [RefreshMode](RefreshMode) -- how often the data of a collector changes + +**Collectors** + +- [IDiagnosticCollector](IDiagnosticCollector) -- the contract of a collector +- [Collectors](Collectors) -- the eleven collectors of the package, what each reports and which Windows functions it calls + +**Formatters** + +- [IOutputFormatter](IOutputFormatter) -- the contract of a formatter +- [Formatters](Formatters) -- the text, JSON and HTML formatters +- [FormatterFactory](FormatterFactory) -- creates a formatter by name, and formats a list of results + +**Helpers** + +- [FormatHelper](FormatHelper) -- formats sizes, durations, percentages, dates and text, and escapes text for JSON and HTML +- [FileHelper](FileHelper) -- writes, appends to and reads text files, tests for files and folders, and takes paths apart +- [List](List) -- a list with a 0-based index +- [Dictionary](Dictionary) -- values found by a key; it cannot be created in version 0.0.0.0 +- [StringBuilder](StringBuilder) -- builds a long string from parts + +## See Also + +- [TBMANLIB](../TBMANLIB/) package -- the toolkit of the same author +- [VBA_JSON](../VBA_JSON/) package -- reads JSON, such as the report that **JsonFormatter** writes +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index d691fe0a4..dd6a06efe 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -16,6 +16,7 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.2 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. +- [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 0.0.0.0 compiles, and its text report, its **Dictionary** class and several of its list and file helpers fail or give wrong results. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.7.0 compiles, and has defects, among them some that can stop a program from responding. diff --git a/docs/_book.yml b/docs/_book.yml index 6519e89f6..e68901a4c 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -627,6 +627,13 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB_Diagnostic Package + subtitle: A diagnostic report of the computer and its processes, as text, JSON or HTML + landing_page: /tB/Packages/Contributed/TBMANLIB_Diagnostic/ + page: /tB/Packages/Contributed/TBMANLIB_Diagnostic/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Xml Package subtitle: XML documents parsed, edited and written, in twinBASIC code landing_page: /tB/Packages/Contributed/TBMANLIB_Xml/ diff --git a/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json b/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json new file mode 100644 index 000000000..d64a7a843 --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB_Diagnostic/api.json @@ -0,0 +1,923 @@ +{ + "format": 3, + "package": "TBMANLIB_Diagnostic", + "project": "TBMANLIB_Diagnostic", + "version": "0.0.0.0", + "types": [ + { + "name": "CPUInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Static/CPUInfoCollector.twin", + "signature": "Public Class CPUInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"02A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"02A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"GetCPUMHzFromRegistry","kind":"function","vis":"private","signature":"Private Function GetCPUMHzFromRegistry() As String","returnType":"String"}, + {"name":"GetCPUNameFromRegistry","kind":"function","vis":"private","signature":"Private Function GetCPUNameFromRegistry() As String","returnType":"String"}, + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "CPULoadCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Dynamic/CPULoadCollector.twin", + "signature": "Public Class CPULoadCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"06A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"06A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"m_Initialized","kind":"field","vis":"private","signature":"Private m_Initialized As Boolean"}, + {"name":"m_LastIdle","kind":"field","vis":"private","signature":"Private m_LastIdle As LongLong"}, + {"name":"m_LastKernel","kind":"field","vis":"private","signature":"Private m_LastKernel As LongLong"}, + {"name":"m_LastUser","kind":"field","vis":"private","signature":"Private m_LastUser As LongLong"} + ] + }, + { + "name": "DelegatesModule", + "kind": "module", + "vis": "public", + "file": "Delegates/Delegates.twin", + "signature": "Public Module DelegatesModule", + "members": [ + {"name":"DiagnosticCompleteHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub DiagnosticCompleteHandler( ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long)"}, + {"name":"DiagnosticErrorHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub DiagnosticErrorHandler( ByVal category As String, ByVal errorMessage As String)"}, + {"name":"DiagnosticProgressHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub DiagnosticProgressHandler( ByVal percent As Long, ByVal message As String)"}, + {"name":"FormatterFactoryDelegate","kind":"delegate","vis":"public","signature":"Public Delegate Function FormatterFactoryDelegate() As IOutputFormatter","returnType":"IOutputFormatter"} + ] + }, + { + "name": "DiagnosticEngine", + "kind": "class", + "vis": "public", + "file": "Core/DiagnosticEngine.twin", + "signature": "Public Class DiagnosticEngine", + "attrs": [{"name":"ClassId","args":"\"F6A7B8C9-D0E1-F2A3-B4C5-D6E7F8A9B0C1\""},{"name":"InterfaceId","args":"\"F6A7B8C9-D0E1-F2A3-B4C5-D6E7F8A9B0C2\""}], + "members": [ + {"name":"AllCompleted","kind":"event","vis":"public","signature":"Public Event AllCompleted(ByVal results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))))"}, + {"name":"Cancel","kind":"sub","vis":"public","signature":"Public Sub Cancel()"}, + {"name":"CollectorCompleted","kind":"event","vis":"public","signature":"Public Event CollectorCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long)"}, + {"name":"CollectorCount","kind":"property","vis":"public","signature":"Public Property Get CollectorCount() As Long","returnType":"Long","accessor":"Get"}, + {"name":"CollectorStarted","kind":"event","vis":"public","signature":"Public Event CollectorStarted(ByVal category As String)"}, + {"name":"Collectors","kind":"property","vis":"public","signature":"Public Property Get Collectors() As List(Of IDiagnosticCollector)","returnType":"List(Of IDiagnosticCollector)","accessor":"Get"}, + {"name":"Error","kind":"event","vis":"public","signature":"Public Event Error(ByVal category As String, ByVal errorMessage As String)"}, + {"name":"IsElevated","kind":"function","vis":"public","signature":"Public Function IsElevated() As Boolean","returnType":"Boolean"}, + {"name":"IsRunning","kind":"property","vis":"public","signature":"Public Property Get IsRunning() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"OnCollectorCompleted","kind":"property","vis":"public","signature":"Public Property Let OnCollectorCompleted(ByVal handler As DiagnosticCompleteHandler)","accessor":"Let"}, + {"name":"OnCollectorStarted","kind":"property","vis":"public","signature":"Public Property Let OnCollectorStarted(ByVal handler As DiagnosticCompleteHandler)","accessor":"Let"}, + {"name":"OnError","kind":"property","vis":"public","signature":"Public Property Let OnError(ByVal handler As DiagnosticErrorHandler)","accessor":"Let"}, + {"name":"OnProgress","kind":"property","vis":"public","signature":"Public Property Let OnProgress(ByVal handler As DiagnosticProgressHandler)","accessor":"Let"}, + {"name":"Progress","kind":"event","vis":"public","signature":"Public Event Progress(ByVal percent As Long, ByVal message As String)"}, + {"name":"RaiseCollectorCompleted","kind":"sub","vis":"private","signature":"Private Sub RaiseCollectorCompleted(ByVal category As String, ByVal success As Boolean, ByVal itemCount As Long)"}, + {"name":"RaiseCollectorStarted","kind":"sub","vis":"private","signature":"Private Sub RaiseCollectorStarted(ByVal category As String)"}, + {"name":"RaiseError","kind":"sub","vis":"private","signature":"Private Sub RaiseError(ByVal category As String, ByVal errorMessage As String)"}, + {"name":"RaiseProgress","kind":"sub","vis":"private","signature":"Private Sub RaiseProgress(ByVal percent As Long, ByVal message As String)"}, + {"name":"RegisterCollector","kind":"sub","vis":"public","signature":"Public Sub RegisterCollector(ByVal collector As IDiagnosticCollector)"}, + {"name":"Results","kind":"property","vis":"public","signature":"Public Property Get Results() As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))","returnType":"List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))","accessor":"Get"}, + {"name":"RunAll","kind":"function","vis":"public","signature":"Public Function RunAll(Optional ByVal useSummary As Boolean = False) As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))","returnType":"List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))"}, + {"name":"RunByRefreshMode","kind":"function","vis":"public","signature":"Public Function RunByRefreshMode(ByVal mode As RefreshMode, Optional ByVal useSummary As Boolean = False) As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))","returnType":"List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))"}, + {"name":"RunCategory","kind":"function","vis":"public","signature":"Public Function RunCategory(ByVal categoryName As String, Optional ByVal useSummary As Boolean = False) As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"UnregisterCollector","kind":"sub","vis":"public","signature":"Public Sub UnregisterCollector(ByVal categoryName As String)"}, + {"name":"m_CancelRequested","kind":"field","vis":"private","signature":"Private m_CancelRequested As Boolean"}, + {"name":"m_Collectors","kind":"field","vis":"private","signature":"Private m_Collectors As List(Of IDiagnosticCollector)"}, + {"name":"m_IsRunning","kind":"field","vis":"private","signature":"Private m_IsRunning As Boolean"}, + {"name":"m_OnCollectorCompleted","kind":"field","vis":"private","signature":"Private m_OnCollectorCompleted As DiagnosticCompleteHandler"}, + {"name":"m_OnCollectorStarted","kind":"field","vis":"private","signature":"Private m_OnCollectorStarted As DiagnosticCompleteHandler"}, + {"name":"m_OnError","kind":"field","vis":"private","signature":"Private m_OnError As DiagnosticErrorHandler"}, + {"name":"m_OnProgress","kind":"field","vis":"private","signature":"Private m_OnProgress As DiagnosticProgressHandler"}, + {"name":"m_Results","kind":"field","vis":"private","signature":"Private m_Results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String)))"} + ] + }, + { + "name": "DiagnosticResult", + "kind": "class", + "vis": "public", + "file": "Generics/DiagnosticResult.twin", + "signature": "Public Class DiagnosticResult(Of T)", + "attrs": [{"name":"COMCreatable","args":"False"},{"name":"ClassId","args":"\"B2C3D4E5-F6A7-B8C9-D0E1-F2A3B4C5D6E7\""},{"name":"InterfaceId","args":"\"B2C3D4E5-F6A7-B8C9-D0E1-F2A3B4C5D6E8\""}], + "members": [ + {"name":"AddItem","kind":"sub","vis":"public","signature":"Public Sub AddItem(ByVal item As T)"}, + {"name":"Category","kind":"property","vis":"public","signature":"Public Property Get Category() As String","returnType":"String","accessor":"Get"}, + {"name":"Category","kind":"property","vis":"public","signature":"Public Property Let Category(ByVal value As String)","accessor":"Let"}, + {"name":"CollectedAt","kind":"property","vis":"public","signature":"Public Property Get CollectedAt() As Date","returnType":"Date","accessor":"Get"}, + {"name":"CollectedAt","kind":"property","vis":"public","signature":"Public Property Let CollectedAt(ByVal value As Date)","accessor":"Let"}, + {"name":"ErrorMessage","kind":"property","vis":"public","signature":"Public Property Get ErrorMessage() As String","returnType":"String","accessor":"Get"}, + {"name":"ErrorMessage","kind":"property","vis":"public","signature":"Public Property Let ErrorMessage(ByVal value As String)","accessor":"Let"}, + {"name":"IsSummary","kind":"property","vis":"public","signature":"Public Property Get IsSummary() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IsSummary","kind":"property","vis":"public","signature":"Public Property Let IsSummary(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ItemCount","kind":"property","vis":"public","signature":"Public Property Get ItemCount() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Items","kind":"property","vis":"public","signature":"Public Property Get Items() As List(Of T)","returnType":"List(Of T)","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal categoryName As String)"}, + {"name":"SetError","kind":"sub","vis":"public","signature":"Public Sub SetError(ByVal errMsg As String)"}, + {"name":"Success","kind":"property","vis":"public","signature":"Public Property Get Success() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Success","kind":"property","vis":"public","signature":"Public Property Let Success(ByVal value As Boolean)","accessor":"Let"}, + {"name":"m_Category","kind":"field","vis":"private","signature":"Private m_Category As String"}, + {"name":"m_CollectedAt","kind":"field","vis":"private","signature":"Private m_CollectedAt As Date"}, + {"name":"m_ErrorMessage","kind":"field","vis":"private","signature":"Private m_ErrorMessage As String"}, + {"name":"m_IsSummary","kind":"field","vis":"private","signature":"Private m_IsSummary As Boolean"}, + {"name":"m_Items","kind":"field","vis":"private","signature":"Private m_Items As List(Of T)"}, + {"name":"m_Success","kind":"field","vis":"private","signature":"Private m_Success As Boolean"} + ] + }, + { + "name": "Dictionary", + "kind": "class", + "vis": "public", + "file": "Generics/Dictionary.twin", + "signature": "Public Class Dictionary(Of TKey, TValue)", + "attrs": [{"name":"COMCreatable","args":"False"},{"name":"ClassId","args":"\"A1B2C3D4-E5F6-A7B8-C9D0-E1F2A3B4C5D6\""},{"name":"InterfaceId","args":"\"A1B2C3D4-E5F6-A7B8-C9D0-E1F2A3B4C5D7\""}], + "members": [ + {"name":"Add","kind":"sub","vis":"public","signature":"Public Sub Add(ByVal key As TKey, ByVal value As TValue)"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"ContainsKey","kind":"function","vis":"public","signature":"Public Function ContainsKey(ByVal key As TKey) As Boolean","returnType":"Boolean"}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get"}, + {"name":"FindKeyIndex","kind":"function","vis":"private","signature":"Private Function FindKeyIndex(ByVal key As TKey) As Long","returnType":"Long"}, + {"name":"Item","kind":"function","vis":"public","signature":"Public Function Item(ByVal key As TKey) As TValue","returnType":"TValue"}, + {"name":"Keys","kind":"property","vis":"public","signature":"Public Property Get Keys() As List(Of TKey)","returnType":"List(Of TKey)","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"Remove","kind":"function","vis":"public","signature":"Public Function Remove(ByVal key As TKey) As Boolean","returnType":"Boolean"}, + {"name":"SetItem","kind":"sub","vis":"public","signature":"Public Sub SetItem(ByVal key As TKey, ByVal value As TValue)"}, + {"name":"TryGetValue","kind":"function","vis":"public","signature":"Public Function TryGetValue(ByVal key As TKey, ByRef value As TValue) As Boolean","returnType":"Boolean"}, + {"name":"Values","kind":"property","vis":"public","signature":"Public Property Get Values() As List(Of TValue)","returnType":"List(Of TValue)","accessor":"Get"}, + {"name":"m_Keys","kind":"field","vis":"private","signature":"Private m_Keys As List(Of TKey)"}, + {"name":"m_Values","kind":"field","vis":"private","signature":"Private m_Values As List(Of TValue)"} + ] + }, + { + "name": "DiskInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/SemiDynamic/DiskInfoCollector.twin", + "signature": "Public Class DiskInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"10A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"10A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "EnvironmentCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Static/EnvironmentCollector.twin", + "signature": "Public Class EnvironmentCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"04A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"04A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"KEY_VARS","kind":"const","vis":"private","signature":"Private Const KEY_VARS As String = \"PATH,TEMP,TMP,USERPROFILE,APPDATA,LOCALAPPDATA,\" & \"PROGRAMFILES,PROGRAMFILES(X86),WINDIR,SYSTEMROOT,COMPUTERNAME,USERNAME,\" & \"HOMEDRIVE,HOMEPATH,PATHEXT,COMSPEC\""}, + {"name":"SUMMARY_VARS","kind":"const","vis":"private","signature":"Private Const SUMMARY_VARS As String = \"PATH,TEMP,USERPROFILE\""} + ] + }, + { + "name": "FileHelper", + "kind": "module", + "vis": "public", + "file": "Utilities/FileHelper.twin", + "signature": "Public Module FileHelper", + "members": [ + {"name":"AppendFile","kind":"function","vis":"public","signature":"Public Function AppendFile(ByVal content As String, ByVal filePath As String) As Variant","returnType":"Variant"}, + {"name":"DeleteFile","kind":"function","vis":"public","signature":"Public Function DeleteFile(ByVal filePath As String) As Boolean","returnType":"Boolean"}, + {"name":"DirectoryExists","kind":"function","vis":"public","signature":"Public Function DirectoryExists(ByVal dirPath As String) As Boolean","returnType":"Boolean"}, + {"name":"EnsureDirectoryExists","kind":"sub","vis":"public","signature":"Public Sub EnsureDirectoryExists(ByVal path As String)"}, + {"name":"FileExists","kind":"function","vis":"public","signature":"Public Function FileExists(ByVal filePath As String) As Boolean","returnType":"Boolean"}, + {"name":"GetDirectoryName","kind":"function","vis":"public","signature":"Public Function GetDirectoryName(ByVal path As String) As String","returnType":"String"}, + {"name":"GetExtension","kind":"function","vis":"public","signature":"Public Function GetExtension(ByVal path As String) As String","returnType":"String"}, + {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal path As String) As String","returnType":"String"}, + {"name":"GetFileNameWithoutExtension","kind":"function","vis":"public","signature":"Public Function GetFileNameWithoutExtension(ByVal path As String) As String","returnType":"String"}, + {"name":"GetFileSize","kind":"function","vis":"public","signature":"Public Function GetFileSize(ByVal filePath As String) As LongLong","returnType":"LongLong"}, + {"name":"GetFullPath","kind":"function","vis":"public","signature":"Public Function GetFullPath(ByVal path As String) As String","returnType":"String"}, + {"name":"IsAbsolutePath","kind":"function","vis":"public","signature":"Public Function IsAbsolutePath(ByVal path As String) As Boolean","returnType":"Boolean"}, + {"name":"NormalizePath","kind":"function","vis":"public","signature":"Public Function NormalizePath(ByVal path As String) As String","returnType":"String"}, + {"name":"ReadFile","kind":"function","vis":"public","signature":"Public Function ReadFile(ByVal filePath As String) As Variant","returnType":"Variant"}, + {"name":"WriteFile","kind":"function","vis":"public","signature":"Public Function WriteFile(ByVal content As String, ByVal filePath As String) As Variant","returnType":"Variant"} + ] + }, + { + "name": "FormatHelper", + "kind": "module", + "vis": "public", + "file": "Utilities/FormatHelper.twin", + "signature": "Public Module FormatHelper", + "members": [ + {"name":"EscapeHtml","kind":"function","vis":"public","signature":"Public Function EscapeHtml(ByVal text As String) As String","returnType":"String"}, + {"name":"EscapeJson","kind":"function","vis":"public","signature":"Public Function EscapeJson(ByVal text As String) As String","returnType":"String"}, + {"name":"FormatBytes","kind":"function","vis":"public","signature":"Public Function FormatBytes(ByVal bytes As LongLong, Optional ByVal decimals As Long = 1) As String","returnType":"String"}, + {"name":"FormatDateTime","kind":"function","vis":"public","signature":"Public Function FormatDateTime(ByVal dt As Date) As String","returnType":"String"}, + {"name":"FormatDateTimeISO","kind":"function","vis":"public","signature":"Public Function FormatDateTimeISO(ByVal dt As Date) As String","returnType":"String"}, + {"name":"FormatPercent","kind":"function","vis":"public","signature":"Public Function FormatPercent(ByVal value As Double, ByVal total As Double, Optional ByVal decimals As Long = 0) As String","returnType":"String"}, + {"name":"FormatPercentValue","kind":"function","vis":"public","signature":"Public Function FormatPercentValue(ByVal percent As Double, Optional ByVal decimals As Long = 0) As String","returnType":"String"}, + {"name":"FormatTime","kind":"function","vis":"public","signature":"Public Function FormatTime(ByVal dt As Date) As String","returnType":"String"}, + {"name":"FormatUptime","kind":"function","vis":"public","signature":"Public Function FormatUptime(ByVal milliseconds As LongLong) As String","returnType":"String"}, + {"name":"PadLeft","kind":"function","vis":"public","signature":"Public Function PadLeft(ByVal text As String, ByVal width As Long, Optional ByVal padChar As String = \" \") As String","returnType":"String"}, + {"name":"PadRight","kind":"function","vis":"public","signature":"Public Function PadRight(ByVal text As String, ByVal width As Long, Optional ByVal padChar As String = \" \") As String","returnType":"String"}, + {"name":"Truncate","kind":"function","vis":"public","signature":"Public Function Truncate(ByVal text As String, ByVal maxLength As Long, Optional ByVal ellipsis As String = \"...\") As String","returnType":"String"} + ] + }, + { + "name": "FormatterFactory", + "kind": "module", + "vis": "public", + "file": "Formatters/FormatterFactory.twin", + "signature": "Public Module FormatterFactory", + "members": [ + {"name":"CreateFormatter","kind":"function","vis":"public","signature":"Public Function CreateFormatter(ByVal formatName As String) As IOutputFormatter","returnType":"IOutputFormatter"}, + {"name":"CreateFormatterFromArg","kind":"function","vis":"public","signature":"Public Function CreateFormatterFromArg(ByVal arg As String) As IOutputFormatter","returnType":"IOutputFormatter"}, + {"name":"FormatResults","kind":"function","vis":"public","signature":"Public Function FormatResults( ByVal results As List(Of DiagnosticResult(Of KeyValuePair(Of String, String))), ByVal formatter As IOutputFormatter) As String","returnType":"String"}, + {"name":"GetSupportedFormats","kind":"function","vis":"public","signature":"Public Function GetSupportedFormats() As String","returnType":"String"} + ] + }, + { + "name": "HtmlFormatter", + "kind": "class", + "vis": "public", + "file": "Formatters/HtmlFormatter.twin", + "signature": "Public Class HtmlFormatter", + "implements": ["IOutputFormatter"], + "attrs": [{"name":"ClassId","args":"\"22A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"22A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"GetCSSStyles","kind":"function","vis":"private","signature":"Private Function GetCSSStyles() As String","returnType":"String"}, + {"name":"IOutputFormatter_BeginFormat","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_BeginFormat()"}, + {"name":"IOutputFormatter_EndCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_EndCategory()"}, + {"name":"IOutputFormatter_EndFormat","kind":"function","vis":"public","signature":"Public Function IOutputFormatter_EndFormat() As String","returnType":"String"}, + {"name":"IOutputFormatter_FileExtension","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FileExtension() As String","returnType":"String","accessor":"Get"}, + {"name":"IOutputFormatter_FormatCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatCategory(ByVal categoryName As String)"}, + {"name":"IOutputFormatter_FormatItem","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatItem(ByVal key As String, ByVal value As String)"}, + {"name":"IOutputFormatter_FormatterName","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FormatterName() As String","returnType":"String","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"m_Builder","kind":"field","vis":"private","signature":"Private m_Builder As StringBuilder"}, + {"name":"m_InCategory","kind":"field","vis":"private","signature":"Private m_InCategory As Boolean"} + ] + }, + { + "name": "IDiagnosticCollector", + "kind": "interface", + "vis": "public", + "file": "Interfaces/IDiagnosticCollector.twin", + "signature": "Public Interface IDiagnosticCollector", + "attrs": [{"name":"InterfaceId","args":"\"C3D4E5F6-A7B8-C9D0-E1F2-A3B4C5D6E7F8\""}], + "members": [ + {"name":"CategoryName","kind":"property","vis":"public","signature":"Property Get CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"Collect","kind":"function","vis":"public","signature":"Function Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"CollectSummary","kind":"function","vis":"public","signature":"Function CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"Description","kind":"property","vis":"public","signature":"Property Get Description() As String","returnType":"String","accessor":"Get"}, + {"name":"RefreshMode","kind":"property","vis":"public","signature":"Property Get RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"RequiresElevation","kind":"property","vis":"public","signature":"Property Get RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"SupportsSummary","kind":"property","vis":"public","signature":"Property Get SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "IOutputFormatter", + "kind": "interface", + "vis": "public", + "file": "Interfaces/IOutputFormatter.twin", + "signature": "Public Interface IOutputFormatter", + "attrs": [{"name":"InterfaceId","args":"\"D4E5F6A7-B8C9-D0E1-F2A3-B4C5D6E7F8A9\""}], + "members": [ + {"name":"BeginFormat","kind":"sub","vis":"public","signature":"Sub BeginFormat()"}, + {"name":"EndCategory","kind":"sub","vis":"public","signature":"Sub EndCategory()"}, + {"name":"EndFormat","kind":"function","vis":"public","signature":"Function EndFormat() As String","returnType":"String"}, + {"name":"FileExtension","kind":"property","vis":"public","signature":"Property Get FileExtension() As String","returnType":"String","accessor":"Get"}, + {"name":"FormatCategory","kind":"sub","vis":"public","signature":"Sub FormatCategory(ByVal categoryName As String)"}, + {"name":"FormatItem","kind":"sub","vis":"public","signature":"Sub FormatItem(ByVal key As String, ByVal value As String)"}, + {"name":"FormatterName","kind":"property","vis":"public","signature":"Property Get FormatterName() As String","returnType":"String","accessor":"Get"} + ] + }, + { + "name": "JsonFormatter", + "kind": "class", + "vis": "public", + "file": "Formatters/JsonFormatter.twin", + "signature": "Public Class JsonFormatter", + "implements": ["IOutputFormatter"], + "attrs": [{"name":"ClassId","args":"\"21A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"21A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IOutputFormatter_BeginFormat","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_BeginFormat()"}, + {"name":"IOutputFormatter_EndCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_EndCategory()"}, + {"name":"IOutputFormatter_EndFormat","kind":"function","vis":"public","signature":"Public Function IOutputFormatter_EndFormat() As String","returnType":"String"}, + {"name":"IOutputFormatter_FileExtension","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FileExtension() As String","returnType":"String","accessor":"Get"}, + {"name":"IOutputFormatter_FormatCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatCategory(ByVal categoryName As String)"}, + {"name":"IOutputFormatter_FormatItem","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatItem(ByVal key As String, ByVal value As String)"}, + {"name":"IOutputFormatter_FormatterName","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FormatterName() As String","returnType":"String","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"ToCamelCase","kind":"function","vis":"private","signature":"Private Function ToCamelCase(ByVal text As String) As String","returnType":"String"}, + {"name":"m_Builder","kind":"field","vis":"private","signature":"Private m_Builder As StringBuilder"}, + {"name":"m_CurrentCategory","kind":"field","vis":"private","signature":"Private m_CurrentCategory As String"}, + {"name":"m_FirstCategory","kind":"field","vis":"private","signature":"Private m_FirstCategory As Boolean"}, + {"name":"m_FirstItem","kind":"field","vis":"private","signature":"Private m_FirstItem As Boolean"}, + {"name":"m_InCategory","kind":"field","vis":"private","signature":"Private m_InCategory As Boolean"} + ] + }, + { + "name": "KeyValuePair", + "kind": "class", + "vis": "public", + "file": "Generics/KeyValuePair.twin", + "signature": "Public Class KeyValuePair(Of TKey, TValue)", + "attrs": [{"name":"COMCreatable","args":"False"},{"name":"ClassId","args":"\"E8A1B2C3-D4E5-F6A7-B8C9-D0E1F2A3B4C5\""},{"name":"InterfaceId","args":"\"E8A1B2C3-D4E5-F6A7-B8C9-D0E1F2A3B4C6\""}], + "members": [ + {"name":"Key","kind":"property","vis":"public","signature":"Public Property Get Key() As TKey","returnType":"TKey","accessor":"Get"}, + {"name":"Key","kind":"property","vis":"public","signature":"Public Property Let Key(ByVal value As TKey)","accessor":"Let"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal key As TKey, ByVal value As TValue)"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String"}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Get Value() As TValue","returnType":"TValue","accessor":"Get"}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Let Value(ByVal v As TValue)","accessor":"Let"}, + {"name":"m_Key","kind":"field","vis":"private","signature":"Private m_Key As TKey"}, + {"name":"m_Value","kind":"field","vis":"private","signature":"Private m_Value As TValue"} + ] + }, + { + "name": "List", + "kind": "class", + "vis": "public", + "file": "Generics/List.twin", + "signature": "Public Class List(Of T)", + "attrs": [{"name":"COMCreatable","args":"False"},{"name":"ClassId","args":"\"F1A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"F1A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""},{"name":"IgnoreWarnings","args":"TB0022"}], + "members": [ + {"name":"Add","kind":"sub","vis":"public","signature":"Public Sub Add(ByVal item As T)"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Get Capacity() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"Contains","kind":"function","vis":"public","signature":"Public Function Contains(ByVal item As T) As Boolean","returnType":"Boolean"}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get"}, + {"name":"IndexOf","kind":"function","vis":"public","signature":"Public Function IndexOf(ByVal item As T) As Long","returnType":"Long"}, + {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal index As Long, ByVal item As T)"}, + {"name":"Item","kind":"function","vis":"public","signature":"Public Function Item(ByVal index As Long) As T","returnType":"T"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal initialCapacity As Long)"}, + {"name":"Remove","kind":"function","vis":"public","signature":"Public Function Remove(ByVal item As T) As Boolean","returnType":"Boolean"}, + {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal index As Long)"}, + {"name":"SetItem","kind":"sub","vis":"public","signature":"Public Sub SetItem(ByVal index As Long, ByVal item As T)"}, + {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant()","returnType":"Variant()"}, + {"name":"m_Items","kind":"field","vis":"private","signature":"Private m_Items As Collection"} + ] + }, + { + "name": "LocaleCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Static/LocaleCollector.twin", + "signature": "Public Class LocaleCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"03A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"03A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"GetLocaleString","kind":"function","vis":"private","signature":"Private Function GetLocaleString(ByVal lcid As Long, ByVal lcType As Long) As String","returnType":"String"}, + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "MemoryInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Dynamic/MemoryInfoCollector.twin", + "signature": "Public Class MemoryInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"07A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"07A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "NetworkInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/SemiDynamic/NetworkInfoCollector.twin", + "signature": "Public Class NetworkInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"11A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"11A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CopyMemory Lib \"kernel32.dll\" Alias \"RtlMoveMemory\" ( ByVal dest As LongPtr, ByVal src As LongPtr, ByVal length As LongPtr)"}, + {"name":"GetAdapterInfoSize","kind":"function","vis":"private","signature":"Private Function GetAdapterInfoSize() As Long","returnType":"Long"}, + {"name":"GetHostNameValue","kind":"function","vis":"private","signature":"Private Function GetHostNameValue() As String","returnType":"String"}, + {"name":"GetPrimaryIPAddress","kind":"function","vis":"private","signature":"Private Function GetPrimaryIPAddress() As String","returnType":"String"}, + {"name":"GetStringFromByteArray","kind":"function","vis":"private","signature":"Private Function GetStringFromByteArray(ByRef buffer() As Byte, ByVal offset As Long, ByVal maxLen As Long) As String","returnType":"String"}, + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "OSInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Static/OSInfoCollector.twin", + "signature": "Public Class OSInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"01A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"01A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"GetWindowsVersionName","kind":"function","vis":"private","signature":"Private Function GetWindowsVersionName(ByVal major As Long, ByVal minor As Long, ByVal build As Long) As String","returnType":"String"}, + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "ProcessInfoCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Dynamic/ProcessInfoCollector.twin", + "signature": "Public Class ProcessInfoCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"09A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"09A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "RefreshModeModule", + "kind": "module", + "vis": "public", + "file": "Core/RefreshMode.twin", + "signature": "Public Module RefreshModeModule", + "members": [] + }, + { + "name": "RefreshMode", + "kind": "enum", + "in": "RefreshModeModule", + "vis": "public", + "file": "Core/RefreshMode.twin", + "signature": "Public Enum RefreshMode", + "values": [ + {"name":"Static","value":"0"}, + {"name":"Dynamic","value":"1"}, + {"name":"SemiDynamic","value":"2"} + ] + }, + { + "name": "RuntimesCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Static/RuntimesCollector.twin", + "signature": "Public Class RuntimesCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"05A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"05A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"CollectDotNetCore","kind":"sub","vis":"private","signature":"Private Sub CollectDotNetCore(ByRef result As DiagnosticResult(Of KeyValuePair(Of String, String)))"}, + {"name":"CollectDotNetFramework","kind":"sub","vis":"private","signature":"Private Sub CollectDotNetFramework(ByRef result As DiagnosticResult(Of KeyValuePair(Of String, String)))"}, + {"name":"CollectVCRedist","kind":"sub","vis":"private","signature":"Private Sub CollectVCRedist(ByRef result As DiagnosticResult(Of KeyValuePair(Of String, String)))"}, + {"name":"GetDotNetVersionFromRelease","kind":"function","vis":"private","signature":"Private Function GetDotNetVersionFromRelease(ByVal release As Long) As String","returnType":"String"}, + {"name":"GetLatestDotNetCore","kind":"function","vis":"private","signature":"Private Function GetLatestDotNetCore() As String","returnType":"String"}, + {"name":"GetLatestDotNetFramework","kind":"function","vis":"private","signature":"Private Function GetLatestDotNetFramework() As String","returnType":"String"}, + {"name":"GetLatestVCRedist","kind":"function","vis":"private","signature":"Private Function GetLatestVCRedist() As String","returnType":"String"}, + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "StringBuilder", + "kind": "class", + "vis": "public", + "file": "Utilities/StringBuilder.twin", + "signature": "Public Class StringBuilder", + "attrs": [{"name":"ClassId","args":"\"E5F6A7B8-C9D0-E1F2-A3B4-C5D6E7F8A9B0\""},{"name":"InterfaceId","args":"\"E5F6A7B8-C9D0-E1F2-A3B4-C5D6E7F8A9B1\""}], + "members": [ + {"name":"Append","kind":"function","vis":"public","signature":"Public Function Append(ByVal text As String) As StringBuilder","returnType":"StringBuilder"}, + {"name":"AppendFormat","kind":"function","vis":"public","signature":"Public Function AppendFormat(ByVal format As String, ParamArray args() As Variant) As StringBuilder","returnType":"StringBuilder"}, + {"name":"AppendLine","kind":"function","vis":"public","signature":"Public Function AppendLine(Optional ByVal text As String = \"\") As StringBuilder","returnType":"StringBuilder"}, + {"name":"AppendRepeat","kind":"function","vis":"public","signature":"Public Function AppendRepeat(ByVal char As String, ByVal count As Long) As StringBuilder","returnType":"StringBuilder"}, + {"name":"Capacity","kind":"property","vis":"public","signature":"Public Property Get Capacity() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"EnsureCapacity","kind":"sub","vis":"private","signature":"Private Sub EnsureCapacity(ByVal minCapacity As Long)"}, + {"name":"GROWTH_FACTOR","kind":"const","vis":"private","signature":"Private Const GROWTH_FACTOR As Long = 2"}, + {"name":"INITIAL_CAPACITY","kind":"const","vis":"private","signature":"Private Const INITIAL_CAPACITY As Long = 256"}, + {"name":"Insert","kind":"function","vis":"public","signature":"Public Function Insert(ByVal index As Long, ByVal text As String) As StringBuilder","returnType":"StringBuilder"}, + {"name":"Length","kind":"property","vis":"public","signature":"Public Property Get Length() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Length","kind":"property","vis":"public","signature":"Public Property Let Length(ByVal value As Long)","accessor":"Let"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal initialCapacity As Long)"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As String"}, + {"name":"m_Capacity","kind":"field","vis":"private","signature":"Private m_Capacity As Long"}, + {"name":"m_Length","kind":"field","vis":"private","signature":"Private m_Length As Long"} + ] + }, + { + "name": "TextFormatter", + "kind": "class", + "vis": "public", + "file": "Formatters/TextFormatter.twin", + "signature": "Public Class TextFormatter", + "implements": ["IOutputFormatter"], + "attrs": [{"name":"ClassId","args":"\"20A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"20A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"BOX_WIDTH","kind":"const","vis":"private","signature":"Private Const BOX_WIDTH As Long = 50"}, + {"name":"GetBottomBorder","kind":"function","vis":"private","signature":"Private Function GetBottomBorder() As String","returnType":"String"}, + {"name":"GetCategoryFooter","kind":"function","vis":"private","signature":"Private Function GetCategoryFooter() As String","returnType":"String"}, + {"name":"GetCategoryHeader","kind":"function","vis":"private","signature":"Private Function GetCategoryHeader(ByVal categoryName As String) As String","returnType":"String"}, + {"name":"GetCenteredLine","kind":"function","vis":"private","signature":"Private Function GetCenteredLine(ByVal text As String) As String","returnType":"String"}, + {"name":"GetItemLine","kind":"function","vis":"private","signature":"Private Function GetItemLine(ByVal key As String, ByVal value As String) As String","returnType":"String"}, + {"name":"GetTopBorder","kind":"function","vis":"private","signature":"Private Function GetTopBorder() As String","returnType":"String"}, + {"name":"IOutputFormatter_BeginFormat","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_BeginFormat()"}, + {"name":"IOutputFormatter_EndCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_EndCategory()"}, + {"name":"IOutputFormatter_EndFormat","kind":"function","vis":"public","signature":"Public Function IOutputFormatter_EndFormat() As String","returnType":"String"}, + {"name":"IOutputFormatter_FileExtension","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FileExtension() As String","returnType":"String","accessor":"Get"}, + {"name":"IOutputFormatter_FormatCategory","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatCategory(ByVal categoryName As String)"}, + {"name":"IOutputFormatter_FormatItem","kind":"sub","vis":"public","signature":"Public Sub IOutputFormatter_FormatItem(ByVal key As String, ByVal value As String)"}, + {"name":"IOutputFormatter_FormatterName","kind":"property","vis":"public","signature":"Public Property Get IOutputFormatter_FormatterName() As String","returnType":"String","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"m_Builder","kind":"field","vis":"private","signature":"Private m_Builder As StringBuilder"}, + {"name":"m_InCategory","kind":"field","vis":"private","signature":"Private m_InCategory As Boolean"} + ] + }, + { + "name": "UptimeCollector", + "kind": "class", + "vis": "public", + "file": "Collectors/Dynamic/UptimeCollector.twin", + "signature": "Public Class UptimeCollector", + "implements": ["IDiagnosticCollector"], + "attrs": [{"name":"ClassId","args":"\"08A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6\""},{"name":"InterfaceId","args":"\"08A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C7\""}], + "members": [ + {"name":"IDiagnosticCollector_CategoryName","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_CategoryName() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_Collect","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_Collect() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_CollectSummary","kind":"function","vis":"public","signature":"Public Function IDiagnosticCollector_CollectSummary() As DiagnosticResult(Of KeyValuePair(Of String, String))","returnType":"DiagnosticResult(Of KeyValuePair(Of String, String))"}, + {"name":"IDiagnosticCollector_Description","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_Description() As String","returnType":"String","accessor":"Get"}, + {"name":"IDiagnosticCollector_RefreshMode","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RefreshMode() As RefreshMode","returnType":"RefreshMode","accessor":"Get"}, + {"name":"IDiagnosticCollector_RequiresElevation","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_RequiresElevation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IDiagnosticCollector_SupportsSummary","kind":"property","vis":"public","signature":"Public Property Get IDiagnosticCollector_SupportsSummary() As Boolean","returnType":"Boolean","accessor":"Get"} + ] + }, + { + "name": "WinAPI", + "kind": "module", + "vis": "public", + "file": "WinAPI/Declarations.twin", + "signature": "Public Module WinAPI", + "members": [ + {"name":"CloseHandle","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CloseHandle Lib \"kernel32.dll\" ( ByVal hObject As LongPtr) As Long","returnType":"Long"}, + {"name":"CreateToolhelp32Snapshot","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function CreateToolhelp32Snapshot Lib \"kernel32.dll\" ( ByVal dwFlags As Long, ByVal th32ProcessID As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"FileTimeToLongLong","kind":"function","vis":"public","signature":"Public Function FileTimeToLongLong(ByRef ft As FILETIME) As LongLong","returnType":"LongLong"}, + {"name":"FillConsoleOutputAttribute","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FillConsoleOutputAttribute Lib \"kernel32.dll\" ( ByVal hConsoleOutput As LongPtr, ByVal wAttribute As Integer, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfAttrsWritten As Long) As Long","returnType":"Long"}, + {"name":"FillConsoleOutputCharacterW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function FillConsoleOutputCharacterW Lib \"kernel32.dll\" ( ByVal hConsoleOutput As LongPtr, ByVal cCharacter As Integer, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfCharsWritten As Long) As Long","returnType":"Long"}, + {"name":"GetACP","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetACP Lib \"kernel32.dll\" () As Long","returnType":"Long"}, + {"name":"GetAdaptersInfo","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetAdaptersInfo Lib \"iphlpapi.dll\" ( ByVal pAdapterInfo As LongPtr, ByRef pOutBufLen As Long) As Long","returnType":"Long"}, + {"name":"GetComputerNameW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetComputerNameW Lib \"kernel32.dll\" ( ByVal lpBuffer As LongPtr, ByRef nSize As Long) As Long","returnType":"Long"}, + {"name":"GetConsoleScreenBufferInfo","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetConsoleScreenBufferInfo Lib \"kernel32.dll\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleScreenBufferInfo As CONSOLE_SCREEN_BUFFER_INFO) As Long","returnType":"Long"}, + {"name":"GetCurrentProcessId","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetCurrentProcessId Lib \"kernel32.dll\" () As Long","returnType":"Long"}, + {"name":"GetDiskFreeSpaceExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetDiskFreeSpaceExW Lib \"kernel32.dll\" ( ByVal lpDirectoryName As LongPtr, ByRef lpFreeBytesAvailableToCaller As LongLong, ByRef lpTotalNumberOfBytes As LongLong, ByRef lpTotalNumberOfFreeBytes As LongLong) As Long","returnType":"Long"}, + {"name":"GetDriveTypeW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetDriveTypeW Lib \"kernel32.dll\" ( ByVal lpRootPathName As LongPtr) As Long","returnType":"Long"}, + {"name":"GetLocaleInfoW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetLocaleInfoW Lib \"kernel32.dll\" ( ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As LongPtr, ByVal cchData As Long) As Long","returnType":"Long"}, + {"name":"GetLogicalDrives","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetLogicalDrives Lib \"kernel32.dll\" () As Long","returnType":"Long"}, + {"name":"GetNativeSystemInfo","kind":"sub","vis":"public","declare":true,"signature":"Public Declare PtrSafe Sub GetNativeSystemInfo Lib \"kernel32.dll\" ( ByRef lpSystemInfo As SYSTEM_INFO)"}, + {"name":"GetOEMCP","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetOEMCP Lib \"kernel32.dll\" () As Long","returnType":"Long"}, + {"name":"GetStdHandle","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetStdHandle Lib \"kernel32.dll\" ( ByVal nStdHandle As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"GetStringFromBuffer","kind":"function","vis":"public","signature":"Public Function GetStringFromBuffer(ByRef buffer() As Integer) As String","returnType":"String"}, + {"name":"GetStringFromByteBuffer","kind":"function","vis":"public","signature":"Public Function GetStringFromByteBuffer(ByRef buffer() As Byte) As String","returnType":"String"}, + {"name":"GetSystemTimes","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetSystemTimes Lib \"kernel32.dll\" ( ByRef lpIdleTime As FILETIME, ByRef lpKernelTime As FILETIME, ByRef lpUserTime As FILETIME) As Long","returnType":"Long"}, + {"name":"GetTickCount64","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetTickCount64 Lib \"kernel32.dll\" () As LongLong","returnType":"LongLong"}, + {"name":"GetTimeZoneInformation","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetTimeZoneInformation Lib \"kernel32.dll\" ( ByRef lpTimeZoneInformation As TIME_ZONE_INFORMATION) As Long","returnType":"Long"}, + {"name":"GetUserDefaultLCID","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetUserDefaultLCID Lib \"kernel32.dll\" () As Long","returnType":"Long"}, + {"name":"GetUserNameW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GetUserNameW Lib \"advapi32.dll\" ( ByVal lpBuffer As LongPtr, ByRef nSize As Long) As Long","returnType":"Long"}, + {"name":"GlobalMemoryStatusEx","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function GlobalMemoryStatusEx Lib \"kernel32.dll\" ( ByRef lpBuffer As MEMORYSTATUSEX) As Long","returnType":"Long"}, + {"name":"IsUserAnAdmin","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function IsUserAnAdmin Lib \"shell32.dll\" () As Long","returnType":"Long"}, + {"name":"MakeCOORD","kind":"function","vis":"public","signature":"Public Function MakeCOORD(ByVal X As Integer, ByVal Y As Integer) As Long","returnType":"Long"}, + {"name":"Process32FirstW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function Process32FirstW Lib \"kernel32.dll\" ( ByVal hSnapshot As LongPtr, ByRef lppe As PROCESSENTRY32W) As Long","returnType":"Long"}, + {"name":"Process32NextW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function Process32NextW Lib \"kernel32.dll\" ( ByVal hSnapshot As LongPtr, ByRef lppe As PROCESSENTRY32W) As Long","returnType":"Long"}, + {"name":"RegCloseKey","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RegCloseKey Lib \"advapi32.dll\" ( ByVal hKey As LongPtr) As Long","returnType":"Long"}, + {"name":"RegEnumKeyExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RegEnumKeyExW Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal dwIndex As Long, ByVal lpName As LongPtr, ByRef lpcName As Long, ByVal lpReserved As LongPtr, ByVal lpClass As LongPtr, ByVal lpcClass As LongPtr, ByVal lpftLastWriteTime As LongPtr) As Long","returnType":"Long"}, + {"name":"RegOpenKeyExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RegOpenKeyExW Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal lpSubKey As LongPtr, ByVal ulOptions As Long, ByVal samDesired As Long, ByRef phkResult As LongPtr) As Long","returnType":"Long"}, + {"name":"RegQueryValueExW","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RegQueryValueExW Lib \"advapi32.dll\" ( ByVal hKey As LongPtr, ByVal lpValueName As LongPtr, ByVal lpReserved As LongPtr, ByRef lpType As Long, ByVal lpData As LongPtr, ByRef lpcbData As Long) As Long","returnType":"Long"}, + {"name":"RtlGetVersion","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function RtlGetVersion Lib \"ntdll.dll\" ( ByRef lpVersionInformation As OSVERSIONINFOEXW) As Long","returnType":"Long"}, + {"name":"SetConsoleCtrlHandler","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetConsoleCtrlHandler Lib \"kernel32.dll\" ( ByVal HandlerRoutine As LongPtr, ByVal Add As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleCursorPosition","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetConsoleCursorPosition Lib \"kernel32.dll\" ( ByVal hConsoleOutput As LongPtr, ByVal dwCursorPosition As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleTextAttribute","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function SetConsoleTextAttribute Lib \"kernel32.dll\" ( ByVal hConsoleOutput As LongPtr, ByVal wAttributes As Integer) As Long","returnType":"Long"}, + {"name":"WSACleanup","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function WSACleanup Lib \"ws2_32.dll\" () As Long","returnType":"Long"}, + {"name":"WSAStartup","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function WSAStartup Lib \"ws2_32.dll\" ( ByVal wVersionRequested As Integer, ByVal lpWSAData As LongPtr) As Long","returnType":"Long"}, + {"name":"gethostname","kind":"function","vis":"public","declare":true,"signature":"Public Declare PtrSafe Function gethostname Lib \"ws2_32.dll\" ( ByVal name As LongPtr, ByVal namelen As Long) As Long","returnType":"Long"} + ] + }, + { + "name": "WinAPIStructures", + "kind": "module", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Module WinAPIStructures", + "members": [ + {"name":"BACKGROUND_BLUE","kind":"const","vis":"public","signature":"Public Const BACKGROUND_BLUE As Integer = &H10"}, + {"name":"BACKGROUND_GREEN","kind":"const","vis":"public","signature":"Public Const BACKGROUND_GREEN As Integer = &H20"}, + {"name":"BACKGROUND_INTENSITY","kind":"const","vis":"public","signature":"Public Const BACKGROUND_INTENSITY As Integer = &H80"}, + {"name":"BACKGROUND_RED","kind":"const","vis":"public","signature":"Public Const BACKGROUND_RED As Integer = &H40"}, + {"name":"DRIVE_CDROM","kind":"const","vis":"public","signature":"Public Const DRIVE_CDROM As Long = 5"}, + {"name":"DRIVE_FIXED","kind":"const","vis":"public","signature":"Public Const DRIVE_FIXED As Long = 3"}, + {"name":"DRIVE_NO_ROOT_DIR","kind":"const","vis":"public","signature":"Public Const DRIVE_NO_ROOT_DIR As Long = 1"}, + {"name":"DRIVE_RAMDISK","kind":"const","vis":"public","signature":"Public Const DRIVE_RAMDISK As Long = 6"}, + {"name":"DRIVE_REMOTE","kind":"const","vis":"public","signature":"Public Const DRIVE_REMOTE As Long = 4"}, + {"name":"DRIVE_REMOVABLE","kind":"const","vis":"public","signature":"Public Const DRIVE_REMOVABLE As Long = 2"}, + {"name":"DRIVE_UNKNOWN","kind":"const","vis":"public","signature":"Public Const DRIVE_UNKNOWN As Long = 0"}, + {"name":"FOREGROUND_BLUE","kind":"const","vis":"public","signature":"Public Const FOREGROUND_BLUE As Integer = &H1"}, + {"name":"FOREGROUND_GREEN","kind":"const","vis":"public","signature":"Public Const FOREGROUND_GREEN As Integer = &H2"}, + {"name":"FOREGROUND_INTENSITY","kind":"const","vis":"public","signature":"Public Const FOREGROUND_INTENSITY As Integer = &H8"}, + {"name":"FOREGROUND_RED","kind":"const","vis":"public","signature":"Public Const FOREGROUND_RED As Integer = &H4"}, + {"name":"HKEY_CURRENT_USER","kind":"const","vis":"public","signature":"Public Const HKEY_CURRENT_USER As LongPtr = &H80000001"}, + {"name":"HKEY_LOCAL_MACHINE","kind":"const","vis":"public","signature":"Public Const HKEY_LOCAL_MACHINE As LongPtr = &H80000002"}, + {"name":"KEY_READ","kind":"const","vis":"public","signature":"Public Const KEY_READ As Long = &H20019"}, + {"name":"KEY_WOW64_32KEY","kind":"const","vis":"public","signature":"Public Const KEY_WOW64_32KEY As Long = &H200"}, + {"name":"KEY_WOW64_64KEY","kind":"const","vis":"public","signature":"Public Const KEY_WOW64_64KEY As Long = &H100"}, + {"name":"LOCALE_SENGLISHCOUNTRYNAME","kind":"const","vis":"public","signature":"Public Const LOCALE_SENGLISHCOUNTRYNAME As Long = &H1002"}, + {"name":"LOCALE_SENGLISHLANGUAGENAME","kind":"const","vis":"public","signature":"Public Const LOCALE_SENGLISHLANGUAGENAME As Long = &H1001"}, + {"name":"LOCALE_SISO3166CTRYNAME","kind":"const","vis":"public","signature":"Public Const LOCALE_SISO3166CTRYNAME As Long = &H5A"}, + {"name":"LOCALE_SISO639LANGNAME","kind":"const","vis":"public","signature":"Public Const LOCALE_SISO639LANGNAME As Long = &H59"}, + {"name":"PROCESSOR_ARCHITECTURE_AMD64","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_AMD64 As Integer = 9"}, + {"name":"PROCESSOR_ARCHITECTURE_ARM","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_ARM As Integer = 5"}, + {"name":"PROCESSOR_ARCHITECTURE_ARM64","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_ARM64 As Integer = 12"}, + {"name":"PROCESSOR_ARCHITECTURE_IA64","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_IA64 As Integer = 6"}, + {"name":"PROCESSOR_ARCHITECTURE_INTEL","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_INTEL As Integer = 0"}, + {"name":"PROCESSOR_ARCHITECTURE_UNKNOWN","kind":"const","vis":"public","signature":"Public Const PROCESSOR_ARCHITECTURE_UNKNOWN As Integer = &HFFFF"}, + {"name":"REG_DWORD","kind":"const","vis":"public","signature":"Public Const REG_DWORD As Long = 4"}, + {"name":"REG_EXPAND_SZ","kind":"const","vis":"public","signature":"Public Const REG_EXPAND_SZ As Long = 2"}, + {"name":"REG_QWORD","kind":"const","vis":"public","signature":"Public Const REG_QWORD As Long = 11"}, + {"name":"REG_SZ","kind":"const","vis":"public","signature":"Public Const REG_SZ As Long = 1"}, + {"name":"STD_ERROR_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_ERROR_HANDLE As Long = -12"}, + {"name":"STD_INPUT_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_INPUT_HANDLE As Long = -10"}, + {"name":"STD_OUTPUT_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_OUTPUT_HANDLE As Long = -11"}, + {"name":"TH32CS_SNAPPROCESS","kind":"const","vis":"public","signature":"Public Const TH32CS_SNAPPROCESS As Long = &H2"} + ] + }, + { + "name": "CONSOLE_SCREEN_BUFFER_INFO", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type CONSOLE_SCREEN_BUFFER_INFO", + "fields": [ + {"name":"dwSize","vis":"public","signature":"dwSize As COORD"}, + {"name":"dwCursorPosition","vis":"public","signature":"dwCursorPosition As COORD"}, + {"name":"wAttributes","vis":"public","signature":"wAttributes As Integer"}, + {"name":"srWindow","vis":"public","signature":"srWindow As SMALL_RECT"}, + {"name":"dwMaximumWindowSize","vis":"public","signature":"dwMaximumWindowSize As COORD"} + ], + "members": [] + }, + { + "name": "COORD", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type COORD", + "fields": [ + {"name":"X","vis":"public","signature":"X As Integer"}, + {"name":"Y","vis":"public","signature":"Y As Integer"} + ], + "members": [] + }, + { + "name": "FILETIME", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type FILETIME", + "fields": [ + {"name":"dwLowDateTime","vis":"public","signature":"dwLowDateTime As Long"}, + {"name":"dwHighDateTime","vis":"public","signature":"dwHighDateTime As Long"} + ], + "members": [] + }, + { + "name": "IP_ADAPTER_INFO", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type IP_ADAPTER_INFO", + "fields": [ + {"name":"pNext","vis":"public","signature":"pNext As LongPtr"}, + {"name":"ComboIndex","vis":"public","signature":"ComboIndex As Long"}, + {"name":"AdapterName","vis":"public","signature":"AdapterName(0 To 259) As Byte"}, + {"name":"Description","vis":"public","signature":"Description(0 To 131) As Byte"}, + {"name":"AddressLength","vis":"public","signature":"AddressLength As Long"}, + {"name":"Address","vis":"public","signature":"Address(0 To 7) As Byte"}, + {"name":"Index","vis":"public","signature":"Index As Long"}, + {"name":"dwType","vis":"public","signature":"dwType As Long"}, + {"name":"DhcpEnabled","vis":"public","signature":"DhcpEnabled As Long"}, + {"name":"CurrentIpAddress","vis":"public","signature":"CurrentIpAddress As LongPtr"}, + {"name":"IpAddressList","vis":"public","signature":"IpAddressList As IP_ADDR_STRING"}, + {"name":"GatewayList","vis":"public","signature":"GatewayList As IP_ADDR_STRING"}, + {"name":"DhcpServer","vis":"public","signature":"DhcpServer As IP_ADDR_STRING"}, + {"name":"HaveWins","vis":"public","signature":"HaveWins As Long"}, + {"name":"PrimaryWinsServer","vis":"public","signature":"PrimaryWinsServer As IP_ADDR_STRING"}, + {"name":"SecondaryWinsServer","vis":"public","signature":"SecondaryWinsServer As IP_ADDR_STRING"}, + {"name":"LeaseObtained","vis":"public","signature":"LeaseObtained As Long"}, + {"name":"LeaseExpires","vis":"public","signature":"LeaseExpires As Long"} + ], + "members": [] + }, + { + "name": "IP_ADDR_STRING", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type IP_ADDR_STRING", + "fields": [ + {"name":"pNext","vis":"public","signature":"pNext As LongPtr"}, + {"name":"IpAddress","vis":"public","signature":"IpAddress(0 To 15) As Byte"}, + {"name":"IpMask","vis":"public","signature":"IpMask(0 To 15) As Byte"}, + {"name":"Context","vis":"public","signature":"Context As Long"} + ], + "members": [] + }, + { + "name": "MEMORYSTATUSEX", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type MEMORYSTATUSEX", + "fields": [ + {"name":"dwLength","vis":"public","signature":"dwLength As Long"}, + {"name":"dwMemoryLoad","vis":"public","signature":"dwMemoryLoad As Long"}, + {"name":"ullTotalPhys","vis":"public","signature":"ullTotalPhys As LongLong"}, + {"name":"ullAvailPhys","vis":"public","signature":"ullAvailPhys As LongLong"}, + {"name":"ullTotalPageFile","vis":"public","signature":"ullTotalPageFile As LongLong"}, + {"name":"ullAvailPageFile","vis":"public","signature":"ullAvailPageFile As LongLong"}, + {"name":"ullTotalVirtual","vis":"public","signature":"ullTotalVirtual As LongLong"}, + {"name":"ullAvailVirtual","vis":"public","signature":"ullAvailVirtual As LongLong"}, + {"name":"ullAvailExtendedVirtual","vis":"public","signature":"ullAvailExtendedVirtual As LongLong"} + ], + "members": [] + }, + { + "name": "OSVERSIONINFOEXW", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type OSVERSIONINFOEXW", + "fields": [ + {"name":"dwOSVersionInfoSize","vis":"public","signature":"dwOSVersionInfoSize As Long"}, + {"name":"dwMajorVersion","vis":"public","signature":"dwMajorVersion As Long"}, + {"name":"dwMinorVersion","vis":"public","signature":"dwMinorVersion As Long"}, + {"name":"dwBuildNumber","vis":"public","signature":"dwBuildNumber As Long"}, + {"name":"dwPlatformId","vis":"public","signature":"dwPlatformId As Long"}, + {"name":"szCSDVersion","vis":"public","signature":"szCSDVersion(0 To 127) As Integer"}, + {"name":"wServicePackMajor","vis":"public","signature":"wServicePackMajor As Integer"}, + {"name":"wServicePackMinor","vis":"public","signature":"wServicePackMinor As Integer"}, + {"name":"wSuiteMask","vis":"public","signature":"wSuiteMask As Integer"}, + {"name":"wProductType","vis":"public","signature":"wProductType As Byte"}, + {"name":"wReserved","vis":"public","signature":"wReserved As Byte"} + ], + "members": [] + }, + { + "name": "PROCESSENTRY32W", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type PROCESSENTRY32W", + "fields": [ + {"name":"dwSize","vis":"public","signature":"dwSize As Long"}, + {"name":"cntUsage","vis":"public","signature":"cntUsage As Long"}, + {"name":"th32ProcessID","vis":"public","signature":"th32ProcessID As Long"}, + {"name":"th32DefaultHeapID","vis":"public","signature":"th32DefaultHeapID As LongPtr"}, + {"name":"th32ModuleID","vis":"public","signature":"th32ModuleID As Long"}, + {"name":"cntThreads","vis":"public","signature":"cntThreads As Long"}, + {"name":"th32ParentProcessID","vis":"public","signature":"th32ParentProcessID As Long"}, + {"name":"pcPriClassBase","vis":"public","signature":"pcPriClassBase As Long"}, + {"name":"dwFlags","vis":"public","signature":"dwFlags As Long"}, + {"name":"szExeFile","vis":"public","signature":"szExeFile(0 To 259) As Integer"} + ], + "members": [] + }, + { + "name": "SMALL_RECT", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type SMALL_RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Integer"}, + {"name":"Top","vis":"public","signature":"Top As Integer"}, + {"name":"Right","vis":"public","signature":"Right As Integer"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Integer"} + ], + "members": [] + }, + { + "name": "SYSTEMTIME", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type SYSTEMTIME", + "fields": [ + {"name":"wYear","vis":"public","signature":"wYear As Integer"}, + {"name":"wMonth","vis":"public","signature":"wMonth As Integer"}, + {"name":"wDayOfWeek","vis":"public","signature":"wDayOfWeek As Integer"}, + {"name":"wDay","vis":"public","signature":"wDay As Integer"}, + {"name":"wHour","vis":"public","signature":"wHour As Integer"}, + {"name":"wMinute","vis":"public","signature":"wMinute As Integer"}, + {"name":"wSecond","vis":"public","signature":"wSecond As Integer"}, + {"name":"wMilliseconds","vis":"public","signature":"wMilliseconds As Integer"} + ], + "members": [] + }, + { + "name": "SYSTEM_INFO", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type SYSTEM_INFO", + "fields": [ + {"name":"wProcessorArchitecture","vis":"public","signature":"wProcessorArchitecture As Integer"}, + {"name":"wReserved","vis":"public","signature":"wReserved As Integer"}, + {"name":"dwPageSize","vis":"public","signature":"dwPageSize As Long"}, + {"name":"lpMinimumApplicationAddress","vis":"public","signature":"lpMinimumApplicationAddress As LongPtr"}, + {"name":"lpMaximumApplicationAddress","vis":"public","signature":"lpMaximumApplicationAddress As LongPtr"}, + {"name":"dwActiveProcessorMask","vis":"public","signature":"dwActiveProcessorMask As LongPtr"}, + {"name":"dwNumberOfProcessors","vis":"public","signature":"dwNumberOfProcessors As Long"}, + {"name":"dwProcessorType","vis":"public","signature":"dwProcessorType As Long"}, + {"name":"dwAllocationGranularity","vis":"public","signature":"dwAllocationGranularity As Long"}, + {"name":"wProcessorLevel","vis":"public","signature":"wProcessorLevel As Integer"}, + {"name":"wProcessorRevision","vis":"public","signature":"wProcessorRevision As Integer"} + ], + "members": [] + }, + { + "name": "TIME_ZONE_INFORMATION", + "kind": "type", + "in": "WinAPIStructures", + "vis": "public", + "file": "WinAPI/Structures.twin", + "signature": "Public Type TIME_ZONE_INFORMATION", + "fields": [ + {"name":"Bias","vis":"public","signature":"Bias As Long"}, + {"name":"StandardName","vis":"public","signature":"StandardName(0 To 31) As Integer"}, + {"name":"StandardDate","vis":"public","signature":"StandardDate As SYSTEMTIME"}, + {"name":"StandardBias","vis":"public","signature":"StandardBias As Long"}, + {"name":"DaylightName","vis":"public","signature":"DaylightName(0 To 31) As Integer"}, + {"name":"DaylightDate","vis":"public","signature":"DaylightDate As SYSTEMTIME"}, + {"name":"DaylightBias","vis":"public","signature":"DaylightBias As Long"} + ], + "members": [] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings b/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings new file mode 100644 index 000000000..584dded2e --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB_Diagnostic/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{CD7AAA1E-5337-41A2-9B5A-3586DAB5CFB2}", + "licence": "", + "name": "[IMPORTED] TBMAN: Diagnostic tools output with JSON/HTML/TEXT", + "path32": "/Packages/TBMANLIB_Diagnostic", + "path64": "/Packages/TBMANLIB_Diagnostic", + "symbolId": "TBMANLIB_Diagnostic", + "versionBuild": "0", + "versionMajor": "0", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 787f7372e6b67f42290bd4764a70b22915ef57d0 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 17:59:11 +0200 Subject: [PATCH 14/15] tsJson: a contributed section of 11 pages (Json, JsonObject, JsonArray, JsonOptions, JsonError, the JSONPath and schema classes), with its snapshot, sample template and book chapter; its departures from RFC 9485, 9535 and 6901 and its known problems each shown by a sample that runs --- WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Reference/Contributed.md | 1 + docs/Reference/Contributed/tsJson/Json.md | 1196 ++++++++++++++ .../Reference/Contributed/tsJson/JsonArray.md | 296 ++++ .../Reference/Contributed/tsJson/JsonError.md | 235 +++ docs/Reference/Contributed/tsJson/JsonHit.md | 55 + .../Contributed/tsJson/JsonObject.md | 342 ++++ .../Contributed/tsJson/JsonOptions.md | 396 +++++ .../Contributed/tsJson/JsonPathQuery.md | 195 +++ .../Contributed/tsJson/JsonSchema.md | 184 +++ .../Contributed/tsJson/JsonValidationError.md | 126 ++ .../tsJson/JsonValidationResult.md | 89 ++ docs/Reference/Contributed/tsJson/index.md | 324 ++++ docs/_book.yml | 7 + indexer/snapshots/contributed/tsJson/api.json | 1416 +++++++++++++++++ .../contributed-tsJson/Settings | 74 + 17 files changed, 4938 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/tsJson/Json.md create mode 100644 docs/Reference/Contributed/tsJson/JsonArray.md create mode 100644 docs/Reference/Contributed/tsJson/JsonError.md create mode 100644 docs/Reference/Contributed/tsJson/JsonHit.md create mode 100644 docs/Reference/Contributed/tsJson/JsonObject.md create mode 100644 docs/Reference/Contributed/tsJson/JsonOptions.md create mode 100644 docs/Reference/Contributed/tsJson/JsonPathQuery.md create mode 100644 docs/Reference/Contributed/tsJson/JsonSchema.md create mode 100644 docs/Reference/Contributed/tsJson/JsonValidationError.md create mode 100644 docs/Reference/Contributed/tsJson/JsonValidationResult.md create mode 100644 docs/Reference/Contributed/tsJson/index.md create mode 100644 indexer/snapshots/contributed/tsJson/api.json create mode 100644 test/example-projects/contributed-tsJson/Settings diff --git a/WIP.md b/WIP.md index 08be57a9e..98f36dc2f 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-three **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, which its landing page describes in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-four **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 3c6787feb..41d51c0a7 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1218, + "pages": 1229, "staticFiles": 933 } diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index 0f0b5f091..48ceb06e7 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -23,6 +23,7 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**TBMANLIB_RES**](TBMANLIB_RES/) -- resources embedded in an executable, as bytes, strings, hex, Base64 or pictures - [**tbProjPacker**](tbProjPacker/) -- a `.twinproj` or `.twinpack` file unpacked into a folder of source files, and packed again - [**tbRegExp**](tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object +- [**tsJson**](tsJson/) -- a JSON model with parsing, JSONPath queries, JSON Pointer, JSON Patch and JSON Schema validation - [**tsPwGen**](tsPwGen/) -- passwords, PINs and passphrases, with their entropy known before they are generated - [**TwinTimerPackage**](TwinTimerPackage/) -- a Win32 timer class that reports the elapsed time - [**VBA_JSON**](VBA_JSON/) -- JSON parsing and serialization, with UTC and ISO 8601 date conversion diff --git a/docs/Reference/Contributed/tsJson/Json.md b/docs/Reference/Contributed/tsJson/Json.md new file mode 100644 index 000000000..79a8fe317 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/Json.md @@ -0,0 +1,1196 @@ +--- +title: Json +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/Json +has_toc: false +--- + +# Json module +{: .no_toc } + +The **Json** module is the entry point of the package. It builds JSON values, reads and writes them as text, bytes and files, finds and changes parts of them with JSON Pointer, JSONPath and JSON Patch, validates them against a JSON Schema, and converts ISO 8601 text. A JSON object is a [**JsonObject**](JsonObject) and a JSON array is a [**JsonArray**](JsonArray). The other JSON values are plain **Variant** values, as [The JSON model](.#the-json-model) describes. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""name"":""Ann"",""tags"":[""a"",""b""]}") +Debug.Print Json.QueryOne(doc, "$.tags[1]") ' b +Debug.Print Json.PointerGet(doc, "/name") ' Ann + +Dim changed As Variant = Json.PointerSet(doc, "/name", "Bea") +Debug.Print Json.Serialize(changed, JsonOptions.Minified) ' {"name":"Bea","tags":["a","b"]} +Debug.Print Json.Serialize(doc, JsonOptions.Minified) ' {"name":"Ann","tags":["a","b"]} +``` + +The module has no state and no settings. Options for one call are a [**JsonOptions**](JsonOptions) object that is passed as the last argument. **Nothing**, the default, means the RFC 8259 defaults of [**JsonOptions.Rfc**](JsonOptions#rfc), except where a function below says otherwise. + +**Errors.** The functions of the module do not raise run-time errors for bad data or bad arguments. A function that fails returns an empty result (**Empty**, **Nothing**, an empty string, **False** or an unbound array) and sets the last error, which the predeclared object [**JsonError**](JsonError) reports. A function that can fail clears the last error when it starts, so the last error always belongs to the latest such call. The members that cannot fail neither clear nor set it: [**TypeOfValue**](#typeofvalue), [**IsObject**](#isobject), [**IsArray**](#isarray), [**Equals**](#equals), [**PointerEscape**](#pointerescape), [**PointerUnescape**](#pointerunescape), [**IsValidPattern**](#isvalidpattern), [**IsMatchPattern**](#ismatchpattern), [**IsSearchPattern**](#issearchpattern) and [**FormatIso8601**](#formatiso8601). The origin of an error is the name of the function that was called, such as `Json.Parse`. The functions [**TryParse**](#tryparse), [**TryParseFile**](#tryparsefile), [**TryParseLines**](#tryparselines) and [**TryParseLinesFile**](#tryparselinesfile) report the origin of the function they call: an error of **TryParse** has the origin `Json.Parse`. + +> [!NOTE] +> A function that finds nothing is not an error when its name says so (**PointerExists**, **PointerTryGet**, the `Try` functions and the JSONPath queries). The `Try` functions return **False**, and a JSONPath query that matches nothing returns an empty result with no error. Check [**JsonError.HasError**](JsonError#haserror), not **IsEmpty**, after a function that can return a JSON null: [**Parse**](#parse) returns **Null** for the text `null`, and **Empty** only for a failure. + +* TOC +{:toc} + +## Types + +### eJsonType +{: .no_toc } + +The JSON type of a value, as [**TypeOfValue**](#typeofvalue) and [**JsonObject.ValueType**](JsonObject#valuetype) report it. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **jtUnknown** | 0 | Not a JSON value: **Empty**, **Nothing**, a **Date**, any object other than a **JsonObject** or a **JsonArray**, and a **Variant** array. | +| **jtNull** | 1 | **Null**. | +| **jtBoolean** | 2 | A **Boolean**. | +| **jtNumber** | 3 | A number of any numeric type: **Byte**, **Integer**, **Long**, **LongLong**, **Single**, **Double**, **Currency** or **Decimal**. | +| **jtString** | 4 | A **String**. | +| **jtArray** | 5 | A [**JsonArray**](JsonArray). | +| **jtObject** | 6 | A [**JsonObject**](JsonObject). | + +## Building values + +### Map +{: .no_toc } + +Returns an empty object, or an object made from pairs of a key and a value. + +Syntax: **Json.Map** ( [ *key1*, *value1* [ , *key2*, *value2* ... ] ] ) **As** [**JsonObject**](JsonObject) + +*key1*, *key2* ... +: *optional* A **String** that names a member. Any other type is an error. + +*value1*, *value2* ... +: *optional* The value of the member before it: a **String**, a number, a **Boolean**, **Null**, a [**JsonObject**](JsonObject) or a [**JsonArray**](JsonArray). The value is stored as it is; it is not checked and, for an object, not copied. + +With no arguments the result is an empty object. The members are in the order of the arguments. If a key appears twice, the second value replaces the first, and the member keeps the place of the first. The function returns **Nothing** and sets the error `InvalidArgument` when the number of arguments is odd or a key is not a **String**. + +```tb check_build check_run +Dim o As JsonObject = Json.Map("name", "Ann", "age", 31, "tags", Json.Arr("a", "b"), "note", Null) +Debug.Print o.Count ' 4 +Debug.Print Json.Serialize(o, JsonOptions.Minified) ' {"name":"Ann","age":31,"tags":["a","b"],"note":null} + +Dim bad As JsonObject = Json.Map("key") +Debug.Print bad Is Nothing ' True +Debug.Print JsonError.Code ' InvalidArgument +Debug.Print JsonError.Origin ' Json.Map +``` + +### Arr +{: .no_toc } + +Returns an empty array, or an array of the arguments. + +Syntax: **Json.Arr** ( [ *value1* [ , *value2* ... ] ] ) **As** [**JsonArray**](JsonArray) + +*value1*, *value2* ... +: *optional* The elements, in order. Each is stored as it is, as [**JsonArray.Add**](JsonArray#add) stores it. + +```tb check_build check_run +Dim a As JsonArray = Json.Arr(1, "two", True, Null, Json.Arr(), Json.Map()) +Debug.Print a.Count ' 6 +Debug.Print Json.Serialize(a, JsonOptions.Minified) ' [1,"two",true,null,[],{}] +``` + +### TypeOfValue +{: .no_toc } + +Returns the JSON type of a value. + +Syntax: **Json.TypeOfValue** ( *value* ) **As** [**eJsonType**](#ejsontype) + +*value* +: *required* Any **Variant**. + +A value that is not a JSON value, including a **Date**, is **jtUnknown**. The function does not change the last error. + +```tb check_build check_run +Debug.Print Json.TypeOfValue(Null) = jtNull ' True +Debug.Print Json.TypeOfValue(3.5) = jtNumber ' True +Debug.Print Json.TypeOfValue("x") = jtString ' True +Debug.Print Json.TypeOfValue(Json.Arr()) = jtArray ' True +Debug.Print Json.TypeOfValue(Json.Map()) = jtObject ' True +Debug.Print Json.TypeOfValue(Empty) = jtUnknown ' True +Debug.Print Json.TypeOfValue(#18-Aug-2026#) = jtUnknown ' True +``` + +### IsObject +{: .no_toc } + +Returns whether a value is a [**JsonObject**](JsonObject). + +Syntax: **Json.IsObject** ( *value* ) **As Boolean** + +*value* +: *required* Any **Variant**. + +The function does not change the last error. A **Variant** that holds an array of values is not a [**JsonArray**](JsonArray), so [**IsArray**](#isarray) is **False** for it. + +### IsArray +{: .no_toc } + +Returns whether a value is a [**JsonArray**](JsonArray). + +Syntax: **Json.IsArray** ( *value* ) **As Boolean** + +*value* +: *required* Any **Variant**. + +The function does not change the last error. The result is **False** for a **Variant** that holds an array of values, such as the result of [**Query**](#query), although [**Serialize**](#serialize) writes such an array as a JSON array. + +```tb check_build check_run +Dim v As Variant = Json.Parse("[1,2]") +Debug.Print Json.IsArray(v) ' True +Debug.Print Json.IsObject(v) ' False + +Dim plain(0 To 1) As Variant +Debug.Print Json.IsArray(plain) ' False +``` + +### Clone +{: .no_toc } + +Returns a deep copy of a value. + +Syntax: **Json.Clone** ( *value* ) **As Variant** + +*value* +: *required* The value to copy. + +Every [**JsonObject**](JsonObject) and [**JsonArray**](JsonArray) inside the value is copied, so the copy shares no container with the original. Other values are copied by value, and an object that is not part of the model is copied by reference. + +A read does not copy: [**JsonObject.Item**](JsonObject#item), [**PointerGet**](#pointerget) and [**Query**](#query) return the container that is inside the document, and a change made through it changes the document. The functions that return a new document, [**PointerSet**](#pointerset), [**PointerRemove**](#pointerremove), [**Patch**](#patch), [**MergePatch**](#mergepatch) and [**JsonObject.Merge**](JsonObject#merge), leave their argument unchanged. + +```tb check_build check_run +Dim a As JsonObject = Json.Map("list", Json.Arr(1, 2)) +Dim b As JsonObject = Json.Clone(a) +b.Arr("list").Add 3 +Debug.Print a.Arr("list").Count ' 2 +Debug.Print b.Arr("list").Count ' 3 + +Dim c As JsonObject = a +c.Arr("list").Add 9 +Debug.Print a.Arr("list").Count ' 3 +``` + +### Equals +{: .no_toc } + +Returns whether two values are equal as JSON values. + +Syntax: **Json.Equals** ( *a*, *b* ) **As Boolean** + +*a*, *b* +: *required* The values to compare. + +Two values are equal when they have the same JSON type and the same content. Numbers are compared by value, so `1` and `1.0` are equal whatever their types are. Strings are compared by character code, so `a` and `A` differ. Arrays are equal when they have the same length and equal elements in the same order. Objects are equal when they have the same keys and equal values; the order of the keys does not matter. Two values that are not JSON values are equal only when both are **Empty** or both are **Nothing**; in particular, two **Date** values are never equal, even when they hold the same date. + +The function does not change the last error. The patch functions use it to compare values: [**Patch**](#patch) for the `test` operation, and [**Diff**](#diff). + +```tb check_build check_run +Dim a As Variant = Json.Parse("{""x"":1.0,""y"":[true,null]}") +Dim b As Variant = Json.Parse("{""y"":[true,null],""x"":1}") +Debug.Print Json.Equals(a, b) ' True +Debug.Print Json.Equals(1, 1.0) ' True +Debug.Print Json.Equals("1", 1) ' False +Debug.Print Json.Equals(Json.Arr(1, 2), Json.Arr(2, 1)) ' False +Debug.Print Json.Equals(#18-Aug-2026#, #18-Aug-2026#) ' False +``` + +## Parsing + +### Parse +{: .no_toc } + +Parses JSON text or JSON bytes into a value. + +Syntax: **Json.Parse** ( *json* [ , *options* ] ) **As Variant** + +*json* +: *required* The document: a **String**, or a **Byte** array that holds UTF-8 or UTF-16 text. Any other type is the error `WrongType`. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. **Nothing**, the default, means [**JsonOptions.Rfc**](JsonOptions#rfc). The syntax, the encoding of bytes, the policy for duplicate keys and the limits come from it. + +Returns the root value of the document: a [**JsonObject**](JsonObject), a [**JsonArray**](JsonArray), a **String**, a **Decimal**, a **Boolean** or **Null**. Returns **Empty** and sets the last error when the text is not valid, the input exceeds a limit, or the bytes cannot be decoded. + +The text must be one JSON value as RFC 8259 defines it, with only the white space characters space, tab, line feed and carriage return around it. With [**Syntax**](JsonOptions#syntax) set to **jsJsonc**, comments and a comma after the last element are also accepted. Details of the reading: + +- **Numbers** become **Decimal** values, with no loss of digits and independent of the regional settings. A number that a **Decimal** cannot hold is an `InvalidNumber` error: one whose digits, read as a whole number, are above 79228162514264337593543950335, or whose scale is outside the range of **Decimal**. The scale is the exponent written, less the number of digits after the decimal point, and it must be from -28 to 28. The documents `1e29`, `1e-29`, `79228162514264337593543950336`, and `3.14159265358979323846264338327950288` (36 digits) are rejected, and so is `1.7976931348623157e308`, the largest **Double**. The documents `1e28`, `1e-28`, `79228162514264337593543950335`, `12345678901234567890123456789` and `0.0000000000000000000000000001` are read in full. The package's own tests treat this as a rule of the package, and RFC 8259 lets an implementation limit the range and precision of numbers; but a document that other JSON software writes can hold a number that this package rejects (see [Known problems](.#known-problems-in-version-10150)). A number is read with its exponent and fraction applied, so `1.5E3` is `1500`, `1.0` is `1`, `0.10` is `0.1` and `-0` is `0`. +- **Strings** are read with the escapes of RFC 8259, including `\u` escapes and surrogate pairs. An unescaped control character (below U+0020) is an `InvalidSyntax` error. A `\u` escape of a high surrogate that is not followed by a low surrogate escape, and a lone low surrogate, are an `InvalidEscape` error. +- **Dates** are never created. A JSON string that holds a date stays a **String**; [**ParseIso8601**](#parseiso8601) converts it. +- **Duplicate keys** in an object are the error `DuplicateKey` unless [**DuplicateKeys**](JsonOptions#duplicatekeys) is **jdkLastWins**. With **jdkLastWins** the last value is kept, in the position of the first. +- **Nesting** is limited by [**MaxDepth**](JsonOptions#maxdepth): the root is at depth 0, each value inside a container is one level deeper than the container, and a value deeper than **MaxDepth**, a scalar as well as a container, is a `DepthLimitExceeded` error. The other limits are [**MaxBytes**](JsonOptions#maxbytes), [**MaxStringLength**](JsonOptions#maxstringlength), [**MaxArrayItems**](JsonOptions#maxarrayitems) and [**MaxObjectMembers**](JsonOptions#maxobjectmembers); exceeding one is a `LimitExceeded` error. +- **A byte array** is decoded as [**Encoding**](JsonOptions#encoding) says: by default a byte order mark selects UTF-8, UTF-16 little endian or UTF-16 big endian, and UTF-8 is the choice without one. Bytes that are not valid in the encoding are an `InvalidEncoding` error. A **String** is not decoded, and a byte order mark at its start is not skipped: it is an `InvalidSyntax` error. + +```tb check_build check_run +Dim t As Variant +Dim v As Variant +For Each t In Array("1.5E3", "1.0", "0.10", "-0", "-1.25e-2", "1e28", "79228162514264337593543950335", "1e29", "1e-29", "3.14159265358979323846264338327950288") + v = Json.Parse(CStr(t)) + If JsonError.HasError Then + Debug.Print t & " -> " & JsonError.Code + Else + Debug.Print t & " -> " & Json.Serialize(v, JsonOptions.Minified) + End If +Next +' Output: +' 1.5E3 -> 1500 +' 1.0 -> 1 +' 0.10 -> 0.1 +' -0 -> 0 +' -1.25e-2 -> -0.0125 +' 1e28 -> 10000000000000000000000000000 +' 79228162514264337593543950335 -> 79228162514264337593543950335 +' 1e29 -> InvalidNumber +' 1e-29 -> InvalidNumber +' 3.14159265358979323846264338327950288 -> InvalidNumber +``` + +The position of a syntax error is in `JsonError.Position`, `JsonError.Line` and `JsonError.Column`, counted from 1 in UTF-16 code units, with a line feed, a carriage return or a carriage return and a line feed each ending a line. `JsonError.Snippet` holds up to 24 characters on each side of the position. + +```tb check_build check_run +Dim v As Variant = Json.Parse("{""a"":[1,2.5,""x"",true,null]}") +Dim o As JsonObject = v +Debug.Print TypeName(v) ' JsonObject +Debug.Print TypeName(o.Arr("a").Item(0)) ' Decimal +Debug.Print TypeName(o.Arr("a").Item(2)) ' String +Debug.Print TypeName(o.Arr("a").Item(3)) ' Boolean +Debug.Print TypeName(o.Arr("a").Item(4)) ' Null + +Dim bad As Variant = Json.Parse("{""a"":1,}") +Debug.Print IsEmpty(bad) ' True +Debug.Print JsonError.Code ' InvalidSyntax +Debug.Print JsonError.ToString() ' Json.Parse: InvalidSyntax - Expected object key. (line 1, column 8, position 8) | {"a":1,} +``` + +The result of **Parse** is a **Variant**, so a variable of a class type can take it directly when the parse succeeds: `Dim o As JsonObject = Json.Parse(text)`. After a failed parse the result is **Empty**, which such a variable does not accept: the assignment raises run-time error 424, *Object required*. Check the last error first, or use [**TryParse**](#tryparse). + +```tb check_build check_run +On Error Resume Next +Dim o As JsonObject = Json.Parse("{") +Debug.Print Err.Number & " " & Err.Description ' 424 Object required +``` + +### TryParse +{: .no_toc } + +Parses JSON text or JSON bytes into a variable and returns whether it succeeded. + +Syntax: **Json.TryParse** ( *json*, *value* [ , *options* ] ) **As Boolean** + +*json* +: *required* The document, as for [**Parse**](#parse). + +*value* +: *required* A **Variant** variable that receives the root value. It is passed **ByRef**. After a failed call it is **Empty**. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**Parse**](#parse). + +Returns **True** when the document was read and **False** when not. It calls [**Parse**](#parse) and tests the last error, so the last error is set as **Parse** sets it, with the origin `Json.Parse`. + +```tb check_build check_run +Dim v As Variant +Debug.Print Json.TryParse("[1,2", v) ' False +Debug.Print IsEmpty(v) ' True +Debug.Print JsonError.Code ' InvalidSyntax +Debug.Print Json.TryParse("[1,2]", v) ' True +Debug.Print Json.IsArray(v) ' True +``` + +## Serializing + +### Serialize +{: .no_toc } + +Writes a value as JSON text. + +Syntax: **Json.Serialize** ( *value* [ , *options* ] ) **As String** + +*value* +: *required* The value to write: any JSON value, as [The JSON model](.#the-json-model) describes. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. **Nothing**, the default, writes the text pretty-printed with 4 spaces of indentation and a line feed (`vbLf`) after each line, as [**JsonOptions.Pretty**](JsonOptions#pretty) does. The format properties and [**MaxDepth**](JsonOptions#maxdepth) are used. + +Returns the text, or an empty string and the last error when the value cannot be written. The errors are `UnsupportedType` for a value that is not a JSON value (an **Empty**, a **Nothing**, or an object of another class), `InvalidNumber` for a number that a **Decimal** cannot hold, such as an infinity or a **Double** above about 7.9E+28, `CircularReference` for a container that contains itself, and `DepthLimitExceeded`. + +- **Numbers** of every numeric type are converted to **Decimal** and written as the text of that value, with a decimal point whatever the regional settings are and without an exponent. A **Double** is converted to **Decimal** first, so it keeps 15 significant digits: `1 / 3` is written as `0.333333333333333`, and `0.1 + 0.2` as `0.3`. A **Double** of 1E+20 is written as `100000000000000000000`. +- **Strings** are written between double quotes. The characters `"` and `\` and the control characters are escaped: `\b`, `\t`, `\n`, `\f` and `\r` where JSON has a short form, and `\u00xx` with lowercase hexadecimal digits for the other control characters. The solidus `/` is written as it is unless [**EscapesSolidus**](JsonOptions#escapessolidus) is **True**. Other characters are written as they are unless [**EnsuresAscii**](JsonOptions#ensuresascii) is **True**, which writes each UTF-16 code unit above U+007F as `\uxxxx`. The character U+007F is not escaped. +- **A Date** is written as a string by [**FormatIso8601**](#formatiso8601), in local time without a zone. +- **A Variant array** is written as a JSON array. An empty or unbound array is `[]`. Only one-dimensional arrays are supported: a two-dimensional array raises run-time error -2147467259, *Unspecified error*, and leaves the last error of the package stuck (see [Known problems](.#known-problems-in-version-10150)). +- **Objects** keep their order, unless [**SortsKeys**](JsonOptions#sortskeys) is **True**, which orders the keys by UTF-16 code unit. +- **Layout.** With [**Indent**](JsonOptions#indent) of 0 or more, each member and element is on its own line, indented by that many spaces for each level, with a space after each colon; [**Newline**](JsonOptions#newline) ends the lines. With **Indent** below 0 the text has no white space. An empty object or array is `{}` or `[]` in both layouts. + +```tb check_build check_run +Dim o As JsonObject = Json.Map("a", 1, "b", Json.Arr(1, 2), "c", Json.Map()) +Debug.Print Json.Serialize(o, JsonOptions.Minified) + +Dim line As Variant +For Each line In Split(Json.Serialize(o), vbLf) + Debug.Print line +Next +' Output: +' {"a":1,"b":[1,2],"c":{}} +' { +' "a": 1, +' "b": [ +' 1, +' 2 +' ], +' "c": {} +' } +``` + +```tb check_build check_run +Debug.Print Json.Serialize(1 / 3, JsonOptions.Minified) ' 0.333333333333333 +Debug.Print Json.Serialize(0.1 + 0.2, JsonOptions.Minified) ' 0.3 +Debug.Print Json.Serialize(1E+20, JsonOptions.Minified) ' 100000000000000000000 +Debug.Print Json.Serialize(CSng(0.1), JsonOptions.Minified) ' 0.1 +Debug.Print Json.Serialize(CDec("1.50"), JsonOptions.Minified) ' 1.5 +``` + +A character that is not a valid UTF-16 sequence, a lone surrogate, is written unchanged by **Serialize**. [**SerializeToUtf8**](#serializetoutf8) and [**WriteFile**](#writefile) cannot encode it and report `InvalidEncoding`. + +```tb check_build check_run +Debug.Print Len(Json.Serialize(ChrW(&HD83D), Nothing)) ' 3 +Dim bytes() As Byte = Json.SerializeToUtf8(ChrW(&HD83D), Nothing) +Debug.Print JsonError.Code ' InvalidEncoding +Debug.Print JsonError.Message ' Text cannot be encoded as UTF-8. +``` + +### SerializeToUtf8 +{: .no_toc } + +Writes a value as JSON text encoded in UTF-8. + +Syntax: **Json.SerializeToUtf8** ( *value* [ , *options* ] ) **As Byte()** + +*value* +: *required* The value to write, as for [**Serialize**](#serialize). + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**Serialize**](#serialize). When [**IncludesBom**](JsonOptions#includesbom) is **True**, the bytes start with the UTF-8 byte order mark `EF BB BF`. + +Returns the bytes. Returns an unbound array and sets the last error when [**Serialize**](#serialize) fails or the text cannot be encoded (`InvalidEncoding`). + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Minified +o.IncludesBom = True +Dim bytes() As Byte = Json.SerializeToUtf8(ChrW$(233), o) +Debug.Print UBound(bytes) - LBound(bytes) + 1 ' 7 +Debug.Print Hex$(bytes(0)) & Hex$(bytes(1)) & Hex$(bytes(2)) ' EFBBBF +Debug.Print Hex$(bytes(4)) & Hex$(bytes(5)) ' C3A9 +``` + +## Files + +### ParseFile +{: .no_toc } + +Reads a file and parses it. + +Syntax: **Json.ParseFile** ( *path* [ , *options* ] ) **As Variant** + +*path* +: *required* The path of the file. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. [**Encoding**](JsonOptions#encoding) selects the encoding of the file, with a byte order mark deciding by default, and [**MaxBytes**](JsonOptions#maxbytes) limits the size of the file. The other properties apply as they do to [**Parse**](#parse). + +Returns the root value, or **Empty** and the last error. A file that does not exist or cannot be opened or read is an `IoError`; a file larger than **MaxBytes** is a `LimitExceeded` error; the other errors are those of [**Parse**](#parse). An empty file is the error `UnexpectedEnd`. + +### TryParseFile +{: .no_toc } + +Reads a file and parses it into a variable, and returns whether it succeeded. + +Syntax: **Json.TryParseFile** ( *path*, *value* [ , *options* ] ) **As Boolean** + +*path* +: *required* The path of the file. + +*value* +: *required* A **Variant** variable that receives the root value. It is passed **ByRef**. After a failed call it is **Empty**. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**ParseFile**](#parsefile). + +Returns **True** when the file was read and parsed. The last error is that of [**ParseFile**](#parsefile), with the origin `Json.ParseFile`. + +### WriteFile +{: .no_toc } + +Writes a value to a file as UTF-8 text. + +Syntax: **Json.WriteFile** ( *path*, *value* [ , *options* ] ) + +*path* +: *required* The path of the file. An existing file is replaced. + +*value* +: *required* The value to write, as for [**Serialize**](#serialize). + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. The format properties and [**IncludesBom**](JsonOptions#includesbom) are used. **Nothing** writes pretty-printed text without a byte order mark. + +The file always has UTF-8 encoding. If the value cannot be serialized, the file is not touched and the last error is that of [**Serialize**](#serialize). If the file cannot be opened or written, the error is `IoError`. The file is opened for writing, and so emptied, before the bytes are written, so a write that fails part-way leaves a short file. + +```tb check_build check_run +Dim path As String = Environ$("TEMP") & "\tsJson-sample-file.json" +Json.WriteFile path, Json.Map("n", 1, "s", "x"), JsonOptions.Minified +Debug.Print JsonError.HasError ' False +Debug.Print Json.Serialize(Json.ParseFile(path), JsonOptions.Minified) ' {"n":1,"s":"x"} +Kill path + +Dim missing As Variant = Json.ParseFile(path) +Debug.Print IsEmpty(missing) ' True +Debug.Print JsonError.Code ' IoError +``` + +## Lines of JSON + +NDJSON, also called JSON Lines, is a text with one JSON value on each line. The functions of this section read and write it. Lines end with a line feed, a carriage return, or both; a line that is empty or holds only spaces is ignored. + +### ParseLines +{: .no_toc } + +Parses NDJSON text into an array of values. + +Syntax: **Json.ParseLines** ( *text* [ , *options* ] ) **As** [**JsonArray**](JsonArray) + +*text* +: *required* A **String** with one JSON value on each line. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. It applies to each line as it applies to [**Parse**](#parse), except that [**MaxBytes**](JsonOptions#maxbytes) is not checked. + +Returns an array that holds the value of each line, in order, or **Nothing** and the last error when a line is not valid. The error is that of [**Parse**](#parse). Its position, line and column are counted in the line that failed, not in the whole text, so `JsonError.Line` is always 1. + +```tb check_build check_run +Dim lines As JsonArray = Json.ParseLines("{""id"":1}" & vbLf & vbLf & "{""id"":2}" & vbLf) +Debug.Print lines.Count ' 2 +Debug.Print lines.Map(1).Num("id") ' 2 + +Dim bad As JsonArray = Json.ParseLines("{""a"":1}" & vbLf & "{bad}") +Debug.Print bad Is Nothing ' True +Debug.Print JsonError.ToString() ' Json.ParseLines: InvalidSyntax - Expected object key. (line 1, column 2, position 2) | {bad} +``` + +### TryParseLines +{: .no_toc } + +Parses NDJSON text into a variable and returns whether it succeeded. + +Syntax: **Json.TryParseLines** ( *text*, *value* [ , *options* ] ) **As Boolean** + +*text* +: *required* A **String** with one JSON value on each line. + +*value* +: *required* A [**JsonArray**](JsonArray) variable that receives the array. It is passed **ByRef**. After a failed call it is **Nothing**. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**ParseLines**](#parselines). + +Returns **True** when every line was read. The last error is that of [**ParseLines**](#parselines), with the origin `Json.ParseLines`. + +### SerializeLines +{: .no_toc } + +Writes a sequence of values as NDJSON text. + +Syntax: **Json.SerializeLines** ( *values* [ , *options* ] ) **As String** + +*values* +: *required* A [**JsonArray**](JsonArray) or a **Variant** array. Each element is written on its own line. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. [**Indent**](JsonOptions#indent) is ignored, because each value is written without white space. The other format properties apply as they do to [**Serialize**](#serialize). **Nothing** uses [**JsonOptions.Minified**](JsonOptions#minified). + +Returns the text, with a line feed after every value including the last, or an empty string when there are no values or a value cannot be written. Any other type of *values* is the error `WrongType`; the other errors are those of [**Serialize**](#serialize). + +```tb check_build check_run +Dim lines As JsonArray = Json.Arr(Json.Map("id", 1), Json.Map("id", 2)) +Debug.Print Replace(Json.SerializeLines(lines), vbLf, "|") ' {"id":1}|{"id":2}| + +Dim plain(0 To 1) As Variant +plain(0) = 1 +plain(1) = "x" +Debug.Print Replace(Json.SerializeLines(plain), vbLf, "|") ' 1|"x"| +``` + +### ParseLinesFile +{: .no_toc } + +Reads a file of NDJSON and parses it. + +Syntax: **Json.ParseLinesFile** ( *path* [ , *options* ] ) **As** [**JsonArray**](JsonArray) + +*path* +: *required* The path of the file. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. [**Encoding**](JsonOptions#encoding) and [**MaxBytes**](JsonOptions#maxbytes) apply to the file, as for [**ParseFile**](#parsefile), and the other properties to each line. + +Returns the array, or **Nothing** and the last error. The errors are those of [**ParseFile**](#parsefile) and [**ParseLines**](#parselines). + +### TryParseLinesFile +{: .no_toc } + +Reads a file of NDJSON into a variable and returns whether it succeeded. + +Syntax: **Json.TryParseLinesFile** ( *path*, *value* [ , *options* ] ) **As Boolean** + +*path* +: *required* The path of the file. + +*value* +: *required* A [**JsonArray**](JsonArray) variable that receives the array. It is passed **ByRef**. After a failed call it is **Nothing**. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**ParseLinesFile**](#parselinesfile). + +Returns **True** when the file was read and every line parsed. The last error is that of [**ParseLinesFile**](#parselinesfile), with the origin `Json.ParseLinesFile`. + +### WriteLinesFile +{: .no_toc } + +Writes a sequence of values to a file as NDJSON, in UTF-8. + +Syntax: **Json.WriteLinesFile** ( *path*, *values* [ , *options* ] ) + +*path* +: *required* The path of the file. An existing file is replaced. + +*values* +: *required* A [**JsonArray**](JsonArray) or a **Variant** array, as for [**SerializeLines**](#serializelines). + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. [**IncludesBom**](JsonOptions#includesbom) adds a byte order mark; the other properties apply as they do to [**SerializeLines**](#serializelines). + +The errors are those of [**SerializeLines**](#serializelines) and [**WriteFile**](#writefile). When there are no values the file is created empty. + +```tb check_build check_run +Dim path As String = Environ$("TEMP") & "\tsJson-sample-lines.ndjson" +Json.WriteLinesFile path, Json.Arr(Json.Map("id", 1), Json.Map("id", 2)) +Dim back As JsonArray = Json.ParseLinesFile(path) +Debug.Print back.Count ' 2 +Debug.Print Json.Equals(back.Map(0), Json.Map("id", 1)) ' True +Kill path +``` + +## JSON Pointer + +A JSON Pointer is a string that names one place in a document, as RFC 6901 defines it. The empty string names the document itself. Any other pointer is a series of tokens, each introduced by `/`. A token selects a member of an object by its key, or an element of an array by its index. The index is written in decimal digits, without a leading zero (`0` is the first element). In an array, the token `-` names the position after the last element. In a token, `~1` stands for `/` and `~0` for `~`. The pointer `/` names the member with the empty key. + +The functions follow RFC 6901, except for these points: + +- The pointer must be a plain string. The form of a pointer as a URI fragment, `#/a/b`, is not accepted: it is the error `InvalidPointer`. +- An array token that is not an index (letters, a leading zero, a sign, or a number above the largest **Long**) is the error `InvalidPointer`, not a missing place. +- The token `-` names no existing element. Reading it is the error `PointerNotFound`, or a plain miss for the functions that report misses without an error. [**PointerSet**](#pointerset) accepts it as the last token and appends. +- An object member is found by its exact key, and a token such as `0` selects the member `"0"` of an object. +- An empty token selects the member with the empty key, and the pointer `/` names the member `""` of the document. An empty token that comes after another token is a defect of version 1.0.15.0: it is read as a copy of the token before it, so `/a/` is read as `/a/a`. [Known problems](.#known-problems-in-version-10150) shows it. +- The value that [**PointerGet**](#pointerget) returns is the container inside the document, not a copy. + +### PointerGet +{: .no_toc } + +Returns the value at a pointer. + +Syntax: **Json.PointerGet** ( *root*, *pointer* ) **As Variant** + +*root* +: *required* The document. + +*pointer* +: *required* A **String** that holds a JSON Pointer. The empty string returns *root*. + +Returns the value, which can be **Null** for a JSON null. Returns **Empty** and sets the last error when the pointer is not valid (`InvalidPointer`) or names no place (`PointerNotFound`). The error's `JsonError.Path` is the pointer up to and including the token that failed. + +```tb check_build check_run +Dim doc As JsonObject = Json.Map("foo", Json.Arr("bar", "baz"), "", 0, "a/b", 1, "m~n", 8, "k""l", 6, "i\j", 5) +Debug.Print Json.Serialize(Json.PointerGet(doc, "/foo"), JsonOptions.Minified) ' ["bar","baz"] +Debug.Print Json.PointerGet(doc, "/foo/0") ' bar +Debug.Print Json.PointerGet(doc, "/") ' 0 +Debug.Print Json.PointerGet(doc, "/a~1b") ' 1 +Debug.Print Json.PointerGet(doc, "/m~0n") ' 8 +Debug.Print Json.PointerGet(doc, "/k""l") ' 6 +Debug.Print Json.PointerGet(doc, "/i\j") ' 5 + +Dim miss As Variant = Json.PointerGet(doc, "/foo/2") +Debug.Print IsEmpty(miss) ' True +Debug.Print JsonError.Code ' PointerNotFound +Debug.Print JsonError.Path ' /foo/2 +``` + +### PointerExists +{: .no_toc } + +Returns whether a pointer names a place in a document. + +Syntax: **Json.PointerExists** ( *root*, *pointer* ) **As Boolean** + +*root* +: *required* The document. + +*pointer* +: *required* A **String** that holds a JSON Pointer. + +Returns **True** when the place exists, including a place that holds a JSON null. A pointer that names no place is not an error. A pointer with invalid syntax, or one whose token is not an index where the document has an array, returns **False** and sets the error `InvalidPointer`. + +### PointerTryGet +{: .no_toc } + +Reads the value at a pointer and returns whether the place exists. + +Syntax: **Json.PointerTryGet** ( *root*, *pointer*, *value* ) **As Boolean** + +*root* +: *required* The document. + +*pointer* +: *required* A **String** that holds a JSON Pointer. + +*value* +: *required* A **Variant** variable that receives the value. It is passed **ByRef**. When the place does not exist it is **Empty**. + +Returns **True** when the place exists. A pointer that names no place is not an error; a pointer with invalid syntax returns **False** and sets the error `InvalidPointer`. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""user"":{""name"":""Ada""},""tags"":[""a"",""b""],""none"":null}") +Dim v As Variant +Debug.Print Json.PointerExists(doc, "/tags/1") ' True +Debug.Print Json.PointerExists(doc, "/tags/2") ' False +Debug.Print JsonError.HasError ' False +Debug.Print Json.PointerTryGet(doc, "/user/name", v) ' True +Debug.Print v ' Ada +Debug.Print Json.PointerTryGet(doc, "/none", v) ' True +Debug.Print IsNull(v) ' True +Debug.Print Json.PointerExists(doc, "tags") ' False +Debug.Print JsonError.Code ' InvalidPointer +``` + +### PointerSet +{: .no_toc } + +Returns a copy of a document with a value stored at a pointer. + +Syntax: **Json.PointerSet** ( *root*, *pointer*, *value* ) **As Variant** + +*root* +: *required* The document. It is not changed. + +*pointer* +: *required* A **String** that holds a JSON Pointer. The empty string replaces the whole document, so the result is a copy of *value*. + +*value* +: *required* The value to store. A copy of it is stored. + +Returns the new document. All the places on the way to the last token must exist, otherwise the error is `PointerNotFound`. In an object, the last token adds the member or replaces it. In an array, the last token replaces the element at that index; an index equal to the length, or `-`, appends the value. An index above the length is the error `PointerNotFound`. The function does not insert: [**Patch**](#patch) with an `add` operation inserts into an array. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""a"":{""b"":[1,2]}}") +Dim copy As Variant = Json.PointerSet(doc, "/a/b/-", 3) +Debug.Print Json.Serialize(copy, JsonOptions.Minified) ' {"a":{"b":[1,2,3]}} +Debug.Print Json.Serialize(doc, JsonOptions.Minified) ' {"a":{"b":[1,2]}} + +copy = Json.PointerSet(doc, "/a/b/0", 9) +Debug.Print Json.Serialize(copy, JsonOptions.Minified) ' {"a":{"b":[9,2]}} + +copy = Json.PointerSet(doc, "/a/c/d", 1) +Debug.Print IsEmpty(copy) ' True +Debug.Print JsonError.Code ' PointerNotFound +``` + +### PointerRemove +{: .no_toc } + +Returns a copy of a document with the value at a pointer removed. + +Syntax: **Json.PointerRemove** ( *root*, *pointer* ) **As Variant** + +*root* +: *required* The document. It is not changed. + +*pointer* +: *required* A **String** that holds a JSON Pointer. The empty string, which names the whole document, is the error `InvalidPointer`, and so is a pointer that ends in `-`. + +Returns the new document. A member of an object is removed; an element of an array is removed and the later elements move down by one. A place that does not exist is the error `PointerNotFound`. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""a"":{""b"":[1,2,3],""c"":true}}") +Debug.Print Json.Serialize(Json.PointerRemove(doc, "/a/b/0"), JsonOptions.Minified) ' {"a":{"b":[2,3],"c":true}} +Debug.Print Json.Serialize(Json.PointerRemove(doc, "/a/c"), JsonOptions.Minified) ' {"a":{"b":[1,2,3]}} +Debug.Print IsEmpty(Json.PointerRemove(doc, "")) ' True +Debug.Print JsonError.Code ' InvalidPointer +``` + +### PointerEscape +{: .no_toc } + +Escapes a key for use as a token of a pointer. + +Syntax: **Json.PointerEscape** ( *token* ) **As String** + +*token* +: *required* The key of an object member. + +Returns the key with `~` replaced by `~0` and then `/` replaced by `~1`. The function does not change the last error. + +### PointerUnescape +{: .no_toc } + +Converts a token of a pointer back to a key. + +Syntax: **Json.PointerUnescape** ( *token* ) **As String** + +*token* +: *required* A token of a pointer. + +Returns the token with `~1` replaced by `/` and then `~0` replaced by `~`. The function does not check that the token is valid and does not change the last error. + +```tb check_build check_run +Debug.Print Json.PointerEscape("a/b~c") ' a~1b~0c +Debug.Print Json.PointerUnescape("a~1b~0c") ' a/b~c + +Dim doc As JsonObject = Json.Map("a/b", 1) +Debug.Print Json.PointerGet(doc, "/" & Json.PointerEscape("a/b")) ' 1 +``` + +## JSONPath + +A JSONPath is a query that selects any number of values from a document, as RFC 9535 defines it. `$.store.book[*].author` selects the `author` of every element of `book`. The syntax that is supported, and the points where it departs from RFC 9535, are on the page of [**JsonPathQuery**](JsonPathQuery#syntax). A path that matches nothing is not an error. A path with invalid syntax is the error `InvalidPath`. The functions below compile the path each time they are called, except that the last path compiled successfully is kept; to run one path many times, compile it once with [**CompilePath**](#compilepath). + +### CompilePath +{: .no_toc } + +Compiles a JSONPath. + +Syntax: **Json.CompilePath** ( *path* ) **As** [**JsonPathQuery**](JsonPathQuery) + +*path* +: *required* A **String** that holds a JSONPath. It must start with `$`. + +Returns a [**JsonPathQuery**](JsonPathQuery), or **Nothing** and the error `InvalidPath` when the path is not valid. The error's position, line, column and snippet locate the fault in the path text. An expression in a filter that nests more than 1000 levels is the error `DepthLimitExceeded`. + +```tb check_build check_run +Dim q As JsonPathQuery = Json.CompilePath("$.store.book[0]") +Debug.Print q.Source ' $.store.book[0] + +Dim bad As JsonPathQuery = Json.CompilePath("store.book") +Debug.Print bad Is Nothing ' True +Debug.Print JsonError.Code ' InvalidPath +Debug.Print JsonError.Message ' JSONPath must start with '$'. +``` + +### Query +{: .no_toc } + +Returns the values that a JSONPath selects. + +Syntax: **Json.Query** ( *root*, *path* ) **As Variant()** + +*root* +: *required* The document. + +*path* +: *required* A **String** that holds a JSONPath. + +Returns an array of the selected values, in document order, with a lower bound of 0. The values are the containers inside the document, not copies. An array that results from a path that matches nothing is unbound, so `UBound` raises run-time error 9 for it. A path with invalid syntax also returns an unbound array and sets the error `InvalidPath`. [**Serialize**](#serialize) writes the array as a JSON array, `[]` when it is unbound. + +```tb check_build check_run +Dim store As Variant = Json.Parse( _ + "{""store"":{""book"":[" & _ + "{""category"":""reference"",""author"":""Nigel Rees"",""title"":""Sayings of the Century"",""price"":8.95}," & _ + "{""category"":""fiction"",""author"":""Evelyn Waugh"",""title"":""Sword of Honour"",""price"":12.99}," & _ + "{""category"":""fiction"",""author"":""Herman Melville"",""title"":""Moby Dick"",""isbn"":""0-553-21311-3"",""price"":8.99}]," & _ + """bicycle"":{""color"":""red"",""price"":399}}}") + +Debug.Print Json.Serialize(Json.Query(store, "$.store.book[*].author"), JsonOptions.Minified) +Debug.Print Json.Serialize(Json.Query(store, "$..price"), JsonOptions.Minified) +Debug.Print Json.Serialize(Json.Query(store, "$.store.book[?@.price < 10].title"), JsonOptions.Minified) +Debug.Print Json.Serialize(Json.Query(store, "$.nothing"), JsonOptions.Minified) +Debug.Print JsonError.HasError + +Dim none() As Variant = Json.Query(store, "$.nothing") +On Error Resume Next +Debug.Print UBound(none) +Debug.Print Err.Number +' Output: +' ["Nigel Rees","Evelyn Waugh","Herman Melville"] +' [8.95,12.99,8.99,399] +' ["Sayings of the Century","Moby Dick"] +' [] +' False +' 9 +``` + +The `UBound` line prints nothing, because the error ends that statement. + +### QueryOne +{: .no_toc } + +Returns the first value that a JSONPath selects. + +Syntax: **Json.QueryOne** ( *root*, *path* ) **As Variant** + +*root* +: *required* The document. + +*path* +: *required* A **String** that holds a JSONPath. + +Returns the first selected value in document order, or **Empty** when nothing is selected, with no error. A selected JSON null is **Null**, so it can be told from a miss. A path with invalid syntax returns **Empty** and sets the error `InvalidPath`. + +### TryQueryOne +{: .no_toc } + +Reads the first value that a JSONPath selects and returns whether there is one. + +Syntax: **Json.TryQueryOne** ( *root*, *path*, *value* ) **As Boolean** + +*root* +: *required* The document. + +*path* +: *required* A **String** that holds a JSONPath. + +*value* +: *required* A **Variant** variable that receives the value. It is passed **ByRef**. When nothing is selected it is **Empty**. + +Returns **True** when at least one value is selected. Nothing selected is not an error; a path with invalid syntax returns **False** and sets the error `InvalidPath`. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""a"":[10,20,30],""n"":null}") +Dim v As Variant +Debug.Print Json.QueryOne(doc, "$.a[-1]") ' 30 +Debug.Print IsNull(Json.QueryOne(doc, "$.n")) ' True +Debug.Print IsEmpty(Json.QueryOne(doc, "$.missing")) ' True +Debug.Print Json.TryQueryOne(doc, "$.a[1:]", v) ' True +Debug.Print v ' 20 +Debug.Print Json.TryQueryOne(doc, "$.missing", v) ' False +Debug.Print JsonError.HasError ' False +``` + +### QueryHits +{: .no_toc } + +Returns the values that a JSONPath selects, each with its JSON Pointer. + +Syntax: **Json.QueryHits** ( *root*, *path* ) **As** [**JsonHit**](JsonHit)**()** + +*root* +: *required* The document. + +*path* +: *required* A **String** that holds a JSONPath. + +Returns an array of [**JsonHit**](JsonHit) objects, in document order, with a lower bound of 0. Each hit has the value and the RFC 6901 pointer of the place where it was found, which [**PointerSet**](#pointerset) and the other pointer functions accept. A path that matches nothing, or is not valid, returns an unbound array, as [**Query**](#query) does. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""store"":{""book"":[{""title"":""A""},{""title"":""B"",""isbn"":""x""}]}}") +Dim hits() As JsonHit = Json.QueryHits(doc, "$..book[?@.isbn].title") +Debug.Print UBound(hits) - LBound(hits) + 1 ' 1 +Debug.Print hits(LBound(hits)).Pointer ' /store/book/1/title +Debug.Print hits(LBound(hits)).Value ' B + +Dim renamed As Variant = Json.PointerSet(doc, hits(LBound(hits)).Pointer, "B2") +Debug.Print Json.PointerGet(renamed, "/store/book/1/title") ' B2 +Debug.Print Json.PointerGet(doc, "/store/book/1/title") ' B +``` + +## Patterns + +The JSONPath functions `match` and `search`, and the keywords `pattern` and `patternProperties` of a JSON Schema, use regular expressions in the I-Regexp dialect of RFC 9485. The functions of this section use the same matcher directly. I-Regexp is a small interoperable subset of regular expressions: the characters are compared by code point, so a surrogate pair is one character; a group does not capture; and there are no anchors, so `^` and `$` are ordinary characters. + +The supported syntax is: + +| Syntax | Meaning | +|:-------|:--------| +| `a` | A character that is not special matches itself. The special characters are `. ? * + { } [ ] ( ) \|` and `\`. | +| `.` | Any character except a line feed or a carriage return. | +| `\` followed by a special character | That character. RFC 9485 also has `\n`, `\r`, `\t`, `\-` and `\^`, which this package does not accept outside a set. | +| `\p{Lu}`, `\P{Lu}` | A character in a Unicode general category, or one that is not. The names are the one-letter groups `L M N P S Z C` and the two-letter categories such as `Lu`, `Nd` and `Zs`. | +| `\d`, `\D`, `\s`, `\S`, `\w`, `\W` | The digits `0` to `9`; the white space characters tab, line feed, form feed, carriage return and space; and the word characters `0` to `9`, `A` to `Z`, `a` to `z` and `_`; each with its complement. These are not part of RFC 9485. | +| `[abc]`, `[a-z]`, `[^a-z]` | A character in a set of characters and ranges, or not in it. A set can contain the escapes above. | +| `x*`, `x+`, `x?`, `x{3}`, `x{2,}`, `x{2,5}` | Repetition of the character, set or group before it. | +| `(...)`, `x\|y` | A group, and a choice between alternatives. | + +Anything else, such as a lookaround, a back reference or a lazy quantifier, makes the pattern invalid. A pattern that is not valid is never an error for these functions: they return **False**, and the last error is not set. The matcher departs from RFC 9485 in these ways: it accepts `\d`, `\D`, `\s`, `\S`, `\w` and `\W`, which the RFC leaves out; it does not accept the escapes `\n`, `\r`, `\t`, `\-` and `\^` that the RFC has, and reads `\n`, `\r` and `\t` inside a set as the letters `n`, `r` and `t`; it rejects a hyphen that comes after a single character at the end of a set, such as `[a-]`, which the RFC allows; and it does not try the other alternatives of a choice once one alternative has matched and the rest of the pattern then fails. The last difference, and the one before it, give wrong results with no error; [Known problems](.#known-problems-in-version-10150) shows them. + +### IsValidPattern +{: .no_toc } + +Returns whether a string is a valid I-Regexp. + +Syntax: **Json.IsValidPattern** ( *pattern* ) **As Boolean** + +*pattern* +: *required* The regular expression. + +The function does not change the last error. + +### IsMatchPattern +{: .no_toc } + +Returns whether a whole string matches a pattern. + +Syntax: **Json.IsMatchPattern** ( *text*, *pattern* ) **As Boolean** + +*text* +: *required* The string to test. + +*pattern* +: *required* An I-Regexp. The whole of *text* must match it. + +Returns **False** when the pattern is not valid. The function does not change the last error. + +### IsSearchPattern +{: .no_toc } + +Returns whether a pattern matches anywhere in a string. + +Syntax: **Json.IsSearchPattern** ( *text*, *pattern* ) **As Boolean** + +*text* +: *required* The string to test. + +*pattern* +: *required* An I-Regexp. It can match any part of *text*, including an empty part. + +Returns **False** when the pattern is not valid. The function does not change the last error. + +```tb check_build check_run +JsonError.Clear +Debug.Print Json.IsValidPattern("[a-z]{2,4}") ' True +Debug.Print Json.IsValidPattern("(") ' False +Debug.Print Json.IsMatchPattern("abc", "[a-z]+") ' True +Debug.Print Json.IsMatchPattern("abc1", "[a-z]+") ' False +Debug.Print Json.IsSearchPattern("abc1", "[0-9]") ' True +Debug.Print Json.IsMatchPattern("2026", "\d{4}") ' True +Debug.Print Json.IsMatchPattern("Ada", "\p{Lu}\p{Ll}+") ' True +Debug.Print JsonError.HasError ' False +``` + +## JSON Patch + +JSON Patch (RFC 6902) describes changes to a document as a list of operations. Each operation is an object with an `op` member (`add`, `remove`, `replace`, `move`, `copy` or `test`), a `path` member that holds a JSON Pointer, and, depending on the operation, a `value` or a `from` member. JSON Merge Patch (RFC 7396) describes the changes as a document of the same shape as the target. The functions never change their arguments; each returns a new document. + +### Patch +{: .no_toc } + +Applies a JSON Patch to a copy of a document. + +Syntax: **Json.Patch** ( *root*, *ops* ) **As Variant** + +*root* +: *required* The document. It is not changed. + +*ops* +: *required* A [**JsonArray**](JsonArray) of operation objects. A single operation object is also accepted, as a patch of one operation, which RFC 6902 does not allow. Any other value is the error `InvalidPatch`. + +Returns the patched document. The operations are applied one after the other to a copy. If one fails, the function returns **Empty** and sets the last error, and nothing is applied. The operations follow RFC 6902: + +- `add` stores a copy of `value` at `path`. In an object it adds the member or replaces it. In an array the index inserts the value before the element at that index; an index equal to the length, or `-`, appends. The parent of the place must exist. A path of `""` replaces the whole document. +- `remove` removes the value at `path`, which must exist. +- `replace` replaces the value at `path`, which must exist. +- `move` removes the value at `from` and adds it at `path`. A `from` that is a parent of `path` fails, because the parent is gone when the value is added. +- `copy` adds a copy of the value at `from` at `path`. +- `test` compares the value at `path` with `value`, as [**Equals**](#equals) compares: numbers by value, and objects without regard to the order of keys. A difference is the error `PatchTestFailed`. + +The errors are `InvalidPatch` for a patch that is malformed (an operation that is not an object, a missing or non-string `path` or `from`, a missing `value`, an unknown `op`), `PatchFailed` for an operation that cannot be applied to the document (an `add` into a scalar, an array index that is out of range), `PatchTestFailed`, and `PointerNotFound` or `InvalidPointer` for a pointer. For `InvalidPatch`, `JsonError.Path` is the position of the operation in the patch, such as `/0`; for the others it is the pointer in the document. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""foo"":""bar""}") +Dim ops As Variant = Json.Parse("[{""op"":""add"",""path"":""/baz"",""value"":""qux""},{""op"":""replace"",""path"":""/foo"",""value"":""BAR""}]") +Debug.Print Json.Serialize(Json.Patch(doc, ops), JsonOptions.Minified) ' {"foo":"BAR","baz":"qux"} +Debug.Print Json.Serialize(doc, JsonOptions.Minified) ' {"foo":"bar"} + +Dim bad As Variant = Json.Parse("[{""op"":""test"",""path"":""/foo"",""value"":""x""},{""op"":""remove"",""path"":""/foo""}]") +Debug.Print IsEmpty(Json.Patch(doc, bad)) ' True +Debug.Print JsonError.Code ' PatchTestFailed +Debug.Print JsonError.Path ' /foo +``` + +### MergePatch +{: .no_toc } + +Applies a JSON Merge Patch to a copy of a document. + +Syntax: **Json.MergePatch** ( *root*, *patch* ) **As Variant** + +*root* +: *required* The document. It is not changed. + +*patch* +: *required* The merge patch. A patch that is not an object replaces the whole document. In an object, a member with a value replaces or adds the member, a member that is an object is merged into the target member, and a member that is **Null** removes the target member. + +Returns the new document. The rules are those of RFC 7396, and the function sets no error. A patch cannot set a member to null, because null means removal. An array in a patch replaces the target array whole. + +```tb check_build check_run +Dim target As Variant = Json.Parse("{""a"":""b"",""c"":{""d"":""e"",""f"":""g""}}") +Dim patch As Variant = Json.Parse("{""a"":""z"",""c"":{""f"":null}}") +Debug.Print Json.Serialize(Json.MergePatch(target, patch), JsonOptions.Minified) ' {"a":"z","c":{"d":"e"}} +``` + +### Diff +{: .no_toc } + +Returns the operations that change one document into another. + +Syntax: **Json.Diff** ( *fromValue*, *toValue* ) **As** [**JsonArray**](JsonArray) + +*fromValue* +: *required* The document to start from. + +*toValue* +: *required* The document to end with. + +Returns a JSON Patch, an array of operations, such that `Json.Patch(fromValue, Json.Diff(fromValue, toValue))` equals *toValue*. The patch uses only `add`, `remove` and `replace`. Objects are compared member by member, with the removals first, then the changes in the order of the first document, then the additions. Arrays are compared element by element: the common length is compared position by position, the extra elements of the first array are removed from the end, and the extra elements of the second are added. A value that differs in type is replaced whole. Equal documents give an empty array. + +```tb check_build check_run +Dim a As Variant = Json.Parse("{""x"":1,""list"":[1,2,3],""gone"":true}") +Dim b As Variant = Json.Parse("{""x"":2,""list"":[1,5],""new"":null}") +Dim ops As JsonArray = Json.Diff(a, b) +Debug.Print Json.Serialize(ops, JsonOptions.Minified) +Debug.Print Json.Equals(Json.Patch(a, ops), b) +Debug.Print Json.Diff(a, a).Count +' Output: +' [{"op":"replace","path":"/x","value":2},{"op":"replace","path":"/list/1","value":5},{"op":"remove","path":"/list/2"},{"op":"remove","path":"/gone"},{"op":"add","path":"/new","value":null}] +' True +' 0 +``` + +## JSON Schema + +A JSON Schema describes the valid form of a document. The package implements draft 2020-12 with a local catalogue of documents; which keywords it checks is on the page of [**JsonSchema**](JsonSchema#keywords). An instance that does not satisfy the schema is not an error: it is reported on the [**JsonValidationResult**](JsonValidationResult) that the functions return. The last error is set only when the schema itself cannot be used. + +### Validate +{: .no_toc } + +Validates a value against a schema. + +Syntax: **Json.Validate** ( *instance*, *schema* [ , *options* ] ) **As** [**JsonValidationResult**](JsonValidationResult) + +*instance* +: *required* The value to check. + +*schema* +: *required* The schema: a [**JsonObject**](JsonObject), or a **Boolean** (**True** accepts everything and **False** accepts nothing). + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. [**IsFormatAssertion**](JsonOptions#isformatassertion) makes the `format` keyword a check, and [**MaxDepth**](JsonOptions#maxdepth) limits the depth of evaluation, including the depth of `$ref` references. + +Returns the report. It lists every failure, not only the first. The function compiles the schema each time it is called and has no catalogue, so a `$ref` can refer only to the schema itself. To use a catalogue, or to check many values, call [**CompileSchema**](#compileschema) once. + +A schema that cannot be used sets the last error, as well as being reported: `InvalidSchema` for a schema that is neither an object nor a Boolean, `RemoteRef` for a `$ref` that is not in the schema or the catalogue, and `DepthLimitExceeded`. + +```tb check_build check_run +Dim schema As Variant = Json.Parse("{""type"":""object"",""required"":[""name""],""properties"":{""name"":{""type"":""string""},""age"":{""type"":""integer"",""minimum"":0}}}") +Dim report As JsonValidationResult = Json.Validate(Json.Parse("{""name"":""Ann"",""age"":-1}"), schema) +Debug.Print report.IsValid ' False +Debug.Print report.ErrorCount ' 1 +Debug.Print report.ToString() ' minimum /age - Number is below minimum. + +Debug.Print Json.Validate(Json.Parse("{""name"":""Ann"",""age"":7}"), schema).IsValid ' True +Debug.Print JsonError.HasError ' False +``` + +### CompileSchema +{: .no_toc } + +Prepares a schema for validation, with a catalogue for `$ref`. + +Syntax: **Json.CompileSchema** ( *schema* [ , *options* ] ) **As** [**JsonSchema**](JsonSchema) + +*schema* +: *required* The schema, as for [**Validate**](#validate). A copy is stored. + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object, as for [**Validate**](#validate). It is kept by reference, and used by each [**JsonSchema.Validate**](JsonSchema#validate) call. + +Returns the [**JsonSchema**](JsonSchema). The call checks nothing about the schema; a fault in it is found when a value is validated. [**JsonSchema.AddDocument**](JsonSchema#adddocument) registers further documents that `$ref` can name. + +## Dates + +ISO 8601 text and **Date** values convert here. A **Date** has no time zone, so the conversion is to local time. The JSON functions never convert a date by themselves: [**Parse**](#parse) leaves date text as a **String**, and [**Serialize**](#serialize) writes a **Date** as a string. + +### ParseIso8601 +{: .no_toc } + +Converts ISO 8601 text to a **Date**. + +Syntax: **Json.ParseIso8601** ( *text* ) **As Date** + +*text* +: *required* A date, or a date with a time, in one of these forms: `yyyy-mm-dd` or `yyyy-mm-ddThh:nn:ss`, then optionally a fraction `.f` of one or more digits, then optionally a zone `Z`, `+hh:mm` or `-hh:mm`. A space can stand for the `T`. The text is trimmed first. The year must have four digits and be from 0100 to 9999. + +Returns the **Date**. Text with no zone is read as the local time. Text with `Z` or an offset names an instant, which Windows converts to the local time of the computer (the Windows function **SystemTimeToTzSpecificLocalTime**). The fraction is cut to milliseconds (it is not rounded). Seconds of 60, the form without seconds, `T` in lowercase, and an offset without a colon are not accepted. On failure the result is a **Date** of 0 and the last error is `InvalidDate`; because a **Date** of 0 is a valid date (30 December 1899), check the last error. + +```tb check_build check_run +Dim d As Date = Json.ParseIso8601("2026-08-19T09:15:30.250") +Debug.Print Json.FormatIso8601(d) ' 2026-08-19T09:15:30.250 +Debug.Print Hour(d) & ":" & Minute(d) ' 9:15 +Debug.Print Json.FormatIso8601(Json.ParseIso8601("2026-08-19")) ' 2026-08-19T00:00:00 + +Dim bad As Date = Json.ParseIso8601("19.08.2026") +Debug.Print bad = 0 ' True +Debug.Print JsonError.Code ' InvalidDate +Debug.Print JsonError.Message ' Expected yyyy-mm-dd. +``` + +```tb check_build check_run +Dim d As Date +Dim t As Variant +For Each t In Array("2026-08-19 09:15:30", "2026-08-19T09:15:30.123456", " 2026-08-19 ", "0100-01-01", "0099-01-01", "2026-08-19T09:15", "2026-08-19t09:15:30", "2026-08-19T09:15:30+0100", "2026-08-19T09:15:60", "2026-02-30", "2026-8-19") + d = Json.ParseIso8601(CStr(t)) + If JsonError.HasError Then + Debug.Print "[" & t & "] -> " & JsonError.Code & ": " & JsonError.Message + Else + Debug.Print "[" & t & "] -> " & Json.FormatIso8601(d) + End If +Next +' Output: +' [2026-08-19 09:15:30] -> 2026-08-19T09:15:30 +' [2026-08-19T09:15:30.123456] -> 2026-08-19T09:15:30.123 +' [ 2026-08-19 ] -> 2026-08-19T00:00:00 +' [0100-01-01] -> 0100-01-01T00:00:00 +' [0099-01-01] -> InvalidDate: Date is out of range. +' [2026-08-19T09:15] -> InvalidDate: Expected yyyy-mm-ddThh:nn:ss. +' [2026-08-19t09:15:30] -> InvalidDate: Expected 'T' between date and time. +' [2026-08-19T09:15:30+0100] -> InvalidDate: Invalid timezone. +' [2026-08-19T09:15:60] -> InvalidDate: Time is out of range. +' [2026-02-30] -> InvalidDate: Date is out of range. +' [2026-8-19] -> InvalidDate: ISO-8601 date is too short. +``` + +A zone is converted with the settings of the computer, so the **Date** that results depends on where the code runs. Two spellings of one instant give the same **Date** on every computer: + +```tb check_build check_run +Dim utc As Date = Json.ParseIso8601("2026-08-19T12:00:00Z") +Dim berlin As Date = Json.ParseIso8601("2026-08-19T14:00:00+02:00") +Debug.Print utc = berlin ' True +``` + +### FormatIso8601 +{: .no_toc } + +Converts a **Date** to ISO 8601 text, in local time. + +Syntax: **Json.FormatIso8601** ( *value* ) **As String** + +*value* +: *required* The **Date**. + +Returns `yyyy-mm-ddThh:nn:ss`, with `.fff` added when the date has a non-zero number of milliseconds. The text has no zone, so it is not an instant until the reader knows the zone. The function does not change the last error. + +## See Also + +- [JsonObject](JsonObject) class -- the JSON object that [**Map**](#map) and [**Parse**](#parse) return +- [JsonArray](JsonArray) class -- the JSON array that [**Arr**](#arr) and [**Parse**](#parse) return +- [JsonOptions](JsonOptions) class -- the options that most functions take +- [JsonError](JsonError) class -- reports the last error and lists the error codes +- [JsonPathQuery](JsonPathQuery) class -- a compiled JSONPath, and the JSONPath syntax +- [JsonSchema](JsonSchema) class -- a compiled schema, and the schema keywords that are checked +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonArray.md b/docs/Reference/Contributed/tsJson/JsonArray.md new file mode 100644 index 000000000..53d404c1a --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonArray.md @@ -0,0 +1,296 @@ +--- +title: JsonArray +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonArray +has_toc: false +--- + +# JsonArray class +{: .no_toc } + +A **JsonArray** is a JSON array: an ordered list of values, numbered from 0. [**Json.Parse**](Json#parse) returns one for a JSON array, and [**Json.Arr**](Json#arr) builds one from its arguments. An empty array can also be created with `New JsonArray`. + +```tb check_build check_run +Dim a As JsonArray = Json.Parse("[10,""x"",null,[1,2]]") +Debug.Print a.Count ' 4 +Debug.Print a.Num(0) + 1 ' 11 +Debug.Print a.Str(1) ' x +Debug.Print IsNull(a.Item(2)) ' True +Debug.Print a.Arr(3).Count ' 2 + +a.Add True +Debug.Print a.Dump(JsonOptions.Minified) ' [10,"x",null,[1,2],true] +``` + +A value can be any **Variant**, and it is stored without a check; [The JSON model](.#the-json-model) lists the values that can be written as JSON. An object value is stored by reference. + +An index outside the array sets the last error for the members that return the stored value or change the array: [**Item**](#item) (read and write), [**Insert**](#insert), [**RemoveAt**](#removeat), [**Map**](#map) and [**Arr**](#arr). The typed readers [**Str**](#str), [**Num**](#num) and [**Bool**](#bool) return a default for an index outside the array and for a JSON null, with no error. [**JsonError**](JsonError) reports the errors. A member that can fail clears the last error when it starts: [**Item**](#item), [**Insert**](#insert), [**RemoveAt**](#removeat), [**Str**](#str), [**Num**](#num), [**Bool**](#bool), [**Map**](#map), [**Arr**](#arr), [**TryGet**](#tryget) and [**Dump**](#dump). [**Count**](#count), [**Clear**](#clear), [**Add**](#add), [**ToVariantArray**](#tovariantarray), [**ValueType**](#valuetype) and [**Clone**](#clone) neither clear nor set it. + +* TOC +{:toc} + +## For Each + +`For Each` over a **JsonArray** returns the elements, in order. A JSON null is **Null**. The loop runs over a copy of the elements taken when it starts. + +```tb check_build check_run +Dim a As JsonArray = Json.Arr(1, Null, "x", Null) +Dim v As Variant +Dim nulls As Long +For Each v In a + If IsNull(v) Then nulls += 1 +Next +Debug.Print nulls ' 2 +``` + +## Properties + +### Count +{: .no_toc } + +The number of elements. A **Long**, read-only. + +Syntax: *n* **=** *array***.Count** + +### Item +{: .no_toc } + +The element at an index. A **Variant**, the default member of the class. + +Syntax: *value* **=** *array***.Item** ( *index* ) + +Syntax: *array***.Item** ( *index* ) **=** *value* + +Syntax: **Set** *array***.Item** ( *index* ) **=** *value* + +*index* +: *required* A **Long**, the position of the element. The first element has index 0. + +*value* +: The value to store. Use **Set** for an object. + +Reading returns the element. A JSON null is **Null**, and an element that is an object is returned itself, not a copy. An index below 0 or not below [**Count**](#count) returns **Empty** and sets the error `IndexOutOfRange`, with the message *Array index N is out of range.* and the pointer `/N` in `JsonError.Path`. Writing replaces an element; an index outside the array is the same error, so [**Add**](#add) is the way to append. Because **Item** is the default member, `array(index)` reads an element. + +```tb check_build check_run +Dim a As JsonArray = Json.Arr("a") +a.Add "b" +a.Insert 1, "mid" +Debug.Print a.Dump(JsonOptions.Minified) ' ["a","mid","b"] +a.RemoveAt 0 +Debug.Print a.Str(0) ' mid +Debug.Print a.Str(9, "d") ' d +Debug.Print JsonError.HasError ' False + +Dim v As Variant = a.Item(9) +Debug.Print IsEmpty(v) ' True +Debug.Print JsonError.Code ' IndexOutOfRange +Debug.Print JsonError.Message ' Array index 9 is out of range. +Debug.Print JsonError.Path ' /9 +``` + +## Methods + +### Add +{: .no_toc } + +Appends an element. + +Syntax: *array***.Add** ( *value* ) + +*value* +: *required* The value to append. An object is stored by reference. + +### Arr +{: .no_toc } + +Returns an element that is an array. + +Syntax: *array***.Arr** ( *index* ) **As** [**JsonArray**](JsonArray) + +*index* +: *required* A **Long**, the position of the element. + +Returns the [**JsonArray**](JsonArray) itself, not a copy. Returns **Nothing** and sets the last error when the index is outside the array (`MissingKey`, *Missing array.*), or the element is not an array, a JSON null included (`WrongType`, *Expected array.*). An index outside an array is reported with the code `MissingKey` by this member, not with `IndexOutOfRange`, and `JsonError.Path` is the pointer `/9` for the index 9. + +```tb check_build check_run +Dim a As JsonArray = Json.Arr(1) +Dim x As JsonArray = a.Arr(9) +Debug.Print JsonError.Code & ": " & JsonError.Message & " [" & JsonError.Path & "]" +Dim z As JsonArray = a.Arr(0) +Debug.Print JsonError.Code & ": " & JsonError.Message & " [" & JsonError.Path & "]" +' Output: +' MissingKey: Missing array. [/9] +' WrongType: Expected array. [/0] +``` + +### Bool +{: .no_toc } + +Returns an element that is a Boolean. + +Syntax: *array***.Bool** ( *index* [ , *default* ] ) **As Boolean** + +*index* +: *required* A **Long**, the position of the element. + +*default* +: *optional* A **Boolean**, the result when the index is outside the array or the element is a JSON null. **False**, the default. + +Returns the element. An element that is not a **Boolean** returns *default* and sets the error `WrongType` (*Expected boolean.*). + +### Clear +{: .no_toc } + +Removes all elements. + +Syntax: *array***.Clear** + +### Clone +{: .no_toc } + +Returns a deep copy of the array. + +Syntax: *array***.Clone** ( ) **As** [**JsonArray**](JsonArray) + +Every object and array inside the array is copied, so the copy shares no container with the original. See [**Json.Clone**](Json#clone) for a value of any type. + +### Dump +{: .no_toc } + +Returns the array as JSON text. + +Syntax: *array***.Dump** ( [ *options* ] ) **As String** + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. **Nothing**, the default, writes pretty-printed text, as [**JsonOptions.Pretty**](JsonOptions#pretty) does. + +The result is that of [**Json.Serialize**](Json#serialize), and its errors are the same, with the origin `JsonArray.Dump`. + +### Insert +{: .no_toc } + +Inserts an element. + +Syntax: *array***.Insert** ( *index*, *value* ) + +*index* +: *required* A **Long**, the position the new element takes. The elements from that position on move up by one. An index equal to [**Count**](#count) appends. An index below 0 or above **Count** is the error `IndexOutOfRange`. + +*value* +: *required* The value to insert. + +### Map +{: .no_toc } + +Returns an element that is an object. + +Syntax: *array***.Map** ( *index* ) **As** [**JsonObject**](JsonObject) + +*index* +: *required* A **Long**, the position of the element. + +Returns the [**JsonObject**](JsonObject) itself, not a copy. Returns **Nothing** and sets the last error when the index is outside the array (`MissingKey`, *Missing object.*), or the element is not an object, a JSON null included (`WrongType`, *Expected object.*). + +```tb check_build check_run +Dim a As JsonArray = Json.Parse("[{""k"":1},[2,3]]") +Debug.Print a.Map(0).Num("k") ' 1 +Debug.Print a.Arr(1).Count ' 2 + +Dim m As JsonObject = a.Map(1) +Debug.Print JsonError.Code ' WrongType +Set m = a.Map(5) +Debug.Print JsonError.Code ' MissingKey +Debug.Print JsonError.Message ' Missing object. +``` + +### Num +{: .no_toc } + +Returns an element that is a number. + +Syntax: *array***.Num** ( *index* [ , *default* ] ) **As Variant** + +*index* +: *required* A **Long**, the position of the element. + +*default* +: *optional* A **Variant**, the result when the index is outside the array or the element is a JSON null. When it is left out, the result is **Empty**. + +Returns the number as a **Decimal**. An element that is not a number, a string of digits included, returns *default* and sets the error `WrongType` (*Expected number.*). + +### RemoveAt +{: .no_toc } + +Removes an element. + +Syntax: *array***.RemoveAt** ( *index* ) + +*index* +: *required* A **Long**, the position of the element. The later elements move down by one. An index outside the array is the error `IndexOutOfRange`. + +### Str +{: .no_toc } + +Returns an element as a string. + +Syntax: *array***.Str** ( *index* [ , *default* ] ) **As String** + +*index* +: *required* A **Long**, the position of the element. + +*default* +: *optional* A **String**, the result when the index is outside the array or the element is a JSON null. The empty string, the default. + +Returns the string. An element that is a number or a **Boolean** is converted, as [**JsonObject.Str**](JsonObject#str) converts it. An element that is an object or an array returns *default* and sets the error `WrongType` (*Expected string.*). + +### ToVariantArray +{: .no_toc } + +Returns the elements as a **Variant** array. + +Syntax: *array***.ToVariantArray** ( ) **As Variant()** + +Returns a copy of the list of elements, with a lower bound of 0. Objects and arrays in it are the stored ones, not copies. The array is unbound when the array is empty. + +```tb check_build check_run +Dim a As JsonArray = Json.Arr(1, 2) +Dim copy() As Variant = a.ToVariantArray() +Debug.Print UBound(copy) - LBound(copy) + 1 ' 2 +a.Add 3 +Debug.Print UBound(copy) - LBound(copy) + 1 ' 2 +``` + +### TryGet +{: .no_toc } + +Reads an element and returns whether the index is valid. + +Syntax: *array***.TryGet** ( *index*, *value* ) **As Boolean** + +*index* +: *required* A **Long**, the position of the element. + +*value* +: *required* A **Variant** variable that receives the value. It is passed **ByRef**. When the index is outside the array it is **Empty**. + +Returns **True** when the index is inside the array, even if the element is a JSON null. An index outside the array is not an error. The member clears the last error. + +### ValueType +{: .no_toc } + +Returns the JSON type of an element. + +Syntax: *array***.ValueType** ( *index* ) **As** [**eJsonType**](Json#ejsontype) + +*index* +: *required* A **Long**, the position of the element. + +Returns **jtUnknown** for an index outside the array, and otherwise the type of the stored value, as [**Json.TypeOfValue**](Json#typeofvalue) reports it. The member does not change the last error. + +## See Also + +- [JsonObject](JsonObject) class -- the JSON object +- [Json](Json) module -- builds, parses and serializes values +- [JsonError](JsonError) class -- reports the last error +- [JsonOptions](JsonOptions) class -- the options of **Dump** +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonError.md b/docs/Reference/Contributed/tsJson/JsonError.md new file mode 100644 index 000000000..0e5e614c5 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonError.md @@ -0,0 +1,235 @@ +--- +title: JsonError +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonError +has_toc: false +--- + +# JsonError class +{: .no_toc } + +The **JsonError** object reports the last error of the package: whether there is one, its code, its message, the function that found it, and where in the input or the document it was found. + +**JsonError** is a predeclared object. It is used by its name, without a variable. The error itself is stored in the package, not in the object, and the package keeps a single last error. There is no member that sets it. + +The functions of the package do not raise run-time errors for bad data or bad arguments. A function that fails returns an empty result and sets the last error. A function that can fail clears the last error when it starts, so the last error belongs to the latest such call; the page of [Json](Json) lists the members that do not clear it. To keep an error across the next call, copy it with [**Snapshot**](#snapshot). A run-time error that escapes from a function of the package, such as the one that [**Json.Serialize**](Json#serialize) raises for a two-dimensional array, stops the clearing for the rest of the run; [Known problems](.#known-problems-in-version-10150) shows it. + +```tb check_build check_run +Dim v As Variant = Json.Parse("[1, 2") +Debug.Print JsonError.HasError ' True +Debug.Print JsonError.Code ' InvalidSyntax +Debug.Print JsonError.Message ' Expected ',' or ']' in array. +Debug.Print JsonError.Origin ' Json.Parse +Debug.Print JsonError.Line & ":" & JsonError.Column ' 1:6 +Debug.Print JsonError.Snippet ' [1, 2 +Debug.Print JsonError.ToString() ' Json.Parse: InvalidSyntax - Expected ',' or ']' in array. (line 1, column 6, position 6) | [1, 2 + +Dim o As JsonObject = Json.Map("a", 1) +Dim m As JsonObject = o.Map("a") +Debug.Print JsonError.ToString() ' JsonObject.Map: WrongType - Expected object. [path: /a] +``` + +A failed call returns an empty value, and some successful calls return the same value: an empty string, **False** or 0. Test [**HasError**](#haserror) to tell the two apart. + +* TOC +{:toc} + +## Error codes + +The code is a **String** that names the error. It is case-sensitive. These are the codes that the package sets. + +| Code | Set when | +|:-----|:---------| +| `InvalidArgument` | [**Json.Map**](Json#map) gets an odd number of arguments, or a key that is not a **String**. | +| `WrongType` | A value has the wrong type for the call: the text passed to [**Json.Parse**](Json#parse) is neither a **String** nor a **Byte** array; [**SerializeLines**](Json#serializelines) gets a value that is not an array; [**JsonObject.Merge**](JsonObject#merge) gets **Nothing**; or a typed reader (**Str**, **Num**, **Bool**, **Map**, **Arr**) finds a member of another type. | +| `MissingKey` | [**JsonObject.Item**](JsonObject#item) or [**Remove**](JsonObject#remove) gets a key that does not exist; [**JsonObject.Map**](JsonObject#map) or [**Arr**](JsonObject#arr) gets a missing key; [**JsonArray.Map**](JsonArray#map) or [**Arr**](JsonArray#arr) gets an index outside the array. | +| `IndexOutOfRange` | The index passed to [**JsonArray.Item**](JsonArray#item), [**Insert**](JsonArray#insert) or [**RemoveAt**](JsonArray#removeat) is outside the array. | +| `InvalidSyntax` | The text is not valid JSON, or a JSONC extension is used in strict JSON. | +| `UnexpectedEnd` | The text ends where a value, a key or the rest of a string is needed, or the document is empty. | +| `InvalidEscape` | A string holds an invalid escape sequence, or a `\u` escape of a surrogate that has no partner. | +| `InvalidNumber` | A number in the text is not valid or is outside the range of a **Decimal**; or [**Serialize**](Json#serialize) is given a number that a **Decimal** cannot hold. | +| `TrailingData` | There is text after the end of the JSON value. | +| `InvalidComment` | A `/*` comment in JSONC is not closed. | +| `DuplicateKey` | An object has the same key twice and [**DuplicateKeys**](JsonOptions#duplicatekeys) is **jdkError**. | +| `DepthLimitExceeded` | Nesting is deeper than [**MaxDepth**](JsonOptions#maxdepth) in parsing, serializing or schema evaluation; or a JSONPath filter nests more than 1000 levels. | +| `LimitExceeded` | The input is longer than [**MaxBytes**](JsonOptions#maxbytes), a string longer than [**MaxStringLength**](JsonOptions#maxstringlength), an array longer than [**MaxArrayItems**](JsonOptions#maxarrayitems), or an object larger than [**MaxObjectMembers**](JsonOptions#maxobjectmembers). | +| `InvalidEncoding` | Bytes are not valid in the encoding, a UTF-16 input has an odd number of bytes, or text cannot be encoded as UTF-8. | +| `IoError` | A file cannot be found, opened, read or written. The message includes the reason from the system. | +| `UnsupportedType` | [**Serialize**](Json#serialize) is given a value that is not a JSON value. | +| `CircularReference` | An object or an array contains itself. | +| `InvalidPointer` | A JSON Pointer has invalid syntax, an array token is not an index, or the pointer names the whole document where a place is needed. | +| `PointerNotFound` | A JSON Pointer names a place that does not exist. | +| `InvalidPath` | A JSONPath has invalid syntax. | +| `InvalidPatch` | A JSON Patch is malformed. | +| `PatchFailed` | An operation of a JSON Patch cannot be applied. | +| `PatchTestFailed` | A `test` operation of a JSON Patch found a different value. | +| `InvalidSchema` | A schema is neither an object nor a Boolean, or [**JsonSchema.AddDocument**](JsonSchema#adddocument) gets an empty URI. | +| `RemoteRef` | A `$ref` of a schema names a document, a pointer or an anchor that is not available. | +| `InvalidDate` | [**Json.ParseIso8601**](Json#parseiso8601) gets text that is not a valid date. | + +An invalid pattern given to a pattern function, and a miss that an `Exists` or `Try` function reports, are not errors, and no code stands for them. + +## Properties + +### Code +{: .no_toc } + +The code of the last error. A **String**, read-only. + +Syntax: *text* **=** **JsonError.Code** + +Returns the code, such as `InvalidSyntax`, or an empty string when there is no error. The codes are listed under [Error codes](#error-codes). + +### Column +{: .no_toc } + +The column of the error in the input. A **Long**, read-only. + +Syntax: *n* **=** **JsonError.Column** + +Returns the column, counted from 1 in UTF-16 code units from the start of the line, or 0 when the error has no location. The location is set for errors in JSON text and in a JSONPath, where it is in the path text. + +### HasError +{: .no_toc } + +Whether there is a last error. A **Boolean**, read-only. + +Syntax: *flag* **=** **JsonError.HasError** + +Returns **True** after a function has failed, and **False** after one has succeeded, because a function clears the last error when it starts. It is also **False** after [**Clear**](#clear). + +### Line +{: .no_toc } + +The line of the error in the input. A **Long**, read-only. + +Syntax: *n* **=** **JsonError.Line** + +Returns the line, counted from 1, or 0 when the error has no location. A line feed, a carriage return, or a carriage return and a line feed together end a line. + +### Message +{: .no_toc } + +The message of the last error. A **String**, read-only. + +Syntax: *text* **=** **JsonError.Message** + +Returns the message in English, such as `Expected object key.`, or an empty string when there is no error. The messages are for a person; the code is the value to test in a program. + +### Origin +{: .no_toc } + +The function that found the last error. A **String**, read-only. + +Syntax: *text* **=** **JsonError.Origin** + +Returns the name of the module or class and of the public member that was called, such as `Json.Parse` or `JsonObject.Item`, or an empty string when there is no error. When a member calls another member of the package, the origin is the member that was called from outside. + +### Path +{: .no_toc } + +The JSON Pointer of the place the error concerns. A **String**, read-only. + +Syntax: *text* **=** **JsonError.Path** + +Returns a pointer, or an empty string when none was recorded. For a member of an object or an array it is the pointer from that object or array, such as `/name` or `/9`. For [**Json.PointerGet**](Json#pointerget) it is the pointer to the token that failed. For a duplicate key it is the pointer of the key. For a failed [**Json.Patch**](Json#patch) it is the position of the operation in the patch, or the pointer in the document. For a schema error it is the pointer in the schema. + +### Position +{: .no_toc } + +The position of the error in the input. A **Long**, read-only. + +Syntax: *n* **=** **JsonError.Position** + +Returns the position, counted from 1 in UTF-16 code units from the start of the text, or 0 when the error has no location. For [**Json.ParseLines**](Json#parselines) it is counted in the line that failed. + +### Snippet +{: .no_toc } + +A short part of the input around the error. A **String**, read-only. + +Syntax: *text* **=** **JsonError.Snippet** + +Returns up to 24 characters before the position and up to 24 after it, or an empty string when none was recorded. + +## Methods + +### Clear +{: .no_toc } + +Clears the last error. + +Syntax: **JsonError.Clear** + +After the call, [**HasError**](#haserror) is **False** and the other properties are empty or 0. The functions of the package clear the last error themselves when they start, so a program needs **Clear** only to discard an error that it has read. + +### Format +{: .no_toc } + +Formats a copy of an error as one line of text. + +Syntax: *text* **=** **JsonError.Format** ( *info* ) **As String** + +*info* +: *required* A [**tJsonErrInfo**](#tjsonerrinfo) returned by [**Snapshot**](#snapshot). It is passed **ByRef**, so it must be a variable. + +Returns what [**ToString**](#tostring) returns for the error that the copy holds, and an empty string when the copy holds no error. + +### Snapshot +{: .no_toc } + +Returns a copy of the last error. + +Syntax: *snap* **=** **JsonError.Snapshot** ( ) **As** [**tJsonErrInfo**](#tjsonerrinfo) + +Returns a [**tJsonErrInfo**](#tjsonerrinfo) with the values of all the properties at the time of the call. The copy belongs to the caller: the next call of a function of the package, which clears the last error, does not change it. + +```tb check_build check_run +Dim bad As Variant = Json.Parse("{") +Dim snap As JsonError.tJsonErrInfo = JsonError.Snapshot() + +Dim ok As Variant = Json.Parse("[]") +Debug.Print JsonError.HasError ' False +Debug.Print snap.HasError ' True +Debug.Print snap.Code ' UnexpectedEnd +Debug.Print JsonError.Format(snap) ' Json.Parse: UnexpectedEnd - Unexpected end of JSON. (line 1, column 2, position 2) | { +``` + +The type is declared inside the class. A variable of that type is declared `As JsonError.tJsonErrInfo`, as the sample above does. + +### ToString +{: .no_toc } + +Returns the last error as one line of text. + +Syntax: *text* **=** **JsonError.ToString** ( ) **As String** + +Returns an empty string when there is no error. Otherwise returns the origin, a colon and a space, the code, a space, a hyphen, a space and the message. Then, when the position is above 0, it adds a space and `(line L, column C, position P)`; when there is a path, a space and `[path: pointer]`; and when there is a snippet, a space, `|`, a space and the snippet. The text is meant for logs and for **Debug.Print**. + +## Types + +### tJsonErrInfo +{: .no_toc } + +A copy of the last error, returned by [**Snapshot**](#snapshot). A user-defined type, declared inside the class. + +| Field | Type | Content | +|:------|:-----|:--------| +| **HasError** | **Boolean** | Whether there was an error. | +| **Code** | **String** | The code, or an empty string. | +| **Message** | **String** | The message, or an empty string. | +| **Origin** | **String** | The origin, or an empty string. | +| **Position** | **Long** | The position, or 0. | +| **Line** | **Long** | The line, or 0. | +| **Column** | **Long** | The column, or 0. | +| **Snippet** | **String** | The snippet, or an empty string. | +| **Path** | **String** | The pointer, or an empty string. | + +The fields are separate values. Unlike the properties of **JsonError**, they do not change when the last error changes. + +## See Also + +- [Json](Json) module -- the functions whose errors it reports +- [JsonObject](JsonObject) and [JsonArray](JsonArray) classes -- their members report errors too +- [JsonOptions](JsonOptions) class -- the limits that several codes refer to +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonHit.md b/docs/Reference/Contributed/tsJson/JsonHit.md new file mode 100644 index 000000000..32e347b56 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonHit.md @@ -0,0 +1,55 @@ +--- +title: JsonHit +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonHit +has_toc: false +--- + +# JsonHit class +{: .no_toc } + +A **JsonHit** is one result of a JSONPath query: the value that was selected, and the RFC 6901 JSON Pointer of the place where it was found. [**Json.QueryHits**](Json#queryhits) and [**JsonPathQuery.QueryHits**](JsonPathQuery#queryhits) return arrays of them. The pointer can be passed to [**Json.PointerGet**](Json#pointerget), [**PointerSet**](Json#pointerset) and [**PointerRemove**](Json#pointerremove) to read or change that place. + +```tb check_build check_run +Dim hits() As JsonHit = Json.QueryHits(Json.Parse("{""a"":[10,20]}"), "$.a[*]") +Dim i As Long +For i = LBound(hits) To UBound(hits) + Debug.Print hits(i).Pointer & " = " & hits(i).Value +Next + +Dim root() As JsonHit = Json.QueryHits(Json.Parse("[1]"), "$") +Debug.Print "[" & root(LBound(root)).Pointer & "]" +' Output: +' /a/0 = 10 +' /a/1 = 20 +' [] +``` + +* TOC +{:toc} + +## Properties + +### Pointer +{: .no_toc } + +The JSON Pointer of the hit. A **String**, read-only. + +Syntax: *text* **=** *hit***.Pointer** + +Returns the pointer, such as `/store/book/1/title`. The pointer of the document itself is the empty string. Keys are escaped, so a key that holds `/` or `~` appears as `~1` or `~0`. + +### Value +{: .no_toc } + +The value that was selected. A **Variant**, read-only. + +Syntax: *value* **=** *hit***.Value** + +Returns the value. A JSON null is **Null**. An object or an array is returned itself, not a copy, so it is the container inside the document. + +## See Also + +- [JsonPathQuery](JsonPathQuery) class -- runs a compiled path +- [Json](Json#queryhits) module -- **QueryHits** for a path in text +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonObject.md b/docs/Reference/Contributed/tsJson/JsonObject.md new file mode 100644 index 000000000..9fce822db --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonObject.md @@ -0,0 +1,342 @@ +--- +title: JsonObject +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonObject +has_toc: false +--- + +# JsonObject class +{: .no_toc } + +A **JsonObject** is a JSON object: an ordered set of members, each a key and a value. [**Json.Parse**](Json#parse) returns one for a JSON object, and [**Json.Map**](Json#map) builds one from pairs of arguments. An empty object can also be created with `New JsonObject`, and an empty array with `New JsonArray`. + +```tb check_build check_run +Dim o As JsonObject = Json.Parse("{""name"":""Ann"",""age"":31,""tags"":[""x"",""y""]}") +Debug.Print o.Str("name") ' Ann +Debug.Print o.Num("age") + 1 ' 32 +Debug.Print o.Arr("tags").Count ' 2 + +o.Item("age") = 32 +Set o.Item("address") = Json.Map("city", "Rome") +Debug.Print o.Dump(JsonOptions.Minified) ' {"name":"Ann","age":32,"tags":["x","y"],"address":{"city":"Rome"}} + +Dim empty1 As New JsonObject +Dim empty2 As New JsonArray +Debug.Print empty1.Count & " " & empty2.Count ' 0 0 +``` + +The members keep the order in which they were added. A key is compared by character code, so `Name` and `name` are different keys, and the empty string is a valid key. A key that exists is replaced in its place; a new key is added at the end. A value can be any **Variant**, and it is stored without a check; [The JSON model](.#the-json-model) lists the values that can be written as JSON. An object value is stored by reference, so the same object can be in two places, and an object that contains itself cannot be serialized (the error is `CircularReference`). + +The members that read a member by key follow two rules. The typed readers [**Str**](#str), [**Num**](#num) and [**Bool**](#bool) return a default for a key that is missing and for a JSON null, with no error. The readers that return the stored value or a container, [**Item**](#item), [**Map**](#map) and [**Arr**](#arr), set the last error for a missing key. [**JsonError**](JsonError) reports it. A member that can fail clears the last error when it starts: reading an [**Item**](#item), [**Str**](#str), [**Num**](#num), [**Bool**](#bool), [**Map**](#map), [**Arr**](#arr), [**TryGet**](#tryget), [**Remove**](#remove), [**Merge**](#merge) and [**Dump**](#dump). [**Count**](#count), [**Exists**](#exists), [**Keys**](#keys), [**Items**](#items), [**Clear**](#clear), [**Clone**](#clone), [**ValueType**](#valuetype) and writing an **Item** neither clear nor set it. + +* TOC +{:toc} + +## For Each + +`For Each` over a **JsonObject** returns the keys, as **String** values, in order. The loop runs over a copy of the keys taken when it starts, so the loop can add or remove members. + +```tb check_build check_run +Dim o As JsonObject = Json.Map("a", 1, "b", 2) +o.Item("a") = 10 +o.Item("c") = 3 +Set o.Item("d") = Json.Arr(1) +o.Remove "b" +Debug.Print o.Dump(JsonOptions.Minified) +Debug.Print o("a") + +Dim k As Variant +For Each k In o + Debug.Print k & "=" & o.ValueType(k) +Next +' Output: +' {"a":10,"c":3,"d":[1]} +' 10 +' a=3 +' c=3 +' d=5 +``` + +## Properties + +### Count +{: .no_toc } + +The number of members. A **Long**, read-only. + +Syntax: *n* **=** *object***.Count** + +### Item +{: .no_toc } + +The value of a member. A **Variant**, the default member of the class. + +Syntax: *value* **=** *object***.Item** ( *key* ) + +Syntax: *object***.Item** ( *key* ) **=** *value* + +Syntax: **Set** *object***.Item** ( *key* ) **=** *value* + +*key* +: *required* A **String**, the key of the member. + +*value* +: The value to store. Use **Set** for a [**JsonObject**](JsonObject) or a [**JsonArray**](JsonArray) or any other object. + +Reading returns the value. A JSON null is **Null**. A value that is an object is returned itself, not a copy. For a key that does not exist, the read returns **Empty** and sets the error `MissingKey`, with the message *Object has no member 'key'.* and the pointer `/key` in `JsonError.Path`. Writing replaces the value of an existing key in its place, or adds the member at the end. Because **Item** is the default member, `object("key")` reads a member. + +```tb check_build check_run +Dim o As JsonObject = Json.Map("a", 1) +Dim v As Variant = o.Item("nope") +Debug.Print IsEmpty(v) ' True +Debug.Print JsonError.Code ' MissingKey +Debug.Print JsonError.Message ' Object has no member 'nope'. +Debug.Print JsonError.Path ' /nope +Debug.Print JsonError.Origin ' JsonObject.Item +Debug.Print o.Exists("nope") ' False +``` + +## Methods + +### Arr +{: .no_toc } + +Returns a member that is an array. + +Syntax: *object***.Arr** ( *key* ) **As** [**JsonArray**](JsonArray) + +*key* +: *required* A **String**, the key of the member. + +Returns the [**JsonArray**](JsonArray) itself, not a copy. Returns **Nothing** and sets the last error when the key is missing (`MissingKey`, *Missing array.*), or the member is not an array, a JSON null included (`WrongType`, *Expected array.*). The pointer of the member is in `JsonError.Path`. A call made on the **Nothing** result raises run-time error 91. + +### Bool +{: .no_toc } + +Returns a member that is a Boolean. + +Syntax: *object***.Bool** ( *key* [ , *default* ] ) **As Boolean** + +*key* +: *required* A **String**, the key of the member. + +*default* +: *optional* A **Boolean**, the result when the member is missing or a JSON null. **False**, the default. + +Returns the member. A member that is not a **Boolean** returns *default* and sets the error `WrongType` (*Expected boolean.*). A missing key or a JSON null is not an error. + +### Clear +{: .no_toc } + +Removes all members. + +Syntax: *object***.Clear** + +### Clone +{: .no_toc } + +Returns a deep copy of the object. + +Syntax: *object***.Clone** ( ) **As** [**JsonObject**](JsonObject) + +Every object and array inside the object is copied, so the copy shares no container with the original. See [**Json.Clone**](Json#clone) for a value of any type. + +### Dump +{: .no_toc } + +Returns the object as JSON text. + +Syntax: *object***.Dump** ( [ *options* ] ) **As String** + +*options* +: *optional* A [**JsonOptions**](JsonOptions) object. **Nothing**, the default, writes pretty-printed text, as [**JsonOptions.Pretty**](JsonOptions#pretty) does. + +The result is that of [**Json.Serialize**](Json#serialize), and its errors are the same, with the origin `JsonObject.Dump`. + +### Exists +{: .no_toc } + +Returns whether a key is present. + +Syntax: *object***.Exists** ( *key* ) **As Boolean** + +*key* +: *required* A **String**, the key to look for. + +Returns **True** when the object has a member with exactly that key. A member whose value is a JSON null exists. The member does not change the last error. + +### Items +{: .no_toc } + +Returns the values of all members. + +Syntax: *object***.Items** ( ) **As Variant()** + +Returns an array of the values, in the order of the members, with a lower bound of 0. The array is unbound when the object is empty. Objects and arrays in the result are the stored ones, not copies. + +### Keys +{: .no_toc } + +Returns the keys of all members. + +Syntax: *object***.Keys** ( ) **As String()** + +Returns an array of the keys, in order, with a lower bound of 0. The array is unbound when the object is empty, and `UBound` raises run-time error 9 for it. Check [**Count**](#count) first. + +```tb check_build check_run +Dim o As JsonObject = Json.Map("x", 1, "y", Json.Arr(2)) +Dim keys() As String = o.Keys() +Debug.Print UBound(keys) - LBound(keys) + 1 ' 2 +Debug.Print keys(0) & keys(1) ' xy +Debug.Print Json.Serialize(o.Items(), JsonOptions.Minified) ' [1,[2]] + +Debug.Print Json.Map().Count ' 0 +``` + +### Map +{: .no_toc } + +Returns a member that is an object. + +Syntax: *object***.Map** ( *key* ) **As** [**JsonObject**](JsonObject) + +*key* +: *required* A **String**, the key of the member. + +Returns the [**JsonObject**](JsonObject) itself, not a copy. Returns **Nothing** and sets the last error when the key is missing (`MissingKey`, *Missing object.*), or the member is not an object, a JSON null included (`WrongType`, *Expected object.*). + +```tb check_build check_run +Dim o As JsonObject = Json.Parse("{""user"":{""name"":""Ada""},""n"":null}") +Debug.Print o.Map("user").Str("name") ' Ada + +Dim m As JsonObject = o.Map("n") +Debug.Print m Is Nothing ' True +Debug.Print JsonError.Code ' WrongType +Set m = o.Map("other") +Debug.Print JsonError.Code ' MissingKey +Debug.Print JsonError.Message ' Missing object. +``` + +### Merge +{: .no_toc } + +Returns a new object that is this object overlaid with another. + +Syntax: *object***.Merge** ( *other* [ , *deep* ] ) **As** [**JsonObject**](JsonObject) + +*other* +: *required* The [**JsonObject**](JsonObject) whose members are laid over this one. **Nothing** is the error `WrongType`, and the result is **Nothing**. + +*deep* +: *optional* A **Boolean**. **True**, the default, merges a member that is an object in both objects member by member. **False** replaces the member whole. + +Neither argument is changed. The result starts as a copy of this object. For each member of *other*: a key that is new is added at the end; a key that exists is replaced in its place by a copy of the value from *other*, except that with *deep* an object over an object is merged in the same way. Arrays, strings, numbers and **Null** replace the old value whole. A **Null** in *other* sets the member to **Null**; it does not remove the member. [**Json.MergePatch**](Json#mergepatch) removes it. + +```tb check_build check_run +Dim a As JsonObject = Json.Map("a", 1, "b", Json.Map("c", 2, "d", 3)) +Dim b As JsonObject = Json.Map("b", Json.Map("d", 9, "e", 4), "f", Null) +Debug.Print Json.Serialize(a.Merge(b), JsonOptions.Minified) ' {"a":1,"b":{"c":2,"d":9,"e":4},"f":null} +Debug.Print Json.Serialize(a.Merge(b, False), JsonOptions.Minified) ' {"a":1,"b":{"d":9,"e":4},"f":null} +Debug.Print Json.Serialize(a, JsonOptions.Minified) ' {"a":1,"b":{"c":2,"d":3}} +``` + +### Num +{: .no_toc } + +Returns a member that is a number. + +Syntax: *object***.Num** ( *key* [ , *default* ] ) **As Variant** + +*key* +: *required* A **String**, the key of the member. + +*default* +: *optional* A **Variant**, the result when the member is missing or a JSON null. When it is left out, the result is **Empty**. + +Returns the number as a **Decimal**. A member that is not a number, a string of digits included, returns *default* and sets the error `WrongType` (*Expected number.*). A missing key or a JSON null is not an error. + +```tb check_build check_run +Dim o As JsonObject = Json.Parse("{""name"":""Ann"",""age"":31,""admin"":false,""boss"":null}") +Debug.Print o.Str("name") ' Ann +Debug.Print o.Str("title", "(none)") ' (none) +Debug.Print o.Str("age") ' 31 +Debug.Print o.Num("age") + 1 ' 32 +Debug.Print o.Bool("admin", True) ' False +Debug.Print o.Str("boss", "none") ' none +Debug.Print JsonError.HasError ' False + +Debug.Print o.Num("name", -1) ' -1 +Debug.Print JsonError.Code ' WrongType +Debug.Print JsonError.Path ' /name +``` + +### Remove +{: .no_toc } + +Removes a member. + +Syntax: *object***.Remove** ( *key* ) + +*key* +: *required* A **String**, the key of the member. + +The later members move up, so the order of the others does not change. A key that is missing is the error `MissingKey`. + +### Str +{: .no_toc } + +Returns a member as a string. + +Syntax: *object***.Str** ( *key* [ , *default* ] ) **As String** + +*key* +: *required* A **String**, the key of the member. + +*default* +: *optional* A **String**, the result when the member is missing or a JSON null. The empty string, the default. + +Returns the string. A member that is a number or a **Boolean** is converted: a number to its text with a decimal point (`31`, `8.95`), and a **Boolean** to `True` or `False`. A member that is an object or an array returns *default* and sets the error `WrongType` (*Expected string.*). A missing key or a JSON null is not an error. + +```tb check_build check_run +Dim o As JsonObject = Json.Parse("{""p"":8.95,""n"":31,""b"":true,""list"":[1]}") +Debug.Print o.Str("p") ' 8.95 +Debug.Print o.Str("n") ' 31 +Debug.Print o.Str("b") ' True +Debug.Print o.Str("list", "none") ' none +Debug.Print JsonError.Code ' WrongType +``` + +### TryGet +{: .no_toc } + +Reads a member and returns whether it exists. + +Syntax: *object***.TryGet** ( *key*, *value* ) **As Boolean** + +*key* +: *required* A **String**, the key of the member. + +*value* +: *required* A **Variant** variable that receives the value. It is passed **ByRef**. When the key is missing it is **Empty**. + +Returns **True** when the member exists, even if its value is a JSON null. A missing key is not an error. The member clears the last error. + +### ValueType +{: .no_toc } + +Returns the JSON type of a member. + +Syntax: *object***.ValueType** ( *key* ) **As** [**eJsonType**](Json#ejsontype) + +*key* +: *required* A **String**, the key of the member. + +Returns **jtUnknown** for a key that is missing, and otherwise the type of the stored value, as [**Json.TypeOfValue**](Json#typeofvalue) reports it. The member does not change the last error. + +## See Also + +- [JsonArray](JsonArray) class -- the JSON array +- [Json](Json) module -- builds, parses and serializes values +- [JsonError](JsonError) class -- reports the last error +- [JsonOptions](JsonOptions) class -- the options of **Dump** +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonOptions.md b/docs/Reference/Contributed/tsJson/JsonOptions.md new file mode 100644 index 000000000..88b43b723 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonOptions.md @@ -0,0 +1,396 @@ +--- +title: JsonOptions +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonOptions +has_toc: false +--- + +# JsonOptions class +{: .no_toc } + +A **JsonOptions** object holds the options of one call to the package: the syntax that [**Json.Parse**](Json#parse) accepts, the layout that [**Json.Serialize**](Json#serialize) writes, the limits on input, and how a schema is checked. It is passed as the last argument of the functions that take options, and **Nothing** there means the defaults of [**Rfc**](#rfc). + +An options object is created from a preset. The predeclared object **JsonOptions**, written without a variable, is a factory: its four properties [**Rfc**](#rfc), [**Jsonc**](#jsonc), [**Minified**](#minified) and [**Pretty**](#pretty) each return a new object with every property set, and [**Clone**](#clone) copies one. + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Minified +o.SortsKeys = True +Debug.Print Json.Serialize(Json.Map("b", 1, "a", 2), o) ' {"a":2,"b":1} +Debug.Print o.Indent ' -1 +Debug.Print JsonOptions.Rfc.Indent ' 4 +``` + +A property of an options object checks nothing when it is set. The value is used by the call that receives the object, and a call does not change the object. + +> [!IMPORTANT] +> Assigning a property on the predeclared object itself raises run-time error 5, *Invalid procedure call or argument*, with the source `JsonOptions` and the message *JsonOptions (default instance) is a factory. Use Rfc, Jsonc, Minified, or Pretty.* Create an options object first and assign the properties of that. +> +> ```tb check_build check_run +> On Error Resume Next +> JsonOptions.Indent = 2 +> Debug.Print Err.Number ' 5 +> Debug.Print Err.Source ' JsonOptions +> Debug.Print Err.Description ' JsonOptions (default instance) is a factory. Use Rfc, Jsonc, Minified, or Pretty. +> ``` +> +> Reading a property of the predeclared object does not raise an error. It returns the empty value of the type, 0, **False** or an empty string, and not the default of the property. +> +> ```tb check_build check_run +> Debug.Print JsonOptions.Indent & "|" & JsonOptions.MaxDepth & "|" & JsonOptions.EscapesSolidus & "|" & JsonOptions.Newline & "|" & JsonOptions.Syntax & "|" & JsonOptions.MaxBytes +> ' Output: +> ' 0|0|False||0|0 +> ``` + +> [!NOTE] +> The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, so that only its presets create options objects. In BETA 1005 twinBASIC does not enforce a **Private** constructor, and a project that references the package can still create an object with **New**. Every property of such an object has the empty value of its type: **Indent** 0, an empty **Newline**, **Syntax** **jsRfc8259**, and a limit of 0, which means no limit. The text it writes has no line breaks and no indentation, but still a space after each colon. The presets are the way to get the defaults. +> +> ```tb check_build check_run +> Dim jo As JsonOptions = New JsonOptions +> Debug.Print Json.Serialize(Json.Map("a", 1, "b", Json.Arr(1, 2)), jo) ' {"a": 1,"b": [1,2]} +> jo.Indent = 2 +> jo.Newline = vbLf +> Debug.Print Replace(Json.Serialize(Json.Map("a", 1), jo), vbLf, "|") ' {| "a": 1|} +> ``` + +* TOC +{:toc} + +## Where each property is used + +| Properties | Used by | +|:-----------|:--------| +| [**Syntax**](#syntax), [**DuplicateKeys**](#duplicatekeys), [**MaxDepth**](#maxdepth), [**MaxStringLength**](#maxstringlength), [**MaxArrayItems**](#maxarrayitems), [**MaxObjectMembers**](#maxobjectmembers) | The parse functions: [**Parse**](Json#parse), [**ParseFile**](Json#parsefile), [**ParseLines**](Json#parselines), [**ParseLinesFile**](Json#parselinesfile) and the `Try` versions. For the lines functions, each line is a document. | +| [**Encoding**](#encoding), [**MaxBytes**](#maxbytes) | The parse functions for bytes and files. [**MaxBytes**](#maxbytes) also limits a **String** passed to **Parse**, counted in UTF-8 bytes, but not the text of **ParseLines**. | +| [**Indent**](#indent), [**Newline**](#newline), [**SortsKeys**](#sortskeys), [**EscapesSolidus**](#escapessolidus), [**EnsuresAscii**](#ensuresascii) | The serialize functions: [**Serialize**](Json#serialize), [**SerializeToUtf8**](Json#serializetoutf8), [**WriteFile**](Json#writefile), [**SerializeLines**](Json#serializelines), [**WriteLinesFile**](Json#writelinesfile), and **Dump** of [**JsonObject**](JsonObject#dump) and [**JsonArray**](JsonArray#dump). **SerializeLines** ignores **Indent**. | +| [**MaxDepth**](#maxdepth) | Also [**Serialize**](Json#serialize) and the functions that call it, and the evaluation of a schema. | +| [**IncludesBom**](#includesbom) | [**SerializeToUtf8**](Json#serializetoutf8), [**WriteFile**](Json#writefile) and [**WriteLinesFile**](Json#writelinesfile). | +| [**IsFormatAssertion**](#isformatassertion) | [**Json.Validate**](Json#validate) and [**JsonSchema.Validate**](JsonSchema#validate). | + +The options do not reach JSONPath. A path expression nests at most 1000 levels whatever **MaxDepth** is. + +## Presets + +### Rfc +{: .no_toc } + +Returns a new options object with the defaults of RFC 8259. + +Syntax: *options* **=** **JsonOptions.Rfc** + +The object is independent of every other one, so it can be changed. These are its values: + +| Property | Value | +|:---------|:------| +| [**Indent**](#indent) | 4 | +| [**Newline**](#newline) | `vbLf` | +| [**Syntax**](#syntax) | **jsRfc8259** | +| [**Encoding**](#encoding) | **jekAuto** | +| [**IncludesBom**](#includesbom) | **False** | +| [**DuplicateKeys**](#duplicatekeys) | **jdkError** | +| [**SortsKeys**](#sortskeys), [**EscapesSolidus**](#escapessolidus), [**EnsuresAscii**](#ensuresascii), [**IsFormatAssertion**](#isformatassertion) | **False** | +| [**MaxDepth**](#maxdepth) | 1000 | +| [**MaxBytes**](#maxbytes) | 16777216 (16 MiB) | +| [**MaxStringLength**](#maxstringlength) | 1048576 (1 MiB) | +| [**MaxArrayItems**](#maxarrayitems) | 1000000 | +| [**MaxObjectMembers**](#maxobjectmembers) | 1000000 | + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Rfc +Debug.Print o.Indent & " " & o.MaxDepth & " " & o.MaxBytes ' 4 1000 16777216 +Debug.Print o Is JsonOptions.Rfc ' False +``` + +### Jsonc +{: .no_toc } + +Returns a new options object that accepts JSONC. + +Syntax: *options* **=** **JsonOptions.Jsonc** + +The same as [**Rfc**](#rfc), with [**Syntax**](#syntax) set to **jsJsonc**. + +### Minified +{: .no_toc } + +Returns a new options object for compact output. + +Syntax: *options* **=** **JsonOptions.Minified** + +The same as [**Rfc**](#rfc), with [**Indent**](#indent) set to -1. + +### Pretty +{: .no_toc } + +Returns a new options object for pretty-printed output. + +Syntax: *options* **=** **JsonOptions.Pretty** + +The same as [**Rfc**](#rfc), which indents by 4 spaces. + +### Clone +{: .no_toc } + +Returns a copy of an options object. + +Syntax: *options***.Clone** ( ) **As** [**JsonOptions**](JsonOptions) + +The copy has the same values and is independent: a change to it does not change the original. + +```tb check_build check_run +Dim a As JsonOptions = JsonOptions.Rfc +a.Indent = 2 +Dim b As JsonOptions = a.Clone() +b.Indent = 8 +Debug.Print a.Indent ' 2 +Debug.Print b.Indent ' 8 +Debug.Print JsonOptions.Rfc.Indent ' 4 +``` + +## Format properties + +### EnsuresAscii +{: .no_toc } + +Whether non-ASCII characters are written as escapes. A **Boolean**, **False** by default. + +Syntax: *options***.EnsuresAscii** [ **=** *flag* ] + +When **True**, [**Serialize**](Json#serialize) writes each UTF-16 code unit above U+007F as `\uxxxx` with lowercase hexadecimal digits, so a character outside the Basic Multilingual Plane becomes two escapes, a surrogate pair. The result contains only ASCII characters. + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Minified +o.EnsuresAscii = True +o.EscapesSolidus = True +Debug.Print Json.Serialize(ChrW$(233) & "a/b", o) ' "\u00e9a\/b" +``` + +### EscapesSolidus +{: .no_toc } + +Whether the solidus is written as an escape. A **Boolean**, **False** by default. + +Syntax: *options***.EscapesSolidus** [ **=** *flag* ] + +When **True**, [**Serialize**](Json#serialize) writes `/` as `\/`. Both spellings are valid JSON for the same character. + +### Indent +{: .no_toc } + +The indentation of pretty-printed output. A **Long**, 4 by default. + +Syntax: *options***.Indent** [ **=** *spaces* ] + +A value of 0 or more starts every member and element on a new line, indented by that many spaces for each level of nesting, and writes a space after each colon. A value of 0 has line breaks and no indentation. A value below 0, and -1 in [**Minified**](#minified), writes the text without white space. + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Pretty +o.Indent = 2 +o.SortsKeys = True +Dim line As Variant +For Each line In Split(Json.Serialize(Json.Map("b", 1, "a", Json.Arr(True)), o), vbLf) + Debug.Print line +Next +' Output: +' { +' "a": [ +' true +' ], +' "b": 1 +' } +``` + +### Newline +{: .no_toc } + +The text that ends a line of pretty-printed output. A **String**, `vbLf` by default. + +Syntax: *options***.Newline** [ **=** *text* ] + +Any string can be used, such as `vbCrLf`. It is not used when [**Indent**](#indent) is below 0. + +### SortsKeys +{: .no_toc } + +Whether the keys of an object are written in sorted order. A **Boolean**, **False** by default. + +Syntax: *options***.SortsKeys** [ **=** *flag* ] + +When **True**, [**Serialize**](Json#serialize) writes the members of every object in the order of their keys, compared by UTF-16 code unit. The objects themselves keep their order. + +## Parse properties + +### DuplicateKeys +{: .no_toc } + +What a repeated key in an object means. An [**eJsonDuplicateKeys**](#ejsonduplicatekeys) member, **jdkError** by default. + +Syntax: *options***.DuplicateKeys** [ **=** *policy* ] + +With **jdkError**, a repeated key is the error `DuplicateKey`. With **jdkLastWins**, the last value is kept, in the position of the first member with that key. + +```tb check_build check_run +Dim text As String = "{""a"":1,""b"":2,""a"":3}" +Debug.Print IsEmpty(Json.Parse(text)) ' True +Debug.Print JsonError.Code ' DuplicateKey + +Dim o As JsonOptions = JsonOptions.Rfc +o.DuplicateKeys = jdkLastWins +Debug.Print Json.Serialize(Json.Parse(text, o), JsonOptions.Minified) ' {"a":3,"b":2} +``` + +The members of the three enumerations are written without a qualifier, as `jdkLastWins` is above; so are their names as types. The qualified form `JsonOptions.eJsonSyntax.jsJsonc` does not compile (TB5027, *Unrecognized member*). + +```tb check_build check_run +Dim s As eJsonSyntax = jsJsonc +Dim t As eJsonType = jtArray +Dim e As eJsonEncodingKind = jekUtf16Be +Debug.Print s & " " & t & " " & e ' 1 5 3 +``` + +```tb check_build expect-error=TB5027 +Dim o As JsonOptions = JsonOptions.Rfc +o.Syntax = JsonOptions.eJsonSyntax.jsJsonc +``` + +### Encoding +{: .no_toc } + +The encoding of bytes and files that are parsed. An [**eJsonEncodingKind**](#ejsonencodingkind) member, **jekAuto** by default. + +Syntax: *options***.Encoding** [ **=** *kind* ] + +With **jekAuto**, a byte order mark selects UTF-8, UTF-16 little endian or UTF-16 big endian, and the encoding is UTF-8 without one. With another member, the bytes are decoded in that encoding, and a byte order mark of the same encoding at the start is skipped. The property does not affect a **String**, and it is not used when text is written, which is always UTF-8. + +### Syntax +{: .no_toc } + +The JSON dialect that is accepted. An [**eJsonSyntax**](#ejsonsyntax) member, **jsRfc8259** by default. + +Syntax: *options***.Syntax** [ **=** *dialect* ] + +**jsRfc8259** accepts only the JSON of RFC 8259. **jsJsonc** also accepts comments, from `//` to the end of the line and between `/*` and `*/`, wherever white space is allowed, and one comma after the last member of an object or the last element of an array. A comment that is not closed is the error `InvalidComment`. Parsing never keeps a comment. + +```tb check_build check_run +Dim text As String = "{""a"": 1, // first" & vbLf & """b"": [1, 2,], /* last */ }" +Debug.Print IsEmpty(Json.Parse(text)) ' True +Debug.Print JsonError.Code ' InvalidSyntax + +Dim v As Variant = Json.Parse(text, JsonOptions.Jsonc) +Debug.Print Json.Serialize(v, JsonOptions.Minified) ' {"a":1,"b":[1,2]} +``` + +## Output properties + +### IncludesBom +{: .no_toc } + +Whether UTF-8 output starts with a byte order mark. A **Boolean**, **False** by default. + +Syntax: *options***.IncludesBom** [ **=** *flag* ] + +When **True**, [**SerializeToUtf8**](Json#serializetoutf8), [**WriteFile**](Json#writefile) and [**WriteLinesFile**](Json#writelinesfile) put the bytes `EF BB BF` first. [**Serialize**](Json#serialize) writes a string and is not affected. + +## Limits + +A limit of 0 means that there is no limit, and so does a value below 0. A limit that is exceeded is the error `LimitExceeded`, except [**MaxDepth**](#maxdepth), which is `DepthLimitExceeded`. + +### MaxArrayItems +{: .no_toc } + +The largest number of elements in an array that is parsed. A **Long**, 1000000 by default. + +Syntax: *options***.MaxArrayItems** [ **=** *n* ] + +### MaxBytes +{: .no_toc } + +The largest size of the input that is parsed. A **Long**, 16777216 (16 MiB) by default. + +Syntax: *options***.MaxBytes** [ **=** *n* ] + +For a byte array it is the number of bytes. For a file it is the size of the file. For a **String** passed to [**Parse**](Json#parse) it is the number of bytes of its UTF-8 encoding. [**ParseLines**](Json#parselines) does not check it for its text. + +### MaxDepth +{: .no_toc } + +The deepest level of nesting. A **Long**, 1000 by default. + +Syntax: *options***.MaxDepth** [ **=** *n* ] + +The root value is at depth 0. A value inside a container is one level deeper than the container. A value deeper than **MaxDepth** is the error `DepthLimitExceeded`, for a scalar as for a container. The limit applies to parsing, to serializing, and to the evaluation of a schema, where each nested subschema and each `$ref` that is followed counts as a level. + +```tb check_build check_run +Dim o As JsonOptions = JsonOptions.Rfc +o.MaxDepth = 2 +Debug.Print IsEmpty(Json.Parse("[[1]]", o)) ' False +Debug.Print IsEmpty(Json.Parse("[[[1]]]", o)) ' True +Debug.Print JsonError.Code ' DepthLimitExceeded +``` + +### MaxObjectMembers +{: .no_toc } + +The largest number of members in an object that is parsed. A **Long**, 1000000 by default. + +Syntax: *options***.MaxObjectMembers** [ **=** *n* ] + +### MaxStringLength +{: .no_toc } + +The longest string that is parsed, in UTF-16 code units. A **Long**, 1048576 (1 MiB) by default. + +Syntax: *options***.MaxStringLength** [ **=** *n* ] + +The limit applies to the keys of objects as to the other strings. It is not applied when a value is written. + +## Schema properties + +### IsFormatAssertion +{: .no_toc } + +Whether the `format` keyword of a schema is checked. A **Boolean**, **False** by default. + +Syntax: *options***.IsFormatAssertion** [ **=** *flag* ] + +By default `format` only annotates, as the schema specification asks: no value is rejected for its format. When **True**, a string that does not have the format named in the schema is a validation error. [**JsonSchema**](JsonSchema#formats) lists the formats that are checked. + +## Enumerations + +### eJsonDuplicateKeys +{: .no_toc } + +What a repeated key in an object means. [**DuplicateKeys**](#duplicatekeys) takes one of these members. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **jdkError** | 0 | A repeated key is the error `DuplicateKey`. | +| **jdkLastWins** | 1 | The last value is kept. | + +### eJsonEncodingKind +{: .no_toc } + +The encoding of bytes that are parsed. [**Encoding**](#encoding) takes one of these members. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **jekAuto** | 0 | A byte order mark selects the encoding; otherwise UTF-8. | +| **jekUtf8** | 1 | UTF-8. | +| **jekUtf16Le** | 2 | UTF-16, little endian. | +| **jekUtf16Be** | 3 | UTF-16, big endian. | + +### eJsonSyntax +{: .no_toc } + +The JSON dialect that is parsed. [**Syntax**](#syntax) takes one of these members. + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **jsRfc8259** | 0 | JSON as RFC 8259 defines it. | +| **jsJsonc** | 1 | JSON with comments and a trailing comma. | + +## See Also + +- [Json](Json) module -- the functions that take options +- [JsonObject](JsonObject) and [JsonArray](JsonArray) classes -- **Dump** takes options +- [JsonSchema](JsonSchema) class -- **IsFormatAssertion** applies to it +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonPathQuery.md b/docs/Reference/Contributed/tsJson/JsonPathQuery.md new file mode 100644 index 000000000..344617067 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonPathQuery.md @@ -0,0 +1,195 @@ +--- +title: JsonPathQuery +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonPathQuery +has_toc: false +--- + +# JsonPathQuery class +{: .no_toc } + +A **JsonPathQuery** is a compiled JSONPath, as RFC 9535 defines the language. [**Json.CompilePath**](Json#compilepath) creates one. A query that is compiled once can be run on any number of documents, with the same four verbs as the [**Json**](Json) module: [**Query**](#query), [**QueryOne**](#queryone), [**TryQueryOne**](#tryqueryone) and [**QueryHits**](#queryhits). + +```tb check_build check_run +Dim q As JsonPathQuery = Json.CompilePath("$.items[*].id") +Dim a As Variant = Json.Parse("{""items"":[{""id"":1},{""id"":2}]}") +Dim b As Variant = Json.Parse("{""items"":[{""id"":7}]}") +Debug.Print q.Source ' $.items[*].id +Debug.Print Json.Serialize(q.Query(a), JsonOptions.Minified) ' [1,2] +Debug.Print Json.Serialize(q.Query(b), JsonOptions.Minified) ' [7] +Debug.Print q.QueryOne(b) ' 7 + +Dim v As Variant +Debug.Print q.TryQueryOne(Json.Parse("{}"), v) ' False +Debug.Print JsonError.HasError ' False +``` + +A query that matches nothing is not an error. Each verb clears the last error when it starts and does not set one, because the path was checked when it was compiled; the origin of any error is therefore `Json.CompilePath` or the facade function that compiled the path. + +* TOC +{:toc} + +## Syntax + +A JSONPath starts with `$`, the root of the document, followed by any number of segments. A path of `$` alone selects the document itself. + +| Syntax | Selects | +|:-------|:--------| +| `.name` | The member `name` of an object. A name starts with a letter, an underscore or a non-ASCII character, and goes on with those and digits. | +| `.*` or `[*]` | Every member of an object, and every element of an array. | +| `['name']` or `["name"]` | The member of an object with that key. The string can hold the escapes `\b \f \n \r \t \/ \\ \' \"` and `\uXXXX`, with surrogate pairs. | +| `[3]`, `[-1]` | The element of an array at that index. A negative index counts from the end. | +| `[start:end:step]` | The elements of an array from *start* up to, not including, *end*, in steps of *step*. Each part is optional, and a negative *step* goes backwards. The rules are those of RFC 9535. A step of 0 selects nothing. | +| `[?filter]` | The members and elements for which the filter is true. | +| `[a, b, ...]` | The union of selectors, each of the kinds above, in the order written. | +| `..name`, `..*`, `..[...]` | The same selection made on the value and on every value inside it, at any depth. | + +The selection works on values in document order: a value comes before the values inside it, and the members and elements in the order they have in the document. A value that several selectors select appears more than once. Space, tab, line feed and carriage return are allowed between tokens. + +A **filter** is an expression made of these parts: + +| Part | Meaning | +|:-----|:--------| +| `@` and `$` followed by segments | A query on the current element (`@`) or on the document (`$`). On its own it is true when it selects at least one value. | +| `==`, `!=`, `<`, `<=`, `>`, `>=` | A comparison of two single values: a query that selects exactly one value, a literal, or a function. | +| `&&`, `\|\|`, `!`, `( )` | And, or, not, and grouping. | +| Literals | A string in single or double quotes, an integer, `true`, `false` and `null`. | +| `length(x)` | The number of characters in a string, of elements in an array, or of members in an object. | +| `count(query)` | The number of values a query selects. | +| `value(query)` | The value that a query selects, when it selects exactly one. | +| `match(text, pattern)`, `search(text, pattern)` | Whether the whole text, or a part of it, matches an I-Regexp. The text and the pattern are strings. The patterns of [**Json.IsMatchPattern**](Json#ismatchpattern) apply. | + +Values are compared as [**Json.Equals**](Json#equals) compares them for `==` and `!=`. The ordering comparisons apply to two numbers and to two strings; strings are ordered by UTF-16 code unit. `length` counts a string in UTF-16 code units. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""a"":[""a"",""b"",""c"",""d""],""o"":{""j"":1,""k"":{""j"":2}}}") +Debug.Print Json.Serialize(Json.Query(doc, "$.a[1:3]"), JsonOptions.Minified) ' ["b","c"] +Debug.Print Json.Serialize(Json.Query(doc, "$.a[::-1]"), JsonOptions.Minified) ' ["d","c","b","a"] +Debug.Print Json.Serialize(Json.Query(doc, "$.a[0, -1]"), JsonOptions.Minified) ' ["a","d"] +Debug.Print Json.Serialize(Json.Query(doc, "$..j"), JsonOptions.Minified) ' [1,2] +Debug.Print Json.Serialize(Json.Query(doc, "$.o.*"), JsonOptions.Minified) ' [1,{"j":2}] + +Dim words As Variant = Json.Parse("{""a"":[{""b"":""j""},{""b"":""kilo""}]}") +Debug.Print Json.Serialize(Json.Query(words, "$.a[?match(@.b, ""[jk]"")]"), JsonOptions.Minified) ' [{"b":"j"}] +Debug.Print Json.Serialize(Json.Query(words, "$.a[?search(@.b, ""[jk]"")]"), JsonOptions.Minified) ' [{"b":"j"},{"b":"kilo"}] +Debug.Print Json.Serialize(Json.Query(words, "$.a[?length(@.b) > 1]"), JsonOptions.Minified) ' [{"b":"kilo"}] +``` + +### Where the syntax departs from RFC 9535 + +The language of the package is a part of RFC 9535, with these differences. The first four are defects of version 1.0.15.0, because the RFC states the behavior they depart from; the first three give wrong results with no error, and [Known problems](.#known-problems-in-version-10150) shows them. + +- **A comparison with a missing value is false.** In RFC 9535 a query that selects nothing is a value that is equal to another such query and to nothing else, so `@.a != 1` is true for an element without `a`, and `@.a == @.b` is true for an element that has neither. In this package a comparison is false whenever either side selects no value or more than one, whatever the operator is. The elements without `a` are not selected by `[?@.a != 1]`, although `[?!(@.a == 1)]` selects them. +- **`<=` and `>=` hold only for numbers and strings.** In RFC 9535 `a <= b` is true when `a < b` or `a == b`, whatever the type of the values. Here two equal booleans, two nulls or two equal arrays do not satisfy `<=`. +- **A function call is not checked when the path is compiled.** RFC 9535 makes a call to an unknown function, a call with the wrong number or kind of arguments, and a query that does not select a single value inside a comparison into errors. Here the path compiles, and the expression is false for every element. The exception is a function name that starts with `t`, `f` or `n`: it is read as the word of a literal, and the path is the error `InvalidPath` with the message *Unknown literal 'name'.* +- **A number literal in a filter is an integer.** RFC 9535 also allows a fraction and an exponent. A fraction or an exponent, as in `@.price < 10.5` or `@.n == 1e3`, ends the expression early and the path is the error `InvalidPath`, with the message *Expected ']' in JSONPath.* The integer must fit a **Long**. Compare with a whole number, or compare a string. +- **`length` counts UTF-16 code units.** RFC 9535 counts the Unicode characters of a string, so the length of a character outside the Basic Multilingual Plane is 1 there and 2 here. +- **A hit is a value and a pointer.** The package returns the value, or the value with an RFC 6901 pointer ([**QueryHits**](#queryhits)), and not the normalized path of RFC 9535. +- **An integer is read leniently.** `007` and `-0` are accepted in an index or a slice, which RFC 9535 does not allow. + +The other selectors, the slices, the descendant segment and the order of the results follow the RFC. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("[{""a"":1},{""a"":2},{""b"":3}]") +Debug.Print Json.Serialize(Json.Query(doc, "$[?@.a != 1]"), JsonOptions.Minified) +Debug.Print Json.Serialize(Json.Query(doc, "$[?!(@.a == 1)]"), JsonOptions.Minified) + +Dim same As Variant = Json.Parse("[{""a"":true,""b"":true},{""a"":null,""b"":null},{""a"":1,""b"":1},{""a"":""x"",""b"":""x""}]") +Debug.Print Json.Serialize(Json.Query(same, "$[?@.a == @.b]"), JsonOptions.Minified) +Debug.Print Json.Serialize(Json.Query(same, "$[?@.a <= @.b]"), JsonOptions.Minified) + +Dim p As Variant +Dim q As JsonPathQuery +For Each p In Array("$[?unknown(@.a)]", "$[?foo(@.a)]", "$[?@.price < 10.5]", "$[007]", "$.0") + Set q = Json.CompilePath(CStr(p)) + If q Is Nothing Then + Debug.Print p & " -> " & JsonError.Code & ": " & JsonError.Message + Else + Debug.Print p & " -> compiled" + End If +Next + +Dim text As Variant = Json.Arr(Json.Map("s", "bc", "n", 1), Json.Map("s", ChrW(&HD83D) & ChrW(&HDE00), "n", 2), Json.Map("s", "d", "n", 3)) +Debug.Print Json.Serialize(Json.Query(text, "$[?length(@.s) == 2].n"), JsonOptions.Minified) +' Output: +' [{"a":2}] +' [{"a":2},{"b":3}] +' [{"a":true,"b":true},{"a":null,"b":null},{"a":1,"b":1},{"a":"x","b":"x"}] +' [{"a":1,"b":1},{"a":"x","b":"x"}] +' $[?unknown(@.a)] -> compiled +' $[?foo(@.a)] -> InvalidPath: Unknown literal 'foo'. +' $[?@.price < 10.5] -> InvalidPath: Expected ']' in JSONPath. +' $[007] -> compiled +' $.0 -> InvalidPath: Expected member name. +' [1,2] +``` + + +## Properties + +### Source +{: .no_toc } + +The text of the path that was compiled. A **String**, read-only. + +Syntax: *text* **=** *query***.Source** + +## Methods + +### Query +{: .no_toc } + +Returns the values that the path selects. + +Syntax: *query***.Query** ( *root* ) **As Variant()** + +*root* +: *required* The document. + +Returns an array of the values, in document order, with a lower bound of 0. The values are the objects and arrays inside the document, not copies. The array is unbound when nothing is selected. [**Json.Query**](Json#query) is the same call on a path in text. + +### QueryHits +{: .no_toc } + +Returns the values that the path selects, each with its JSON Pointer. + +Syntax: *query***.QueryHits** ( *root* ) **As** [**JsonHit**](JsonHit)**()** + +*root* +: *required* The document. + +Returns an array of [**JsonHit**](JsonHit) objects, in document order, with a lower bound of 0, or an unbound array when nothing is selected. [**Json.QueryHits**](Json#queryhits) is the same call on a path in text. + +### QueryOne +{: .no_toc } + +Returns the first value that the path selects. + +Syntax: *query***.QueryOne** ( *root* ) **As Variant** + +*root* +: *required* The document. + +Returns the first value in document order, or **Empty** when nothing is selected. A selected JSON null is **Null**. [**Json.QueryOne**](Json#queryone) is the same call on a path in text. + +### TryQueryOne +{: .no_toc } + +Reads the first value that the path selects and returns whether there is one. + +Syntax: *query***.TryQueryOne** ( *root*, *value* ) **As Boolean** + +*root* +: *required* The document. + +*value* +: *required* A **Variant** variable that receives the value. It is passed **ByRef**. When nothing is selected it is **Empty**. + +Returns **True** when the path selects at least one value. [**Json.TryQueryOne**](Json#tryqueryone) is the same call on a path in text. + +## See Also + +- [Json](Json#jsonpath) module -- compiles a path and runs the same queries on a path in text +- [JsonHit](JsonHit) class -- a value with its pointer +- [Json.IsMatchPattern](Json#ismatchpattern) function -- the regular expressions of **match** and **search** +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonSchema.md b/docs/Reference/Contributed/tsJson/JsonSchema.md new file mode 100644 index 000000000..340718477 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonSchema.md @@ -0,0 +1,184 @@ +--- +title: JsonSchema +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonSchema +has_toc: false +--- + +# JsonSchema class +{: .no_toc } + +A **JsonSchema** is a compiled JSON Schema in the draft 2020-12 dialect, with a local catalogue of further schema documents that its `$ref` keywords can name. [**Json.CompileSchema**](Json#compileschema) creates one. [**Validate**](#validate) checks a value against it and returns a [**JsonValidationResult**](JsonValidationResult) that lists every failure. For a single check of one value, [**Json.Validate**](Json#validate) does the same without a catalogue. + +```tb check_build check_run +Dim schema As JsonSchema = Json.CompileSchema(Json.Parse("{""$ref"":""https://example.invalid/name#/$defs/short""}")) +schema.AddDocument "https://example.invalid/name", Json.Parse("{""$defs"":{""short"":{""type"":""string"",""maxLength"":3}}}") +Debug.Print schema.Validate("abc").IsValid ' True +Debug.Print schema.Validate("abcd").IsValid ' False +Debug.Print schema.Validate("abcd").ToString() ' maxLength / - String is longer than maxLength. +``` + +A failure of the value is not an error: it is on the result, and the last error is not set. The last error is set only when the schema cannot be used: a subschema that is neither an object nor a Boolean (`InvalidSchema`), a `$ref` that cannot be resolved (`RemoteRef`), or evaluation that goes deeper than [**MaxDepth**](JsonOptions#maxdepth) (`DepthLimitExceeded`). When that happens, the evaluation stops, so the result holds the failures found up to that point and the failure of the schema itself. + +The package does not check the schema against the meta-schema, and the keyword `$schema` is ignored: every schema is evaluated as draft 2020-12. A keyword with a value of the wrong type, such as a `minimum` that is a string, is read as 0 or ignored, depending on the keyword, and for the keywords that read a number, a list or an object it sets the last error `WrongType` while the evaluation goes on: `Json.Validate(1, {"minimum": "x"})` is valid and leaves the last error `WrongType`. A subschema that is an array, such as the array form of `items` from older drafts, is the error `InvalidSchema`. + +* TOC +{:toc} + +## Keywords + +These keywords are evaluated. Any other keyword, such as `title`, `description`, `default`, `examples`, `$schema`, `$comment`, `$defs`, `contentEncoding`, `$dynamicRef` or `$vocabulary`, is ignored. `$defs` is only a place for `$ref` to point to. + +| Group | Keywords | +|:------|:---------| +| Reference | `$id`, `$ref`, `$anchor` | +| Any value | `type`, `enum`, `const` | +| Strings | `minLength`, `maxLength`, `pattern`, `format` | +| Numbers | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` | +| Arrays | `minItems`, `maxItems`, `uniqueItems`, `prefixItems`, `items`, `contains`, `minContains`, `maxContains`, `unevaluatedItems` | +| Objects | `minProperties`, `maxProperties`, `required`, `dependentRequired`, `properties`, `patternProperties`, `additionalProperties`, `propertyNames`, `dependentSchemas`, `unevaluatedProperties` | +| Combination | `allOf`, `anyOf`, `oneOf`, `not`, `if`, `then`, `else` | + +A schema is an object or a Boolean. **True** accepts every value. **False** rejects every value with a failure whose keyword is `false`. The keywords of a schema object are all applied, `$ref` included: a `$ref` is evaluated as an applicator next to its siblings, as draft 2020-12 says. + +Points that decide what is accepted: + +- **Types.** The `type` keyword takes one name or an array of names: `null`, `boolean`, `object`, `array`, `string`, `number` and `integer`. A number is an `integer` when it has no fractional part, whatever its written form is, so `1.0` is an integer. +- **Numbers** are compared exactly as **Decimal** values. `multipleOf` divides as a **Decimal**, so `0.3` is a multiple of `0.1`. +- **Lengths.** `minLength` and `maxLength` count UTF-16 code units, so a character outside the Basic Multilingual Plane counts as two, where the JSON Schema draft counts characters. A string of the one character U+1F600 fails `"maxLength": 1`. +- **Patterns.** `pattern` and `patternProperties` use the I-Regexp matcher of [**Json.IsSearchPattern**](Json#issearchpattern), and a pattern matches when it is found anywhere in the string. I-Regexp has no anchors: `^` and `$` are ordinary characters, so a pattern such as `^[a-z]+$` requires the characters `^` and `$` to be in the string, and there is no way to require that the whole string matches. The string `abc` fails the pattern `^[a-z]+$`, and the string `^abc$` passes it. The JSON Schema draft names ECMA-262 regular expressions for `pattern`, in which `^` and `$` are anchors, so a schema written for the usual regular expressions rejects valid strings here, with no error. A pattern that is not a valid I-Regexp never matches. The matcher has the defects that [Known problems](.#known-problems-in-version-10150) describes. +- **Uniqueness.** `uniqueItems` compares elements as [**Json.Equals**](Json#equals) does. +- **Unevaluated keywords.** `unevaluatedProperties` and `unevaluatedItems` apply to the members and elements that no other keyword has evaluated, including those evaluated through `$ref`, `allOf`, `anyOf` (the valid branches), `oneOf`, and `if` with `then` or `else`. +- **`contains`** needs at least one element that satisfies the subschema, unless `minContains` says otherwise, and `maxContains` limits the number of such elements. A `minContains` of 0 accepts an array without a match. +- **Reporting.** `anyOf` and `oneOf` report one failure each, and not the failures of their branches. `not` reports one failure when the subschema accepts the value. + +```tb check_build check_run +Dim sch As Variant = Json.Parse("{""if"":{""properties"":{""kind"":{""const"":""car""}},""required"":[""kind""]},""then"":{""required"":[""wheels""]},""else"":{""required"":[""legs""]}}") +Debug.Print Json.Validate(Json.Parse("{""kind"":""car"",""wheels"":4}"), sch).IsValid ' True +Debug.Print Json.Validate(Json.Parse("{""kind"":""car""}"), sch).ToString() ' required / - Missing required property 'wheels'. +Debug.Print Json.Validate(Json.Parse("{""kind"":""dog"",""legs"":4}"), sch).IsValid ' True +``` + +```tb check_build check_run +Debug.Print Json.Validate(Json.Parse("0.3"), Json.Parse("{""multipleOf"":0.1}")).IsValid ' True +Debug.Print Json.Validate(Json.Parse("1.0"), Json.Parse("{""type"":""integer""}")).IsValid ' True +Debug.Print Json.Validate(Json.Parse("1.5"), Json.Parse("{""type"":""integer""}")).IsValid ' False +``` + +The failures that a value can have, with the keyword and the message of each, are on the page of [**JsonValidationError**](JsonValidationError#messages). + +## References and the catalogue + +A `$ref` names another schema. The forms that are understood are: + +| `$ref` | Refers to | +|:-------|:----------| +| `#` | The root of the current resource: the schema, or the subschema with the closest enclosing `$id`. | +| `#/$defs/name` | The place that a JSON Pointer names inside the current resource. | +| `#name` | The subschema with `"$anchor": "name"` inside the current resource. | +| `https://example.com/other` | The catalogue document registered under exactly that URI. | +| `https://example.com/other#/$defs/name`, `...#name` | A place or an anchor inside a catalogue document. | + +A relative reference is resolved against the base URI, which is the `$id` of the closest enclosing resource: a reference that contains `://` is absolute, one that starts with `/` is taken as it is, and any other is added to the base URI after its last `/`. The reference is then looked up in the catalogue by its text, compared by character code. + +There is no network access. A reference to a document that is neither the current resource nor in the catalogue is the error `RemoteRef`, as is a pointer or an anchor that cannot be found. The documents are added with [**AddDocument**](#adddocument), under the URI that `$ref` uses; the `$id` inside a document is not used as its catalogue key. + +## Formats + +The `format` keyword is an annotation that rejects nothing, unless [**IsFormatAssertion**](JsonOptions#isformatassertion) is **True** in the options passed to [**Json.CompileSchema**](Json#compileschema) or [**Json.Validate**](Json#validate). Then it applies to strings, with these checks: + +| Format | A string is accepted when | +|:-------|:--------------------------| +| `date-time` | [**Json.ParseIso8601**](Json#parseiso8601) accepts it and it contains a `T`. A zone is optional. | +| `date` | It has exactly 10 characters and [**Json.ParseIso8601**](Json#parseiso8601) accepts it. | +| `email` | It contains `@`, and not as the first character. | +| `hostname` | It is not empty and has no space. | +| `ipv4` | The check is meant to accept four decimal numbers from 0 to 255, without leading zeros, separated by `.`. In version 1.0.15.0 it rejects almost every valid address (see below). | +| `uuid` | It has 36 characters, and the ninth is `-`. | +| `json-pointer` | It is empty or starts with `/`. | +| `regex`, `iregexp` | It is a valid I-Regexp. | +| `uri`, `uri-reference`, `iri`, `iri-reference`, `uri-template`, `time`, `duration`, `idn-email`, `idn-hostname`, `relative-json-pointer`, `ipv6` | It is not empty. | +| any other name | It is always accepted. | + +Most of these checks are approximations of the formats they are named for. In particular, a `date-time` need not have a zone, which RFC 3339 requires: `2026-08-19T09:15:00` is accepted as a `date-time`, and `2026-08-19` is not. An `email` such as `a@` is accepted, and so is a `hostname` such as `-bad-`; a `json-pointer` such as `/a~2`, whose escape is invalid, is accepted too; and a `uri`, `time` or `ipv6` is accepted when it is not empty. + +The `ipv4` check has a defect in version 1.0.15.0. It builds the number of each part in one variable that is not set back to 0 for the next part, because the code declares the variable inside a loop and twinBASIC, like VBA, keeps its value from one pass to the next. The parts run together as one number, so `1.2.3.4` is read as 1234, which is above 255. Only addresses whose digits make a number not above 255 when joined, such as `0.0.0.0`, `0.1.0.0` and `0.0.0.1`, are accepted; `1.2.3.4`, `10.0.0.1`, `192.168.1.1` and `255.255.255.255` are rejected, although they are valid. The check is silent: the validation fails with the keyword `format`, as for a wrong address. + +```tb check_build check_run +Dim sch As Variant = Json.Parse("{""type"":""string"",""format"":""date""}") +Dim o As JsonOptions = JsonOptions.Rfc +o.IsFormatAssertion = True +Debug.Print Json.Validate("2026-08-19", sch, o).IsValid ' True +Debug.Print Json.Validate("19.08.2026", sch, o).IsValid ' False +Debug.Print Json.Validate("19.08.2026", sch).IsValid ' True + +Dim dt As Variant = Json.Parse("{""format"":""date-time""}") +Debug.Print Json.Validate("2026-08-19T09:15:00", dt, o).IsValid ' True +Debug.Print Json.Validate("2026-08-19T09:15:00+01:00", dt, o).IsValid ' True +Debug.Print Json.Validate("2026-08-19", dt, o).IsValid ' False + +Dim ip As Variant = Json.Parse("{""format"":""ipv4""}") +Debug.Print Json.Validate("0.0.0.0", ip, o).IsValid ' True +Debug.Print Json.Validate("1.2.3.4", ip, o).IsValid ' False +Debug.Print Json.Validate("192.168.1.1", ip, o).IsValid ' False +Debug.Print Json.Validate("256.1.1.1", ip, o).IsValid ' False +``` + +## Where the package departs from JSON Schema 2020-12 + +- **`pattern` and `patternProperties` use I-Regexp**, not the ECMA-262 regular expressions that the draft names. There are no anchors, and the matcher has the defects of [Known problems](.#known-problems-in-version-10150). +- **`minLength` and `maxLength` count UTF-16 code units**, not characters. +- **`format` is checked loosely**, as the table above shows, and `ipv4` is wrong for nearly every address. +- **Some keywords are not evaluated**: `$dynamicRef`, `$dynamicAnchor`, `$vocabulary`, and the `content` keywords. The schema is not checked against the meta-schema, and `$schema` does not select a dialect. +- **A `$ref` is resolved against the schema and the catalogue only.** A relative reference is resolved by a simple rule, and the `$id` of a catalogue document is not its key. +- **An unusable schema stops the evaluation**, and the result holds the failures found up to that point. + +The keywords for types, numbers, arrays, objects, combination and the unevaluated keywords follow the draft. + +```tb check_build check_run +Dim anchored As Variant = Json.Parse("{""pattern"":""^[a-z]+$""}") +Debug.Print Json.Validate("abc", anchored).IsValid ' False +Debug.Print Json.Validate("^abc$", anchored).IsValid ' True +Dim oneChar As Variant = Json.Parse("{""maxLength"":1}") +Debug.Print Json.Validate(ChrW(&HD83D) & ChrW(&HDE00), oneChar).IsValid ' False +JsonError.Clear +Debug.Print Json.Validate(1, Json.Parse("{""minimum"":""x""}")).IsValid ' True +Debug.Print JsonError.Code ' WrongType +``` + +## Methods + +### AddDocument +{: .no_toc } + +Registers a schema document in the catalogue. + +Syntax: *schema***.AddDocument** ( *uri*, *document* ) + +*uri* +: *required* A **String**, the key of the document. It is compared by character code. An empty string is the error `InvalidSchema`, and nothing is added. + +*document* +: *required* The schema document: a [**JsonObject**](JsonObject) or a **Boolean**. A copy is stored, so a later change to the original does not change the catalogue. + +A document that is registered under a URI that is already in the catalogue replaces it. The documents can be added at any time before [**Validate**](#validate) reaches the `$ref` that needs them. + +### Validate +{: .no_toc } + +Checks a value against the schema. + +Syntax: *schema***.Validate** ( *instance* ) **As** [**JsonValidationResult**](JsonValidationResult) + +*instance* +: *required* The value to check. + +Returns the result, which is never **Nothing**. It lists every failure of the value, in the order the keywords are evaluated. The options that [**Json.CompileSchema**](Json#compileschema) was given apply to every call. The call clears the last error when it starts, and sets it only for a schema that cannot be used, as described above. + +## See Also + +- [Json](Json#json-schema) module -- **Validate** and **CompileSchema** +- [JsonValidationResult](JsonValidationResult) class -- the result of **Validate** +- [JsonValidationError](JsonValidationError) class -- one failure +- [JsonOptions](JsonOptions) class -- **IsFormatAssertion** and **MaxDepth** +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonValidationError.md b/docs/Reference/Contributed/tsJson/JsonValidationError.md new file mode 100644 index 000000000..48804ea93 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonValidationError.md @@ -0,0 +1,126 @@ +--- +title: JsonValidationError +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonValidationError +has_toc: false +--- + +# JsonValidationError class +{: .no_toc } + +A **JsonValidationError** describes one failure of a value against a schema: the place in the value, the place in the schema, the keyword that failed, and a message. [**JsonValidationResult.Errors**](JsonValidationResult#errors) returns them. The class is not related to [**JsonError**](JsonError), which reports the last error of a function. + +```tb check_build check_run +Dim schema As Variant = Json.Parse("{""properties"":{""age"":{""type"":""integer"",""minimum"":0}}}") +Dim report As JsonValidationResult = Json.Validate(Json.Parse("{""age"":-5}"), schema) +Dim errs() As JsonValidationError = report.Errors() +Dim e As JsonValidationError = errs(0) +Debug.Print e.Keyword ' minimum +Debug.Print e.InstancePointer ' /age +Debug.Print e.SchemaPointer ' /properties/age/minimum +Debug.Print e.Message ' Number is below minimum. +Debug.Print e.ToString() ' minimum /age - Number is below minimum. +``` + +* TOC +{:toc} + +## Properties + +### InstancePointer +{: .no_toc } + +The place in the checked value. A **String**, read-only. + +Syntax: *text* **=** *error***.InstancePointer** + +Returns the RFC 6901 JSON Pointer of the value that failed. The empty string is the value itself. [**Json.PointerGet**](Json#pointerget) finds the value with it. + +### Keyword +{: .no_toc } + +The keyword that failed. A **String**, read-only. + +Syntax: *text* **=** *error***.Keyword** + +Returns the name of the keyword, such as `type`, `required` or `minimum`. Two failures have other names: `false`, for a schema that is **False**, and `$schema`, for a schema that is neither an object nor a Boolean. `depth` stands for evaluation that went deeper than [**MaxDepth**](JsonOptions#maxdepth). + +### Message +{: .no_toc } + +A description of the failure. A **String**, read-only. + +Syntax: *text* **=** *error***.Message** + +Returns the message in English. [Messages](#messages) lists them. + +### SchemaPointer +{: .no_toc } + +The place in the schema. A **String**, read-only. + +Syntax: *text* **=** *error***.SchemaPointer** + +Returns the path that the evaluation took to the keyword, written as a JSON Pointer, such as `/properties/age/minimum`. Where a `$ref` was followed, the path continues after `/$ref`, so it is a path of the evaluation, and not always a pointer into one document. + +```tb check_build check_run +Dim sc As JsonSchema = Json.CompileSchema(Json.Parse("{""$ref"":""https://example.invalid/name#/$defs/short""}")) +sc.AddDocument "https://example.invalid/name", Json.Parse("{""$defs"":{""short"":{""type"":""string"",""maxLength"":3}}}") +Dim errs() As JsonValidationError = sc.Validate("abcd").Errors() +Debug.Print errs(0).SchemaPointer ' /$ref/maxLength +``` + +## Methods + +### ToString +{: .no_toc } + +Returns the failure as one line of text. + +Syntax: *text* **=** *error***.ToString** ( ) **As String** + +Returns the keyword, a space, the instance pointer (`/` for the value itself), a space, a hyphen, a space and the message, as in `minimum /age - Number is below minimum.`. + +## Messages + +These are the messages, by keyword. A message in quotes holds the name or the number that the failure concerns. + +| Keyword | Message | +|:--------|:--------| +| `type` | *Type is not 'name'.* for one type name, *Type is not in the type list.* for a list | +| `const` | *Value does not equal const.* | +| `enum` | *Value is not in enum.* | +| `minLength` | *String is shorter than minLength.* | +| `maxLength` | *String is longer than maxLength.* | +| `pattern` | *String does not match pattern.* | +| `format` | *String does not match format 'name'.* | +| `minimum` | *Number is below minimum.* | +| `maximum` | *Number is above maximum.* | +| `exclusiveMinimum` | *Number is not above exclusiveMinimum.* | +| `exclusiveMaximum` | *Number is not below exclusiveMaximum.* | +| `multipleOf` | *Number is not a multiple of multipleOf.* | +| `minItems` | *Array is shorter than minItems.* | +| `maxItems` | *Array is longer than maxItems.* | +| `uniqueItems` | *Array items are not unique.* | +| `contains` | *Array does not contain enough matching items.* | +| `maxContains` | *Array contains too many matching items.* | +| `minProperties` | *Object has too few properties.* | +| `maxProperties` | *Object has too many properties.* | +| `required` | *Missing required property 'name'.* | +| `dependentRequired` | *Missing dependent property 'name'.* | +| `anyOf` | *Instance matches no anyOf schema.* | +| `oneOf` | *Instance matches N oneOf schemas.*, where N is the number of schemas that matched: 0, or more than 1 | +| `not` | *Instance matches the negated schema.* | +| `false` | *Schema is false.* | +| `$ref` | *Schema $ref is not in the local catalogue.* | +| `$schema` | *Schema is not an object.* | +| `depth` | *Maximum nesting depth exceeded.* | + +A keyword that fails for a part of the value, such as `items` or `properties`, reports the failures of its subschema under their own keywords, with the pointer of the part. + +## See Also + +- [JsonValidationResult](JsonValidationResult) class -- holds the failures of one validation +- [JsonSchema](JsonSchema) class -- the keywords that are checked +- [JsonError](JsonError) class -- reports the last error of a function +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/JsonValidationResult.md b/docs/Reference/Contributed/tsJson/JsonValidationResult.md new file mode 100644 index 000000000..abec40733 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/JsonValidationResult.md @@ -0,0 +1,89 @@ +--- +title: JsonValidationResult +parent: tsJson Package +permalink: /tB/Packages/Contributed/tsJson/JsonValidationResult +has_toc: false +--- + +# JsonValidationResult class +{: .no_toc } + +A **JsonValidationResult** is the report of a schema validation: whether the value satisfied the schema, and if it did not, the list of failures. [**Json.Validate**](Json#validate) and [**JsonSchema.Validate**](JsonSchema#validate) return one, never **Nothing**. A failure of the value is only on the result; it does not set the last error of [**JsonError**](JsonError). + +```tb check_build check_run +Dim schema As Variant = Json.Parse("{""type"":""object"",""required"":[""id"",""tags""],""properties"":{""id"":{""type"":""integer"",""minimum"":1},""tags"":{""type"":""array"",""items"":{""type"":""string""},""uniqueItems"":true}},""additionalProperties"":false}") +Dim inst As Variant = Json.Parse("{""id"":0,""tags"":[""a"",1,""a""],""extra"":true}") +Dim report As JsonValidationResult = Json.Validate(inst, schema) +Debug.Print report.IsValid +Debug.Print report.ErrorCount + +Dim line As Variant +For Each line In Split(report.ToString(), vbLf) + Debug.Print line +Next +' Output: +' False +' 4 +' minimum /id - Number is below minimum. +' uniqueItems /tags - Array items are not unique. +' type /tags/1 - Type is not 'string'. +' false /extra - Schema is false. +``` + +* TOC +{:toc} + +## Properties + +### ErrorCount +{: .no_toc } + +The number of failures. A **Long**, read-only. + +Syntax: *n* **=** *result***.ErrorCount** + +### IsValid +{: .no_toc } + +Whether the value satisfied the schema. A **Boolean**, read-only. + +Syntax: *flag* **=** *result***.IsValid** + +Returns **True** when [**ErrorCount**](#errorcount) is 0. + +## Methods + +### Errors +{: .no_toc } + +Returns the failures. + +Syntax: *result***.Errors** ( ) **As** [**JsonValidationError**](JsonValidationError)**()** + +Returns an array of [**JsonValidationError**](JsonValidationError) objects, in the order the failures were found, with a lower bound of 0. The array is unbound when the value is valid, so check [**IsValid**](#isvalid) or [**ErrorCount**](#errorcount) first. + +```tb check_build check_run +Dim report As JsonValidationResult = Json.Validate(Json.Parse("[1,""x""]"), Json.Parse("{""items"":{""type"":""number""}}")) +Dim errs() As JsonValidationError = report.Errors() +Debug.Print UBound(errs) - LBound(errs) + 1 ' 1 +Debug.Print errs(0).Keyword ' type +Debug.Print errs(0).InstancePointer ' /1 +Debug.Print errs(0).SchemaPointer ' /items/type +Debug.Print errs(0).Message ' Type is not 'number'. +``` + +### ToString +{: .no_toc } + +Returns the report as text. + +Syntax: *text* **=** *result***.ToString** ( ) **As String** + +Returns `valid` when there is no failure. Otherwise returns one line for each failure, separated by a line feed (`vbLf`), each line as [**JsonValidationError.ToString**](JsonValidationError#tostring) writes it. + +## See Also + +- [JsonValidationError](JsonValidationError) class -- one failure +- [JsonSchema](JsonSchema) class -- validates a value against a compiled schema +- [Json](Json#validate) module -- **Validate** for a single check +- [tsJson Package](.) -- the package diff --git a/docs/Reference/Contributed/tsJson/index.md b/docs/Reference/Contributed/tsJson/index.md new file mode 100644 index 000000000..08b834621 --- /dev/null +++ b/docs/Reference/Contributed/tsJson/index.md @@ -0,0 +1,324 @@ +--- +title: tsJson Package +parent: Contributed Packages +has_toc: false +exclude_from_docs: + - JsonBuf + - JsonEncoding + - JsonErrorStore + - JsonIRegexp + - JsonIo + - JsonIso8601 + - JsonLexer + - JsonLimits + - JsonParser + - JsonPatch + - JsonPath + - JsonPathNode + - JsonPointer + - JsonReNode + - JsonSchemaEngine + - JsonSerializer + - JsonText + - JsonUnicodeCats + - JsonValues + - Tests + - TestsCore + - TestsDates + - TestsExtNst + - TestsExtNstData + - TestsIo + - TestsParse + - TestsPatch + - TestsPath + - TestsPointer + - TestsRegexp + - TestsSchema + - TestsSerialize + - TestsSmoke + - TestsTour +indexed_from: 1.0.15.0 +permalink: /tB/Packages/Contributed/tsJson/ +--- + +# tsJson Package +{: .no_toc } + +**Author:** thosti. + +The **tsJson** contributed package reads, builds and writes JSON, finds values in a document with JSON Pointer and JSONPath, changes documents with JSON Patch and JSON Merge Patch, validates them against a JSON Schema, and converts ISO 8601 dates. The package describes itself as "Parse, query, patch, validate - one JSON model." This section documents version 1.0.15.0, published under the MIT licence. + +All of it works on one model: a JSON object is a [**JsonObject**](JsonObject), a JSON array is a [**JsonArray**](JsonArray), and the other JSON values are plain **Variant** values. The [**Json**](Json) module is the entry point, with a function for each task. [**JsonOptions**](JsonOptions) holds the options of a call, and [**JsonError**](JsonError) reports what went wrong. + +```tb check_build check_run +Dim doc As Variant = Json.Parse("{""store"":{""book"":[{""title"":""A"",""price"":8.95},{""title"":""B"",""price"":12.99}]}}") +If JsonError.HasError Then + Debug.Print JsonError.ToString() +Else + Debug.Print Json.QueryOne(doc, "$.store.book[?@.price < 10].title") ' A + Debug.Print Json.PointerGet(doc, "/store/book/1/title") ' B + + Dim cheaper As Variant = Json.PointerSet(doc, "/store/book/1/price", 9.5) + Debug.Print Json.Serialize(Json.Diff(doc, cheaper), JsonOptions.Minified) ' [{"op":"replace","path":"/store/book/1/price","value":9.5}] + + Dim report As JsonValidationResult = Json.Validate(doc, Json.Parse("{""type"":""array""}")) + Debug.Print report.ToString() ' type / - Type is not 'array'. +End If +``` + +## The JSON model + +A value of JSON has these counterparts in twinBASIC. A function that reads JSON creates them, and a function that writes JSON accepts them. + +| JSON | In twinBASIC | Notes | +|:-----|:-------------|:------| +| object | [**JsonObject**](JsonObject) | Members in the order they were added. Keys are compared by character code, so `a` and `A` are different keys. | +| array | [**JsonArray**](JsonArray) | Elements numbered from 0. | +| string | **String** | | +| number | **Decimal** when read; any numeric type when written | A **Decimal** keeps every digit. The package rejects a number outside the range of **Decimal** (see [Parse](Json#parse)). | +| `true`, `false` | **Boolean** | | +| `null` | **Null** | Not **Empty** and not **Nothing**. | + +A **Date** is not a JSON value. [**Json.Serialize**](Json#serialize) writes one as an ISO 8601 string in local time, and [**Json.Parse**](Json#parse) never creates one: a string that holds a date stays a string until [**Json.ParseIso8601**](Json#parseiso8601) converts it. **Empty**, **Nothing**, and any object that is not a **JsonObject** or a **JsonArray**, are not JSON values either, and [**Json.Serialize**](Json#serialize) refuses them with the error `UnsupportedType`. [**Json.TypeOfValue**](Json#typeofvalue) classifies a value. + +A value that is read from a document is the object or array inside it, not a copy. Changing it changes the document. [**Json.Clone**](Json#clone) makes a deep copy, and the functions that return a changed document ([**PointerSet**](Json#pointerset), [**PointerRemove**](Json#pointerremove), [**Patch**](Json#patch), [**MergePatch**](Json#mergepatch)) work on a copy and leave their argument as it was. + +Where a function returns a list, the array is unbound when the list is empty, and `UBound` raises run-time error 9 for it. This applies to [**Json.Query**](Json#query), [**Json.QueryHits**](Json#queryhits), [**JsonObject.Keys**](JsonObject#keys), [**JsonObject.Items**](JsonObject#items), [**JsonArray.ToVariantArray**](JsonArray#tovariantarray) and [**JsonValidationResult.Errors**](JsonValidationResult#errors). [**Json.Serialize**](Json#serialize) writes such an array as `[]`. + +## Errors + +The package does not raise run-time errors for bad data or bad arguments. A function that fails returns an empty result (**Empty**, **Nothing**, an empty string or **False**) and sets the last error, which the predeclared object [**JsonError**](JsonError) reports: **HasError** is **True**, **Code** is a short name such as `InvalidSyntax`, and **Message**, **Origin**, the position in the text and the JSON Pointer of the place say more. A function that can fail clears the last error when it starts, so the last error belongs to the latest call. A search that finds nothing, and an invalid regular expression given to a pattern function, are not errors. [**JsonError**](JsonError#error-codes) lists the codes. + +```tb check_build check_run +Dim v As Variant = Json.Parse("[1, 2,]") +Debug.Print IsEmpty(v) ' True +Debug.Print JsonError.Code ' InvalidSyntax + +Debug.Print Json.TryParse("[1, 2,]", v, JsonOptions.Jsonc) ' True +Debug.Print Json.Serialize(v, JsonOptions.Minified) ' [1,2] +``` + +The package itself raises a run-time error on purpose in one case: an assignment to a property of the predeclared [**JsonOptions**](JsonOptions) object, which is a factory (error 5). The other run-time errors that a program meets are those of the language, such as error 91 for a call on the **Nothing** that a failed function returned, error 424 for the assignment of the **Empty** that a failed [**Parse**](Json#parse) returned to a variable of a class type, and error 9 for `UBound` of an unbound array. One more comes from [**Serialize**](Json#serialize), which raises error -2147467259 for a two-dimensional array, and leaves the last error stuck (see [Known problems](#known-problems-in-version-10150)). + +## The standards + +| Standard | What the package provides | +|:---------|:--------------------------| +| RFC 8259, JSON | [**Parse**](Json#parse) and [**Serialize**](Json#serialize), with limits on size and depth, and the JSONC dialect (comments and a trailing comma) as an option. | +| NDJSON, JSON Lines | One value on each line: [**ParseLines**](Json#parselines) and [**SerializeLines**](Json#serializelines), and the same for files. | +| RFC 6901, JSON Pointer | [**PointerGet**](Json#pointerget), [**PointerSet**](Json#pointerset) and the other pointer functions. | +| RFC 9535, JSONPath | [**Query**](Json#query) and the other query functions, and [**CompilePath**](Json#compilepath) for a path that is used again. The syntax and its [departures from the RFC](JsonPathQuery#where-the-syntax-departs-from-rfc-9535) are on the page of [**JsonPathQuery**](JsonPathQuery). | +| RFC 9485, I-Regexp | The regular expressions of JSONPath and of schema patterns, and [**IsMatchPattern**](Json#ismatchpattern) and its two partners. | +| RFC 6902, JSON Patch | [**Patch**](Json#patch), and [**Diff**](Json#diff) to make a patch. | +| RFC 7396, JSON Merge Patch | [**MergePatch**](Json#mergepatch). | +| JSON Schema, draft 2020-12 | [**Validate**](Json#validate) and [**CompileSchema**](Json#compileschema), with a local catalogue of documents. [**JsonSchema**](JsonSchema#keywords) lists the keywords that are checked. | +| ISO 8601 | [**ParseIso8601**](Json#parseiso8601) and [**FormatIso8601**](Json#formatiso8601). | + +The package reads and writes text, byte arrays (UTF-8, UTF-16 little endian and big endian, with a byte order mark detected) and files, and always writes UTF-8. It has no streaming interface and no mapping of JSON onto user-defined types. + +### Where the package departs from the standards + +Each departure is described where it applies; this is the list. + +- **RFC 8259.** The reading is strict. A number that a **Decimal** cannot hold is an error, which the RFC allows an implementation to do ([Parse](Json#parse)). The package accepts a byte order mark in bytes but not at the start of a **String**. +- **RFC 6901.** The form of a pointer as a URI fragment, `#/a/b`, is not accepted. An empty token after another token names the wrong place (a defect, in [Known problems](#known-problems-in-version-10150)). +- **RFC 6902.** A patch can be a single operation object, which the RFC does not allow. The operations behave as the RFC says in every case that was tested: the add, remove, replace, move, copy and test operations, the index rules for arrays and the handling of the root. +- **RFC 7396.** None found. +- **RFC 9535.** A comparison with a missing value is false, `<=` and `>=` are false for equal values that are not numbers or strings, a call of an unknown function is not an error, a number literal in a filter must be an integer, `length` counts UTF-16 code units, and the results are values and pointers, not normalized paths. The first three are defects ([JsonPathQuery](JsonPathQuery#where-the-syntax-departs-from-rfc-9535)). +- **RFC 9485.** The matcher accepts `\d`, `\s` and `\w`, which the RFC leaves out, and does not accept `\n`, `\r`, `\t`, `\-`, `\^` or a hyphen after a single character at the end of a set, which the RFC has. It does not try the other alternatives of a choice once one has matched ([Patterns](Json#patterns)). +- **JSON Schema, draft 2020-12.** `pattern` uses I-Regexp, not ECMA-262 regular expressions; lengths are counted in UTF-16 code units; `format` is checked loosely, and `ipv4` wrongly; some keywords are not evaluated ([JsonSchema](JsonSchema#where-the-package-departs-from-json-schema-2020-12)). +- **ISO 8601.** Only the extended forms with a date, or with a date and a time with seconds, are read ([ParseIso8601](Json#parseiso8601)). + +Compared with the other JSON sections of this reference: [**VBA_JSON**](../VBA_JSON/) and the [**cJson**](../TBMANLIB/cJson) class of TBMANLIB (which uses the VBA-JSON code) return **Dictionary** and **Collection** objects, read numbers as **Double** or, when long, as **String**, and raise run-time error 10001 for text they cannot read, while VBA-JSON does not check for commas between items and ignores text after the value. tsJson has its own classes, reads numbers as **Decimal**, accepts only valid JSON unless JSONC is asked for, and reports errors through **JsonError**. + +## Classes and modules + +- [Json](Json) -- the module that builds, parses and serializes values and applies pointers, queries, patches, schemas and dates + - [Map](Json#map), [Arr](Json#arr), [TypeOfValue](Json#typeofvalue), [IsObject](Json#isobject), [IsArray](Json#isarray), [Clone](Json#clone), [Equals](Json#equals) -- build, classify, copy and compare values + - [Parse](Json#parse), [TryParse](Json#tryparse), [Serialize](Json#serialize), [SerializeToUtf8](Json#serializetoutf8) -- text and bytes + - [ParseFile](Json#parsefile), [TryParseFile](Json#tryparsefile), [WriteFile](Json#writefile) -- files + - [ParseLines](Json#parselines), [TryParseLines](Json#tryparselines), [SerializeLines](Json#serializelines), [ParseLinesFile](Json#parselinesfile), [TryParseLinesFile](Json#tryparselinesfile), [WriteLinesFile](Json#writelinesfile) -- NDJSON + - [PointerGet](Json#pointerget), [PointerExists](Json#pointerexists), [PointerTryGet](Json#pointertryget), [PointerSet](Json#pointerset), [PointerRemove](Json#pointerremove), [PointerEscape](Json#pointerescape), [PointerUnescape](Json#pointerunescape) -- JSON Pointer + - [CompilePath](Json#compilepath), [Query](Json#query), [QueryOne](Json#queryone), [TryQueryOne](Json#tryqueryone), [QueryHits](Json#queryhits) -- JSONPath + - [IsValidPattern](Json#isvalidpattern), [IsMatchPattern](Json#ismatchpattern), [IsSearchPattern](Json#issearchpattern) -- I-Regexp + - [Patch](Json#patch), [MergePatch](Json#mergepatch), [Diff](Json#diff) -- JSON Patch and Merge Patch + - [Validate](Json#validate), [CompileSchema](Json#compileschema) -- JSON Schema + - [ParseIso8601](Json#parseiso8601), [FormatIso8601](Json#formatiso8601) -- dates + - [eJsonType](Json#ejsontype) -- the JSON types +- [JsonObject](JsonObject) -- a JSON object + - [Count](JsonObject#count), [Item](JsonObject#item) -- the size and the members + - [Arr](JsonObject#arr), [Bool](JsonObject#bool), [Map](JsonObject#map), [Num](JsonObject#num), [Str](JsonObject#str), [TryGet](JsonObject#tryget), [ValueType](JsonObject#valuetype) -- typed readers + - [Clear](JsonObject#clear), [Clone](JsonObject#clone), [Dump](JsonObject#dump), [Exists](JsonObject#exists), [Items](JsonObject#items), [Keys](JsonObject#keys), [Merge](JsonObject#merge), [Remove](JsonObject#remove) -- the other members +- [JsonArray](JsonArray) -- a JSON array + - [Count](JsonArray#count), [Item](JsonArray#item) -- the size and the elements + - [Arr](JsonArray#arr), [Bool](JsonArray#bool), [Map](JsonArray#map), [Num](JsonArray#num), [Str](JsonArray#str), [TryGet](JsonArray#tryget), [ValueType](JsonArray#valuetype) -- typed readers + - [Add](JsonArray#add), [Clear](JsonArray#clear), [Clone](JsonArray#clone), [Dump](JsonArray#dump), [Insert](JsonArray#insert), [RemoveAt](JsonArray#removeat), [ToVariantArray](JsonArray#tovariantarray) -- the other members +- [JsonOptions](JsonOptions) -- the options of a call; its predeclared object creates the presets + - [Rfc](JsonOptions#rfc), [Jsonc](JsonOptions#jsonc), [Minified](JsonOptions#minified), [Pretty](JsonOptions#pretty), [Clone](JsonOptions#clone) -- the presets and the copy + - the 15 properties: [DuplicateKeys](JsonOptions#duplicatekeys), [Encoding](JsonOptions#encoding), [EnsuresAscii](JsonOptions#ensuresascii), [EscapesSolidus](JsonOptions#escapessolidus), [IncludesBom](JsonOptions#includesbom), [Indent](JsonOptions#indent), [IsFormatAssertion](JsonOptions#isformatassertion), [MaxArrayItems](JsonOptions#maxarrayitems), [MaxBytes](JsonOptions#maxbytes), [MaxDepth](JsonOptions#maxdepth), [MaxObjectMembers](JsonOptions#maxobjectmembers), [MaxStringLength](JsonOptions#maxstringlength), [Newline](JsonOptions#newline), [SortsKeys](JsonOptions#sortskeys), [Syntax](JsonOptions#syntax) + - [eJsonDuplicateKeys](JsonOptions#ejsonduplicatekeys), [eJsonEncodingKind](JsonOptions#ejsonencodingkind), [eJsonSyntax](JsonOptions#ejsonsyntax) -- the enumerations +- [JsonError](JsonError) -- the predeclared object that reports the last error; it also lists the error codes + - [Clear](JsonError#clear), [Code](JsonError#code), [Column](JsonError#column), [Format](JsonError#format), [HasError](JsonError#haserror), [Line](JsonError#line), [Message](JsonError#message), [Origin](JsonError#origin), [Path](JsonError#path), [Position](JsonError#position), [Snapshot](JsonError#snapshot), [Snippet](JsonError#snippet), [ToString](JsonError#tostring) -- its members + - [tJsonErrInfo](JsonError#tjsonerrinfo) -- the type that **Snapshot** returns +- [JsonPathQuery](JsonPathQuery) -- a compiled JSONPath, and the JSONPath syntax + - [Source](JsonPathQuery#source), [Query](JsonPathQuery#query), [QueryHits](JsonPathQuery#queryhits), [QueryOne](JsonPathQuery#queryone), [TryQueryOne](JsonPathQuery#tryqueryone) -- its members +- [JsonHit](JsonHit) -- one result of a JSONPath query, with its pointer + - [Pointer](JsonHit#pointer), [Value](JsonHit#value) -- its members +- [JsonSchema](JsonSchema) -- a compiled schema with a local catalogue, and the keywords that are checked + - [AddDocument](JsonSchema#adddocument), [Validate](JsonSchema#validate) -- its members +- [JsonValidationResult](JsonValidationResult) -- the report of a validation + - [ErrorCount](JsonValidationResult#errorcount), [Errors](JsonValidationResult#errors), [IsValid](JsonValidationResult#isvalid), [ToString](JsonValidationResult#tostring) -- its members +- [JsonValidationError](JsonValidationError) -- one failure of a validation, and the table of messages + - [InstancePointer](JsonValidationError#instancepointer), [Keyword](JsonValidationError#keyword), [Message](JsonValidationError#message), [SchemaPointer](JsonValidationError#schemapointer), [ToString](JsonValidationError#tostring) -- its members + +## Test code + +The package also contains its own test code, which has no pages: the modules **Tests**, **TestsCore**, **TestsParse**, **TestsSerialize**, **TestsIo**, **TestsDates**, **TestsPointer**, **TestsPath**, **TestsPatch**, **TestsSchema**, **TestsRegexp**, **TestsExtNst**, **TestsExtNstData**, **TestsSmoke** and **TestsTour**. **Tests.RunAll** runs twelve areas, **Core**, **Parse**, **Serialize**, **Io**, **Dates**, **Pointer**, **Path**, **Patch**, **Schema**, **Regexp**, **ExtNst** and **Smoke**, and prints the result of each check with **Debug.Print**, so the lines appear in the DEBUG CONSOLE of the IDE and nowhere else. Each check prints `OK`, `FAIL` or `SKIP`, a failed check does not stop the run, and the last lines give the counts of each area and in total and say that the tests passed or failed. The **Io** area and the demonstration write small files to the folder named by `%TEMP%` and delete them. The **ExtNst** area (**TestsExtNst**, with its data in **TestsExtNstData**) runs the accept and reject cases of the public JSONTestSuite against **Parse**, and skips the cases that the package decides differently: documents with a repeated key, which are an error by default, and numbers outside the range of **Decimal**. **Tests.RunDemo** runs **TestsTour**, which demonstrates the package on one order document and prints each result as pretty-printed JSON, with the calls that made it as comment lines in front. In version 1.0.15.0 a run has 714 checks that pass, none that fail and 6 that are skipped, and it ends with the line `=== tsJson tests passed` followed by `=` characters. The areas have 96 (**Core**), 66 (**Parse**), 22 (**Serialize**), 23 (**Io**), 21 (**Dates**), 29 (**Pointer**), 38 (**Path**), 34 (**Patch**), 62 (**Schema**), 38 (**Regexp**), 278 (**ExtNst**) and 7 (**Smoke**) checks that pass; the six skipped checks are in **ExtNst**. The tests pass although the problems below exist, because no check covers them. + +## Internal modules + +Nineteen more modules and classes are part of the package's implementation and have no pages. The package describes all of them except **JsonLimits** as not part of its public API: **JsonBuf** (the string builder), **JsonEncoding** (the UTF-8 and UTF-16 codecs), **JsonErrorStore** (the storage behind [**JsonError**](JsonError)), **JsonIo** (the file access), **JsonIso8601** (the date conversion behind [**ParseIso8601**](Json#parseiso8601)), **JsonLexer**, **JsonParser** and **JsonText** (the JSON reader), **JsonSerializer** (the writer), **JsonValues** (helpers shared by [**JsonObject**](JsonObject) and [**JsonArray**](JsonArray)), **JsonPointer**, **JsonPatch**, **JsonPath** with its node class **JsonPathNode**, and **JsonSchemaEngine**, which implement the functions of [**Json**](Json) that are named after them, **JsonReNode** (a node of a compiled regular expression), **JsonUnicodeCats** (the table of Unicode categories for `\p{...}`) and **JsonIRegexp**, the I-Regexp matcher that [**IsMatchPattern**](Json#ismatchpattern) and its partners call. **JsonLimits** holds the constants of the default limits, which [**JsonOptions**](JsonOptions#rfc) lists with their values. None of them is declared **Private**, so a project that references the package can name them. + +```tb check_build check_run +Debug.Print JsonLimits.DEFAULT_MAX_DEPTH ' 1000 +Debug.Print JsonLimits.DEFAULT_MAX_BYTES ' 16777216 +``` + +The test modules are public in the same way, so their names, such as **Tests**, are visible to the whole project. A module of the project with the same name takes precedence, with no error: in a project with its own module **Tests**, a call of `Tests.Run` reaches the project's module, and the package's **Tests** can no longer be named. + +```tb check_build slot=file projname=tsjson-tests +Module Tests + Public Function Run() As String + Return "mine" + End Function +End Module +``` + +```tb check_build check_run projname=tsjson-tests +Debug.Print Tests.Run() ' mine +``` + +## Known problems in version 1.0.15.0 + +> [!WARNING] +> In version 1.0.15.0 these problems give a wrong result with no error. +> +> - **A choice in a regular expression is not tried again.** When one alternative matches and the rest of the pattern then fails, the matcher does not try the other alternatives, so `a|ab` does not match `ab`, and `(Mr|Mrs) Smith` does not match `Mrs Smith`, although `(Mrs|Mr) Smith` matches both. RFC 9485 defines the match of the whole pattern. A choice whose alternatives start the same way works when the longer alternative comes first. The matcher serves [**IsMatchPattern**](Json#ismatchpattern), [**IsSearchPattern**](Json#issearchpattern), the JSONPath functions `match` and `search`, and the schema keywords `pattern` and `patternProperties`. +> - **`\n`, `\r` and `\t` are not understood.** RFC 9485 has these escapes. Outside a character set the pattern is invalid, and inside a set `[\n]` means the letter `n`. Write the control character itself in the pattern string, for example `ChrW(9)`, which is an ordinary character in the dialect. The escapes `\-` and `\^`, which the RFC also has, are invalid outside a set, and `[a-]` is invalid, although the RFC allows a last hyphen; put the hyphen first, as in `[-a]`. +> - **A schema `pattern` cannot be anchored.** The dialect has no `^` and `$`: they are ordinary characters, so `^[a-z]+$` needs a `^` and a `$` in the string. The JSON Schema draft names regular expressions in which they are anchors, so a string such as `abc` fails a `pattern` that was written for them. +> - **A JSONPath filter compares a missing value differently from RFC 9535.** A comparison is false whenever one side selects no value, whatever the operator is, so `[?@.a != 1]` leaves out the elements that have no `a`, and `[?@.a == @.b]` leaves out an element that has neither, where the RFC selects both. `<=` and `>=` are false for two equal booleans, nulls or arrays. A call of a function that does not exist is not an error: the filter is false for every element. [**JsonPathQuery**](JsonPathQuery#where-the-syntax-departs-from-rfc-9535) lists these and the other departures. +> - **A JSON Pointer with an empty token after another token names the wrong place.** The token is read as a copy of the token before it, so `/a/` is read as `/a/a`: [**PointerGet**](Json#pointerget) returns the wrong value, and [**PointerSet**](Json#pointerset), [**PointerRemove**](Json#pointerremove) and [**Patch**](Json#patch) change the wrong member. The pointers that [**QueryHits**](Json#queryhits) returns for a member with an empty key are affected. The code declares a variable inside a loop and relies on it being empty in each pass; twinBASIC, like VBA, keeps its value from the pass before. +> - **The `ipv4` format rejects almost every valid address** when [**IsFormatAssertion**](JsonOptions#isformatassertion) is **True**, for the same reason: the number of the previous part is not set back to 0. +> - **A run-time error that escapes from a function stops the last error from being cleared.** [**Json.Serialize**](Json#serialize) raises error -2147467259 for a two-dimensional array. The function has by then marked the call as nested, and the mark stays: from then on a function neither clears the last error when it starts nor sets its origin, so [**JsonError.HasError**](JsonError#haserror) stays **True** after a call that worked, and the origin of an error is `Json.Serialize`. The call `JsonErrorStore.LeaveNested`, of an internal module, ends the mark. +> +> ```tb check_build check_run +> Debug.Print Json.IsMatchPattern("ab", "a|ab") ' False +> Debug.Print Json.IsMatchPattern("Mrs Smith", "(Mr|Mrs) Smith") ' False +> Debug.Print Json.IsMatchPattern("Mrs Smith", "(Mrs|Mr) Smith") ' True +> Debug.Print Json.IsValidPattern("a\tb") ' False +> Debug.Print Json.IsMatchPattern("n", "[\n]") ' True +> Debug.Print Json.IsMatchPattern(vbLf, "[\n]") ' False +> Debug.Print Json.IsValidPattern("a\-b") ' False +> Debug.Print Json.IsValidPattern("[a-]") ' False +> Debug.Print Json.Validate("abc", Json.Parse("{""pattern"":""^[a-z]+$""}")).IsValid ' False +> ``` +> +> ```tb check_build check_run +> Dim doc As Variant = Json.Parse("[{""a"":1},{""a"":2},{""b"":3}]") +> Debug.Print Json.Serialize(Json.Query(doc, "$[?@.a != 1]"), JsonOptions.Minified) ' [{"a":2}] +> Debug.Print Json.Serialize(Json.Query(Json.Parse("[{""c"":1}]"), "$[?@.a == @.b]"), JsonOptions.Minified) ' [] +> Debug.Print Json.Serialize(Json.Query(doc, "$[?unknown(@.a)]"), JsonOptions.Minified) ' [] +> Debug.Print JsonError.HasError ' False +> ``` +> +> ```tb check_build check_run +> Dim doc As Variant = Json.Parse("{""a"":{""a"":1,"""":2}}") +> Debug.Print Json.PointerGet(doc, "/a/") +> Debug.Print Json.Serialize(Json.PointerSet(doc, "/a/", 9), JsonOptions.Minified) +> Debug.Print Json.Serialize(Json.PointerRemove(doc, "/a/"), JsonOptions.Minified) +> +> Dim hit As JsonHit = Json.QueryHits(doc, "$.a['']")(0) +> Debug.Print hit.Pointer & " " & hit.Value & " " & Json.PointerGet(doc, hit.Pointer) +> +> Dim i As Long +> For i = 1 To 3 +> Dim n As Long +> n += 1 +> Debug.Print n +> Next +> ' Output: +> ' 1 +> ' {"a":{"a":9,"":2}} +> ' {"a":{"":2}} +> ' /a/ 2 1 +> ' 1 +> ' 2 +> ' 3 +> ``` +> +> ```tb check_build check_run +> Dim o As JsonOptions = JsonOptions.Rfc +> o.IsFormatAssertion = True +> Dim ip As Variant = Json.Parse("{""format"":""ipv4""}") +> Debug.Print Json.Validate("0.0.0.0", ip, o).IsValid ' True +> Debug.Print Json.Validate("1.2.3.4", ip, o).IsValid ' False +> Debug.Print Json.Validate("192.168.1.1", ip, o).IsValid ' False +> ``` +> +> ```tb check_build check_run +> Dim grid(0 To 1, 0 To 1) As Variant +> JsonError.Clear +> Dim text As String +> On Error Resume Next +> text = Json.Serialize(grid, JsonOptions.Minified) +> Debug.Print Err.Number & " " & Err.Description ' -2147467259 Unspecified error +> Err.Clear +> On Error GoTo 0 +> +> Dim v As Variant = Json.Parse("{") +> v = Json.Parse("[1]") +> Debug.Print JsonError.HasError & " " & JsonError.Code & " " & JsonError.Origin ' True UnexpectedEnd Json.Serialize +> +> JsonErrorStore.LeaveNested +> v = Json.Parse("[1]") +> Debug.Print JsonError.HasError ' False +> ``` + +> [!NOTE] +> The package has three more problems, each of which is an error that the reader cannot miss. +> +> - **Numbers must fit a Decimal.** [**Parse**](Json#parse) rejects a valid JSON number with the error `InvalidNumber` when its digits make a whole number above 79228162514264337593543950335, or its scale is outside -28 to 28, so `1e29`, `1e-29`, a number of 36 digits such as pi, and the largest **Double**, `1.7976931348623157e308`, are errors. [**Serialize**](Json#serialize) fails the same way for a **Double** above that range. RFC 8259 allows an implementation to limit the range of numbers. +> - **A JSONPath filter takes integer literals only.** RFC 9535 allows a fraction and an exponent. `[?@.price < 10.5]` is the error `InvalidPath`. Compare with a whole number, or compare strings. +> - **`Tests.SetContinueOnFail` has no effect.** A failed check never stops **Tests.RunAll**, whatever the setting. +> +> ```tb check_build check_run +> Debug.Print IsEmpty(Json.Parse("1e29")) ' True +> Debug.Print JsonError.Code ' InvalidNumber +> Debug.Print IsEmpty(Json.Parse("1.7976931348623157e308")) ' True +> Debug.Print IsEmpty(Json.Parse("1e28")) ' False +> Debug.Print Json.CompilePath("$[?@.price < 10.5]") Is Nothing ' True +> Debug.Print JsonError.Code ' InvalidPath +> ``` + +The first check of this sample fails on purpose. The second check still runs after `SetContinueOnFail False`: + +```tb check_build check_run +Tests.BeginRun +Tests.SetContinueOnFail False +Tests.CheckTrue "first", False +Tests.CheckTrue "second", True +Debug.Print Tests.FailCount() +' Output: +' === tsJson tests =========================================== +' FAIL first +' expected: True +' actual: False +' OK second +' 1 +``` + +## See Also + +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project +- [VBA_JSON](../VBA_JSON/) package -- JSON parsing and writing with **Dictionary** and **Collection** objects +- [cJson](../TBMANLIB/cJson) class -- the JSON class of TBMANLIB diff --git a/docs/_book.yml b/docs/_book.yml index e68901a4c..06c6fbe0e 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -565,6 +565,13 @@ parts: landing_is_target: true outline_closed: true + - title: tsJson Package + subtitle: A JSON model with parsing, JSONPath queries, JSON Pointer, JSON Patch and JSON Schema validation + landing_page: /tB/Packages/Contributed/tsJson/ + page: /tB/Packages/Contributed/tsJson/ + landing_is_target: true + outline_closed: true + - title: tsPwGen Package subtitle: Passwords, PINs and passphrases, with their entropy known before they are generated landing_page: /tB/Packages/Contributed/tsPwGen/ diff --git a/indexer/snapshots/contributed/tsJson/api.json b/indexer/snapshots/contributed/tsJson/api.json new file mode 100644 index 000000000..a266e6adb --- /dev/null +++ b/indexer/snapshots/contributed/tsJson/api.json @@ -0,0 +1,1416 @@ +{ + "format": 3, + "package": "tsJson", + "project": "tsJson", + "version": "1.0.15.0", + "types": [ + { + "name": "Json", + "kind": "module", + "vis": "public", + "file": "Json.twin", + "signature": "Public Module Json", + "description": "tsJson facade: parse, build, serialize, pointer, query, patch, and validate JSON.", + "members": [ + {"name":"Arr","kind":"function","vis":"public","signature":"Public Function Arr(ParamArray values() As Variant) As JsonArray","returnType":"JsonArray","description":"Empty array, or an array of the given values."}, + {"name":"AsArray","kind":"function","vis":"private","signature":"Private Function AsArray(ByVal values As Variant) As JsonArray","returnType":"JsonArray"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone(ByVal value As Variant) As Variant","returnType":"Variant","description":"Deep copy of a JSON value."}, + {"name":"CompilePath","kind":"function","vis":"public","signature":"Public Function CompilePath(ByVal path As String) As JsonPathQuery","returnType":"JsonPathQuery","description":"Compiles a JSONPath (RFC 9535). Syntax errors set `JsonError`."}, + {"name":"CompileSchema","kind":"function","vis":"public","signature":"Public Function CompileSchema(ByVal schema As Variant, Optional ByVal options As JsonOptions = Nothing) As JsonSchema","returnType":"JsonSchema","description":"Compiles a schema. Register further documents with `AddDocument`."}, + {"name":"Diff","kind":"function","vis":"public","signature":"Public Function Diff(ByVal fromValue As Variant, ByVal toValue As Variant) As JsonArray","returnType":"JsonArray","description":"RFC 6902 ops such that `Patch(a, Diff(a, b))` equals `b`."}, + {"name":"Equals","kind":"function","vis":"public","signature":"Public Function Equals(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean","description":"Structural JSON equality. Object key order does not matter."}, + {"name":"FormatIso8601","kind":"function","vis":"public","signature":"Public Function FormatIso8601(ByVal value As Date) As String","returnType":"String","description":"Formats a `Date` as local ISO-8601 without timezone."}, + {"name":"InputText","kind":"function","vis":"private","signature":"Private Function InputText(ByVal json As Variant, ByVal options As JsonOptions) As String","returnType":"String"}, + {"name":"IsArray","kind":"function","vis":"public","signature":"Public Function IsArray(ByVal value As Variant) As Boolean","returnType":"Boolean","description":"True when `value` is a `JsonArray`."}, + {"name":"IsMatchPattern","kind":"function","vis":"public","signature":"Public Function IsMatchPattern(ByVal text As String, ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `text` matches `pattern` in full. Invalid patterns return `False`."}, + {"name":"IsObject","kind":"function","vis":"public","signature":"Public Function IsObject(ByVal value As Variant) As Boolean","returnType":"Boolean","description":"True when `value` is a `JsonObject`."}, + {"name":"IsSearchPattern","kind":"function","vis":"public","signature":"Public Function IsSearchPattern(ByVal text As String, ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `pattern` occurs in `text`. Invalid patterns return `False`."}, + {"name":"IsValidPattern","kind":"function","vis":"public","signature":"Public Function IsValidPattern(ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `pattern` is a valid I-Regexp (RFC 9485). Invalid patterns are not an error."}, + {"name":"LineOptions","kind":"function","vis":"private","signature":"Private Function LineOptions(ByVal options As JsonOptions) As JsonOptions","returnType":"JsonOptions"}, + {"name":"Map","kind":"function","vis":"public","signature":"Public Function Map(ParamArray keysAndValues() As Variant) As JsonObject","returnType":"JsonObject","description":"Empty object, or an object from `key, value` pairs."}, + {"name":"MergePatch","kind":"function","vis":"public","signature":"Public Function MergePatch(ByVal root As Variant, ByVal patch As Variant) As Variant","returnType":"Variant","description":"Applies RFC 7396 Merge Patch to a copy of `root`. JSON null deletes keys."}, + {"name":"ParamCount","kind":"function","vis":"private","signature":"Private Function ParamCount(ByRef args() As Variant) As Long","returnType":"Long"}, + {"name":"Parse","kind":"function","vis":"public","signature":"Public Function Parse(ByVal json As Variant, Optional ByVal options As JsonOptions = Nothing) As Variant","returnType":"Variant","description":"Parses JSON text or bytes. `Nothing` options are RFC 8259 defaults."}, + {"name":"ParseFile","kind":"function","vis":"public","signature":"Public Function ParseFile(ByVal path As String, Optional ByVal options As JsonOptions = Nothing) As Variant","returnType":"Variant","description":"Parses a JSON file. Output encoding of writes is UTF-8."}, + {"name":"ParseIso8601","kind":"function","vis":"public","signature":"Public Function ParseIso8601(ByVal text As String) As Date","returnType":"Date","description":"Parses ISO-8601. No zone is local wall clock; Z or offset is converted to local `Date`."}, + {"name":"ParseLines","kind":"function","vis":"public","signature":"Public Function ParseLines(ByVal text As String, Optional ByVal options As JsonOptions = Nothing) As JsonArray","returnType":"JsonArray","description":"Parses NDJSON / JSON Lines. Empty lines are ignored. Values are minified on write."}, + {"name":"ParseLinesCore","kind":"function","vis":"private","signature":"Private Function ParseLinesCore(ByVal text As String, ByVal options As JsonOptions) As JsonArray","returnType":"JsonArray"}, + {"name":"ParseLinesFile","kind":"function","vis":"public","signature":"Public Function ParseLinesFile(ByVal path As String, Optional ByVal options As JsonOptions = Nothing) As JsonArray","returnType":"JsonArray","description":"Parses an NDJSON file."}, + {"name":"Patch","kind":"function","vis":"public","signature":"Public Function Patch(ByVal root As Variant, ByVal ops As Variant) As Variant","returnType":"Variant","description":"Applies RFC 6902 Patch to a copy of `root`. Failure leaves `root` unchanged."}, + {"name":"PointerEscape","kind":"function","vis":"public","signature":"Public Function PointerEscape(ByVal token As String) As String","returnType":"String","description":"Escapes `~` and `/` for a JSON Pointer token."}, + {"name":"PointerExists","kind":"function","vis":"public","signature":"Public Function PointerExists(ByVal root As Variant, ByVal pointer As String) As Boolean","returnType":"Boolean","description":"True when the pointer exists. A miss is not an error; invalid syntax is."}, + {"name":"PointerGet","kind":"function","vis":"public","signature":"Public Function PointerGet(ByVal root As Variant, ByVal pointer As String) As Variant","returnType":"Variant","description":"Value at a JSON Pointer. Missing sets `JsonError` (`PointerNotFound`)."}, + {"name":"PointerRemove","kind":"function","vis":"public","signature":"Public Function PointerRemove(ByVal root As Variant, ByVal pointer As String) As Variant","returnType":"Variant","description":"Returns a copy of `root` with the value at `pointer` removed."}, + {"name":"PointerSet","kind":"function","vis":"public","signature":"Public Function PointerSet(ByVal root As Variant, ByVal pointer As String, ByVal value As Variant) As Variant","returnType":"Variant","description":"Returns a copy of `root` with `value` stored at `pointer`."}, + {"name":"PointerTryGet","kind":"function","vis":"public","signature":"Public Function PointerTryGet(ByVal root As Variant, ByVal pointer As String, ByRef value As Variant) As Boolean","returnType":"Boolean","description":"Copies the pointer target into `value`. A miss is not an error."}, + {"name":"PointerUnescape","kind":"function","vis":"public","signature":"Public Function PointerUnescape(ByVal token As String) As String","returnType":"String","description":"Unescapes a JSON Pointer token."}, + {"name":"Query","kind":"function","vis":"public","signature":"Public Function Query(ByVal root As Variant, ByVal path As String) As Variant()","returnType":"Variant()","description":"All JSONPath matches. A miss is not an error."}, + {"name":"QueryHits","kind":"function","vis":"public","signature":"Public Function QueryHits(ByVal root As Variant, ByVal path As String) As JsonHit()","returnType":"JsonHit()","description":"JSONPath hits with RFC 6901 Pointers. A miss is not an error."}, + {"name":"QueryOne","kind":"function","vis":"public","signature":"Public Function QueryOne(ByVal root As Variant, ByVal path As String) As Variant","returnType":"Variant","description":"First JSONPath match, or `Empty`. A miss is not an error."}, + {"name":"Serialize","kind":"function","vis":"public","signature":"Public Function Serialize(ByVal value As Variant, Optional ByVal options As JsonOptions = Nothing) As String","returnType":"String","description":"Serializes `value` to JSON text. `Nothing` uses RFC pretty defaults."}, + {"name":"SerializeLines","kind":"function","vis":"public","signature":"Public Function SerializeLines(ByVal values As Variant, Optional ByVal options As JsonOptions = Nothing) As String","returnType":"String","description":"Serializes values as NDJSON. Each line is minified."}, + {"name":"SerializeToUtf8","kind":"function","vis":"public","signature":"Public Function SerializeToUtf8(ByVal value As Variant, Optional ByVal options As JsonOptions = Nothing) As Byte()","returnType":"Byte()","description":"Serializes `value` to UTF-8 bytes."}, + {"name":"TryParse","kind":"function","vis":"public","signature":"Public Function TryParse(ByVal json As Variant, ByRef value As Variant, Optional ByVal options As JsonOptions = Nothing) As Boolean","returnType":"Boolean","description":"Parse into `value`. `False` on error; then `value` is `Empty`."}, + {"name":"TryParseFile","kind":"function","vis":"public","signature":"Public Function TryParseFile(ByVal path As String, ByRef value As Variant, Optional ByVal options As JsonOptions = Nothing) As Boolean","returnType":"Boolean","description":"ParseFile into `value`. `False` on error."}, + {"name":"TryParseLines","kind":"function","vis":"public","signature":"Public Function TryParseLines(ByVal text As String, ByRef value As JsonArray, Optional ByVal options As JsonOptions = Nothing) As Boolean","returnType":"Boolean","description":"ParseLines into `value`. `False` on error."}, + {"name":"TryParseLinesFile","kind":"function","vis":"public","signature":"Public Function TryParseLinesFile(ByVal path As String, ByRef value As JsonArray, Optional ByVal options As JsonOptions = Nothing) As Boolean","returnType":"Boolean","description":"ParseLinesFile into `value`. `False` on error."}, + {"name":"TryQueryOne","kind":"function","vis":"public","signature":"Public Function TryQueryOne(ByVal root As Variant, ByVal path As String, ByRef value As Variant) As Boolean","returnType":"Boolean","description":"First JSONPath match into `value`. A miss is not an error."}, + {"name":"TypeOfValue","kind":"function","vis":"public","signature":"Public Function TypeOfValue(ByVal value As Variant) As eJsonType","returnType":"eJsonType","description":"JSON type of `value`. `Empty`, `Nothing`, and `Date` are `jtUnknown`."}, + {"name":"Validate","kind":"function","vis":"public","signature":"Public Function Validate(ByVal instance As Variant, ByVal schema As Variant, Optional ByVal options As JsonOptions = Nothing) As JsonValidationResult","returnType":"JsonValidationResult","description":"One-shot JSON Schema 2020-12 validation without a catalogue."}, + {"name":"WriteFile","kind":"sub","vis":"public","signature":"Public Sub WriteFile(ByVal path As String, ByVal value As Variant, Optional ByVal options As JsonOptions = Nothing)","description":"Writes `value` as UTF-8. Sets `JsonError` on failure."}, + {"name":"WriteLinesFile","kind":"sub","vis":"public","signature":"Public Sub WriteLinesFile(ByVal path As String, ByVal values As Variant, Optional ByVal options As JsonOptions = Nothing)","description":"Writes NDJSON to `path` as UTF-8."} + ] + }, + { + "name": "eJsonType", + "kind": "enum", + "in": "Json", + "vis": "public", + "file": "Json.twin", + "signature": "Public Enum eJsonType", + "description": "JSON type of a value.", + "values": [ + {"name":"jtUnknown"}, + {"name":"jtNull"}, + {"name":"jtBoolean"}, + {"name":"jtNumber"}, + {"name":"jtString"}, + {"name":"jtArray"}, + {"name":"jtObject"} + ] + }, + { + "name": "JsonArray", + "kind": "class", + "vis": "public", + "file": "JsonArray.twin", + "signature": "Public Class JsonArray", + "description": "0-based JSON array. `For Each` yields values.\n\n### Notes\n- Out-of-range index sets `JsonError` (`IndexOutOfRange`).\n- `Clone` is a deep copy.\n\n### Example\n```basic\nDim a As JsonArray = Json.Arr(1, 2)\nn = a.Item(0)\n```", + "members": [ + {"name":"Add","kind":"sub","vis":"public","signature":"Public Sub Add(ByVal value As Variant)","description":"Appends `value` at the end."}, + {"name":"Arr","kind":"function","vis":"public","signature":"Public Function Arr(ByVal index As Long) As JsonArray","returnType":"JsonArray","description":"Reads a nested array. Out of range or wrong type sets `JsonError`."}, + {"name":"Bool","kind":"function","vis":"public","signature":"Public Function Bool(ByVal index As Long, Optional ByVal default As Boolean = False) As Boolean","returnType":"Boolean","description":"Reads a boolean element. Out of range or JSON null returns `default` without error."}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()","description":"Removes all elements."}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As JsonArray","returnType":"JsonArray","description":"Deep copy with a new container."}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Number of elements."}, + {"name":"Dump","kind":"function","vis":"public","signature":"Public Function Dump(Optional ByVal options As JsonOptions = Nothing) As String","returnType":"String","description":"Serializes this array. `Nothing` uses `JsonOptions.Pretty`."}, + {"name":"EnsureCapacity","kind":"sub","vis":"private","signature":"Private Sub EnsureCapacity(ByVal needed As Long)"}, + {"name":"INITIAL_CAPACITY","kind":"const","vis":"private","signature":"Private Const INITIAL_CAPACITY As Long = 8"}, + {"name":"InRange","kind":"function","vis":"private","signature":"Private Function InRange(ByVal index As Long) As Boolean","returnType":"Boolean"}, + {"name":"Insert","kind":"sub","vis":"public","signature":"Public Sub Insert(ByVal index As Long, ByVal value As Variant)","description":"Inserts `value` at `index`. `index = Count` appends."}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal index As Long) As Variant","returnType":"Variant","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Default 0-based indexer. Out of range on get sets `JsonError` (`IndexOutOfRange`)."}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal index As Long, ByVal value As Variant)","accessor":"Let","attrs":[{"name":"DefaultMember"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal index As Long, ByVal value As Object)","accessor":"Set","attrs":[{"name":"DefaultMember"}]}, + {"name":"Map","kind":"function","vis":"public","signature":"Public Function Map(ByVal index As Long) As JsonObject","returnType":"JsonObject","description":"Reads a nested object. Out of range or wrong type sets `JsonError`."}, + {"name":"Num","kind":"function","vis":"public","signature":"Public Function Num(ByVal index As Long, Optional ByVal default As Variant) As Variant","returnType":"Variant","description":"Reads a number element. Out of range or JSON null returns `default` without error."}, + {"name":"RaiseIndexError","kind":"sub","vis":"private","signature":"Private Sub RaiseIndexError(ByVal index As Long)"}, + {"name":"RemoveAt","kind":"sub","vis":"public","signature":"Public Sub RemoveAt(ByVal index As Long)","description":"Removes the element at `index`."}, + {"name":"Str","kind":"function","vis":"public","signature":"Public Function Str(ByVal index As Long, Optional ByVal default As String = \"\") As String","returnType":"String","description":"Reads a string element. Out of range or JSON null returns `default` without error."}, + {"name":"ToVariantArray","kind":"function","vis":"public","signature":"Public Function ToVariantArray() As Variant()","returnType":"Variant()","description":"Copy of the elements as `Variant()`."}, + {"name":"TryGet","kind":"function","vis":"public","signature":"Public Function TryGet(ByVal index As Long, ByRef value As Variant) As Boolean","returnType":"Boolean","description":"Copies the element into `value` when `index` is in range."}, + {"name":"ValueType","kind":"function","vis":"public","signature":"Public Function ValueType(ByVal index As Long) As eJsonType","returnType":"eJsonType","description":"JSON type of the element. Out of range returns `jtUnknown`."}, + {"name":"_NewEnum","kind":"function","vis":"public","signature":"Public Function _NewEnum() As stdole.IUnknown","returnType":"stdole.IUnknown","hidden":true,"attrs":[{"name":"Hidden"},{"name":"Enumerator"}],"description":"COM enumerator over array values. Hidden; used by `For Each`."}, + {"name":"mCapacity","kind":"field","vis":"private","signature":"Private mCapacity As Long"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mValues","kind":"field","vis":"private","signature":"Private mValues() As Variant"} + ] + }, + { + "name": "JsonBuf", + "kind": "module", + "vis": "public", + "file": "Internal/JsonBuf.twin", + "signature": "Module JsonBuf", + "description": "Linear string builder. Not part of the public API.", + "members": [ + {"name":"AddChar","kind":"sub","vis":"public","signature":"Public Sub AddChar(ByRef buf As tBuf, ByVal codeUnit As Long)"}, + {"name":"AddText","kind":"sub","vis":"public","signature":"Public Sub AddText(ByRef buf As tBuf, ByVal text As String)"}, + {"name":"Ensure","kind":"sub","vis":"private","signature":"Private Sub Ensure(ByRef buf As tBuf, ByVal needed As Long)"}, + {"name":"Init","kind":"sub","vis":"public","signature":"Public Sub Init(ByRef buf As tBuf, Optional ByVal capacity As Long = 0)"}, + {"name":"MIN_CAP","kind":"const","vis":"private","signature":"Private Const MIN_CAP As Long = 64"}, + {"name":"Text","kind":"function","vis":"public","signature":"Public Function Text(ByRef buf As tBuf) As String","returnType":"String"} + ] + }, + { + "name": "tBuf", + "kind": "type", + "in": "JsonBuf", + "vis": "public", + "file": "Internal/JsonBuf.twin", + "signature": "Public Type tBuf", + "fields": [ + {"name":"Data","vis":"public","signature":"Data As String"}, + {"name":"Len","vis":"public","signature":"Len As Long"} + ], + "members": [] + }, + { + "name": "JsonEncoding", + "kind": "module", + "vis": "public", + "file": "Internal/JsonEncoding.twin", + "signature": "Module JsonEncoding", + "description": "UTF-8 and UTF-16 codecs for byte parse and file output. Not part of the public API.", + "members": [ + {"name":"BomOf","kind":"function","vis":"private","signature":"Private Function BomOf(ByRef bytes() As Byte, ByVal lo As Long, ByVal n As Long) As eJsonEncodingKind","returnType":"eJsonEncodingKind"}, + {"name":"BoundsOf","kind":"function","vis":"private","signature":"Private Function BoundsOf(ByRef bytes() As Byte, ByRef lo As Long, ByRef hi As Long) As Boolean","returnType":"Boolean"}, + {"name":"CP_UTF8","kind":"const","vis":"private","signature":"Private Const CP_UTF8 As Long = 65001"}, + {"name":"DecodeBytes","kind":"function","vis":"public","signature":"Public Function DecodeBytes(ByRef bytes() As Byte, ByVal encoding As eJsonEncodingKind) As String","returnType":"String","description":"Decodes `bytes` to a string. `jekAuto` uses a BOM, otherwise UTF-8."}, + {"name":"DecodeUtf16","kind":"function","vis":"private","signature":"Private Function DecodeUtf16(ByRef bytes() As Byte, ByVal startAt As Long, ByVal count As Long, ByVal bigEndian As Boolean) As String","returnType":"String"}, + {"name":"DecodeUtf8","kind":"function","vis":"private","signature":"Private Function DecodeUtf8(ByRef bytes() As Byte, ByVal startAt As Long, ByVal count As Long) As String","returnType":"String"}, + {"name":"ERR_ENC","kind":"const","vis":"private","signature":"Private Const ERR_ENC As String = \"InvalidEncoding\""}, + {"name":"EncodeUtf8","kind":"function","vis":"public","signature":"Public Function EncodeUtf8(ByVal text As String, ByVal includesBom As Boolean) As Byte()","returnType":"Byte()","description":"Encodes `text` as UTF-8, optionally with a BOM."}, + {"name":"MB_ERR_INVALID_CHARS","kind":"const","vis":"private","signature":"Private Const MB_ERR_INVALID_CHARS As Long = 8"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MultiByteToWideChar Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"Utf8Length","kind":"function","vis":"public","signature":"Public Function Utf8Length(ByVal text As String) As Long","returnType":"Long"}, + {"name":"WC_ERR_INVALID_CHARS","kind":"const","vis":"private","signature":"Private Const WC_ERR_INVALID_CHARS As Long = &H80&"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function WideCharToMultiByte Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpDefaultChar As LongPtr, ByVal lpUsedDefaultChar As LongPtr) As Long","returnType":"Long"} + ] + }, + { + "name": "JsonError", + "kind": "class", + "vis": "public", + "file": "JsonError.twin", + "signature": "Public Class JsonError", + "attrs": [{"name":"PredeclaredId"},{"name":"COMCreatable","args":"False"}], + "description": "Last JSON error of the facade.\nThe predeclared instance `JsonError` tracks the current error. `Snapshot` copies it into `tJsonErrInfo` before the next fallible operation overwrites it.\n\n### Notes\n- There is no `SetError` on this class.\n- Getters on the predeclared instance read the last-error store.\n\n### Example\n```basic\nDim v As Variant\nv = Json.Parse(s)\nIf JsonError.HasError Then Debug.Print JsonError.ToString()\n```", + "members": [ + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()","description":"Clears the last-error store.\n\n### Syntax\n`JsonError.Clear`"}, + {"name":"Code","kind":"property","vis":"public","signature":"Public Property Get Code() As String","returnType":"String","accessor":"Get","description":"Machine-readable error code.\n\n### Return value\nA code such as `DuplicateKey` or `InvalidPointer`, or an empty string when `HasError` is false."}, + {"name":"Column","kind":"property","vis":"public","signature":"Public Property Get Column() As Long","returnType":"Long","accessor":"Get","description":"1-based column in the input.\n\n### Return value\nThe column, or `0` if unknown."}, + {"name":"Format","kind":"function","vis":"public","signature":"Public Function Format(ByRef info As tJsonErrInfo) As String","returnType":"String","description":"Formats a `tJsonErrInfo` snapshot like `ToString`."}, + {"name":"HasError","kind":"property","vis":"public","signature":"Public Property Get HasError() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when an error is present.\nThe predeclared instance reads the last-error store.\n\n### Syntax\n`ok = Not JsonError.HasError`\n\n### Return value\n`True` if this instance currently holds an error; otherwise `False`."}, + {"name":"Line","kind":"property","vis":"public","signature":"Public Property Get Line() As Long","returnType":"Long","accessor":"Get","description":"1-based line in the input.\n\n### Return value\nThe line, or `0` if unknown."}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Get Message() As String","returnType":"String","accessor":"Get","description":"Human-readable message for this error.\n\n### Return value\nThe message text, or an empty string when `HasError` is false."}, + {"name":"New","kind":"sub","vis":"private","signature":"Private Sub New()","description":"Blocks external construction. The predeclared instance is the only instance."}, + {"name":"Origin","kind":"property","vis":"public","signature":"Public Property Get Origin() As String","returnType":"String","accessor":"Get","description":"Public entry that produced this error.\n\n### Return value\nA value such as `Json.Parse` or `JsonObject.Item`, or an empty string when `HasError` is false."}, + {"name":"Path","kind":"property","vis":"public","signature":"Public Property Get Path() As String","returnType":"String","accessor":"Get","description":"JSON Pointer of the affected value.\n\n### Return value\nThe pointer string, or empty if none was recorded."}, + {"name":"Position","kind":"property","vis":"public","signature":"Public Property Get Position() As Long","returnType":"Long","accessor":"Get","description":"1-based character position in the input.\n\n### Return value\nThe position, or `0` if unknown."}, + {"name":"Snapshot","kind":"function","vis":"public","signature":"Public Function Snapshot() As tJsonErrInfo","returnType":"tJsonErrInfo","description":"Snapshot of the current error.\nThe next fallible facade call does not change this value.\n\n### Syntax\n`snap = JsonError.Snapshot()`\n\n### Example\n```basic\nDim snap As tJsonErrInfo\nsnap = JsonError.Snapshot()\nv = Json.Parse(nextJson)\n```"}, + {"name":"SnapshotFromStore","kind":"function","vis":"private","signature":"Private Function SnapshotFromStore() As tJsonErrInfo","returnType":"tJsonErrInfo"}, + {"name":"Snippet","kind":"property","vis":"public","signature":"Public Property Get Snippet() As String","returnType":"String","accessor":"Get","description":"Short excerpt around the error location.\n\n### Return value\nThe snippet, or an empty string if none was recorded."}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String","description":"One-line summary for logs.\n\n### Syntax\n`text = JsonError.ToString()`\n\n### Return value\nEmpty when `HasError` is false; otherwise `Origin: Code - Message` plus optional location and snippet."} + ] + }, + { + "name": "tJsonErrInfo", + "kind": "type", + "in": "JsonError", + "vis": "public", + "file": "JsonError.twin", + "signature": "Public Type tJsonErrInfo", + "fields": [ + {"name":"HasError","vis":"public","signature":"HasError As Boolean"}, + {"name":"Code","vis":"public","signature":"Code As String"}, + {"name":"Message","vis":"public","signature":"Message As String"}, + {"name":"Origin","vis":"public","signature":"Origin As String"}, + {"name":"Position","vis":"public","signature":"Position As Long"}, + {"name":"Line","vis":"public","signature":"Line As Long"}, + {"name":"Column","vis":"public","signature":"Column As Long"}, + {"name":"Snippet","vis":"public","signature":"Snippet As String"}, + {"name":"Path","vis":"public","signature":"Path As String"} + ], + "members": [] + }, + { + "name": "JsonErrorStore", + "kind": "module", + "vis": "public", + "file": "Internal/JsonErrorStore.twin", + "signature": "Module JsonErrorStore", + "description": "Internal last-error store. Not part of the public API.", + "members": [ + {"name":"BeginOp","kind":"sub","vis":"public","signature":"Public Sub BeginOp(ByVal origin As String)"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"Code","kind":"function","vis":"public","signature":"Public Function Code() As String","returnType":"String"}, + {"name":"Column","kind":"function","vis":"public","signature":"Public Function Column() As Long","returnType":"Long"}, + {"name":"EnterNested","kind":"sub","vis":"public","signature":"Public Sub EnterNested()","description":"Suppresses nested `BeginOp` so Origin stays the outer public entry."}, + {"name":"HasError","kind":"function","vis":"public","signature":"Public Function HasError() As Boolean","returnType":"Boolean"}, + {"name":"LeaveNested","kind":"sub","vis":"public","signature":"Public Sub LeaveNested()","description":"Ends a matching `EnterNested`."}, + {"name":"Line","kind":"function","vis":"public","signature":"Public Function Line() As Long","returnType":"Long"}, + {"name":"Message","kind":"function","vis":"public","signature":"Public Function Message() As String","returnType":"String"}, + {"name":"Origin","kind":"function","vis":"public","signature":"Public Function Origin() As String","returnType":"String"}, + {"name":"Path","kind":"function","vis":"public","signature":"Public Function Path() As String","returnType":"String"}, + {"name":"Position","kind":"function","vis":"public","signature":"Public Function Position() As Long","returnType":"Long"}, + {"name":"SetError","kind":"sub","vis":"public","signature":"Public Sub SetError( ByVal code As String, ByVal message As String, Optional ByVal origin As String = \"\", Optional ByVal position As Long = 0, Optional ByVal line As Long = 0, Optional ByVal column As Long = 0, Optional ByVal snippet As String = \"\", Optional ByVal path As String = \"\")"}, + {"name":"Snippet","kind":"function","vis":"public","signature":"Public Function Snippet() As String","returnType":"String"}, + {"name":"mCode","kind":"field","vis":"private","signature":"Private mCode As String"}, + {"name":"mColumn","kind":"field","vis":"private","signature":"Private mColumn As Long"}, + {"name":"mHasError","kind":"field","vis":"private","signature":"Private mHasError As Boolean"}, + {"name":"mLine","kind":"field","vis":"private","signature":"Private mLine As Long"}, + {"name":"mMessage","kind":"field","vis":"private","signature":"Private mMessage As String"}, + {"name":"mNestDepth","kind":"field","vis":"private","signature":"Private mNestDepth As Long"}, + {"name":"mOrigin","kind":"field","vis":"private","signature":"Private mOrigin As String"}, + {"name":"mPath","kind":"field","vis":"private","signature":"Private mPath As String"}, + {"name":"mPosition","kind":"field","vis":"private","signature":"Private mPosition As Long"}, + {"name":"mSnippet","kind":"field","vis":"private","signature":"Private mSnippet As String"} + ] + }, + { + "name": "JsonHit", + "kind": "class", + "vis": "public", + "file": "JsonHit.twin", + "signature": "Public Class JsonHit", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "One JSONPath hit: the value and its RFC 6901 Pointer.\nCreated only by `Json.QueryHits` / `JsonPathQuery.QueryHits`.", + "members": [ + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach(ByVal value As Variant, ByVal pointer As String)"}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"Pointer","kind":"property","vis":"public","signature":"Public Property Get Pointer() As String","returnType":"String","accessor":"Get","description":"RFC 6901 JSON Pointer of the hit. Empty string is the document root."}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Get Value() As Variant","returnType":"Variant","accessor":"Get","description":"The matched JSON value."}, + {"name":"mPointer","kind":"field","vis":"private","signature":"Private mPointer As String"}, + {"name":"mValue","kind":"field","vis":"private","signature":"Private mValue As Variant"} + ] + }, + { + "name": "JsonIRegexp", + "kind": "module", + "vis": "public", + "file": "Internal/JsonIRegexp.twin", + "signature": "Module JsonIRegexp", + "description": "I-Regexp (RFC 9485) compile and match. Invalid patterns return False without JsonError.", + "members": [ + {"name":"Advance","kind":"sub","vis":"private","signature":"Private Sub Advance()"}, + {"name":"AdvanceCp","kind":"sub","vis":"private","signature":"Private Sub AdvanceCp(ByVal text As String, ByRef pos As Long)"}, + {"name":"ClassPeekNotEnd","kind":"function","vis":"private","signature":"Private Function ClassPeekNotEnd() As Boolean","returnType":"Boolean"}, + {"name":"Compile","kind":"function","vis":"private","signature":"Private Function Compile(ByVal pattern As String) As JsonReNode","returnType":"JsonReNode"}, + {"name":"CompilePattern","kind":"function","vis":"public","signature":"Public Function CompilePattern(ByVal pattern As String) As JsonReNode","returnType":"JsonReNode","description":"Compiles `pattern`, or `Nothing` when it is not a valid I-Regexp."}, + {"name":"EnterDepth","kind":"function","vis":"private","signature":"Private Function EnterDepth() As Boolean","returnType":"Boolean"}, + {"name":"FlattenConcat","kind":"sub","vis":"private","signature":"Private Sub FlattenConcat(ByVal node As JsonReNode, ByRef parts() As JsonReNode, ByRef n As Long)"}, + {"name":"IsMatch","kind":"function","vis":"public","signature":"Public Function IsMatch(ByVal text As String, ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `text` matches `pattern` in full."}, + {"name":"IsMeta","kind":"function","vis":"private","signature":"Private Function IsMeta(ByVal ch As Long) As Boolean","returnType":"Boolean"}, + {"name":"IsSearch","kind":"function","vis":"public","signature":"Public Function IsSearch(ByVal text As String, ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `pattern` occurs somewhere in `text`."}, + {"name":"IsValid","kind":"function","vis":"public","signature":"Public Function IsValid(ByVal pattern As String) As Boolean","returnType":"Boolean","description":"True when `pattern` is a valid I-Regexp."}, + {"name":"LeaveDepth","kind":"sub","vis":"private","signature":"Private Sub LeaveDepth()"}, + {"name":"MatchAtom","kind":"function","vis":"private","signature":"Private Function MatchAtom(ByVal node As JsonReNode, ByVal text As String, ByRef pos As Long) As Boolean","returnType":"Boolean"}, + {"name":"MatchList","kind":"function","vis":"private","signature":"Private Function MatchList( ByRef parts() As JsonReNode, ByVal idx As Long, ByVal n As Long, ByVal text As String, ByRef pos As Long) As Boolean","returnType":"Boolean"}, + {"name":"MatchNode","kind":"function","vis":"private","signature":"Private Function MatchNode(ByVal node As JsonReNode, ByVal text As String, ByRef pos As Long) As Boolean","returnType":"Boolean"}, + {"name":"MatchRepeatThen","kind":"function","vis":"private","signature":"Private Function MatchRepeatThen( ByVal node As JsonReNode, ByRef parts() As JsonReNode, ByVal idx As Long, ByVal n As Long, ByVal text As String, ByRef pos As Long) As Boolean","returnType":"Boolean"}, + {"name":"NamedClass","kind":"function","vis":"private","signature":"Private Function NamedClass(ByVal lo As Long, ByVal hi As Long, ByVal negate As Boolean) As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseAlt","kind":"function","vis":"private","signature":"Private Function ParseAlt() As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseAtom","kind":"function","vis":"private","signature":"Private Function ParseAtom() As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseClass","kind":"function","vis":"private","signature":"Private Function ParseClass() As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseClassEnd","kind":"function","vis":"private","signature":"Private Function ParseClassEnd(ByRef hi As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseClassItem","kind":"function","vis":"private","signature":"Private Function ParseClassItem(ByVal dest As JsonReNode, ByRef lo As Long, ByRef hi As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseConcat","kind":"function","vis":"private","signature":"Private Function ParseConcat() As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseDecimal","kind":"function","vis":"private","signature":"Private Function ParseDecimal(ByRef n As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseEscape","kind":"function","vis":"private","signature":"Private Function ParseEscape(ByVal inClass As Boolean) As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParsePiece","kind":"function","vis":"private","signature":"Private Function ParsePiece() As JsonReNode","returnType":"JsonReNode"}, + {"name":"ParseQuantifier","kind":"function","vis":"private","signature":"Private Function ParseQuantifier(ByRef minN As Long, ByRef maxN As Long) As Boolean","returnType":"Boolean"}, + {"name":"Peek","kind":"function","vis":"private","signature":"Private Function Peek() As Long","returnType":"Long"}, + {"name":"ReadCp","kind":"function","vis":"private","signature":"Private Function ReadCp(ByVal text As String, ByRef pos As Long, ByRef cp As Long) As Boolean","returnType":"Boolean"}, + {"name":"SearchCompiled","kind":"function","vis":"public","signature":"Public Function SearchCompiled(ByVal text As String, ByVal root As JsonReNode) As Boolean","returnType":"Boolean","description":"True when compiled `root` occurs in `text`."}, + {"name":"UnicodeProp","kind":"function","vis":"private","signature":"Private Function UnicodeProp(ByVal negate As Boolean) As JsonReNode","returnType":"JsonReNode"}, + {"name":"WordClass","kind":"function","vis":"private","signature":"Private Function WordClass(ByVal negate As Boolean) As JsonReNode","returnType":"JsonReNode"}, + {"name":"WsClass","kind":"function","vis":"private","signature":"Private Function WsClass(ByVal negate As Boolean) As JsonReNode","returnType":"JsonReNode"}, + {"name":"mDepth","kind":"field","vis":"private","signature":"Private mDepth As Long"}, + {"name":"mLen","kind":"field","vis":"private","signature":"Private mLen As Long"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mOk","kind":"field","vis":"private","signature":"Private mOk As Boolean"}, + {"name":"mPat","kind":"field","vis":"private","signature":"Private mPat As String"}, + {"name":"mPos","kind":"field","vis":"private","signature":"Private mPos As Long"} + ] + }, + { + "name": "JsonIo", + "kind": "module", + "vis": "public", + "file": "Internal/JsonIo.twin", + "signature": "Module JsonIo", + "description": "File bytes for parse and write. Not part of the public API.", + "members": [ + {"name":"ERR_IO","kind":"const","vis":"private","signature":"Private Const ERR_IO As String = \"IoError\""}, + {"name":"ERR_LIMIT","kind":"const","vis":"private","signature":"Private Const ERR_LIMIT As String = \"LimitExceeded\""}, + {"name":"ReadBytes","kind":"function","vis":"public","signature":"Public Function ReadBytes(ByVal path As String, ByVal options As JsonOptions) As Byte()","returnType":"Byte()","description":"Reads a file as bytes. Enforces `MaxBytes`."}, + {"name":"ReadText","kind":"function","vis":"public","signature":"Public Function ReadText(ByVal path As String, ByVal options As JsonOptions) As String","returnType":"String","description":"Reads `path` as text using `options.Encoding`."}, + {"name":"WriteBytes","kind":"function","vis":"public","signature":"Public Function WriteBytes(ByVal path As String, ByRef bytes() As Byte) As Boolean","returnType":"Boolean","description":"Writes `bytes` to `path`, replacing the file."} + ] + }, + { + "name": "JsonIso8601", + "kind": "module", + "vis": "public", + "file": "Internal/JsonIso8601.twin", + "signature": "Module JsonIso8601", + "description": "ISO-8601 parse and format. Timezone is input-only. Not part of the public API.", + "members": [ + {"name":"DateToSystemTime","kind":"sub","vis":"private","signature":"Private Sub DateToSystemTime(ByVal d As Date, ByRef st As SYSTEMTIME)"}, + {"name":"ERR_DATE","kind":"const","vis":"private","signature":"Private Const ERR_DATE As String = \"InvalidDate\""}, + {"name":"FailDate","kind":"sub","vis":"private","signature":"Private Sub FailDate(ByVal message As String)"}, + {"name":"FormatDate","kind":"function","vis":"public","signature":"Public Function FormatDate(ByVal d As Date) As String","returnType":"String","description":"Formats `d` as local ISO-8601 without timezone."}, + {"name":"IsValidDateText","kind":"function","vis":"public","signature":"Public Function IsValidDateText(ByVal text As String) As Boolean","returnType":"Boolean","description":"True when `text` is valid ISO-8601 under this pack's rules. Does not set JsonError."}, + {"name":"MakeDate","kind":"function","vis":"private","signature":"Private Function MakeDate( ByVal y As Long, ByVal m As Long, ByVal d As Long, ByVal h As Long, ByVal mi As Long, ByVal sec As Long, ByVal ms As Long, ByRef result As Date) As Boolean","returnType":"Boolean"}, + {"name":"Pad2","kind":"function","vis":"private","signature":"Private Function Pad2(ByVal n As Long) As String","returnType":"String"}, + {"name":"Pad3","kind":"function","vis":"private","signature":"Private Function Pad3(ByVal n As Long) As String","returnType":"String"}, + {"name":"Pad4","kind":"function","vis":"private","signature":"Private Function Pad4(ByVal n As Long) As String","returnType":"String"}, + {"name":"ParseDate","kind":"function","vis":"public","signature":"Public Function ParseDate(ByVal text As String) As Date","returnType":"Date","description":"Parses ISO-8601. Missing zone is local; Z or offset is converted to local `Date`."}, + {"name":"ParseDigits","kind":"function","vis":"private","signature":"Private Function ParseDigits(ByVal text As String, ByVal startPos As Long, ByVal length As Long, ByRef value As Long) As Boolean","returnType":"Boolean"}, + {"name":"SystemTimeToDate","kind":"function","vis":"private","signature":"Private Function SystemTimeToDate(ByRef st As SYSTEMTIME) As Date","returnType":"Date"}, + {"name":"SystemTimeToTzSpecificLocalTime","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SystemTimeToTzSpecificLocalTime Lib \"kernel32\" ( ByVal lpTimeZoneInformation As LongPtr, ByRef lpUniversalTime As SYSTEMTIME, ByRef lpLocalTime As SYSTEMTIME) As Long","returnType":"Long"}, + {"name":"ToLocal","kind":"function","vis":"private","signature":"Private Function ToLocal(ByVal wall As Date, ByVal offsetMin As Long) As Date","returnType":"Date"}, + {"name":"mQuiet","kind":"field","vis":"private","signature":"Private mQuiet As Boolean"}, + {"name":"mQuietFail","kind":"field","vis":"private","signature":"Private mQuietFail As Boolean"} + ] + }, + { + "name": "SYSTEMTIME", + "kind": "type", + "in": "JsonIso8601", + "vis": "private", + "public": false, + "file": "Internal/JsonIso8601.twin", + "signature": "Private Type SYSTEMTIME", + "fields": [ + {"name":"wYear","vis":"public","signature":"wYear As Integer"}, + {"name":"wMonth","vis":"public","signature":"wMonth As Integer"}, + {"name":"wDayOfWeek","vis":"public","signature":"wDayOfWeek As Integer"}, + {"name":"wDay","vis":"public","signature":"wDay As Integer"}, + {"name":"wHour","vis":"public","signature":"wHour As Integer"}, + {"name":"wMinute","vis":"public","signature":"wMinute As Integer"}, + {"name":"wSecond","vis":"public","signature":"wSecond As Integer"}, + {"name":"wMilliseconds","vis":"public","signature":"wMilliseconds As Integer"} + ], + "members": [] + }, + { + "name": "JsonLexer", + "kind": "module", + "vis": "public", + "file": "Internal/JsonLexer.twin", + "signature": "Module JsonLexer", + "description": "JSON text scanner. Not part of the public API.", + "members": [ + {"name":"Advance","kind":"sub","vis":"public","signature":"Public Sub Advance(ByRef st As tScan)"}, + {"name":"IsDigit","kind":"function","vis":"public","signature":"Public Function IsDigit(ByVal ch As Long) As Boolean","returnType":"Boolean"}, + {"name":"Open","kind":"sub","vis":"public","signature":"Public Sub Open( ByRef st As tScan, ByVal source As String, ByVal jsonc As Boolean, ByVal maxString As Long)"}, + {"name":"Peek","kind":"function","vis":"public","signature":"Public Function Peek(ByRef st As tScan) As Long","returnType":"Long"}, + {"name":"SkipSpace","kind":"function","vis":"public","signature":"Public Function SkipSpace(ByRef st As tScan) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "tScan", + "kind": "type", + "in": "JsonLexer", + "vis": "public", + "file": "Internal/JsonLexer.twin", + "signature": "Public Type tScan", + "fields": [ + {"name":"Source","vis":"public","signature":"Source As String"}, + {"name":"Len","vis":"public","signature":"Len As Long"}, + {"name":"Pos","vis":"public","signature":"Pos As Long"}, + {"name":"Jsonc","vis":"public","signature":"Jsonc As Boolean"}, + {"name":"MaxString","vis":"public","signature":"MaxString As Long"} + ], + "members": [] + }, + { + "name": "JsonLimits", + "kind": "module", + "vis": "public", + "file": "Internal/JsonLimits.twin", + "signature": "Module JsonLimits", + "description": "Default parse and serialize limits.\n`0` on `JsonOptions` means unlimited. `Nothing` for the `Of` helpers yields these constants.", + "members": [ + {"name":"DEFAULT_MAX_ARRAY_ITEMS","kind":"const","vis":"public","signature":"Public Const DEFAULT_MAX_ARRAY_ITEMS As Long = 1000000"}, + {"name":"DEFAULT_MAX_BYTES","kind":"const","vis":"public","signature":"Public Const DEFAULT_MAX_BYTES As Long = 16777216"}, + {"name":"DEFAULT_MAX_DEPTH","kind":"const","vis":"public","signature":"Public Const DEFAULT_MAX_DEPTH As Long = 1000"}, + {"name":"DEFAULT_MAX_OBJECT_MEMBERS","kind":"const","vis":"public","signature":"Public Const DEFAULT_MAX_OBJECT_MEMBERS As Long = 1000000"}, + {"name":"DEFAULT_MAX_STRING_LENGTH","kind":"const","vis":"public","signature":"Public Const DEFAULT_MAX_STRING_LENGTH As Long = 1048576"}, + {"name":"LONG_MAX","kind":"const","vis":"public","signature":"Public Const LONG_MAX As Long = 2147483647"}, + {"name":"MaxArrayItemsOf","kind":"function","vis":"public","signature":"Public Function MaxArrayItemsOf(ByVal options As JsonOptions) As Long","returnType":"Long"}, + {"name":"MaxBytesOf","kind":"function","vis":"public","signature":"Public Function MaxBytesOf(ByVal options As JsonOptions) As Long","returnType":"Long"}, + {"name":"MaxDepthOf","kind":"function","vis":"public","signature":"Public Function MaxDepthOf(ByVal options As JsonOptions) As Long","returnType":"Long"}, + {"name":"MaxObjectMembersOf","kind":"function","vis":"public","signature":"Public Function MaxObjectMembersOf(ByVal options As JsonOptions) As Long","returnType":"Long"}, + {"name":"MaxStringLengthOf","kind":"function","vis":"public","signature":"Public Function MaxStringLengthOf(ByVal options As JsonOptions) As Long","returnType":"Long"}, + {"name":"TryAddDigit","kind":"function","vis":"public","signature":"Public Function TryAddDigit(ByRef n As Long, ByVal digit As Long) As Boolean","returnType":"Boolean","description":"Sets `n` to `n * 10 + digit` when the result fits in a Long."} + ] + }, + { + "name": "JsonObject", + "kind": "class", + "vis": "public", + "file": "JsonObject.twin", + "signature": "Public Class JsonObject", + "description": "Ordered, case-sensitive JSON object (`vbBinaryCompare`).\n`For Each` yields keys (`String`). Values via `Item` or `Items`.\n\n### Notes\n- Missing key on `Item` / `Remove` sets `JsonError` (`MissingKey`).\n- `Str` / `Num` / `Bool`: missing or JSON null returns the default without error.\n- `Clone` is a deep copy. `Merge` returns a new object.\n\n### Example\n```basic\nDim o As JsonObject = Json.Map(\"n\", 1)\nDebug.Print o.Str(\"n\")\n```", + "members": [ + {"name":"Arr","kind":"function","vis":"public","signature":"Public Function Arr(ByVal key As String) As JsonArray","returnType":"JsonArray","description":"Reads a nested array. Missing or wrong type sets `JsonError` and returns `Nothing`."}, + {"name":"Bool","kind":"function","vis":"public","signature":"Public Function Bool(ByVal key As String, Optional ByVal default As Boolean = False) As Boolean","returnType":"Boolean","description":"Reads a boolean member. Missing or JSON null returns `default` without error."}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()","description":"Removes all members."}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As JsonObject","returnType":"JsonObject","description":"Deep copy with new containers."}, + {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Number of members."}, + {"name":"Dump","kind":"function","vis":"public","signature":"Public Function Dump(Optional ByVal options As JsonOptions = Nothing) As String","returnType":"String","description":"Serializes this object. `Nothing` uses `JsonOptions.Pretty`."}, + {"name":"EnsureCapacity","kind":"sub","vis":"private","signature":"Private Sub EnsureCapacity(ByVal needed As Long)"}, + {"name":"Exists","kind":"function","vis":"public","signature":"Public Function Exists(ByVal key As String) As Boolean","returnType":"Boolean","description":"True when `key` is present. Does not set `JsonError`."}, + {"name":"INITIAL_CAPACITY","kind":"const","vis":"private","signature":"Private Const INITIAL_CAPACITY As Long = 8"}, + {"name":"IndexKey","kind":"function","vis":"private","signature":"Private Function IndexKey(ByVal key As String) As String","returnType":"String"}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal key As String) As Variant","returnType":"Variant","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Default indexer. Missing key on get sets `JsonError` (`MissingKey`) and returns `Empty`."}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal key As String, ByVal value As Variant)","accessor":"Let","attrs":[{"name":"DefaultMember"}]}, + {"name":"Item","kind":"property","vis":"public","signature":"Public Property Set Item(ByVal key As String, ByVal value As Object)","accessor":"Set","attrs":[{"name":"DefaultMember"}]}, + {"name":"Items","kind":"function","vis":"public","signature":"Public Function Items() As Variant()","returnType":"Variant()","description":"Member values in insertion order."}, + {"name":"KEY_PREFIX","kind":"const","vis":"private","signature":"Private Const KEY_PREFIX As Long = 1"}, + {"name":"Keys","kind":"function","vis":"public","signature":"Public Function Keys() As String()","returnType":"String()","description":"Member names in insertion order."}, + {"name":"Map","kind":"function","vis":"public","signature":"Public Function Map(ByVal key As String) As JsonObject","returnType":"JsonObject","description":"Reads a nested object. Missing or wrong type sets `JsonError` and returns `Nothing`."}, + {"name":"Merge","kind":"function","vis":"public","signature":"Public Function Merge(ByVal other As JsonObject, Optional ByVal deep As Boolean = True) As JsonObject","returnType":"JsonObject","description":"Overlay of this object with `other`. Default is a deep merge of nested objects."}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New()"}, + {"name":"Num","kind":"function","vis":"public","signature":"Public Function Num(ByVal key As String, Optional ByVal default As Variant) As Variant","returnType":"Variant","description":"Reads a number member. Missing or JSON null returns `default` without error."}, + {"name":"PutMember","kind":"sub","vis":"private","signature":"Private Sub PutMember(ByVal key As String, ByVal value As Variant)"}, + {"name":"RebuildIndex","kind":"sub","vis":"private","signature":"Private Sub RebuildIndex()"}, + {"name":"Remove","kind":"sub","vis":"public","signature":"Public Sub Remove(ByVal key As String)","description":"Removes the member named `key`. Missing key sets `JsonError` (`MissingKey`)."}, + {"name":"SlotOf","kind":"function","vis":"private","signature":"Private Function SlotOf(ByVal key As String) As Long","returnType":"Long"}, + {"name":"Str","kind":"function","vis":"public","signature":"Public Function Str(ByVal key As String, Optional ByVal default As String = \"\") As String","returnType":"String","description":"Reads a string member. Missing or JSON null returns `default` without error."}, + {"name":"TryGet","kind":"function","vis":"public","signature":"Public Function TryGet(ByVal key As String, ByRef value As Variant) As Boolean","returnType":"Boolean","description":"Copies the member into `value` when `key` exists."}, + {"name":"ValueType","kind":"function","vis":"public","signature":"Public Function ValueType(ByVal key As String) As eJsonType","returnType":"eJsonType","description":"JSON type of the member. Missing key returns `jtUnknown`."}, + {"name":"_NewEnum","kind":"function","vis":"public","signature":"Public Function _NewEnum() As stdole.IUnknown","returnType":"stdole.IUnknown","hidden":true,"attrs":[{"name":"Hidden"},{"name":"Enumerator"}],"description":"COM enumerator over object keys. Hidden; used by `For Each`."}, + {"name":"mCapacity","kind":"field","vis":"private","signature":"Private mCapacity As Long"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mIndex","kind":"field","vis":"private","signature":"Private mIndex As Collection"}, + {"name":"mKeys","kind":"field","vis":"private","signature":"Private mKeys() As String"}, + {"name":"mValues","kind":"field","vis":"private","signature":"Private mValues() As Variant"} + ] + }, + { + "name": "JsonOptions", + "kind": "class", + "vis": "public", + "file": "JsonOptions.twin", + "signature": "Public Class JsonOptions", + "attrs": [{"name":"PredeclaredId"},{"name":"COMCreatable","args":"False"}], + "description": "Per-call options for parse, serialize, path, patch, and schema.\nThe predeclared instance is a factory (`Rfc`, `Jsonc`, `Minified`, `Pretty`), not shared mutable state.\n\n### Notes\n- `JsonOptions.Rfc` is the starting point (RFC 8259 defaults, fresh instance).\n- Do not set properties on `JsonOptions` itself; use a preset or `Clone` first.\n- Limit `0` means unlimited.\n\n### Example\n```basic\nDim o As JsonOptions = JsonOptions.Rfc\no.Newline = vbCrLf\nJson.WriteFile \"config.json\", v, o\n```", + "members": [ + {"name":"ApplyRfcDefaults","kind":"sub","vis":"private","signature":"Private Sub ApplyRfcDefaults(ByVal o As JsonOptions)"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As JsonOptions","returnType":"JsonOptions","description":"Fork of this recipe: same property values on an independent instance."}, + {"name":"DuplicateKeys","kind":"property","vis":"public","signature":"Public Property Get DuplicateKeys() As eJsonDuplicateKeys","returnType":"eJsonDuplicateKeys","accessor":"Get","description":"Duplicate object-key policy. Default `jdkError`."}, + {"name":"DuplicateKeys","kind":"property","vis":"public","signature":"Public Property Let DuplicateKeys(ByVal value As eJsonDuplicateKeys)","accessor":"Let"}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Get Encoding() As eJsonEncodingKind","returnType":"eJsonEncodingKind","accessor":"Get","description":"Byte-parse encoding. Default `jekAuto` (BOM, else UTF-8)."}, + {"name":"Encoding","kind":"property","vis":"public","signature":"Public Property Let Encoding(ByVal value As eJsonEncodingKind)","accessor":"Let"}, + {"name":"EnsuresAscii","kind":"property","vis":"public","signature":"Public Property Get EnsuresAscii() As Boolean","returnType":"Boolean","accessor":"Get","description":"When true, non-ASCII is written as `\\uXXXX`. Default `False`."}, + {"name":"EnsuresAscii","kind":"property","vis":"public","signature":"Public Property Let EnsuresAscii(ByVal value As Boolean)","accessor":"Let"}, + {"name":"EscapesSolidus","kind":"property","vis":"public","signature":"Public Property Get EscapesSolidus() As Boolean","returnType":"Boolean","accessor":"Get","description":"When true, `/` is written as `\\/`. Default `False`."}, + {"name":"EscapesSolidus","kind":"property","vis":"public","signature":"Public Property Let EscapesSolidus(ByVal value As Boolean)","accessor":"Let"}, + {"name":"GuardFactory","kind":"sub","vis":"private","signature":"Private Sub GuardFactory()"}, + {"name":"IncludesBom","kind":"property","vis":"public","signature":"Public Property Get IncludesBom() As Boolean","returnType":"Boolean","accessor":"Get","description":"When true, UTF-8 file output includes a BOM. Default `False`."}, + {"name":"IncludesBom","kind":"property","vis":"public","signature":"Public Property Let IncludesBom(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Indent","kind":"property","vis":"public","signature":"Public Property Get Indent() As Long","returnType":"Long","accessor":"Get","description":"Pretty-print indent. `4` pretty, `-1` minified. Default `4`."}, + {"name":"Indent","kind":"property","vis":"public","signature":"Public Property Let Indent(ByVal value As Long)","accessor":"Let"}, + {"name":"IsFormatAssertion","kind":"property","vis":"public","signature":"Public Property Get IsFormatAssertion() As Boolean","returnType":"Boolean","accessor":"Get","description":"When true, schema `format` is asserted. Default `False` (annotation only)."}, + {"name":"IsFormatAssertion","kind":"property","vis":"public","signature":"Public Property Let IsFormatAssertion(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Jsonc","kind":"property","vis":"public","signature":"Public Property Get Jsonc() As JsonOptions","returnType":"JsonOptions","accessor":"Get","description":"New options with JSONC syntax (comments and trailing commas)."}, + {"name":"MaxArrayItems","kind":"property","vis":"public","signature":"Public Property Get MaxArrayItems() As Long","returnType":"Long","accessor":"Get","description":"Maximum array length. Default `1000000`. `0` means unlimited."}, + {"name":"MaxArrayItems","kind":"property","vis":"public","signature":"Public Property Let MaxArrayItems(ByVal value As Long)","accessor":"Let"}, + {"name":"MaxBytes","kind":"property","vis":"public","signature":"Public Property Get MaxBytes() As Long","returnType":"Long","accessor":"Get","description":"Maximum input size in bytes. Default 16 MiB. `0` means unlimited."}, + {"name":"MaxBytes","kind":"property","vis":"public","signature":"Public Property Let MaxBytes(ByVal value As Long)","accessor":"Let"}, + {"name":"MaxDepth","kind":"property","vis":"public","signature":"Public Property Get MaxDepth() As Long","returnType":"Long","accessor":"Get","description":"Maximum nesting depth. Default `1000`. `0` means unlimited."}, + {"name":"MaxDepth","kind":"property","vis":"public","signature":"Public Property Let MaxDepth(ByVal value As Long)","accessor":"Let"}, + {"name":"MaxObjectMembers","kind":"property","vis":"public","signature":"Public Property Get MaxObjectMembers() As Long","returnType":"Long","accessor":"Get","description":"Maximum object member count. Default `1000000`. `0` means unlimited."}, + {"name":"MaxObjectMembers","kind":"property","vis":"public","signature":"Public Property Let MaxObjectMembers(ByVal value As Long)","accessor":"Let"}, + {"name":"MaxStringLength","kind":"property","vis":"public","signature":"Public Property Get MaxStringLength() As Long","returnType":"Long","accessor":"Get","description":"Maximum string length in UTF-16 code units. Default 1 MiB. `0` means unlimited."}, + {"name":"MaxStringLength","kind":"property","vis":"public","signature":"Public Property Let MaxStringLength(ByVal value As Long)","accessor":"Let"}, + {"name":"Minified","kind":"property","vis":"public","signature":"Public Property Get Minified() As JsonOptions","returnType":"JsonOptions","accessor":"Get","description":"New options for compact output (`Indent = -1`)."}, + {"name":"New","kind":"sub","vis":"private","signature":"Private Sub New()","description":"Blocks external construction. Only this class's own factory members create instances."}, + {"name":"Newline","kind":"property","vis":"public","signature":"Public Property Get Newline() As String","returnType":"String","accessor":"Get","description":"Newline used when pretty-printing. Default `vbLf`."}, + {"name":"Newline","kind":"property","vis":"public","signature":"Public Property Let Newline(ByVal value As String)","accessor":"Let"}, + {"name":"Pretty","kind":"property","vis":"public","signature":"Public Property Get Pretty() As JsonOptions","returnType":"JsonOptions","accessor":"Get","description":"New options for pretty-printed output (`Indent = 4`)."}, + {"name":"Rfc","kind":"property","vis":"public","signature":"Public Property Get Rfc() As JsonOptions","returnType":"JsonOptions","accessor":"Get","description":"Starting point: RFC 8259 defaults on a fresh mutable instance."}, + {"name":"SortsKeys","kind":"property","vis":"public","signature":"Public Property Get SortsKeys() As Boolean","returnType":"Boolean","accessor":"Get","description":"When true, serialize object keys in UTF-16 code-unit order. Default `False`."}, + {"name":"SortsKeys","kind":"property","vis":"public","signature":"Public Property Let SortsKeys(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Syntax","kind":"property","vis":"public","signature":"Public Property Get Syntax() As eJsonSyntax","returnType":"eJsonSyntax","accessor":"Get","description":"JSON dialect. Default `jsRfc8259`."}, + {"name":"Syntax","kind":"property","vis":"public","signature":"Public Property Let Syntax(ByVal value As eJsonSyntax)","accessor":"Let"}, + {"name":"mDuplicateKeys","kind":"field","vis":"private","signature":"Private mDuplicateKeys As eJsonDuplicateKeys"}, + {"name":"mEncoding","kind":"field","vis":"private","signature":"Private mEncoding As eJsonEncodingKind"}, + {"name":"mEnsuresAscii","kind":"field","vis":"private","signature":"Private mEnsuresAscii As Boolean"}, + {"name":"mEscapesSolidus","kind":"field","vis":"private","signature":"Private mEscapesSolidus As Boolean"}, + {"name":"mIncludesBom","kind":"field","vis":"private","signature":"Private mIncludesBom As Boolean"}, + {"name":"mIndent","kind":"field","vis":"private","signature":"Private mIndent As Long"}, + {"name":"mIsFormatAssertion","kind":"field","vis":"private","signature":"Private mIsFormatAssertion As Boolean"}, + {"name":"mMaxArrayItems","kind":"field","vis":"private","signature":"Private mMaxArrayItems As Long"}, + {"name":"mMaxBytes","kind":"field","vis":"private","signature":"Private mMaxBytes As Long"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mMaxObjectMembers","kind":"field","vis":"private","signature":"Private mMaxObjectMembers As Long"}, + {"name":"mMaxStringLength","kind":"field","vis":"private","signature":"Private mMaxStringLength As Long"}, + {"name":"mNewline","kind":"field","vis":"private","signature":"Private mNewline As String"}, + {"name":"mSortsKeys","kind":"field","vis":"private","signature":"Private mSortsKeys As Boolean"}, + {"name":"mSyntax","kind":"field","vis":"private","signature":"Private mSyntax As eJsonSyntax"} + ] + }, + { + "name": "eJsonDuplicateKeys", + "kind": "enum", + "in": "JsonOptions", + "vis": "public", + "file": "JsonOptions.twin", + "signature": "Public Enum eJsonDuplicateKeys", + "description": "Duplicate object keys: error or last-wins.", + "values": [ + {"name":"jdkError"}, + {"name":"jdkLastWins"} + ] + }, + { + "name": "eJsonEncodingKind", + "kind": "enum", + "in": "JsonOptions", + "vis": "public", + "file": "JsonOptions.twin", + "signature": "Public Enum eJsonEncodingKind", + "description": "Byte-parse encoding. `jekAuto` uses BOM, otherwise UTF-8.", + "values": [ + {"name":"jekAuto"}, + {"name":"jekUtf8"}, + {"name":"jekUtf16Le"}, + {"name":"jekUtf16Be"} + ] + }, + { + "name": "eJsonSyntax", + "kind": "enum", + "in": "JsonOptions", + "vis": "public", + "file": "JsonOptions.twin", + "signature": "Public Enum eJsonSyntax", + "description": "Parse syntax: RFC 8259 or JSONC.", + "values": [ + {"name":"jsRfc8259"}, + {"name":"jsJsonc"} + ] + }, + { + "name": "JsonParser", + "kind": "module", + "vis": "public", + "file": "Internal/JsonParser.twin", + "signature": "Module JsonParser", + "description": "RFC 8259 parser with optional JSONC. Not part of the public API.", + "members": [ + {"name":"AppendEscape","kind":"function","vis":"private","signature":"Private Function AppendEscape(ByRef buf As JsonBuf.tBuf) As Boolean","returnType":"Boolean"}, + {"name":"DecimalFromJsonNumber","kind":"function","vis":"private","signature":"Private Function DecimalFromJsonNumber(ByVal raw As String, ByRef result As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ERR_DEPTH","kind":"const","vis":"private","signature":"Private Const ERR_DEPTH As String = \"DepthLimitExceeded\""}, + {"name":"ERR_DUP","kind":"const","vis":"private","signature":"Private Const ERR_DUP As String = \"DuplicateKey\""}, + {"name":"ERR_END","kind":"const","vis":"private","signature":"Private Const ERR_END As String = \"UnexpectedEnd\""}, + {"name":"ERR_ESCAPE","kind":"const","vis":"private","signature":"Private Const ERR_ESCAPE As String = \"InvalidEscape\""}, + {"name":"ERR_LIMIT","kind":"const","vis":"private","signature":"Private Const ERR_LIMIT As String = \"LimitExceeded\""}, + {"name":"ERR_NUMBER","kind":"const","vis":"private","signature":"Private Const ERR_NUMBER As String = \"InvalidNumber\""}, + {"name":"ERR_SYNTAX","kind":"const","vis":"private","signature":"Private Const ERR_SYNTAX As String = \"InvalidSyntax\""}, + {"name":"ERR_TRAILING","kind":"const","vis":"private","signature":"Private Const ERR_TRAILING As String = \"TrailingData\""}, + {"name":"Fail","kind":"sub","vis":"private","signature":"Private Sub Fail(ByVal code As String, ByVal message As String)"}, + {"name":"ParseArray","kind":"function","vis":"private","signature":"Private Function ParseArray(ByRef result As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseDocument","kind":"function","vis":"public","signature":"Public Function ParseDocument(ByVal text As String, ByVal options As JsonOptions) As Variant","returnType":"Variant","description":"Parses a JSON document to a root value."}, + {"name":"ParseLiteral","kind":"function","vis":"private","signature":"Private Function ParseLiteral(ByRef result As Variant, ByVal word As String, ByVal value As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"ParseNull","kind":"function","vis":"private","signature":"Private Function ParseNull(ByRef result As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ParseNumber","kind":"function","vis":"private","signature":"Private Function ParseNumber(ByRef result As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ParseObject","kind":"function","vis":"private","signature":"Private Function ParseObject(ByRef result As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseString","kind":"function","vis":"private","signature":"Private Function ParseString(ByRef text As String) As Boolean","returnType":"Boolean"}, + {"name":"ParseValue","kind":"function","vis":"private","signature":"Private Function ParseValue(ByRef result As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"Power10","kind":"function","vis":"private","signature":"Private Function Power10(ByVal n As Long) As Variant","returnType":"Variant"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mMaxItems","kind":"field","vis":"private","signature":"Private mMaxItems As Long"}, + {"name":"mMaxMembers","kind":"field","vis":"private","signature":"Private mMaxMembers As Long"}, + {"name":"mOpts","kind":"field","vis":"private","signature":"Private mOpts As JsonOptions"}, + {"name":"mSt","kind":"field","vis":"private","signature":"Private mSt As JsonLexer.tScan"} + ] + }, + { + "name": "JsonPatch", + "kind": "module", + "vis": "public", + "file": "Internal/JsonPatch.twin", + "signature": "Module JsonPatch", + "description": "JSON Patch, Merge Patch, and Diff. Not part of the public API.", + "members": [ + {"name":"AddOp","kind":"sub","vis":"private","signature":"Private Sub AddOp(ByVal ops As JsonArray, ByVal op As String, ByVal path As String, ByVal value As Variant, ByVal fromPtr As String)"}, + {"name":"ApplyMerge","kind":"function","vis":"public","signature":"Public Function ApplyMerge(ByVal root As Variant, ByVal patch As Variant) As Variant","returnType":"Variant","description":"Applies RFC 7396 Merge Patch to a copy of `root`."}, + {"name":"ApplyOp","kind":"function","vis":"private","signature":"Private Function ApplyOp(ByRef root As Variant, ByVal opObj As JsonObject, ByVal opPath As String) As Boolean","returnType":"Boolean"}, + {"name":"ApplyPatch","kind":"function","vis":"public","signature":"Public Function ApplyPatch(ByVal root As Variant, ByVal ops As Variant) As Variant","returnType":"Variant","description":"Applies RFC 6902 ops to a copy of `root`."}, + {"name":"DiffArrays","kind":"sub","vis":"private","signature":"Private Sub DiffArrays(ByVal ops As JsonArray, ByVal fromValue As Variant, ByVal toValue As Variant, ByVal path As String)"}, + {"name":"DiffAt","kind":"sub","vis":"private","signature":"Private Sub DiffAt(ByVal ops As JsonArray, ByVal fromValue As Variant, ByVal toValue As Variant, ByVal path As String)"}, + {"name":"DiffObjects","kind":"sub","vis":"private","signature":"Private Sub DiffObjects(ByVal ops As JsonArray, ByVal fromValue As Variant, ByVal toValue As Variant, ByVal path As String)"}, + {"name":"DiffValues","kind":"function","vis":"public","signature":"Public Function DiffValues(ByVal fromValue As Variant, ByVal toValue As Variant) As JsonArray","returnType":"JsonArray","description":"RFC 6902 ops such that Patch(from, Diff(from, to)) equals to."}, + {"name":"ERR_OP","kind":"const","vis":"private","signature":"Private Const ERR_OP As String = \"InvalidPatch\""}, + {"name":"ERR_PATCH","kind":"const","vis":"private","signature":"Private Const ERR_PATCH As String = \"PatchFailed\""}, + {"name":"ERR_TEST","kind":"const","vis":"private","signature":"Private Const ERR_TEST As String = \"PatchTestFailed\""}, + {"name":"IndexToken","kind":"function","vis":"private","signature":"Private Function IndexToken(ByVal tok As String, ByRef index As Long) As Boolean","returnType":"Boolean"}, + {"name":"MergeValue","kind":"function","vis":"private","signature":"Private Function MergeValue(ByVal target As Variant, ByVal patch As Variant) As Variant","returnType":"Variant"}, + {"name":"OpAdd","kind":"function","vis":"private","signature":"Private Function OpAdd(ByRef root As Variant, ByVal pointer As String, ByVal value As Variant, ByVal insertArray As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"OpRemove","kind":"function","vis":"private","signature":"Private Function OpRemove(ByRef root As Variant, ByVal pointer As String) As Boolean","returnType":"Boolean"}, + {"name":"OpsArray","kind":"function","vis":"private","signature":"Private Function OpsArray(ByVal ops As Variant) As JsonArray","returnType":"JsonArray"}, + {"name":"RequireFrom","kind":"function","vis":"private","signature":"Private Function RequireFrom(ByVal opObj As JsonObject, ByVal opPath As String, ByRef fromPtr As String) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "JsonPath", + "kind": "module", + "vis": "public", + "file": "Internal/JsonPath.twin", + "signature": "Module JsonPath", + "description": "JSONPath compile and eval (RFC 9535). Not part of the public API.", + "members": [ + {"name":"AddFilter","kind":"sub","vis":"private","signature":"Private Sub AddFilter( ByVal value As Variant, ByVal pointer As String, ByVal expr As JsonPathNode, ByVal root As Variant, ByVal out As Collection)"}, + {"name":"AddHit","kind":"sub","vis":"private","signature":"Private Sub AddHit(ByVal out As Collection, ByVal value As Variant, ByVal pointer As String)"}, + {"name":"AddIndexed","kind":"sub","vis":"private","signature":"Private Sub AddIndexed(ByVal value As Variant, ByVal pointer As String, ByVal index As Long, ByVal out As Collection)"}, + {"name":"AddNamed","kind":"sub","vis":"private","signature":"Private Sub AddNamed(ByVal value As Variant, ByVal pointer As String, ByVal name As String, ByVal out As Collection)"}, + {"name":"AddSlice","kind":"sub","vis":"private","signature":"Private Sub AddSlice(ByVal value As Variant, ByVal pointer As String, ByVal seg As JsonPathNode, ByVal out As Collection)"}, + {"name":"AddWild","kind":"sub","vis":"private","signature":"Private Sub AddWild(ByVal value As Variant, ByVal pointer As String, ByVal out As Collection)"}, + {"name":"Advance","kind":"sub","vis":"private","signature":"Private Sub Advance()"}, + {"name":"ApplyOne","kind":"sub","vis":"private","signature":"Private Sub ApplyOne( ByVal seg As JsonPathNode, ByVal value As Variant, ByVal pointer As String, ByVal root As Variant, ByVal out As Collection)"}, + {"name":"ApplySeg","kind":"function","vis":"private","signature":"Private Function ApplySeg(ByVal seg As JsonPathNode, ByVal input As Collection, ByVal root As Variant) As Collection","returnType":"Collection"}, + {"name":"CmpOrdered","kind":"function","vis":"private","signature":"Private Function CmpOrdered(ByVal a As Variant, ByVal b As Variant, ByVal op As String) As Boolean","returnType":"Boolean"}, + {"name":"CollectDescendants","kind":"sub","vis":"private","signature":"Private Sub CollectDescendants(ByVal value As Variant, ByVal pointer As String, ByVal out As Collection)"}, + {"name":"Compile","kind":"function","vis":"public","signature":"Public Function Compile(ByVal path As String) As JsonPathQuery","returnType":"JsonPathQuery","description":"Compiles `path`. Caches the last successful compile."}, + {"name":"CompileFresh","kind":"function","vis":"private","signature":"Private Function CompileFresh(ByVal path As String) As JsonPathQuery","returnType":"JsonPathQuery"}, + {"name":"ERR_DEPTH","kind":"const","vis":"private","signature":"Private Const ERR_DEPTH As String = \"DepthLimitExceeded\""}, + {"name":"ERR_PATH","kind":"const","vis":"private","signature":"Private Const ERR_PATH As String = \"InvalidPath\""}, + {"name":"EnterDepth","kind":"function","vis":"private","signature":"Private Function EnterDepth() As Boolean","returnType":"Boolean"}, + {"name":"EvalBool","kind":"function","vis":"private","signature":"Private Function EvalBool( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant) As Boolean","returnType":"Boolean"}, + {"name":"EvalCmp","kind":"function","vis":"private","signature":"Private Function EvalCmp( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant) As Boolean","returnType":"Boolean"}, + {"name":"EvalHits","kind":"function","vis":"public","signature":"Public Function EvalHits(ByVal head As JsonPathNode, ByVal root As Variant) As JsonHit()","returnType":"JsonHit()"}, + {"name":"EvalQuery","kind":"function","vis":"private","signature":"Private Function EvalQuery( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant) As Collection","returnType":"Collection"}, + {"name":"EvalValues","kind":"function","vis":"public","signature":"Public Function EvalValues(ByVal head As JsonPathNode, ByVal root As Variant) As Variant()","returnType":"Variant()"}, + {"name":"Fail","kind":"sub","vis":"private","signature":"Private Sub Fail(ByVal message As String)"}, + {"name":"FailCode","kind":"sub","vis":"private","signature":"Private Sub FailCode(ByVal code As String, ByVal message As String)"}, + {"name":"FuncBool","kind":"function","vis":"private","signature":"Private Function FuncBool( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant) As Boolean","returnType":"Boolean"}, + {"name":"FuncValue","kind":"function","vis":"private","signature":"Private Function FuncValue( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant, ByRef result As Variant) As Boolean","returnType":"Boolean"}, + {"name":"HitsFrom","kind":"function","vis":"private","signature":"Private Function HitsFrom(ByVal nodes As Collection) As JsonHit()","returnType":"JsonHit()"}, + {"name":"IdentitySeg","kind":"function","vis":"private","signature":"Private Function IdentitySeg() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"IsNameChar","kind":"function","vis":"private","signature":"Private Function IsNameChar(ByVal ch As Long) As Boolean","returnType":"Boolean"}, + {"name":"IsNameFirst","kind":"function","vis":"private","signature":"Private Function IsNameFirst(ByVal ch As Long) As Boolean","returnType":"Boolean"}, + {"name":"LastOf","kind":"function","vis":"private","signature":"Private Function LastOf(ByVal n As JsonPathNode) As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"LeaveDepth","kind":"sub","vis":"private","signature":"Private Sub LeaveDepth()"}, + {"name":"NormIndex","kind":"function","vis":"private","signature":"Private Function NormIndex(ByVal i As Long, ByVal n As Long, ByVal forward As Boolean) As Long","returnType":"Long"}, + {"name":"ParseAnd","kind":"function","vis":"private","signature":"Private Function ParseAnd() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseBracket","kind":"function","vis":"private","signature":"Private Function ParseBracket() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseCmpOp","kind":"function","vis":"private","signature":"Private Function ParseCmpOp() As String","returnType":"String"}, + {"name":"ParseComp","kind":"function","vis":"private","signature":"Private Function ParseComp() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseCompVal","kind":"function","vis":"private","signature":"Private Function ParseCompVal() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseDotOrBracketSelector","kind":"function","vis":"private","signature":"Private Function ParseDotOrBracketSelector() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseDotSelector","kind":"function","vis":"private","signature":"Private Function ParseDotSelector() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseIdentifier","kind":"function","vis":"private","signature":"Private Function ParseIdentifier() As String","returnType":"String"}, + {"name":"ParseIndexOrSlice","kind":"function","vis":"private","signature":"Private Function ParseIndexOrSlice() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseIntToken","kind":"function","vis":"private","signature":"Private Function ParseIntToken(ByRef n As Long) As Boolean","returnType":"Boolean"}, + {"name":"ParseJsonString","kind":"function","vis":"private","signature":"Private Function ParseJsonString() As String","returnType":"String"}, + {"name":"ParseLiteralWord","kind":"function","vis":"private","signature":"Private Function ParseLiteralWord() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseNot","kind":"function","vis":"private","signature":"Private Function ParseNot() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseOr","kind":"function","vis":"private","signature":"Private Function ParseOr() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseRelSegments","kind":"function","vis":"private","signature":"Private Function ParseRelSegments() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseSegment","kind":"function","vis":"private","signature":"Private Function ParseSegment() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"ParseSelector","kind":"function","vis":"private","signature":"Private Function ParseSelector() As JsonPathNode","returnType":"JsonPathNode"}, + {"name":"Peek","kind":"function","vis":"private","signature":"Private Function Peek() As Long","returnType":"Long"}, + {"name":"Peek2","kind":"function","vis":"private","signature":"Private Function Peek2() As Long","returnType":"Long"}, + {"name":"ScalarOf","kind":"function","vis":"private","signature":"Private Function ScalarOf( ByVal expr As JsonPathNode, ByVal cur As Variant, ByVal curPtr As String, ByVal root As Variant, ByRef result As Variant) As Boolean","returnType":"Boolean"}, + {"name":"SkipS","kind":"sub","vis":"private","signature":"Private Sub SkipS()"}, + {"name":"mCacheQ","kind":"field","vis":"private","signature":"Private mCacheQ As JsonPathQuery"}, + {"name":"mCacheSrc","kind":"field","vis":"private","signature":"Private mCacheSrc As String"}, + {"name":"mDepth","kind":"field","vis":"private","signature":"Private mDepth As Long"}, + {"name":"mLen","kind":"field","vis":"private","signature":"Private mLen As Long"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mOk","kind":"field","vis":"private","signature":"Private mOk As Boolean"}, + {"name":"mPos","kind":"field","vis":"private","signature":"Private mPos As Long"}, + {"name":"mSrc","kind":"field","vis":"private","signature":"Private mSrc As String"} + ] + }, + { + "name": "JsonPathNode", + "kind": "class", + "vis": "public", + "file": "Internal/JsonPathNode.twin", + "signature": "Public Class JsonPathNode", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "JSONPath AST node. Not part of the public API.", + "members": [ + {"name":"CmpOp","kind":"field","vis":"public","signature":"Public CmpOp As String"}, + {"name":"Index","kind":"field","vis":"public","signature":"Public Index As Long"}, + {"name":"Kids","kind":"field","vis":"public","signature":"Public Kids As Collection"}, + {"name":"Kind","kind":"field","vis":"public","signature":"Public Kind As eJsonPathKind"}, + {"name":"Left","kind":"field","vis":"public","signature":"Public Left As JsonPathNode"}, + {"name":"Name","kind":"field","vis":"public","signature":"Public Name As String"}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"NextSeg","kind":"field","vis":"public","signature":"Public NextSeg As JsonPathNode"}, + {"name":"Right","kind":"field","vis":"public","signature":"Public Right As JsonPathNode"}, + {"name":"SliceEnd","kind":"field","vis":"public","signature":"Public SliceEnd As Variant"}, + {"name":"SliceStart","kind":"field","vis":"public","signature":"Public SliceStart As Variant"}, + {"name":"SliceStep","kind":"field","vis":"public","signature":"Public SliceStep As Variant"} + ] + }, + { + "name": "eJsonPathKind", + "kind": "enum", + "in": "JsonPathNode", + "vis": "public", + "file": "Internal/JsonPathNode.twin", + "signature": "Public Enum eJsonPathKind", + "values": [ + {"name":"jpkName"}, + {"name":"jpkIndex"}, + {"name":"jpkWild"}, + {"name":"jpkSlice"}, + {"name":"jpkDescendant"}, + {"name":"jpkUnion"}, + {"name":"jpkFilter"}, + {"name":"jpkLit"}, + {"name":"jpkCurrent"}, + {"name":"jpkRoot"}, + {"name":"jpkCmp"}, + {"name":"jpkAnd"}, + {"name":"jpkOr"}, + {"name":"jpkNot"}, + {"name":"jpkFunc"}, + {"name":"jpkQuery"} + ] + }, + { + "name": "JsonPathQuery", + "kind": "class", + "vis": "public", + "file": "JsonPathQuery.twin", + "signature": "Public Class JsonPathQuery", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "Compiled JSONPath query (RFC 9535).\nCreated by `Json.CompilePath`. Same verbs as the `Json` facade.", + "members": [ + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach(ByVal source As String, ByVal head As JsonPathNode)"}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"Query","kind":"function","vis":"public","signature":"Public Function Query(ByVal root As Variant) As Variant()","returnType":"Variant()","description":"All matched values. A miss is not an error."}, + {"name":"QueryHits","kind":"function","vis":"public","signature":"Public Function QueryHits(ByVal root As Variant) As JsonHit()","returnType":"JsonHit()","description":"Matches with RFC 6901 Pointers. A miss is not an error."}, + {"name":"QueryOne","kind":"function","vis":"public","signature":"Public Function QueryOne(ByVal root As Variant) As Variant","returnType":"Variant","description":"First matched value, or `Empty` when none match."}, + {"name":"Source","kind":"property","vis":"public","signature":"Public Property Get Source() As String","returnType":"String","accessor":"Get","description":"Original JSONPath text."}, + {"name":"TryQueryOne","kind":"function","vis":"public","signature":"Public Function TryQueryOne(ByVal root As Variant, ByRef value As Variant) As Boolean","returnType":"Boolean","description":"Copies the first match into `value`. A miss is not an error."}, + {"name":"mHead","kind":"field","vis":"private","signature":"Private mHead As JsonPathNode"}, + {"name":"mSource","kind":"field","vis":"private","signature":"Private mSource As String"} + ] + }, + { + "name": "JsonPointer", + "kind": "module", + "vis": "public", + "file": "Internal/JsonPointer.twin", + "signature": "Module JsonPointer", + "description": "RFC 6901 JSON Pointer. Set and remove copy the root. Not part of the public API.", + "members": [ + {"name":"DeleteAt","kind":"function","vis":"private","signature":"Private Function DeleteAt( ByRef parent As Variant, ByRef tokens() As String, ByVal n As Long, ByVal offset As Long) As Boolean","returnType":"Boolean"}, + {"name":"ERR_MISS","kind":"const","vis":"private","signature":"Private Const ERR_MISS As String = \"PointerNotFound\""}, + {"name":"ERR_PTR","kind":"const","vis":"private","signature":"Private Const ERR_PTR As String = \"InvalidPointer\""}, + {"name":"EscapeToken","kind":"function","vis":"public","signature":"Public Function EscapeToken(ByVal token As String) As String","returnType":"String"}, + {"name":"ParseIndex","kind":"function","vis":"private","signature":"Private Function ParseIndex(ByVal tok As String, ByRef index As Long) As Boolean","returnType":"Boolean"}, + {"name":"PutAt","kind":"function","vis":"private","signature":"Private Function PutAt( ByRef parent As Variant, ByRef tokens() As String, ByVal n As Long, ByVal offset As Long, ByVal value As Variant) As Boolean","returnType":"Boolean"}, + {"name":"RemoveAt","kind":"function","vis":"public","signature":"Public Function RemoveAt(ByVal root As Variant, ByVal pointer As String) As Variant","returnType":"Variant","description":"Returns a copy of `root` with the value at `pointer` removed."}, + {"name":"SetAt","kind":"function","vis":"public","signature":"Public Function SetAt(ByVal root As Variant, ByVal pointer As String, ByVal value As Variant) As Variant","returnType":"Variant","description":"Returns a copy of `root` with `value` stored at `pointer`."}, + {"name":"SplitLast","kind":"function","vis":"public","signature":"Public Function SplitLast( ByVal pointer As String, ByRef parentPointer As String, ByRef token As String) As Boolean","returnType":"Boolean"}, + {"name":"SplitPointer","kind":"function","vis":"private","signature":"Private Function SplitPointer(ByVal pointer As String, ByRef tokens() As String, ByRef count As Long) As Boolean","returnType":"Boolean"}, + {"name":"StepInto","kind":"function","vis":"private","signature":"Private Function StepInto( ByRef cur As Variant, ByVal tok As String, ByVal path As String, ByVal missingIsError As Boolean) As Boolean","returnType":"Boolean"}, + {"name":"TryGet","kind":"function","vis":"public","signature":"Public Function TryGet( ByVal root As Variant, ByVal pointer As String, ByRef value As Variant, ByVal missingIsError As Boolean) As Boolean","returnType":"Boolean","description":"Walks `pointer` from `root`. Missing is an error only when `missingIsError` is true."}, + {"name":"UnescapeToken","kind":"function","vis":"public","signature":"Public Function UnescapeToken(ByVal token As String) As String","returnType":"String"}, + {"name":"ValidEscape","kind":"function","vis":"private","signature":"Private Function ValidEscape(ByVal raw As String) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "JsonReNode", + "kind": "class", + "vis": "public", + "file": "Internal/JsonReNode.twin", + "signature": "Public Class JsonReNode", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "I-Regexp syntax tree node. Not part of the public API.", + "members": [ + {"name":"AddProp","kind":"sub","vis":"friend","signature":"Friend Sub AddProp(ByVal prop As String, ByVal compl As Boolean)"}, + {"name":"AddRange","kind":"sub","vis":"friend","signature":"Friend Sub AddRange(ByVal lo As Long, ByVal hi As Long)"}, + {"name":"CopyRangesTo","kind":"sub","vis":"friend","signature":"Friend Sub CopyRangesTo(ByVal dest As JsonReNode)"}, + {"name":"Cp","kind":"field","vis":"public","signature":"Public Cp As Long"}, + {"name":"HasUnicodeProps","kind":"function","vis":"friend","signature":"Friend Function HasUnicodeProps() As Boolean","returnType":"Boolean"}, + {"name":"Kind","kind":"field","vis":"public","signature":"Public Kind As eJsonReKind"}, + {"name":"Left","kind":"field","vis":"public","signature":"Public Left As JsonReNode"}, + {"name":"MatchesCp","kind":"function","vis":"friend","signature":"Friend Function MatchesCp(ByVal cp As Long) As Boolean","returnType":"Boolean"}, + {"name":"MaxN","kind":"field","vis":"public","signature":"Public MaxN As Long"}, + {"name":"MinN","kind":"field","vis":"public","signature":"Public MinN As Long"}, + {"name":"Negate","kind":"field","vis":"public","signature":"Public Negate As Boolean"}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"Right","kind":"field","vis":"public","signature":"Public Right As JsonReNode"}, + {"name":"mPropCompl","kind":"field","vis":"private","signature":"Private mPropCompl() As Boolean"}, + {"name":"mPropCount","kind":"field","vis":"private","signature":"Private mPropCount As Long"}, + {"name":"mProps","kind":"field","vis":"private","signature":"Private mProps() As String"}, + {"name":"mRangeCount","kind":"field","vis":"private","signature":"Private mRangeCount As Long"}, + {"name":"mRangeHi","kind":"field","vis":"private","signature":"Private mRangeHi() As Long"}, + {"name":"mRangeLo","kind":"field","vis":"private","signature":"Private mRangeLo() As Long"} + ] + }, + { + "name": "eJsonReKind", + "kind": "enum", + "in": "JsonReNode", + "vis": "public", + "file": "Internal/JsonReNode.twin", + "signature": "Public Enum eJsonReKind", + "values": [ + {"name":"jrkAlt"}, + {"name":"jrkConcat"}, + {"name":"jrkRepeat"}, + {"name":"jrkChar"}, + {"name":"jrkClass"}, + {"name":"jrkDot"}, + {"name":"jrkEmpty"} + ] + }, + { + "name": "JsonSchema", + "kind": "class", + "vis": "public", + "file": "JsonSchema.twin", + "signature": "Public Class JsonSchema", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "Compiled JSON Schema (2020-12) with a local document catalogue.\nCreated by `Json.CompileSchema`. `AddDocument` registers `uri` as the only catalogue key.", + "members": [ + {"name":"AddDocument","kind":"sub","vis":"public","signature":"Public Sub AddDocument(ByVal uri As String, ByVal document As Variant)","description":"Registers `document` under exactly this `uri`. `$id` is not a second catalogue entry."}, + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach(ByVal schema As Variant, ByVal options As JsonOptions)"}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"Validate","kind":"function","vis":"public","signature":"Public Function Validate(ByVal instance As Variant) As JsonValidationResult","returnType":"JsonValidationResult","description":"Validates `instance`. Instance failures are on the result; schema runtime errors also set `JsonError`."}, + {"name":"mCatalog","kind":"field","vis":"private","signature":"Private mCatalog As Collection"}, + {"name":"mOptions","kind":"field","vis":"private","signature":"Private mOptions As JsonOptions"}, + {"name":"mSchema","kind":"field","vis":"private","signature":"Private mSchema As Variant"} + ] + }, + { + "name": "JsonSchemaEngine", + "kind": "module", + "vis": "public", + "file": "Internal/JsonSchemaEngine.twin", + "signature": "Module JsonSchemaEngine", + "description": "JSON Schema 2020-12 evaluator with a local catalogue. Not part of the public API.", + "members": [ + {"name":"AddErr","kind":"sub","vis":"private","signature":"Private Sub AddErr( ByVal result As JsonValidationResult, ByVal instPtr As String, ByVal schPtr As String, ByVal keyword As String, ByVal message As String)"}, + {"name":"ApplyApplicators","kind":"sub","vis":"private","signature":"Private Sub ApplyApplicators( ByVal sch As JsonObject, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"ApplyValidation","kind":"sub","vis":"private","signature":"Private Sub ApplyValidation( ByVal sch As JsonObject, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"CatalogGet","kind":"function","vis":"private","signature":"Private Function CatalogGet(ByVal uri As String, ByRef doc As Variant) As Boolean","returnType":"Boolean"}, + {"name":"CheckArray","kind":"sub","vis":"private","signature":"Private Sub CheckArray( ByVal sch As JsonObject, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"CheckEnum","kind":"sub","vis":"private","signature":"Private Sub CheckEnum(ByVal arr As JsonArray, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult)"}, + {"name":"CheckNumber","kind":"sub","vis":"private","signature":"Private Sub CheckNumber(ByVal sch As JsonObject, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult)"}, + {"name":"CheckObject","kind":"sub","vis":"private","signature":"Private Sub CheckObject( ByVal sch As JsonObject, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal baseUri As String)"}, + {"name":"CheckString","kind":"sub","vis":"private","signature":"Private Sub CheckString(ByVal sch As JsonObject, ByVal text As String, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult)"}, + {"name":"CheckType","kind":"sub","vis":"private","signature":"Private Sub CheckType(ByVal spec As Variant, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult)"}, + {"name":"CurrentResource","kind":"function","vis":"private","signature":"Private Function CurrentResource() As Variant","returnType":"Variant"}, + {"name":"ERR_DEPTH","kind":"const","vis":"private","signature":"Private Const ERR_DEPTH As String = \"DepthLimitExceeded\""}, + {"name":"ERR_REMOTE","kind":"const","vis":"private","signature":"Private Const ERR_REMOTE As String = \"RemoteRef\""}, + {"name":"ERR_SCHEMA","kind":"const","vis":"private","signature":"Private Const ERR_SCHEMA As String = \"InvalidSchema\""}, + {"name":"EvalAnyOrOne","kind":"sub","vis":"private","signature":"Private Sub EvalAnyOrOne( ByVal arr As JsonArray, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String, ByVal oneOf As Boolean)"}, + {"name":"EvalListAll","kind":"sub","vis":"private","signature":"Private Sub EvalListAll( ByVal arr As JsonArray, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"EvalSchema","kind":"sub","vis":"private","signature":"Private Sub EvalSchema( ByVal schema As Variant, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"EvalSchemaBody","kind":"sub","vis":"private","signature":"Private Sub EvalSchemaBody( ByVal schema As Variant, ByVal instance As Variant, ByVal instPtr As String, ByVal schPtr As String, ByVal result As JsonValidationResult, ByVal evalProps As Collection, ByVal evalItems As Collection, ByVal baseUri As String)"}, + {"name":"FindAnchor","kind":"function","vis":"private","signature":"Private Function FindAnchor(ByVal doc As Variant, ByVal name As String, ByRef target As Variant) As Boolean","returnType":"Boolean"}, + {"name":"FormatOk","kind":"function","vis":"private","signature":"Private Function FormatOk(ByVal fmt As String, ByVal text As String) As Boolean","returnType":"Boolean"}, + {"name":"IsIntegerValue","kind":"function","vis":"private","signature":"Private Function IsIntegerValue(ByVal instance As Variant) As Boolean","returnType":"Boolean"}, + {"name":"IsIpv4","kind":"function","vis":"private","signature":"Private Function IsIpv4(ByVal text As String) As Boolean","returnType":"Boolean"}, + {"name":"Mark","kind":"sub","vis":"private","signature":"Private Sub Mark(ByVal col As Collection, ByVal key As String)"}, + {"name":"NewProps","kind":"function","vis":"private","signature":"Private Function NewProps() As Collection","returnType":"Collection"}, + {"name":"PopResource","kind":"sub","vis":"private","signature":"Private Sub PopResource()"}, + {"name":"PushResource","kind":"sub","vis":"private","signature":"Private Sub PushResource(ByVal sch As Variant)"}, + {"name":"ResolveRef","kind":"function","vis":"private","signature":"Private Function ResolveRef( ByVal ref As String, ByVal baseUri As String, ByVal schPtr As String, ByRef target As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ResolveUri","kind":"function","vis":"private","signature":"Private Function ResolveUri(ByVal baseUri As String, ByVal ref As String) As String","returnType":"String"}, + {"name":"SearchPattern","kind":"function","vis":"private","signature":"Private Function SearchPattern(ByVal text As String, ByVal pattern As String) As Boolean","returnType":"Boolean"}, + {"name":"SplitRef","kind":"sub","vis":"private","signature":"Private Sub SplitRef(ByVal ref As String, ByRef uri As String, ByRef frag As String)"}, + {"name":"StripFrag","kind":"function","vis":"private","signature":"Private Function StripFrag(ByVal uri As String) As String","returnType":"String"}, + {"name":"TypeNameOk","kind":"function","vis":"private","signature":"Private Function TypeNameOk(ByVal name As String, ByVal it As eJsonType, ByVal instance As Variant) As Boolean","returnType":"Boolean"}, + {"name":"UnionKeys","kind":"sub","vis":"private","signature":"Private Sub UnionKeys(ByVal dest As Collection, ByVal src As Collection)"}, + {"name":"Validate","kind":"function","vis":"public","signature":"Public Function Validate( ByVal schema As Variant, ByVal instance As Variant, ByVal options As JsonOptions, ByVal catalog As Collection) As JsonValidationResult","returnType":"JsonValidationResult","description":"Validates `instance` against `schema` using `catalog`."}, + {"name":"mCatalog","kind":"field","vis":"private","signature":"Private mCatalog As Collection"}, + {"name":"mEvalDepth","kind":"field","vis":"private","signature":"Private mEvalDepth As Long"}, + {"name":"mFormatAssert","kind":"field","vis":"private","signature":"Private mFormatAssert As Boolean"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mOptions","kind":"field","vis":"private","signature":"Private mOptions As JsonOptions"}, + {"name":"mReCache","kind":"field","vis":"private","signature":"Private mReCache As Collection"}, + {"name":"mReFail","kind":"field","vis":"private","signature":"Private mReFail As Collection"}, + {"name":"mResources","kind":"field","vis":"private","signature":"Private mResources As Collection"}, + {"name":"mRootSchema","kind":"field","vis":"private","signature":"Private mRootSchema As Variant"}, + {"name":"mRuntimeFail","kind":"field","vis":"private","signature":"Private mRuntimeFail As Boolean"} + ] + }, + { + "name": "JsonSerializer", + "kind": "module", + "vis": "public", + "file": "Internal/JsonSerializer.twin", + "signature": "Module JsonSerializer", + "description": "JSON serializer. Cycles are an error. Not part of the public API.", + "members": [ + {"name":"BreakLine","kind":"sub","vis":"private","signature":"Private Sub BreakLine(ByRef buf As JsonBuf.tBuf, ByVal depth As Long)"}, + {"name":"ERR_CYCLE","kind":"const","vis":"private","signature":"Private Const ERR_CYCLE As String = \"CircularReference\""}, + {"name":"ERR_DEPTH","kind":"const","vis":"private","signature":"Private Const ERR_DEPTH As String = \"DepthLimitExceeded\""}, + {"name":"ERR_NUMBER","kind":"const","vis":"private","signature":"Private Const ERR_NUMBER As String = \"InvalidNumber\""}, + {"name":"ERR_TYPE","kind":"const","vis":"private","signature":"Private Const ERR_TYPE As String = \"UnsupportedType\""}, + {"name":"Hex4","kind":"function","vis":"private","signature":"Private Function Hex4(ByVal n As Long) As String","returnType":"String"}, + {"name":"IsOnStack","kind":"function","vis":"private","signature":"Private Function IsOnStack(ByVal p As LongPtr) As Boolean","returnType":"Boolean"}, + {"name":"Pop","kind":"sub","vis":"private","signature":"Private Sub Pop()"}, + {"name":"Push","kind":"sub","vis":"private","signature":"Private Sub Push(ByVal p As LongPtr)"}, + {"name":"SerializeValue","kind":"function","vis":"public","signature":"Public Function SerializeValue(ByVal value As Variant, ByVal options As JsonOptions) As String","returnType":"String","description":"Serializes `value` to JSON text."}, + {"name":"SiftDown","kind":"sub","vis":"private","signature":"Private Sub SiftDown(ByRef keys() As String, ByVal start As Long, ByVal n As Long)"}, + {"name":"SortKeys","kind":"sub","vis":"private","signature":"Private Sub SortKeys(ByRef keys() As String, ByVal n As Long)"}, + {"name":"WriteAny","kind":"function","vis":"private","signature":"Private Function WriteAny(ByRef buf As JsonBuf.tBuf, ByVal value As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"WriteArray","kind":"function","vis":"private","signature":"Private Function WriteArray(ByRef buf As JsonBuf.tBuf, ByVal value As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"WriteNumber","kind":"function","vis":"private","signature":"Private Function WriteNumber(ByRef buf As JsonBuf.tBuf, ByVal value As Variant) As Boolean","returnType":"Boolean"}, + {"name":"WriteObject","kind":"function","vis":"private","signature":"Private Function WriteObject(ByRef buf As JsonBuf.tBuf, ByVal value As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"WriteString","kind":"function","vis":"private","signature":"Private Function WriteString(ByRef buf As JsonBuf.tBuf, ByVal text As String) As Boolean","returnType":"Boolean"}, + {"name":"WriteVariantArray","kind":"function","vis":"private","signature":"Private Function WriteVariantArray(ByRef buf As JsonBuf.tBuf, ByVal value As Variant, ByVal depth As Long) As Boolean","returnType":"Boolean"}, + {"name":"mEnsureAscii","kind":"field","vis":"private","signature":"Private mEnsureAscii As Boolean"}, + {"name":"mEscapeSolidus","kind":"field","vis":"private","signature":"Private mEscapeSolidus As Boolean"}, + {"name":"mIndent","kind":"field","vis":"private","signature":"Private mIndent As Long"}, + {"name":"mMaxDepth","kind":"field","vis":"private","signature":"Private mMaxDepth As Long"}, + {"name":"mNewline","kind":"field","vis":"private","signature":"Private mNewline As String"}, + {"name":"mSortKeys","kind":"field","vis":"private","signature":"Private mSortKeys As Boolean"}, + {"name":"mStack","kind":"field","vis":"private","signature":"Private mStack() As LongPtr"}, + {"name":"mStackCount","kind":"field","vis":"private","signature":"Private mStackCount As Long"} + ] + }, + { + "name": "JsonText", + "kind": "module", + "vis": "public", + "file": "Internal/JsonText.twin", + "signature": "Module JsonText", + "description": "Line, column, snippet, and shared `\\\\u` helpers. Not part of the public API.", + "members": [ + {"name":"AddUtf16Unit","kind":"function","vis":"public","signature":"Public Function AddUtf16Unit( ByRef buf As JsonBuf.tBuf, ByVal cp As Long, ByVal src As String, ByRef pos As Long, ByVal srcLen As Long, ByRef message As String) As Boolean","returnType":"Boolean","description":"Appends `cp` to `buf`. A high surrogate must be followed by `\\\\u` and a low surrogate."}, + {"name":"FailParse","kind":"sub","vis":"public","signature":"Public Sub FailParse( ByVal code As String, ByVal message As String, ByVal source As String, ByVal position As Long, Optional ByVal path As String = \"\")"}, + {"name":"HexDigit","kind":"function","vis":"public","signature":"Public Function HexDigit(ByVal ch As Long, ByRef digit As Long) As Boolean","returnType":"Boolean"}, + {"name":"LineColumn","kind":"sub","vis":"public","signature":"Public Sub LineColumn(ByVal source As String, ByVal position As Long, ByRef line As Long, ByRef column As Long)"}, + {"name":"ParseHex4","kind":"function","vis":"public","signature":"Public Function ParseHex4( ByVal src As String, ByRef pos As Long, ByVal srcLen As Long, ByRef value As Long) As Boolean","returnType":"Boolean","description":"Reads four hex digits from `src` at `pos` into `value`."}, + {"name":"Snippet","kind":"function","vis":"public","signature":"Public Function Snippet(ByVal source As String, ByVal position As Long, Optional ByVal radius As Long = 24) As String","returnType":"String"} + ] + }, + { + "name": "JsonUnicodeCats", + "kind": "module", + "vis": "public", + "file": "Internal/JsonUnicodeCats.twin", + "signature": "Module JsonUnicodeCats", + "description": "Unicode General_Category lookup for I-Regexp \\p / \\P. Not part of the public API.", + "members": [ + {"name":"CAT_CC","kind":"const","vis":"public","signature":"Public Const CAT_CC As Long = 26"}, + {"name":"CAT_CF","kind":"const","vis":"public","signature":"Public Const CAT_CF As Long = 27"}, + {"name":"CAT_CN","kind":"const","vis":"public","signature":"Public Const CAT_CN As Long = 0"}, + {"name":"CAT_CO","kind":"const","vis":"public","signature":"Public Const CAT_CO As Long = 29"}, + {"name":"CAT_CS","kind":"const","vis":"public","signature":"Public Const CAT_CS As Long = 28"}, + {"name":"CAT_LL","kind":"const","vis":"public","signature":"Public Const CAT_LL As Long = 2"}, + {"name":"CAT_LM","kind":"const","vis":"public","signature":"Public Const CAT_LM As Long = 4"}, + {"name":"CAT_LO","kind":"const","vis":"public","signature":"Public Const CAT_LO As Long = 5"}, + {"name":"CAT_LT","kind":"const","vis":"public","signature":"Public Const CAT_LT As Long = 3"}, + {"name":"CAT_LU","kind":"const","vis":"public","signature":"Public Const CAT_LU As Long = 1"}, + {"name":"CAT_MC","kind":"const","vis":"public","signature":"Public Const CAT_MC As Long = 7"}, + {"name":"CAT_ME","kind":"const","vis":"public","signature":"Public Const CAT_ME As Long = 8"}, + {"name":"CAT_MN","kind":"const","vis":"public","signature":"Public Const CAT_MN As Long = 6"}, + {"name":"CAT_ND","kind":"const","vis":"public","signature":"Public Const CAT_ND As Long = 9"}, + {"name":"CAT_NL","kind":"const","vis":"public","signature":"Public Const CAT_NL As Long = 10"}, + {"name":"CAT_NO","kind":"const","vis":"public","signature":"Public Const CAT_NO As Long = 11"}, + {"name":"CAT_PC","kind":"const","vis":"public","signature":"Public Const CAT_PC As Long = 12"}, + {"name":"CAT_PD","kind":"const","vis":"public","signature":"Public Const CAT_PD As Long = 13"}, + {"name":"CAT_PE","kind":"const","vis":"public","signature":"Public Const CAT_PE As Long = 15"}, + {"name":"CAT_PF","kind":"const","vis":"public","signature":"Public Const CAT_PF As Long = 17"}, + {"name":"CAT_PI","kind":"const","vis":"public","signature":"Public Const CAT_PI As Long = 16"}, + {"name":"CAT_PO","kind":"const","vis":"public","signature":"Public Const CAT_PO As Long = 18"}, + {"name":"CAT_PS","kind":"const","vis":"public","signature":"Public Const CAT_PS As Long = 14"}, + {"name":"CAT_SC","kind":"const","vis":"public","signature":"Public Const CAT_SC As Long = 20"}, + {"name":"CAT_SK","kind":"const","vis":"public","signature":"Public Const CAT_SK As Long = 21"}, + {"name":"CAT_SM","kind":"const","vis":"public","signature":"Public Const CAT_SM As Long = 19"}, + {"name":"CAT_SO","kind":"const","vis":"public","signature":"Public Const CAT_SO As Long = 22"}, + {"name":"CAT_ZL","kind":"const","vis":"public","signature":"Public Const CAT_ZL As Long = 24"}, + {"name":"CAT_ZP","kind":"const","vis":"public","signature":"Public Const CAT_ZP As Long = 25"}, + {"name":"CAT_ZS","kind":"const","vis":"public","signature":"Public Const CAT_ZS As Long = 23"}, + {"name":"CategoryOf","kind":"function","vis":"public","signature":"Public Function CategoryOf(ByVal cp As Long) As Long","returnType":"Long","description":"Unicode General_Category of `cp`. Unassigned scalars are `CAT_CN`."}, + {"name":"Ensure","kind":"sub","vis":"private","signature":"Private Sub Ensure()"}, + {"name":"Hex2","kind":"function","vis":"private","signature":"Private Function Hex2(ByVal packed As String, ByVal p As Long) As Long","returnType":"Long"}, + {"name":"Hex8","kind":"function","vis":"private","signature":"Private Function Hex8(ByVal packed As String, ByVal p As Long) As Long","returnType":"Long"}, + {"name":"IsKnownProp","kind":"function","vis":"public","signature":"Public Function IsKnownProp(ByVal prop As String) As Boolean","returnType":"Boolean","description":"True when `prop` is a Unicode General_Category name (`L`, `Nd`, …)."}, + {"name":"MatchesProp","kind":"function","vis":"public","signature":"Public Function MatchesProp(ByVal cp As Long, ByVal prop As String) As Boolean","returnType":"Boolean","description":"True when `cp` matches I-Regexp property `prop` (`L`, `Nd`, …)."}, + {"name":"PackedData","kind":"function","vis":"private","signature":"Private Function PackedData() As String","returnType":"String"}, + {"name":"Unpack","kind":"sub","vis":"private","signature":"Private Sub Unpack(ByVal packed As String)"}, + {"name":"mCat","kind":"field","vis":"private","signature":"Private mCat() As Integer"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mEnd","kind":"field","vis":"private","signature":"Private mEnd() As Long"}, + {"name":"mReady","kind":"field","vis":"private","signature":"Private mReady As Boolean"}, + {"name":"mStart","kind":"field","vis":"private","signature":"Private mStart() As Long"} + ] + }, + { + "name": "JsonValidationError", + "kind": "class", + "vis": "public", + "file": "JsonValidationError.twin", + "signature": "Public Class JsonValidationError", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "One JSON Schema instance error: pointers, keyword, and message.", + "members": [ + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach( ByVal instancePointer As String, ByVal schemaPointer As String, ByVal keyword As String, ByVal message As String)"}, + {"name":"InstancePointer","kind":"property","vis":"public","signature":"Public Property Get InstancePointer() As String","returnType":"String","accessor":"Get","description":"RFC 6901 pointer in the instance. Empty string is the root."}, + {"name":"Keyword","kind":"property","vis":"public","signature":"Public Property Get Keyword() As String","returnType":"String","accessor":"Get","description":"Keyword that produced this error, such as `type` or `required`."}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Get Message() As String","returnType":"String","accessor":"Get","description":"Human-readable message."}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"SchemaPointer","kind":"property","vis":"public","signature":"Public Property Get SchemaPointer() As String","returnType":"String","accessor":"Get","description":"RFC 6901 pointer in the schema."}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String","description":"One-line summary: keyword, instance pointer, message."}, + {"name":"mInstancePointer","kind":"field","vis":"private","signature":"Private mInstancePointer As String"}, + {"name":"mKeyword","kind":"field","vis":"private","signature":"Private mKeyword As String"}, + {"name":"mMessage","kind":"field","vis":"private","signature":"Private mMessage As String"}, + {"name":"mSchemaPointer","kind":"field","vis":"private","signature":"Private mSchemaPointer As String"} + ] + }, + { + "name": "JsonValidationResult", + "kind": "class", + "vis": "public", + "file": "JsonValidationResult.twin", + "signature": "Public Class JsonValidationResult", + "attrs": [{"name":"COMCreatable","args":"False"}], + "description": "JSON Schema validation report. Instance errors live here, not on `JsonError`.", + "members": [ + {"name":"AddError","kind":"sub","vis":"friend","signature":"Friend Sub AddError(ByVal errItem As JsonValidationError)"}, + {"name":"ErrorCount","kind":"property","vis":"public","signature":"Public Property Get ErrorCount() As Long","returnType":"Long","accessor":"Get","description":"Number of instance errors."}, + {"name":"Errors","kind":"function","vis":"public","signature":"Public Function Errors() As JsonValidationError()","returnType":"JsonValidationError()","description":"All instance errors."}, + {"name":"IsValid","kind":"property","vis":"public","signature":"Public Property Get IsValid() As Boolean","returnType":"Boolean","accessor":"Get","description":"True when the instance satisfies the schema."}, + {"name":"New","kind":"sub","vis":"friend","signature":"Friend Sub New()"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString() As String","returnType":"String","description":"Report: `valid`, or one line per error."}, + {"name":"mErrors","kind":"field","vis":"private","signature":"Private mErrors As Collection"} + ] + }, + { + "name": "JsonValues", + "kind": "module", + "vis": "public", + "file": "Internal/JsonValues.twin", + "signature": "Module JsonValues", + "description": "Shared Variant helpers for `JsonObject` and `JsonArray`. Not part of the public API.", + "members": [ + {"name":"ArrayPath","kind":"function","vis":"public","signature":"Public Function ArrayPath(ByVal index As Long) As String","returnType":"String"}, + {"name":"ArraysEqual","kind":"function","vis":"private","signature":"Private Function ArraysEqual(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean"}, + {"name":"Assign","kind":"sub","vis":"public","signature":"Public Sub Assign(ByRef dest As Variant, ByVal src As Variant)","description":"Assigns `src` to `dest`, using `Set` when `src` is an object."}, + {"name":"ChildPointer","kind":"function","vis":"public","signature":"Public Function ChildPointer(ByVal pointer As String, ByVal token As String) As String","returnType":"String"}, + {"name":"Classify","kind":"function","vis":"public","signature":"Public Function Classify(ByVal value As Variant) As eJsonType","returnType":"eJsonType","description":"JSON type of a Variant. `Date`, `Empty`, and `Nothing` are `jtUnknown`."}, + {"name":"ClearSlot","kind":"sub","vis":"public","signature":"Public Sub ClearSlot(ByRef dest As Variant)"}, + {"name":"CloneOf","kind":"function","vis":"public","signature":"Public Function CloneOf(ByVal value As Variant) As Variant","returnType":"Variant","description":"Deep-clones `JsonObject` / `JsonArray`; other values are copied."}, + {"name":"ERR_INDEX_OUT_OF_RANGE","kind":"const","vis":"public","signature":"Public Const ERR_INDEX_OUT_OF_RANGE As String = \"IndexOutOfRange\""}, + {"name":"ERR_MISSING_KEY","kind":"const","vis":"public","signature":"Public Const ERR_MISSING_KEY As String = \"MissingKey\""}, + {"name":"ERR_WRONG_TYPE","kind":"const","vis":"public","signature":"Public Const ERR_WRONG_TYPE As String = \"WrongType\""}, + {"name":"EqualsOf","kind":"function","vis":"public","signature":"Public Function EqualsOf(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean","description":"Structural equality for JSON values, including nested objects and arrays."}, + {"name":"GetArr","kind":"function","vis":"public","signature":"Public Function GetArr( ByVal found As Boolean, ByVal value As Variant, ByVal path As String) As JsonArray","returnType":"JsonArray","description":"Reads an array slot. Missing or wrong type sets an error and returns `Nothing`."}, + {"name":"GetBool","kind":"function","vis":"public","signature":"Public Function GetBool( ByVal found As Boolean, ByVal value As Variant, ByVal default As Boolean, ByVal path As String) As Boolean","returnType":"Boolean","description":"Reads a boolean slot, or `default` when missing or JSON null."}, + {"name":"GetMap","kind":"function","vis":"public","signature":"Public Function GetMap( ByVal found As Boolean, ByVal value As Variant, ByVal path As String) As JsonObject","returnType":"JsonObject","description":"Reads an object slot. Missing or wrong type sets an error and returns `Nothing`."}, + {"name":"GetNum","kind":"function","vis":"public","signature":"Public Function GetNum( ByVal found As Boolean, ByVal value As Variant, ByVal default As Variant, ByVal path As String) As Variant","returnType":"Variant","description":"Reads a number slot as `Decimal`, or `default` when missing or JSON null."}, + {"name":"GetStr","kind":"function","vis":"public","signature":"Public Function GetStr( ByVal found As Boolean, ByVal value As Variant, ByVal default As String, ByVal path As String) As String","returnType":"String","description":"Reads a string slot, or `default` when missing or JSON null."}, + {"name":"NewEnumFromStrings","kind":"function","vis":"public","signature":"Public Function NewEnumFromStrings(ByRef keys() As String, ByVal count As Long) As stdole.IUnknown","returnType":"stdole.IUnknown"}, + {"name":"NewEnumFromVariants","kind":"function","vis":"public","signature":"Public Function NewEnumFromVariants(ByRef values() As Variant, ByVal count As Long) As stdole.IUnknown","returnType":"stdole.IUnknown"}, + {"name":"NumFallback","kind":"function","vis":"private","signature":"Private Function NumFallback(ByVal default As Variant) As Variant","returnType":"Variant"}, + {"name":"NumbersEqual","kind":"function","vis":"private","signature":"Private Function NumbersEqual(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ObjectPath","kind":"function","vis":"public","signature":"Public Function ObjectPath(ByVal key As String) As String","returnType":"String"}, + {"name":"ObjectsEqual","kind":"function","vis":"private","signature":"Private Function ObjectsEqual(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean"}, + {"name":"PointerToken","kind":"function","vis":"public","signature":"Public Function PointerToken(ByVal key As String) As String","returnType":"String"}, + {"name":"PutCloned","kind":"sub","vis":"public","signature":"Public Sub PutCloned(ByVal dest As JsonObject, ByVal key As String, ByVal value As Variant)"}, + {"name":"ScalarToString","kind":"function","vis":"private","signature":"Private Function ScalarToString(ByVal value As Variant) As String","returnType":"String"}, + {"name":"TryCopy","kind":"function","vis":"public","signature":"Public Function TryCopy( ByVal found As Boolean, ByVal value As Variant, ByRef outValue As Variant) As Boolean","returnType":"Boolean"}, + {"name":"TypeAt","kind":"function","vis":"public","signature":"Public Function TypeAt(ByVal found As Boolean, ByVal value As Variant) As eJsonType","returnType":"eJsonType"}, + {"name":"UnescapePointerToken","kind":"function","vis":"public","signature":"Public Function UnescapePointerToken(ByVal token As String) As String","returnType":"String"}, + {"name":"UnknownsEqual","kind":"function","vis":"private","signature":"Private Function UnknownsEqual(ByVal a As Variant, ByVal b As Variant) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "Tests", + "kind": "module", + "vis": "public", + "file": "Tests/Tests.twin", + "signature": "Module Tests", + "description": "Test run: start areas, check claims, print a uniform report. Copy between projects; edit `RunAll` / `RunDemo`.", + "members": [ + {"name":"AREA_WIDTH","kind":"const","vis":"private","signature":"Private Const AREA_WIDTH As Long = 14"}, + {"name":"AtSite","kind":"function","vis":"private","signature":"Private Function AtSite() As String","returnType":"String"}, + {"name":"BeginArea","kind":"sub","vis":"public","signature":"Public Sub BeginArea(ByVal area As String)"}, + {"name":"BeginRun","kind":"sub","vis":"public","signature":"Public Sub BeginRun()"}, + {"name":"CheckEmpty","kind":"sub","vis":"public","signature":"Public Sub CheckEmpty(ByVal name As String, ByVal isEmptyValue As Boolean)"}, + {"name":"CheckEqual","kind":"sub","vis":"public","signature":"Public Sub CheckEqual(ByVal name As String, ByVal expected As String, ByVal actual As String)"}, + {"name":"CheckEqualLong","kind":"sub","vis":"public","signature":"Public Sub CheckEqualLong(ByVal name As String, ByVal expected As Long, ByVal actual As Long)"}, + {"name":"CheckError","kind":"sub","vis":"public","signature":"Public Sub CheckError( ByVal name As String, ByVal expectedCode As String, ByVal hasError As Boolean, ByVal actualCode As String, Optional ByVal expectedOrigin As String = \"\", Optional ByVal actualOrigin As String = \"\", Optional ByVal errText As String = \"\")"}, + {"name":"CheckFalse","kind":"sub","vis":"public","signature":"Public Sub CheckFalse(ByVal name As String, ByVal condition As Boolean)"}, + {"name":"CheckNoError","kind":"sub","vis":"public","signature":"Public Sub CheckNoError(ByVal name As String, ByVal hasError As Boolean, Optional ByVal errText As String = \"\")"}, + {"name":"CheckNotNothing","kind":"sub","vis":"public","signature":"Public Sub CheckNotNothing(ByVal name As String, ByVal isNothingRef As Boolean)"}, + {"name":"CheckNothing","kind":"sub","vis":"public","signature":"Public Sub CheckNothing(ByVal name As String, ByVal isNothingRef As Boolean)"}, + {"name":"CheckNull","kind":"sub","vis":"public","signature":"Public Sub CheckNull(ByVal name As String, ByVal isNullValue As Boolean)"}, + {"name":"CheckTrue","kind":"sub","vis":"public","signature":"Public Sub CheckTrue(ByVal name As String, ByVal condition As Boolean)"}, + {"name":"ElapsedSince","kind":"function","vis":"private","signature":"Private Function ElapsedSince(ByVal t0 As Double) As Double","returnType":"Double"}, + {"name":"EndRun","kind":"sub","vis":"public","signature":"Public Sub EndRun()"}, + {"name":"Fail","kind":"sub","vis":"public","signature":"Public Sub Fail( ByVal name As String, Optional ByVal expected As String = \"\", Optional ByVal actual As String = \"\", Optional ByVal errText As String = \"\")"}, + {"name":"FailCount","kind":"function","vis":"public","signature":"Public Function FailCount() As Long","returnType":"Long"}, + {"name":"FlushArea","kind":"sub","vis":"private","signature":"Private Sub FlushArea()"}, + {"name":"HereModule","kind":"sub","vis":"public","signature":"Public Sub HereModule(ByVal componentName As String)"}, + {"name":"HereProc","kind":"sub","vis":"public","signature":"Public Sub HereProc(ByVal procedureName As String)"}, + {"name":"Ok","kind":"sub","vis":"public","signature":"Public Sub Ok(ByVal name As String)"}, + {"name":"PadLeft","kind":"function","vis":"private","signature":"Private Function PadLeft(ByVal text As String, ByVal width As Long) As String","returnType":"String"}, + {"name":"PadRight","kind":"function","vis":"private","signature":"Private Function PadRight(ByVal text As String, ByVal width As Long) As String","returnType":"String"}, + {"name":"PrintAt","kind":"sub","vis":"private","signature":"Private Sub PrintAt()"}, + {"name":"RULE_WIDTH","kind":"const","vis":"private","signature":"Private Const RULE_WIDTH As Long = 60"}, + {"name":"RuleLine","kind":"function","vis":"private","signature":"Private Function RuleLine(ByVal mark As String, ByVal title As String) As String","returnType":"String"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"RunDemo","kind":"sub","vis":"public","signature":"Public Sub RunDemo()"}, + {"name":"SetContinueOnFail","kind":"sub","vis":"public","signature":"Public Sub SetContinueOnFail(ByVal value As Boolean)"}, + {"name":"SetQuiet","kind":"sub","vis":"public","signature":"Public Sub SetQuiet(ByVal value As Boolean)"}, + {"name":"Skip","kind":"sub","vis":"public","signature":"Public Sub Skip(ByVal name As String, ByVal reason As String)"}, + {"name":"mAreaFail","kind":"field","vis":"private","signature":"Private mAreaFail As Long"}, + {"name":"mAreaName","kind":"field","vis":"private","signature":"Private mAreaName As String"}, + {"name":"mAreaPass","kind":"field","vis":"private","signature":"Private mAreaPass As Long"}, + {"name":"mAreaSkip","kind":"field","vis":"private","signature":"Private mAreaSkip As Long"}, + {"name":"mComponent","kind":"field","vis":"private","signature":"Private mComponent As String"}, + {"name":"mContinueOnFail","kind":"field","vis":"private","signature":"Private mContinueOnFail As Boolean"}, + {"name":"mFail","kind":"field","vis":"private","signature":"Private mFail As Long"}, + {"name":"mPass","kind":"field","vis":"private","signature":"Private mPass As Long"}, + {"name":"mProc","kind":"field","vis":"private","signature":"Private mProc As String"}, + {"name":"mQuiet","kind":"field","vis":"private","signature":"Private mQuiet As Boolean"}, + {"name":"mSkip","kind":"field","vis":"private","signature":"Private mSkip As Long"}, + {"name":"mStats","kind":"field","vis":"private","signature":"Private mStats As String"}, + {"name":"mT0","kind":"field","vis":"private","signature":"Private mT0 As Double"} + ] + }, + { + "name": "TestsCore", + "kind": "module", + "vis": "public", + "file": "Tests/TestsCore.twin", + "signature": "Module TestsCore", + "description": "Core checks: types, Map/Arr, clone, equals, merge, options, and last-error.", + "members": [ + {"name":"Array_Mutate_And_Bounds","kind":"sub","vis":"public","signature":"Public Sub Array_Mutate_And_Bounds()"}, + {"name":"Clone_Equals_Merge","kind":"sub","vis":"public","signature":"Public Sub Clone_Equals_Merge()"}, + {"name":"Error_Snapshot_And_Origin","kind":"sub","vis":"public","signature":"Public Sub Error_Snapshot_And_Origin()"}, + {"name":"Map_And_Arr","kind":"sub","vis":"public","signature":"Public Sub Map_And_Arr()"}, + {"name":"Object_Read_Write_Remove","kind":"sub","vis":"public","signature":"Public Sub Object_Read_Write_Remove()"}, + {"name":"Options_Factory","kind":"sub","vis":"public","signature":"Public Sub Options_Factory()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Types_And_Classify","kind":"sub","vis":"public","signature":"Public Sub Types_And_Classify()"} + ] + }, + { + "name": "TestsDates", + "kind": "module", + "vis": "public", + "file": "Tests/TestsDates.twin", + "signature": "Module TestsDates", + "description": "ISO-8601 helper checks: local wall clock, Z/offset, format without zone.", + "members": [ + {"name":"Format_Has_No_Zone","kind":"sub","vis":"public","signature":"Public Sub Format_Has_No_Zone()"}, + {"name":"Invalid_And_Parse_Stays_String","kind":"sub","vis":"public","signature":"Public Sub Invalid_And_Parse_Stays_String()"}, + {"name":"Local_Date_And_DateTime","kind":"sub","vis":"public","signature":"Public Sub Local_Date_And_DateTime()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Zone_Converts_To_Local","kind":"sub","vis":"public","signature":"Public Sub Zone_Converts_To_Local()"} + ] + }, + { + "name": "TestsExtNst", + "kind": "module", + "vis": "public", + "file": "Tests/TestsExtNst.twin", + "signature": "Module TestsExtNst", + "description": "External nst/JSONTestSuite parse accept/reject cases.", + "members": [ + {"name":"Nst_Accept_Reject","kind":"sub","vis":"public","signature":"Public Sub Nst_Accept_Reject()"}, + {"name":"PayloadOf","kind":"function","vis":"private","signature":"Private Function PayloadOf(ByVal hex As String) As Variant","returnType":"Variant"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"SkipReason","kind":"function","vis":"private","signature":"Private Function SkipReason(ByVal fileName As String) As String","returnType":"String"} + ] + }, + { + "name": "TestsExtNstData", + "kind": "module", + "vis": "public", + "file": "Tests/TestsExtNstData.twin", + "signature": "Module TestsExtNstData", + "description": "Embedded nst/JSONTestSuite parse cases. Generated data; not part of the public API.", + "members": [ + {"name":"CaseCount","kind":"function","vis":"public","signature":"Public Function CaseCount() As Long","returnType":"Long"}, + {"name":"CaseHex","kind":"function","vis":"public","signature":"Public Function CaseHex(ByVal index As Long) As String","returnType":"String"}, + {"name":"CaseKind","kind":"function","vis":"public","signature":"Public Function CaseKind(ByVal index As Long) As String","returnType":"String"}, + {"name":"CaseName","kind":"function","vis":"public","signature":"Public Function CaseName(ByVal index As Long) As String","returnType":"String"}, + {"name":"Ensure","kind":"sub","vis":"private","signature":"Private Sub Ensure()"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mHex","kind":"field","vis":"private","signature":"Private mHex() As String"}, + {"name":"mKinds","kind":"field","vis":"private","signature":"Private mKinds() As String"}, + {"name":"mNames","kind":"field","vis":"private","signature":"Private mNames() As String"}, + {"name":"mReady","kind":"field","vis":"private","signature":"Private mReady As Boolean"} + ] + }, + { + "name": "TestsIo", + "kind": "module", + "vis": "public", + "file": "Tests/TestsIo.twin", + "signature": "Module TestsIo", + "description": "File and NDJSON checks: ParseFile, WriteFile, ParseLines, SerializeLines.", + "members": [ + {"name":"DeleteTemp","kind":"sub","vis":"private","signature":"Private Sub DeleteTemp(ByVal path As String)"}, + {"name":"File_Errors","kind":"sub","vis":"public","signature":"Public Sub File_Errors()"}, + {"name":"File_Roundtrip","kind":"sub","vis":"public","signature":"Public Sub File_Roundtrip()"}, + {"name":"Lines_File","kind":"sub","vis":"public","signature":"Public Sub Lines_File()"}, + {"name":"Lines_Text","kind":"sub","vis":"public","signature":"Public Sub Lines_Text()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"TempPath","kind":"function","vis":"private","signature":"Private Function TempPath(ByVal fileName As String) As String","returnType":"String"} + ] + }, + { + "name": "TestsParse", + "kind": "module", + "vis": "public", + "file": "Tests/TestsParse.twin", + "signature": "Module TestsParse", + "description": "Parse checks: RFC 8259, JSONC, encodings, limits, and TryParse.", + "members": [ + {"name":"Bytes_And_Bom","kind":"sub","vis":"public","signature":"Public Sub Bytes_And_Bom()"}, + {"name":"Containers","kind":"sub","vis":"public","signature":"Public Sub Containers()"}, + {"name":"Duplicate_Keys","kind":"sub","vis":"public","signature":"Public Sub Duplicate_Keys()"}, + {"name":"Jsonc_Comments_And_Commas","kind":"sub","vis":"public","signature":"Public Sub Jsonc_Comments_And_Commas()"}, + {"name":"Limits","kind":"sub","vis":"public","signature":"Public Sub Limits()"}, + {"name":"Rfc_Rejects","kind":"sub","vis":"public","signature":"Public Sub Rfc_Rejects()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Scalars_And_Whitespace","kind":"sub","vis":"public","signature":"Public Sub Scalars_And_Whitespace()"}, + {"name":"Strings_And_Escapes","kind":"sub","vis":"public","signature":"Public Sub Strings_And_Escapes()"}, + {"name":"TryParse_Slot","kind":"sub","vis":"public","signature":"Public Sub TryParse_Slot()"}, + {"name":"Utf16Be","kind":"function","vis":"private","signature":"Private Function Utf16Be(ByVal text As String, ByVal withBom As Boolean) As Byte()","returnType":"Byte()"}, + {"name":"Utf16Le","kind":"function","vis":"private","signature":"Private Function Utf16Le(ByVal text As String, ByVal withBom As Boolean) As Byte()","returnType":"Byte()"} + ] + }, + { + "name": "TestsPatch", + "kind": "module", + "vis": "public", + "file": "Tests/TestsPatch.twin", + "signature": "Module TestsPatch", + "description": "Patch, Merge Patch, and Diff checks (RFC 6902 / RFC 7396).", + "members": [ + {"name":"Diff_Roundtrip","kind":"sub","vis":"public","signature":"Public Sub Diff_Roundtrip()"}, + {"name":"MergePatch_Deletes","kind":"sub","vis":"public","signature":"Public Sub MergePatch_Deletes()"}, + {"name":"Merge_Vs_MergePatch","kind":"sub","vis":"public","signature":"Public Sub Merge_Vs_MergePatch()"}, + {"name":"Patch_Ops","kind":"sub","vis":"public","signature":"Public Sub Patch_Ops()"}, + {"name":"Patch_Test_And_Errors","kind":"sub","vis":"public","signature":"Public Sub Patch_Test_And_Errors()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"} + ] + }, + { + "name": "TestsPath", + "kind": "module", + "vis": "public", + "file": "Tests/TestsPath.twin", + "signature": "Module TestsPath", + "description": "JSONPath checks: selectors, filters, functions, hits, and compile.", + "members": [ + {"name":"CheckHits","kind":"sub","vis":"private","signature":"Private Sub CheckHits(ByVal name As String, ByVal root As Variant, ByVal path As String, ByVal expectedJson As String)"}, + {"name":"Compile_And_Hits","kind":"sub","vis":"public","signature":"Public Sub Compile_And_Hits()"}, + {"name":"Escapes_And_Depth","kind":"sub","vis":"public","signature":"Public Sub Escapes_And_Depth()"}, + {"name":"Filter_And_Functions","kind":"sub","vis":"public","signature":"Public Sub Filter_And_Functions()"}, + {"name":"HitCount","kind":"function","vis":"private","signature":"Private Function HitCount(ByRef a() As Variant) As Long","returnType":"Long"}, + {"name":"Miss_Vs_Invalid","kind":"sub","vis":"public","signature":"Public Sub Miss_Vs_Invalid()"}, + {"name":"Name_Wildcard_Index","kind":"sub","vis":"public","signature":"Public Sub Name_Wildcard_Index()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Slice_Union_Descendant","kind":"sub","vis":"public","signature":"Public Sub Slice_Union_Descendant()"} + ] + }, + { + "name": "TestsPointer", + "kind": "module", + "vis": "public", + "file": "Tests/TestsPointer.twin", + "signature": "Module TestsPointer", + "description": "JSON Pointer checks: get, exists, copy-on-write set/remove, and escapes.", + "members": [ + {"name":"Escape_And_Special_Keys","kind":"sub","vis":"public","signature":"Public Sub Escape_And_Special_Keys()"}, + {"name":"Get_Exists_TryGet","kind":"sub","vis":"public","signature":"Public Sub Get_Exists_TryGet()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Set_Remove_Copy","kind":"sub","vis":"public","signature":"Public Sub Set_Remove_Copy()"}, + {"name":"Syntax_And_Dash","kind":"sub","vis":"public","signature":"Public Sub Syntax_And_Dash()"} + ] + }, + { + "name": "TestsRegexp", + "kind": "module", + "vis": "public", + "file": "Tests/TestsRegexp.twin", + "signature": "Module TestsRegexp", + "description": "I-Regexp checks: valid, match, search, Unicode, and invalid patterns.", + "members": [ + {"name":"Classes_And_Dot","kind":"sub","vis":"public","signature":"Public Sub Classes_And_Dot()"}, + {"name":"Invalid_Is_Not_Error","kind":"sub","vis":"public","signature":"Public Sub Invalid_Is_Not_Error()"}, + {"name":"Quantifiers_And_Alt","kind":"sub","vis":"public","signature":"Public Sub Quantifiers_And_Alt()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Unicode_Properties","kind":"sub","vis":"public","signature":"Public Sub Unicode_Properties()"}, + {"name":"Valid_Match_Search","kind":"sub","vis":"public","signature":"Public Sub Valid_Match_Search()"} + ] + }, + { + "name": "TestsSchema", + "kind": "module", + "vis": "public", + "file": "Tests/TestsSchema.twin", + "signature": "Module TestsSchema", + "description": "JSON Schema 2020-12 checks: keywords, catalogue, unevaluated*, and format.", + "members": [ + {"name":"Applicators_And_If","kind":"sub","vis":"public","signature":"Public Sub Applicators_And_If()"}, + {"name":"Format_Annotation","kind":"sub","vis":"public","signature":"Public Sub Format_Annotation()"}, + {"name":"Integer_And_Ref_Limits","kind":"sub","vis":"public","signature":"Public Sub Integer_And_Ref_Limits()"}, + {"name":"Number_String_Array_Object","kind":"sub","vis":"public","signature":"Public Sub Number_String_Array_Object()"}, + {"name":"Ref_And_Catalogue","kind":"sub","vis":"public","signature":"Public Sub Ref_And_Catalogue()"}, + {"name":"Report_Pointers","kind":"sub","vis":"public","signature":"Public Sub Report_Pointers()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Type_Required_Enum","kind":"sub","vis":"public","signature":"Public Sub Type_Required_Enum()"}, + {"name":"Unevaluated","kind":"sub","vis":"public","signature":"Public Sub Unevaluated()"} + ] + }, + { + "name": "TestsSerialize", + "kind": "module", + "vis": "public", + "file": "Tests/TestsSerialize.twin", + "signature": "Module TestsSerialize", + "description": "Serialize checks: pretty/minify, escapes, cycles, limits, and UTF-8.", + "members": [ + {"name":"Cycle_And_Unsupported","kind":"sub","vis":"public","signature":"Public Sub Cycle_And_Unsupported()"}, + {"name":"Depth_And_Numbers","kind":"sub","vis":"public","signature":"Public Sub Depth_And_Numbers()"}, + {"name":"Pretty_Minify_And_Options","kind":"sub","vis":"public","signature":"Public Sub Pretty_Minify_And_Options()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"}, + {"name":"Scalars_And_Roundtrip","kind":"sub","vis":"public","signature":"Public Sub Scalars_And_Roundtrip()"}, + {"name":"Utf8_Bytes","kind":"sub","vis":"public","signature":"Public Sub Utf8_Bytes()"} + ] + }, + { + "name": "TestsSmoke", + "kind": "module", + "vis": "public", + "file": "Tests/TestsSmoke.twin", + "signature": "Module TestsSmoke", + "description": "Smoke checks: 1 MiB string and depth-200 parse/serialize.", + "members": [ + {"name":"Deep_Array","kind":"sub","vis":"public","signature":"Public Sub Deep_Array()"}, + {"name":"Large_String","kind":"sub","vis":"public","signature":"Public Sub Large_String()"}, + {"name":"RunAll","kind":"sub","vis":"public","signature":"Public Sub RunAll()"} + ] + }, + { + "name": "TestsTour", + "kind": "module", + "vis": "public", + "file": "Tests/TestsTour.twin", + "signature": "Module TestsTour", + "description": "JSONC demo of one order through parse, pointer, path, patch, schema, dates, and I-Regexp.", + "members": [ + {"name":"ADDRESS_URI","kind":"const","vis":"private","signature":"Private Const ADDRESS_URI As String = \"https://example.invalid/address\""}, + {"name":"AddressSchema","kind":"function","vis":"private","signature":"Private Function AddressSchema() As JsonObject","returnType":"JsonObject"}, + {"name":"CREATED_AT","kind":"const","vis":"private","signature":"Private Const CREATED_AT As String = \"2026-08-19T09:15:00\""}, + {"name":"DeleteTemp","kind":"sub","vis":"private","signature":"Private Sub DeleteTemp(ByVal filePath As String)"}, + {"name":"HitCount","kind":"function","vis":"private","signature":"Private Function HitCount(ByRef a() As Variant) As Long","returnType":"Long"}, + {"name":"HitsAsArr","kind":"function","vis":"private","signature":"Private Function HitsAsArr(ByRef a() As Variant) As JsonArray","returnType":"JsonArray"}, + {"name":"JNum","kind":"function","vis":"private","signature":"Private Function JNum(ByVal jsonNumber As String) As Variant","returnType":"Variant"}, + {"name":"ORDER_ID","kind":"const","vis":"private","signature":"Private Const ORDER_ID As String = \"ORD-2026-0819\""}, + {"name":"OrderSchema","kind":"function","vis":"private","signature":"Private Function OrderSchema() As JsonObject","returnType":"JsonObject"}, + {"name":"RunDemo","kind":"sub","vis":"public","signature":"Public Sub RunDemo()"}, + {"name":"SKU_PATTERN","kind":"const","vis":"private","signature":"Private Const SKU_PATTERN As String = \"TS-[0-9]{4}\""}, + {"name":"SampleAddress","kind":"function","vis":"private","signature":"Private Function SampleAddress( ByVal street As String, ByVal zip As String, ByVal city As String, ByVal country As String) As JsonObject","returnType":"JsonObject"}, + {"name":"SampleCustomer","kind":"function","vis":"private","signature":"Private Function SampleCustomer() As JsonObject","returnType":"JsonObject"}, + {"name":"SampleEvent","kind":"function","vis":"private","signature":"Private Function SampleEvent(ByVal at As String, ByVal eventName As String, ByVal byWhom As String) As JsonObject","returnType":"JsonObject"}, + {"name":"SampleEvents","kind":"function","vis":"private","signature":"Private Function SampleEvents() As JsonArray","returnType":"JsonArray"}, + {"name":"SampleHistory","kind":"function","vis":"private","signature":"Private Function SampleHistory() As JsonArray","returnType":"JsonArray"}, + {"name":"SampleItem","kind":"function","vis":"private","signature":"Private Function SampleItem( ByVal sku As String, ByVal name As String, ByVal qty As Long, ByVal price As Variant, ByVal taxRate As Variant, ByVal tags As JsonArray) As JsonObject","returnType":"JsonObject"}, + {"name":"SampleItems","kind":"function","vis":"private","signature":"Private Function SampleItems() As JsonArray","returnType":"JsonArray"}, + {"name":"SampleOrder","kind":"function","vis":"private","signature":"Private Function SampleOrder() As JsonObject","returnType":"JsonObject"}, + {"name":"ShowJsonc","kind":"sub","vis":"private","signature":"Private Sub ShowJsonc(ByVal value As Variant, ParamArray code() As Variant)"}, + {"name":"TempPath","kind":"function","vis":"private","signature":"Private Function TempPath(ByVal fileName As String) As String","returnType":"String"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-tsJson/Settings b/test/example-projects/contributed-tsJson/Settings new file mode 100644 index 000000000..7cfe65c4a --- /dev/null +++ b/test/example-projects/contributed-tsJson/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{7A57DD01-85F1-4234-9448-EADD83B01813}", + "licence": "MIT", + "name": "[IMPORTED] Parse, query, patch, validate - one JSON model.", + "path32": "/Packages/tsJson", + "path64": "/Packages/tsJson", + "symbolId": "tsJson", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "15" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} From 9b9d9544ee69b815118d9d1ce60e544eea3a267c Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 12 Oct 2026 18:42:29 +0200 Subject: [PATCH 15/15] TBMANLIB_ConsoleCore and TBMANLIB_ConsoleComponents: two contributed sections of 5 and 9 pages, listed under Experimental Packages: the components cannot take a console object of the published ConsoleCore, whose key input is wrong; a NOTE on each says the samples harness cannot yet capture a console (owner) --- WIP.md | 2 +- builder/page-baseline.json | 2 +- .../TBMANLIB_ConsoleComponents/Helpers.md | 521 +++++++++ .../cConsoleLayout.md | 390 +++++++ .../cConsolePanel.md | 219 ++++ .../cConsoleTheme.md | 129 +++ .../TBMANLIB_ConsoleComponents/cDataGrid.md | 256 +++++ .../cProgressBar.md | 197 ++++ .../TBMANLIB_ConsoleComponents/cSparkline.md | 240 ++++ .../TBMANLIB_ConsoleComponents/cVBar.md | 196 ++++ .../TBMANLIB_ConsoleComponents/index.md | 103 ++ .../Contributed/TBMANLIB_ConsoleCore/Types.md | 276 +++++ .../TBMANLIB_ConsoleCore/cConsole.md | 1002 +++++++++++++++++ .../TBMANLIB_ConsoleCore/cConsoleBuffer.md | 482 ++++++++ .../Contributed/TBMANLIB_ConsoleCore/index.md | 86 ++ .../TBMANLIB_ConsoleCore/mConsoleStyle.md | 436 +++++++ docs/Reference/Experimental.md | 2 + docs/_book.yml | 14 + .../TBMANLIB_ConsoleComponents/api.json | 320 ++++++ .../contributed/TBMANLIB_ConsoleCore/api.json | 865 ++++++++++++++ .../Settings | 87 ++ .../contributed-TBMANLIB_ConsoleCore/Settings | 74 ++ 22 files changed, 5897 insertions(+), 2 deletions(-) create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleTheme.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cProgressBar.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cSparkline.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cVBar.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleCore/Types.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsole.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsoleBuffer.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleCore/index.md create mode 100644 docs/Reference/Contributed/TBMANLIB_ConsoleCore/mConsoleStyle.md create mode 100644 indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json create mode 100644 indexer/snapshots/contributed/TBMANLIB_ConsoleCore/api.json create mode 100644 test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings create mode 100644 test/example-projects/contributed-TBMANLIB_ConsoleCore/Settings diff --git a/WIP.md b/WIP.md index 98f36dc2f..33aaaf4d6 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-four **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_Diagnostic, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerPackage, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modPackUnpack` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib, BigNumberLib and WinReg do not compile on BETA 1005 (each documented from its source alone, its samples compile-only), xCalendar has defects that can hang a program, TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a private copy of it), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary or make a text report and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text. TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 41d51c0a7..24f7e74e2 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1229, + "pages": 1243, "staticFiles": 933 } diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md new file mode 100644 index 000000000..592df2303 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md @@ -0,0 +1,521 @@ +--- +title: TBMANLIB_ConsoleComponents Helpers +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/Helpers +has_toc: false +--- + +# cColorHelper, cLayoutHelper +{: .no_toc } + +Two small classes that compute values and draw nothing: **cColorHelper** chooses a colour for a number, and **cLayoutHelper** works out positions and rectangles in the console window. + +**cColorHelper** needs no console and no state. **cLayoutHelper** is made with a [**cConsole**](../TBMANLIB_ConsoleCore/cConsole), and reads its window size each time a member is called. Both are created with `New`. + +```tb check_build check_run +Dim helper As New cColorHelper + +Debug.Print helper.HeatmapColor(10, 0, 100) +Debug.Print helper.HeatmapColor(60, 0, 100) +Debug.Print helper.HeatmapColor(95, 0, 100) +Debug.Print helper.CPUUsageColor(72) +' Output: +' 10 +' 14 +' 12 +' 14 +``` + +The four numbers are **ConsoleColor** values: 10 is **Green**, 14 is **Yellow** and 12 is **Red**. + +* TOC +{:toc} + +## cColorHelper class + +Every colour parameter and result is a [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), and every function is called on an object, as in `helper.DimColor(...)`. The numbers that give the choice of a colour are in each entry; they are fixed in the code. In the syntax lines of this section, *object* is a **cColorHelper**. + +Known problems in version 0.0.0.0: [**BrightenColor**](#brightencolor) returns 16 for **DarkGray**, [**DimColor**](#dimcolor) returns **DarkGray** for every dark colour, [**IntensityColor**](#intensitycolor) ignores its *baseColor*, and [**GradientColor**](#gradientcolor) chooses among three colours and does not blend. + +### BrightenColor +{: .no_toc } + +Returns a brighter version of a colour. + +Syntax: *object*.**BrightenColor** ( *color* ) **As ConsoleColor** + +*color* +: *required* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor). + +For a colour from **Black** (0) up to **DarkGray** (8) the result is the colour plus 8: **Black** gives **DarkGray**, **DarkBlue** gives **Blue**, **Gray** gives **White**. **DarkGray** is in that range as well, and 8 + 8 is 16, which is not a **ConsoleColor**. The result 16 has bit 4 set; used as a text colour it adds the blue background bit to the attribute of the cell. For every colour from **Blue** (9) up, the result is **White**. + +```tb check_build check_run +Dim helper As New cColorHelper + +Debug.Print helper.BrightenColor(ConsoleColor.DarkBlue) +Debug.Print helper.BrightenColor(ConsoleColor.DarkGray) +Debug.Print helper.BrightenColor(ConsoleColor.Red) +' Output: +' 9 +' 16 +' 15 +``` + +### CPUUsageColor +{: .no_toc } + +Returns a colour for a processor load. + +Syntax: *object*.**CPUUsageColor** ( *usage* ) **As ConsoleColor** + +*usage* +: *required* A **Double**, a percentage. + +**Green** below 50, **Yellow** from 50 up to, but not including, 80, and **Red** from 80. The value is not limited to 0 to 100. + +### CycleColor +{: .no_toc } + +Returns the colour for an item number, going round a list of colours. + +Syntax: *object*.**CycleColor** ( *index* , *colors* ) **As ConsoleColor** + +*index* +: *required* A **Long**, the number of the item. A negative number counts back from the end of the list. + +*colors* +: *required* An array of **ConsoleColor**, passed by reference, that starts at index 0. + +Returns `colors(index Mod count)`, with a negative remainder raised by *count*. Returns **Gray** for an array with no element. The code takes the result of `Mod` as an index, so an array whose lower bound is not 0 gives a wrong element or error 9, *Subscript out of range*. + +### DimColor +{: .no_toc } + +Returns a dimmer version of a colour. + +Syntax: *object*.**DimColor** ( *color* ) **As ConsoleColor** + +*color* +: *required* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor). + +For a colour from **Blue** (9) to **White** (15) the result is the colour less 8, the dark colour of the same hue: **Red** gives **DarkRed**, **White** gives **Gray**. For every colour from **Black** to **DarkGray**, the result is **DarkGray**; so the dimmer version of **Black** is the brighter **DarkGray**. + +### DiskUsageColor +{: .no_toc } + +Returns a colour for the use of a disk. + +Syntax: *object*.**DiskUsageColor** ( *usage* ) **As ConsoleColor** + +*usage* +: *required* A **Double**, a percentage. + +**Green** below 80, **Yellow** from 80 up to, but not including, 95, and **Red** from 95. + +### GetContrastColor +{: .no_toc } + +Returns a text colour that can be read on a background. + +Syntax: *object*.**GetContrastColor** ( *bgColor* ) **As ConsoleColor** + +*bgColor* +: *required* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), the background. + +Returns **White** for a background from **Black** (0) to **DarkGray** (8), and **Black** for **Blue** (9) and up. **DarkYellow** and **Gray** are in the first group, so white text is chosen on a grey background. + +### GradientColor +{: .no_toc } + +Returns one of three colours for a value in a range. + +Syntax: *object*.**GradientColor** ( *value* , *min* , *max* , *color1* , *color2* , *color3* ) **As ConsoleColor** + +*value* +: *required* A **Double**. + +*min*, *max* +: *required* **Double** values, the ends of the range. When *max* is not greater than *min*, the position is taken as the middle of the range. + +*color1*, *color2*, *color3* +: *required* **ConsoleColor** values. + +The position of *value* in the range, limited to 0 to 1, gives *color1* below 0.25, *color2* from 0.25 up to, but not including, 0.75, and *color3* from 0.75. The comment of the source speaks of a smooth transition; the console has 16 colours and the function does not blend. + +### HeatmapColor +{: .no_toc } + +Returns a colour for a value in a range: one colour for the low part, one for the middle and one for the high part. + +Syntax: *object*.**HeatmapColor** ( *value* , *min* , *max* [ , *lowColor* [ , *midColor* [ , *highColor* ] ] ] ) **As ConsoleColor** + +*value* +: *required* A **Double**. + +*min*, *max* +: *required* **Double** values, the ends of the range. When *max* is not greater than *min*, the position is taken as the middle of the range, which gives *midColor*. + +*lowColor* +: *optional* A **ConsoleColor**, for positions below 0.5. The default is **Green**. + +*midColor* +: *optional* A **ConsoleColor**, for positions from 0.5 up to, but not including, 0.8. The default is **Yellow**. + +*highColor* +: *optional* A **ConsoleColor**, for positions from 0.8. The default is **Red**. + +The position is `(value - min) / (max - min)`, limited to 0 to 1. + +### IntensityColor +{: .no_toc } + +Returns one of eight colours for a value in a range. + +Syntax: *object*.**IntensityColor** ( *value* , *min* , *max* [ , *baseColor* ] ) **As ConsoleColor** + +*value* +: *required* A **Double**. + +*min*, *max* +: *required* **Double** values, the ends of the range. When *max* is not greater than *min*, the position is taken as the middle of the range. + +*baseColor* +: *optional* A **ConsoleColor**. The default is **Gray**. It is not used: the function never reaches the line that returns it. + +The level is `CLng(position * 7)`, from 0 to 7, which rounds half to even. The result is **Black**, **DarkBlue**, **DarkGreen**, **DarkCyan**, **DarkRed**, **DarkMagenta**, **DarkYellow** or **DarkGray** for the levels 0 to 7, in that order. This is the order of the numbers of the colours, not an order of brightness, so a rising value does not give a steadily lighter colour. + +### IsDarkColor +{: .no_toc } + +Tells whether a colour is dark. + +Syntax: *object*.**IsDarkColor** ( *color* ) **As Boolean** + +*color* +: *required* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor). + +Returns **True** for **Black** (0) up to **DarkGray** (8), which includes **Gray**. + +### MemoryUsageColor +{: .no_toc } + +Returns a colour for the use of memory. + +Syntax: *object*.**MemoryUsageColor** ( *usage* ) **As ConsoleColor** + +*usage* +: *required* A **Double**, a percentage. + +**Green** below 70, **Yellow** from 70 up to, but not including, 90, and **Red** from 90. + +## cLayoutHelper class + +Computes positions and [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect) rectangles from the size of the window of a console. A rectangle has inclusive edges, and columns and rows are counted from 0. Every member except [**SpacedPositions**](#spacedpositions) reads [**Width**](../TBMANLIB_ConsoleCore/cConsole#width) and [**Height**](../TBMANLIB_ConsoleCore/cConsole#height) of the console when it is called, so the results follow a window that the user resizes; without a console they are computed for 80 columns by 25 rows. In the syntax lines of this section, *object* is a **cLayoutHelper**. + +The members that give several rectangles or numbers return them in a **Variant** that holds an array, with indexes from 0, in the order the entry names. The rectangles of one result do not overlap and have no gap between them, except where the entry says so. A division by a count of 0 raises error 11, *Division by zero*. + +```tb check_build +Dim con As New cConsole +Dim layout As cLayoutHelper = New cLayoutHelper(con) +Dim columns As Variant + +Debug.Print layout.CenterX(20) & " " & layout.CenterY(5) +Debug.Print layout.PercentX(25) & " " & layout.PercentY(50) +columns = layout.SpacedPositions(3, 10, 2, 5) +Debug.Print columns(0) & " " & columns(1) & " " & columns(2) +' Output: +' 30 10 +' 20 12 +' 5 17 29 +``` + +The output is that of a console of 80 columns by 25 rows, which is also the size that a program without a console gets. + +> [!NOTE] +> The members that give a **ConsoleRect** or a **ConsolePosition** can be called, but a program that references the published package TBMANLIB_ConsoleCore cannot read the result: the compiler refuses the members of the value and its assignment to a variable of the type. See [The two packages](./#the-two-packages). The members that give a **Long** or an array of **Long** are not affected. + +### New +{: .no_toc } + +Creates a layout helper for a console. + +Syntax: **New cLayoutHelper** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole). + +Keeps the console. The console does not have to be in full-screen mode. + +### Center +{: .no_toc } + +Returns the position at which an area of a given size is centred in the window. + +Syntax: *object*.**Center** ( *width* , *height* ) **As ConsolePosition** + +*width*, *height* +: *required* **Long** values, the size of the area in columns and rows. + +Returns a [**ConsolePosition**](../TBMANLIB_ConsoleCore/Types#consoleposition) with **Col** `(Width - width) \ 2` and **Row** `(Height - height) \ 2`. The values are negative when the area is larger than the window. + +### CenterRect +{: .no_toc } + +Returns the rectangle of an area of a given size, centred in the window. + +Syntax: *object*.**CenterRect** ( *width* , *height* ) **As ConsoleRect** + +*width*, *height* +: *required* **Long** values. + +The rectangle starts at the position that [**Center**](#center) returns and is *width* columns wide and *height* rows high. [**CenterRect**](../TBMANLIB_ConsoleCore/cConsole#centerrect) of **cConsole** gives the same rectangle. + +### CenterX +{: .no_toc } + +Returns the column at which an area of a given width is centred. + +Syntax: *object*.**CenterX** ( *width* ) **As Long** + +*width* +: *required* A **Long**. + +Returns `(Width - width) \ 2`. + +### CenterY +{: .no_toc } + +Returns the row at which an area of a given height is centred. + +Syntax: *object*.**CenterY** ( *height* ) **As Long** + +*height* +: *required* A **Long**. + +Returns `(Height - height) \ 2`. + +### ClampRect +{: .no_toc } + +Cuts a rectangle to the window. + +Syntax: *object*.**ClampRect** ( *rect* ) **As ConsoleRect** + +*rect* +: *required* A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect). + +Raises **Left** and **Top** to 0 when they are negative, and lowers **Right** and **Bottom** to the last column and the last row of the window. A rectangle that lies wholly outside the window is cut to an invalid one; [**IsValidRect**](#isvalidrect) tells. + +### EvenColumns +{: .no_toc } + +Divides the window into columns of the same width. + +Syntax: *object*.**EvenColumns** ( *count* , *margin* ) **As Variant** + +*count* +: *required* A **Long**, the number of columns. + +*margin* +: *required* A **Long**, the number of cells to leave free on every side of the window. + +Returns *count* rectangles, from left to right. Each is `(Width - 2 * margin) \ count` columns wide and reaches from the row *margin* to the row `Height - 1 - margin`. The last one is widened to the right margin, so it takes the remainder. + +### EvenRows +{: .no_toc } + +Divides the window into rows of the same height. + +Syntax: *object*.**EvenRows** ( *count* , *margin* ) **As Variant** + +*count* +: *required* A **Long**, the number of rows. + +*margin* +: *required* A **Long**, the number of cells to leave free on every side of the window. + +Returns *count* rectangles, from top to bottom, each as wide as the window less the margins and `(Height - 2 * margin) \ count` rows high. The last one is heightened to the bottom margin. + +### GetRectHeight +{: .no_toc } + +Returns the height of a rectangle. + +Syntax: *object*.**GetRectHeight** ( *rect* ) **As Long** + +*rect* +: *required* A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect). + +Returns `Bottom - Top + 1`, as [**ConsoleRect_Height**](../TBMANLIB_ConsoleCore/Types#consolerect_height) does. + +### GetRectWidth +{: .no_toc } + +Returns the width of a rectangle. + +Syntax: *object*.**GetRectWidth** ( *rect* ) **As Long** + +*rect* +: *required* A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect). + +Returns `Right - Left + 1`, as [**ConsoleRect_Width**](../TBMANLIB_ConsoleCore/Types#consolerect_width) does. + +### Grid +{: .no_toc } + +Divides the window into a grid of cells. + +Syntax: *object*.**Grid** ( *cols* , *rows* ) **As Variant** + +*cols*, *rows* +: *required* **Long** values, the number of columns and rows. + +Returns `cols * rows` rectangles, row by row from the top left. Each cell is `Width \ cols` columns wide and `Height \ rows` rows high, with no gap between cells. The remainder of the division is not covered: the right and bottom edges of the grid can end short of the window. [**CreateGrid**](cConsoleLayout#creategrid) of **cConsoleLayout** leaves a gap and gives the remainder to the last column and row. + +### HeaderFooter +{: .no_toc } + +Divides the window into a header, a body and a footer. + +Syntax: *object*.**HeaderFooter** ( *headerHeight* , *footerHeight* ) **As Variant** + +*headerHeight*, *footerHeight* +: *required* **Long** values, the number of rows of the header and of the footer. + +Returns three rectangles as wide as the window: the header (the first *headerHeight* rows), the body (the rows between) and the footer (the last *footerHeight* rows), in that order. + +### IsValidRect +{: .no_toc } + +Tells whether a rectangle has a positive size. + +Syntax: *object*.**IsValidRect** ( *rect* ) **As Boolean** + +*rect* +: *required* A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect). + +Returns **True** when `Left <= Right` and `Top <= Bottom`. + +### Percent +{: .no_toc } + +Returns the position at given percentages of the window. + +Syntax: *object*.**Percent** ( *percentX* , *percentY* ) **As ConsolePosition** + +*percentX*, *percentY* +: *required* **Double** values, the share of the width and of the height. They are not limited to 0 to 100. + +Returns a [**ConsolePosition**](../TBMANLIB_ConsoleCore/Types#consoleposition) with **Col** `CLng(Width * percentX / 100)` and **Row** `CLng(Height * percentY / 100)`. + +### PercentRect +{: .no_toc } + +Returns the rectangle at given percentages of the window. + +Syntax: *object*.**PercentRect** ( *leftPercent* , *topPercent* , *widthPercent* , *heightPercent* ) **As ConsoleRect** + +*leftPercent*, *topPercent*, *widthPercent*, *heightPercent* +: *required* **Double** values. The first two give the upper left corner, and the last two the size, all as percentages of the width and the height. + +**Left** is `CLng(Width * leftPercent / 100)`, and **Right** is `CLng(Width * (leftPercent + widthPercent) / 100) - 1`, and **Top** and **Bottom** are computed in the same way from the height. + +### PercentX +{: .no_toc } + +Returns the column at a percentage of the width. + +Syntax: *object*.**PercentX** ( *percent* ) **As Long** + +*percent* +: *required* A **Double**. Unlike [**PercentX**](../TBMANLIB_ConsoleCore/cConsole#percentx) of **cConsole**, this one does not limit it to 0 to 100. + +Returns `CLng(Width * percent / 100)`. + +### PercentY +{: .no_toc } + +Returns the row at a percentage of the height. + +Syntax: *object*.**PercentY** ( *percent* ) **As Long** + +*percent* +: *required* A **Double**, not limited to 0 to 100. + +Returns `CLng(Height * percent / 100)`. + +### Sidebar +{: .no_toc } + +Divides the window into a sidebar on the left and a content area. + +Syntax: *object*.**Sidebar** ( *sidebarWidth* ) **As Variant** + +*sidebarWidth* +: *required* A **Long**, the number of columns of the sidebar. + +Returns two rectangles as high as the window: the sidebar (the first *sidebarWidth* columns) and the content area (the rest), in that order. + +### SpacedPositions +{: .no_toc } + +Returns the columns of items of equal width placed in a row. + +Syntax: *object*.**SpacedPositions** ( *count* , *itemWidth* , *spacing* , *startX* ) **As Variant** + +*count* +: *required* A **Long**, the number of items. + +*itemWidth* +: *required* A **Long**, the width of an item in columns. + +*spacing* +: *required* A **Long**, the number of blank columns between two items. + +*startX* +: *required* A **Long**, the column of the first item. + +Returns *count* **Long** values: the column of item *i* is `startX + i * (itemWidth + spacing)`. This member does not use the size of the window. + +### SplitHorizontal +{: .no_toc } + +Divides the window into a top part and a bottom part. + +Syntax: *object*.**SplitHorizontal** ( *topPercent* ) **As Variant** + +*topPercent* +: *required* A **Double**, the share of the height for the top part, in percent. + +Returns two rectangles as wide as the window: the top one, from row 0 to row `PercentY(topPercent) - 1`, and the bottom one, from row `PercentY(topPercent)` to the last row. The line between the parts is horizontal. The member of the same name in [**cConsoleLayout**](cConsoleLayout#splithorizontal) divides the width instead. + +### SplitVertical +{: .no_toc } + +Divides the window into a left part and a right part. + +Syntax: *object*.**SplitVertical** ( *leftPercent* ) **As Variant** + +*leftPercent* +: *required* A **Double**, the share of the width for the left part, in percent. + +Returns two rectangles as high as the window: the left one, from column 0 to column `PercentX(leftPercent) - 1`, and the right one, from column `PercentX(leftPercent)` to the last column. The line between the parts is vertical. The member of the same name in [**cConsoleLayout**](cConsoleLayout#splitvertical) divides the height instead. + +### WithMargins +{: .no_toc } + +Returns the window less a margin on each side. + +Syntax: *object*.**WithMargins** ( *left* , *top* , *right* , *bottom* ) **As ConsoleRect** + +*left*, *top*, *right*, *bottom* +: *required* **Long** values, the number of cells to leave free at each side. + +Returns the rectangle from column *left* and row *top* to column `Width - 1 - right` and row `Height - 1 - bottom`. + +## See Also + +- [cConsoleLayout](cConsoleLayout) class -- panels placed by name +- [cConsolePanel](cConsolePanel) class +- [cConsole](../TBMANLIB_ConsoleCore/cConsole) class -- **Width**, **Height** and its own **Center** and **Percent** members +- [Types](../TBMANLIB_ConsoleCore/Types) of ConsoleCore -- **ConsoleRect**, **ConsolePosition** and **ConsoleColor** diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md new file mode 100644 index 000000000..7dedb8784 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md @@ -0,0 +1,390 @@ +--- +title: cConsoleLayout +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout +has_toc: false +--- + +# cConsoleLayout class +{: .no_toc } + +Keeps a set of named [**cConsolePanel**](cConsolePanel) objects, creates them in common arrangements (a split, a grid, a sidebar, a header and footer), and draws their borders. + +A **cConsoleLayout** is made with a [**cConsole**](../TBMANLIB_ConsoleCore/cConsole). It does not place content: it works out the rectangles of panels and draws the borders and titles of all of them in one call. The program writes the content of each panel into the area that the panel's [**ContentRect**](cConsolePanel#contentrect) returns. + +```tb check_build +Dim con As New cConsole +Dim layout As cConsoleLayout = New cConsoleLayout(con) +Dim p As cConsolePanel + +layout.AddPanel "root", 0, 0, 40, 12, "Monitor" +layout.SplitHorizontal "root", 0.25, "list", "detail" + +Set p = layout.GetPanel("list") +Debug.Print p.ContentWidth() & " " & p.ContentHeight() +Set p = layout.GetPanel("detail") +Debug.Print p.ContentWidth() & " " & p.ContentHeight() +Debug.Print layout.PanelCount +' Output: +' 5 4 +' 22 4 +' 3 +``` + +The root panel covers columns 0 to 39 and rows 0 to 11, so its content area is columns 2 to 37 and rows 2 to 9. **list** takes columns 2 to 10 and **detail** columns 12 to 37, both in rows 2 to 9; the sample prints the size of the content area inside the border and padding of each. A program that references only the published package TBMANLIB_ConsoleCore cannot read the **Rect** of a panel; see [The two packages](./#the-two-packages). + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cConsoleLayout**. Columns and rows are counted from 0 in the buffer. + +## Panels, names and the root panel + +Each panel has a name, which is unique in the layout and case-sensitive. [**GetPanel**](#getpanel) finds a panel by it. The layout keeps the panels in the order of creation, which is the order in which [**Render**](#render) draws them, so a later panel is drawn over an earlier one. + +The first panel that is added becomes the *root panel*, by any member that adds one: [**AddPanel**](#addpanel), [**AddPanelByRect**](#addpanelbyrect), or one of the arrangements. When the root panel is removed, the first remaining panel becomes the root. The root panel decides the area in which the arrangements work: [**CreateGrid**](#creategrid), [**CreateSidebar**](#createsidebar) and [**CreateHeaderFooter**](#createheaderfooter) use the content area of the root panel, and the whole console, as it was when the layout was made or last resized, when the layout has no panel. A layout that has no panel yet and starts with an arrangement, such as [**CreateHeaderFooter**](#createheaderfooter), makes the first panel of that arrangement the root panel, and the arrangements that follow then work inside its content area. A program that wants the arrangements to work inside one panel adds that panel first. + +[**SplitHorizontal**](#splithorizontal) and [**SplitVertical**](#splitvertical) work inside the content area of the panel they name. The panels they make are new entries of the layout; the panel that was split stays where it is, and the new panels lie over its inside. + +The arrangements make panels with the default style of [**cConsolePanel**](cConsolePanel): a single border and a padding of 1. A panel needs at least 5 columns and 5 rows to have a content area of one cell. + +The members that make several panels return them in a **Variant** that holds an array of **cConsolePanel**, in a fixed order. A program takes the panels out one at a time, or finds them with **GetPanel**: + +```tb check_build +Dim con As New cConsole +Dim layout As cConsoleLayout = New cConsoleLayout(con) +Dim parts As Variant +Dim leftPanel As cConsolePanel +Dim rightPanel As cConsolePanel + +layout.AddPanel "root", 0, 0, 40, 12 +parts = layout.SplitHorizontal("root", 0.5, "left", "right") +Set leftPanel = parts(0) +Set rightPanel = parts(1) +leftPanel.Title = "Files" +rightPanel.BorderStyle = PanelBorderStyle.Double +``` + +## Constructor + +### New +{: .no_toc } + +Creates a layout for a console. + +Syntax: **New cConsoleLayout** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole). + +Keeps the console, reads its [**Width**](../TBMANLIB_ConsoleCore/cConsole#width) and [**Height**](../TBMANLIB_ConsoleCore/cConsole#height) as the size of the layout, and creates the empty list and the `Scripting.Dictionary` object in which the panels are kept by name. The console does not have to be in full-screen mode yet: the layout reads the console's [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) each time it draws, not when it is made. + +## Properties + +### PanelCount +{: .no_toc } + +The number of panels in the layout. **Long**, read-only. + +Syntax: *object*.**PanelCount** + +## Methods + +### AddPanel +{: .no_toc } + +Creates a panel and adds it to the layout. + +Syntax: *object*.**AddPanel** ( *name* , *left* , *top* , *width* , *height* [ , *title* ] ) **As cConsolePanel** + +*name* +: *required* A **String**, the name of the panel. It has to be new. + +*left*, *top* +: *required* **Long** values, the position of the upper left corner. + +*width*, *height* +: *required* **Long** values, the size in columns and rows. + +*title* +: *optional* A **String**, the title on the top border. The default is an empty string, which draws no title. + +Returns the new [**cConsolePanel**](cConsolePanel), with its default style. The first panel of a layout is its root panel. A name that is in use raises error 457, *This key is already associated with an element of this collection*, from the dictionary, but the panel has been added to the list by then, so [**PanelCount**](#panelcount) counts it, [**Render**](#render) draws it, and no name leads to it. + +### AddPanelByRect +{: .no_toc } + +Creates a panel from a rectangle and adds it to the layout. + +Syntax: *object*.**AddPanelByRect** ( *name* , *rect* [ , *title* ] ) **As cConsolePanel** + +*name* +: *required* A **String**, the name of the panel. + +*rect* +: *required* A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect), with inclusive edges. + +*title* +: *optional* A **String**. + +Calls [**AddPanel**](#addpanel) with the left and top edges of *rect* and a width and height of `Right - Left + 1` and `Bottom - Top + 1`. + +### AutoResize +{: .no_toc } + +Sets the size of the layout to the current size of the console. + +Syntax: *object*.**AutoResize** + +Calls [**Resize**](#resize) with the console's [**Width**](../TBMANLIB_ConsoleCore/cConsole#width) and [**Height**](../TBMANLIB_ConsoleCore/cConsole#height). + +### Clear +{: .no_toc } + +Removes every panel. + +Syntax: *object*.**Clear** + +Replaces the list and the dictionary with empty ones, and leaves the layout with no root panel. Nothing is erased from the buffer. + +### ClearAllContent +{: .no_toc } + +Fills the content area of every panel with spaces. + +Syntax: *object*.**ClearAllContent** [ ( *theme* ) ] + +*theme* +: *optional* A [**cConsoleTheme**](cConsoleTheme). The default is **Nothing**. + +Calls [**ClearContent**](cConsolePanel#clearcontent) of each panel with the buffer of the console, using the theme's **Foreground** and **Background** colours, or **Gray** on **Black** when no theme is given. Raises error 91 when the console has no buffer. + +### ClearPanelContent +{: .no_toc } + +Fills the content area of one panel with spaces. + +Syntax: *object*.**ClearPanelContent** ( *name* [ , *theme* ] ) + +*name* +: *required* A **String**, the name of the panel. A name that is not in the layout does nothing. + +*theme* +: *optional* A **cConsoleTheme**, as for [**ClearAllContent**](#clearallcontent). + +### CreateGrid +{: .no_toc } + +Divides the root panel's content area into a grid of panels. + +Syntax: *object*.**CreateGrid** ( *cols* , *rows* , *namePrefix* ) **As Variant** + +*cols*, *rows* +: *required* **Long** values, the number of columns and rows of the grid. A value of 0 raises error -2147467259, *Unspecified error*. + +*namePrefix* +: *required* A **String**. The panels are named *namePrefix*`_0`, *namePrefix*`_1` and so on. + +Creates `cols * rows` panels, row by row from the top left, and returns them in a **Variant** that holds an array in that order, with indexes from 0. The area is the content area of the root panel, or the whole console when the layout has no panel. Its width is divided by *cols* with whole numbers, and its height by *rows*; call the results *colStep* and *rowStep*. A panel that is not in the last column is `colStep - 1` cells wide, which leaves one blank column to its right, and a panel that is not in the last row is `rowStep - 1` rows high; the panels of the last column and the last row reach to the edge of the area, so they take the remainder. + +```tb check_build +Dim con As New cConsole +Dim layout As cConsoleLayout = New cConsoleLayout(con) +Dim p As cConsolePanel +Dim i As Long + +layout.AddPanel "root", 0, 0, 60, 20 ' the content area is columns 2 to 57, rows 2 to 17 +layout.CreateGrid 2, 2, "cell" +For i = 0 To 3 + Set p = layout.GetPanel("cell_" & i) + Debug.Print p.ContentWidth() & " " & p.ContentHeight() +Next +' Output: +' 23 3 +' 24 3 +' 23 4 +' 24 4 +``` + +The content area of the root panel has 56 columns and 16 rows, so *colStep* is 28 and *rowStep* is 8. The cells are columns 2 to 28 and 30 to 57, and rows 2 to 8 and 10 to 17; the sample prints the size of the content area inside the border and padding of each cell. + +### CreateHeaderFooter +{: .no_toc } + +Divides the root panel's content area into a header, a body and a footer. + +Syntax: *object*.**CreateHeaderFooter** ( *headerHeight* , *footerHeight* , *headerName* , *contentName* , *footerName* ) **As Variant** + +*headerHeight*, *footerHeight* +: *required* **Long** values, the number of rows of the header and of the footer. + +*headerName*, *contentName*, *footerName* +: *required* **String** values, the names of the three panels. + +Returns the header, the body and the footer, in that order, as an array of three panels. The header takes the first *headerHeight* rows of the area, the footer the last *footerHeight* rows, and the body the rows between them, with no blank row between the panels. Each panel is as wide as the area. See [Panels, names and the root panel](#panels-names-and-the-root-panel) for the area. + +### CreateSidebar +{: .no_toc } + +Divides the root panel's content area into a sidebar on the left and a content panel. + +Syntax: *object*.**CreateSidebar** ( *sidebarWidth* , *sidebarName* , *contentName* ) **As Variant** + +*sidebarWidth* +: *required* A **Long**, the number of columns of the sidebar. + +*sidebarName*, *contentName* +: *required* **String** values, the names of the two panels. + +Returns the sidebar and the content panel, in that order, as an array of two panels. The sidebar takes the first *sidebarWidth* columns of the area and the content panel the rest, with no blank column between them. Both are as high as the area. + +### GetAllPanels +{: .no_toc } + +Returns every panel. + +Syntax: *object*.**GetAllPanels** ( ) **As Variant** + +Returns a **Variant** that holds an array of **cConsolePanel** in the order of creation, with indexes from 0. + +### GetPanel +{: .no_toc } + +Finds a panel by its name. + +Syntax: *object*.**GetPanel** ( *name* ) **As cConsolePanel** + +*name* +: *required* A **String**. The comparison is case-sensitive. + +Returns the panel, or **Nothing** when the layout has no panel of that name. + +### GetPanelNames +{: .no_toc } + +Returns the names of the panels. + +Syntax: *object*.**GetPanelNames** ( ) **As Variant** + +Returns a **Variant** that holds an array of **String**, with indexes from 0, in the order of the dictionary, which is the order in which the names were added. + +### HasPanel +{: .no_toc } + +Tells whether a name is in use. + +Syntax: *object*.**HasPanel** ( *name* ) **As Boolean** + +*name* +: *required* A **String**. + +### RemovePanel +{: .no_toc } + +Removes a panel. + +Syntax: *object*.**RemovePanel** ( *name* ) + +*name* +: *required* A **String**. A name that is not in the layout does nothing. + +Removes the panel from the dictionary and the list. When it was the root panel, the first panel that is left becomes the root, or the layout has none. Nothing is erased from the buffer. + +### Render +{: .no_toc } + +Draws the border and title of every panel. + +Syntax: *object*.**Render** [ ( *theme* ) ] + +*theme* +: *optional* A [**cConsoleTheme**](cConsoleTheme). The default is **Nothing**. + +Calls [**Render**](cConsolePanel#render) of each panel in order of creation, with the buffer of the console. The border colour is the theme's **Border** and the background is its **Background**; without a theme they are **Gray** and **Black**. The insides of the panels are not changed; [**ClearAllContent**](#clearallcontent) does that. When the console is not in full-screen mode, the buffer is **Nothing** and the call raises error 91. + +```tb check_build +Dim con As New cConsole +Dim layout As cConsoleLayout +Dim theme As cConsoleTheme = mConsoleThemeFactory.CreateHTOP() + +con.EnterFullScreenMode +Set layout = New cConsoleLayout(con) +layout.AddPanel "root", 0, 0, con.Width, con.Height, "Application" +layout.CreateHeaderFooter 1, 1, "head", "body", "foot" + +layout.ClearAllContent theme +layout.Render theme +con.Refresh +``` + +### RenderPanel +{: .no_toc } + +Draws the border and title of one panel. + +Syntax: *object*.**RenderPanel** ( *name* [ , *theme* ] ) + +*name* +: *required* A **String**. A name that is not in the layout does nothing. + +*theme* +: *optional* A **cConsoleTheme**, as for [**Render**](#render). + +### Resize +{: .no_toc } + +Meant to scale the panels to a new console size. + +Syntax: *object*.**Resize** ( *newWidth* , *newHeight* ) + +*newWidth*, *newHeight* +: *required* **Long** values, the new size of the console area in columns and rows. + +The method stores the new size, then computes the factors `newWidth / stored width` and `newHeight / stored height` and moves and resizes every panel by them. The stored size is replaced before the factors are computed, so both are 1, and no panel changes. The only effect is the new size, which [**CreateGrid**](#creategrid), [**CreateSidebar**](#createsidebar) and [**CreateHeaderFooter**](#createheaderfooter) use when the layout has no panel. A *newWidth* or *newHeight* of 0 raises error 6, *Overflow*, since the factor is 0 divided by 0. + +A program that has to follow the size of the window ([**OnResize**](../TBMANLIB_ConsoleCore/cConsole#onresize) of the console reports it) calls [**Clear**](#clear) and makes the panels again. + +### SplitHorizontal +{: .no_toc } + +Divides a panel's content area into a left panel and a right panel. + +Syntax: *object*.**SplitHorizontal** ( *parentName* , *ratio* , *leftName* , *rightName* ) **As Variant** + +*parentName* +: *required* A **String**, the name of the panel to divide. + +*ratio* +: *required* A **Double**, the share of the width for the left panel, from 0 to 1. It is not checked. + +*leftName*, *rightName* +: *required* **String** values, the names of the new panels. + +Returns an array of two panels, the left one first, in a **Variant**. When the parent is not in the layout, the method creates nothing and returns **Nothing** in the **Variant**. + +The left panel is `CLng(ContentWidth * ratio)` columns wide, starting at the left edge of the parent's content area, and the right panel starts one column after it and reaches the right edge. Both are as high as the content area. The method divides the width, so the line between the panels is vertical. The member of the same name in [**cLayoutHelper**](Helpers#splithorizontal) divides the height instead. + +### SplitVertical +{: .no_toc } + +Divides a panel's content area into a top panel and a bottom panel. + +Syntax: *object*.**SplitVertical** ( *parentName* , *ratio* , *topName* , *bottomName* ) **As Variant** + +*parentName* +: *required* A **String**, the name of the panel to divide. + +*ratio* +: *required* A **Double**, the share of the height for the top panel, from 0 to 1. + +*topName*, *bottomName* +: *required* **String** values, the names of the new panels. + +Returns an array of two panels, the top one first, or **Nothing** when the parent is not in the layout. The top panel is `CLng(ContentHeight * ratio)` rows high, from the top edge of the parent's content area, and the bottom panel starts one row after it and reaches the bottom edge. The line between them is horizontal. The member of the same name in [**cLayoutHelper**](Helpers#splitvertical) divides the width instead. + +## See Also + +- [cConsolePanel](cConsolePanel) class -- one panel +- [cConsoleTheme](cConsoleTheme) class -- the colours for **Render** and **ClearAllContent** +- [Helpers](Helpers) -- **cLayoutHelper**, which works out rectangles without panels +- [cConsole](../TBMANLIB_ConsoleCore/cConsole) class -- the console and its buffer diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md new file mode 100644 index 000000000..0898e254a --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel.md @@ -0,0 +1,219 @@ +--- +title: cConsolePanel +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cConsolePanel +has_toc: false +--- + +# cConsolePanel class +{: .no_toc } + +A rectangle of the console with a border, a title and padding, that draws its border into a [**cConsoleBuffer**](../TBMANLIB_ConsoleCore/cConsoleBuffer) and tells where its content area is. + +A panel holds a position, a size and a few style fields, and draws nothing until [**Render**](#render) is called with a buffer. It does not hold content: the program writes into the area that [**ContentRect**](#contentrect) returns. The panel takes the buffer to draw into as a parameter and holds no console. [**cConsoleLayout**](cConsoleLayout) creates panels and keeps them by name. + +```tb check_build +Dim panel As cConsolePanel = New cConsolePanel("main", 2, 1, 20, 6) +Dim buf As cConsoleBuffer = New cConsoleBuffer(30, 10) +Dim ci As ConsoleCharInfo + +panel.Title = "Log" +panel.BorderStyle = PanelBorderStyle.Double +panel.Render buf, ConsoleColor.Cyan, ConsoleColor.Black + +ci = buf.GetChar(2, 1) ' the upper left corner +Debug.Print Hex$(AscW(ci.Char)) & " " & ci.ForeColor +ci = buf.GetChar(4, 1) ' the first character of the title +Debug.Print ci.Char & " " & ci.ForeColor +Debug.Print panel.ContentWidth() & " " & panel.ContentHeight() +' Output: +' 2554 11 +' L 15 +' 16 2 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cConsolePanel**. Columns and rows are counted from 0 in the buffer. + +## The rectangle and the content area + +[**Rect**](#rect) is the rectangle of the panel with its edges inclusive: a panel at column 2, row 1, 20 columns wide and 6 rows high has `Rect.Left` 2, `Rect.Top` 1, `Rect.Right` 21 and `Rect.Bottom` 6. The border is drawn on the edge cells. + +The content area is what is left inside. [**ContentRect**](#contentrect) takes one cell from each side for the border, unless [**BorderStyle**](#borderstyle) is **None**, and then [**Padding**](#padding) cells from each side. The sample above has a content area of 16 columns by 2 rows, from column 4, row 3 to column 19, row 4. The content area can be empty or have a negative size when the panel is small. + +## Fields + +The class has seven public fields, not properties. They are read and written as `panel.Title`, and a write takes effect at the next [**Render**](#render). + +### BorderColor +{: .no_toc } + +The colour of the two spaces that surround the title on the top border. A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor). The starting value is **Gray**. + +The border itself is drawn in the colours that are passed to [**Render**](#render), and not in this one. + +### BorderStyle +{: .no_toc } + +The border style. A [**PanelBorderStyle**](#panelborderstyle). The starting value is **Single**. + +### Name +{: .no_toc } + +The name of the panel. **String**. The constructor sets it. [**cConsoleLayout**](cConsoleLayout) keeps its own list of names, so changing this field later does not change the name under which the layout finds the panel. + +### Padding +{: .no_toc } + +The number of cells between the border and the content area, on every side. **Long**. The starting value is 1. + +### Rect +{: .no_toc } + +The rectangle of the panel. A [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect), with inclusive edges. The constructor, [**Move**](#move) and [**Resize**](#resize) set it. A program that references the published package TBMANLIB_ConsoleCore cannot read its fields: see [The two packages](./#the-two-packages). [**ContentWidth**](#contentwidth) and [**ContentHeight**](#contentheight) give sizes as **Long** values. + +### Title +{: .no_toc } + +The text on the top border. **String**. The starting value is an empty string, which draws no title. + +[**Render**](#render) writes a space, the title and a space, from column `Rect.Left + 1`, in the row `Rect.Top`. A title longer than `Rect.Right - Rect.Left - 3` characters is cut, without an ellipsis, and a panel that is too narrow for one character draws no title. The title is drawn on a black background. + +### TitleColor +{: .no_toc } + +The colour of the title text. A **ConsoleColor**. The starting value is **White**. + +## Enumeration + +### PanelBorderStyle +{: .no_toc } + +The border style of a panel. The enumeration is declared in the class, and its members are written without the class, as in `PanelBorderStyle.Rounded`; the type is `cConsolePanel.PanelBorderStyle` or `PanelBorderStyle`. + +| Member | Value | Border | +|---|---:|---| +| **None** | 0 | No border, and no title. The content area starts at the edge plus the padding. | +| **Single** | 1 | Single lines: U+250C, U+2510, U+2514, U+2518, U+2500, U+2502. The default. | +| **Double** | 2 | Double lines: U+2554, U+2557, U+255A, U+255D, U+2550, U+2551. | +| **Rounded** | 3 | Single lines with rounded corners: U+256D, U+256E, U+2570, U+256F, with U+2500 and U+2502. | +| **Bold** | 4 | Heavy lines: U+250F, U+2513, U+2517, U+251B, U+2501, U+2503. | + +**Single** and **Double** are drawn by [**DrawBox**](../TBMANLIB_ConsoleCore/cConsoleBuffer#drawbox) of the buffer, and the other two by the panel itself. + +## Constructor + +### New +{: .no_toc } + +Creates a panel. + +Syntax: **New cConsolePanel** [ ( [ *name* [ , *left* [ , *top* [ , *width* [ , *height* ] ] ] ] ] ) ] + +*name* +: *optional* A **String**. The default is an empty string. + +*left*, *top* +: *optional* **Long** values, the position of the upper left corner. The defaults are 0. + +*width*, *height* +: *optional* **Long** values, the size in columns and rows. The defaults are 10 and 5. + +Sets [**Rect**](#rect) to `left`, `top`, `left + width - 1` and `top + height - 1`, [**BorderStyle**](#borderstyle) to **Single**, [**BorderColor**](#bordercolor) to **Gray**, [**TitleColor**](#titlecolor) to **White** and [**Padding**](#padding) to 1. + +## Methods + +### ClearContent +{: .no_toc } + +Fills the content area with spaces. + +Syntax: *object*.**ClearContent** ( *buffer* [ , *fore* [ , *back* ] ] ) + +*buffer* +: *required* A [**cConsoleBuffer**](../TBMANLIB_ConsoleCore/cConsoleBuffer). + +*fore* +: *optional* A **ConsoleColor**, the colour of the cells. The default is **Gray**. + +*back* +: *optional* A **ConsoleColor**, the background. The default is **Black**. + +The padding and the border are left as they are. Nothing is done when the content area is less than 2 cells wide or 2 cells high. + +### ContentHeight +{: .no_toc } + +Returns the height of the content area. + +Syntax: *object*.**ContentHeight** ( ) **As Long** + +Returns `ContentRect.Bottom - ContentRect.Top + 1`. It is 0 or negative when the border and the padding take all the rows. + +### ContentRect +{: .no_toc } + +Returns the content area. + +Syntax: *object*.**ContentRect** ( ) **As ConsoleRect** + +Returns a [**ConsoleRect**](../TBMANLIB_ConsoleCore/Types#consolerect) that is [**Rect**](#rect) less one cell on each side for the border (none when [**BorderStyle**](#borderstyle) is **None**) and less [**Padding**](#padding) cells on each side. + +### ContentWidth +{: .no_toc } + +Returns the width of the content area. + +Syntax: *object*.**ContentWidth** ( ) **As Long** + +Returns `ContentRect.Right - ContentRect.Left + 1`. + +### Move +{: .no_toc } + +Moves the panel and keeps its size. + +Syntax: *object*.**Move** ( *left* , *top* ) + +*left*, *top* +: *required* **Long** values, the new position of the upper left corner. + +Nothing is drawn, and the old cells are not cleared. + +### Render +{: .no_toc } + +Draws the border and the title. + +Syntax: *object*.**Render** ( *buffer* [ , *fore* [ , *back* ] ] ) + +*buffer* +: *required* A [**cConsoleBuffer**](../TBMANLIB_ConsoleCore/cConsoleBuffer). + +*fore* +: *optional* A **ConsoleColor**, the colour of the border. The default is **Gray**. [**BorderColor**](#bordercolor) is not used for it. + +*back* +: *optional* A **ConsoleColor**, the background of the border. The default is **Black**. + +Draws the border of the style in [**BorderStyle**](#borderstyle) around [**Rect**](#rect) and then the [**Title**](#title). The inside of the panel is not changed: the text that was there stays, and [**ClearContent**](#clearcontent) removes it. With the style **None** the method draws nothing, not even the title. Cells outside the buffer are dropped. + +### Resize +{: .no_toc } + +Gives the panel a new size and keeps its position. + +Syntax: *object*.**Resize** ( *width* , *height* ) + +*width*, *height* +: *required* **Long** values, the new size in columns and rows. + +Sets `Rect.Right` to `Rect.Left + width - 1` and `Rect.Bottom` to `Rect.Top + height - 1`. Nothing is drawn. + +## See Also + +- [cConsoleLayout](cConsoleLayout) class -- creates and places named panels +- [cConsoleTheme](cConsoleTheme) class -- colours for **Render** +- [cConsoleBuffer](../TBMANLIB_ConsoleCore/cConsoleBuffer) class -- **DrawBox** and the other drawing members diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleTheme.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleTheme.md new file mode 100644 index 000000000..2f9b88d00 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleTheme.md @@ -0,0 +1,129 @@ +--- +title: cConsoleTheme +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cConsoleTheme +has_toc: false +--- + +# cConsoleTheme class +{: .no_toc } + +A set of 14 named colours, which a program uses to draw all its parts in one style, with three ready-made sets in the module **mConsoleThemeFactory**. + +A **cConsoleTheme** has no behaviour of its own besides two methods. Its fields are [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor) values that a program reads when it draws. The package reads only three of them: [**cConsoleLayout**](cConsoleLayout) uses **Border** and **Background** to draw the borders of its panels, and **Foreground** and **Background** to clear their content areas. The other fields are for the program's own drawing, for example with [**cDataGrid**](cDataGrid) and [**cProgressBar**](cProgressBar). + +```tb check_build check_run +Dim theme As cConsoleTheme = mConsoleThemeFactory.CreateHTOP() +Dim themeCopy As cConsoleTheme = theme.Clone() +Dim blank As New cConsoleTheme + +themeCopy.Border = ConsoleColor.Red +Debug.Print theme.Border & " " & themeCopy.Border +Debug.Print theme.GetColor(ThemeColorType.tcSuccess) +Debug.Print blank.Background & " " & blank.Foreground +' Output: +' 7 12 +' 10 +' 0 0 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cConsoleTheme**. + +## Fields + +The class has 14 public fields, all of the type [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor). A theme that is made with `New cConsoleTheme` has every field at 0, **Black**, until the program sets it; the functions of [mConsoleThemeFactory](#mconsolethemefactory-module) return themes with all fields set. + +| Field | Meant for | Read by the package | +|---|---|---| +| **Background** | the background of the screen and of panels | [**cConsoleLayout**](cConsoleLayout): the background of borders, and the colour of cleared areas | +| **Foreground** | ordinary text | [**cConsoleLayout.ClearAllContent**](cConsoleLayout#clearallcontent) and [**ClearPanelContent**](cConsoleLayout#clearpanelcontent) | +| **Border** | borders | [**cConsoleLayout.Render**](cConsoleLayout#render) and [**RenderPanel**](cConsoleLayout#renderpanel) | +| **Primary** | the main accent colour | no | +| **Secondary** | a second accent colour | no | +| **Success** | a good result | no | +| **Warning** | a result that needs attention | no | +| **Error** | a failure | no | +| **Info** | neutral information | no | +| **Muted** | less important text | no | +| **HeaderBackground** | the background of a header or a table heading | no | +| **HeaderForeground** | the text of a header or a table heading | no | +| **SelectedBackground** | the background of a selected item | no | +| **SelectedForeground** | the text of a selected item | no | + +The class has no comment on the use of the fields, so the second column describes each from its name. [**cDataGrid**](cDataGrid) has its own colour properties and does not read a theme. + +## Methods + +### Clone +{: .no_toc } + +Returns a copy of the theme. + +Syntax: *object*.**Clone** ( ) **As cConsoleTheme** + +Returns a new **cConsoleTheme** with the 14 fields of the object. Changing the copy does not change the original. + +### GetColor +{: .no_toc } + +Returns a colour by its kind. + +Syntax: *object*.**GetColor** ( *colorType* ) **As ConsoleColor** + +*colorType* +: *required* A [**ThemeColorType**](../TBMANLIB_ConsoleCore/Types#themecolortype): **tcPrimary**, **tcSecondary**, **tcSuccess**, **tcWarning**, **tcError**, **tcInfo**, **tcMuted**, **tcBackground**, **tcForeground**, **tcBorder**, **tcHeaderBackground**, **tcHeaderForeground**, **tcSelectedBackground** or **tcSelectedForeground**. + +Returns the field of the same name. For a value that is not one of the 14 members, it returns **Foreground**. The enumeration is declared in the package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/Types#themecolortype). + +## mConsoleThemeFactory module + +The module has three functions that return a new, complete theme each time they are called. They have no parameters, and are written with the name of the module, as in `mConsoleThemeFactory.CreateLight()`. + +| Field | **CreateDefault** | **CreateHTOP** | **CreateLight** | +|---|---|---|---| +| **Background** | Black | Black | White | +| **Foreground** | Gray | Gray | Black | +| **Primary** | Blue | Cyan | Blue | +| **Secondary** | DarkGray | DarkBlue | Gray | +| **Success** | Green | Green | DarkGreen | +| **Warning** | Yellow | Yellow | DarkYellow | +| **Error** | Red | Red | DarkRed | +| **Info** | Cyan | Cyan | DarkCyan | +| **Muted** | DarkGray | DarkGray | Gray | +| **Border** | DarkGray | Gray | Gray | +| **HeaderBackground** | DarkBlue | DarkBlue | DarkGray | +| **HeaderForeground** | White | White | White | +| **SelectedBackground** | DarkBlue | Blue | Gray | +| **SelectedForeground** | White | White | Black | + +### CreateDefault +{: .no_toc } + +Returns the dark theme: grey text on black, with a dark grey border and a blue accent. + +Syntax: **CreateDefault** ( ) **As cConsoleTheme** + +### CreateHTOP +{: .no_toc } + +Returns a theme in the style of the Linux program `htop`: the dark theme with a cyan accent, a light grey border and a bright blue selection. + +Syntax: **CreateHTOP** ( ) **As cConsoleTheme** + +### CreateLight +{: .no_toc } + +Returns the light theme: black text on white, with the darker colours for success, warning, error and information, and grey for the border, the muted text and the selection. + +Syntax: **CreateLight** ( ) **As cConsoleTheme** + +A console shows the colours of its own colour table, so the "white" of the light theme is the white of that table, and a console whose table is dark can show it differently. + +## See Also + +- [cConsoleLayout](cConsoleLayout) class -- draws with a theme +- [cConsolePanel](cConsolePanel) class +- [Types](../TBMANLIB_ConsoleCore/Types) of ConsoleCore -- **ConsoleColor** and **ThemeColorType** diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md new file mode 100644 index 000000000..78e465e30 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cDataGrid.md @@ -0,0 +1,256 @@ +--- +title: cDataGrid +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cDataGrid +has_toc: false +--- + +# cDataGrid class +{: .no_toc } + +Draws a table of text into the screen buffer, with a header row, columns of fixed widths, aligned cells, coloured rows and one selected row. + +A **cDataGrid** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. The program gives it the rows of text, the widths and alignments of the columns and the colours of the rows with the four `Set` members, and then calls [**Render**](#render), which draws the whole table at a position. The grid holds no scroll position and does not read keys: a program that lets the user choose a row calls [**MoveUp**](#moveup) and [**MoveDown**](#movedown) from its own key handler and draws the table again. + +```tb check_build +Dim con As New cConsole +Dim grid As cDataGrid +Dim rows(0 To 2) As String +Dim widths(0 To 1) As Long +Dim aligns(0 To 1) As ColumnAlignment +Dim colors(0 To 2) As ConsoleColor +Dim ci As ConsoleCharInfo + +con.EnterFullScreenMode +Set grid = New cDataGrid(con) + +rows(0) = "Name" & vbTab & "Size" ' the first row is the header +rows(1) = "readme.txt" & vbTab & "1,204" +rows(2) = "setup.exe" & vbTab & "88,512" +widths(0) = 12: widths(1) = 8 +aligns(0) = ColumnAlignment.Left: aligns(1) = ColumnAlignment.Right +colors(1) = ConsoleColor.Green: colors(2) = ConsoleColor.Yellow + +grid.SetRows rows +grid.SetColumnWidths widths +grid.SetColumnAlignments aligns +grid.SetRowColors colors +grid.SelectedRow = 1 ' the second row under the header +grid.Render 0, 0 + +ci = con.Buffer.GetChar(0, 3) ' the selected row +Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor +ci = con.Buffer.GetChar(0, 2) +Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor +con.ExitFullScreenMode +' Output: +' s 15 1 +' r 10 0 +``` + +The sample draws these four rows, the first at row 0 and column 0. The selected row is white on dark blue, and the row above it is green: + +```text +Name │ Size +────────────┼──────── +readme.txt │ 1,204 +setup.exe │ 88,512 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cDataGrid**. Columns and rows are counted from 0 in the buffer. + +## The data + +The text of the table is an array of **String**, one element for each row. Within a row the cells are separated by the tab character, `vbTab`. When [**ShowHeader**](#showheader) is **True**, which is the default, the first element is the header row and the others are data rows. + +The grid needs all four of the arrays of [**SetRows**](#setrows), [**SetColumnWidths**](#setcolumnwidths), [**SetColumnAlignments**](#setcolumnalignments) and [**SetRowColors**](#setrowcolors) before [**Render**](#render). **Render** raises error 9, *Subscript out of range*, when any of the four was never set. + +The number of columns is the number of elements of the widths array. A cell is cut to the width of its column, without an ellipsis, and padded with spaces to that width; an alignment of **Center** puts the extra space of an odd padding on the right. A row with fewer cells than columns has empty cells, and the cells beyond the last column are dropped. + +With [**ShowBorders**](#showborders) **True**, which is the default, a vertical bar U+2502 is drawn in the column after each column except the last, and a line of U+2500 with U+253C where the bars are lies under the header. That is not a frame: the table has no outer border and no lines between data rows. With **ShowBorders** at **False** there is no separator column and no line, and the columns follow each other with no space between. + +The colours are these. The header is drawn in the header colour on black. A data row is drawn in the *fore* and *back* colours of [**Render**](#render), or in the colour from [**SetRowColors**](#setrowcolors) when one is given, on *back*. The selected row is white on the selection colour. The separator line and the vertical bars take *fore* and *back*, and in the header the header colour. + +## Enumeration + +### ColumnAlignment +{: .no_toc } + +The alignment of the cells of a column. The enumeration is declared in the class, and its members are written without the class, as in `ColumnAlignment.Right`. + +| Member | Value | Cells | +|---|---:|---| +| **Left** | 0 | start at the left edge of the column; the default | +| **Center** | 1 | are centred; for an odd padding the extra space is on the right | +| **Right** | 2 | end at the right edge of the column | + +An element of the alignment array that is missing, because the array is shorter than the number of columns, is **Left**. + +## Constructor + +### New +{: .no_toc } + +Creates a grid for a console. + +Syntax: **New cDataGrid** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole) that is in full-screen mode. + +Keeps the console and its current [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer). The grid starts with the header colour **Cyan**, the selection colour **DarkBlue**, no selected row (the row number -1), the header shown and the separators shown. When the console is not in full-screen mode, the buffer is **Nothing** and [**Render**](#render) raises error 91. See [How a component works](./#how-a-component-works). + +## Properties + +All the properties can be set and none can be read: each has a `Let` and no `Get`. They take effect at the next [**Render**](#render). + +### HeaderColor +{: .no_toc } + +The colour of the header row. A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), write-only. The default is **Cyan**. The header is drawn on a black background whatever *back* is. + +Syntax: *object*.**HeaderColor** **=** *value* + +### SelectedRow +{: .no_toc } + +The selected row. **Long**, write-only. The default is -1, which selects none. + +Syntax: *object*.**SelectedRow** **=** *value* + +*value* +: A **Long**, the number of the row counted from 0 among the data rows, not counting the header row. + +### SelectionColor +{: .no_toc } + +The background colour of the selected row. A **ConsoleColor**, write-only. The default is **DarkBlue**. The text of the selected row is **White**. + +Syntax: *object*.**SelectionColor** **=** *value* + +### ShowBorders +{: .no_toc } + +Whether the column separators and the line under the header are drawn. **Boolean**, write-only. The default is **True**. + +Syntax: *object*.**ShowBorders** **=** *value* + +### ShowHeader +{: .no_toc } + +Whether the first row is a header. **Boolean**, write-only. The default is **True**. With **False**, every row is a data row, and no line is drawn under the first row. + +Syntax: *object*.**ShowHeader** **=** *value* + +## Methods + +### GetRowCount +{: .no_toc } + +Returns the number of data rows. + +Syntax: *object*.**GetRowCount** ( ) **As Long** + +Returns the number of elements of the rows array, less one when [**ShowHeader**](#showheader) is **True**. Raises error 9 when [**SetRows**](#setrows) was never called. + +### MoveDown +{: .no_toc } + +Selects the next row. + +Syntax: *object*.**MoveDown** ( *maxRow* ) + +*maxRow* +: *required* A **Long**, the number of data rows. The selection moves down only while the selected row is less than *maxRow* - 1, so the last data row is the last one reachable. [**GetRowCount**](#getrowcount) gives the number. + +With no selection (-1) the first data row is selected. + +### MoveUp +{: .no_toc } + +Selects the previous row. + +Syntax: *object*.**MoveUp** ( ) + +The selection moves up only while the selected row is above 0, so the first data row is the first one reachable, and a call with no selection does nothing. + +### Render +{: .no_toc } + +Draws the table. + +Syntax: *object*.**Render** ( *col* , *row* [ , *fore* [ , *back* ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the upper left cell of the table. + +*fore* +: *optional* A **ConsoleColor**, the text colour of the data rows without a row colour, and of the separators. The default is **Gray**. + +*back* +: *optional* A **ConsoleColor**, the background of the data rows and the separators. The default is **Black**. + +Draws the header row, the line under it when [**ShowBorders**](#showborders) is on, and then every data row, one row of the buffer each, going down from *row*. The grid is as wide as the column widths plus one column for each separator, and as high as the number of rows plus one for the line. Rows that reach below the buffer are dropped. + +### ScrollTo +{: .no_toc } + +Selects a row. + +Syntax: *object*.**ScrollTo** ( *row* ) + +*row* +: *required* A **Long**, the number of the data row, counted from 0. + +The method only sets the selected row, as [**SelectedRow**](#selectedrow) does. It does not scroll: the grid has no scroll position, and always draws all its rows. + +### SetColumnAlignments +{: .no_toc } + +Sets the alignment of each column. + +Syntax: *object*.**SetColumnAlignments** ( *alignments* ) + +*alignments* +: *required* An array of [**ColumnAlignment**](#columnalignment), passed by reference. The element for the first column is the one at the lower bound of the array. The method copies the array. + +### SetColumnWidths +{: .no_toc } + +Sets the width of each column. + +Syntax: *object*.**SetColumnWidths** ( *widths* ) + +*widths* +: *required* An array of **Long**, passed by reference, the width in cells of each column. Its number of elements is the number of columns. The method copies the array. + +### SetRowColors +{: .no_toc } + +Sets the text colour of rows. + +Syntax: *object*.**SetRowColors** ( *colors* ) + +*colors* +: *required* An array of [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), passed by reference. The method copies the array. + +Both arrays start at index 0. The colour of the row at index *i* of the array of [**SetRows**](#setrows) is `colors(i)`. The header row is counted, so with a header the first data row takes `colors(1)`, and `colors(0)` is not used: the header has its own colour. A row whose index lies beyond the end of the colour array keeps *fore*. The selected row ignores its colour. + +### SetRows +{: .no_toc } + +Sets the text of the table. + +Syntax: *object*.**SetRows** ( *rows* ) + +*rows* +: *required* An array of **String**, passed by reference, one element for each row, with the cells separated by the tab character. The method copies the array: later changes to it do not reach the grid. + +## See Also + +- [cConsolePanel](cConsolePanel) class -- a bordered area to draw the table in +- [cProgressBar](cProgressBar) class and [cSparkline](cSparkline) class -- other drawing components +- [cConsoleBuffer](../TBMANLIB_ConsoleCore/cConsoleBuffer) class -- the buffer the table is drawn into diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cProgressBar.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cProgressBar.md new file mode 100644 index 000000000..a682de59c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cProgressBar.md @@ -0,0 +1,197 @@ +--- +title: cProgressBar +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cProgressBar +has_toc: false +--- + +# cProgressBar class +{: .no_toc } + +Draws a progress bar, horizontal or vertical, into the screen buffer of the full-screen mode. + +A **cProgressBar** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. Its `Render` members write a bar made of a filled part and an empty part into that buffer; they do not clear the cells around the bar and do not write to the console. [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh) of the console shows the result. The filled part is drawn with the character U+2588 (a full block), in green, and the empty part with U+2591 (a light shade), in dark grey, unless the call says otherwise. + +```tb check_build +Dim con As New cConsole +Dim bar As cProgressBar +Dim ci As ConsoleCharInfo + +con.EnterFullScreenMode ' before the bar is made: it keeps the buffer +Set bar = New cProgressBar(con) + +bar.Render 0, 0, 10, 50 +ci = con.Buffer.GetChar(4, 0) +Debug.Print Hex$(AscW(ci.Char)) & " " & ci.ForeColor +ci = con.Buffer.GetChar(5, 0) +Debug.Print Hex$(AscW(ci.Char)) & " " & ci.ForeColor +con.ExitFullScreenMode +' Output: +' 2588 10 +' 2591 8 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cProgressBar**. Columns and rows are counted from 0 in the buffer, and a position is a column and a row, in that order. + +## Common behaviour + +In every `Render` member, *percent* is a **Double** from 0 to 100; a value outside the range is taken as the nearest end. The number of filled cells is `CLng(percent / 100 * size)`, which rounds half to even, so 25 percent of 10 cells is 2 cells. The filled and empty parts together are exactly *size* cells. + +*filledChar* and *emptyChar* are strings of which only the first character is used; an empty string raises error 5, *Invalid procedure call or argument*. The colours *filledColor* and *emptyColor* are [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor) values, and the cells have a black background. A cell outside the buffer is dropped. + +A bar that is made before the console enters full-screen mode holds no buffer, and its first `Render` raises error 91, *Object variable or With block variable not set*; see [How a component works](./#how-a-component-works). + +## Constructor + +### New +{: .no_toc } + +Creates a progress bar for a console. + +Syntax: **New cProgressBar** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole) that is in full-screen mode. + +Keeps the console and its current [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer). When the console is not in full-screen mode, the buffer is **Nothing** and every `Render` member raises error 91. The bar keeps that buffer object: after the console leaves full-screen mode and enters it again, the console has a new buffer and the bar still draws into the old one. + +## Methods + +### GetProgressColor +{: .no_toc } + +Returns a colour for a percentage: one colour for low values, one for medium values and one for high values. + +Syntax: *object*.**GetProgressColor** ( *percent* [ , *lowColor* [ , *midColor* [ , *highColor* ] ] ] ) **As ConsoleColor** + +*percent* +: *required* A **Double**. This method does not limit the value to 0 to 100. + +*lowColor* +: *optional* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), for values below 50. The default is **Green**. + +*midColor* +: *optional* A **ConsoleColor**, for values from 50 up to, but not including, 80. The default is **Yellow**. + +*highColor* +: *optional* A **ConsoleColor**, for values of 80 and above. The default is **Red**. + +The method does not draw, so it needs no buffer. + +```tb check_build +Dim con As New cConsole +Dim bar As cProgressBar = New cProgressBar(con) + +Debug.Print bar.GetProgressColor(10) & " " & bar.GetProgressColor(50) & " " & bar.GetProgressColor(80) +' Output: +' 10 14 12 +``` + +### Render +{: .no_toc } + +Draws a horizontal bar. + +Syntax: *object*.**Render** ( *col* , *row* , *width* , *percent* [ , *filledChar* [ , *emptyChar* [ , *filledColor* [ , *emptyColor* ] ] ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the left end of the bar. + +*width* +: *required* A **Long**, the number of cells. + +*percent* +: *required* A **Double**, 0 to 100. + +*filledChar* +: *optional* A **String**, the character of the filled part. The default is U+2588. + +*emptyChar* +: *optional* A **String**, the character of the empty part. The default is U+2591. + +*filledColor* +: *optional* A **ConsoleColor**. The default is **Green**. + +*emptyColor* +: *optional* A **ConsoleColor**. The default is **DarkGray**. + +The filled part starts at *col* and the empty part follows it, to *col* + *width* - 1. A *width* of 0 or less draws nothing. + +### RenderVertical +{: .no_toc } + +Draws a vertical bar. + +Syntax: *object*.**RenderVertical** ( *col* , *row* , *height* , *percent* [ , *filledChar* [ , *emptyChar* [ , *filledColor* [ , *emptyColor* ] ] ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the top of the bar. + +*height* +: *required* A **Long**, the number of cells. + +*percent*, *filledChar*, *emptyChar*, *filledColor*, *emptyColor* +: As for [**Render**](#render). + +The filled part grows from the bottom, in the cells from `row + height - 1` upwards, and the empty part is drawn from *row* downwards. + +### RenderWithAutoColor +{: .no_toc } + +Draws a horizontal bar whose filled colour depends on the percentage. + +Syntax: *object*.**RenderWithAutoColor** ( *col* , *row* , *width* , *percent* [ , *filledChar* [ , *emptyChar* [ , *emptyColor* [ , *lowColor* [ , *midColor* [ , *highColor* ] ] ] ] ] ] ) + +*col*, *row*, *width*, *percent*, *filledChar*, *emptyChar* +: As for [**Render**](#render). + +*emptyColor* +: *optional* A **ConsoleColor**. The default is **DarkGray**. + +*lowColor*, *midColor*, *highColor* +: *optional* **ConsoleColor** values for [**GetProgressColor**](#getprogresscolor). The defaults are **Green**, **Yellow** and **Red**. + +Chooses the filled colour with **GetProgressColor** and calls **Render**. Note the order of the parameters: the empty colour comes before the three colours, and there is no *filledColor*. + +### RenderWithBrackets +{: .no_toc } + +Draws a horizontal bar between square brackets. + +Syntax: *object*.**RenderWithBrackets** ( *col* , *row* , *width* , *percent* [ , *filledChar* [ , *emptyChar* [ , *filledColor* [ , *emptyColor* [ , *bracketColor* ] ] ] ] ] ) + +*col*, *row*, *width*, *percent*, *filledChar*, *emptyChar*, *filledColor*, *emptyColor* +: As for [**Render**](#render), except that *width* includes the two brackets. + +*bracketColor* +: *optional* A **ConsoleColor**, the colour of the brackets. The default is **Gray**. + +Draws `[` at *col*, the bar in the *width* - 2 cells after it, and `]` at *col* + *width* - 1. Despite its source comment, the method draws no percentage; [**RenderWithLabel**](#renderwithlabel) does. + +### RenderWithLabel +{: .no_toc } + +Draws a label, a horizontal bar and the percentage in a row. + +Syntax: *object*.**RenderWithLabel** ( *col* , *row* , *width* , *percent* , *label* [ , *filledChar* [ , *emptyChar* [ , *filledColor* [ , *emptyColor* [ , *labelColor* ] ] ] ] ] ) + +*col*, *row*, *width*, *percent*, *filledChar*, *emptyChar*, *filledColor*, *emptyColor* +: As for [**Render**](#render), except that *width* is the width of the whole row: label, bar and percentage. + +*label* +: *required* A **String**, written first. + +*labelColor* +: *optional* A **ConsoleColor**, the colour of the label and the percentage. The default is **Gray**. + +The row is the label, a space, the bar, a space, and the percentage rounded to a whole number with a percent sign. The bar gets what is left of *width* after the label and the percentage text. The text has one character more for each digit of the rounded percentage, so the bar is one cell narrower at 10 percent and at 100 percent than just below them. When the bar would be less than 3 cells wide, the method draws only the label, a space and the percentage, in *labelColor*, and no bar. + +## See Also + +- [cVBar](cVBar) class -- vertical bar charts +- [cSparkline](cSparkline) class -- line charts of a series of values +- [DrawProgressBar](../TBMANLIB_ConsoleCore/cConsole#drawprogressbar) of **cConsole** -- a bar that is written to the console directly, without a buffer +- [cConsoleBuffer](../TBMANLIB_ConsoleCore/cConsoleBuffer) class -- the buffer that the bar draws into diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cSparkline.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cSparkline.md new file mode 100644 index 000000000..c2d146762 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cSparkline.md @@ -0,0 +1,240 @@ +--- +title: cSparkline +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cSparkline +has_toc: false +--- + +# cSparkline class +{: .no_toc } + +Draws a small chart of a series of numbers into the screen buffer, as Braille dots, as asterisks joined by vertical bars, or as columns of blocks. + +A **cSparkline** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. [**Render**](#render) takes a series of **Double** values, scales them between a minimum and a maximum, and draws one column of the chart for each of *width* columns of the buffer. [**RenderWithStats**](#renderwithstats) also writes the smallest, the average and the largest value under the chart. The class draws into the buffer only; [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh) of the console shows it. + +```tb check_build +Dim con As New cConsole +Dim chart As cSparkline +Dim cpu(0 To 9) As Double +Dim i As Long + +con.EnterFullScreenMode +Set chart = New cSparkline(con) + +For i = 0 To 9 + cpu(i) = 10 + i * 8 +Next +chart.RenderWithStats 2, 1, 10, 4, cpu, 0, 100, ConsoleColor.Green, ConsoleColor.Black, SparklineCharSet.Braille +con.Refresh +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cSparkline**. Columns and rows are counted from 0 in the buffer. + +## The series + +The series is an array of **Double** values that has to start at index 0: the drawing code addresses the values by position from 0, so an array with a lower bound of 1 raises error 9, *Subscript out of range*. + +A series of fewer than 2 values draws nothing, and an array that was never allocated raises error 9. Each value is scaled to `(value - minValue) / (maxValue - minValue)` and limited to 0 to 1. A *minValue* equal to *maxValue* raises error 11, *Division by zero*. A *width* of 0 raises error 11 too. + +The chart has *width* columns. When the series has more values than columns, each column takes the value at position `CLng(x * count / width)`, where *x* is the column counted from 0; the Braille set takes all the values from that position up to the position of the next column. When the series has fewer values than columns, the ASCII and Block sets repeat values, and the Braille set leaves some columns empty. The series is not averaged or smoothed. + +## Character sets + +### Braille +{: .no_toc } + +Each cell of the chart is a character of the Braille Patterns block, U+2800 plus a number from 0 to 255 in which each dot is one bit. The chart is *width* cells wide and `(height + 3) \ 4` cells high, so *height* counts dots, four to a cell. Every cell of that area is written, with U+2800 where no dot is set, so the chart clears its area. + +For each value, the vertical position in dots is `CLng((1 - scaled) * height)`, counted from the top: 0 for the maximum, and *height* for the minimum. The dot is set in the cell row `position \ 4` at the dot `position Mod 4` of the cell. The code uses the first four bits of the Braille pattern for those four positions, which are the dots 1, 2 and 3 of the left column, from the top, and the dot 4, which is the top dot of the right column. A value in the lowest of the four positions of a cell therefore appears at the top of the right column, and not below the third dot. + +A value equal to the minimum has the position *height*. When *height* is a multiple of 4 that position lies below the last cell row, and the value sets no dot. + +With 5 values, 5 columns and a height of 4, the cells are: + +```tb check_build +Dim con As New cConsole +Dim chart As cSparkline +Dim v(0 To 4) As Double +Dim ci As ConsoleCharInfo +Dim x As Long +Dim line As String + +con.EnterFullScreenMode +Set chart = New cSparkline(con) +v(0) = 100: v(1) = 75: v(2) = 50: v(3) = 25: v(4) = 0 + +chart.Render 0, 0, 5, 4, v, 0, 100 +For x = 0 To 4 + ci = con.Buffer.GetChar(x, 0) + line = line & Hex$(AscW(ci.Char)) & " " +Next +Debug.Print Trim$(line) +con.ExitFullScreenMode +' Output: +' 2801 2802 2804 2808 2800 +``` + +The cells are `U+2801`, `U+2802`, `U+2804` and `U+2808` for the values 100, 75, 50 and 25: the dots 1, 2, 3 and 4. The value 0 sets no dot. + +Rows of cells are drawn in the reverse order. The code draws the cell row for dot rows 0 to 3 (the top ones) on the bottom row of the chart, and the cell row for dot rows 4 to 7 above it. For a *height* above 4 the chart is therefore upside down in its rows: a value in the upper half of the range appears in the lower row. + +```tb check_build +Dim con As New cConsole +Dim chart As cSparkline +Dim v(0 To 4) As Double +Dim ci As ConsoleCharInfo +Dim x As Long +Dim y As Long +Dim line As String + +con.EnterFullScreenMode +Set chart = New cSparkline(con) +v(0) = 0: v(1) = 25: v(2) = 50: v(3) = 75: v(4) = 100 ' a rising series + +chart.Render 0, 0, 5, 8, v, 0, 100 +For y = 0 To 1 + line = "" + For x = 0 To 4 + ci = con.Buffer.GetChar(x, y) + line = line & Hex$(AscW(ci.Char)) & " " + Next + Debug.Print Trim$(line) +Next +con.ExitFullScreenMode +' Output: +' 2800 2804 2801 2800 2800 +' 2800 2800 2800 2804 2801 +``` + +The series rises, and the first row (the top one) holds the dots of the values 25 and 50, while the second row holds those of 75 and 100. + +### ASCII +{: .no_toc } + +The chart is *width* columns wide and *height* rows high. A value is drawn as `*` in the row `CLng((1 - scaled) * (height - 1))` counted from the top, so the maximum is in the top row and the minimum in the bottom row. When the point of a column lies more than one row above or below the point of the column on its left, the cells of that left column strictly between the two rows are filled with U+2502 (a vertical bar). The set writes only the points and the bars; it does not clear the other cells of its area. + +### Block +{: .no_toc } + +The chart is *width* columns wide and *height* rows high. Each column is a bar of U+2588 (a full block) of `CLng(scaled * height)` cells from the bottom, and a column whose value is above the minimum has at least one cell. The cells above the bar are set to spaces, so the area is cleared. + +```tb check_build +Dim con As New cConsole +Dim chart As cSparkline +Dim v(0 To 4) As Double +Dim ci As ConsoleCharInfo +Dim x As Long +Dim y As Long +Dim line As String + +con.EnterFullScreenMode +Set chart = New cSparkline(con) +v(0) = 0: v(1) = 25: v(2) = 50: v(3) = 75: v(4) = 100 + +chart.Render 0, 0, 5, 4, v, 0, 100, ConsoleColor.Green, ConsoleColor.Black, SparklineCharSet.Block +For y = 0 To 3 + line = "" + For x = 0 To 4 + ci = con.Buffer.GetChar(x, y) + If ci.Char = ChrW$(&H2588) Then line = line & "#" Else line = line & " " + Next + Debug.Print "[" & line & "]" +Next +con.ExitFullScreenMode +' Output: +' [ #] +' [ ##] +' [ ###] +' [ ####] +``` + +## Enumeration + +### SparklineCharSet +{: .no_toc } + +The character set of a chart. The enumeration is declared in the class, and its members are written without the class, as in `SparklineCharSet.Block`. + +| Member | Value | Drawn with | +|---|---:|---| +| **Braille** | 0 | Braille dots; the default. See [Braille](#braille). | +| **ASCII** | 1 | `*` and the bar U+2502. See [ASCII](#ascii). | +| **Block** | 2 | U+2588 and spaces. See [Block](#block). | + +The name **ASCII** is the author's: the set also uses the bar U+2502, which is not an ASCII character. + +## Constructor + +### New +{: .no_toc } + +Creates a sparkline for a console. + +Syntax: **New cSparkline** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole) that is in full-screen mode. + +Keeps the console and its current [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer). When the console is not in full-screen mode, the buffer is **Nothing** and [**Render**](#render) raises error 91. See [How a component works](./#how-a-component-works). + +## Methods + +### Render +{: .no_toc } + +Draws a chart of a series of values. + +Syntax: *object*.**Render** ( *col* , *row* , *width* , *height* , *values* [ , *minValue* [ , *maxValue* [ , *fore* [ , *back* [ , *charSet* ] ] ] ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the upper left cell of the chart. + +*width* +: *required* A **Long**, the number of columns of the chart. + +*height* +: *required* A **Long**, the height of the chart: in rows for the sets ASCII and Block, and in dots for Braille, four to a row. + +*values* +: *required* An array of **Double**, passed by reference, that starts at index 0. See [The series](#the-series). + +*minValue* +: *optional* A **Double**, the value drawn at the bottom. The default is 0. + +*maxValue* +: *optional* A **Double**, the value drawn at the top. The default is 100. + +*fore* +: *optional* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), the colour of the chart. The default is **Green**. + +*back* +: *optional* A **ConsoleColor**, the background. The default is **Black**. + +*charSet* +: *optional* A [**SparklineCharSet**](#sparklinecharset). The default is **Braille**. + +Returns without drawing when the array has fewer than 2 values. + +### RenderWithStats +{: .no_toc } + +Draws a chart and a line with the smallest, the average and the largest value. + +Syntax: *object*.**RenderWithStats** ( *col* , *row* , *width* , *height* , *values* [ , *minValue* [ , *maxValue* [ , *fore* [ , *back* [ , *charSet* [ , *statColor* ] ] ] ] ] ] ) + +*col*, *row*, *width*, *height*, *values*, *minValue*, *maxValue*, *fore*, *back*, *charSet* +: As for [**Render**](#render). + +*statColor* +: *optional* A **ConsoleColor**, the colour of the text line. The default is **Gray**. + +Draws the chart with **Render**, then writes `Min:`, `Avg:` and `Max:` with the three numbers, each formatted with one decimal, in one line at the row `row + height + 1`, starting at *col*, on the background *back*. The numbers are taken from all the values, and not from *minValue* and *maxValue*. The row is the same for all three character sets, so a Braille chart, which is `(height + 3) \ 4` rows high, has a gap before the line. The decimal separator is that of the regional settings of the user. An array with no element raises error 9. + +## See Also + +- [cVBar](cVBar) class -- bar charts +- [cProgressBar](cProgressBar) class -- progress bars +- [cColorHelper](Helpers#ccolorhelper-class) -- colours chosen from values diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cVBar.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cVBar.md new file mode 100644 index 000000000..ffc2498c1 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cVBar.md @@ -0,0 +1,196 @@ +--- +title: cVBar +parent: TBMANLIB_ConsoleComponents Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/cVBar +has_toc: false +--- + +# cVBar class +{: .no_toc } + +Draws vertical bars into the screen buffer: a single bar, a row of bars from an array, bars coloured by value, a bar with a label, and a history of values that scrolls to the left. + +A **cVBar** keeps the [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer) of the console it was made with. Each bar is one column wide and *height* rows high, and grows from the bottom. The members draw into the buffer only; [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh) of the console shows it. + +```tb check_build +Dim con As New cConsole +Dim chart As cVBar +Dim ci As ConsoleCharInfo +Dim y As Long + +con.EnterFullScreenMode +Set chart = New cVBar(con) + +chart.Render 3, 0, 4, 50 ' a bar 4 rows high, half full +For y = 0 To 3 + ci = con.Buffer.GetChar(3, y) + Debug.Print y & " " & Hex$(AscW(ci.Char)) +Next +con.ExitFullScreenMode +' Output: +' 0 20 +' 1 20 +' 2 2588 +' 3 2588 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cVBar**. Columns and rows are counted from 0 in the buffer. + +## How a bar is drawn + +A bar of *height* rows is drawn in the rows *row* to *row* + *height* - 1 of one column. The number of filled rows is `CLng(percent / 100 * height)`, which rounds half to even, and a bar with a *percent* above 0 has at least one filled row. The filled rows are the bottom ones, drawn with the bar character in the colours *fore* on *back*. The rows above them are overwritten with spaces in the same colours, so a bar that is drawn again on top of a taller one clears it. + +A *percent* outside 0 to 100 is limited to the range. In the members that take *minValue* and *maxValue*, each value is first converted to a percentage with `100 * (value - minValue) / (maxValue - minValue)`; a *maxValue* equal to *minValue* raises error 11, *Division by zero*. + +A bar that is made before the console enters full-screen mode holds no buffer, and its first draw raises error 91, *Object variable or With block variable not set*; see [How a component works](./#how-a-component-works). + +## Enumeration + +### BarCharStyle +{: .no_toc } + +The character of the filled rows. The enumeration is declared in the class, and its members are written without the class, as in `BarCharStyle.Dark`. + +| Member | Value | Character | +|---|---:|---| +| **Block** | 0 | U+2588, a full block; the default | +| **Light** | 1 | U+2591, a light shade | +| **Medium** | 2 | U+2592, a medium shade | +| **Dark** | 3 | U+2593, a dark shade | +| **Custom** | 4 | U+2588, as **Block** | + +**Custom** is meant for a character chosen by the program, but no member has a parameter for one, so **Custom** draws the same character as **Block**. A value outside 0 to 4 leaves the character empty, and a bar with at least one filled row then raises error 5, *Invalid procedure call or argument*. + +## Constructor + +### New +{: .no_toc } + +Creates a bar chart for a console. + +Syntax: **New cVBar** ( *console* ) + +*console* +: *required* A [**cConsole**](../TBMANLIB_ConsoleCore/cConsole) that is in full-screen mode. + +Keeps the console and its current [**Buffer**](../TBMANLIB_ConsoleCore/cConsole#buffer). When the console is not in full-screen mode, the buffer is **Nothing** and every draw raises error 91. + +## Methods + +### Render +{: .no_toc } + +Draws one bar. + +Syntax: *object*.**Render** ( *col* , *row* , *height* , *percent* [ , *fore* [ , *back* [ , *charStyle* ] ] ] ) + +*col*, *row* +: *required* **Long** values, the column of the bar and the row of its top. + +*height* +: *required* A **Long**, the number of rows. + +*percent* +: *required* A **Double**, 0 to 100. + +*fore* +: *optional* A [**ConsoleColor**](../TBMANLIB_ConsoleCore/Types#consolecolor), the colour of the bar. The default is **Green**. + +*back* +: *optional* A **ConsoleColor**, the background. The default is **Black**. + +*charStyle* +: *optional* A [**BarCharStyle**](#barcharstyle). The default is **Block**. + +### RenderBars +{: .no_toc } + +Draws a bar for each value of an array, side by side. + +Syntax: *object*.**RenderBars** ( *col* , *row* , *height* , *values* [ , *spacing* [ , *minValue* [ , *maxValue* [ , *fore* [ , *back* [ , *charStyle* ] ] ] ] ] ] ) + +*col*, *row*, *height* +: *required* **Long** values: the column of the first bar, the row of the top of the bars, and their height. + +*values* +: *required* An array of **Double**, passed by reference. The bars follow the order of the array, from its lower bound to its upper bound. + +*spacing* +: *optional* A **Long**, the number of blank columns between two bars. The default is 1. The bars are `1 + spacing` columns apart; the blank columns are not written. + +*minValue*, *maxValue* +: *optional* **Double** values, the values that give an empty bar and a full bar. The defaults are 0 and 100. + +*fore*, *back*, *charStyle* +: *optional*, as for [**Render**](#render). + +The bar for the first value is in column *col*, and the next bars are at *col* + (`1 + spacing`), and so on. + +### RenderHistory +{: .no_toc } + +Draws a series of values as bars with no gap, the last value at the right. + +Syntax: *object*.**RenderHistory** ( *col* , *row* , *height* , *history* [ , *minValue* [ , *maxValue* [ , *fore* [ , *back* [ , *charStyle* ] ] ] ] ] ) + +*col*, *row*, *height* +: *required* **Long** values: the column of the last bar, the row of the top of the bars, and their height. + +*history* +: *required* An array of **Double**, passed by reference, that starts at index 0. The last element is the newest value. + +*minValue*, *maxValue*, *fore*, *back*, *charStyle* +: *optional*, as for [**RenderBars**](#renderbars). + +The last element of the array is drawn in column *col*, the one before it in column *col* - 1, and so on to the left, one column each, so *col* is the right end of the chart and not its start. Columns left of the buffer are dropped. Nothing is drawn for an array with no element. The drawing code addresses the values by position from 0, so an array with a lower bound of 1 raises error 9, *Subscript out of range*. A program that shows a moving history keeps its own array, shifts it by one value for each new reading, and draws it again. + +### RenderWithGradient +{: .no_toc } + +Draws a bar for each value, coloured by its value. + +Syntax: *object*.**RenderWithGradient** ( *col* , *row* , *height* , *values* [ , *spacing* [ , *minValue* [ , *maxValue* [ , *lowColor* [ , *midColor* [ , *highColor* [ , *back* [ , *charStyle* ] ] ] ] ] ] ] ] ) + +*col*, *row*, *height*, *values*, *spacing*, *minValue*, *maxValue* +: As for [**RenderBars**](#renderbars). + +*lowColor* +: *optional* A **ConsoleColor**, for bars below 50 percent. The default is **Green**. + +*midColor* +: *optional* A **ConsoleColor**, for bars from 50 percent up to, but not including, 80 percent. The default is **Yellow**. + +*highColor* +: *optional* A **ConsoleColor**, for bars of 80 percent and more. The default is **Red**. + +*back*, *charStyle* +: *optional*, as for [**Render**](#render). + +The colour of each bar is one of the three; there is no gradient between them. Note the order of the parameters: the three colours come before *back*, and there is no *fore*. + +### RenderWithLabel +{: .no_toc } + +Draws one bar and a label below it. + +Syntax: *object*.**RenderWithLabel** ( *col* , *row* , *height* , *percent* , *label* [ , *fore* [ , *back* [ , *charStyle* [ , *labelColor* ] ] ] ] ) + +*col*, *row*, *height*, *percent*, *fore*, *back*, *charStyle* +: As for [**Render**](#render). + +*label* +: *required* A **String**. + +*labelColor* +: *optional* A **ConsoleColor**, the colour of the label. The default is **Gray**. + +Draws the bar with **Render**, and writes the label in the row `row + height + 1`, which leaves one blank row under the bar. The label starts in column `col - Len(label) \ 2 + 1`, or in column 0 when that is negative. That is one column to the right of the column that would centre the label under the bar. + +## See Also + +- [cProgressBar](cProgressBar) class -- progress bars +- [cSparkline](cSparkline) class -- line charts +- [cColorHelper](Helpers#ccolorhelper-class) -- colours chosen from values diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md new file mode 100644 index 000000000..3cfb8162f --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md @@ -0,0 +1,103 @@ +--- +title: TBMANLIB_ConsoleComponents Package +parent: Experimental Packages +has_toc: false +indexed_from: 0.0.0.0 +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/ +--- + +# TBMANLIB_ConsoleComponents Package +{: .no_toc } + +**Author:** woeoio. + +The **TBMANLIB_ConsoleComponents** contributed package is a set of text-interface components for console programs. It has progress bars ([**cProgressBar**](cProgressBar)), small line charts ([**cSparkline**](cSparkline)), bar charts ([**cVBar**](cVBar)), a table ([**cDataGrid**](cDataGrid)), bordered panels ([**cConsolePanel**](cConsolePanel)), a manager that places panels in a grid, a sidebar or a header and footer ([**cConsoleLayout**](cConsoleLayout)), colour themes ([**cConsoleTheme**](cConsoleTheme)), and helpers for colours and positions ([**cColorHelper**, **cLayoutHelper**](Helpers)). The components draw into the screen buffer of the full-screen mode of [**cConsole**](../TBMANLIB_ConsoleCore/cConsole), in the package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/); the program then writes the buffer to the console with [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh). This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence declared. The package describes itself as "TBMAN: Text UI (TUI) component library base on ConsoleCore". + +> [!NOTE] +> The tools that check this documentation's samples run a sample and compare what it prints with **Debug.Print**. They cannot yet read what a program writes to a console. So the samples on these pages are compiled, and what the components draw on the console has not been checked by running them. Statements about what a component draws follow the package's source, and will be checked once the tools can capture a console. + +```tb check_build +Dim con As New cConsole +Dim layout As cConsoleLayout +Dim bar As cProgressBar +Dim theme As cConsoleTheme = mConsoleThemeFactory.CreateHTOP() + +con.EnterFullScreenMode ' the components need the buffer of this mode +Set layout = New cConsoleLayout(con) +Set bar = New cProgressBar(con) + +layout.AddPanel "main", 0, 0, con.Width, 5, "Copying" +layout.Render theme +bar.RenderWithLabel 2, 2, con.Width - 4, 62, "files" +con.Refresh + +con.ReadKey True +con.Restore +``` + +> [!WARNING] +> In version 0.0.0.0 the components that take a **cConsole** cannot be used with the published package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/). The package compiles, but the constructors of [**cProgressBar**](cProgressBar), [**cSparkline**](cSparkline), [**cVBar**](cVBar), [**cDataGrid**](cDataGrid), [**cConsoleLayout**](cConsoleLayout) and [**cLayoutHelper**](Helpers), and the members of [**cConsolePanel**](cConsolePanel) that take a **cConsoleBuffer**, raise error -2147467262, *No such interface supported*, when they are given an object of the published ConsoleCore, and a program cannot read the **ConsoleRect** and **ConsolePosition** values that the components return. [The two packages](#the-two-packages) has the details. Only [**cConsoleTheme**](cConsoleTheme), the module **mConsoleThemeFactory**, [**cColorHelper**](Helpers#ccolorhelper-class) and the geometry of **cConsolePanel** work. The outputs that the samples on these pages show are those of the code of the two packages compiled into one project, and not of a project that references the two packages. + +* TOC +{:toc} + +## Using the package + +### The two packages + +Like ConsoleCore, the package is meant for a console application; see [Which project it needs](../TBMANLIB_ConsoleCore/cConsole#which-project-it-needs). + +A component names types of ConsoleCore in its constructor and its parameters: **cConsole**, **cConsoleBuffer**, **ConsoleColor**, **ConsoleRect** and others. The references of a package are not visible to the project that uses it, so a program that creates a component, or names one of these types, references **TBMANLIB_ConsoleCore** as well as this package. Both are added as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. TWINSERV lists both packages at version 0.0.0.0. + +The project file of this package lists ConsoleCore as an embedded reference: a copy of version 1.0.0.0, named *New project*, with the identifier `{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA00}`. The published package is version 0.0.0.0, named TBMANLIB_ConsoleCore, with an identifier of its own. The six source files of the embedded copy are the same as those of the published package; its project data differs in the identifier, the name and the description, and it does not declare its package visibility as public. + +The components are compiled against the embedded copy, whose types are not those of the published package, although they have the same names. A project that references both therefore meets two sets of types, and this is the result: + +- **Objects.** Passing a **cConsole** or a **cConsoleBuffer** of the published package to a component compiles. At run time the call raises error -2147467262, *No such interface supported*: the constructors of **cProgressBar**, **cSparkline**, **cVBar**, **cDataGrid**, **cConsoleLayout** and **cLayoutHelper** do, and so do **Render** and **ClearContent** of **cConsolePanel**. +- **Structures.** A **ConsoleRect** or **ConsolePosition** that a component returns, such as the **Rect** field of **cConsolePanel**, or the result of **ContentRect**, **CenterRect**, **Center** or **Grid**, has the type of the embedded copy. The compiler refuses to read a member of it (TB5027, *[ConsoleRect] does not contain 'Left', or it is inaccessible in this context*) and to assign it to a **ConsoleRect** variable or pass it to **ConsoleRect_Contains** (TB5028, *Unable to convert type 'ConsoleRect' to type 'ConsoleRect'*). +- **Enumerations.** A **ConsoleColor** is a number, so the colour members work: **cConsoleTheme**, **mConsoleThemeFactory** and **cColorHelper** give and take colours, and **cConsolePanel** keeps its style fields. +- **Nested enumerations.** The enumerations of the components, such as **PanelBorderStyle**, are written without the class: `PanelBorderStyle.Double`. The form `cConsolePanel.PanelBorderStyle.Double` is refused (TB5079, *Unrecognized symbol 'cConsolePanel'*). + +A program that references only this package cannot create a **cConsole**, and a component cannot be created without one. The members that this documentation describes for the components are the members of the package's code; the sample outputs show what that code prints when it is compiled together with the code of ConsoleCore. + +### How a component works + +Every component keeps what its constructor was given and draws only into the buffer. The pattern for a drawing component is: + +1. Create a [**cConsole**](../TBMANLIB_ConsoleCore/cConsole) and call [**EnterFullScreenMode**](../TBMANLIB_ConsoleCore/cConsole#enterfullscreenmode). +2. Create the component with the console: `Set bar = New cProgressBar(con)`. +3. Call the component's `Render` members to draw into the buffer. +4. Call [**Refresh**](../TBMANLIB_ConsoleCore/cConsole#refresh) to show the buffer. + +**cProgressBar**, **cSparkline**, **cVBar** and **cDataGrid** take `console.Buffer` in their constructor and keep it. If the console is not in full-screen mode at that moment, **Buffer** is **Nothing**, the component holds **Nothing**, and its first draw raises error 91, *Object variable or With block variable not set*. A component keeps the buffer it was given: after [**ExitFullScreenMode**](../TBMANLIB_ConsoleCore/cConsole#exitfullscreenmode) and a new **EnterFullScreenMode** the console has a new buffer, and the component still draws into the old one, which the console no longer shows. [**cConsolePanel**](cConsolePanel) and the themes take no console, and [**cConsoleLayout**](cConsoleLayout) reads the buffer of the console each time it draws. + +The components do not clear the area they draw in, except where a page says so, do not check that what they draw lies inside the buffer (cells outside it are dropped), and do not flush. A bar drawn over earlier text leaves the rest of that text in place. + +Positions are columns and rows of the buffer, counted from 0 at the top left, in that order. A component draws on a black background unless a parameter sets another; the defaults of each member are in its entry. The bars, borders and charts use characters such as U+2588 and U+2500, so the font of the console has to contain them. + +## Defects in version 0.0.0.0 + +- [**cSparkline**](cSparkline#braille) with the Braille character set draws a chart taller than 4 dots with its character rows in reverse order, and draws nothing for a value equal to the minimum when the height is a multiple of 4. +- [**cDataGrid**](cDataGrid) raises error 9 in **Render** unless all four of its arrays are set; [**SetRowColors**](cDataGrid#setrowcolors) is indexed like the data array, header included; [**ScrollTo**](cDataGrid#scrollto) only selects a row and scrolls nothing; its properties can be set and not read. +- [**cConsoleLayout.Resize**](cConsoleLayout#resize) computes the scale factors after it stores the new size, so they are always 1 and no panel changes. [**AddPanel**](cConsoleLayout#addpanel) with a name in use raises error 457 after it has added the panel to the list, which then counts it. The first panel added becomes the root panel whatever method adds it. +- [**cConsoleLayout.SplitHorizontal**](cConsoleLayout#splithorizontal) makes a left and a right panel, and [**cLayoutHelper.SplitHorizontal**](Helpers#splithorizontal) makes a top and a bottom rectangle. +- [**cColorHelper.BrightenColor**](Helpers#brightencolor) returns 16 for **DarkGray**, which is not a **ConsoleColor**. [**DimColor**](Helpers#dimcolor) of a dark colour returns **DarkGray**. [**IntensityColor**](Helpers#intensitycolor) ignores its base colour, and [**GradientColor**](Helpers#gradientcolor) is a three-step choice, not a gradient. +- [**cVBar**](cVBar#barcharstyle) has a style, **Custom**, that draws the same character as **Block**, and [**cProgressBar.RenderWithBrackets**](cProgressBar#renderwithbrackets) draws no percentage. + +## Classes and modules + +- [cConsoleLayout](cConsoleLayout) -- a set of named panels, with splits, a grid, a sidebar and a header and footer +- [cConsolePanel](cConsolePanel) -- a rectangle with a border, a title and padding, and the content area inside it +- [cConsoleTheme](cConsoleTheme) -- a set of 14 named colours, and the three ready-made themes of **mConsoleThemeFactory** +- [cDataGrid](cDataGrid) -- a table of text with column widths, alignments and a selected row +- [cProgressBar](cProgressBar) -- horizontal and vertical progress bars +- [cSparkline](cSparkline) -- a small line or bar chart of a series of values +- [cVBar](cVBar) -- vertical bar charts, single, grouped and as a history +- [Helpers](Helpers) -- **cColorHelper** picks colours for values, and **cLayoutHelper** computes rectangles and positions in the console window + +## See Also + +- [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/) package -- the console, the screen buffer and the types that the components use +- [cConsole](../TBMANLIB_ConsoleCore/cConsole) class -- the full-screen mode and **Refresh** +- [cConsoleBuffer](../TBMANLIB_ConsoleCore/cConsoleBuffer) class -- the grid that the components draw into +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleCore/Types.md b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/Types.md new file mode 100644 index 000000000..c86a9ba03 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/Types.md @@ -0,0 +1,276 @@ +--- +title: TBMANLIB_ConsoleCore Types +parent: TBMANLIB_ConsoleCore Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/Types +has_toc: false +--- + +# TBMANLIB_ConsoleCore types +{: .no_toc } + +The enumerations, structures, helper functions and handler types that [**cConsole**](cConsole) and [**cConsoleBuffer**](cConsoleBuffer) use in their parameters and results. They are declared in two modules, **mConsoleTypes** and **mConsoleDelegates**, and are public: a member of an enumeration is written with its enumeration, as in `ConsoleColor.Red`, a structure is used by its name, and the four functions are written with the name of the module or without it. + +```tb check_build check_run +Dim r As ConsoleRect + +r = ConsoleRect_Create(2, 1, 11, 4) +Debug.Print ConsoleRect_Width(r) +Debug.Print ConsoleRect_Height(r) +Debug.Print ConsoleRect_Contains(r, 11, 4) +Debug.Print ConsoleRect_Contains(r, 12, 4) +' Output: +' 10 +' 4 +' True +' False +``` + +* TOC +{:toc} + +## Enumerations + +### ConsoleColor +{: .no_toc } + +The 16 colours of the console. The type of the colour parameters and properties of **cConsole** and **cConsoleBuffer**. + +| Member | Value | Member | Value | +|---|---:|---|---:| +| **Black** | 0 | **DarkGray** | 8 | +| **DarkBlue** | 1 | **Blue** | 9 | +| **DarkGreen** | 2 | **Green** | 10 | +| **DarkCyan** | 3 | **Cyan** | 11 | +| **DarkRed** | 4 | **Red** | 12 | +| **DarkMagenta** | 5 | **Magenta** | 13 | +| **DarkYellow** | 6 | **Yellow** | 14 | +| **Gray** | 7 | **White** | 15 | + +The value is the colour as the Windows console writes it in a colour attribute: bit 0 is blue, bit 1 is green, bit 2 is red, and bit 3 makes the colour brighter. **DarkYellow** (6) is green and red together, and **Gray** (7) is all three without the brightness. A cell takes the text colour in the low four bits of its attribute and the background colour in the next four. The numbers are not those of the ANSI escape sequences, where 1 is red and 4 is blue; the functions of [**mConsoleStyle**](mConsoleStyle) write those sequences. + +What a colour looks like depends on the colour table of the console, which the user can change. + +### ConsoleEventType +{: .no_toc } + +Kinds of console event: **EventNone** (0), **EventResize** (1), **EventKeyPress** (2), **EventMouseClick** (3), **EventFocus** (4), **EventCtrlC** (5), **EventCtrlBreak** (6) and **EventClose** (7). + +The enumeration is the type of the **EventType** field of [**ConsoleEventInfo**](#consoleeventinfo). No member of the package returns that structure or reads this enumeration. + +### ConsoleKey +{: .no_toc } + +Named key codes: **KeyEnter** (13), **KeyEscape** (27), **KeySpace** (32), **KeyBackspace** (8), **KeyTab** (9), **KeyUp** (72), **KeyDown** (80), **KeyLeft** (75), **KeyRight** (77), **KeyHome** (71), **KeyEnd** (79), **KeyPageUp** (73), **KeyPageDown** (81), **KeyInsert** (82), **KeyDelete** (83), **KeyF1** to **KeyF10** (59 to 68), **KeyF11** (133) and **KeyF12** (134). + +The **KeyCode** field of a [**ConsoleKeyInfo**](#consolekeyinfo) holds the Windows virtual-key code of the key. Only the first five members of the enumeration, **KeyEnter**, **KeyEscape**, **KeySpace**, **KeyBackspace** and **KeyTab**, are virtual-key codes. The others are the codes that the C library function `_getch` returns for the same keys, so a comparison such as `keyInfo.KeyCode = ConsoleKey.KeyUp` does not test for the Up Arrow key: the virtual-key code 72 is that of the H key. The Up Arrow key has the code 38, and the H key gives 72. + +For these keys, compare with the constants of Visual Basic: + +| Key | Compare with | Value | +|---|---|---:| +| Up, Down, Left, Right | `vbKeyUp`, `vbKeyDown`, `vbKeyLeft`, `vbKeyRight` | 38, 40, 37, 39 | +| Home, End | `vbKeyHome`, `vbKeyEnd` | 36, 35 | +| Page Up, Page Down | `vbKeyPageUp`, `vbKeyPageDown` | 33, 34 | +| Insert, Delete | `vbKeyInsert`, `vbKeyDelete` | 45, 46 | +| F1 to F12 | `vbKeyF1` to `vbKeyF12` | 112 to 123 | + +```tb check_build slot=module +Public Sub OnKey(ByRef keyInfo As ConsoleKeyInfo, ByRef handled As Boolean) + Select Case keyInfo.KeyCode + Case vbKeyUp, vbKeyDown + handled = True ' the navigation keys + Case ConsoleKey.KeyEnter, ConsoleKey.KeyEscape + handled = True ' these two members equal the virtual-key codes + End Select +End Sub +``` + +### ConsoleMode +{: .no_toc } + +Flags: **ModeNormal** (0), **ModeVirtualTerminal** (1), **ModeFullScreen** (2) and **ModeRaw** (4). No member of the package uses the enumeration; it is not the console mode that [**EnableVirtualTerminal**](cConsole#enablevirtualterminal) sets. + +### ConsoleTextStyle +{: .no_toc } + +Flags: **StyleNone** (0), **StyleBold** (1), **StyleUnderline** (2) and **StyleReverse** (4). The enumeration is the type of the **Style** field of [**ConsoleCharInfo**](#consolecharinfo), and of a variable inside **cConsole**. Nothing sets or reads it: **GetChar** always returns **StyleNone**. Text styles come from the escape sequences of [**mConsoleStyle**](mConsoleStyle). + +### ThemeColorType +{: .no_toc } + +The kinds of colour that a theme holds. It is declared in this package, and used by [**GetColor**](../TBMANLIB_ConsoleComponents/cConsoleTheme#getcolor) of the ConsoleComponents package. + +| Member | Value | Member | Value | +|---|---:|---|---:| +| **tcPrimary** | 1 | **tcBorder** | 10 | +| **tcSecondary** | 2 | **tcHeaderBackground** | 11 | +| **tcSuccess** | 3 | **tcHeaderForeground** | 12 | +| **tcWarning** | 4 | **tcSelectedBackground** | 13 | +| **tcError** | 5 | **tcSelectedForeground** | 14 | +| **tcInfo** | 6 | | | +| **tcMuted** | 7 | | | +| **tcBackground** | 8 | | | +| **tcForeground** | 9 | | | + +## Structures + +### ConsoleCharInfo +{: .no_toc } + +One cell of a [**cConsoleBuffer**](cConsoleBuffer), as [**GetChar**](cConsoleBuffer#getchar) returns it. + +| Field | Type | Meaning | +|---|---|---| +| **Char** | **String** | The character, one UTF-16 code unit; an empty string for a position outside the buffer. | +| **ForeColor** | [**ConsoleColor**](#consolecolor) | The text colour. | +| **BackColor** | [**ConsoleColor**](#consolecolor) | The background colour. | +| **Style** | [**ConsoleTextStyle**](#consoletextstyle) | Always 0. | + +### ConsoleEventInfo +{: .no_toc } + +A structure for an event: **EventType** ([**ConsoleEventType**](#consoleeventtype)), **KeyInfo** ([**ConsoleKeyInfo**](#consolekeyinfo)), **MouseInfo** ([**ConsoleMouseInfo**](#consolemouseinfo)) and **NewSize** ([**ConsoleSize**](#consolesize)). No member of the package uses it. + +### ConsoleKeyInfo +{: .no_toc } + +A key press, as [**ReadKey**](cConsole#readkey), [**PeekEvent**](cConsole#peekevent) and the handler of [**OnKeyPress**](cConsole#onkeypress) deliver it. + +| Field | Type | Meaning | +|---|---|---| +| **KeyCode** | **Long** | The Windows virtual-key code of the key. See [**ConsoleKey**](#consolekey). | +| **KeyChar** | **String** | The character that the key produced. A key without a character, such as an arrow key or Shift, gives the character with code 0. An empty structure, such as the result of a timeout, has an empty string. | +| **IsAltPressed** | **Boolean** | Alt, left or right, was down. | +| **IsCtrlPressed** | **Boolean** | Ctrl, left or right, was down. | +| **IsShiftPressed** | **Boolean** | Shift was down. | + +### ConsoleMouseInfo +{: .no_toc } + +A mouse event, as the handler of [**OnMouseClick**](cConsole#onmouseclick) receives it. + +| Field | Type | Meaning | +|---|---|---| +| **Col**, **Row** | **Long** | The character cell under the pointer, counted from the top left of the screen buffer. | +| **Button** | **Long** | The `dwButtonState` value of the Windows event: a bit mask of the buttons that are down, 1 for the left button, 2 for the right button and 4 for the second button from the left, and 0 when none is down. For a turn of the wheel, Windows puts the direction and the distance of the turn in the upper 16 bits. | +| **IsDoubleClick** | **Boolean** | The event carries the double-click flag. | + +### ConsolePosition +{: .no_toc } + +A position: **Col** and **Row**, both **Long**, counted from 0. [**GetCursorPosition**](cConsole#getcursorposition) returns one. + +### ConsoleRect +{: .no_toc } + +A rectangle of cells: **Left**, **Top**, **Right** and **Bottom**, all **Long**. The edges are inclusive, so the rectangle is `Right - Left + 1` cells wide and `Bottom - Top + 1` high. [**CenterRect**](cConsole#centerrect) and [**GetDirtyRect**](cConsoleBuffer#getdirtyrect) return one, and the layout members of the ConsoleComponents package use it. + +### ConsoleSize +{: .no_toc } + +A size: **Width** and **Height**, both **Long**, in cells. [**GetSize**](cConsole#getsize) and [**GetBufferSize**](cConsole#getbuffersize) return one. + +## Functions + +The functions are members of the module **mConsoleTypes**. They work on a [**ConsoleRect**](#consolerect), and are the only code of the module. + +### ConsoleRect_Contains +{: .no_toc } + +Tells whether a point lies in a rectangle. + +Syntax: **ConsoleRect_Contains** ( *rect* , *x* , *y* ) **As Boolean** + +*rect* +: *required* A [**ConsoleRect**](#consolerect). + +*x*, *y* +: *required* **Long** values, the column and the row of the point. + +Returns **True** when `x >= rect.Left`, `x <= rect.Right`, `y >= rect.Top` and `y <= rect.Bottom`; the edges belong to the rectangle. + +### ConsoleRect_Create +{: .no_toc } + +Makes a rectangle from four numbers. + +Syntax: **ConsoleRect_Create** ( *left* , *top* , *right* , *bottom* ) **As ConsoleRect** + +*left*, *top*, *right*, *bottom* +: *required* **Long** values, the edges, inclusive. Nothing is checked: a rectangle with *right* less than *left* is returned as given. + +### ConsoleRect_Height +{: .no_toc } + +Returns the height of a rectangle. + +Syntax: **ConsoleRect_Height** ( *rect* ) **As Long** + +*rect* +: *required* A [**ConsoleRect**](#consolerect). + +Returns `rect.Bottom - rect.Top + 1`. + +### ConsoleRect_Width +{: .no_toc } + +Returns the width of a rectangle. + +Syntax: **ConsoleRect_Width** ( *rect* ) **As Long** + +*rect* +: *required* A [**ConsoleRect**](#consolerect). + +Returns `rect.Right - rect.Left + 1`. + +## Handler types + +The six types of the module **mConsoleDelegates** are delegates: the type of the address of a procedure. A program passes `AddressOf` and the name of a procedure of a module whose parameters match the type exactly. Four of them are the types of the handler properties of [**cConsole**](cConsole); the other two are not used by any member of the package. + +### ConsoleCtrlHandler +{: .no_toc } + +Syntax: **Delegate Function ConsoleCtrlHandler** ( **ByVal** *ctrlType* **As Long** ) **As Long** + +The type of [**OnCtrlEvent**](cConsole#onctrlevent). Windows calls the procedure with the kind of event, on a thread of its own. + +### ConsoleKeyHandler +{: .no_toc } + +Syntax: **Delegate Sub ConsoleKeyHandler** ( **ByRef** *keyInfo* **As ConsoleKeyInfo** , **ByRef** *handled* **As Boolean** ) + +The type of [**OnKeyPress**](cConsole#onkeypress). *keyInfo* is the [**ConsoleKeyInfo**](#consolekeyinfo) of the key. The package does not read *handled* after the call. + +### ConsoleMouseHandler +{: .no_toc } + +Syntax: **Delegate Sub ConsoleMouseHandler** ( **ByRef** *mouseInfo* **As ConsoleMouseInfo** , **ByRef** *handled* **As Boolean** ) + +The type of [**OnMouseClick**](cConsole#onmouseclick). + +### ConsoleRenderCallback +{: .no_toc } + +Syntax: **Delegate Sub ConsoleRenderCallback** ( **ByVal** *buffer* **As cConsoleBuffer** ) + +Not used by any member. Meant, by its comment, as a procedure that draws in full-screen mode. + +### ConsoleResizeHandler +{: .no_toc } + +Syntax: **Delegate Sub ConsoleResizeHandler** ( **ByVal** *newWidth* **As Long** , **ByVal** *newHeight* **As Long** ) + +The type of [**OnResize**](cConsole#onresize). + +### ConsoleTimerCallback +{: .no_toc } + +Syntax: **Delegate Sub ConsoleTimerCallback** ( ) + +Not used by any member. Meant, by its comment, as a timer procedure. + +## See Also + +- [cConsole](cConsole) class -- the members that use these types +- [cConsoleBuffer](cConsoleBuffer) class +- [mConsoleStyle](mConsoleStyle) module -- text styles as escape sequences diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsole.md b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsole.md new file mode 100644 index 000000000..8154ab1ab --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsole.md @@ -0,0 +1,1002 @@ +--- +title: cConsole +parent: TBMANLIB_ConsoleCore Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/cConsole +has_toc: false +--- + +# cConsole class +{: .no_toc } + +Controls the console of a program: it writes text in colour, reads lines and keys, moves the cursor, changes the code pages and the virtual-terminal mode, and runs a full-screen mode that draws through a screen buffer and reports keyboard, mouse and window events. + +A **cConsole** works on the standard input, output and error handles of the program, which it asks Windows for when it is created. Nearly every member is a thin layer over one or two functions of the Windows console API, and does not check whether the call worked. Each entry below names the functions it calls. + +```tb check_build +Dim con As New cConsole +con.EnableUTF8 +con.EnableVirtualTerminal + +con.Title = "Report" +con.WriteLineColored "Done", ConsoleColor.Green +con.WriteAtColored 0, 2, "caf" & ChrW$(233), ConsoleColor.Yellow +con.WriteLine +con.Restore +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cConsole**. The page uses these terms: the *window* is the part of the screen buffer that the console window shows, and the *screen buffer* is the whole grid of character cells that Windows keeps for the console, which can be taller than the window. Columns and rows are counted from 0, and a position is a column and a row, in that order. + +## Creating a cConsole + +A **cConsole** is created with `New`. [**New**](#new) reads the standard handles and records the state that [**Restore**](#restore) puts back: the input and output modes, the colour attributes and the two code pages. + +The class is predeclared, so the name `cConsole` can also be used as an object, as in `cConsole.WriteLine "Hello"`, without `New`. + +A program should create one **cConsole**. Each object records the console state at the moment it is created, and puts that state back when it is restored. A second object created after the first one has changed the modes records the changed modes as the original ones. + +The object restores the console when it is destroyed: its terminate event calls [**Restore**](#restore). A program that must put the console back at once, for example before it starts another program, calls **Restore** itself. + +## Which project it needs + +The package is meant for a console application: a project with [*Is Console Application*](../../../IDE/Project-Settings#is-console-application) set to **Yes**, as the **Standard EXE (Console App)** template does (see [Console Applications](../../../../Features/Project-Configuration/Project-Types#console-applications)). Such a program runs in the Command Prompt window it was started from, or in a console window of its own, and Windows gives it the three standard handles that **cConsole** uses. + +A program with a window and no console can create a **cConsole**, but every call that reaches Windows fails; see [Without a console](#without-a-console). Such a program can call `AllocConsole`, which the module `mConsoleAPI` declares, before it creates the object: the program then has a console of its own, and the write members show their text in it. + +## Without a console + +The members that read or draw on the console depend on Windows handing the program a console. A program built as a GUI program has no standard handles: `GetStdHandle` returns 0 for all three, and Windows refuses each console call that **cConsole** makes. + +The class checks the result of two functions only. [**EnableVirtualTerminal**](#enablevirtualterminal) returns **False** when `SetConsoleMode` fails for the output handle, and [**New**](#new), [**Clear**](#clear), [**ClearLine**](#clearline), [**GetSize**](#getsize), [**GetBufferSize**](#getbuffersize) and [**GetCursorPosition**](#getcursorposition) look at the result of `GetConsoleScreenBufferInfo`. Every other call is unchecked: a failed call raises no error, changes nothing, and leaves the value it should have filled in at its starting value, which is 0, **False** or an empty string. The table lists what a program without a console sees. + +| Member | Without a console | +|---|---| +| [**New**](#new) | Succeeds. The size is taken as 80 columns by 25 rows, and the original colours as Gray on Black. | +| [**WriteTo**](#writeto) and the other write members | Write nothing. | +| [**Width**](#width), [**Height**](#height), [**GetSize**](#getsize) | 80 and 25. | +| [**GetBufferSize**](#getbuffersize) | 80 by 300. | +| [**GetCursorPosition**](#getcursorposition) | Column 0, row 0. | +| [**CursorVisible**](#cursorvisible), [**CursorSize**](#cursorsize) | **False** and 0. | +| [**Title**](#title) | An empty string. | +| [**WindowHandle**](#windowhandle) | 0. | +| [**InputCodePage**](#inputcodepage), [**OutputCodePage**](#outputcodepage) | 0. | +| [**EnableVirtualTerminal**](#enablevirtualterminal) | **False**. | +| [**ReadLine**](#readline) | An empty string, at once. | +| [**KeyAvailable**](#keyavailable), [**PeekEvent**](#peekevent) | **False**, and an empty [**ConsoleKeyInfo**](Types#consolekeyinfo). | +| [**ReadKeyTimeout**](#readkeytimeout) | Returns the empty structure when the time is over. | +| [**ReadKey**](#readkey), [**ReadPassword**](#readpassword) | Do not return; see [Known problems](#known-problems-in-version-0000). | +| [**EnterFullScreenMode**](#enterfullscreenmode) | Creates the 80 by 25 [**cConsoleBuffer**](cConsoleBuffer); [**Refresh**](#refresh) writes nothing. | +| [**IsKeyPressed**](#iskeypressed) | Works, since it does not use the console. | + +A buffer made by [**EnterFullScreenMode**](#enterfullscreenmode) is an ordinary in-memory grid, so a program without a console can still draw into it and read the cells back with [**GetChar**](cConsoleBuffer#getchar). + +### Redirected input and output + +A console application can also have its output sent to a file or a pipe (`mytool > out.txt`) or its input read from one. The members write and read with `WriteConsoleW`, `ReadConsoleW` and `ReadConsoleInputW`, which work only on a console handle. With the output redirected, the write members therefore write nothing: after [**WriteLine**](#writeline) and [**WriteErrorLine**](#writeerrorline) the files are empty. They raise no error, since they do not check the result; the template's `Console.WriteLine` raises error 5 in the same case (see [Writing a command-line tool](../../../../Features/Project-Configuration/Project-Types#writing-a-command-line-tool-output-exit-code-and-arguments)). The members that need a screen buffer fail as they do without a console: [**GetSize**](#getsize) gives 80 by 25 and [**EnableVirtualTerminal**](#enablevirtualterminal) returns **False**. + +A program that must work with redirected output writes with `WriteFile` in that case, as the page cited above shows, and uses **cConsole** only when the handles are console handles. + +With the input read from the null device or from a file, [**ReadLine**](#readline) returns an empty string at once and [**KeyAvailable**](#keyavailable) is **False**. [**ReadKey**](#readkey) does not return. + +## Known problems in version 0.0.0.0 + +> [!WARNING] +> In version 0.0.0.0, [**ReadKey**](#readkey) and [**ReadPassword**](#readpassword) never return when the program has no console input: `ReadKey` loops until `ReadConsoleInputW` delivers a key-down event, and the call fails at once when the program has no console or its input is not a console. A comparison of a key with a member of the [**ConsoleKey**](Types#consolekey) enumeration is true for the wrong key and raises no error: [**KeyCode**](Types#consolekeyinfo) holds Windows virtual-key codes, but the arrow, navigation and function keys of **ConsoleKey** have other values, so `keyInfo.KeyCode = ConsoleKey.KeyUp` is false for the Up Arrow key (38) and true for the H key (72). + +- **KeyAvailable and the key-up events.** [**KeyAvailable**](#keyavailable) is true while the input queue holds any event, including the event for a key that was released, so a loop that tests it and then calls [**ReadKey**](#readkey) can block in **ReadKey** until the next key press. +- **The colours of the Write members.** [**WriteColored**](#writecolored), [**WriteLineColored**](#writelinecolored) and [**WriteAtColored**](#writeatcolored) use a black background unless one is given, and put back the colours that the object tracks, which are Gray on Black until a property sets others, and not the colours that the console had. +- **Full-screen drawing.** [**Refresh**](#refresh) writes the buffer at the top left of the screen buffer, not of the window; see [**EnterFullScreenMode**](#enterfullscreenmode). A negative column or row in the buffer's dirty rectangle makes the next **Refresh** draw nothing; see [The dirty rectangle](cConsoleBuffer#the-dirty-rectangle). +- **The handlers.** A handler set with [**OnCtrlEvent**](#onctrlevent) cannot be removed again, and the compiler refuses `0` and **Nothing** for the other three handler properties, so none can be cleared. +- **The resize sizes.** [**OnResize**](#onresize) is given the size of the screen buffer, and not of the window that [**Width**](#width) and [**Height**](#height) report. + +## Constructor + +### New +{: .no_toc } + +Creates a **cConsole** and records the state of the console. + +Syntax: **New cConsole** [ ( *inputBufferSize* ) ] + +*inputBufferSize* +: *optional* A **Long**, the number of characters that [**ReadLine**](#readline) can read in one call. The default is 4096. + +The constructor asks Windows for the three standard handles and for the console window, saves the input and output modes, the colour attributes, the width of the screen buffer and the height of the window, and the two code pages, and sets the tracked colours to Gray on Black. A negative *inputBufferSize* raises error 5, *Invalid procedure call or argument*, since the constructor makes the read buffer with `String$`. + +Calls `GetStdHandle` (three times), `GetConsoleWindow`, `GetConsoleMode` (twice), `GetConsoleScreenBufferInfo`, `GetConsoleCP` and `GetConsoleOutputCP`. Without a console, the size is taken as 80 by 25 and the original colours as Gray on Black. + +## Properties + +### BackColor +{: .no_toc } + +The background colour that the object tracks, and that the write members use. A [**ConsoleColor**](Types#consolecolor). + +Syntax: *object*.**BackColor** [ **=** *value* ] + +*value* +: A [**ConsoleColor**](Types#consolecolor). + +Setting the property stores the value and applies the pair of [**ForeColor**](#forecolor) and **BackColor** to the console. Reading it returns the stored value, which is **Black** at the start, and not a colour read from the console. + +Calls `SetConsoleTextAttribute` when it is set. Without a console, nothing is applied. + +### Buffer +{: .no_toc } + +The screen buffer of the full-screen mode. A [**cConsoleBuffer**](cConsoleBuffer), read-only. + +Syntax: *object*.**Buffer** + +Returns the buffer that [**EnterFullScreenMode**](#enterfullscreenmode) made, and **Nothing** when the object is not in full-screen mode. A component object of the [ConsoleComponents](../TBMANLIB_ConsoleComponents/) package keeps the buffer that this property returned when it was created, so it must be created after **EnterFullScreenMode**. + +### CursorSize +{: .no_toc } + +The height of the cursor, as a percentage of the character cell. **Long**, read/write. + +Syntax: *object*.**CursorSize** [ **=** *value* ] + +*value* +: A **Long**. A value below 1 is stored as 1, and a value above 100 as 100. + +Calls `GetConsoleCursorInfo`, and `SetConsoleCursorInfo` when it is set. Without a console, reading it gives 0. + +### CursorVisible +{: .no_toc } + +Whether the cursor is shown. **Boolean**, read/write. + +Syntax: *object*.**CursorVisible** [ **=** *value* ] + +*value* +: A **Boolean**. + +Reading the property asks the console each time. [**EnterFullScreenMode**](#enterfullscreenmode) hides the cursor and [**ExitFullScreenMode**](#exitfullscreenmode) shows it, whatever it was before. + +Calls `GetConsoleCursorInfo`, and `SetConsoleCursorInfo` when it is set. Without a console, reading it gives **False**. + +### ForeColor +{: .no_toc } + +The text colour that the object tracks, and that the write members use. A [**ConsoleColor**](Types#consolecolor). + +Syntax: *object*.**ForeColor** [ **=** *value* ] + +*value* +: A [**ConsoleColor**](Types#consolecolor). + +Works as [**BackColor**](#backcolor). The stored value is **Gray** at the start. Calls `SetConsoleTextAttribute` when it is set. + +### FPS +{: .no_toc } + +An estimate of the frames drawn per second. **Double**, read-only. + +Syntax: *object*.**FPS** + +The value is the number of calls of [**Refresh**](#refresh) and [**RefreshAll**](#refreshall) since the start of the current count, multiplied by 1000 and divided by the milliseconds since then. It is 0 before the first call. **Refresh** starts a new count when it finds that a second has passed since the start of the count, and **RefreshAll** only adds to the count, so a program that calls only **RefreshAll** gets the average since its first call. When the program stops drawing, the value falls, since the time keeps running. The clock is `GetTickCount`, whose steps are about 15 milliseconds. + +### FullScreenMode +{: .no_toc } + +Whether the object is in full-screen mode. **Boolean**, read-only. + +Syntax: *object*.**FullScreenMode** + +**True** between [**EnterFullScreenMode**](#enterfullscreenmode) and [**ExitFullScreenMode**](#exitfullscreenmode). + +### Height +{: .no_toc } + +The number of rows of the console window. **Long**, read-only. + +Syntax: *object*.**Height** + +The height of the window, and not of the screen buffer; [**GetBufferSize**](#getbuffersize) returns that. Each read calls [**GetSize**](#getsize). Without a console it is 25. + +### InputCodePage +{: .no_toc } + +The code page that the console uses for input. **Long**, read/write. + +Syntax: *object*.**InputCodePage** [ **=** *value* ] + +*value* +: A **Long**, a code page number such as 65001 for UTF-8. + +Calls `GetConsoleCP`, and `SetConsoleCP` when it is set. A value that Windows refuses is ignored without an error. [**Restore**](#restore) and [**DisableUTF8**](#disableutf8) put back the code page that the console had when the object was created. Without a console, reading it gives 0. + +### LastRenderTime +{: .no_toc } + +The time that the last [**Refresh**](#refresh) or [**RefreshAll**](#refreshall) took, in milliseconds. **Long**, read-only. + +Syntax: *object*.**LastRenderTime** + +The value is the difference of two `GetTickCount` readings, so it is a multiple of the clock step, about 15 milliseconds, and is often 0 for a small draw. It is 0 until the first draw. + +### OnCtrlEvent +{: .no_toc } + +Sets the procedure that Windows calls when the user presses Ctrl+C or Ctrl+Break, closes the console window, logs off or shuts down. Write-only. + +Syntax: *object*.**OnCtrlEvent** **=** *handler* + +*handler* +: A [**ConsoleCtrlHandler**](Types#consolectrlhandler), the address of a procedure of a module with one **Long** parameter that returns a **Long**, as in `con.OnCtrlEvent = AddressOf CtrlHandler`. + +The procedure receives the kind of event in *ctrlType*, one of the constants `CTRL_C_EVENT` (0), `CTRL_BREAK_EVENT` (1), `CTRL_CLOSE_EVENT` (2), `CTRL_LOGOFF_EVENT` (5) and `CTRL_SHUTDOWN_EVENT` (6) of the module `mConsoleAPI`. It returns a non-zero value to say that it handled the event, and 0 to pass the event to the next handler; the default handler of Windows ends the program. + +```tb check_build slot=module +Public Function CtrlHandler(ByVal ctrlType As Long) As Long + If ctrlType = CTRL_C_EVENT Then + CtrlHandler = 1 ' handled: the program goes on + End If +End Function + +Public Sub Setup(ByVal con As cConsole) + con.OnCtrlEvent = AddressOf CtrlHandler +End Sub +``` + +Windows calls the procedure on a thread of its own, not on the thread that runs the program, so it should do little: set a flag that the main loop reads. + +The property registers the address with `SetConsoleCtrlHandler`. The registration is not removed again: assigning another procedure adds a second registration and leaves the first, [**Restore**](#restore) does not remove it, and the code never calls `SetConsoleCtrlHandler` with the remove option. [**ProcessEvents**](#processevents) does not call the procedure; only Windows does. + +### OnKeyPress +{: .no_toc } + +Sets the procedure that [**ProcessEvents**](#processevents) calls for each key that is pressed. Write-only. + +Syntax: *object*.**OnKeyPress** **=** *handler* + +*handler* +: A [**ConsoleKeyHandler**](Types#consolekeyhandler), the address of a procedure of a module with the parameters `ByRef keyInfo As ConsoleKeyInfo, ByRef handled As Boolean`. + +**ProcessEvents** calls the procedure once for each key-down event, with the key in *keyInfo*, including the keys that are only modifiers, such as Shift. The key-up events are not passed. The value that the procedure stores in *handled* is not read. While no handler is set, **ProcessEvents** leaves the key events in the queue, so that [**ReadKey**](#readkey) and [**ReadLine**](#readline) can read them. Once a handler is set it cannot be removed: the compiler refuses `0` and **Nothing** for the property. + +```tb check_build slot=module +Public Sub OnKey(ByRef keyInfo As ConsoleKeyInfo, ByRef handled As Boolean) + If keyInfo.IsCtrlPressed And keyInfo.KeyCode = vbKeyQ Then + handled = True + End If +End Sub + +Public Sub Setup(ByVal con As cConsole) + con.OnKeyPress = AddressOf OnKey +End Sub +``` + +### OnMouseClick +{: .no_toc } + +Sets the procedure that [**ProcessEvents**](#processevents) calls for each mouse event. Write-only. + +Syntax: *object*.**OnMouseClick** **=** *handler* + +*handler* +: A [**ConsoleMouseHandler**](Types#consolemousehandler), the address of a procedure of a module with the parameters `ByRef mouseInfo As ConsoleMouseInfo, ByRef handled As Boolean`. + +Setting a handler also changes the input mode of the console: it turns on mouse input, turns on the extended flags, and turns off quick-edit mode, so that the console passes mouse events to the program instead of selecting text with them. [**DisableVirtualTerminal**](#disablevirtualterminal) and [**Restore**](#restore) put the original mode back, and so does [**ExitFullScreenMode**](#exitfullscreenmode) while the full-screen mode is on. + +Despite the name, the procedure is called for every mouse event: a move, a button press, a button release and a turn of the wheel, as well as a double click. *mouseInfo* holds the position in **Col** and **Row**, the buttons that are down in **Button** (a bit mask of the Windows `dwButtonState` field: 1 for the left button, 2 for the right button, and 0 when none is down), and **IsDoubleClick**, which is true when the event carries the double-click flag. The program has to compare **Button** with its previous value to find a click. + +### OnResize +{: .no_toc } + +Sets the procedure that [**ProcessEvents**](#processevents) calls when the size of the console changes. Write-only. + +Syntax: *object*.**OnResize** **=** *handler* + +*handler* +: A [**ConsoleResizeHandler**](Types#consoleresizehandler), the address of a procedure of a module with the parameters `ByVal newWidth As Long, ByVal newHeight As Long`. + +Windows reports a change of the size only while the input mode has the window-input flag, which [**EnterFullScreenMode**](#enterfullscreenmode) turns on and nothing else in the class does. Outside full-screen mode the handler is not called. When an event arrives and the size differs from the one the object last saw, the object resizes its buffer to it, which discards the contents of the buffer, and then calls the handler; the program draws the buffer again. + +The two numbers are copied from the event record, which holds the new size of the screen buffer. They are not read from the window, whose size [**Width**](#width) and [**Height**](#height) report, and the two can differ, since the screen buffer can be taller than the window. + +### OutputCodePage +{: .no_toc } + +The code page that the console uses for output. **Long**, read/write. + +Syntax: *object*.**OutputCodePage** [ **=** *value* ] + +*value* +: A **Long**, a code page number such as 65001 for UTF-8. + +Works as [**InputCodePage**](#inputcodepage), with `GetConsoleOutputCP` and `SetConsoleOutputCP`. + +### Title +{: .no_toc } + +The title of the console window. **String**, read/write. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. + +Reading it returns up to 259 characters. Calls `GetConsoleTitleW`, and `SetConsoleTitleW` when it is set. Without a console, reading it gives an empty string and setting it does nothing. + +### VirtualTerminalEnabled +{: .no_toc } + +Whether [**EnableVirtualTerminal**](#enablevirtualterminal) turned the virtual-terminal mode on. **Boolean**, read-only. + +Syntax: *object*.**VirtualTerminalEnabled** + +The value comes from the object and not from the console. It is **False** when the console had the mode on already, until the program calls **EnableVirtualTerminal**. + +### Width +{: .no_toc } + +The number of columns of the console window. **Long**, read-only. + +Syntax: *object*.**Width** + +The width of the window, and not of the screen buffer. Each read calls [**GetSize**](#getsize). Without a console it is 80. + +### WindowHandle +{: .no_toc } + +The window handle of the console, as `GetConsoleWindow` returned it when the object was created. **LongPtr**, read-only. + +Syntax: *object*.**WindowHandle** + +It is 0 when the program had no console window at that time. + +## Methods + +### Beep +{: .no_toc } + +Writes the bell character, code 7. + +Syntax: *object*.**Beep** + +The method calls [**WriteTo**](#writeto) with `Chr$(7)`. Whether the console makes a sound, and which, is the console's choice; the method does not call the Windows `Beep` function. + +### CenterRect +{: .no_toc } + +Returns a rectangle of a given size, centred in the window. + +Syntax: *object*.**CenterRect** ( *contentWidth* , *contentHeight* ) **As ConsoleRect** + +*contentWidth* +: *required* A **Long**, the width in columns. + +*contentHeight* +: *required* A **Long**, the height in rows. + +Returns a [**ConsoleRect**](Types#consolerect) whose **Left** is [**CenterX**](#centerx) of the width and **Top** is [**CenterY**](#centery) of the height, with **Right** and **Bottom** set so that it has the given size. A rectangle larger than the window has negative **Left** and **Top**. Uses the size of the window, 80 by 25 without a console. + +### CenterX +{: .no_toc } + +Returns the column at which text of a given width is centred in the window. + +Syntax: *object*.**CenterX** ( *textWidth* ) + +*textWidth* +: *required* A **Long**, the width in columns. + +Returns `(Width - textWidth) \ 2`, a **Long**; it is negative when the text is wider than the window. + +### CenterY +{: .no_toc } + +Returns the row at which content of a given height is centred in the window. + +Syntax: *object*.**CenterY** ( *textHeight* ) + +*textHeight* +: *required* A **Long**, the height in rows. + +Returns `(Height - textHeight) \ 2`, a **Long**. + +### Clear +{: .no_toc } + +Fills the screen buffer with spaces in the tracked colours and moves the cursor to the top left. + +Syntax: *object*.**Clear** + +The method fills the whole screen buffer, not only the part that the window shows, with a space in the colours of [**ForeColor**](#forecolor) and [**BackColor**](#backcolor), and then moves the cursor to column 0, row 0 of the screen buffer. When the window shows another part of the buffer, the cursor moves out of sight. In full-screen mode it does not clear the [**Buffer**](#buffer); [**Clear**](cConsoleBuffer#clear) of the buffer does that. + +Calls `GetConsoleScreenBufferInfo`, `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute` and `SetConsoleCursorPosition`. Without a console, the first call fails and the method returns at once. + +### ClearLine +{: .no_toc } + +Fills the current line, from the cursor to the end of the line, with spaces in the tracked colours. + +Syntax: *object*.**ClearLine** + +The cursor does not move. Calls `GetConsoleScreenBufferInfo`, `FillConsoleOutputCharacterW` and `FillConsoleOutputAttribute`. Without a console, the method returns at once. + +### DisableUTF8 +{: .no_toc } + +Puts back the code pages that the console had when the object was created. + +Syntax: *object*.**DisableUTF8** + +Despite its name, the method does not switch to a particular code page: it sets the input and the output code page to the saved values, whatever they are now. Calls `SetConsoleCP` and `SetConsoleOutputCP`. + +### DisableVirtualTerminal +{: .no_toc } + +Puts back the input and output modes that the console had when the object was created. + +Syntax: *object*.**DisableVirtualTerminal** + +The method restores both modes as they were, so it also undoes other changes of the input mode, such as the window input that [**EnterFullScreenMode**](#enterfullscreenmode) turns on and the mouse input that [**OnMouseClick**](#onmouseclick) turns on. It sets [**VirtualTerminalEnabled**](#virtualterminalenabled) to **False**. Calls `SetConsoleMode` twice. + +### DrawBox +{: .no_toc } + +Draws a rectangle of line-drawing characters directly on the console. + +Syntax: *object*.**DrawBox** ( *left* , *top* , *right* , *bottom* [ , *doubleLine* ] ) + +*left*, *top* +: *required* **Long** values, the column and the row of the upper left corner. + +*right*, *bottom* +: *required* **Long** values, the column and the row of the lower right corner. + +*doubleLine* +: *optional* A **Boolean**. **True** draws with the double-line characters (U+2554, U+2557, U+255A, U+255D, U+2550, U+2551), and **False**, the default, with the single-line ones (U+250C, U+2510, U+2514, U+2518, U+2500, U+2502). + +The box is drawn with [**WriteAt**](#writeat), in the current colour attributes of the console; the inside is left as it is. *right* has to be greater than *left*: for *right* equal to or less than *left* the method raises error 5, *Invalid procedure call or argument*, since the length of the horizontal line is negative, and it raises it after it has written the four corners. + +The method draws on the console, not on the full-screen [**Buffer**](#buffer). To draw a box on the buffer use [**DrawBox**](cConsoleBuffer#drawbox) of the buffer, which takes the colours as parameters. + +Calls `SetConsoleCursorPosition` and `WriteConsoleW`, through **WriteAt**. A position that is not in the screen buffer is ignored by Windows. Without a console, nothing is drawn. + +### DrawProgressBar +{: .no_toc } + +Draws a bar with a percentage directly on the console. + +Syntax: *object*.**DrawProgressBar** ( *col* , *row* , *width* , *percent* [ , *usedColor* [ , *freeColor* ] ] ) + +*col*, *row* +: *required* **Long** values, the column and the row of the left bracket. + +*width* +: *required* A **Long**, the number of cells between the brackets. + +*percent* +: *required* A **Double**. A value below 0 is taken as 0, and a value above 100 as 100. + +*usedColor* +: *optional* A [**ConsoleColor**](Types#consolecolor), the colour of the filled part. The default is **Green**. + +*freeColor* +: *optional* A [**ConsoleColor**](Types#consolecolor), the colour of the empty part. The default is **DarkGray**. + +Writes `[`, then `CLng(percent / 100 * width)` full blocks (U+2588) in *usedColor*, then the rest as light-shade blocks (U+2591) in *freeColor*, and then `] ` and the percentage rounded to a whole number with a percent sign. The brackets and the percentage are in the current colour attributes of the console; the blocks have a black background. The whole line is *width* + 4 cells plus the digits of the percentage. A negative *width* raises error 5. The cursor is left after the text. The conversion rounds half to even: a width of 10 and 25 percent gives 2 blocks. + +Calls `SetConsoleCursorPosition` and `WriteConsoleW`. Without a console, nothing is drawn. + +### EnableUTF8 +{: .no_toc } + +Sets the input and the output code page of the console to UTF-8, code page 65001. + +Syntax: *object*.**EnableUTF8** + +Calls `SetConsoleCP` and `SetConsoleOutputCP`. The write and read members of this class use the wide-character functions of Windows, which do not depend on the code page, so the setting does not change what they put in the screen buffer: a euro sign written with [**WriteTo**](#writeto) is stored as U+20AC with the output code page 437 and with 65001. The setting matters for text that reaches the console in another way, such as the output of a program that this program starts. + +The code pages are put back by [**DisableUTF8**](#disableutf8) and [**Restore**](#restore). Without a console, the calls fail without an error. + +### EnableVirtualTerminal +{: .no_toc } + +Turns on the processing of escape sequences, so that the strings of [mConsoleStyle](mConsoleStyle) change colours and move the cursor. + +Syntax: *object*.**EnableVirtualTerminal** **As Boolean** + +Adds `ENABLE_VIRTUAL_TERMINAL_PROCESSING` to the output mode. When Windows refuses, for example on a system or a console that does not have the mode, or when there is no console, the method returns **False** and changes nothing else. Otherwise it adds `ENABLE_VIRTUAL_TERMINAL_INPUT` to the input mode, ignoring whether that call worked, sets [**VirtualTerminalEnabled**](#virtualterminalenabled) and returns **True**. + +[**ReadKey**](#readkey) is not affected by the input flag, since it turns the input mode off while it waits. + +The mode is put back by [**DisableVirtualTerminal**](#disablevirtualterminal) and [**Restore**](#restore). Calls `GetConsoleMode` and `SetConsoleMode` for both handles. + +### EnterFullScreenMode +{: .no_toc } + +Starts the full-screen mode: it makes an in-memory screen buffer of the size of the window, hides the cursor and turns on window input. + +Syntax: *object*.**EnterFullScreenMode** + +The method does nothing when the mode is already on. It creates a [**cConsoleBuffer**](cConsoleBuffer) with the width and height of the window, which [**Buffer**](#buffer) returns; sets [**CursorVisible**](#cursorvisible) to **False**; and adds `ENABLE_WINDOW_INPUT` to the input mode, so that Windows reports a change of size. + +Despite the name, the window is not enlarged, the console does not switch to an alternate screen buffer, and the text that is on the screen is neither saved nor cleared. The program draws into the buffer with its [methods](cConsoleBuffer), and [**Refresh**](#refresh) copies the changed cells to the console. [**ExitFullScreenMode**](#exitfullscreenmode) ends the mode and leaves the last picture on the screen. + +[**Refresh**](#refresh) writes the cells of the buffer to the rows 0 to *Height* - 1 of the screen buffer, from column 0. When the screen buffer is taller than the window and the window shows another part of it, as it does in a classic console window that has scrolled, the picture is drawn out of sight. + +Calls `GetConsoleScreenBufferInfo`, `GetConsoleCursorInfo`, `SetConsoleCursorInfo`, `GetConsoleMode` and `SetConsoleMode`. Without a console, the buffer is 80 by 25. + +```tb check_build slot=module +Private m_Running As Boolean + +Public Sub OnKey(ByRef keyInfo As ConsoleKeyInfo, ByRef handled As Boolean) + If keyInfo.KeyCode = ConsoleKey.KeyEscape Then m_Running = False +End Sub + +Public Sub RunDemo() + Dim con As New cConsole + Dim n As Long + + con.OnKeyPress = AddressOf OnKey + con.EnterFullScreenMode + m_Running = True + + Do While m_Running + con.ProcessEvents + n += 1 + con.Buffer.Clear + con.Buffer.WriteAt 2, 1, "Press Escape to quit. Frame " & n, ConsoleColor.Yellow, ConsoleColor.Black + con.Refresh + mConsoleAPI.Sleep 30 + Loop + + con.ExitFullScreenMode + con.Restore +End Sub +``` + +### ExitFullScreenMode +{: .no_toc } + +Ends the full-screen mode. + +Syntax: *object*.**ExitFullScreenMode** + +The method does nothing when the mode is off. It releases the buffer, so that [**Buffer**](#buffer) returns **Nothing** again, shows the cursor, and sets the input mode to the one saved when the object was created, which also turns off the mouse input that [**OnMouseClick**](#onmouseclick) turned on. An object that was made with the old buffer, such as a component of the ConsoleComponents package, still holds it, and the console no longer shows it. The screen is not cleared. Calls `SetConsoleMode` and `SetConsoleCursorInfo`. + +### FlushInput +{: .no_toc } + +Discards the events that are waiting in the input queue. + +Syntax: *object*.**FlushInput** + +All events are discarded: keys, mouse events and size events. Calls `FlushConsoleInputBuffer`. + +### GetBufferSize +{: .no_toc } + +Returns the size of the screen buffer. + +Syntax: *object*.**GetBufferSize** **As ConsoleSize** + +Returns a [**ConsoleSize**](Types#consolesize) with the columns and rows of the whole screen buffer, which can be larger than the window. Calls `GetConsoleScreenBufferInfo`. Without a console, it is 80 by 300. + +### GetCursorPosition +{: .no_toc } + +Returns the position of the cursor. + +Syntax: *object*.**GetCursorPosition** **As ConsolePosition** + +Returns a [**ConsolePosition**](Types#consoleposition). The column and the row are counted from the top left of the screen buffer, not of the window. Calls `GetConsoleScreenBufferInfo`. Without a console, it is column 0, row 0. + +### GetSize +{: .no_toc } + +Returns the size of the console window. + +Syntax: *object*.**GetSize** **As ConsoleSize** + +Returns a [**ConsoleSize**](Types#consolesize) with the columns and rows of the window, from the window rectangle that Windows reports. [**Width**](#width) and [**Height**](#height) return its two fields. Calls `GetConsoleScreenBufferInfo`. Without a console, it is 80 by 25. + +### IsKeyPressed +{: .no_toc } + +Tells whether a key is down at this moment. + +Syntax: *object*.**IsKeyPressed** ( *keyCode* ) **As Boolean** + +*keyCode* +: *required* A **Long**, a Windows virtual-key code, such as 27 for Escape or 38 for the Up Arrow. + +Returns **True** when the most significant bit of the result of `GetAsyncKeyState` is set. The function asks Windows for the state of the keyboard, so it does not read the console's input queue, does not remove any event from it, and does not depend on which window has the focus. It works without a console. + +### KeyAvailable +{: .no_toc } + +Tells whether the input queue holds an event. + +Syntax: *object*.**KeyAvailable** **As Boolean** + +Returns **True** when the queue holds one or more events. The queue holds every kind of input: key presses and key releases, mouse events, focus events and size events, so a **True** result does not mean that a key is waiting. After a key is read, the event for its release is still in the queue, and **KeyAvailable** is true until a following [**ReadKey**](#readkey) or [**FlushInput**](#flushinput) removes it. + +Calls `GetNumberOfConsoleInputEvents`. Without a console, it returns **False**. + +### PeekEvent +{: .no_toc } + +Returns the first event in the queue, when it is a key press, without removing it. + +Syntax: *object*.**PeekEvent** **As ConsoleKeyInfo** + +Returns the [**ConsoleKeyInfo**](Types#consolekeyinfo) of the first event when that event is a key-down event. For any other first event, a key release, a mouse event or an empty queue, it returns an empty structure: **KeyCode** 0 and an empty **KeyChar**. It does not look past the first event. Calls `PeekConsoleInputW`. + +### PercentX +{: .no_toc } + +Returns the column that lies a given percentage across the window. + +Syntax: *object*.**PercentX** ( *percent* ) + +*percent* +: *required* A **Double**. A value below 0 is taken as 0, and a value above 100 as 100. + +Returns `CLng(Width * percent / 100)`, a **Long**, rounded half to even. + +### PercentY +{: .no_toc } + +Returns the row that lies a given percentage down the window. + +Syntax: *object*.**PercentY** ( *percent* ) + +*percent* +: *required* A **Double**. A value below 0 is taken as 0, and a value above 100 as 100. + +Returns `CLng(Height * percent / 100)`, a **Long**. + +### ProcessEvents +{: .no_toc } + +Reads the events in the input queue and calls the handlers. + +Syntax: *object*.**ProcessEvents** + +A full-screen program calls this method once in each pass of its main loop. It takes the events from the queue in order, while the queue is not empty, and treats each by its kind. + +| Event | Handling | +|---|---| +| Size change | Always taken from the queue. It resizes the full-screen buffer and calls [**OnResize**](#onresize), as described there. | +| Key | Taken from the queue and passed to [**OnKeyPress**](#onkeypress) when a key handler is set. Without a handler the method stops at once and leaves this event and everything behind it in the queue. | +| Mouse | Taken from the queue, and passed to [**OnMouseClick**](#onmouseclick) when a mouse handler is set. | +| Any other | Taken from the queue and dropped. | + +A program that wants to read keys with [**ReadKey**](#readkey) or [**ReadLine**](#readline) therefore sets no key handler. A queue that holds a key event for which nobody asks stops the processing of the size and mouse events behind it. + +The method calls `GetNumberOfConsoleInputEvents` (through [**KeyAvailable**](#keyavailable)), `PeekConsoleInputW` and `ReadConsoleInputW`. Without a console, **KeyAvailable** is **False** and nothing happens. + +### ReadKey +{: .no_toc } + +Waits for a key press and returns it. + +Syntax: *object*.**ReadKey** [ ( *intercept* ) ] **As ConsoleKeyInfo** + +*intercept* +: *optional* A **Boolean**. When it is **False**, the default, the character of the key is also written to the console. **True** writes nothing. + +Returns a [**ConsoleKeyInfo**](Types#consolekeyinfo) for the first key-down event. **KeyCode** is the Windows virtual-key code, and **KeyChar** the character of the key, or the character with code 0 when the key has none, such as an arrow key. The three flags say whether Alt, Ctrl and Shift were down. Any key-down event counts, including the keys that are only modifiers, so a press of Shift is returned as **KeyCode** 16. Other events are taken from the queue and dropped while the method waits, and the release of a key is ignored. A key that is held down gives one result for each repeat. + +While it waits, the method sets the input mode to 0 and puts the old mode back before it returns. With no mode flags the console does not echo the key, does not edit a line, and does not turn Ctrl+C into an interruption. When *intercept* is **False** the method writes **KeyChar** itself with [**WriteTo**](#writeto), even when it is the character with code 0. + +Calls `GetConsoleMode`, `SetConsoleMode` (twice) and `ReadConsoleInputW`. Without a console, or with the input redirected, `ReadConsoleInputW` fails at once and the method never returns; see [Known problems](#known-problems-in-version-0000). + +```tb check_build +Dim con As New cConsole +Dim key As ConsoleKeyInfo + +con.WriteTo "Press a key: " +key = con.ReadKey(True) +con.WriteLine +con.WriteLine "Key code " & key.KeyCode & ", character " & AscW(key.KeyChar) +``` + +### ReadKeyTimeout +{: .no_toc } + +Waits for a key press for a given time. + +Syntax: *object*.**ReadKeyTimeout** ( *timeoutMs* [ , *intercept* ] ) **As ConsoleKeyInfo** + +*timeoutMs* +: *required* A **Long**, the time to wait in milliseconds. + +*intercept* +: *optional* A **Boolean**, as for [**ReadKey**](#readkey). + +The method asks [**KeyAvailable**](#keyavailable) every millisecond (it calls `Sleep` between the asks) until the time is over. As soon as the queue holds any event it returns [**ReadKey**](#readkey). When the time is over it returns an empty structure, with **KeyCode** 0. + +Since **KeyAvailable** is true for any event, a pending event that is not a key press, such as a key release, makes **ReadKey** wait for the next key press with no time limit. Calls `GetNumberOfConsoleInputEvents`, `Sleep`, `GetTickCount` and the functions of **ReadKey**. Without a console, it returns the empty structure after the time. + +### ReadLine +{: .no_toc } + +Reads a line of text. + +Syntax: *object*.**ReadLine** [ ( *prompt* ) ] **As String** + +*prompt* +: *optional* A **String**, written with [**WriteTo**](#writeto) before the console reads. The default is an empty string. + +Waits in the console's line mode, with its echo and editing, until the user presses Enter, and returns the text without the carriage return and the line feed (all of them are removed). At most the number of characters of the read buffer (see [**New**](#new)) is read in one call. Calls `ReadConsoleW`. When the console is not in line mode, the result is that of the current mode. Without a console, or with the input redirected, `ReadConsoleW` fails and the method returns an empty string at once. + +```tb check_build +Dim con As New cConsole +Dim userName As String + +userName = con.ReadLine("Name: ") +con.WriteLineF "Hello, {0}.", userName +``` + +### ReadPassword +{: .no_toc } + +Reads a line of text and shows a mask character for each key. + +Syntax: *object*.**ReadPassword** ( [ *prompt* [ , *maskChar* ] ] ) **As String** + +*prompt* +: *optional* A **String**, written before the input. The default is an empty string. + +*maskChar* +: *optional* A **String**, written once for each character typed. The default is `*`. An empty string shows nothing. + +Reads with [**ReadKey**](#readkey), with the echo off. Enter ends the input and writes a line break. Backspace takes off the last character of the result and writes a backspace, a space and a backspace, which erases one cell. That is one cell even when *maskChar* has more than one character, and when *maskChar* is empty it is a cell of the prompt, since no mask was written. Escape cancels: the method returns an empty string. A key whose character has a code of 32 or more is added to the result and the mask is written. Keys with other characters, such as Tab, and keys without a character are ignored. Without a console it does not return, as **ReadKey** does not. + +```tb check_build +Dim con As New cConsole +Dim secret As String + +secret = con.ReadPassword("Password: ", "*") +con.WriteLine "Read " & Len(secret) & " characters." +``` + +### Refresh +{: .no_toc } + +Writes the changed part of the full-screen buffer to the console. + +Syntax: *object*.**Refresh** + +In full-screen mode the method calls [**Flush**](cConsoleBuffer#flush) of the buffer, which writes only the rectangle that has changed since the last write, measures the time for [**LastRenderTime**](#lastrendertime) and updates [**FPS**](#fps). Outside full-screen mode it does nothing. Calls `WriteConsoleOutputW`, with the standard output handle. Without a console, the call fails and nothing is shown. + +### RefreshAll +{: .no_toc } + +Writes the whole full-screen buffer to the console. + +Syntax: *object*.**RefreshAll** + +Like [**Refresh**](#refresh), but the whole buffer is written, whether or not it has changed. A program uses it after something else has drawn on the screen, for example after an error message, to restore the picture. Outside full-screen mode it does nothing. + +### ResetColors +{: .no_toc } + +Puts back the colour attributes that the console had when the object was created. + +Syntax: *object*.**ResetColors** + +Sets the attributes with `SetConsoleTextAttribute` and sets [**ForeColor**](#forecolor) and [**BackColor**](#backcolor) to the colours in the saved attributes (the low four bits, and the next four). Without a console, the saved attributes are Gray on Black. + +### Restore +{: .no_toc } + +Puts the console back as it was when the object was created. + +Syntax: *object*.**Restore** + +The method leaves the full-screen mode if it is on, sets the input mode and the output mode to the saved ones, sets the saved colour attributes, and sets the two saved code pages. It does not clear the screen, and does not put back the cursor size. The object stays usable, and the method can be called again. Calls `SetConsoleMode` (twice), `SetConsoleTextAttribute`, `SetConsoleCP` and `SetConsoleOutputCP`. The terminate event of the object calls it. + +### SetBufferSize +{: .no_toc } + +Sets the size of the screen buffer of the console. + +Syntax: *object*.**SetBufferSize** ( *width* , *height* ) + +*width*, *height* +: *required* **Long** values, the columns and rows of the buffer. + +A value outside the range of an **Integer** raises error 6, *Overflow*. Windows refuses a buffer smaller than the window, and the method does not check. The buffer of full-screen mode is not changed here: it is resized when the size event arrives, which [**ProcessEvents**](#processevents) reads. Calls `SetConsoleScreenBufferSize`. + +### SetColors +{: .no_toc } + +Sets the text and background colours. + +Syntax: *object*.**SetColors** ( *fore* [ , *back* ] ) + +*fore* +: *required* A [**ConsoleColor**](Types#consolecolor), the text colour. + +*back* +: *optional* A [**ConsoleColor**](Types#consolecolor), the background colour. The default is **Black**, and not the current background. + +Stores both values in [**ForeColor**](#forecolor) and [**BackColor**](#backcolor), and applies them with `SetConsoleTextAttribute`. + +### SetCursorPosition +{: .no_toc } + +Moves the cursor. + +Syntax: *object*.**SetCursorPosition** ( *col* , *row* ) + +*col*, *row* +: *required* **Long** values, the column and the row, counted from the top left of the screen buffer. + +A value outside the range of an **Integer** raises error 6, *Overflow*. A position outside the screen buffer is refused by Windows, and the method does not check. Calls `SetConsoleCursorPosition`. + +### WriteAt +{: .no_toc } + +Writes text at a position. + +Syntax: *object*.**WriteAt** ( *col* , *row* , *text* ) + +*col*, *row* +: *required* **Long** values, the column and the row. + +*text* +: *required* A **String**. + +Moves the cursor with [**SetCursorPosition**](#setcursorposition) and writes the text with [**WriteTo**](#writeto), in the current colour attributes of the console. The cursor stays after the text. + +### WriteAtColored +{: .no_toc } + +Writes text at a position in given colours. + +Syntax: *object*.**WriteAtColored** ( *col* , *row* , *text* , *fore* [ , *back* ] ) + +*col*, *row* +: *required* **Long** values, the column and the row. + +*text* +: *required* A **String**. + +*fore* +: *required* A [**ConsoleColor**](Types#consolecolor). + +*back* +: *optional* A [**ConsoleColor**](Types#consolecolor). The default is **Black**. + +Moves the cursor and calls [**WriteColored**](#writecolored), so the colours that were tracked before are put back after the text. + +### WriteColored +{: .no_toc } + +Writes text in given colours. + +Syntax: *object*.**WriteColored** ( *text* , *fore* [ , *back* ] ) + +*text* +: *required* A **String**. + +*fore* +: *required* A [**ConsoleColor**](Types#consolecolor), the text colour. + +*back* +: *optional* A [**ConsoleColor**](Types#consolecolor), the background colour. The default is **Black**, whatever the current background is. + +Sets the colours with [**SetColors**](#setcolors), writes the text with [**WriteTo**](#writeto), and sets [**ForeColor**](#forecolor) and [**BackColor**](#backcolor) back to the values they had before the call. Those are the values that the object tracks: Gray on Black until a property or [**SetColors**](#setcolors) changes them. They are not read from the console, so a console that started in other colours ends up in Gray on Black after the first call, and not in its own colours. [**ResetColors**](#resetcolors) puts back the original ones. + +```tb check_build +Dim con As New cConsole + +con.WriteTo "Status: " +con.WriteColored "OK", ConsoleColor.Black, ConsoleColor.Green +con.WriteLine +``` + +### WriteError +{: .no_toc } + +Writes text to the standard error handle. + +Syntax: *object*.**WriteError** ( *text* ) + +*text* +: *required* A **String**. + +Calls `WriteConsoleW` on the standard error handle, so the text is written when that handle is a console and is lost when the error output is redirected to a file or a pipe. No line break is added. + +### WriteErrorLine +{: .no_toc } + +Writes text and a line feed to the standard error handle. + +Syntax: *object*.**WriteErrorLine** ( *text* ) + +*text* +: *required* A **String**. + +Calls [**WriteError**](#writeerror) with the text and a line feed (code 10). + +### WriteF +{: .no_toc } + +Writes text with numbered placeholders. + +Syntax: *object*.**WriteF** ( *format* , *args* ... ) + +*format* +: *required* A **String** with the placeholders `{0}`, `{1}` and so on. + +*args* +: *optional* A list of values, one for each placeholder, with `{0}` for the first. + +Replaces `{0}` with the first value, `{1}` with the second, and so on, in that order, each converted with **CStr**, and writes the result with [**WriteTo**](#writeto). The only form of a placeholder is a number in braces; a format such as `{0:N2}` is not understood and stays as it is. The replacements are made one after the other on the whole text, so a value that contains `{1}` has that text replaced by the second value. A value that **CStr** cannot convert raises the error that **CStr** raises. A placeholder with no value stays in the text. + +```tb check_build +Dim con As New cConsole + +con.WriteLineF "{0} + {1} = {2}", 2, 3, 2 + 3 +``` + +### WriteLine +{: .no_toc } + +Writes text and a line feed. + +Syntax: *object*.**WriteLine** [ ( *text* ) ] + +*text* +: *optional* A **String**. The default is an empty string, which writes only the line feed. + +Calls [**WriteTo**](#writeto) with the text and `vbLf`, the line feed (code 10) alone. The console moves to the start of the next line when it processes output, which it does by default. + +### WriteLineColored +{: .no_toc } + +Writes text in given colours, and a line feed. + +Syntax: *object*.**WriteLineColored** ( *text* , *fore* [ , *back* ] ) + +*text* +: *required* A **String**. + +*fore* +: *required* A [**ConsoleColor**](Types#consolecolor). + +*back* +: *optional* A [**ConsoleColor**](Types#consolecolor). The default is **Black**. + +Calls [**WriteColored**](#writecolored) with the text and a line feed, so the line feed is written in the given colours too. + +### WriteLineF +{: .no_toc } + +Writes text with numbered placeholders, and a line feed. + +Syntax: *object*.**WriteLineF** ( *format* , *args* ... ) + +Replaces the placeholders as [**WriteF**](#writef) does and writes the result with [**WriteLine**](#writeline). + +### WriteTo +{: .no_toc } + +Writes text, without a line break. + +Syntax: *object*.**WriteTo** ( *text* ) + +*text* +: *required* A **String**. Every UTF-16 code unit of the string is written, in the current colour attributes of the console. + +Calls `WriteConsoleW` on the standard output handle with the characters of the string, and ignores the result. The text is not converted, so any Unicode character that the console font can draw appears as it is. Control characters are handled by the console: a carriage return, a line feed, a backspace and the bell take effect, and escape sequences take effect only when the virtual-terminal mode is on; see [**EnableVirtualTerminal**](#enablevirtualterminal). When the output is redirected, or the program has no console, nothing is written. + +## See Also + +- [cConsoleBuffer](cConsoleBuffer) class -- the screen buffer of the full-screen mode +- [Types](Types) -- the colours, the key, mouse and size structures, and the handler types +- [mConsoleStyle](mConsoleStyle) module -- escape sequences for colours and cursor movement +- [TBMANLIB_ConsoleCore](./) package +- [TBMANLIB_ConsoleComponents](../TBMANLIB_ConsoleComponents/) package -- text interface components that draw into the buffer diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsoleBuffer.md b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsoleBuffer.md new file mode 100644 index 000000000..e074e7789 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/cConsoleBuffer.md @@ -0,0 +1,482 @@ +--- +title: cConsoleBuffer +parent: TBMANLIB_ConsoleCore Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/cConsoleBuffer +has_toc: false +--- + +# cConsoleBuffer class +{: .no_toc } + +A grid of character cells in memory, with a colour pair for each cell and a record of the rectangle that has changed, that is drawn on the console in one call. + +The drawing members change cells of the grid and nothing on the screen. [**Flush**](#flush) writes the cells that changed since the last write to the console with a single call of `WriteConsoleOutputW`, so a program draws a whole picture without flicker. The full-screen mode of [**cConsole**](cConsole#enterfullscreenmode) makes one buffer, which its [**Buffer**](cConsole#buffer) property returns and its [**Refresh**](cConsole#refresh) method flushes. A program can also create a buffer with `New`, and it needs no console to do so, since the grid is ordinary memory. + +```tb check_build check_run +Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3) +Dim ci As ConsoleCharInfo + +buf.WriteAt 2, 1, "Hi", ConsoleColor.Yellow, ConsoleColor.DarkBlue +ci = buf.GetChar(2, 1) +Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor +ci = buf.GetChar(3, 1) +Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor +ci = buf.GetChar(0, 0) +Debug.Print "[" & ci.Char & "] " & ci.ForeColor & " " & ci.BackColor +' Output: +' H 14 1 +' i 14 1 +' [ ] 7 0 +``` + +* TOC +{:toc} + +In the syntax lines of this page, *object* is a **cConsoleBuffer**. Columns and rows are counted from 0, and a position is a column and a row, in that order. + +## The cells + +The grid has *Width* columns and *Height* rows. A cell holds one UTF-16 code unit and one colour attribute, which is the foreground [**ConsoleColor**](Types#consolecolor) plus 16 times the background. The cells are kept row after row as the `CHAR_INFO` structure of Windows (two 16-bit values, 4 bytes), so the cell at *col* and *row* is number `row * Width + col`. [**BufferPtr**](#bufferptr) returns the address of the first cell. + +A character outside the Basic Multilingual Plane, such as most emoji, is two code units and takes two cells. The buffer has no notion of the width of a character: a character that the console draws two cells wide still takes one cell of the grid. + +Defaults: where a member takes *fore* and *back* and they are omitted, the cell gets **Gray** on **Black**. The members do not use the colours of the [**cConsole**](cConsole#forecolor) object. + +## The dirty rectangle + +The buffer records the smallest rectangle that contains every cell changed since the last [**Flush**](#flush). [**SetChar**](#setchar), [**WriteAt**](#writeat), [**FillRect**](#fillrect) and [**CopyRegion**](#copyregion) grow the rectangle, and so do [**DrawBox**](#drawbox), [**DrawHLine**](#drawhline) and [**DrawVLine**](#drawvline) through **SetChar**. [**Clear**](#clear) and [**Resize**](#resize) mark the whole grid, and [**Flush**](#flush) and [**ClearDirty**](#cleardirty) empty the rectangle. [**IsDirty**](#isdirty) tells whether it holds anything, and [**GetDirtyRect**](#getdirtyrect) returns it. After the rectangle is emptied, its **Left** is *Width*, its **Top** is *Height*, and its **Right** and **Bottom** are 0. + +```tb check_build check_run +Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3) +Dim r As ConsoleRect + +Debug.Print buf.IsDirty ' a new buffer is all dirty +buf.ClearDirty +Debug.Print buf.IsDirty +buf.SetChar 4, 1, "x" +buf.SetChar 6, 2, "y" +r = buf.GetDirtyRect +Debug.Print r.Left & " " & r.Top & " " & r.Right & " " & r.Bottom +' Output: +' True +' False +' 4 1 6 2 +``` + +[**WriteAt**](#writeat), [**FillRect**](#fillrect) and [**CopyRegion**](#copyregion) pass the rectangle they were given to the record without checking it against the buffer, so a call with a position outside the grid can leave a dirty rectangle that reaches beyond the grid. [**Flush**](#flush) then asks Windows to write a region that is partly outside the screen buffer. A row below the grid or a column beyond its width does no harm: Windows clips the region. A negative column or row does: the next **Flush** writes nothing, and empties the rectangle all the same, so the cells that changed since the previous flush stay off the screen until they change again or [**RefreshAll**](cConsole#refreshall) of the console writes the whole buffer. + +## Batch updates + +A program that changes most of the grid in one pass can skip the bookkeeping. Between [**BeginBatchUpdate**](#beginbatchupdate) and [**EndBatchUpdate**](#endbatchupdate) the members do not grow the dirty rectangle, and [**EndBatchUpdate**](#endbatchupdate) marks the whole grid as dirty, so the next [**Flush**](#flush) writes everything. Batches nest: the grid is marked when the outermost one ends. + +```tb check_build check_run +Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3) +Dim r As ConsoleRect + +buf.ClearDirty +buf.BeginBatchUpdate +buf.SetChar 4, 1, "x" +Debug.Print buf.IsInBatchMode & " " & buf.IsDirty +buf.EndBatchUpdate +r = buf.GetDirtyRect +Debug.Print buf.IsInBatchMode & " " & buf.IsDirty +Debug.Print r.Left & " " & r.Top & " " & r.Right & " " & r.Bottom +' Output: +' True False +' False True +' 0 0 9 2 +``` + +## Known problems in version 0.0.0.0 + +> [!WARNING] +> In version 0.0.0.0, [**ScrollRegion**](#scrollregion) moves a region by less than the distance asked for when the region starts closer to the top or left edge than that distance, and clears the full distance, so cells are lost without an error. A [**WriteAt**](#writeat), [**FillRect**](#fillrect) or [**CopyRegion**](#copyregion) with a negative column or row makes the next [**Flush**](#flush) draw nothing, and raises no error; see [The dirty rectangle](#the-dirty-rectangle). + +## Constructor + +### New +{: .no_toc } + +Creates a buffer and clears it. + +Syntax: **New cConsoleBuffer** ( *width* , *height* ) + +*width* +: *required* A **Long**, the number of columns, 1 or more. + +*height* +: *required* A **Long**, the number of rows, 1 or more. + +The constructor does not check the sizes. It calls [**Resize**](#resize), so every cell holds a space in **Gray** on **Black**, and the whole grid is dirty. The buffer takes 4 bytes for each cell. + +## Properties + +### BufferPtr +{: .no_toc } + +The address of the first cell. **LongPtr**, read-only. + +Syntax: *object*.**BufferPtr** + +The address is that of the first element of the array of cells, `VarPtr(m_Buffer(0))`. It is valid until [**Resize**](#resize) is called, which allocates a new array. A program can read or write the cells through it, for example with `CopyMemory`; see [The cells](#the-cells) for the layout. The dirty rectangle does not follow such writes, so the program calls [**Flush**](#flush) with the flag *forceAll*, or [**FlushRegion**](#flushregion). + +### Height +{: .no_toc } + +The number of rows. **Long**, read-only. + +Syntax: *object*.**Height** + +### IsDirty +{: .no_toc } + +Whether a cell has changed since the last write to the console. **Boolean**, read-only. + +Syntax: *object*.**IsDirty** + +### IsInBatchMode +{: .no_toc } + +Whether a batch update is open. **Boolean**, read-only. + +Syntax: *object*.**IsInBatchMode** + +**True** from [**BeginBatchUpdate**](#beginbatchupdate) until the matching [**EndBatchUpdate**](#endbatchupdate). + +### Width +{: .no_toc } + +The number of columns. **Long**, read-only. + +Syntax: *object*.**Width** + +## Methods + +### BeginBatchUpdate +{: .no_toc } + +Starts a batch update. + +Syntax: *object*.**BeginBatchUpdate** + +Adds 1 to a counter. While the counter is above 0, [**SetChar**](#setchar), [**WriteAt**](#writeat), [**FillRect**](#fillrect), [**CopyRegion**](#copyregion) and the other members do not grow the dirty rectangle. + +### Clear +{: .no_toc } + +Fills every cell with one character and one pair of colours. + +Syntax: *object*.**Clear** [ *fore* [ , *back* [ , *fillChar* ] ] ] + +*fore* +: *optional* A [**ConsoleColor**](Types#consolecolor), the text colour. The default is **Gray**. + +*back* +: *optional* A [**ConsoleColor**](Types#consolecolor), the background colour. The default is **Black**. + +*fillChar* +: *optional* A **String** whose first character is used. The default is a space. An empty string raises error 5, *Invalid procedure call or argument*. + +Marks the whole grid as dirty, also during a batch update. + +### ClearDirty +{: .no_toc } + +Empties the dirty rectangle without writing anything. + +Syntax: *object*.**ClearDirty** + +Sets [**IsDirty**](#isdirty) to **False** and the rectangle to its empty form. The cells that were dirty are not written by the next [**Flush**](#flush), unless that call forces all. + +### CopyRegion +{: .no_toc } + +Copies a rectangle of cells to another position. + +Syntax: *object*.**CopyRegion** ( *srcLeft* , *srcTop* , *srcRight* , *srcBottom* , *dstLeft* , *dstTop* ) + +*srcLeft*, *srcTop*, *srcRight*, *srcBottom* +: *required* **Long** values, the corners of the source rectangle, inclusive. + +*dstLeft*, *dstTop* +: *required* **Long** values, the position of the upper left corner of the copy. + +The source rectangle is first cut to the grid. Its left and top edges are raised to 0 and its right and bottom edges lowered to the last column and row, and the destination is not moved to match, so a source that reaches outside the grid is copied from its cut position to the unchanged destination. Cells whose destination lies outside the grid are skipped. Source and destination can overlap; the copy runs in the direction that does not overwrite cells before they are read. The method passes the corners of the destination and of the source rectangle to the dirty rectangle, which a batch update suppresses, and it does not cut the destination corners to the grid. + +### DrawBox +{: .no_toc } + +Draws the outline of a rectangle with line-drawing characters. + +Syntax: *object*.**DrawBox** ( *left* , *top* , *right* , *bottom* [ , *fore* [ , *back* [ , *doubleLine* ] ] ] ) + +*left*, *top* +: *required* **Long** values, the column and the row of the upper left corner. + +*right*, *bottom* +: *required* **Long** values, the column and the row of the lower right corner. + +*fore*, *back* +: *optional* [**ConsoleColor**](Types#consolecolor) values. The defaults are **Gray** and **Black**. + +*doubleLine* +: *optional* A **Boolean**. **True** uses the double-line characters (U+2554, U+2557, U+255A, U+255D, U+2550, U+2551), **False**, the default, the single-line ones (U+250C, U+2510, U+2514, U+2518, U+2500, U+2502). + +The four corners, then the top and bottom lines, then the left and right lines are written with [**SetChar**](#setchar), so the parts outside the grid are left out. The inside of the box is not changed. No error is raised for a rectangle that is one cell wide or high; the corners overwrite each other. + +### DrawHLine +{: .no_toc } + +Draws a horizontal line. + +Syntax: *object*.**DrawHLine** ( *col* , *row* , *length* [ , *fore* [ , *back* [ , *doubleLine* ] ] ] ) + +*col*, *row* +: *required* **Long** values, the start of the line. + +*length* +: *required* A **Long**, the number of cells. + +*fore*, *back*, *doubleLine* +: *optional*, as for [**DrawBox**](#drawbox). The line uses U+2500, or U+2550 when *doubleLine* is **True**. + +The cells are written with [**SetChar**](#setchar). + +### DrawVLine +{: .no_toc } + +Draws a vertical line. + +Syntax: *object*.**DrawVLine** ( *col* , *row* , *length* [ , *fore* [ , *back* [ , *doubleLine* ] ] ] ) + +*col*, *row* +: *required* **Long** values, the start of the line. + +*length* +: *required* A **Long**, the number of cells, going down. + +*fore*, *back*, *doubleLine* +: *optional*, as for [**DrawBox**](#drawbox). The line uses U+2502, or U+2551 when *doubleLine* is **True**. + +### EndBatchUpdate +{: .no_toc } + +Ends a batch update. + +Syntax: *object*.**EndBatchUpdate** + +Subtracts 1 from the counter when it is above 0. When the counter reaches 0 the whole grid is marked as dirty. A call without a matching **BeginBatchUpdate** does nothing. + +### FillRect +{: .no_toc } + +Fills a rectangle with one character and one pair of colours. + +Syntax: *object*.**FillRect** ( *left* , *top* , *right* , *bottom* [ , *char* [ , *fore* [ , *back* ] ] ] ) + +*left*, *top*, *right*, *bottom* +: *required* **Long** values, the corners, inclusive. The rectangle is cut to the grid: a left or top below 0 is raised to 0, and a right or bottom beyond the grid is lowered to its last column or row. + +*char* +: *optional* A **String** whose first character is used. The default is a space. An empty string raises error 5. + +*fore*, *back* +: *optional* [**ConsoleColor**](Types#consolecolor) values. The defaults are **Gray** and **Black**. + +The two corners of the cut rectangle are passed to the dirty rectangle. A rectangle with *left* greater than *right* changes no cell. + +### Flush +{: .no_toc } + +Writes the changed cells to a console screen buffer. + +Syntax: *object*.**Flush** ( *hOutput* [ , *forceAll* ] ) + +*hOutput* +: *required* A **LongPtr**, the handle of a console screen buffer with write access. [**Refresh**](cConsole#refresh) of **cConsole** passes the standard output handle. + +*forceAll* +: *optional* A **Boolean**. **True** writes every cell. The default, **False**, writes the dirty rectangle only, and returns at once when nothing is dirty. + +The cells go to the same positions in the screen buffer: the cell at column *c* and row *r* of the grid is written at column *c* and row *r*, counted from the top left of the screen buffer and not of the window. Afterwards the dirty rectangle is emptied. The method does not check the result of `WriteConsoleOutputW`: when the call fails, because the handle is not a console, the cells are not shown and the dirty rectangle is emptied all the same. Calls `WriteConsoleOutputW`. Without a console, nothing is shown. + +### FlushRegion +{: .no_toc } + +Writes a rectangle of cells to a console screen buffer. + +Syntax: *object*.**FlushRegion** ( *hOutput* , *left* , *top* , *right* , *bottom* ) + +*hOutput* +: *required* A **LongPtr**, as for [**Flush**](#flush). + +*left*, *top*, *right*, *bottom* +: *required* **Long** values, the corners, inclusive. The rectangle is cut to the grid. + +Writes the rectangle whether or not it is dirty, at the same position in the screen buffer. The dirty rectangle is left as it is. Calls `WriteConsoleOutputW`. + +### GetChar +{: .no_toc } + +Returns the character and the colours of a cell. + +Syntax: *object*.**GetChar** ( *col* , *row* ) **As ConsoleCharInfo** + +*col*, *row* +: *required* **Long** values, the position of the cell. + +Returns a [**ConsoleCharInfo**](Types#consolecharinfo). **Char** holds the one character, **ForeColor** and **BackColor** the colours; **Style** is always 0. For a position outside the grid it returns the empty structure: **Char** is an empty string and both colours are **Black**. + +### GetDirtyRect +{: .no_toc } + +Returns the dirty rectangle. + +Syntax: *object*.**GetDirtyRect** **As ConsoleRect** + +Returns a [**ConsoleRect**](Types#consolerect). When nothing is dirty it is the empty form described in [The dirty rectangle](#the-dirty-rectangle). + +### Resize +{: .no_toc } + +Gives the buffer a new size and clears it. + +Syntax: *object*.**Resize** ( *width* , *height* ) + +*width*, *height* +: *required* **Long** values, the new number of columns and rows. + +The old contents are lost, not copied: the method allocates a new array and calls [**Clear**](#clear) with its defaults, so every cell is a space in **Gray** on **Black** and the whole grid is dirty. Any address from [**BufferPtr**](#bufferptr) is no longer valid. The full-screen mode of **cConsole** calls this method when a size event shows a new size, so a program redraws after the resize handler. + +### ScrollRegion +{: .no_toc } + +Moves the cells of a rectangle by a number of rows and columns, and clears the cells that this uncovers. + +Syntax: *object*.**ScrollRegion** ( *left* , *top* , *right* , *bottom* , *deltaRow* , *deltaCol* ) + +*left*, *top*, *right*, *bottom* +: *required* **Long** values, the corners of the region, inclusive. The region is cut to the grid. + +*deltaRow* +: *required* A **Long**. A positive value moves the content down, a negative value up. + +*deltaCol* +: *required* A **Long**. A positive value moves the content right, a negative value left. + +The rows (or columns) that the move uncovers are filled with spaces in **Gray** on **Black**. Cells moved out of the region are lost. The method does nothing when both deltas are 0. Note the order of the two deltas: rows come first. + +The method works by copying from a source rectangle that is *deltaRow* rows above and *deltaCol* columns left of the region. When that source reaches above the top of the grid, or left of its left edge, it is cut (see [**CopyRegion**](#copyregion)) and the destination is not moved, so the content moves by the distance between the region and the edge, which is less than the delta, while the full number of rows or columns is cleared. At the top edge a scroll down of one row therefore moves nothing and only clears the first row. Scrolling up and to the left, and scrolling down or right in a region that is as far from the top or left edge as the delta, work as expected. + +```tb check_build check_run +Dim a As cConsoleBuffer = New cConsoleBuffer(5, 4) +Dim b As cConsoleBuffer = New cConsoleBuffer(5, 4) +Dim ci As ConsoleCharInfo +Dim y As Long +Dim textA As String +Dim textB As String + +For y = 0 To 3 + a.WriteAt 0, y, String$(5, Chr$(65 + y)) ' the rows A, B, C, D + b.WriteAt 0, y, String$(5, Chr$(65 + y)) +Next + +a.ScrollRegion 0, 0, 4, 3, -1, 0 ' up by one row +b.ScrollRegion 0, 0, 4, 3, 1, 0 ' down by one row, from the top edge + +For y = 0 To 3 + ci = a.GetChar(0, y) + textA = textA & ci.Char + ci = b.GetChar(0, y) + textB = textB & ci.Char +Next +Debug.Print "[" & textA & "]" +Debug.Print "[" & textB & "]" +' Output: +' [BCD ] +' [ BCD] +``` + +The second line should read `[ ABC]`. Moving the region down from a distance of 1 row below the top, with `b.ScrollRegion 0, 1, 4, 3, 1, 0`, gives the expected `A BC`. + +### SetChar +{: .no_toc } + +Sets one cell. + +Syntax: *object*.**SetChar** ( *col* , *row* , *char* [ , *fore* [ , *back* ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the cell. A position outside the grid is ignored, and nothing is marked. + +*char* +: *required* A **String**. Only its first character is used. An empty string raises error 5, *Invalid procedure call or argument*. + +*fore*, *back* +: *optional* [**ConsoleColor**](Types#consolecolor) values. The defaults are **Gray** and **Black**. + +Marks the cell as dirty. + +### WriteAt +{: .no_toc } + +Writes a string into a row of cells. + +Syntax: *object*.**WriteAt** ( *col* , *row* , *text* [ , *fore* [ , *back* ] ] ) + +*col*, *row* +: *required* **Long** values, the position of the first character. + +*text* +: *required* A **String**. Each UTF-16 code unit takes one cell, including carriage returns, line feeds and tabs, which are stored as ordinary characters. Characters that fall to the right of the last column are dropped, and characters to the left of column 0 are skipped. The text does not wrap. + +*fore*, *back* +: *optional* [**ConsoleColor**](Types#consolecolor) values. The defaults are **Gray** and **Black**. + +Nothing is written when *row* is outside the grid. The dirty rectangle is grown by the first and the last column of the text and by *row* in every case, even when they lie outside the grid, and also when *text* is empty. + +### WriteTextEx +{: .no_toc } + +Writes a string, shortened to a width with an ellipsis. + +Syntax: *object*.**WriteTextEx** ( *text* , *col* , *row* , *fore* , *back* , *maxWidth* [ , *ellipsis* ] ) + +*text* +: *required* A **String**. + +*col*, *row* +: *required* **Long** values, the position of the first character. + +*fore*, *back* +: *required* [**ConsoleColor**](Types#consolecolor) values. Unlike the other drawing members, this one has no defaults for the colours. + +*maxWidth* +: *required* A **Long**, the most cells to write. A value of 0 or less writes nothing. + +*ellipsis* +: *optional* A **String**, the mark that shows the text was cut. The default is the character U+2026, the horizontal ellipsis. + +When *text* is longer than *maxWidth*, the method writes the first `maxWidth - Len(ellipsis)` characters of the text followed by the ellipsis, so that the result is *maxWidth* characters. When the ellipsis itself is as long as *maxWidth* or longer, only the first *maxWidth* characters of the ellipsis are written, and nothing of the text. Shorter text is written whole. The order of the parameters differs from [**WriteAt**](#writeat): the text comes first. + +```tb check_build check_run +Dim buf As cConsoleBuffer = New cConsoleBuffer(20, 2) +Dim ci As ConsoleCharInfo +Dim x As Long +Dim line As String + +buf.WriteTextEx "Filename.extension", 0, 0, ConsoleColor.Gray, ConsoleColor.Black, 10, "..." +For x = 0 To 9 + ci = buf.GetChar(x, 0) + line = line & ci.Char +Next +Debug.Print line +' Output: +' Filenam... +``` + +## See Also + +- [cConsole](cConsole) class -- the console, and the full-screen mode that owns a buffer +- [Types](Types) -- **ConsoleColor**, **ConsoleCharInfo** and **ConsoleRect** +- [cProgressBar](../TBMANLIB_ConsoleComponents/cProgressBar), [cDataGrid](../TBMANLIB_ConsoleComponents/cDataGrid) and the other components, which draw into a buffer diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleCore/index.md b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/index.md new file mode 100644 index 000000000..57dc01c6c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/index.md @@ -0,0 +1,86 @@ +--- +title: TBMANLIB_ConsoleCore Package +parent: Experimental Packages +has_toc: false +exclude_from_docs: + - mConsoleAPI +indexed_from: 0.0.0.0 +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/ +--- + +# TBMANLIB_ConsoleCore Package +{: .no_toc } + +**Author:** woeoio. + +The **TBMANLIB_ConsoleCore** contributed package is a library for the console of a twinBASIC program. Its class [**cConsole**](cConsole) writes text in 16 colours, reads lines, single keys and passwords, moves the cursor, switches the console to UTF-8 and to the escape-sequence mode, and has a full-screen mode in which a program draws into an in-memory grid, [**cConsoleBuffer**](cConsoleBuffer), and receives key, mouse and window-size events. The module [**mConsoleStyle**](mConsoleStyle) builds the escape sequences for colours, text styles and cursor movement, and has helpers that align, shorten and wrap text. This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence declared. The package describes itself as "TBMAN: Modern console library with UTF-8, colors, and full-screen support". + +The library is built on the Windows console functions of `kernel32.dll`. The package [TBMANLIB_ConsoleComponents](../TBMANLIB_ConsoleComponents/), by the same author, is a set of text-interface components that draw into the buffer of this package. + +> [!NOTE] +> The tools that check this documentation's samples run a sample and compare what it prints with **Debug.Print**. They cannot yet read what a program writes to a console, or send it keys and mouse events. So the samples on these pages are compiled, and what they show on the console has not been checked by running them. Statements about console output, keys and the mouse follow the package's source, and will be checked once the tools can capture a console. + +```tb check_build +Dim con As New cConsole + +con.EnableUTF8 +con.EnableVirtualTerminal +con.Title = "Demo" +con.WriteLineColored "Hello, world", ConsoleColor.Green +con.WriteAt 0, 2, mConsoleStyle.Bold & "Bold text" & mConsoleStyle.Reset +con.WriteLine +con.Restore +``` + +> [!WARNING] +> Several members of version 0.0.0.0 do not do what their names suggest. [**ReadKey**](cConsole#readkey) never returns in a program that has no console input, and a comparison of a key with a member of [**ConsoleKey**](Types#consolekey) is true for the wrong key. [Defects in version 0.0.0.0](#defects-in-version-0000) lists them with the pages that describe them. + +* TOC +{:toc} + +## Which project it needs + +The package works on the standard input, output and error handles that Windows gives a program, and the members that read or draw reach Windows through them. It is meant for a console application: a project with [*Is Console Application*](../../../IDE/Project-Settings#is-console-application) set to **Yes**, which the **Standard EXE (Console App)** template does, as [Console Applications](../../../../Features/Project-Configuration/Project-Types#console-applications) describes. Add the package as [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) describes. + +A program with no console can create a **cConsole**, and the buffer of the full-screen mode then works as an in-memory grid, but nothing is shown and the members that wait for a key never return. [Without a console](cConsole#without-a-console) lists what each member does in that case. When the output of a console program is redirected to a file or a pipe, the write members write nothing; see [Redirected input and output](cConsole#redirected-input-and-output). + +## What the package offers + +- **Text output.** [**WriteTo**](cConsole#writeto), [**WriteLine**](cConsole#writeline), [**WriteF**](cConsole#writef) with numbered placeholders, the coloured variants, [**WriteAt**](cConsole#writeat) at a position, and [**WriteError**](cConsole#writeerror) for the standard error handle. +- **Input.** [**ReadLine**](cConsole#readline), [**ReadKey**](cConsole#readkey), [**ReadKeyTimeout**](cConsole#readkeytimeout), [**ReadPassword**](cConsole#readpassword), and tests for pending input. +- **Console state.** Colours, the cursor, the title, the code pages, the escape-sequence mode and the size, and [**Restore**](cConsole#restore), which puts the state of the console back as it was. +- **Full-screen mode.** [**EnterFullScreenMode**](cConsole#enterfullscreenmode), [**ProcessEvents**](cConsole#processevents) with handlers for keys, the mouse and the window size, and [**Refresh**](cConsole#refresh), which writes the changed part of the buffer in one call. +- **The buffer.** [**cConsoleBuffer**](cConsoleBuffer) draws characters, lines, boxes and filled rectangles, copies and scrolls regions, and keeps a dirty rectangle so that [**Flush**](cConsoleBuffer#flush) writes only what changed. +- **Escape sequences.** [**mConsoleStyle**](mConsoleStyle) returns the sequences for 16, 256 and 24-bit colours, bold, underline and the other styles, cursor movement, clearing, scrolling and the alternate screen, and constants for box-drawing, block, arrow and symbol characters. + +## Defects in version 0.0.0.0 + +- [**ReadKey**](cConsole#readkey) and [**ReadPassword**](cConsole#readpassword) never return when the program has no console input. +- The key codes of [**ConsoleKey**](Types#consolekey) are not the virtual-key codes that [**ReadKey**](cConsole#readkey) and the key handler return, except for Enter, Escape, Space, Backspace and Tab. +- [**KeyAvailable**](cConsole#keyavailable) is true for any input event, including a key release. +- [**WriteColored**](cConsole#writecolored), [**WriteLineColored**](cConsole#writelinecolored) and [**WriteAtColored**](cConsole#writeatcolored) default the background to black, and leave the console in Gray on Black. +- [**Refresh**](cConsole#refresh) draws at the top of the screen buffer, which is out of sight when the window shows another part of it, and draws nothing after a [**WriteAt**](cConsoleBuffer#writeat) with a negative column or row. +- [**OnResize**](cConsole#onresize) is given the size of the screen buffer, not of the window. +- [**OnCtrlEvent**](cConsole#onctrlevent) adds a handler that is never removed, and none of the four handler properties can be cleared. +- [**ScrollRegion**](cConsoleBuffer#scrollregion) of the buffer moves a region by less than the distance asked for when the region is near the top or left edge of the buffer. +- [**RepeatStr**](mConsoleStyle#repeatstr) repeats only the first character of its string, and [**StripAnsi**](mConsoleStyle#stripansi) removes text after some sequences. +- Of the types in [Types](Types), [**ConsoleTextStyle**](Types#consoletextstyle), [**ConsoleMode**](Types#consolemode), [**ConsoleEventType**](Types#consoleeventtype), [**ConsoleEventInfo**](Types#consoleeventinfo) and the delegates **ConsoleTimerCallback** and **ConsoleRenderCallback** are not used by any member. + +## Classes and modules + +- [cConsole](cConsole) -- writes and reads text, controls the cursor, colours and modes of the console, and runs the full-screen mode +- [cConsoleBuffer](cConsoleBuffer) -- a grid of characters and colours in memory, written to the console in one call +- [mConsoleStyle](mConsoleStyle) -- escape sequences for colours, styles and cursor movement, text helpers and drawing characters +- [Types](Types) -- the enumerations, the structures such as **ConsoleRect** and **ConsoleKeyInfo**, and the handler types + +## Declarations of the Windows API + +A fifth public module, **mConsoleAPI**, has no page of its own, since a program does not need it, but a project that references the package can use its members. It declares 51 functions of `kernel32.dll`, `user32.dll` and `oleaut32.dll`: the console functions that **cConsole** calls and others, such as `AllocConsole`, `FreeConsole`, `AttachConsole` and `CreateConsoleScreenBuffer`, and the general functions `Sleep`, `GetTickCount`, `GetAsyncKeyState`, `SetTimer` and `KillTimer`. It also declares 12 structures (among them `COORD`, `SMALL_RECT`, `CHAR_INFO`, `INPUT_RECORD` and `RECT`), 47 constants for modes, events, code pages and key states, and five helper functions: `MakeCOORD` and `MakeDWORD` pack two 16-bit values into a **Long**, `LoWord` and `HiWord` take them out, and `ColorAttribute` combines a text and a background [**ConsoleColor**](Types#consolecolor) into a console attribute. All these names are public, so they are visible in a project that references the package. + +## See Also + +- [cConsole](cConsole) class -- the console +- [cConsoleBuffer](cConsoleBuffer) class -- the screen buffer of the full-screen mode +- [TBMANLIB_ConsoleComponents](../TBMANLIB_ConsoleComponents/) package -- progress bars, charts, panels, layouts and a data grid that draw into the buffer +- [Console Applications](../../../../Features/Project-Configuration/Project-Types#console-applications) -- the project type that the package is made for +- [Importing a package from TWINSERV](../../../../Features/Packages/Importing-TWINSERV) -- adding a published package to a project diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleCore/mConsoleStyle.md b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/mConsoleStyle.md new file mode 100644 index 000000000..4ba149e90 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleCore/mConsoleStyle.md @@ -0,0 +1,436 @@ +--- +title: mConsoleStyle +parent: TBMANLIB_ConsoleCore Package +permalink: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/mConsoleStyle +has_toc: false +--- + +# mConsoleStyle module +{: .no_toc } + +Builds the escape sequences that change colours and text styles, move the cursor, clear the screen and set the window title, and shortens, aligns, wraps and cleans text. + +Each function returns a **String** and writes nothing: the program passes the string to [**WriteTo**](cConsole#writeto) or another output member. A sequence acts only when the console processes virtual-terminal sequences, which [**EnableVirtualTerminal**](cConsole#enablevirtualterminal) turns on; in a console without that mode the characters of the sequence appear as text. The module needs no console and no **cConsole** object, so its functions also run in a program that has none. + +The functions are written with the name of the module, as in `mConsoleStyle.Bold`. A function that takes no arguments is called with or without parentheses. + +```tb check_build check_run +Dim s As String + +s = mConsoleStyle.Colored("Hi", mConsoleStyle.FgRed) +Debug.Print Replace(s, Chr$(27), "") +s = mConsoleStyle.ColoredRGB("Hi", 255, 128, 0, 0, 0, 64) +Debug.Print Replace(s, Chr$(27), "") +Debug.Print Replace(mConsoleStyle.CursorPosition(5, 10), Chr$(27), "") +' Output: +' [31mHi[0m +' [38;2;255;128;0m[48;2;0;0;64mHi[0m +' [5;10H +``` + +* TOC +{:toc} + +The sequences of the first sections start with `ESC [`, the control sequence introducer, written below as CSI; `ESC` is the character with code 27. The text styles and colours stay in force until another sequence changes them, so a program ends a coloured text with [**Reset**](#text-styles). + +```tb check_build +Dim con As New cConsole + +If con.EnableVirtualTerminal() Then + con.WriteLine mConsoleStyle.Bold & "Title" & mConsoleStyle.Reset + con.WriteLine mConsoleStyle.Colored("Warning", mConsoleStyle.FgYellow) + con.WriteTo mConsoleStyle.CursorUp(1) & mConsoleStyle.ClearLine() +End If +con.Restore +``` + +## Known problems in version 0.0.0.0 + +- [**RepeatStr**](#repeatstr) repeats the first character of its string, not the string. +- [**StripAnsi**](#stripansi) ends a sequence only at one of eight letters, so it also removes the text after the sequences that end in another letter, such as the one made by **CursorHide**. + +## Cursor movement + +### Cursor functions +{: .no_toc } + +| Function | Returns | Moves | +|---|---|---| +| **CursorUp** ( [ *n* ] ) | CSI *n* `A` | up *n* lines | +| **CursorDown** ( [ *n* ] ) | CSI *n* `B` | down *n* lines | +| **CursorForward** ( [ *n* ] ) | CSI *n* `C` | right *n* columns | +| **CursorBack** ( [ *n* ] ) | CSI *n* `D` | left *n* columns | +| **CursorNextLine** ( [ *n* ] ) | CSI *n* `E` | to the start of the line *n* lines down | +| **CursorPrevLine** ( [ *n* ] ) | CSI *n* `F` | to the start of the line *n* lines up | +| **CursorColumn** ( *n* ) | CSI *n* `G` | to column *n* of the current line | +| **CursorPosition** ( *row* , *col* ) | CSI *row* `;` *col* `H` | to the row and the column | +| **CursorSave** | CSI `s` | saves the position of the cursor | +| **CursorRestore** | CSI `u` | goes to the saved position | +| **CursorHide** | CSI `?25l` | hides the cursor | +| **CursorShow** | CSI `?25h` | shows the cursor | + +*n*, *row* and *col* are **Long** values. *n* of the first six functions is optional and defaults to 1; **CursorColumn** has no default. The numbers of these sequences are counted from 1, whereas [**SetCursorPosition**](cConsole#setcursorposition) of **cConsole** counts from 0, takes the column first, and works on the screen buffer, not the window. **CursorPosition** takes the row first, as the sequence does. The value is written as it is: a negative number or 0 is not checked. + +## Clearing + +### ClearScreen, ClearLine +{: .no_toc } + +Syntax: **ClearScreen** ( [ *mode* ] ) **As String** + +Syntax: **ClearLine** ( [ *mode* ] ) **As String** + +*mode* +: *optional* A **Long**. The default is 2. For **ClearScreen**: 0 clears from the cursor to the end of the screen, 1 from the start to the cursor, 2 the whole screen, and 3 the whole screen and the scrolled-off lines. For **ClearLine**: 0 clears from the cursor to the end of the line, 1 from the start of the line to the cursor, 2 the whole line. + +**ClearScreen** returns CSI *mode* `J` and **ClearLine** returns CSI *mode* `K`. They differ from [**Clear**](cConsole#clear) and [**ClearLine**](cConsole#clearline) of **cConsole**, which fill the screen buffer with the tracked colours through the console functions; the sequences clear with the colours that are in force. Whether a console honours mode 3 depends on the console. + +## Text styles + +### Style functions +{: .no_toc } + +| Function | Returns | Effect | +|---|---|---| +| **Reset** | CSI `0m` | ends every colour and style | +| **Bold** | CSI `1m` | bold, or brighter colour | +| **Dim_** | CSI `2m` | dim text | +| **Italic** | CSI `3m` | italic | +| **Underline** | CSI `4m` | underlined text | +| **Blink** | CSI `5m` | blinking text | +| **Reverse** | CSI `7m` | swaps the text and background colours | +| **Hidden** | CSI `8m` | hidden text | +| **Strikethrough** | CSI `9m` | struck-through text | + +The function for dim text is called **Dim_** because **Dim** is a keyword of the language. Which of these styles a console draws depends on the console: a console may show bold as a brighter colour, or ignore blinking and italics. + +## Colours + +### Colour functions +{: .no_toc } + +The 16 colours of the escape sequences. The foreground sequence ends in `m` and begins CSI; the number is the code in the table. + +| Colour | Foreground function | Code | Background function | Code | +|---|---|---:|---|---:| +| Black | **FgBlack** | 30 | **BgBlack** | 40 | +| Red | **FgRed** | 31 | **BgRed** | 41 | +| Green | **FgGreen** | 32 | **BgGreen** | 42 | +| Yellow | **FgYellow** | 33 | **BgYellow** | 43 | +| Blue | **FgBlue** | 34 | **BgBlue** | 44 | +| Magenta | **FgMagenta** | 35 | **BgMagenta** | 45 | +| Cyan | **FgCyan** | 36 | **BgCyan** | 46 | +| White | **FgWhite** | 37 | **BgWhite** | 47 | +| Default | **FgDefault** | 39 | **BgDefault** | 49 | +| Bright black | **FgBrightBlack** | 90 | **BgBrightBlack** | 100 | +| Bright red | **FgBrightRed** | 91 | **BgBrightRed** | 101 | +| Bright green | **FgBrightGreen** | 92 | **BgBrightGreen** | 102 | +| Bright yellow | **FgBrightYellow** | 93 | **BgBrightYellow** | 103 | +| Bright blue | **FgBrightBlue** | 94 | **BgBrightBlue** | 104 | +| Bright magenta | **FgBrightMagenta** | 95 | **BgBrightMagenta** | 105 | +| Bright cyan | **FgBrightCyan** | 96 | **BgBrightCyan** | 106 | +| Bright white | **FgBrightWhite** | 97 | **BgBrightWhite** | 107 | + +None of these functions has parameters. The order of the colours is that of the ANSI standard, not that of [**ConsoleColor**](Types#consolecolor), whose numbers are those of the Windows console attributes: ANSI red is 1 where **ConsoleColor.DarkRed** is 4. A program that uses both has to map the colours itself. + +### Fg256, Bg256, FgRGB, BgRGB +{: .no_toc } + +Syntax: **Fg256** ( *colorIndex* ) **As String** + +Syntax: **Bg256** ( *colorIndex* ) **As String** + +Syntax: **FgRGB** ( *r* , *g* , *b* ) **As String** + +Syntax: **BgRGB** ( *r* , *g* , *b* ) **As String** + +*colorIndex* +: *required* A **Long**, 0 to 255, an index of the 256-colour palette of the console. + +*r*, *g*, *b* +: *required* **Long** values, 0 to 255, the red, green and blue parts of a 24-bit colour. + +**Fg256** returns CSI `38;5;` *colorIndex* `m` and **Bg256** CSI `48;5;` *colorIndex* `m`. **FgRGB** returns CSI `38;2;` *r* `;` *g* `;` *b* `m` and **BgRGB** CSI `48;2;` *r* `;` *g* `;` *b* `m`. The numbers are written as given and not checked. A console that has no 256-colour or true-colour mode shows the nearest colour or ignores the sequence. + +## Scrolling + +### ScrollUp, ScrollDown +{: .no_toc } + +Syntax: **ScrollUp** ( [ *n* ] ) **As String** + +Syntax: **ScrollDown** ( [ *n* ] ) **As String** + +*n* +: *optional* A **Long**, the number of lines. The default is 1. + +**ScrollUp** returns CSI *n* `S`, which scrolls the text up, and **ScrollDown** returns CSI *n* `T`, which scrolls it down. + +## Window and screen + +### SetTitle +{: .no_toc } + +Returns the sequence that sets the title of the window. + +Syntax: **SetTitle** ( *title* ) **As String** + +*title* +: *required* A **String**. + +Returns `ESC ] 0 ;` *title* and the bell character (code 7). The title of the console window can also be set with the [**Title**](cConsole#title) property of **cConsole**, which calls a Windows function and does not need the virtual-terminal mode. + +### AltScreenOn, AltScreenOff +{: .no_toc } + +Syntax: **AltScreenOn** **As String** + +Syntax: **AltScreenOff** **As String** + +**AltScreenOn** returns CSI `?1049h`, which switches to the alternate screen buffer, a second screen that leaves the text of the main one intact; **AltScreenOff** returns CSI `?1049l`, which switches back. The full-screen mode of [**cConsole**](cConsole#enterfullscreenmode) does not use the alternate buffer. In a console with virtual-terminal processing on, **AltScreenOn** shows an empty screen, the text written afterwards goes to it, and **AltScreenOff** brings back the earlier text. A program that wants its picture to vanish when it ends writes **AltScreenOn** first and **AltScreenOff** last. + +### Hyperlink +{: .no_toc } + +Returns the sequence for a text that links to a web address. + +Syntax: **Hyperlink** ( *url* , *text* ) **As String** + +*url* +: *required* A **String**, the address. + +*text* +: *required* A **String**, the visible text. + +Returns `ESC ] 8 ; ;` *url* and the bell character, then *text*, then `ESC ] 8 ; ;` and the bell character. A console that supports this operating system command (OSC 8) shows *text* as a link; another console shows *text* only, or the characters of the sequence. + +## Styled text + +### Styled +{: .no_toc } + +Wraps text in a style and ends the style after it. + +Syntax: **Styled** ( *text* , *style* ) **As String** + +*text* +: *required* A **String**. + +*style* +: *required* A **String**, the sequences to put before the text, such as `mConsoleStyle.Bold & mConsoleStyle.FgCyan`. + +Returns *style*, *text* and **Reset**. + +### Colored +{: .no_toc } + +Wraps text in a foreground colour, and optionally a background colour, and ends them after it. + +Syntax: **Colored** ( *text* , *fgColor* [ , *bgColor* ] ) **As String** + +*text* +: *required* A **String**. + +*fgColor* +: *required* A **String**, a foreground sequence such as `mConsoleStyle.FgRed`. + +*bgColor* +: *optional* A **String**, a background sequence. The default is an empty string, which leaves the background alone. + +Returns *fgColor*, *bgColor*, *text* and **Reset**. The function does not check that the two strings are colour sequences; any strings are joined. + +### ColoredRGB +{: .no_toc } + +Wraps text in a true-colour foreground, and optionally a true-colour background. + +Syntax: **ColoredRGB** ( *text* , *fgR* , *fgG* , *fgB* [ , *bgR* [ , *bgG* [ , *bgB* ] ] ] ) **As String** + +*text* +: *required* A **String**. + +*fgR*, *fgG*, *fgB* +: *required* **Long** values, 0 to 255, the foreground colour. + +*bgR*, *bgG*, *bgB* +: *optional* **Long** values, 0 to 255, the background colour. They default to -1. The background is used only when all three are 0 or more. + +Returns **FgRGB**, the background sequence when all three background values are given, *text*, and **Reset**. + +## Text helpers + +These functions count characters with **Len**, so they are correct for text that holds no escape sequences and no characters that the console draws double width. [**StripAnsi**](#stripansi) removes the sequences from a text first. + +### CenterText +{: .no_toc } + +Pads text to a width, with the text in the middle. + +Syntax: **CenterText** ( *text* , *width* [ , *fillChar* ] ) **As String** + +*text* +: *required* A **String**. + +*width* +: *required* A **Long**, the length of the result. + +*fillChar* +: *optional* A **String** whose first character is used to pad. The default is a space. + +Returns a string *width* characters long. The padding on the left is `(width - Len(text)) \ 2` characters, and the rest goes to the right, so an odd amount of padding gives the extra character to the right. Text that is as long as *width* or longer is cut to *width* characters. + +### LeftAlign +{: .no_toc } + +Pads text on the right to a width. + +Syntax: **LeftAlign** ( *text* , *width* [ , *fillChar* ] ) **As String** + +Parameters as for [**CenterText**](#centertext). Text that is as long as *width* or longer is cut to *width* characters. + +### RightAlign +{: .no_toc } + +Pads text on the left to a width. + +Syntax: **RightAlign** ( *text* , *width* [ , *fillChar* ] ) **As String** + +Parameters as for [**CenterText**](#centertext). Text that is as long as *width* or longer is cut to its first *width* characters, not its last. + +### RepeatStr +{: .no_toc } + +Intended to repeat a string a number of times. + +Syntax: **RepeatStr** ( *s* , *count* ) **As String** + +*s* +: *required* A **String**. + +*count* +: *required* A **Long**. A value of 0 or less returns an empty string. + +The function is implemented with `String$(count, s)`, which repeats only the first character of *s*. `RepeatStr("ab", 3)` therefore returns `aaa`, and not `ababab`. For a one-character *s* the result is as expected. To repeat a longer string, build it with a loop or with `Replace(Space$(count), " ", s)`. + +### StripAnsi +{: .no_toc } + +Removes escape sequences from text. + +Syntax: **StripAnsi** ( *text* ) **As String** + +*text* +: *required* A **String**. + +The function drops every character from an `ESC` character up to and including the next letter among `m`, `H`, `J`, `K`, `A`, `B`, `C` and `D`, and returns the rest. That covers the colour and style sequences (they end in `m`) and the cursor positioning and clearing sequences of this module that end in one of those letters. A sequence that ends in another letter, such as `l` of **CursorHide** and `h` of **CursorShow**, `s` and `u` of the save and restore, `S` and `T` of the scroll functions, `E`, `F` and `G` of the line functions, `?1049h` of **AltScreenOn**, or the bell character that ends **SetTitle** and **Hyperlink**, is not recognised: the function goes on dropping characters until it meets one of the eight letters, so some of the text that follows is lost too. + +```tb check_build check_run +Debug.Print mConsoleStyle.StripAnsi(mConsoleStyle.Colored("Hello", mConsoleStyle.FgRed)) +Debug.Print mConsoleStyle.StripAnsi(mConsoleStyle.CursorHide & "Hello") +Debug.Print mConsoleStyle.RepeatStr("ab", 3) +' Output: +' Hello +' ello +' aaa +``` + +The second line shows the loss: the sequence of **CursorHide** ends in `l`, so the function skips on to the `H` of `Hello`. + +### Truncate +{: .no_toc } + +Shortens text to a length, with a mark at the end. + +Syntax: **Truncate** ( *text* , *maxLen* [ , *ellipsis* ] ) **As String** + +*text* +: *required* A **String**. + +*maxLen* +: *required* A **Long**, the longest result. + +*ellipsis* +: *optional* A **String**, the mark that shows the text was cut. The default is three periods. + +Returns *text* when it is no longer than *maxLen*. Otherwise it returns the first `maxLen - Len(ellipsis)` characters of *text* followed by *ellipsis*; when *maxLen* is not larger than the length of *ellipsis*, it returns the first *maxLen* characters of *ellipsis*. + +### WordWrap +{: .no_toc } + +Breaks text into lines of at most a width, at spaces. + +Syntax: **WordWrap** ( *text* , *width* ) **As String** + +*text* +: *required* A **String**. + +*width* +: *required* A **Long**, the longest line in characters. + +Splits *text* at the space characters and joins the words into lines, with a line feed (code 10) between lines. A word that is longer than *width* is not broken; it is put on a line of its own. Other characters, such as line breaks inside *text*, count as part of a word. + +```tb check_build check_run +Dim lines() As String +Dim i As Long + +lines = Split(mConsoleStyle.WordWrap("one two three four", 9), vbLf) +For i = 0 To UBound(lines) + Debug.Print "[" & mConsoleStyle.CenterText(lines(i), 9) & "]" +Next +' Output: +' [ one two ] +' [ three ] +' [ four ] +``` + +## Characters + +The module holds **String** constants for drawing text with characters that are not on the keyboard. Each is one character, and is written with the name of the module, as in `mConsoleStyle.BOX_HORIZONTAL`. The font of the console has to contain the character; otherwise the console draws a substitute. + +### Box-drawing characters +{: .no_toc } + +| Single line | | Double line | | +|---|---|---|---| +| **BOX_HORIZONTAL** | U+2500 | **BOX_D_HORIZONTAL** | U+2550 | +| **BOX_VERTICAL** | U+2502 | **BOX_D_VERTICAL** | U+2551 | +| **BOX_TOP_LEFT** | U+250C | **BOX_D_TOP_LEFT** | U+2554 | +| **BOX_TOP_RIGHT** | U+2510 | **BOX_D_TOP_RIGHT** | U+2557 | +| **BOX_BOTTOM_LEFT** | U+2514 | **BOX_D_BOTTOM_LEFT** | U+255A | +| **BOX_BOTTOM_RIGHT** | U+2518 | **BOX_D_BOTTOM_RIGHT** | U+255D | +| **BOX_T_LEFT** | U+251C | **BOX_D_T_LEFT** | U+2560 | +| **BOX_T_RIGHT** | U+2524 | **BOX_D_T_RIGHT** | U+2563 | +| **BOX_T_TOP** | U+252C | **BOX_D_T_TOP** | U+2566 | +| **BOX_T_BOTTOM** | U+2534 | **BOX_D_T_BOTTOM** | U+2569 | +| **BOX_CROSS** | U+253C | **BOX_D_CROSS** | U+256C | + +### Block, progress and arrow characters +{: .no_toc } + +| Constant | Code point | Constant | Code point | +|---|---|---|---| +| **BLOCK_FULL** | U+2588 | **ARROW_UP** | U+2191 | +| **BLOCK_UPPER_HALF** | U+2580 | **ARROW_DOWN** | U+2193 | +| **BLOCK_LOWER_HALF** | U+2584 | **ARROW_LEFT** | U+2190 | +| **BLOCK_LEFT_HALF** | U+258C | **ARROW_RIGHT** | U+2192 | +| **BLOCK_RIGHT_HALF** | U+2590 | **ARROW_UP_DOWN** | U+2195 | +| **BLOCK_LIGHT** | U+2591 | **ARROW_LEFT_RIGHT** | U+2194 | +| **BLOCK_MEDIUM** | U+2592 | **PROGRESS_FILLED** | U+2588 | +| **BLOCK_DARK** | U+2593 | **PROGRESS_EMPTY** | U+2591 | + +### Symbol characters +{: .no_toc } + +| Constant | Code point | Constant | Code point | +|---|---|---|---| +| **CHECK_MARK** | U+2713 | **SQUARE** | U+25A0 | +| **CROSS_MARK** | U+2717 | **SQUARE_EMPTY** | U+25A1 | +| **BULLET** | U+2022 | **STAR** | U+2605 | +| **DIAMOND** | U+25C6 | **STAR_EMPTY** | U+2606 | +| **CIRCLE** | U+25CF | **CIRCLE_EMPTY** | U+25CB | + +## See Also + +- [cConsole](cConsole) class -- **EnableVirtualTerminal**, and the members that write the strings +- [Types](Types) -- **ConsoleColor**, whose numbers differ from the ANSI colour numbers +- [TBMANLIB_ConsoleCore](./) package diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index dd6a06efe..801eb270c 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -16,6 +16,8 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.2 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. +- [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 0.0.0.0 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore. +- [**TBMANLIB_ConsoleCore**](../Contributed/TBMANLIB_ConsoleCore/) -- a console library with colours, UTF-8 and a full-screen mode. Version 0.0.0.0 compiles, and it reads keys wrongly and has several helpers that give wrong results. - [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 0.0.0.0 compiles, and its text report, its **Dictionary** class and several of its list and file helpers fail or give wrong results. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.0 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 0.0.0.0 does not compile in twinBASIC BETA 1005. diff --git a/docs/_book.yml b/docs/_book.yml index 06c6fbe0e..0640276a1 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -634,6 +634,20 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB_ConsoleComponents Package + subtitle: Text-interface components for console programs, drawn into the buffer of TBMANLIB_ConsoleCore + landing_page: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/ + page: /tB/Packages/Contributed/TBMANLIB_ConsoleComponents/ + landing_is_target: true + outline_closed: true + + - title: TBMANLIB_ConsoleCore Package + subtitle: A console library with colours, UTF-8 and a full-screen mode + landing_page: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/ + page: /tB/Packages/Contributed/TBMANLIB_ConsoleCore/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Diagnostic Package subtitle: A diagnostic report of the computer and its processes, as text, JSON or HTML landing_page: /tB/Packages/Contributed/TBMANLIB_Diagnostic/ diff --git a/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json b/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json new file mode 100644 index 000000000..d0c38e4ec --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB_ConsoleComponents/api.json @@ -0,0 +1,320 @@ +{ + "format": 3, + "package": "TBMANLIB_ConsoleComponents", + "project": "TBMANLIB_ConsoleComponents", + "version": "0.0.0.0", + "types": [ + { + "name": "cColorHelper", + "kind": "class", + "vis": "public", + "file": "Components/Helpers/cColorHelper.twin", + "signature": "Public Class cColorHelper", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"BrightenColor","kind":"function","vis":"public","signature":"Public Function BrightenColor(ByVal color As ConsoleColor) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"CPUUsageColor","kind":"function","vis":"public","signature":"Public Function CPUUsageColor(ByVal usage As Double) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"CycleColor","kind":"function","vis":"public","signature":"Public Function CycleColor(ByVal index As Long, ByRef colors() As ConsoleColor) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"DimColor","kind":"function","vis":"public","signature":"Public Function DimColor(ByVal color As ConsoleColor) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"DiskUsageColor","kind":"function","vis":"public","signature":"Public Function DiskUsageColor(ByVal usage As Double) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"GetContrastColor","kind":"function","vis":"public","signature":"Public Function GetContrastColor(ByVal bgColor As ConsoleColor) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"GradientColor","kind":"function","vis":"public","signature":"Public Function GradientColor(ByVal value As Double, ByVal min As Double, ByVal max As Double, ByVal color1 As ConsoleColor, ByVal color2 As ConsoleColor, ByVal color3 As ConsoleColor) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"HeatmapColor","kind":"function","vis":"public","signature":"Public Function HeatmapColor(ByVal value As Double, ByVal min As Double, ByVal max As Double, Optional ByVal lowColor As ConsoleColor = ConsoleColor.Green, Optional ByVal midColor As ConsoleColor = ConsoleColor.Yellow, Optional ByVal highColor As ConsoleColor = ConsoleColor.Red) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"IntensityColor","kind":"function","vis":"public","signature":"Public Function IntensityColor(ByVal value As Double, ByVal min As Double, ByVal max As Double, Optional ByVal baseColor As ConsoleColor = ConsoleColor.Gray) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"IsDarkColor","kind":"function","vis":"public","signature":"Public Function IsDarkColor(ByVal color As ConsoleColor) As Boolean","returnType":"Boolean"}, + {"name":"MemoryUsageColor","kind":"function","vis":"public","signature":"Public Function MemoryUsageColor(ByVal usage As Double) As ConsoleColor","returnType":"ConsoleColor"} + ] + }, + { + "name": "cConsoleLayout", + "kind": "class", + "vis": "public", + "file": "Components/Panels/cConsoleLayout.twin", + "signature": "Public Class cConsoleLayout", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AddPanel","kind":"function","vis":"public","signature":"Public Function AddPanel(ByVal name As String, ByVal left As Long, ByVal top As Long, ByVal width As Long, ByVal height As Long, Optional ByVal title As String = \"\") As cConsolePanel","returnType":"cConsolePanel"}, + {"name":"AddPanelByRect","kind":"function","vis":"public","signature":"Public Function AddPanelByRect(ByVal name As String, ByVal rect As ConsoleRect, Optional ByVal title As String = \"\") As cConsolePanel","returnType":"cConsolePanel"}, + {"name":"AutoResize","kind":"sub","vis":"public","signature":"Public Sub AutoResize()"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"ClearAllContent","kind":"sub","vis":"public","signature":"Public Sub ClearAllContent(Optional ByVal theme As cConsoleTheme = Nothing)"}, + {"name":"ClearPanelContent","kind":"sub","vis":"public","signature":"Public Sub ClearPanelContent(ByVal name As String, Optional ByVal theme As cConsoleTheme = Nothing)"}, + {"name":"CreateGrid","kind":"function","vis":"public","signature":"Public Function CreateGrid(ByVal cols As Long, ByVal rows As Long, ByVal namePrefix As String) As Variant","returnType":"Variant"}, + {"name":"CreateHeaderFooter","kind":"function","vis":"public","signature":"Public Function CreateHeaderFooter(ByVal headerHeight As Long, ByVal footerHeight As Long, ByVal headerName As String, ByVal contentName As String, ByVal footerName As String) As Variant","returnType":"Variant"}, + {"name":"CreateSidebar","kind":"function","vis":"public","signature":"Public Function CreateSidebar(ByVal sidebarWidth As Long, ByVal sidebarName As String, ByVal contentName As String) As Variant","returnType":"Variant"}, + {"name":"GetAllPanels","kind":"function","vis":"public","signature":"Public Function GetAllPanels() As Variant","returnType":"Variant"}, + {"name":"GetPanel","kind":"function","vis":"public","signature":"Public Function GetPanel(ByVal name As String) As cConsolePanel","returnType":"cConsolePanel"}, + {"name":"GetPanelNames","kind":"function","vis":"public","signature":"Public Function GetPanelNames() As Variant","returnType":"Variant"}, + {"name":"HasPanel","kind":"function","vis":"public","signature":"Public Function HasPanel(ByVal name As String) As Boolean","returnType":"Boolean"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"PanelCount","kind":"property","vis":"public","signature":"Public Property Get PanelCount() As Long","returnType":"Long","accessor":"Get"}, + {"name":"RemovePanel","kind":"sub","vis":"public","signature":"Public Sub RemovePanel(ByVal name As String)"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(Optional ByVal theme As cConsoleTheme = Nothing)"}, + {"name":"RenderPanel","kind":"sub","vis":"public","signature":"Public Sub RenderPanel(ByVal name As String, Optional ByVal theme As cConsoleTheme = Nothing)"}, + {"name":"Resize","kind":"sub","vis":"public","signature":"Public Sub Resize(ByVal newWidth As Long, ByVal newHeight As Long)"}, + {"name":"SplitHorizontal","kind":"function","vis":"public","signature":"Public Function SplitHorizontal(ByVal parentName As String, ByVal ratio As Double, ByVal leftName As String, ByVal rightName As String) As Variant","returnType":"Variant"}, + {"name":"SplitVertical","kind":"function","vis":"public","signature":"Public Function SplitVertical(ByVal parentName As String, ByVal ratio As Double, ByVal topName As String, ByVal bottomName As String) As Variant","returnType":"Variant"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"}, + {"name":"m_ConsoleHeight","kind":"field","vis":"private","signature":"Private m_ConsoleHeight As Long"}, + {"name":"m_ConsoleWidth","kind":"field","vis":"private","signature":"Private m_ConsoleWidth As Long"}, + {"name":"m_Initialized","kind":"field","vis":"private","signature":"Private m_Initialized As Boolean"}, + {"name":"m_Panels","kind":"field","vis":"private","signature":"Private m_Panels As Collection"}, + {"name":"m_PanelsDict","kind":"field","vis":"private","signature":"Private m_PanelsDict As Object"}, + {"name":"m_RootPanel","kind":"field","vis":"private","signature":"Private m_RootPanel As cConsolePanel"} + ] + }, + { + "name": "cConsolePanel", + "kind": "class", + "vis": "public", + "file": "Components/Panels/cConsolePanel.twin", + "signature": "Public Class cConsolePanel", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"BorderColor","kind":"field","vis":"public","signature":"Public BorderColor As ConsoleColor"}, + {"name":"BorderStyle","kind":"field","vis":"public","signature":"Public BorderStyle As PanelBorderStyle"}, + {"name":"ClearContent","kind":"sub","vis":"public","signature":"Public Sub ClearContent(ByVal buffer As cConsoleBuffer, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"ContentHeight","kind":"function","vis":"public","signature":"Public Function ContentHeight() As Long","returnType":"Long"}, + {"name":"ContentRect","kind":"function","vis":"public","signature":"Public Function ContentRect() As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"ContentWidth","kind":"function","vis":"public","signature":"Public Function ContentWidth() As Long","returnType":"Long"}, + {"name":"DrawBoldBorder","kind":"sub","vis":"private","signature":"Private Sub DrawBoldBorder(ByVal buffer As cConsoleBuffer, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"DrawDoubleBorder","kind":"sub","vis":"private","signature":"Private Sub DrawDoubleBorder(ByVal buffer As cConsoleBuffer, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"DrawRoundedBorder","kind":"sub","vis":"private","signature":"Private Sub DrawRoundedBorder(ByVal buffer As cConsoleBuffer, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"DrawSingleBorder","kind":"sub","vis":"private","signature":"Private Sub DrawSingleBorder(ByVal buffer As cConsoleBuffer, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"DrawTitle","kind":"sub","vis":"private","signature":"Private Sub DrawTitle(ByVal buffer As cConsoleBuffer)"}, + {"name":"Move","kind":"sub","vis":"public","signature":"Public Sub Move(ByVal left As Long, ByVal top As Long)"}, + {"name":"Name","kind":"field","vis":"public","signature":"Public Name As String"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(Optional ByVal name As String = \"\", Optional ByVal left As Long = 0, Optional ByVal top As Long = 0, Optional ByVal width As Long = 10, Optional ByVal height As Long = 5)"}, + {"name":"Padding","kind":"field","vis":"public","signature":"Public Padding As Long"}, + {"name":"Rect","kind":"field","vis":"public","signature":"Public Rect As ConsoleRect"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(ByVal buffer As cConsoleBuffer, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"Resize","kind":"sub","vis":"public","signature":"Public Sub Resize(ByVal width As Long, ByVal height As Long)"}, + {"name":"Title","kind":"field","vis":"public","signature":"Public Title As String"}, + {"name":"TitleColor","kind":"field","vis":"public","signature":"Public TitleColor As ConsoleColor"} + ] + }, + { + "name": "PanelBorderStyle", + "kind": "enum", + "in": "cConsolePanel", + "vis": "public", + "file": "Components/Panels/cConsolePanel.twin", + "signature": "Public Enum PanelBorderStyle", + "values": [ + {"name":"None","value":"0"}, + {"name":"Single","value":"1"}, + {"name":"Double","value":"2"}, + {"name":"Rounded","value":"3"}, + {"name":"Bold","value":"4"} + ] + }, + { + "name": "cConsoleTheme", + "kind": "class", + "vis": "public", + "file": "Components/Themes/cConsoleTheme.twin", + "signature": "Public Class cConsoleTheme", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Background","kind":"field","vis":"public","signature":"Public Background As ConsoleColor"}, + {"name":"Border","kind":"field","vis":"public","signature":"Public Border As ConsoleColor"}, + {"name":"Clone","kind":"function","vis":"public","signature":"Public Function Clone() As cConsoleTheme","returnType":"cConsoleTheme"}, + {"name":"Error","kind":"field","vis":"public","signature":"Public Error As ConsoleColor"}, + {"name":"Foreground","kind":"field","vis":"public","signature":"Public Foreground As ConsoleColor"}, + {"name":"GetColor","kind":"function","vis":"public","signature":"Public Function GetColor(ByVal colorType As ThemeColorType) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"HeaderBackground","kind":"field","vis":"public","signature":"Public HeaderBackground As ConsoleColor"}, + {"name":"HeaderForeground","kind":"field","vis":"public","signature":"Public HeaderForeground As ConsoleColor"}, + {"name":"Info","kind":"field","vis":"public","signature":"Public Info As ConsoleColor"}, + {"name":"Muted","kind":"field","vis":"public","signature":"Public Muted As ConsoleColor"}, + {"name":"Primary","kind":"field","vis":"public","signature":"Public Primary As ConsoleColor"}, + {"name":"Secondary","kind":"field","vis":"public","signature":"Public Secondary As ConsoleColor"}, + {"name":"SelectedBackground","kind":"field","vis":"public","signature":"Public SelectedBackground As ConsoleColor"}, + {"name":"SelectedForeground","kind":"field","vis":"public","signature":"Public SelectedForeground As ConsoleColor"}, + {"name":"Success","kind":"field","vis":"public","signature":"Public Success As ConsoleColor"}, + {"name":"Warning","kind":"field","vis":"public","signature":"Public Warning As ConsoleColor"} + ] + }, + { + "name": "cDataGrid", + "kind": "class", + "vis": "public", + "file": "Components/Controls/cDataGrid.twin", + "signature": "Public Class cDataGrid", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"DrawRow","kind":"sub","vis":"private","signature":"Private Sub DrawRow(ByVal col As Long, ByVal row As Long, ByVal text As String, ByVal fore As ConsoleColor, ByVal back As ConsoleColor, ByVal isHeader As Boolean)"}, + {"name":"DrawSeparator","kind":"sub","vis":"private","signature":"Private Sub DrawSeparator(ByVal col As Long, ByVal row As Long, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"GetRowCount","kind":"function","vis":"public","signature":"Public Function GetRowCount() As Long","returnType":"Long"}, + {"name":"HeaderColor","kind":"property","vis":"public","signature":"Public Property Let HeaderColor(ByVal value As ConsoleColor)","accessor":"Let"}, + {"name":"MoveDown","kind":"sub","vis":"public","signature":"Public Sub MoveDown(ByVal maxRow As Long)"}, + {"name":"MoveUp","kind":"sub","vis":"public","signature":"Public Sub MoveUp()"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(ByVal col As Long, ByVal row As Long, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"ScrollTo","kind":"sub","vis":"public","signature":"Public Sub ScrollTo(ByVal row As Long)"}, + {"name":"SelectedRow","kind":"property","vis":"public","signature":"Public Property Let SelectedRow(ByVal value As Long)","accessor":"Let"}, + {"name":"SelectionColor","kind":"property","vis":"public","signature":"Public Property Let SelectionColor(ByVal value As ConsoleColor)","accessor":"Let"}, + {"name":"SetColumnAlignments","kind":"sub","vis":"public","signature":"Public Sub SetColumnAlignments(ByRef alignments() As ColumnAlignment)"}, + {"name":"SetColumnWidths","kind":"sub","vis":"public","signature":"Public Sub SetColumnWidths(ByRef widths() As Long)"}, + {"name":"SetRowColors","kind":"sub","vis":"public","signature":"Public Sub SetRowColors(ByRef colors() As ConsoleColor)"}, + {"name":"SetRows","kind":"sub","vis":"public","signature":"Public Sub SetRows(ByRef rows() As String)"}, + {"name":"ShowBorders","kind":"property","vis":"public","signature":"Public Property Let ShowBorders(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowHeader","kind":"property","vis":"public","signature":"Public Property Let ShowHeader(ByVal value As Boolean)","accessor":"Let"}, + {"name":"SplitText","kind":"sub","vis":"private","signature":"Private Sub SplitText(ByVal text As String, ByRef columns() As String, ByVal maxCols As Long)"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As cConsoleBuffer"}, + {"name":"m_ColAlignments","kind":"field","vis":"private","signature":"Private m_ColAlignments() As ColumnAlignment"}, + {"name":"m_ColWidths","kind":"field","vis":"private","signature":"Private m_ColWidths() As Long"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"}, + {"name":"m_HeaderColor","kind":"field","vis":"private","signature":"Private m_HeaderColor As ConsoleColor"}, + {"name":"m_HeaderRow","kind":"field","vis":"private","signature":"Private m_HeaderRow As Boolean"}, + {"name":"m_RowColors","kind":"field","vis":"private","signature":"Private m_RowColors() As ConsoleColor"}, + {"name":"m_Rows","kind":"field","vis":"private","signature":"Private m_Rows() As String"}, + {"name":"m_SelectedRow","kind":"field","vis":"private","signature":"Private m_SelectedRow As Long"}, + {"name":"m_SelectionColor","kind":"field","vis":"private","signature":"Private m_SelectionColor As ConsoleColor"}, + {"name":"m_ShowBorders","kind":"field","vis":"private","signature":"Private m_ShowBorders As Boolean"} + ] + }, + { + "name": "ColumnAlignment", + "kind": "enum", + "in": "cDataGrid", + "vis": "public", + "file": "Components/Controls/cDataGrid.twin", + "signature": "Public Enum ColumnAlignment", + "values": [ + {"name":"Left","value":"0"}, + {"name":"Center","value":"1"}, + {"name":"Right","value":"2"} + ] + }, + { + "name": "cLayoutHelper", + "kind": "class", + "vis": "public", + "file": "Components/Helpers/cLayoutHelper.twin", + "signature": "Public Class cLayoutHelper", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Center","kind":"function","vis":"public","signature":"Public Function Center(ByVal width As Long, ByVal height As Long) As ConsolePosition","returnType":"ConsolePosition"}, + {"name":"CenterRect","kind":"function","vis":"public","signature":"Public Function CenterRect(ByVal width As Long, ByVal height As Long) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"CenterX","kind":"function","vis":"public","signature":"Public Function CenterX(ByVal width As Long) As Long","returnType":"Long"}, + {"name":"CenterY","kind":"function","vis":"public","signature":"Public Function CenterY(ByVal height As Long) As Long","returnType":"Long"}, + {"name":"ClampRect","kind":"function","vis":"public","signature":"Public Function ClampRect(ByVal rect As ConsoleRect) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"EvenColumns","kind":"function","vis":"public","signature":"Public Function EvenColumns(ByVal count As Long, ByVal margin As Long) As Variant","returnType":"Variant"}, + {"name":"EvenRows","kind":"function","vis":"public","signature":"Public Function EvenRows(ByVal count As Long, ByVal margin As Long) As Variant","returnType":"Variant"}, + {"name":"GetRectHeight","kind":"function","vis":"public","signature":"Public Function GetRectHeight(ByVal rect As ConsoleRect) As Long","returnType":"Long"}, + {"name":"GetRectWidth","kind":"function","vis":"public","signature":"Public Function GetRectWidth(ByVal rect As ConsoleRect) As Long","returnType":"Long"}, + {"name":"Grid","kind":"function","vis":"public","signature":"Public Function Grid(ByVal cols As Long, ByVal rows As Long) As Variant","returnType":"Variant"}, + {"name":"HeaderFooter","kind":"function","vis":"public","signature":"Public Function HeaderFooter(ByVal headerHeight As Long, ByVal footerHeight As Long) As Variant","returnType":"Variant"}, + {"name":"IsValidRect","kind":"function","vis":"public","signature":"Public Function IsValidRect(ByVal rect As ConsoleRect) As Boolean","returnType":"Boolean"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"Percent","kind":"function","vis":"public","signature":"Public Function Percent(ByVal percentX As Double, ByVal percentY As Double) As ConsolePosition","returnType":"ConsolePosition"}, + {"name":"PercentRect","kind":"function","vis":"public","signature":"Public Function PercentRect(ByVal leftPercent As Double, ByVal topPercent As Double, ByVal widthPercent As Double, ByVal heightPercent As Double) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"PercentX","kind":"function","vis":"public","signature":"Public Function PercentX(ByVal percent As Double) As Long","returnType":"Long"}, + {"name":"PercentY","kind":"function","vis":"public","signature":"Public Function PercentY(ByVal percent As Double) As Long","returnType":"Long"}, + {"name":"Sidebar","kind":"function","vis":"public","signature":"Public Function Sidebar(ByVal sidebarWidth As Long) As Variant","returnType":"Variant"}, + {"name":"SpacedPositions","kind":"function","vis":"public","signature":"Public Function SpacedPositions(ByVal count As Long, ByVal itemWidth As Long, ByVal spacing As Long, ByVal startX As Long) As Variant","returnType":"Variant"}, + {"name":"SplitHorizontal","kind":"function","vis":"public","signature":"Public Function SplitHorizontal(ByVal topPercent As Double) As Variant","returnType":"Variant"}, + {"name":"SplitVertical","kind":"function","vis":"public","signature":"Public Function SplitVertical(ByVal leftPercent As Double) As Variant","returnType":"Variant"}, + {"name":"WithMargins","kind":"function","vis":"public","signature":"Public Function WithMargins(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"} + ] + }, + { + "name": "cProgressBar", + "kind": "class", + "vis": "public", + "file": "Components/Controls/cProgressBar.twin", + "signature": "Public Class cProgressBar", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"GetProgressColor","kind":"function","vis":"public","signature":"Public Function GetProgressColor(ByVal percent As Double, Optional ByVal lowColor As ConsoleColor = ConsoleColor.Green, Optional ByVal midColor As ConsoleColor = ConsoleColor.Yellow, Optional ByVal highColor As ConsoleColor = ConsoleColor.Red) As ConsoleColor","returnType":"ConsoleColor"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal percent As Double, Optional ByVal filledChar As String = \"█\", Optional ByVal emptyChar As String = \"░\", Optional ByVal filledColor As ConsoleColor = ConsoleColor.Green, Optional ByVal emptyColor As ConsoleColor = ConsoleColor.DarkGray)"}, + {"name":"RenderVertical","kind":"sub","vis":"public","signature":"Public Sub RenderVertical(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByVal percent As Double, Optional ByVal filledChar As String = \"█\", Optional ByVal emptyChar As String = \"░\", Optional ByVal filledColor As ConsoleColor = ConsoleColor.Green, Optional ByVal emptyColor As ConsoleColor = ConsoleColor.DarkGray)"}, + {"name":"RenderWithAutoColor","kind":"sub","vis":"public","signature":"Public Sub RenderWithAutoColor(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal percent As Double, Optional ByVal filledChar As String = \"█\", Optional ByVal emptyChar As String = \"░\", Optional ByVal emptyColor As ConsoleColor = ConsoleColor.DarkGray, Optional ByVal lowColor As ConsoleColor = ConsoleColor.Green, Optional ByVal midColor As ConsoleColor = ConsoleColor.Yellow, Optional ByVal highColor As ConsoleColor = ConsoleColor.Red)"}, + {"name":"RenderWithBrackets","kind":"sub","vis":"public","signature":"Public Sub RenderWithBrackets(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal percent As Double, Optional ByVal filledChar As String = \"█\", Optional ByVal emptyChar As String = \"░\", Optional ByVal filledColor As ConsoleColor = ConsoleColor.Green, Optional ByVal emptyColor As ConsoleColor = ConsoleColor.DarkGray, Optional ByVal bracketColor As ConsoleColor = ConsoleColor.Gray)"}, + {"name":"RenderWithLabel","kind":"sub","vis":"public","signature":"Public Sub RenderWithLabel(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal percent As Double, ByVal label As String, Optional ByVal filledChar As String = \"█\", Optional ByVal emptyChar As String = \"░\", Optional ByVal filledColor As ConsoleColor = ConsoleColor.Green, Optional ByVal emptyColor As ConsoleColor = ConsoleColor.DarkGray, Optional ByVal labelColor As ConsoleColor = ConsoleColor.Gray)"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As cConsoleBuffer"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"} + ] + }, + { + "name": "cSparkline", + "kind": "class", + "vis": "public", + "file": "Components/Controls/cSparkline.twin", + "signature": "Public Class cSparkline", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal height As Long, ByRef values() As Double, Optional ByVal minValue As Double = 0, Optional ByVal maxValue As Double = 100, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charSet As SparklineCharSet = SparklineCharSet.Braille)"}, + {"name":"RenderASCII","kind":"sub","vis":"private","signature":"Private Sub RenderASCII(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal height As Long, ByRef values() As Double, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"RenderBlock","kind":"sub","vis":"private","signature":"Private Sub RenderBlock(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal height As Long, ByRef values() As Double, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"RenderBraille","kind":"sub","vis":"private","signature":"Private Sub RenderBraille(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal height As Long, ByRef values() As Double, ByVal fore As ConsoleColor, ByVal back As ConsoleColor)"}, + {"name":"RenderWithStats","kind":"sub","vis":"public","signature":"Public Sub RenderWithStats(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal height As Long, ByRef values() As Double, Optional ByVal minValue As Double = 0, Optional ByVal maxValue As Double = 100, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charSet As SparklineCharSet = SparklineCharSet.Braille, Optional ByVal statColor As ConsoleColor = ConsoleColor.Gray)"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As cConsoleBuffer"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"} + ] + }, + { + "name": "SparklineCharSet", + "kind": "enum", + "in": "cSparkline", + "vis": "public", + "file": "Components/Controls/cSparkline.twin", + "signature": "Public Enum SparklineCharSet", + "values": [ + {"name":"Braille","value":"0"}, + {"name":"ASCII","value":"1"}, + {"name":"Block","value":"2"} + ] + }, + { + "name": "cVBar", + "kind": "class", + "vis": "public", + "file": "Components/Controls/cVBar.twin", + "signature": "Public Class cVBar", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal console As cConsole)"}, + {"name":"Render","kind":"sub","vis":"public","signature":"Public Sub Render(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByVal percent As Double, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charStyle As BarCharStyle = BarCharStyle.Block)"}, + {"name":"RenderBars","kind":"sub","vis":"public","signature":"Public Sub RenderBars(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByRef values() As Double, Optional ByVal spacing As Long = 1, Optional ByVal minValue As Double = 0, Optional ByVal maxValue As Double = 100, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charStyle As BarCharStyle = BarCharStyle.Block)"}, + {"name":"RenderHistory","kind":"sub","vis":"public","signature":"Public Sub RenderHistory(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByRef history() As Double, Optional ByVal minValue As Double = 0, Optional ByVal maxValue As Double = 100, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charStyle As BarCharStyle = BarCharStyle.Block)"}, + {"name":"RenderWithGradient","kind":"sub","vis":"public","signature":"Public Sub RenderWithGradient(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByRef values() As Double, Optional ByVal spacing As Long = 1, Optional ByVal minValue As Double = 0, Optional ByVal maxValue As Double = 100, Optional ByVal lowColor As ConsoleColor = ConsoleColor.Green, Optional ByVal midColor As ConsoleColor = ConsoleColor.Yellow, Optional ByVal highColor As ConsoleColor = ConsoleColor.Red, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charStyle As BarCharStyle = BarCharStyle.Block)"}, + {"name":"RenderWithLabel","kind":"sub","vis":"public","signature":"Public Sub RenderWithLabel(ByVal col As Long, ByVal row As Long, ByVal height As Long, ByVal percent As Double, ByVal label As String, Optional ByVal fore As ConsoleColor = ConsoleColor.Green, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal charStyle As BarCharStyle = BarCharStyle.Block, Optional ByVal labelColor As ConsoleColor = ConsoleColor.Gray)"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As cConsoleBuffer"}, + {"name":"m_Console","kind":"field","vis":"private","signature":"Private m_Console As cConsole"} + ] + }, + { + "name": "BarCharStyle", + "kind": "enum", + "in": "cVBar", + "vis": "public", + "file": "Components/Controls/cVBar.twin", + "signature": "Public Enum BarCharStyle", + "values": [ + {"name":"Block","value":"0"}, + {"name":"Light","value":"1"}, + {"name":"Medium","value":"2"}, + {"name":"Dark","value":"3"}, + {"name":"Custom","value":"4"} + ] + }, + { + "name": "mConsoleThemeFactory", + "kind": "module", + "vis": "public", + "file": "Components/Themes/mConsoleThemeFactory.twin", + "signature": "Public Module mConsoleThemeFactory", + "members": [ + {"name":"CreateDefault","kind":"function","vis":"public","signature":"Public Function CreateDefault() As cConsoleTheme","returnType":"cConsoleTheme"}, + {"name":"CreateHTOP","kind":"function","vis":"public","signature":"Public Function CreateHTOP() As cConsoleTheme","returnType":"cConsoleTheme"}, + {"name":"CreateLight","kind":"function","vis":"public","signature":"Public Function CreateLight() As cConsoleTheme","returnType":"cConsoleTheme"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/indexer/snapshots/contributed/TBMANLIB_ConsoleCore/api.json b/indexer/snapshots/contributed/TBMANLIB_ConsoleCore/api.json new file mode 100644 index 000000000..901e81322 --- /dev/null +++ b/indexer/snapshots/contributed/TBMANLIB_ConsoleCore/api.json @@ -0,0 +1,865 @@ +{ + "format": 3, + "package": "TBMANLIB_ConsoleCore", + "project": "TBMANLIB_ConsoleCore", + "version": "0.0.0.0", + "types": [ + { + "name": "cConsole", + "kind": "class", + "vis": "public", + "file": "Core/cConsole.twin", + "signature": "Public Class cConsole", + "attrs": [{"name":"PredeclaredId"},{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"ApplyColors","kind":"sub","vis":"private","signature":"Private Sub ApplyColors()"}, + {"name":"BackColor","kind":"property","vis":"public","signature":"Public Property Get BackColor() As ConsoleColor","returnType":"ConsoleColor","accessor":"Get"}, + {"name":"BackColor","kind":"property","vis":"public","signature":"Public Property Let BackColor(ByVal value As ConsoleColor)","accessor":"Let"}, + {"name":"Beep","kind":"sub","vis":"public","signature":"Public Sub Beep()"}, + {"name":"Buffer","kind":"property","vis":"public","signature":"Public Property Get Buffer() As cConsoleBuffer","returnType":"cConsoleBuffer","accessor":"Get"}, + {"name":"CenterRect","kind":"function","vis":"public","signature":"Public Function CenterRect(ByVal contentWidth As Long, ByVal contentHeight As Long) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"CenterX","kind":"function","vis":"public","signature":"Public Function CenterX(ByVal textWidth As Long) As Long","returnType":"Long"}, + {"name":"CenterY","kind":"function","vis":"public","signature":"Public Function CenterY(ByVal textHeight As Long) As Long","returnType":"Long"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear()"}, + {"name":"ClearLine","kind":"sub","vis":"public","signature":"Public Sub ClearLine()"}, + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" ( ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr)"}, + {"name":"CursorSize","kind":"property","vis":"public","signature":"Public Property Get CursorSize() As Long","returnType":"Long","accessor":"Get"}, + {"name":"CursorSize","kind":"property","vis":"public","signature":"Public Property Let CursorSize(ByVal value As Long)","accessor":"Let"}, + {"name":"CursorVisible","kind":"property","vis":"public","signature":"Public Property Get CursorVisible() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"CursorVisible","kind":"property","vis":"public","signature":"Public Property Let CursorVisible(ByVal value As Boolean)","accessor":"Let"}, + {"name":"DisableUTF8","kind":"sub","vis":"public","signature":"Public Sub DisableUTF8()"}, + {"name":"DisableVirtualTerminal","kind":"sub","vis":"public","signature":"Public Sub DisableVirtualTerminal()"}, + {"name":"DrawBox","kind":"sub","vis":"public","signature":"Public Sub DrawBox(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long, Optional ByVal doubleLine As Boolean = False)"}, + {"name":"DrawProgressBar","kind":"sub","vis":"public","signature":"Public Sub DrawProgressBar(ByVal col As Long, ByVal row As Long, ByVal width As Long, ByVal percent As Double, Optional ByVal usedColor As ConsoleColor = ConsoleColor.Green, Optional ByVal freeColor As ConsoleColor = ConsoleColor.DarkGray)"}, + {"name":"EnableUTF8","kind":"sub","vis":"public","signature":"Public Sub EnableUTF8()"}, + {"name":"EnableVirtualTerminal","kind":"function","vis":"public","signature":"Public Function EnableVirtualTerminal() As Boolean","returnType":"Boolean"}, + {"name":"EnterFullScreenMode","kind":"sub","vis":"public","signature":"Public Sub EnterFullScreenMode()"}, + {"name":"ExitFullScreenMode","kind":"sub","vis":"public","signature":"Public Sub ExitFullScreenMode()"}, + {"name":"FPS","kind":"property","vis":"public","signature":"Public Property Get FPS() As Double","returnType":"Double","accessor":"Get"}, + {"name":"FlushInput","kind":"sub","vis":"public","signature":"Public Sub FlushInput()"}, + {"name":"ForeColor","kind":"property","vis":"public","signature":"Public Property Get ForeColor() As ConsoleColor","returnType":"ConsoleColor","accessor":"Get"}, + {"name":"ForeColor","kind":"property","vis":"public","signature":"Public Property Let ForeColor(ByVal value As ConsoleColor)","accessor":"Let"}, + {"name":"FullScreenMode","kind":"property","vis":"public","signature":"Public Property Get FullScreenMode() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"GetBufferSize","kind":"function","vis":"public","signature":"Public Function GetBufferSize() As ConsoleSize","returnType":"ConsoleSize"}, + {"name":"GetCursorPosition","kind":"function","vis":"public","signature":"Public Function GetCursorPosition() As ConsolePosition","returnType":"ConsolePosition"}, + {"name":"GetSize","kind":"function","vis":"public","signature":"Public Function GetSize() As ConsoleSize","returnType":"ConsoleSize"}, + {"name":"HandleKeyEvent","kind":"sub","vis":"private","signature":"Private Sub HandleKeyEvent(ByRef ir As INPUT_RECORD)"}, + {"name":"HandleMouseEvent","kind":"sub","vis":"private","signature":"Private Sub HandleMouseEvent(ByRef ir As INPUT_RECORD)"}, + {"name":"HandleResizeEvent","kind":"sub","vis":"private","signature":"Private Sub HandleResizeEvent(ByRef ir As INPUT_RECORD)"}, + {"name":"Height","kind":"property","vis":"public","signature":"Public Property Get Height() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Initialize","kind":"sub","vis":"private","signature":"Private Sub Initialize()"}, + {"name":"InputCodePage","kind":"property","vis":"public","signature":"Public Property Get InputCodePage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"InputCodePage","kind":"property","vis":"public","signature":"Public Property Let InputCodePage(ByVal value As Long)","accessor":"Let"}, + {"name":"IsKeyPressed","kind":"function","vis":"public","signature":"Public Function IsKeyPressed(ByVal keyCode As Long) As Boolean","returnType":"Boolean"}, + {"name":"KeyAvailable","kind":"function","vis":"public","signature":"Public Function KeyAvailable() As Boolean","returnType":"Boolean"}, + {"name":"LastRenderTime","kind":"property","vis":"public","signature":"Public Property Get LastRenderTime() As Long","returnType":"Long","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(Optional ByVal inputBufferSize As Long = 4096)"}, + {"name":"OnCtrlEvent","kind":"property","vis":"public","signature":"Public Property Let OnCtrlEvent(ByVal handler As ConsoleCtrlHandler)","accessor":"Let"}, + {"name":"OnKeyPress","kind":"property","vis":"public","signature":"Public Property Let OnKeyPress(ByVal handler As ConsoleKeyHandler)","accessor":"Let"}, + {"name":"OnMouseClick","kind":"property","vis":"public","signature":"Public Property Let OnMouseClick(ByVal handler As ConsoleMouseHandler)","accessor":"Let"}, + {"name":"OnResize","kind":"property","vis":"public","signature":"Public Property Let OnResize(ByVal handler As ConsoleResizeHandler)","accessor":"Let"}, + {"name":"OutputCodePage","kind":"property","vis":"public","signature":"Public Property Get OutputCodePage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"OutputCodePage","kind":"property","vis":"public","signature":"Public Property Let OutputCodePage(ByVal value As Long)","accessor":"Let"}, + {"name":"PeekEvent","kind":"function","vis":"public","signature":"Public Function PeekEvent() As ConsoleKeyInfo","returnType":"ConsoleKeyInfo"}, + {"name":"PercentX","kind":"function","vis":"public","signature":"Public Function PercentX(ByVal percent As Double) As Long","returnType":"Long"}, + {"name":"PercentY","kind":"function","vis":"public","signature":"Public Function PercentY(ByVal percent As Double) As Long","returnType":"Long"}, + {"name":"ProcessEvents","kind":"sub","vis":"public","signature":"Public Sub ProcessEvents()"}, + {"name":"ReadKey","kind":"function","vis":"public","signature":"Public Function ReadKey(Optional ByVal intercept As Boolean = False) As ConsoleKeyInfo","returnType":"ConsoleKeyInfo"}, + {"name":"ReadKeyTimeout","kind":"function","vis":"public","signature":"Public Function ReadKeyTimeout(ByVal timeoutMs As Long, Optional ByVal intercept As Boolean = False) As ConsoleKeyInfo","returnType":"ConsoleKeyInfo"}, + {"name":"ReadLine","kind":"function","vis":"public","signature":"Public Function ReadLine(Optional ByVal prompt As String = \"\") As String","returnType":"String"}, + {"name":"ReadPassword","kind":"function","vis":"public","signature":"Public Function ReadPassword(Optional ByVal prompt As String = \"\", Optional ByVal maskChar As String = \"*\") As String","returnType":"String"}, + {"name":"Refresh","kind":"sub","vis":"public","signature":"Public Sub Refresh()"}, + {"name":"RefreshAll","kind":"sub","vis":"public","signature":"Public Sub RefreshAll()"}, + {"name":"ResetColors","kind":"sub","vis":"public","signature":"Public Sub ResetColors()"}, + {"name":"Restore","kind":"sub","vis":"public","signature":"Public Sub Restore()"}, + {"name":"SetBufferSize","kind":"sub","vis":"public","signature":"Public Sub SetBufferSize(ByVal width As Long, ByVal height As Long)"}, + {"name":"SetColors","kind":"sub","vis":"public","signature":"Public Sub SetColors(ByVal fore As ConsoleColor, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"SetCursorPosition","kind":"sub","vis":"public","signature":"Public Sub SetCursorPosition(ByVal col As Long, ByVal row As Long)"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"VirtualTerminalEnabled","kind":"property","vis":"public","signature":"Public Property Get VirtualTerminalEnabled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Width","kind":"property","vis":"public","signature":"Public Property Get Width() As Long","returnType":"Long","accessor":"Get"}, + {"name":"WindowHandle","kind":"property","vis":"public","signature":"Public Property Get WindowHandle() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"WriteAt","kind":"sub","vis":"public","signature":"Public Sub WriteAt(ByVal col As Long, ByVal row As Long, ByVal text As String)"}, + {"name":"WriteAtColored","kind":"sub","vis":"public","signature":"Public Sub WriteAtColored(ByVal col As Long, ByVal row As Long, ByVal text As String, ByVal fore As ConsoleColor, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"WriteColored","kind":"sub","vis":"public","signature":"Public Sub WriteColored(ByVal text As String, ByVal fore As ConsoleColor, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"WriteError","kind":"sub","vis":"public","signature":"Public Sub WriteError(ByVal text As String)"}, + {"name":"WriteErrorLine","kind":"sub","vis":"public","signature":"Public Sub WriteErrorLine(ByVal text As String)"}, + {"name":"WriteF","kind":"sub","vis":"public","signature":"Public Sub WriteF(ByVal format As String, ParamArray args() As Variant)"}, + {"name":"WriteLine","kind":"sub","vis":"public","signature":"Public Sub WriteLine(Optional ByVal text As String = \"\")"}, + {"name":"WriteLineColored","kind":"sub","vis":"public","signature":"Public Sub WriteLineColored(ByVal text As String, ByVal fore As ConsoleColor, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"WriteLineF","kind":"sub","vis":"public","signature":"Public Sub WriteLineF(ByVal format As String, ParamArray args() As Variant)"}, + {"name":"WriteTo","kind":"sub","vis":"public","signature":"Public Sub WriteTo(ByVal text As String)"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer As cConsoleBuffer"}, + {"name":"m_CurrentBackColor","kind":"field","vis":"private","signature":"Private m_CurrentBackColor As ConsoleColor"}, + {"name":"m_CurrentForeColor","kind":"field","vis":"private","signature":"Private m_CurrentForeColor As ConsoleColor"}, + {"name":"m_CurrentStyle","kind":"field","vis":"private","signature":"Private m_CurrentStyle As ConsoleTextStyle"}, + {"name":"m_FrameCount","kind":"field","vis":"private","signature":"Private m_FrameCount As Long"}, + {"name":"m_FrameStartTime","kind":"field","vis":"private","signature":"Private m_FrameStartTime As Long"}, + {"name":"m_FullScreenMode","kind":"field","vis":"private","signature":"Private m_FullScreenMode As Boolean"}, + {"name":"m_HasCtrlHandler","kind":"field","vis":"private","signature":"Private m_HasCtrlHandler As Boolean"}, + {"name":"m_HasKeyHandler","kind":"field","vis":"private","signature":"Private m_HasKeyHandler As Boolean"}, + {"name":"m_HasMouseHandler","kind":"field","vis":"private","signature":"Private m_HasMouseHandler As Boolean"}, + {"name":"m_HasResizeHandler","kind":"field","vis":"private","signature":"Private m_HasResizeHandler As Boolean"}, + {"name":"m_Initialized","kind":"field","vis":"private","signature":"Private m_Initialized As Boolean"}, + {"name":"m_InputBuffer","kind":"field","vis":"private","signature":"Private m_InputBuffer As String"}, + {"name":"m_InputBufferSize","kind":"field","vis":"private","signature":"Private m_InputBufferSize As Long"}, + {"name":"m_LastHeight","kind":"field","vis":"private","signature":"Private m_LastHeight As Long"}, + {"name":"m_LastRenderTime","kind":"field","vis":"private","signature":"Private m_LastRenderTime As Long"}, + {"name":"m_LastWidth","kind":"field","vis":"private","signature":"Private m_LastWidth As Long"}, + {"name":"m_OnCtrl","kind":"field","vis":"private","signature":"Private m_OnCtrl As LongPtr"}, + {"name":"m_OnKey","kind":"field","vis":"private","signature":"Private m_OnKey As LongPtr"}, + {"name":"m_OnMouse","kind":"field","vis":"private","signature":"Private m_OnMouse As LongPtr"}, + {"name":"m_OnRender","kind":"field","vis":"private","signature":"Private m_OnRender As LongPtr"}, + {"name":"m_OnResize","kind":"field","vis":"private","signature":"Private m_OnResize As LongPtr"}, + {"name":"m_OriginalAttributes","kind":"field","vis":"private","signature":"Private m_OriginalAttributes As Integer"}, + {"name":"m_OriginalCodePage","kind":"field","vis":"private","signature":"Private m_OriginalCodePage As Long"}, + {"name":"m_OriginalInputMode","kind":"field","vis":"private","signature":"Private m_OriginalInputMode As Long"}, + {"name":"m_OriginalOutputCP","kind":"field","vis":"private","signature":"Private m_OriginalOutputCP As Long"}, + {"name":"m_OriginalOutputMode","kind":"field","vis":"private","signature":"Private m_OriginalOutputMode As Long"}, + {"name":"m_VTEnabled","kind":"field","vis":"private","signature":"Private m_VTEnabled As Boolean"}, + {"name":"m_hStdErr","kind":"field","vis":"private","signature":"Private m_hStdErr As LongPtr"}, + {"name":"m_hStdIn","kind":"field","vis":"private","signature":"Private m_hStdIn As LongPtr"}, + {"name":"m_hStdOut","kind":"field","vis":"private","signature":"Private m_hStdOut As LongPtr"}, + {"name":"m_hWindow","kind":"field","vis":"private","signature":"Private m_hWindow As LongPtr"} + ] + }, + { + "name": "cConsoleBuffer", + "kind": "class", + "vis": "public", + "file": "Core/cConsoleBuffer.twin", + "signature": "Public Class cConsoleBuffer", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"BeginBatchUpdate","kind":"sub","vis":"public","signature":"Public Sub BeginBatchUpdate()"}, + {"name":"BufferPtr","kind":"property","vis":"public","signature":"Public Property Get BufferPtr() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"Clear","kind":"sub","vis":"public","signature":"Public Sub Clear(Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal fillChar As String = \" \")"}, + {"name":"ClearDirty","kind":"sub","vis":"public","signature":"Public Sub ClearDirty()"}, + {"name":"CopyRegion","kind":"sub","vis":"public","signature":"Public Sub CopyRegion(ByVal srcLeft As Long, ByVal srcTop As Long, ByVal srcRight As Long, ByVal srcBottom As Long, ByVal dstLeft As Long, ByVal dstTop As Long)"}, + {"name":"DrawBox","kind":"sub","vis":"public","signature":"Public Sub DrawBox(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal doubleLine As Boolean = False)"}, + {"name":"DrawHLine","kind":"sub","vis":"public","signature":"Public Sub DrawHLine(ByVal col As Long, ByVal row As Long, ByVal length As Long, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal doubleLine As Boolean = False)"}, + {"name":"DrawVLine","kind":"sub","vis":"public","signature":"Public Sub DrawVLine(ByVal col As Long, ByVal row As Long, ByVal length As Long, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black, Optional ByVal doubleLine As Boolean = False)"}, + {"name":"EndBatchUpdate","kind":"sub","vis":"public","signature":"Public Sub EndBatchUpdate()"}, + {"name":"ExpandDirtyRect","kind":"sub","vis":"private","signature":"Private Sub ExpandDirtyRect(ByVal col As Long, ByVal row As Long)"}, + {"name":"FillRect","kind":"sub","vis":"public","signature":"Public Sub FillRect(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long, Optional ByVal char As String = \" \", Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"Flush","kind":"sub","vis":"public","signature":"Public Sub Flush(ByVal hOutput As LongPtr, Optional ByVal forceAll As Boolean = False)"}, + {"name":"FlushRegion","kind":"sub","vis":"public","signature":"Public Sub FlushRegion(ByVal hOutput As LongPtr, ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long)"}, + {"name":"GetChar","kind":"function","vis":"public","signature":"Public Function GetChar(ByVal col As Long, ByVal row As Long) As ConsoleCharInfo","returnType":"ConsoleCharInfo"}, + {"name":"GetDirtyRect","kind":"function","vis":"public","signature":"Public Function GetDirtyRect() As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"Height","kind":"property","vis":"public","signature":"Public Property Get Height() As Long","returnType":"Long","accessor":"Get"}, + {"name":"IsDirty","kind":"property","vis":"public","signature":"Public Property Get IsDirty() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"IsInBatchMode","kind":"property","vis":"public","signature":"Public Property Get IsInBatchMode() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"New","kind":"sub","vis":"public","signature":"Public Sub New(ByVal width As Long, ByVal height As Long)"}, + {"name":"Resize","kind":"sub","vis":"public","signature":"Public Sub Resize(ByVal width As Long, ByVal height As Long)"}, + {"name":"ScrollRegion","kind":"sub","vis":"public","signature":"Public Sub ScrollRegion(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long, ByVal deltaRow As Long, ByVal deltaCol As Long)"}, + {"name":"SetChar","kind":"sub","vis":"public","signature":"Public Sub SetChar(ByVal col As Long, ByVal row As Long, ByVal char As String, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"Width","kind":"property","vis":"public","signature":"Public Property Get Width() As Long","returnType":"Long","accessor":"Get"}, + {"name":"WriteAt","kind":"sub","vis":"public","signature":"Public Sub WriteAt(ByVal col As Long, ByVal row As Long, ByVal text As String, Optional ByVal fore As ConsoleColor = ConsoleColor.Gray, Optional ByVal back As ConsoleColor = ConsoleColor.Black)"}, + {"name":"WriteTextEx","kind":"sub","vis":"public","signature":"Public Sub WriteTextEx(ByVal text As String, ByVal col As Long, ByVal row As Long, ByVal fore As ConsoleColor, ByVal back As ConsoleColor, ByVal maxWidth As Long, Optional ByVal ellipsis As String = \"…\")"}, + {"name":"m_BatchMode","kind":"field","vis":"private","signature":"Private m_BatchMode As Integer"}, + {"name":"m_Buffer","kind":"field","vis":"private","signature":"Private m_Buffer() As CHAR_INFO"}, + {"name":"m_DirtyRect","kind":"field","vis":"private","signature":"Private m_DirtyRect As ConsoleRect"}, + {"name":"m_Height","kind":"field","vis":"private","signature":"Private m_Height As Long"}, + {"name":"m_IsDirty","kind":"field","vis":"private","signature":"Private m_IsDirty As Boolean"}, + {"name":"m_Width","kind":"field","vis":"private","signature":"Private m_Width As Long"} + ] + }, + { + "name": "mConsoleAPI", + "kind": "module", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Module mConsoleAPI", + "members": [ + {"name":"ATTACH_PARENT_PROCESS","kind":"const","vis":"public","signature":"Public Const ATTACH_PARENT_PROCESS As Long = -1"}, + {"name":"AllocConsole","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function AllocConsole Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"AttachConsole","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function AttachConsole Lib \"kernel32\" ( ByVal dwProcessId As Long) As Long","returnType":"Long"}, + {"name":"CAPSLOCK_ON","kind":"const","vis":"public","signature":"Public Const CAPSLOCK_ON As Long = &H80"}, + {"name":"CONSOLE_TEXTMODE_BUFFER","kind":"const","vis":"public","signature":"Public Const CONSOLE_TEXTMODE_BUFFER As Long = &H1"}, + {"name":"CP_ACP","kind":"const","vis":"public","signature":"Public Const CP_ACP As Long = 0"}, + {"name":"CP_OEMCP","kind":"const","vis":"public","signature":"Public Const CP_OEMCP As Long = 1"}, + {"name":"CP_UTF7","kind":"const","vis":"public","signature":"Public Const CP_UTF7 As Long = 65000"}, + {"name":"CP_UTF8","kind":"const","vis":"public","signature":"Public Const CP_UTF8 As Long = 65001"}, + {"name":"CTRL_BREAK_EVENT","kind":"const","vis":"public","signature":"Public Const CTRL_BREAK_EVENT As Long = 1"}, + {"name":"CTRL_CLOSE_EVENT","kind":"const","vis":"public","signature":"Public Const CTRL_CLOSE_EVENT As Long = 2"}, + {"name":"CTRL_C_EVENT","kind":"const","vis":"public","signature":"Public Const CTRL_C_EVENT As Long = 0"}, + {"name":"CTRL_LOGOFF_EVENT","kind":"const","vis":"public","signature":"Public Const CTRL_LOGOFF_EVENT As Long = 5"}, + {"name":"CTRL_SHUTDOWN_EVENT","kind":"const","vis":"public","signature":"Public Const CTRL_SHUTDOWN_EVENT As Long = 6"}, + {"name":"ColorAttribute","kind":"function","vis":"public","signature":"Public Function ColorAttribute(ByVal fore As ConsoleColor, ByVal back As ConsoleColor) As Integer","returnType":"Integer"}, + {"name":"CreateConsoleScreenBuffer","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function CreateConsoleScreenBuffer Lib \"kernel32\" ( ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As LongPtr, ByVal dwFlags As Long, ByVal lpScreenBufferData As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"DISABLE_NEWLINE_AUTO_RETURN","kind":"const","vis":"public","signature":"Public Const DISABLE_NEWLINE_AUTO_RETURN As Long = &H8"}, + {"name":"ENABLE_AUTO_POSITION","kind":"const","vis":"public","signature":"Public Const ENABLE_AUTO_POSITION As Long = &H100"}, + {"name":"ENABLE_ECHO_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_ECHO_INPUT As Long = &H4"}, + {"name":"ENABLE_EXTENDED_FLAGS","kind":"const","vis":"public","signature":"Public Const ENABLE_EXTENDED_FLAGS As Long = &H80"}, + {"name":"ENABLE_INSERT_MODE","kind":"const","vis":"public","signature":"Public Const ENABLE_INSERT_MODE As Long = &H20"}, + {"name":"ENABLE_LINE_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_LINE_INPUT As Long = &H2"}, + {"name":"ENABLE_LVB_GRID_WORLDWIDE","kind":"const","vis":"public","signature":"Public Const ENABLE_LVB_GRID_WORLDWIDE As Long = &H10"}, + {"name":"ENABLE_MOUSE_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_MOUSE_INPUT As Long = &H10"}, + {"name":"ENABLE_PROCESSED_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_PROCESSED_INPUT As Long = &H1"}, + {"name":"ENABLE_PROCESSED_OUTPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_PROCESSED_OUTPUT As Long = &H1"}, + {"name":"ENABLE_QUICK_EDIT_MODE","kind":"const","vis":"public","signature":"Public Const ENABLE_QUICK_EDIT_MODE As Long = &H40"}, + {"name":"ENABLE_VIRTUAL_TERMINAL_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_VIRTUAL_TERMINAL_INPUT As Long = &H200"}, + {"name":"ENABLE_VIRTUAL_TERMINAL_PROCESSING","kind":"const","vis":"public","signature":"Public Const ENABLE_VIRTUAL_TERMINAL_PROCESSING As Long = &H4"}, + {"name":"ENABLE_WINDOW_INPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_WINDOW_INPUT As Long = &H8"}, + {"name":"ENABLE_WRAP_AT_EOL_OUTPUT","kind":"const","vis":"public","signature":"Public Const ENABLE_WRAP_AT_EOL_OUTPUT As Long = &H2"}, + {"name":"ENHANCED_KEY","kind":"const","vis":"public","signature":"Public Const ENHANCED_KEY As Long = &H100"}, + {"name":"FILE_SHARE_READ","kind":"const","vis":"public","signature":"Public Const FILE_SHARE_READ As Long = &H1"}, + {"name":"FILE_SHARE_WRITE","kind":"const","vis":"public","signature":"Public Const FILE_SHARE_WRITE As Long = &H2"}, + {"name":"FOCUS_EVENT","kind":"const","vis":"public","signature":"Public Const FOCUS_EVENT As Integer = &H10"}, + {"name":"FillConsoleOutputAttribute","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function FillConsoleOutputAttribute Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal wAttribute As Integer, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfAttrsWritten As Long) As Long","returnType":"Long"}, + {"name":"FillConsoleOutputCharacterW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function FillConsoleOutputCharacterW Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal cCharacter As Integer, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfCharsWritten As Long) As Long","returnType":"Long"}, + {"name":"FlushConsoleInputBuffer","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function FlushConsoleInputBuffer Lib \"kernel32\" ( ByVal hConsoleInput As LongPtr) As Long","returnType":"Long"}, + {"name":"FreeConsole","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function FreeConsole Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GENERIC_READ","kind":"const","vis":"public","signature":"Public Const GENERIC_READ As Long = &H80000000"}, + {"name":"GENERIC_WRITE","kind":"const","vis":"public","signature":"Public Const GENERIC_WRITE As Long = &H40000000"}, + {"name":"GetAsyncKeyState","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetAsyncKeyState Lib \"user32\" (ByVal vKey As Long) As Integer","returnType":"Integer"}, + {"name":"GetConsoleCP","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleCP Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetConsoleCursorInfo","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleCursorInfo Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleCursorInfo As CONSOLE_CURSOR_INFO) As Long","returnType":"Long"}, + {"name":"GetConsoleMode","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleMode Lib \"kernel32\" ( ByVal hConsoleHandle As LongPtr, ByRef lpMode As Long) As Long","returnType":"Long"}, + {"name":"GetConsoleOutputCP","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleOutputCP Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetConsoleScreenBufferInfo","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleScreenBufferInfo Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleScreenBufferInfo As CONSOLE_SCREEN_BUFFER_INFO) As Long","returnType":"Long"}, + {"name":"GetConsoleScreenBufferInfoEx","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleScreenBufferInfoEx Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleScreenBufferInfoEx As CONSOLE_SCREEN_BUFFER_INFOEX) As Long","returnType":"Long"}, + {"name":"GetConsoleTitleW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleTitleW Lib \"kernel32\" ( ByVal lpConsoleTitle As LongPtr, ByVal nSize As Long) As Long","returnType":"Long"}, + {"name":"GetConsoleWindow","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetConsoleWindow Lib \"kernel32\" () As LongPtr","returnType":"LongPtr"}, + {"name":"GetCurrentConsoleFontEx","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetCurrentConsoleFontEx Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal bMaximumWindow As Long, ByRef lpConsoleCurrentFontEx As CONSOLE_FONT_INFOEX) As Long","returnType":"Long"}, + {"name":"GetNumberOfConsoleInputEvents","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetNumberOfConsoleInputEvents Lib \"kernel32\" ( ByVal hConsoleInput As LongPtr, ByRef lpNumberOfEvents As Long) As Long","returnType":"Long"}, + {"name":"GetStdHandle","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetStdHandle Lib \"kernel32\" ( ByVal nStdHandle As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"GetTickCount","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetTickCount Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"GetWindowRect","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function GetWindowRect Lib \"user32\" ( ByVal hwnd As LongPtr, ByRef lpRect As RECT) As Long","returnType":"Long"}, + {"name":"HiWord","kind":"function","vis":"public","signature":"Public Function HiWord(ByVal dw As Long) As Integer","returnType":"Integer"}, + {"name":"KEY_EVENT","kind":"const","vis":"public","signature":"Public Const KEY_EVENT As Integer = &H1"}, + {"name":"KillTimer","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function KillTimer Lib \"user32\" ( ByVal hwnd As LongPtr, ByVal uIDEvent As LongPtr) As Long","returnType":"Long"}, + {"name":"LEFT_ALT_PRESSED","kind":"const","vis":"public","signature":"Public Const LEFT_ALT_PRESSED As Long = &H2"}, + {"name":"LEFT_CTRL_PRESSED","kind":"const","vis":"public","signature":"Public Const LEFT_CTRL_PRESSED As Long = &H8"}, + {"name":"LoWord","kind":"function","vis":"public","signature":"Public Function LoWord(ByVal dw As Long) As Integer","returnType":"Integer"}, + {"name":"MENU_EVENT","kind":"const","vis":"public","signature":"Public Const MENU_EVENT As Integer = &H8"}, + {"name":"MOUSE_EVENT","kind":"const","vis":"public","signature":"Public Const MOUSE_EVENT As Integer = &H2"}, + {"name":"MakeCOORD","kind":"function","vis":"public","signature":"Public Function MakeCOORD(ByVal x As Integer, ByVal y As Integer) As Long","returnType":"Long"}, + {"name":"MakeDWORD","kind":"function","vis":"public","signature":"Public Function MakeDWORD(ByVal loWord As Integer, ByVal hiWord As Integer) As Long","returnType":"Long"}, + {"name":"MoveWindow","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function MoveWindow Lib \"user32\" ( ByVal hwnd As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long","returnType":"Long"}, + {"name":"MultiByteToWideChar","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function MultiByteToWideChar Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long","returnType":"Long"}, + {"name":"NUMLOCK_ON","kind":"const","vis":"public","signature":"Public Const NUMLOCK_ON As Long = &H20"}, + {"name":"PeekConsoleInputW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function PeekConsoleInputW Lib \"kernel32\" ( ByVal hConsoleInput As LongPtr, ByRef lpBuffer As INPUT_RECORD, ByVal nLength As Long, ByRef lpNumberOfEventsRead As Long) As Long","returnType":"Long"}, + {"name":"RIGHT_ALT_PRESSED","kind":"const","vis":"public","signature":"Public Const RIGHT_ALT_PRESSED As Long = &H1"}, + {"name":"RIGHT_CTRL_PRESSED","kind":"const","vis":"public","signature":"Public Const RIGHT_CTRL_PRESSED As Long = &H4"}, + {"name":"ReadConsoleInputW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function ReadConsoleInputW Lib \"kernel32\" ( ByVal hConsoleInput As LongPtr, ByRef lpBuffer As INPUT_RECORD, ByVal nLength As Long, ByRef lpNumberOfEventsRead As Long) As Long","returnType":"Long"}, + {"name":"ReadConsoleW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function ReadConsoleW Lib \"kernel32\" ( ByVal hConsoleInput As LongPtr, ByVal lpBuffer As LongPtr, ByVal nNumberOfCharsToRead As Long, ByRef lpNumberOfCharsRead As Long, ByVal pInputControl As LongPtr) As Long","returnType":"Long"}, + {"name":"SCROLLLOCK_ON","kind":"const","vis":"public","signature":"Public Const SCROLLLOCK_ON As Long = &H40"}, + {"name":"SHIFT_PRESSED","kind":"const","vis":"public","signature":"Public Const SHIFT_PRESSED As Long = &H10"}, + {"name":"STD_ERROR_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_ERROR_HANDLE As Long = -12"}, + {"name":"STD_INPUT_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_INPUT_HANDLE As Long = -10"}, + {"name":"STD_OUTPUT_HANDLE","kind":"const","vis":"public","signature":"Public Const STD_OUTPUT_HANDLE As Long = -11"}, + {"name":"ScrollConsoleScreenBufferW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function ScrollConsoleScreenBufferW Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpScrollRectangle As SMALL_RECT, ByVal lpClipRectangle As LongPtr, ByVal dwDestinationOrigin As Long, ByRef lpFill As CHAR_INFO) As Long","returnType":"Long"}, + {"name":"SetConsoleActiveScreenBuffer","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleActiveScreenBuffer Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr) As Long","returnType":"Long"}, + {"name":"SetConsoleCP","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleCP Lib \"kernel32\" ( ByVal wCodePageID As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleCtrlHandler","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleCtrlHandler Lib \"kernel32\" ( ByVal HandlerRoutine As LongPtr, ByVal Add As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleCursorInfo","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleCursorInfo Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleCursorInfo As CONSOLE_CURSOR_INFO) As Long","returnType":"Long"}, + {"name":"SetConsoleCursorPosition","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleCursorPosition Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal dwCursorPosition As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleMode","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleMode Lib \"kernel32\" ( ByVal hConsoleHandle As LongPtr, ByVal dwMode As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleOutputCP","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleOutputCP Lib \"kernel32\" ( ByVal wCodePageID As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleScreenBufferInfoEx","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleScreenBufferInfoEx Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByRef lpConsoleScreenBufferInfoEx As CONSOLE_SCREEN_BUFFER_INFOEX) As Long","returnType":"Long"}, + {"name":"SetConsoleScreenBufferSize","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleScreenBufferSize Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal dwSize As Long) As Long","returnType":"Long"}, + {"name":"SetConsoleTextAttribute","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleTextAttribute Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal wAttributes As Integer) As Long","returnType":"Long"}, + {"name":"SetConsoleTitleW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleTitleW Lib \"kernel32\" ( ByVal lpConsoleTitle As LongPtr) As Long","returnType":"Long"}, + {"name":"SetConsoleWindowInfo","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetConsoleWindowInfo Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal bAbsolute As Long, ByRef lpConsoleWindow As SMALL_RECT) As Long","returnType":"Long"}, + {"name":"SetCurrentConsoleFontEx","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetCurrentConsoleFontEx Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal bMaximumWindow As Long, ByRef lpConsoleCurrentFontEx As CONSOLE_FONT_INFOEX) As Long","returnType":"Long"}, + {"name":"SetStdHandle","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetStdHandle Lib \"kernel32\" ( ByVal nStdHandle As Long, ByVal hHandle As LongPtr) As Long","returnType":"Long"}, + {"name":"SetTimer","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SetTimer Lib \"user32\" ( ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"ShowWindow","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function ShowWindow Lib \"user32\" ( ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long","returnType":"Long"}, + {"name":"Sleep","kind":"sub","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Sub Sleep Lib \"kernel32\" (ByVal dwMilliseconds As Long)"}, + {"name":"SysAllocStringLen","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function SysAllocStringLen Lib \"oleaut32\" ( ByVal olestr As LongPtr, ByVal cch As Long) As String","returnType":"String"}, + {"name":"WINDOW_BUFFER_SIZE_EVENT","kind":"const","vis":"public","signature":"Public Const WINDOW_BUFFER_SIZE_EVENT As Integer = &H4"}, + {"name":"WideCharToMultiByte","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function WideCharToMultiByte Lib \"kernel32\" ( ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpDefaultChar As LongPtr, ByVal lpUsedDefaultChar As LongPtr) As Long","returnType":"Long"}, + {"name":"WriteConsoleOutputAttribute","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function WriteConsoleOutputAttribute Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal lpAttribute As LongPtr, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfAttrsWritten As Long) As Long","returnType":"Long"}, + {"name":"WriteConsoleOutputCharacterW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function WriteConsoleOutputCharacterW Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal lpCharacter As LongPtr, ByVal nLength As Long, ByVal dwWriteCoord As Long, ByRef lpNumberOfCharsWritten As Long) As Long","returnType":"Long"}, + {"name":"WriteConsoleOutputW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function WriteConsoleOutputW Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal lpBuffer As LongPtr, ByVal dwBufferSize As Long, ByVal dwBufferCoord As Long, ByRef lpWriteRegion As SMALL_RECT) As Long","returnType":"Long"}, + {"name":"WriteConsoleW","kind":"function","vis":"public","declare":true,"signature":"Public DeclareWide PtrSafe Function WriteConsoleW Lib \"kernel32\" ( ByVal hConsoleOutput As LongPtr, ByVal lpBuffer As LongPtr, ByVal nNumberOfCharsToWrite As Long, ByRef lpNumberOfCharsWritten As Long, ByVal lpReserved As LongPtr) As Long","returnType":"Long"} + ] + }, + { + "name": "CHAR_INFO", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type CHAR_INFO", + "fields": [ + {"name":"UnicodeChar","vis":"public","signature":"UnicodeChar As Integer"}, + {"name":"Attributes","vis":"public","signature":"Attributes As Integer"} + ], + "members": [] + }, + { + "name": "CONSOLE_CURSOR_INFO", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type CONSOLE_CURSOR_INFO", + "fields": [ + {"name":"dwSize","vis":"public","signature":"dwSize As Long"}, + {"name":"bVisible","vis":"public","signature":"bVisible As Long"} + ], + "members": [] + }, + { + "name": "CONSOLE_FONT_INFOEX", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type CONSOLE_FONT_INFOEX", + "fields": [ + {"name":"cbSize","vis":"public","signature":"cbSize As Long"}, + {"name":"nFont","vis":"public","signature":"nFont As Long"}, + {"name":"dwFontSize","vis":"public","signature":"dwFontSize As COORD"}, + {"name":"FontFamily","vis":"public","signature":"FontFamily As Long"}, + {"name":"FontWeight","vis":"public","signature":"FontWeight As Long"}, + {"name":"FaceName","vis":"public","signature":"FaceName(0 To 31) As Integer"} + ], + "members": [] + }, + { + "name": "CONSOLE_SCREEN_BUFFER_INFO", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type CONSOLE_SCREEN_BUFFER_INFO", + "fields": [ + {"name":"dwSize","vis":"public","signature":"dwSize As COORD"}, + {"name":"dwCursorPosition","vis":"public","signature":"dwCursorPosition As COORD"}, + {"name":"wAttributes","vis":"public","signature":"wAttributes As Integer"}, + {"name":"srWindow","vis":"public","signature":"srWindow As SMALL_RECT"}, + {"name":"dwMaximumWindowSize","vis":"public","signature":"dwMaximumWindowSize As COORD"} + ], + "members": [] + }, + { + "name": "CONSOLE_SCREEN_BUFFER_INFOEX", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type CONSOLE_SCREEN_BUFFER_INFOEX", + "fields": [ + {"name":"cbSize","vis":"public","signature":"cbSize As Long"}, + {"name":"dwSize","vis":"public","signature":"dwSize As COORD"}, + {"name":"dwCursorPosition","vis":"public","signature":"dwCursorPosition As COORD"}, + {"name":"wAttributes","vis":"public","signature":"wAttributes As Integer"}, + {"name":"srWindow","vis":"public","signature":"srWindow As SMALL_RECT"}, + {"name":"dwMaximumWindowSize","vis":"public","signature":"dwMaximumWindowSize As COORD"}, + {"name":"wPopupAttributes","vis":"public","signature":"wPopupAttributes As Integer"}, + {"name":"bFullscreenSupported","vis":"public","signature":"bFullscreenSupported As Long"}, + {"name":"ColorTable","vis":"public","signature":"ColorTable(0 To 15) As Long"} + ], + "members": [] + }, + { + "name": "COORD", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type COORD", + "fields": [ + {"name":"X","vis":"public","signature":"X As Integer"}, + {"name":"Y","vis":"public","signature":"Y As Integer"} + ], + "members": [] + }, + { + "name": "INPUT_RECORD", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type INPUT_RECORD", + "fields": [ + {"name":"EventType","vis":"public","signature":"EventType As Integer"}, + {"name":"Padding","vis":"public","signature":"Padding As Integer"}, + {"name":"Event","vis":"public","signature":"Event(0 To 15) As Byte"} + ], + "members": [] + }, + { + "name": "KEY_EVENT_RECORD", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type KEY_EVENT_RECORD", + "fields": [ + {"name":"bKeyDown","vis":"public","signature":"bKeyDown As Long"}, + {"name":"wRepeatCount","vis":"public","signature":"wRepeatCount As Integer"}, + {"name":"wVirtualKeyCode","vis":"public","signature":"wVirtualKeyCode As Integer"}, + {"name":"wVirtualScanCode","vis":"public","signature":"wVirtualScanCode As Integer"}, + {"name":"UnicodeChar","vis":"public","signature":"UnicodeChar As Integer"}, + {"name":"dwControlKeyState","vis":"public","signature":"dwControlKeyState As Long"} + ], + "members": [] + }, + { + "name": "MOUSE_EVENT_RECORD", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type MOUSE_EVENT_RECORD", + "fields": [ + {"name":"dwMousePosition","vis":"public","signature":"dwMousePosition As COORD"}, + {"name":"dwButtonState","vis":"public","signature":"dwButtonState As Long"}, + {"name":"dwControlKeyState","vis":"public","signature":"dwControlKeyState As Long"}, + {"name":"dwEventFlags","vis":"public","signature":"dwEventFlags As Long"} + ], + "members": [] + }, + { + "name": "RECT", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Long"}, + {"name":"Top","vis":"public","signature":"Top As Long"}, + {"name":"Right","vis":"public","signature":"Right As Long"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Long"} + ], + "members": [] + }, + { + "name": "SMALL_RECT", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type SMALL_RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Integer"}, + {"name":"Top","vis":"public","signature":"Top As Integer"}, + {"name":"Right","vis":"public","signature":"Right As Integer"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Integer"} + ], + "members": [] + }, + { + "name": "WINDOW_BUFFER_SIZE_RECORD", + "kind": "type", + "in": "mConsoleAPI", + "vis": "public", + "file": "Core/mConsoleAPI.twin", + "signature": "Public Type WINDOW_BUFFER_SIZE_RECORD", + "fields": [ + {"name":"dwSize","vis":"public","signature":"dwSize As COORD"} + ], + "members": [] + }, + { + "name": "mConsoleDelegates", + "kind": "module", + "vis": "public", + "file": "Core/mConsoleDelegates.twin", + "signature": "Public Module mConsoleDelegates", + "members": [ + {"name":"ConsoleCtrlHandler","kind":"delegate","vis":"public","signature":"Public Delegate Function ConsoleCtrlHandler (ByVal ctrlType As Long) As Long","returnType":"Long"}, + {"name":"ConsoleKeyHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub ConsoleKeyHandler (ByRef keyInfo As ConsoleKeyInfo, ByRef handled As Boolean)"}, + {"name":"ConsoleMouseHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub ConsoleMouseHandler (ByRef mouseInfo As ConsoleMouseInfo, ByRef handled As Boolean)"}, + {"name":"ConsoleRenderCallback","kind":"delegate","vis":"public","signature":"Public Delegate Sub ConsoleRenderCallback (ByVal buffer As cConsoleBuffer)"}, + {"name":"ConsoleResizeHandler","kind":"delegate","vis":"public","signature":"Public Delegate Sub ConsoleResizeHandler (ByVal newWidth As Long, ByVal newHeight As Long)"}, + {"name":"ConsoleTimerCallback","kind":"delegate","vis":"public","signature":"Public Delegate Sub ConsoleTimerCallback ()"} + ] + }, + { + "name": "mConsoleStyle", + "kind": "module", + "vis": "public", + "file": "Core/mConsoleStyle.twin", + "signature": "Public Module mConsoleStyle", + "members": [ + {"name":"ARROW_DOWN","kind":"const","vis":"public","signature":"Public Const ARROW_DOWN As String = \"↓\""}, + {"name":"ARROW_LEFT","kind":"const","vis":"public","signature":"Public Const ARROW_LEFT As String = \"←\""}, + {"name":"ARROW_LEFT_RIGHT","kind":"const","vis":"public","signature":"Public Const ARROW_LEFT_RIGHT As String = \"↔\""}, + {"name":"ARROW_RIGHT","kind":"const","vis":"public","signature":"Public Const ARROW_RIGHT As String = \"→\""}, + {"name":"ARROW_UP","kind":"const","vis":"public","signature":"Public Const ARROW_UP As String = \"↑\""}, + {"name":"ARROW_UP_DOWN","kind":"const","vis":"public","signature":"Public Const ARROW_UP_DOWN As String = \"↕\""}, + {"name":"AltScreenOff","kind":"function","vis":"public","signature":"Public Function AltScreenOff() As String","returnType":"String"}, + {"name":"AltScreenOn","kind":"function","vis":"public","signature":"Public Function AltScreenOn() As String","returnType":"String"}, + {"name":"BLOCK_DARK","kind":"const","vis":"public","signature":"Public Const BLOCK_DARK As String = \"▓\""}, + {"name":"BLOCK_FULL","kind":"const","vis":"public","signature":"Public Const BLOCK_FULL As String = \"█\""}, + {"name":"BLOCK_LEFT_HALF","kind":"const","vis":"public","signature":"Public Const BLOCK_LEFT_HALF As String = \"▌\""}, + {"name":"BLOCK_LIGHT","kind":"const","vis":"public","signature":"Public Const BLOCK_LIGHT As String = \"░\""}, + {"name":"BLOCK_LOWER_HALF","kind":"const","vis":"public","signature":"Public Const BLOCK_LOWER_HALF As String = \"▄\""}, + {"name":"BLOCK_MEDIUM","kind":"const","vis":"public","signature":"Public Const BLOCK_MEDIUM As String = \"▒\""}, + {"name":"BLOCK_RIGHT_HALF","kind":"const","vis":"public","signature":"Public Const BLOCK_RIGHT_HALF As String = \"▐\""}, + {"name":"BLOCK_UPPER_HALF","kind":"const","vis":"public","signature":"Public Const BLOCK_UPPER_HALF As String = \"▀\""}, + {"name":"BOX_BOTTOM_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_BOTTOM_LEFT As String = \"└\""}, + {"name":"BOX_BOTTOM_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_BOTTOM_RIGHT As String = \"┘\""}, + {"name":"BOX_CROSS","kind":"const","vis":"public","signature":"Public Const BOX_CROSS As String = \"┼\""}, + {"name":"BOX_D_BOTTOM_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_D_BOTTOM_LEFT As String = \"╚\""}, + {"name":"BOX_D_BOTTOM_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_D_BOTTOM_RIGHT As String = \"╝\""}, + {"name":"BOX_D_CROSS","kind":"const","vis":"public","signature":"Public Const BOX_D_CROSS As String = \"╬\""}, + {"name":"BOX_D_HORIZONTAL","kind":"const","vis":"public","signature":"Public Const BOX_D_HORIZONTAL As String = \"═\""}, + {"name":"BOX_D_TOP_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_D_TOP_LEFT As String = \"╔\""}, + {"name":"BOX_D_TOP_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_D_TOP_RIGHT As String = \"╗\""}, + {"name":"BOX_D_T_BOTTOM","kind":"const","vis":"public","signature":"Public Const BOX_D_T_BOTTOM As String = \"╩\""}, + {"name":"BOX_D_T_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_D_T_LEFT As String = \"╠\""}, + {"name":"BOX_D_T_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_D_T_RIGHT As String = \"╣\""}, + {"name":"BOX_D_T_TOP","kind":"const","vis":"public","signature":"Public Const BOX_D_T_TOP As String = \"╦\""}, + {"name":"BOX_D_VERTICAL","kind":"const","vis":"public","signature":"Public Const BOX_D_VERTICAL As String = \"║\""}, + {"name":"BOX_HORIZONTAL","kind":"const","vis":"public","signature":"Public Const BOX_HORIZONTAL As String = \"─\""}, + {"name":"BOX_TOP_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_TOP_LEFT As String = \"┌\""}, + {"name":"BOX_TOP_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_TOP_RIGHT As String = \"┐\""}, + {"name":"BOX_T_BOTTOM","kind":"const","vis":"public","signature":"Public Const BOX_T_BOTTOM As String = \"┴\""}, + {"name":"BOX_T_LEFT","kind":"const","vis":"public","signature":"Public Const BOX_T_LEFT As String = \"├\""}, + {"name":"BOX_T_RIGHT","kind":"const","vis":"public","signature":"Public Const BOX_T_RIGHT As String = \"┤\""}, + {"name":"BOX_T_TOP","kind":"const","vis":"public","signature":"Public Const BOX_T_TOP As String = \"┬\""}, + {"name":"BOX_VERTICAL","kind":"const","vis":"public","signature":"Public Const BOX_VERTICAL As String = \"│\""}, + {"name":"BULLET","kind":"const","vis":"public","signature":"Public Const BULLET As String = \"•\""}, + {"name":"Bg256","kind":"function","vis":"public","signature":"Public Function Bg256(ByVal colorIndex As Long) As String","returnType":"String"}, + {"name":"BgBlack","kind":"function","vis":"public","signature":"Public Function BgBlack() As String","returnType":"String"}, + {"name":"BgBlue","kind":"function","vis":"public","signature":"Public Function BgBlue() As String","returnType":"String"}, + {"name":"BgBrightBlack","kind":"function","vis":"public","signature":"Public Function BgBrightBlack() As String","returnType":"String"}, + {"name":"BgBrightBlue","kind":"function","vis":"public","signature":"Public Function BgBrightBlue() As String","returnType":"String"}, + {"name":"BgBrightCyan","kind":"function","vis":"public","signature":"Public Function BgBrightCyan() As String","returnType":"String"}, + {"name":"BgBrightGreen","kind":"function","vis":"public","signature":"Public Function BgBrightGreen() As String","returnType":"String"}, + {"name":"BgBrightMagenta","kind":"function","vis":"public","signature":"Public Function BgBrightMagenta() As String","returnType":"String"}, + {"name":"BgBrightRed","kind":"function","vis":"public","signature":"Public Function BgBrightRed() As String","returnType":"String"}, + {"name":"BgBrightWhite","kind":"function","vis":"public","signature":"Public Function BgBrightWhite() As String","returnType":"String"}, + {"name":"BgBrightYellow","kind":"function","vis":"public","signature":"Public Function BgBrightYellow() As String","returnType":"String"}, + {"name":"BgCyan","kind":"function","vis":"public","signature":"Public Function BgCyan() As String","returnType":"String"}, + {"name":"BgDefault","kind":"function","vis":"public","signature":"Public Function BgDefault() As String","returnType":"String"}, + {"name":"BgGreen","kind":"function","vis":"public","signature":"Public Function BgGreen() As String","returnType":"String"}, + {"name":"BgMagenta","kind":"function","vis":"public","signature":"Public Function BgMagenta() As String","returnType":"String"}, + {"name":"BgRGB","kind":"function","vis":"public","signature":"Public Function BgRGB(ByVal r As Long, ByVal g As Long, ByVal b As Long) As String","returnType":"String"}, + {"name":"BgRed","kind":"function","vis":"public","signature":"Public Function BgRed() As String","returnType":"String"}, + {"name":"BgWhite","kind":"function","vis":"public","signature":"Public Function BgWhite() As String","returnType":"String"}, + {"name":"BgYellow","kind":"function","vis":"public","signature":"Public Function BgYellow() As String","returnType":"String"}, + {"name":"Blink","kind":"function","vis":"public","signature":"Public Function Blink() As String","returnType":"String"}, + {"name":"Bold","kind":"function","vis":"public","signature":"Public Function Bold() As String","returnType":"String"}, + {"name":"CHECK_MARK","kind":"const","vis":"public","signature":"Public Const CHECK_MARK As String = \"✓\""}, + {"name":"CIRCLE","kind":"const","vis":"public","signature":"Public Const CIRCLE As String = \"●\""}, + {"name":"CIRCLE_EMPTY","kind":"const","vis":"public","signature":"Public Const CIRCLE_EMPTY As String = \"○\""}, + {"name":"CROSS_MARK","kind":"const","vis":"public","signature":"Public Const CROSS_MARK As String = \"✗\""}, + {"name":"CSI","kind":"const","vis":"private","signature":"Private Const CSI As String = Chr$(27) & \"[\""}, + {"name":"CenterText","kind":"function","vis":"public","signature":"Public Function CenterText(ByVal text As String, ByVal width As Long, Optional ByVal fillChar As String = \" \") As String","returnType":"String"}, + {"name":"ClearLine","kind":"function","vis":"public","signature":"Public Function ClearLine(Optional ByVal mode As Long = 2) As String","returnType":"String"}, + {"name":"ClearScreen","kind":"function","vis":"public","signature":"Public Function ClearScreen(Optional ByVal mode As Long = 2) As String","returnType":"String"}, + {"name":"Colored","kind":"function","vis":"public","signature":"Public Function Colored(ByVal text As String, ByVal fgColor As String, Optional ByVal bgColor As String = \"\") As String","returnType":"String"}, + {"name":"ColoredRGB","kind":"function","vis":"public","signature":"Public Function ColoredRGB(ByVal text As String, ByVal fgR As Long, ByVal fgG As Long, ByVal fgB As Long, Optional ByVal bgR As Long = -1, Optional ByVal bgG As Long = -1, Optional ByVal bgB As Long = -1) As String","returnType":"String"}, + {"name":"CursorBack","kind":"function","vis":"public","signature":"Public Function CursorBack(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"CursorColumn","kind":"function","vis":"public","signature":"Public Function CursorColumn(ByVal n As Long) As String","returnType":"String"}, + {"name":"CursorDown","kind":"function","vis":"public","signature":"Public Function CursorDown(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"CursorForward","kind":"function","vis":"public","signature":"Public Function CursorForward(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"CursorHide","kind":"function","vis":"public","signature":"Public Function CursorHide() As String","returnType":"String"}, + {"name":"CursorNextLine","kind":"function","vis":"public","signature":"Public Function CursorNextLine(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"CursorPosition","kind":"function","vis":"public","signature":"Public Function CursorPosition(ByVal row As Long, ByVal col As Long) As String","returnType":"String"}, + {"name":"CursorPrevLine","kind":"function","vis":"public","signature":"Public Function CursorPrevLine(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"CursorRestore","kind":"function","vis":"public","signature":"Public Function CursorRestore() As String","returnType":"String"}, + {"name":"CursorSave","kind":"function","vis":"public","signature":"Public Function CursorSave() As String","returnType":"String"}, + {"name":"CursorShow","kind":"function","vis":"public","signature":"Public Function CursorShow() As String","returnType":"String"}, + {"name":"CursorUp","kind":"function","vis":"public","signature":"Public Function CursorUp(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"DIAMOND","kind":"const","vis":"public","signature":"Public Const DIAMOND As String = \"◆\""}, + {"name":"Dim_","kind":"function","vis":"public","signature":"Public Function Dim_() As String","returnType":"String"}, + {"name":"ESC","kind":"const","vis":"private","signature":"Private Const ESC As String = Chr$(27)"}, + {"name":"Fg256","kind":"function","vis":"public","signature":"Public Function Fg256(ByVal colorIndex As Long) As String","returnType":"String"}, + {"name":"FgBlack","kind":"function","vis":"public","signature":"Public Function FgBlack() As String","returnType":"String"}, + {"name":"FgBlue","kind":"function","vis":"public","signature":"Public Function FgBlue() As String","returnType":"String"}, + {"name":"FgBrightBlack","kind":"function","vis":"public","signature":"Public Function FgBrightBlack() As String","returnType":"String"}, + {"name":"FgBrightBlue","kind":"function","vis":"public","signature":"Public Function FgBrightBlue() As String","returnType":"String"}, + {"name":"FgBrightCyan","kind":"function","vis":"public","signature":"Public Function FgBrightCyan() As String","returnType":"String"}, + {"name":"FgBrightGreen","kind":"function","vis":"public","signature":"Public Function FgBrightGreen() As String","returnType":"String"}, + {"name":"FgBrightMagenta","kind":"function","vis":"public","signature":"Public Function FgBrightMagenta() As String","returnType":"String"}, + {"name":"FgBrightRed","kind":"function","vis":"public","signature":"Public Function FgBrightRed() As String","returnType":"String"}, + {"name":"FgBrightWhite","kind":"function","vis":"public","signature":"Public Function FgBrightWhite() As String","returnType":"String"}, + {"name":"FgBrightYellow","kind":"function","vis":"public","signature":"Public Function FgBrightYellow() As String","returnType":"String"}, + {"name":"FgCyan","kind":"function","vis":"public","signature":"Public Function FgCyan() As String","returnType":"String"}, + {"name":"FgDefault","kind":"function","vis":"public","signature":"Public Function FgDefault() As String","returnType":"String"}, + {"name":"FgGreen","kind":"function","vis":"public","signature":"Public Function FgGreen() As String","returnType":"String"}, + {"name":"FgMagenta","kind":"function","vis":"public","signature":"Public Function FgMagenta() As String","returnType":"String"}, + {"name":"FgRGB","kind":"function","vis":"public","signature":"Public Function FgRGB(ByVal r As Long, ByVal g As Long, ByVal b As Long) As String","returnType":"String"}, + {"name":"FgRed","kind":"function","vis":"public","signature":"Public Function FgRed() As String","returnType":"String"}, + {"name":"FgWhite","kind":"function","vis":"public","signature":"Public Function FgWhite() As String","returnType":"String"}, + {"name":"FgYellow","kind":"function","vis":"public","signature":"Public Function FgYellow() As String","returnType":"String"}, + {"name":"Hidden","kind":"function","vis":"public","signature":"Public Function Hidden() As String","returnType":"String"}, + {"name":"Hyperlink","kind":"function","vis":"public","signature":"Public Function Hyperlink(ByVal url As String, ByVal text As String) As String","returnType":"String"}, + {"name":"Italic","kind":"function","vis":"public","signature":"Public Function Italic() As String","returnType":"String"}, + {"name":"LeftAlign","kind":"function","vis":"public","signature":"Public Function LeftAlign(ByVal text As String, ByVal width As Long, Optional ByVal fillChar As String = \" \") As String","returnType":"String"}, + {"name":"PROGRESS_EMPTY","kind":"const","vis":"public","signature":"Public Const PROGRESS_EMPTY As String = \"░\""}, + {"name":"PROGRESS_FILLED","kind":"const","vis":"public","signature":"Public Const PROGRESS_FILLED As String = \"█\""}, + {"name":"RepeatStr","kind":"function","vis":"public","signature":"Public Function RepeatStr(ByVal s As String, ByVal count As Long) As String","returnType":"String"}, + {"name":"Reset","kind":"function","vis":"public","signature":"Public Function Reset() As String","returnType":"String"}, + {"name":"Reverse","kind":"function","vis":"public","signature":"Public Function Reverse() As String","returnType":"String"}, + {"name":"RightAlign","kind":"function","vis":"public","signature":"Public Function RightAlign(ByVal text As String, ByVal width As Long, Optional ByVal fillChar As String = \" \") As String","returnType":"String"}, + {"name":"SQUARE","kind":"const","vis":"public","signature":"Public Const SQUARE As String = \"■\""}, + {"name":"SQUARE_EMPTY","kind":"const","vis":"public","signature":"Public Const SQUARE_EMPTY As String = \"□\""}, + {"name":"STAR","kind":"const","vis":"public","signature":"Public Const STAR As String = \"★\""}, + {"name":"STAR_EMPTY","kind":"const","vis":"public","signature":"Public Const STAR_EMPTY As String = \"☆\""}, + {"name":"ScrollDown","kind":"function","vis":"public","signature":"Public Function ScrollDown(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"ScrollUp","kind":"function","vis":"public","signature":"Public Function ScrollUp(Optional ByVal n As Long = 1) As String","returnType":"String"}, + {"name":"SetTitle","kind":"function","vis":"public","signature":"Public Function SetTitle(ByVal title As String) As String","returnType":"String"}, + {"name":"Strikethrough","kind":"function","vis":"public","signature":"Public Function Strikethrough() As String","returnType":"String"}, + {"name":"StripAnsi","kind":"function","vis":"public","signature":"Public Function StripAnsi(ByVal text As String) As String","returnType":"String"}, + {"name":"Styled","kind":"function","vis":"public","signature":"Public Function Styled(ByVal text As String, ByVal style As String) As String","returnType":"String"}, + {"name":"Truncate","kind":"function","vis":"public","signature":"Public Function Truncate(ByVal text As String, ByVal maxLen As Long, Optional ByVal ellipsis As String = \"...\") As String","returnType":"String"}, + {"name":"Underline","kind":"function","vis":"public","signature":"Public Function Underline() As String","returnType":"String"}, + {"name":"WordWrap","kind":"function","vis":"public","signature":"Public Function WordWrap(ByVal text As String, ByVal width As Long) As String","returnType":"String"} + ] + }, + { + "name": "mConsoleTypes", + "kind": "module", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Module mConsoleTypes", + "members": [ + {"name":"ConsoleRect_Contains","kind":"function","vis":"public","signature":"Public Function ConsoleRect_Contains(ByVal rect As ConsoleRect, ByVal x As Long, ByVal y As Long) As Boolean","returnType":"Boolean"}, + {"name":"ConsoleRect_Create","kind":"function","vis":"public","signature":"Public Function ConsoleRect_Create(ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long) As ConsoleRect","returnType":"ConsoleRect"}, + {"name":"ConsoleRect_Height","kind":"function","vis":"public","signature":"Public Function ConsoleRect_Height(ByVal rect As ConsoleRect) As Long","returnType":"Long"}, + {"name":"ConsoleRect_Width","kind":"function","vis":"public","signature":"Public Function ConsoleRect_Width(ByVal rect As ConsoleRect) As Long","returnType":"Long"} + ] + }, + { + "name": "ConsoleCharInfo", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleCharInfo", + "fields": [ + {"name":"Char","vis":"public","signature":"Char As String"}, + {"name":"ForeColor","vis":"public","signature":"ForeColor As ConsoleColor"}, + {"name":"BackColor","vis":"public","signature":"BackColor As ConsoleColor"}, + {"name":"Style","vis":"public","signature":"Style As ConsoleTextStyle"} + ], + "members": [] + }, + { + "name": "ConsoleColor", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ConsoleColor", + "values": [ + {"name":"Black","value":"0"}, + {"name":"DarkBlue","value":"1"}, + {"name":"DarkGreen","value":"2"}, + {"name":"DarkCyan","value":"3"}, + {"name":"DarkRed","value":"4"}, + {"name":"DarkMagenta","value":"5"}, + {"name":"DarkYellow","value":"6"}, + {"name":"Gray","value":"7"}, + {"name":"DarkGray","value":"8"}, + {"name":"Blue","value":"9"}, + {"name":"Green","value":"10"}, + {"name":"Cyan","value":"11"}, + {"name":"Red","value":"12"}, + {"name":"Magenta","value":"13"}, + {"name":"Yellow","value":"14"}, + {"name":"White","value":"15"} + ] + }, + { + "name": "ConsoleEventInfo", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleEventInfo", + "fields": [ + {"name":"EventType","vis":"public","signature":"EventType As ConsoleEventType"}, + {"name":"KeyInfo","vis":"public","signature":"KeyInfo As ConsoleKeyInfo"}, + {"name":"MouseInfo","vis":"public","signature":"MouseInfo As ConsoleMouseInfo"}, + {"name":"NewSize","vis":"public","signature":"NewSize As ConsoleSize"} + ], + "members": [] + }, + { + "name": "ConsoleEventType", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ConsoleEventType", + "values": [ + {"name":"EventNone","value":"0"}, + {"name":"EventResize","value":"1"}, + {"name":"EventKeyPress","value":"2"}, + {"name":"EventMouseClick","value":"3"}, + {"name":"EventFocus","value":"4"}, + {"name":"EventCtrlC","value":"5"}, + {"name":"EventCtrlBreak","value":"6"}, + {"name":"EventClose","value":"7"} + ] + }, + { + "name": "ConsoleKey", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ConsoleKey", + "values": [ + {"name":"KeyEnter","value":"13"}, + {"name":"KeyEscape","value":"27"}, + {"name":"KeySpace","value":"32"}, + {"name":"KeyBackspace","value":"8"}, + {"name":"KeyTab","value":"9"}, + {"name":"KeyUp","value":"72"}, + {"name":"KeyDown","value":"80"}, + {"name":"KeyLeft","value":"75"}, + {"name":"KeyRight","value":"77"}, + {"name":"KeyHome","value":"71"}, + {"name":"KeyEnd","value":"79"}, + {"name":"KeyPageUp","value":"73"}, + {"name":"KeyPageDown","value":"81"}, + {"name":"KeyInsert","value":"82"}, + {"name":"KeyDelete","value":"83"}, + {"name":"KeyF1","value":"59"}, + {"name":"KeyF2","value":"60"}, + {"name":"KeyF3","value":"61"}, + {"name":"KeyF4","value":"62"}, + {"name":"KeyF5","value":"63"}, + {"name":"KeyF6","value":"64"}, + {"name":"KeyF7","value":"65"}, + {"name":"KeyF8","value":"66"}, + {"name":"KeyF9","value":"67"}, + {"name":"KeyF10","value":"68"}, + {"name":"KeyF11","value":"133"}, + {"name":"KeyF12","value":"134"} + ] + }, + { + "name": "ConsoleKeyInfo", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleKeyInfo", + "fields": [ + {"name":"KeyCode","vis":"public","signature":"KeyCode As Long"}, + {"name":"KeyChar","vis":"public","signature":"KeyChar As String"}, + {"name":"IsAltPressed","vis":"public","signature":"IsAltPressed As Boolean"}, + {"name":"IsCtrlPressed","vis":"public","signature":"IsCtrlPressed As Boolean"}, + {"name":"IsShiftPressed","vis":"public","signature":"IsShiftPressed As Boolean"} + ], + "members": [] + }, + { + "name": "ConsoleMode", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ConsoleMode", + "attrs": [{"name":"Flags"}], + "values": [ + {"name":"ModeNormal","value":"0"}, + {"name":"ModeVirtualTerminal","value":"1"}, + {"name":"ModeFullScreen","value":"2"}, + {"name":"ModeRaw","value":"4"} + ] + }, + { + "name": "ConsoleMouseInfo", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleMouseInfo", + "fields": [ + {"name":"Col","vis":"public","signature":"Col As Long"}, + {"name":"Row","vis":"public","signature":"Row As Long"}, + {"name":"Button","vis":"public","signature":"Button As Long"}, + {"name":"IsDoubleClick","vis":"public","signature":"IsDoubleClick As Boolean"} + ], + "members": [] + }, + { + "name": "ConsolePosition", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsolePosition", + "fields": [ + {"name":"Col","vis":"public","signature":"Col As Long"}, + {"name":"Row","vis":"public","signature":"Row As Long"} + ], + "members": [] + }, + { + "name": "ConsoleRect", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleRect", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Long"}, + {"name":"Top","vis":"public","signature":"Top As Long"}, + {"name":"Right","vis":"public","signature":"Right As Long"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Long"} + ], + "members": [] + }, + { + "name": "ConsoleSize", + "kind": "type", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Type ConsoleSize", + "fields": [ + {"name":"Width","vis":"public","signature":"Width As Long"}, + {"name":"Height","vis":"public","signature":"Height As Long"} + ], + "members": [] + }, + { + "name": "ConsoleTextStyle", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ConsoleTextStyle", + "attrs": [{"name":"Flags"}], + "values": [ + {"name":"StyleNone","value":"0"}, + {"name":"StyleBold","value":"1"}, + {"name":"StyleUnderline","value":"2"}, + {"name":"StyleReverse","value":"4"} + ] + }, + { + "name": "ThemeColorType", + "kind": "enum", + "in": "mConsoleTypes", + "vis": "public", + "file": "Core/mConsoleTypes.twin", + "signature": "Public Enum ThemeColorType", + "values": [ + {"name":"tcPrimary","value":"1"}, + {"name":"tcSecondary","value":"2"}, + {"name":"tcSuccess","value":"3"}, + {"name":"tcWarning","value":"4"}, + {"name":"tcError","value":"5"}, + {"name":"tcInfo","value":"6"}, + {"name":"tcMuted","value":"7"}, + {"name":"tcBackground","value":"8"}, + {"name":"tcForeground","value":"9"}, + {"name":"tcBorder","value":"10"}, + {"name":"tcHeaderBackground","value":"11"}, + {"name":"tcHeaderForeground","value":"12"}, + {"name":"tcSelectedBackground","value":"13"}, + {"name":"tcSelectedForeground","value":"14"} + ] + } + ], + "aliases": [], + "problems": [] +} diff --git a/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings b/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings new file mode 100644 index 000000000..88f567b5c --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB_ConsoleComponents/Settings @@ -0,0 +1,87 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{A761CB2F-8CAF-47E7-8FE1-2067E6F93FFB}", + "licence": "", + "name": "[IMPORTED] TBMAN: Text UI (TUI) component library base on ConsoleCore", + "path32": "/Packages/TBMANLIB_ConsoleComponents", + "path64": "/Packages/TBMANLIB_ConsoleComponents", + "symbolId": "TBMANLIB_ConsoleComponents", + "versionBuild": "0", + "versionMajor": "0", + "versionMinor": "0", + "versionRevision": "0" + }, + { + "autoCtlExt": true, + "id": "{A0CCFE06-651A-40A4-84D9-0CA4200A8224}", + "licence": "", + "name": "[IMPORTED] TBMAN: Modern console library with UTF-8, colors, and full-screen support.", + "path32": "/Packages/TBMANLIB_ConsoleCore", + "path64": "/Packages/TBMANLIB_ConsoleCore", + "symbolId": "TBMANLIB_ConsoleCore", + "versionBuild": "0", + "versionMajor": "0", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/example-projects/contributed-TBMANLIB_ConsoleCore/Settings b/test/example-projects/contributed-TBMANLIB_ConsoleCore/Settings new file mode 100644 index 000000000..f81f3de70 --- /dev/null +++ b/test/example-projects/contributed-TBMANLIB_ConsoleCore/Settings @@ -0,0 +1,74 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Template for scripts/check_examples.mjs. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "autoCtlExt": true, + "id": "{A0CCFE06-651A-40A4-84D9-0CA4200A8224}", + "licence": "", + "name": "[IMPORTED] TBMAN: Modern console library with UTF-8, colors, and full-screen support.", + "path32": "/Packages/TBMANLIB_ConsoleCore", + "path64": "/Packages/TBMANLIB_ConsoleCore", + "symbolId": "TBMANLIB_ConsoleCore", + "versionBuild": "0", + "versionMajor": "0", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +}