From 53394a8a32e371397f7de4104ba2207ff141d29b Mon Sep 17 00:00:00 2001 From: Hecon5 <54177882+hecon5@users.noreply.github.com> Date: Sat, 23 Sep 2023 09:03:00 -0400 Subject: [PATCH 01/73] Updating clsPerformance, as some objects never restart timing, and when resetting some objects are not cleared. Fixes #331 --- .../modules/clsPerformance.cls | 185 +++++++++++++++++- 1 file changed, 179 insertions(+), 6 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index 1621c427..c3ac1779 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -20,6 +20,7 @@ Attribute VB_Exposed = False ' : microsecond level. For additional details, see the following link: ' : http://www.mendipdatasystems.co.uk/timer-comparison-tests/4594552971 '--------------------------------------------------------------------------------------- + Option Compare Database Option Explicit @@ -76,6 +77,7 @@ End Sub Public Property Get CallStack() As String Dim lngCallStackPosition As Long + Dim strCallStackElement As String If Not Me.Enabled Then Exit Property @@ -96,6 +98,11 @@ Public Property Get CallStack() As String End Property +Public Property Get CurrentCategoryName() As String + CurrentCategoryName = this.CategoryName +End Property + + '--------------------------------------------------------------------------------------- ' Procedure : CategoryStart ' Author : Adam Waller @@ -105,15 +112,15 @@ End Property ' Public Sub CategoryStart(strName As String) If Not Me.Enabled Then Exit Sub - If this.CategoryName <> vbNullString Then CategoryEnd If this.Categories Is Nothing Then Set this.Categories = New Dictionary + If this.CategoryName <> vbNullString Then CategoryEnd StartTimer this.Categories, strName this.CategoryName = strName End Sub '--------------------------------------------------------------------------------------- -' Procedure : ComponentEnd +' Procedure : CategoryEnd ' Author : Adam Waller ' Date : 11/3/2020 ' Purpose : End the timing of the active component @@ -128,6 +135,11 @@ Public Sub CategoryEnd(Optional lngCount As Long = 1) End Sub +Public Property Get CurrentOperationName() As String + CurrentOperationName = this.OperationName +End Property + + '--------------------------------------------------------------------------------------- ' Procedure : OperationStart ' Author : Adam Waller @@ -199,6 +211,8 @@ Public Sub OperationEnd(Optional lngCount As Long = 1) this.OperationName = vbNullString End If End With + Else + this.OperationName = vbNullString End If End If @@ -331,6 +345,9 @@ Public Sub ResumeTiming() ' Resume current operation If this.OperationName <> vbNullString Then StartTimer this.Operations, this.OperationName + ' Resume current Category + If this.CategoryName <> vbNullString Then StartTimer this.Categories, this.CategoryName + End Sub @@ -439,6 +456,9 @@ Public Function GetReports() As String curTotal = curTotal + this.Operations(varKey).Total Next varKey .Add strSpacer + .Add ListResult("TOTALS:", CStr(dblCount), _ + Format(curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) + .Add strSpacer If Not this.Overall Is Nothing Then .Add ListResult("Other Operations", vbNullString, _ Format(this.Overall.Total - curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) @@ -479,10 +499,163 @@ End Function ' : MyFancyTest 23 2.45 '--------------------------------------------------------------------------------------- ' -Private Function ListResult(strHeading As String, strResult1 As String, strResult2 As String, _ - lngCol() As Long) As String - ListResult = PadRight(strHeading, lngCol(0)) & _ - PadRight(strResult1, lngCol(1)) & strResult2 +Public Function ListResult(ByRef strHeading As String _ + , ByRef strResult1 As String _ + , ByRef strResult2 As String _ + , ByRef lngCol() As Long) As String + ListResult = ListResultIndent(strHeading, strResult1, strResult2, lngCol) +End Function + + +Public Function ListResultIndent(ByRef strHeading As String _ + , ByRef strResult1 As String _ + , ByRef strResult2 As String _ + , ByRef lngCol() As Long _ + , Optional ByVal ColumnIndent As Long = 2) As String + + Dim Col1StrArr() As String + Dim Col2StrArr() As String + Dim Col3StrArr() As String + + Dim Col1Rows As Long + Dim Col2Rows As Long + Dim Col3Rows As Long + + Dim RowTotal As Long + Dim RowPosition As Long + + Dim StrOutput As clsConcat + + On Error Resume Next + Perf.OperationStart ModuleName & ".ListResultIndent" + + Col1StrArr = FitStringToColumn(strHeading, lngCol(0) - 1, ColumnIndent) + Col2StrArr = FitStringToColumn(strResult1, lngCol(1) - 1, ColumnIndent) + Col3StrArr = FitStringToColumn(strResult2, lngCol(2) - 1, ColumnIndent) + + Col1Rows = UBound(Col1StrArr) + Col2Rows = UBound(Col2StrArr) + Col3Rows = UBound(Col3StrArr) + + RowTotal = MaxValue(Col1Rows, Col2Rows, Col3Rows) + + Set StrOutput = New clsConcat + + For RowPosition = 0 To RowTotal + + If Col1Rows >= RowPosition Then + StrOutput.Add PadRight(Col1StrArr(RowPosition), lngCol(0)) + Else + StrOutput.Add Space$(lngCol(0)) + End If + If Col2Rows >= RowPosition Then + StrOutput.Add PadRight(Col2StrArr(RowPosition), lngCol(1)) + Else + StrOutput.Add Space$(lngCol(1)) + End If + If Col3Rows >= RowPosition Then + StrOutput.Add PadRight(Col3StrArr(RowPosition), lngCol(2)) + Else + StrOutput.Add Space$(lngCol(2)) + End If + ' Don't add a new line for the last line; it's handled outside this tool + If RowTotal > RowPosition Then StrOutput.Add vbNewLine + + Next RowPosition + + ListResultIndent = StrOutput.GetStr + Perf.OperationEnd +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : FitStringToColumn +' Author : hecon5 +' Date : May 18, 2022 +' Purpose : Takes in a long string and returns an array of strings ColumnWidth wide. +'--------------------------------------------------------------------------------------- +' +Public Function FitStringToColumn(ByRef LongString As String _ + , Optional ByRef ColumnWidth As Long = 200 _ + , Optional ByRef ColumnIndent As Long = 0) As String() + + Dim RowTotal As Long + Dim StrLen As Long + Dim StrIndentedLen As Long + Dim StrTextWidth As Long + Dim StrPosition As Long + Dim ArrPosition As Long + Dim StrArr() As String + Dim ColumnWidthInternal As Long + + On Error Resume Next + Perf.OperationStart ModuleName & ".FitStringToColumn" + If Len(LongString) = 0 Then Exit Function + ColumnWidthInternal = ColumnWidth + If ColumnWidthInternal <= 0 Then ColumnWidthInternal = 1 + + StrTextWidth = ColumnWidthInternal - ColumnIndent + + StrLen = Len(LongString) + RowTotal = RoundUp((StrLen - ColumnWidthInternal) / StrTextWidth) + 1 + If RowTotal < 1 Then RowTotal = 1 + StrPosition = 1 + + ReDim StrArr(0 To (RowTotal - 1)) + + ' The first row is longer. + StrArr(ArrPosition) = Mid$(LongString, StrPosition, ColumnWidthInternal) + If RowTotal <= 1 Then GoTo Exit_Here ' Don't do the rest if there's only one row... + + StrPosition = StrPosition + ColumnWidthInternal + + For ArrPosition = 1 To (RowTotal - 1) + StrArr(ArrPosition) = Space$(ColumnIndent) & Mid$(LongString, StrPosition, StrTextWidth) + StrPosition = StrPosition + StrTextWidth + Next ArrPosition + +Exit_Here: + CatchAny eelError, "Could not fit to column", Perf.CurrentOperationName + FitStringToColumn = StrArr + Perf.OperationEnd +End Function + + +Public Function FitStringToWidth(ByRef LongString As String _ + , Optional ByRef MaxWidth As Long = 200 _ + , Optional ByRef DesiredWidth As Long = 75) As String + ' Fits a string to a message box if it's wider than MaxWidth + Dim OutputConcat As clsConcat + Dim StrPosition As Long + Dim StrLen As Long ' Length of total string + Dim NewLineCount As Long ' Number of newlines + Dim ArrPosition As Long + Dim StrArrLen As Long ' Length of substring + Dim StringArr() As String + + Perf.OperationStart "FitStringToWidth" + StrLen = Len(LongString) + If StrLen > MaxWidth Then + Perf.OperationStart "FitStringToWidth.Resize" + StringArr = Split(LongString, vbNewLine, , vbTextCompare) + NewLineCount = UBound(StringArr) - LBound(StringArr) + Set OutputConcat = New clsConcat + For ArrPosition = 0 To NewLineCount + StrPosition = 1 + StrArrLen = Len(StringArr(ArrPosition)) + If ArrPosition > 0 Then OutputConcat.Add vbNewLine + Do While StrPosition < StrArrLen + If StrPosition > 1 Then OutputConcat.Add vbNewLine + OutputConcat.Add Mid$(StringArr(ArrPosition), StrPosition, DesiredWidth) + StrPosition = StrPosition + DesiredWidth + Loop + Next ArrPosition + FitStringToWidth = OutputConcat.GetStr + Perf.OperationEnd + Else + FitStringToWidth = LongString + End If + Perf.OperationEnd End Function From 4a27077e2d2843d901fe9860d795df32912bb4a6 Mon Sep 17 00:00:00 2001 From: Hecon5 <54177882+hecon5@users.noreply.github.com> Date: Sat, 23 Sep 2023 09:19:57 -0400 Subject: [PATCH 02/73] Fixing Private/Public declarations. --- .../modules/clsPerformance.cls | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index c3ac1779..3f345019 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -499,7 +499,7 @@ End Function ' : MyFancyTest 23 2.45 '--------------------------------------------------------------------------------------- ' -Public Function ListResult(ByRef strHeading As String _ +Private Function ListResult(ByRef strHeading As String _ , ByRef strResult1 As String _ , ByRef strResult2 As String _ , ByRef lngCol() As Long) As String @@ -507,7 +507,7 @@ Public Function ListResult(ByRef strHeading As String _ End Function -Public Function ListResultIndent(ByRef strHeading As String _ +Private Function ListResultIndent(ByRef strHeading As String _ , ByRef strResult1 As String _ , ByRef strResult2 As String _ , ByRef lngCol() As Long _ @@ -524,10 +524,9 @@ Public Function ListResultIndent(ByRef strHeading As String _ Dim RowTotal As Long Dim RowPosition As Long - Dim StrOutput As clsConcat + Dim strOutput As clsConcat On Error Resume Next - Perf.OperationStart ModuleName & ".ListResultIndent" Col1StrArr = FitStringToColumn(strHeading, lngCol(0) - 1, ColumnIndent) Col2StrArr = FitStringToColumn(strResult1, lngCol(1) - 1, ColumnIndent) @@ -539,32 +538,74 @@ Public Function ListResultIndent(ByRef strHeading As String _ RowTotal = MaxValue(Col1Rows, Col2Rows, Col3Rows) - Set StrOutput = New clsConcat + Set strOutput = New clsConcat For RowPosition = 0 To RowTotal If Col1Rows >= RowPosition Then - StrOutput.Add PadRight(Col1StrArr(RowPosition), lngCol(0)) + strOutput.Add PadRight(Col1StrArr(RowPosition), lngCol(0)) Else - StrOutput.Add Space$(lngCol(0)) + strOutput.Add Space$(lngCol(0)) End If If Col2Rows >= RowPosition Then - StrOutput.Add PadRight(Col2StrArr(RowPosition), lngCol(1)) + strOutput.Add PadRight(Col2StrArr(RowPosition), lngCol(1)) Else - StrOutput.Add Space$(lngCol(1)) + strOutput.Add Space$(lngCol(1)) End If If Col3Rows >= RowPosition Then - StrOutput.Add PadRight(Col3StrArr(RowPosition), lngCol(2)) + strOutput.Add PadRight(Col3StrArr(RowPosition), lngCol(2)) Else - StrOutput.Add Space$(lngCol(2)) + strOutput.Add Space$(lngCol(2)) End If ' Don't add a new line for the last line; it's handled outside this tool - If RowTotal > RowPosition Then StrOutput.Add vbNewLine + If RowTotal > RowPosition Then strOutput.Add vbNewLine Next RowPosition - ListResultIndent = StrOutput.GetStr - Perf.OperationEnd + ListResultIndent = strOutput.GetStr +End Function + + +Private Function MaxValue(ParamArray ValueIn() As Variant) As Variant + Dim Output As Variant + Dim ArrayPosition As Long + + ' Load the first value in to compare to. + Output = ValueIn(LBound(ValueIn)) + + For ArrayPosition = LBound(ValueIn) + 1 To UBound(ValueIn) + If Output < ValueIn(ArrayPosition) Then Output = ValueIn(ArrayPosition) + Next ArrayPosition + + MaxValue = Output +End Function + + +Private Function RoundUp(ByVal Value As Double) As Long + Dim lngVal As Long + Dim deltaValue As Double + + lngVal = CLng(Value) + deltaValue = lngVal - Value + + If deltaValue < 0 Then + RoundUp = lngVal + 1 + Else + RoundUp = lngVal + End If +End Function +Private Function RoundDown(ByVal Value As Double) As Long + Dim lngVal As Long + Dim deltaValue As Double + + lngVal = CLng(Value) + deltaValue = lngVal - Value + + If deltaValue <= 0 Then + RoundDown = lngVal + Else + RoundDown = lngVal - 1 + End If End Function @@ -575,7 +616,7 @@ End Function ' Purpose : Takes in a long string and returns an array of strings ColumnWidth wide. '--------------------------------------------------------------------------------------- ' -Public Function FitStringToColumn(ByRef LongString As String _ +Private Function FitStringToColumn(ByRef LongString As String _ , Optional ByRef ColumnWidth As Long = 200 _ , Optional ByRef ColumnIndent As Long = 0) As String() @@ -589,7 +630,6 @@ Public Function FitStringToColumn(ByRef LongString As String _ Dim ColumnWidthInternal As Long On Error Resume Next - Perf.OperationStart ModuleName & ".FitStringToColumn" If Len(LongString) = 0 Then Exit Function ColumnWidthInternal = ColumnWidth If ColumnWidthInternal <= 0 Then ColumnWidthInternal = 1 @@ -615,13 +655,11 @@ Public Function FitStringToColumn(ByRef LongString As String _ Next ArrPosition Exit_Here: - CatchAny eelError, "Could not fit to column", Perf.CurrentOperationName FitStringToColumn = StrArr - Perf.OperationEnd End Function -Public Function FitStringToWidth(ByRef LongString As String _ +Private Function FitStringToWidth(ByRef LongString As String _ , Optional ByRef MaxWidth As Long = 200 _ , Optional ByRef DesiredWidth As Long = 75) As String ' Fits a string to a message box if it's wider than MaxWidth @@ -633,10 +671,8 @@ Public Function FitStringToWidth(ByRef LongString As String _ Dim StrArrLen As Long ' Length of substring Dim StringArr() As String - Perf.OperationStart "FitStringToWidth" StrLen = Len(LongString) If StrLen > MaxWidth Then - Perf.OperationStart "FitStringToWidth.Resize" StringArr = Split(LongString, vbNewLine, , vbTextCompare) NewLineCount = UBound(StringArr) - LBound(StringArr) Set OutputConcat = New clsConcat @@ -651,11 +687,9 @@ Public Function FitStringToWidth(ByRef LongString As String _ Loop Next ArrPosition FitStringToWidth = OutputConcat.GetStr - Perf.OperationEnd Else FitStringToWidth = LongString End If - Perf.OperationEnd End Function @@ -666,7 +700,9 @@ End Function ' Purpose : Pads a string '--------------------------------------------------------------------------------------- ' -Private Function PadRight(strText As String, lngLen As Long, Optional lngMinTrailingSpaces As Long = 1) As String +Private Function PadRight(strText As String _ + , lngLen As Long _ + , Optional lngMinTrailingSpaces As Long = 1) As String Dim strResult As String Dim strTrimmed As String From bd2453851852ee993118b01788c549021bf09f7f Mon Sep 17 00:00:00 2001 From: Hecon5 <54177882+hecon5@users.noreply.github.com> Date: Sat, 23 Sep 2023 09:21:13 -0400 Subject: [PATCH 03/73] This isn't actually used. --- Version Control.accda.src/modules/clsPerformance.cls | 1 - 1 file changed, 1 deletion(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index 3f345019..7ef5bc1f 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -77,7 +77,6 @@ End Sub Public Property Get CallStack() As String Dim lngCallStackPosition As Long - Dim strCallStackElement As String If Not Me.Enabled Then Exit Property From 6e8007a4fe57d34df86aba93441573c2037c3646 Mon Sep 17 00:00:00 2001 From: Hecon5 <54177882+hecon5@users.noreply.github.com> Date: Sat, 23 Sep 2023 09:26:30 -0400 Subject: [PATCH 04/73] Bump Version --- Version Control.accda.src/dbs-properties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Version Control.accda.src/dbs-properties.json b/Version Control.accda.src/dbs-properties.json index 9c197f0f..0f394126 100644 --- a/Version Control.accda.src/dbs-properties.json +++ b/Version Control.accda.src/dbs-properties.json @@ -41,7 +41,7 @@ "Type": 10 }, "AppVersion": { - "Value": "4.0.22", + "Value": "4.0.24", "Type": 10 }, "Auto Compact": { From 62fde81a442197c48a7f5919f097bbc802ba22d0 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 2 Oct 2023 11:19:38 -0500 Subject: [PATCH 05/73] Update API examples Removed dependency on an external function, and added an example for building from source. --- Version Control.accda.src/modules/modAPI.bas | 60 +++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modAPI.bas b/Version Control.accda.src/modules/modAPI.bas index b6a230ca..99daca10 100644 --- a/Version Control.accda.src/modules/modAPI.bas +++ b/Version Control.accda.src/modules/modAPI.bas @@ -220,7 +220,7 @@ Public Function ExampleLoadAddInAndRunExport() Dim objAddIn As Object ' VBProject ' Build default add-in path - strAddInPath = GetAddInFileName + strAddInPath = Environ$("AppData") & "\MSAccessVCS\Version Control.accda" ' See if add-in project is already loaded. For Each proj In VBE.VBProjects @@ -256,3 +256,61 @@ Public Function ExampleLoadAddInAndRunExport() End If End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : ExampleBuildFromSource +' Author : Adam Waller +' Date : 9/6/2023 +' Purpose : This function can be copied to a local database and triggered with a +' : command line argument or other automation technique to load the VCS +' : add-in file and build this project from source. +' : NOTE: This expects the add-in to be installed in the default location +' : and using the default file name. +'--------------------------------------------------------------------------------------- +' +Public Function ExampleBuildFromSource() + + Dim strAddInPath As String + Dim proj As Object ' VBProject + Dim objAddIn As Object ' VBProject + + ' Build default add-in path + strAddInPath = Environ$("AppData") & "\MSAccessVCS\Version Control.accda" + + ' See if add-in project is already loaded. + For Each proj In VBE.VBProjects + If StrComp(proj.FileName, strAddInPath, vbTextCompare) = 0 Then + Set objAddIn = proj + End If + Next proj + + ' If not loaded, then attempt to load the add-in. + If objAddIn Is Nothing Then + + ' The following lines will load the add-in at the application level, + ' but will not actually call the function. Ignore the error of function not found. + ' https://stackoverflow.com/questions/62270088/how-can-i-launch-an-access-add-in-not-com-add-in-from-vba-code + On Error Resume Next + Application.Run strAddInPath & "!DummyFunction" + On Error GoTo 0 + + ' See if it is loaded now... + For Each proj In VBE.VBProjects + If StrComp(proj.FileName, strAddInPath, vbTextCompare) = 0 Then + Set objAddIn = proj + End If + Next proj + End If + + If objAddIn Is Nothing Then + MsgBox "Unable to load Version Control add-in. Please ensure that it has been installed" & vbCrLf & _ + "and is functioning correctly. (It should be available in the Add-ins menu.)", vbExclamation + Else + ' Set the application interaction level to silent to skip confirmation dialogs. + Application.Run "MSAccessVCS.SetInteractionMode", 1 + ' Launch the build process (as if we clicked the button on the ribbon) + Application.Run "MSAccessVCS.HandleRibbonCommand", "btnBuild" + End If + +End Function From 3273089c3255a84a270e6c205f42a775e30e4da3 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 2 Oct 2023 11:29:03 -0500 Subject: [PATCH 06/73] Refine some dialect-specific SQL string quotations Backticks only apply to MySQL, while square brackets are used with MSSQL and Access. #442 --- .../modules/clsSqlFormatter.cls | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Version Control.accda.src/modules/clsSqlFormatter.cls b/Version Control.accda.src/modules/clsSqlFormatter.cls index 517f9ae3..423794b8 100644 --- a/Version Control.accda.src/modules/clsSqlFormatter.cls +++ b/Version Control.accda.src/modules/clsSqlFormatter.cls @@ -720,7 +720,9 @@ End Function ' Procedure : GetQuotedString ' Author : Adam Waller ' Date : 8/12/2023 -' Purpose : Return a quoted string (Applies four possible rules) +' Purpose : Return a quoted string (dialect-specific) +' : https://stackoverflow.com/q/10573922/4121863 +' : https://stackoverflow.com/q/9719869/4121863 '--------------------------------------------------------------------------------------- ' Private Function GetQuotedString(Optional lngStartOffset As Long = 0) As String @@ -733,26 +735,28 @@ Private Function GetQuotedString(Optional lngStartOffset As Long = 0) As String ' Build out RegEx expression .Add "^(" - ' (1) backtick quoted string using `` to escape - .Add "((`[^`]*($|`))+)|" - - ' (2) square bracket quoted string (SQL Server) using ]] to escape - .Add "((\[[^\]]*($|\]))(\][^\]]*($|\]))*)|" - + ' Accomodate dialect-specific variants Select Case m_intDialect + Case esdMySQL - ' (3) double quoted string using "" or \" to escape + ' Backtick quoted string using `` to escape + .Add "((`[^`]*($|`))+)|" + + ' Double quoted string using "" or \" to escape .Add "((""[^""\\\\]*(?:\\\\.[^""\\\\]*)*(""|$))+)|" - ' (4) single quoted string using '' or \' to escape - .Add "((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+)" ' sx', + ' Single quoted string using '' or \' to escape + .Add "((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+)" Case Else - ' (3) double quoted string using "" to escape + ' Square bracket quoted string (SQL Server) using ]] to escape + .Add "((\[[^\]]*($|\]))(\][^\]]*($|\]))*)|" + + ' Double quoted string using "" to escape .Add "((""[^""]*(""|$))+)|" - ' (4) single quoted string using '' to escape - .Add "((\'[^\']*(\'|$))+)" ' sx', + ' Single quoted string using '' to escape + .Add "((\'[^\']*(\'|$))+)" End Select .Add ")" From 1cda77474b798e2462b93b78f7e9a765a14a9d83 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 19 Oct 2023 14:02:29 -0500 Subject: [PATCH 07/73] Allow wrapping of long names in performance class Extending the performance class to allow the wrapping of long names used for categories or operations. (Not really needed within this project, but could potentially be helpful in the future with translations.) #441 --- .../modules/clsPerformance.cls | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index 1621c427..4644c35e 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -474,15 +474,49 @@ End Function ' Author : Adam Waller ' Date : 11/3/2020 ' Purpose : List the result of a test in a fixed width format. The result strings -' : are positioned at the number of characters specified. +' : are positioned at the number of characters specified. If the heading size +' : exceeds the width of the column, the text will be wrapped. ' : I.e: ' : MyFancyTest 23 2.45 +' : My very long nam +' : e that I probabl +' : y should condens +' : e 12 3.23 '--------------------------------------------------------------------------------------- ' Private Function ListResult(strHeading As String, strResult1 As String, strResult2 As String, _ lngCol() As Long) As String - ListResult = PadRight(strHeading, lngCol(0)) & _ - PadRight(strResult1, lngCol(1)) & strResult2 + + Dim strRowHeading As String + Dim lngPos As Long + Dim intMax As Integer + + ' Wrap at one character less than the column width + intMax = lngCol(0) - 1 + + ' Use concatenation class in case we need to deal with line wrapping + With New clsConcat + + ' Check for size overflow on heading. (Wrap on multiple lines) + strRowHeading = strHeading + If Len(strRowHeading) > intMax Then + lngPos = 1 + Do While lngPos + intMax <= Len(strHeading) + ' Add segment and linebreak + .Add Mid$(strHeading, lngPos, intMax), " ", vbCrLf + lngPos = lngPos + intMax + Loop + ' Get last heading line to use with results + strRowHeading = Mid$(strHeading, lngPos) + End If + + ' Display heading and amounts + .Add PadRight(strRowHeading, lngCol(0)) + .Add PadRight(strResult1, lngCol(1)) + .Add strResult2 + ListResult = .GetStr + End With + End Function @@ -538,7 +572,7 @@ Private Function SortItemsByTime(dItems As Dictionary) As Dictionary ' Build our list of records For Each varKey In dItems.Keys ' Create a record like this: "00062840.170000|Export Form Objects ..." - strRecord = Format(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 100) + strRecord = Format(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 255) ' Add to array. varItems(lngCnt) = strRecord ' Increment counter for array From eae289b42e1d0d8aa0bc12d48dbdbcc5dda7f1ef Mon Sep 17 00:00:00 2001 From: Hecon5 <54177882+hecon5@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:41:03 -0400 Subject: [PATCH 08/73] Update based on feedback from @joyfullservice. --- .../modules/clsPerformance.cls | 253 ++++-------------- 1 file changed, 57 insertions(+), 196 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index 7ef5bc1f..17fd8bdf 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -96,7 +96,13 @@ Public Property Get CallStack() As String End Property - +'--------------------------------------------------------------------------------------- +' Procedure : CurrentCategoryName +' Author : hecon5 +' Date : 10/3/2023 +' Purpose : Return the current category name. +'--------------------------------------------------------------------------------------- +' Public Property Get CurrentCategoryName() As String CurrentCategoryName = this.CategoryName End Property @@ -134,11 +140,6 @@ Public Sub CategoryEnd(Optional lngCount As Long = 1) End Sub -Public Property Get CurrentOperationName() As String - CurrentOperationName = this.OperationName -End Property - - '--------------------------------------------------------------------------------------- ' Procedure : OperationStart ' Author : Adam Waller @@ -218,6 +219,18 @@ Public Sub OperationEnd(Optional lngCount As Long = 1) End Sub +'--------------------------------------------------------------------------------------- +' Procedure : CurrentOperationName +' Author : hecon5 +' Date : 10/3/2023 +' Purpose : Return the current operation's name. +'--------------------------------------------------------------------------------------- +' +Public Property Get CurrentOperationName() As String + CurrentOperationName = this.OperationName +End Property + + '--------------------------------------------------------------------------------------- ' Procedure : DigitsAfterDecimal ' Author : Eugen Albiker @@ -273,7 +286,9 @@ End Function '--------------------------------------------------------------------------------------- ' Private Sub StartTimer(dItems As Dictionary, strName As String) + Dim cItem As clsPerformanceItem + If Not dItems.Exists(strName) Then Set cItem = New clsPerformanceItem dItems.Add strName, cItem @@ -455,9 +470,6 @@ Public Function GetReports() As String curTotal = curTotal + this.Operations(varKey).Total Next varKey .Add strSpacer - .Add ListResult("TOTALS:", CStr(dblCount), _ - Format(curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) - .Add strSpacer If Not this.Overall Is Nothing Then .Add ListResult("Other Operations", vbNullString, _ Format(this.Overall.Total - curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) @@ -493,202 +505,51 @@ End Function ' Author : Adam Waller ' Date : 11/3/2020 ' Purpose : List the result of a test in a fixed width format. The result strings -' : are positioned at the number of characters specified. +' : are positioned at the number of characters specified. If the heading size +' : exceeds the width of the column, the text will be wrapped. ' : I.e: ' : MyFancyTest 23 2.45 +' : My very long nam +' : e that I probabl +' : y should condens +' : e 12 3.23 '--------------------------------------------------------------------------------------- ' -Private Function ListResult(ByRef strHeading As String _ - , ByRef strResult1 As String _ - , ByRef strResult2 As String _ - , ByRef lngCol() As Long) As String - ListResult = ListResultIndent(strHeading, strResult1, strResult2, lngCol) -End Function - - -Private Function ListResultIndent(ByRef strHeading As String _ - , ByRef strResult1 As String _ - , ByRef strResult2 As String _ - , ByRef lngCol() As Long _ - , Optional ByVal ColumnIndent As Long = 2) As String - - Dim Col1StrArr() As String - Dim Col2StrArr() As String - Dim Col3StrArr() As String +Private Function ListResult(strHeading As String _ + , strResult1 As String _ + , strResult2 As String _ + , lngCol() As Long) As String - Dim Col1Rows As Long - Dim Col2Rows As Long - Dim Col3Rows As Long + Dim strRowHeading As String + Dim lngPos As Long + Dim intMax As Integer - Dim RowTotal As Long - Dim RowPosition As Long + ' Wrap at one character less than the column width + intMax = lngCol(0) - 1 - Dim strOutput As clsConcat - - On Error Resume Next - - Col1StrArr = FitStringToColumn(strHeading, lngCol(0) - 1, ColumnIndent) - Col2StrArr = FitStringToColumn(strResult1, lngCol(1) - 1, ColumnIndent) - Col3StrArr = FitStringToColumn(strResult2, lngCol(2) - 1, ColumnIndent) - - Col1Rows = UBound(Col1StrArr) - Col2Rows = UBound(Col2StrArr) - Col3Rows = UBound(Col3StrArr) - - RowTotal = MaxValue(Col1Rows, Col2Rows, Col3Rows) - - Set strOutput = New clsConcat - - For RowPosition = 0 To RowTotal + ' Use concatenation class in case we need to deal with line wrapping + With New clsConcat - If Col1Rows >= RowPosition Then - strOutput.Add PadRight(Col1StrArr(RowPosition), lngCol(0)) - Else - strOutput.Add Space$(lngCol(0)) - End If - If Col2Rows >= RowPosition Then - strOutput.Add PadRight(Col2StrArr(RowPosition), lngCol(1)) - Else - strOutput.Add Space$(lngCol(1)) - End If - If Col3Rows >= RowPosition Then - strOutput.Add PadRight(Col3StrArr(RowPosition), lngCol(2)) - Else - strOutput.Add Space$(lngCol(2)) + ' Check for size overflow on heading. (Wrap on multiple lines) + strRowHeading = strHeading + If Len(strRowHeading) > intMax Then + lngPos = 1 + Do While lngPos + intMax <= Len(strHeading) + ' Add segment and linebreak + .Add Mid$(strHeading, lngPos, intMax), " ", vbCrLf + lngPos = lngPos + intMax + Loop + ' Get last heading line to use with results + strRowHeading = Mid$(strHeading, lngPos) End If - ' Don't add a new line for the last line; it's handled outside this tool - If RowTotal > RowPosition Then strOutput.Add vbNewLine - - Next RowPosition - - ListResultIndent = strOutput.GetStr -End Function - - -Private Function MaxValue(ParamArray ValueIn() As Variant) As Variant - Dim Output As Variant - Dim ArrayPosition As Long - - ' Load the first value in to compare to. - Output = ValueIn(LBound(ValueIn)) - - For ArrayPosition = LBound(ValueIn) + 1 To UBound(ValueIn) - If Output < ValueIn(ArrayPosition) Then Output = ValueIn(ArrayPosition) - Next ArrayPosition - - MaxValue = Output -End Function - - -Private Function RoundUp(ByVal Value As Double) As Long - Dim lngVal As Long - Dim deltaValue As Double - - lngVal = CLng(Value) - deltaValue = lngVal - Value - - If deltaValue < 0 Then - RoundUp = lngVal + 1 - Else - RoundUp = lngVal - End If -End Function -Private Function RoundDown(ByVal Value As Double) As Long - Dim lngVal As Long - Dim deltaValue As Double - - lngVal = CLng(Value) - deltaValue = lngVal - Value - - If deltaValue <= 0 Then - RoundDown = lngVal - Else - RoundDown = lngVal - 1 - End If -End Function - - -'--------------------------------------------------------------------------------------- -' Procedure : FitStringToColumn -' Author : hecon5 -' Date : May 18, 2022 -' Purpose : Takes in a long string and returns an array of strings ColumnWidth wide. -'--------------------------------------------------------------------------------------- -' -Private Function FitStringToColumn(ByRef LongString As String _ - , Optional ByRef ColumnWidth As Long = 200 _ - , Optional ByRef ColumnIndent As Long = 0) As String() - - Dim RowTotal As Long - Dim StrLen As Long - Dim StrIndentedLen As Long - Dim StrTextWidth As Long - Dim StrPosition As Long - Dim ArrPosition As Long - Dim StrArr() As String - Dim ColumnWidthInternal As Long - - On Error Resume Next - If Len(LongString) = 0 Then Exit Function - ColumnWidthInternal = ColumnWidth - If ColumnWidthInternal <= 0 Then ColumnWidthInternal = 1 - - StrTextWidth = ColumnWidthInternal - ColumnIndent - - StrLen = Len(LongString) - RowTotal = RoundUp((StrLen - ColumnWidthInternal) / StrTextWidth) + 1 - If RowTotal < 1 Then RowTotal = 1 - StrPosition = 1 - - ReDim StrArr(0 To (RowTotal - 1)) - - ' The first row is longer. - StrArr(ArrPosition) = Mid$(LongString, StrPosition, ColumnWidthInternal) - If RowTotal <= 1 Then GoTo Exit_Here ' Don't do the rest if there's only one row... - - StrPosition = StrPosition + ColumnWidthInternal - - For ArrPosition = 1 To (RowTotal - 1) - StrArr(ArrPosition) = Space$(ColumnIndent) & Mid$(LongString, StrPosition, StrTextWidth) - StrPosition = StrPosition + StrTextWidth - Next ArrPosition - -Exit_Here: - FitStringToColumn = StrArr -End Function + ' Display heading and amounts + .Add PadRight(strRowHeading, lngCol(0)) + .Add PadRight(strResult1, lngCol(1)) + .Add strResult2 + ListResult = .GetStr + End With -Private Function FitStringToWidth(ByRef LongString As String _ - , Optional ByRef MaxWidth As Long = 200 _ - , Optional ByRef DesiredWidth As Long = 75) As String - ' Fits a string to a message box if it's wider than MaxWidth - Dim OutputConcat As clsConcat - Dim StrPosition As Long - Dim StrLen As Long ' Length of total string - Dim NewLineCount As Long ' Number of newlines - Dim ArrPosition As Long - Dim StrArrLen As Long ' Length of substring - Dim StringArr() As String - - StrLen = Len(LongString) - If StrLen > MaxWidth Then - StringArr = Split(LongString, vbNewLine, , vbTextCompare) - NewLineCount = UBound(StringArr) - LBound(StringArr) - Set OutputConcat = New clsConcat - For ArrPosition = 0 To NewLineCount - StrPosition = 1 - StrArrLen = Len(StringArr(ArrPosition)) - If ArrPosition > 0 Then OutputConcat.Add vbNewLine - Do While StrPosition < StrArrLen - If StrPosition > 1 Then OutputConcat.Add vbNewLine - OutputConcat.Add Mid$(StringArr(ArrPosition), StrPosition, DesiredWidth) - StrPosition = StrPosition + DesiredWidth - Loop - Next ArrPosition - FitStringToWidth = OutputConcat.GetStr - Else - FitStringToWidth = LongString - End If End Function @@ -700,7 +561,7 @@ End Function '--------------------------------------------------------------------------------------- ' Private Function PadRight(strText As String _ - , lngLen As Long _ + , lngLen As Long _ , Optional lngMinTrailingSpaces As Long = 1) As String Dim strResult As String @@ -746,7 +607,7 @@ Private Function SortItemsByTime(dItems As Dictionary) As Dictionary ' Build our list of records For Each varKey In dItems.Keys ' Create a record like this: "00062840.170000|Export Form Objects ..." - strRecord = Format(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 100) + strRecord = Format(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 255) ' Add to array. varItems(lngCnt) = strRecord ' Increment counter for array From 8c6d7f949e1a23e51b6d22073bd917659e4167d4 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 19 Oct 2023 15:18:28 -0500 Subject: [PATCH 09/73] Resolve conflict with upstream file Putting the comma after the argument seems to be the preferred industry-standard approach, based on ChatGPT and Bard. --- .../modules/clsPerformance.cls | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index 17fd8bdf..aae14f60 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -96,6 +96,7 @@ Public Property Get CallStack() As String End Property + '--------------------------------------------------------------------------------------- ' Procedure : CurrentCategoryName ' Author : hecon5 @@ -515,10 +516,10 @@ End Function ' : e 12 3.23 '--------------------------------------------------------------------------------------- ' -Private Function ListResult(strHeading As String _ - , strResult1 As String _ - , strResult2 As String _ - , lngCol() As Long) As String +Private Function ListResult(strHeading As String, _ + strResult1 As String, _ + strResult2 As String, _ + lngCol() As Long) As String Dim strRowHeading As String Dim lngPos As Long @@ -560,9 +561,9 @@ End Function ' Purpose : Pads a string '--------------------------------------------------------------------------------------- ' -Private Function PadRight(strText As String _ - , lngLen As Long _ - , Optional lngMinTrailingSpaces As Long = 1) As String +Private Function PadRight(strText As String, _ + lngLen As Long, _ + Optional lngMinTrailingSpaces As Long = 1) As String Dim strResult As String Dim strTrimmed As String From 6cdac0c2824675ede287b22caf1561907dc0ebea Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 19 Oct 2023 16:20:29 -0500 Subject: [PATCH 10/73] Add option to pass path to build API You can now specify the source files path when you initiate a build through the API. This allows automated builds to be run even if a copy of the database does not yet exist. (Such as after checking out a project in an automated CI workflow.) #430 --- Version Control.accda.src/dbs-properties.json | 2 +- .../forms/frmVCSMain.bas | 72 +++++++++++++------ .../modules/clsVersionControl.cls | 4 +- Version Control.accda.src/modules/modAPI.bas | 15 ++-- 4 files changed, 65 insertions(+), 28 deletions(-) diff --git a/Version Control.accda.src/dbs-properties.json b/Version Control.accda.src/dbs-properties.json index 0f394126..a4853bdb 100644 --- a/Version Control.accda.src/dbs-properties.json +++ b/Version Control.accda.src/dbs-properties.json @@ -41,7 +41,7 @@ "Type": 10 }, "AppVersion": { - "Value": "4.0.24", + "Value": "4.0.25", "Type": 10 }, "Auto Compact": { diff --git a/Version Control.accda.src/forms/frmVCSMain.bas b/Version Control.accda.src/forms/frmVCSMain.bas index 1f90d7b8..08f52751 100644 --- a/Version Control.accda.src/forms/frmVCSMain.bas +++ b/Version Control.accda.src/forms/frmVCSMain.bas @@ -16,10 +16,10 @@ Begin Form Width =9360 DatasheetFontHeight =11 ItemSuffix =33 - Left =3225 - Top =2430 - Right =18945 - Bottom =14175 + Left =20761 + Top =2250 + Right =-29055 + Bottom =13995 OnUnload ="[Event Procedure]" RecSrcDt = Begin 0x79e78b777268e540 @@ -1749,6 +1749,9 @@ Public objSingleObject As AccessObject ' (The Log object has already been reset at this point, so we can't use Log.LogFilePath.) Public strLastLogFilePath As String +' Use this property to set the path to the source files (such as a build triggered from the API) +Public strSourcePath As String + '--------------------------------------------------------------------------------------- ' Procedure : cmdBuild_Click @@ -1760,11 +1763,6 @@ Public strLastLogFilePath As String Public Sub cmdBuild_Click() Dim strFolder As String - Dim strMsg(0 To 2) As String - Dim intChoice As VbMsgBoxResult - - DoCmd.Hourglass True - DoEvents ' Make sure we use the add-in to build the add-in. If CodeProject.FullName = CurrentProject.FullName Then @@ -1774,6 +1772,43 @@ Public Sub cmdBuild_Click() Exit Sub End If + ' Get source files folder + If Len(Me.strSourcePath) Then + ' Use specified build folder + strFolder = Me.strSourcePath + Else + ' Attempt to get the source folder from the current database, or from + ' a folder picker dialog. + strFolder = GetSourceFolder + ' Exit out of build if the user cancelled any of the confirmations. + If strFolder = vbNullString Then Exit Sub + End If + + ' Build project using the selected source folder + ' (Use a timer so we can release the reference to this form before beginning the + ' build process, just in case we need to import a form with the same name.) + If strFolder <> vbNullString Then SetTimer "Build", strFolder, chkFullBuild + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : GetSourceFolder +' Author : Adam Waller +' Date : 10/19/2023 +' Purpose : Return the source files folder from either the currently open database +' : or from a folder picker dialog. (Returns an empty string if the user +' : cancels the selection.) +'--------------------------------------------------------------------------------------- +' +Private Function GetSourceFolder() As String + + Dim strMsg(0 To 2) As String + Dim intChoice As VbMsgBoxResult + + DoCmd.Hourglass True + DoEvents + ' Close the current database if it is currently open. If DatabaseFileOpen Then If FolderHasVcsOptionsFile(Options.GetExportFolder) Then @@ -1795,18 +1830,18 @@ Public Sub cmdBuild_Click() End If If intChoice = vbYes Then ' Rebuild the open project - strFolder = Options.GetExportFolder + GetSourceFolder = Options.GetExportFolder ElseIf intChoice = vbCancel Then ' Canceled out of build option. DoCmd.Hourglass False - Exit Sub + Exit Function End If End If End If ' If we aren't doing the current database, then prompt user to find a folder ' with source files to use for the build. - If strFolder = vbNullString Then + If GetSourceFolder = vbNullString Then ' Show a folder picker to select the file with source code. DoCmd.Hourglass False @@ -1820,26 +1855,21 @@ Public Sub cmdBuild_Click() ' Selected a folder If FolderHasVcsOptionsFile(.SelectedItems(1)) Then ' Has source files - strFolder = .SelectedItems(1) & PathSep + GetSourceFolder = .SelectedItems(1) & PathSep DoCmd.Hourglass True Else MsgBox2 "Source files not found", "Required source files were not found in this folder.", _ "You selected: " & .SelectedItems(1), vbExclamation - Exit Sub + Exit Function End If Else ' Canceled dialog - Exit Sub + Exit Function End If End With End If - ' Build project using the selected source folder - ' (Use a timer so we can release the reference to this form before beginning the - ' build process, just in case we need to import a form with the same name.) - If strFolder <> vbNullString Then SetTimer "Build", strFolder, chkFullBuild - -End Sub +End Function '--------------------------------------------------------------------------------------- diff --git a/Version Control.accda.src/modules/clsVersionControl.cls b/Version Control.accda.src/modules/clsVersionControl.cls index e3c32285..35c22c47 100644 --- a/Version Control.accda.src/modules/clsVersionControl.cls +++ b/Version Control.accda.src/modules/clsVersionControl.cls @@ -118,11 +118,12 @@ End Sub ' Purpose : Initiate a full build from source '--------------------------------------------------------------------------------------- ' -Public Sub Build() +Public Sub Build(Optional strSourceFolder As String) DoCmd.OpenForm "frmVCSMain", , , , , acHidden With Form_frmVCSMain ' Make sure we are doing a full build. If Not .chkFullBuild Then .chkFullBuild = True + .strSourcePath = strSourceFolder .cmdBuild_Click End With End Sub @@ -426,6 +427,7 @@ Public Sub ActivateHook() End If End Sub + '--------------------------------------------------------------------------------------- ' Procedure : Class_Initialize ' Author : Adam Waller diff --git a/Version Control.accda.src/modules/modAPI.bas b/Version Control.accda.src/modules/modAPI.bas index 99daca10..e83c3c34 100644 --- a/Version Control.accda.src/modules/modAPI.bas +++ b/Version Control.accda.src/modules/modAPI.bas @@ -44,7 +44,7 @@ End Enum ' : Access add-in.) '--------------------------------------------------------------------------------------- ' -Public Function HandleRibbonCommand(strCommand As String) As Boolean +Public Function HandleRibbonCommand(strCommand As String, Optional strArgument As String) As Boolean ' The function is called by Application.Run which can be re-entrant but we really ' don't want it to be since that'd cause errors. To avoid this, we will ignore any ' commands while the current command is running. @@ -62,17 +62,21 @@ Public Function HandleRibbonCommand(strCommand As String) As Boolean ' Make sure we are not attempting to run this from the current database when making ' changes to the add-in itself. (It will re-run the command through the add-in.) If RunningOnLocal() Then - RunInAddIn "HandleRibbonCommand", True, strCommand + RunInAddIn "HandleRibbonCommand", True, strCommand, strArgument GoTo CleanUp End If ' If a function is not found, this will throw an error. It is up to the ribbon ' designer to ensure that the control IDs match public procedures in the VCS - ' (clsVersionControl) class module. Additional parameters are not supported. + ' (clsVersionControl) class module. ' For example, to run VCS.Export, the ribbon button ID should be named "btnExport" ' Trim off control ID prefix when calling command - CallByName VCS, Mid(strCommand, 4), VbMethod + If Len(strArgument) Then + CallByName VCS, Mid(strCommand, 4), VbMethod, strArgument + Else + CallByName VCS, Mid(strCommand, 4), VbMethod + End If CleanUp: IsRunning = False @@ -310,7 +314,8 @@ Public Function ExampleBuildFromSource() ' Set the application interaction level to silent to skip confirmation dialogs. Application.Run "MSAccessVCS.SetInteractionMode", 1 ' Launch the build process (as if we clicked the button on the ribbon) - Application.Run "MSAccessVCS.HandleRibbonCommand", "btnBuild" + ' Optionally specify a specific folder of source files to build from. + Application.Run "MSAccessVCS.HandleRibbonCommand", "btnBuild" ', "c:\path\to\source\folder" End If End Function From 734d83524038d6b71c3e8a263c58fd87c4180ee1 Mon Sep 17 00:00:00 2001 From: bclothier Date: Thu, 19 Oct 2023 18:12:02 -0500 Subject: [PATCH 11/73] The logic for checking of existence of git files wasn't always working as expected due to searching the current directory rather than using the export folder. --- Version Control.accda.src/modules/modVCSUtility.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 40c424b9..b15f3b4d 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -794,7 +794,7 @@ Public Sub CheckGitFiles() Dim strFile As String Dim blnAdded As Boolean - strPath = CurrentProject.Path & PathSep + strPath = Options.GetExportFolder If FSO.FolderExists(strPath & ".git") Then ' gitignore file From 4703b7bac13a6799e7f4221cfdbbdfc1e6303a67 Mon Sep 17 00:00:00 2001 From: bclothier Date: Thu, 19 Oct 2023 18:17:33 -0500 Subject: [PATCH 12/73] Add a check when loading XML and verify it was successfully parsed. This avoid generating a bad export where the data are not actually exported due to invalid XML being generated by Application.ExportXML. Unfortunately, if a table contains any characters that aren't valid for XML document, it won't try to escape them and include them as literals. Even if they were escaped, they might not be accepted anyway. XML specifications forbids having any characters in 0x01-0x31 range so if a table data contains such characters, this can cause the XML export to fail. In this case, tab delimited will have to be used instead. However, the previous version was simply silently exporting as if everything is hunky-dory when it's not. Hence, the error. --- Version Control.accda.src/modules/modSanitize.bas | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modSanitize.bas b/Version Control.accda.src/modules/modSanitize.bas index 2e284b39..c84f2a70 100644 --- a/Version Control.accda.src/modules/modSanitize.bas +++ b/Version Control.accda.src/modules/modSanitize.bas @@ -618,7 +618,11 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri Set objXml = New MSXML2.DOMDocument60 End If - objXml.LoadXML strFile + If objXml.LoadXML(strFile) = False Then + Log.Error eelError, _ + "Unable to parse the XML for file '" & strPath & "'. This may be due to containing malformed XML. Check the source XML document for validity. In some cases, this may be due to table data containing characters not allowed in XML documents.", ModuleName & ".SanitizeXML" + Exit Function + End If ' Determine if it's a table data with schema For Each objNode In objXml.SelectNodes("/root/dataroot") From 0d9ac0a74c1e0815a90931cec21c7b469e3b8965 Mon Sep 17 00:00:00 2001 From: bclothier Date: Thu, 19 Oct 2023 18:19:17 -0500 Subject: [PATCH 13/73] The export log was littered with bunch of warnings about unclosed blocks. This seems to be due to not closing it when evaluating the UseTheme. Even if we skipped it, we still need to remove it from m_colBlocks to balance everything out. --- Version Control.accda.src/modules/modSanitize.bas | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Version Control.accda.src/modules/modSanitize.bas b/Version Control.accda.src/modules/modSanitize.bas index c84f2a70..5eb760be 100644 --- a/Version Control.accda.src/modules/modSanitize.bas +++ b/Version Control.accda.src/modules/modSanitize.bas @@ -394,8 +394,12 @@ Private Sub CloseBlock() ' Skip if we are not using themes for this control (UseTheme=0) ' (Applies to "CommandButton", "Tab", "ToggleButton") - If dBlock.Exists("UseTheme") Then Exit Sub - + If dBlock.Exists("UseTheme") Then + ' Remove this block + m_colBlocks.Remove m_colBlocks.Count + Exit Sub + End If + ' Build array of base properties varBase = Array("Back", "AlternateBack", "Border", _ "Fore", "Gridline", "HoverFore", _ From 736c33c7066d783fbf7fe95778567800f3dfb611 Mon Sep 17 00:00:00 2001 From: bclothier Date: Fri, 20 Oct 2023 11:45:31 -0500 Subject: [PATCH 14/73] Fix a subscript out of range error where the tokens advance beyond the end of the string but the function GetNextTokenID returns 0, which then fails within FormatSQL function since there is no member at index 0. It's not clear why this only fails every second time a query is exported but it is the case where if it fails, exporting it next time will not yield the error. Do it 3rd time, then it fails. --- Version Control.accda.src/modules/clsSqlFormatter.cls | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/clsSqlFormatter.cls b/Version Control.accda.src/modules/clsSqlFormatter.cls index 423794b8..bdbe7866 100644 --- a/Version Control.accda.src/modules/clsSqlFormatter.cls +++ b/Version Control.accda.src/modules/clsSqlFormatter.cls @@ -82,7 +82,7 @@ Private m_varWordCache(1 To 2) As Variant '--------------------------------------------------------------------------------------- -' Procedure : Format +' Procedure : FormatSQL ' Author : Adam Waller ' Date : 4/1/2020 ' Purpose : This is the main function used outside the class for SQL formatting. @@ -1214,6 +1214,12 @@ End Function ' Private Function GetNextTokenID(lngCurrentToken As Long, Optional intExceptType As eTokenTypes) As Long Dim intToken As Integer + + If lngCurrentToken + 1 > m_colTokens.Count Then + GetNextTokenID = lngCurrentToken + 1 + Exit Function + End If + For intToken = lngCurrentToken + 1 To m_colTokens.Count If m_colTokens(intToken)(0) <> intExceptType Then GetNextTokenID = intToken From 5b5e03755014cc7b45160130413e680cbf8303c4 Mon Sep 17 00:00:00 2001 From: bclothier Date: Fri, 20 Oct 2023 11:46:05 -0500 Subject: [PATCH 15/73] Add more types of queries that should not be formatted by SQL formatter because they are a variant of pass-through queries. --- .../modules/clsDbQuery.cls | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbQuery.cls b/Version Control.accda.src/modules/clsDbQuery.cls index c09e1aa4..cd7d08df 100644 --- a/Version Control.accda.src/modules/clsDbQuery.cls +++ b/Version Control.accda.src/modules/clsDbQuery.cls @@ -62,12 +62,17 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) On Error GoTo 0 If strSql <> vbNullString Then ' Pass-through queries should not be formatted, since they support formatting and comments. - If Options.SaveQuerySQL And dbs.QueryDefs(m_Query.Name).Type <> dbQSQLPassThrough Then - With New clsSqlFormatter - Perf.OperationStart "Format SQL" - WriteFile .FormatSQL(strSql), strFile - Perf.OperationEnd - End With + If Options.SaveQuerySQL Then + Select Case dbs.QueryDefs(m_Query.Name).Type + Case dbQSQLPassThrough, dbQSPTBulk, dbQSetOperation + 'Do not format + Case Else + With New clsSqlFormatter + Perf.OperationStart "Format SQL" + WriteFile .FormatSQL(strSql), strFile + Perf.OperationEnd + End With + End Select Else WriteFile strSql, strFile End If From 490d82eaea90d91fedb394460be92187ed88bcb6 Mon Sep 17 00:00:00 2001 From: bclothier Date: Fri, 20 Oct 2023 11:47:41 -0500 Subject: [PATCH 16/73] The AutoClose may run after the form has closed (e.g. if the user is quick to close it) which may result in an error about object members not available. Since the form is closed, there's no point in setting the timer interval. To avoid the error when debugging, we add a IsLoaded check and skip it if it's not loaded. --- Version Control.accda.src/forms/frmVCSMain.bas | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/forms/frmVCSMain.bas b/Version Control.accda.src/forms/frmVCSMain.bas index 08f52751..de354f4c 100644 --- a/Version Control.accda.src/forms/frmVCSMain.bas +++ b/Version Control.accda.src/forms/frmVCSMain.bas @@ -2138,7 +2138,13 @@ End Sub '--------------------------------------------------------------------------------------- ' Public Sub AutoClose() - Me.TimerInterval = 2000 + 'The procedure may be called when the form has been closed. + 'In this case, a VBA error may occur, so we check if the + 'form is loaded before setting the property. We do not use + 'the Me.Name because that would be also an error. + If IsLoaded(acForm, "frmVCSMain", False) Then + Me.TimerInterval = 2000 + End If End Sub From a666c9dbe161d00580c517874955222b1f86e142 Mon Sep 17 00:00:00 2001 From: hecon5 <54177882+hecon5@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:33:38 -0400 Subject: [PATCH 17/73] Fix issue with LogUnhandledErrors and simplify use. (#449) --- .../modules/modErrorHandling.bas | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/Version Control.accda.src/modules/modErrorHandling.bas b/Version Control.accda.src/modules/modErrorHandling.bas index 78bb4272..0cbf3713 100644 --- a/Version Control.accda.src/modules/modErrorHandling.bas +++ b/Version Control.accda.src/modules/modErrorHandling.bas @@ -9,6 +9,7 @@ Option Compare Database Option Private Module Option Explicit +Private Const ModuleName As String = "modErrorHandling" Private Type udtThis blnInError As Boolean ' Monitor error state @@ -41,20 +42,22 @@ End Function ' Purpose : Log any unhandled error condition, also breaking code execution if that ' : option is currently set. (Run this before any ON ERROR directive which ' : will siently reset any current VBA error condition.) +' +' Example : See Sub `CatchTest` for example use. +' '--------------------------------------------------------------------------------------- ' -Public Sub LogUnhandledErrors() - - Dim blnBreak As Boolean +Public Sub LogUnhandledErrors(Optional ByRef CallingFunction As String = vbNullString) ' Check for any unhandled errors If (Err.Number <> 0) And Not this.blnInError Then - ' Don't reference the property this till we have loaded the options. - If OptionsLoaded Then blnBreak = Options.BreakOnError + this.blnInError = True ' Set flag so we don't create a loop while logging the error + ' With the above flag, options will load in background and we don't depend on + ' flags outside of this routine. ' Check current BreakOnError mode - If blnBreak Then + If Options.BreakOnError Then ' Stop the code here so we can investigate the source of the error. Debug.Print "Error " & Err.Number & ": " & Err.Description Stop @@ -81,20 +84,48 @@ Public Sub LogUnhandledErrors() '=========================================================================== Else ' Log otherwise unhandled error - If Not Log(False) Is Nothing Then - ' Set flag so we don't create a loop while logging the error - this.blnInError = True - ' We don't know the procedure that it originated from, but we should at least - ' log that the error occurred. A review of the log file may help identify the source. - Log.Error eelError, "Unhandled error, likely before `On Error` directive", "Unknown" - this.blnInError = False - End If + ' We don't know the procedure that it originated from, but we should at least + ' log that the error occurred. A review of the log file may help identify the source. + Log.Error eelError, "Unhandled error, likely before `On Error` directive", CallingFunction & ".Unknown.LogUnhandledErrors" End If + this.blnInError = False End If End Sub +'--------------------------------------------------------------------------------------- +' Procedure : CatchTest +' Author : hecon5 +' Date : 10/20/2023 +' Purpose : Validates that Catch operates correctly and that LogUnhandledErrors +' : doesn't create an infinite loop whether or not log exists. +' : +' : To use, run normally, after loading options / other core dependancies. +' : Then Stop the code (in VBA IDE) and then run again. Stopping code execution +' : +'--------------------------------------------------------------------------------------- +' +Public Sub CatchTest() + + ' Specifiying a Const FunctionName allows copy/paste code and having the wrong FunctionName + ' names if (when) they change. + Const FunctionName As String = ModuleName & ".CatchTest" + + On Error Resume Next ' Clear out any errors that may happen, and continue on when errors happen. + Err.Raise 24601, "Pre Log Test" + + ' This is the "standard" way of catching errors without losing them. + LogUnhandledErrors FunctionName + On Error Resume Next + + ' "Pretend" code tossing an error. + Err.Raise 24602, "Post Log Test" + ' Checking for any issues post code execution. + CatchAny eelError, "Catch Test Validation", FunctionName + +End Sub + '--------------------------------------------------------------------------------------- ' Procedure : Catch ' Author : Adam Waller From 40cffbc61e31b1cf8ec2e34dffb0aa143e8ddf83 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 24 Oct 2023 13:49:52 -0500 Subject: [PATCH 18/73] Add option to SplitLayoutFromVBA This option (on by default) will save the VBA code from forms and reports as a related .cls file. (Still under development.) #378 Also removed the "Strip out Publish Option" from the options form. I have never heard of a case where this needs to be changed, and it frees up space for the new option we are adding without cluttering the form. --- .../forms/frmVCSOptions.bas | 126 +++++++++--------- .../modules/clsOptions.cls | 3 + 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSOptions.bas b/Version Control.accda.src/forms/frmVCSOptions.bas index d9bf3360..df1e3ac2 100644 --- a/Version Control.accda.src/forms/frmVCSOptions.bas +++ b/Version Control.accda.src/forms/frmVCSOptions.bas @@ -15,11 +15,11 @@ Begin Form GridY =24 Width =10080 DatasheetFontHeight =11 - ItemSuffix =250 - Left =-25575 - Top =1500 - Right =-5310 - Bottom =14085 + ItemSuffix =252 + Left =3225 + Top =2430 + Right =13890 + Bottom =14175 RecSrcDt = Begin 0x79e78b777268e540 End @@ -794,8 +794,8 @@ Begin Form OverlapFlags =247 Left =1020 Top =3360 - TabIndex =2 - Name ="chkStripPublishOption" + TabIndex =4 + Name ="chkExtractThemeFiles" LayoutCachedLeft =1020 LayoutCachedTop =3360 @@ -806,14 +806,14 @@ Begin Form OverlapFlags =247 Left =1320 Top =3300 - Width =2640 + Width =2340 Height =315 ForeColor =5324600 - Name ="Label34" - Caption ="Strip out Publish Option" + Name ="Label112" + Caption ="Extract Theme Files" LayoutCachedLeft =1320 LayoutCachedTop =3300 - LayoutCachedWidth =3960 + LayoutCachedWidth =3660 LayoutCachedHeight =3615 ForeThemeColorIndex =-1 ForeTint =100.0 @@ -830,7 +830,7 @@ Begin Form Top =3720 Width =1980 Height =315 - TabIndex =3 + TabIndex =2 Name ="cboSanitizeLevel" RowSourceType ="Value List" ColumnWidths ="0" @@ -868,7 +868,7 @@ Begin Form Top =4140 Width =1980 Height =315 - TabIndex =4 + TabIndex =3 Name ="cboSanitizeColors" RowSourceType ="Value List" ColumnWidths ="0" @@ -990,9 +990,39 @@ Begin Form Begin CheckBox OverlapFlags =247 Left =5340 - Top =4620 + Top =4200 TabIndex =8 - Name ="chkExtractThemeFiles" + Name ="chkFormatSQL" + + LayoutCachedLeft =5340 + LayoutCachedTop =4200 + LayoutCachedWidth =5600 + LayoutCachedHeight =4440 + Begin + Begin Label + OverlapFlags =247 + Left =5640 + Top =4140 + Width =2340 + Height =315 + ForeColor =5324600 + Name ="Label249" + Caption ="Format SQL" + LayoutCachedLeft =5640 + LayoutCachedTop =4140 + LayoutCachedWidth =7980 + LayoutCachedHeight =4455 + ForeThemeColorIndex =-1 + ForeTint =100.0 + End + End + End + Begin CheckBox + OverlapFlags =247 + Left =5340 + Top =4620 + TabIndex =9 + Name ="chkSplitLayoutFromVBA" LayoutCachedLeft =5340 LayoutCachedTop =4620 @@ -1006,8 +1036,8 @@ Begin Form Width =2340 Height =315 ForeColor =5324600 - Name ="Label112" - Caption ="Extract Theme Files" + Name ="Label251" + Caption ="Split Layout from VBA" LayoutCachedLeft =5640 LayoutCachedTop =4560 LayoutCachedWidth =7980 @@ -1024,7 +1054,7 @@ Begin Form Top =5100 Width =2700 Height =315 - TabIndex =9 + TabIndex =10 Name ="txtRunBeforeExport" LayoutCachedLeft =3540 @@ -1057,7 +1087,7 @@ Begin Form Top =5520 Width =2700 Height =315 - TabIndex =10 + TabIndex =11 Name ="txtRunAfterExport" LayoutCachedLeft =3540 @@ -1089,7 +1119,7 @@ Begin Form Left =7140 Top =5640 Width =2160 - TabIndex =11 + TabIndex =12 Name ="cmdExplainOptions" Caption ="Explain options..." HyperlinkAddress ="https://github.com/joyfullservice/msaccess-vcs-addin/wiki/Documentation#options" @@ -1153,20 +1183,6 @@ Begin Form PressedThemeColorIndex =-1 PressedShade =100.0 End - Begin Label - OverlapFlags =247 - Left =6120 - Top =2400 - Width =2160 - Height =240 - FontSize =10 - Name ="Label46" - Caption ="(Blank for default)" - LayoutCachedLeft =6120 - LayoutCachedTop =2400 - LayoutCachedWidth =8280 - LayoutCachedHeight =2640 - End Begin CommandButton FontUnderline = NotDefault TabStop = NotDefault @@ -1176,7 +1192,7 @@ Begin Form Width =1140 Height =240 FontSize =10 - TabIndex =12 + TabIndex =13 Name ="cmdPrintSettingsOptions" Caption ="Options..." OnClick ="[Event Procedure]" @@ -1212,35 +1228,19 @@ Begin Form PressedForeThemeColorIndex =10 PressedForeTint =100.0 End - Begin CheckBox + Begin Label OverlapFlags =247 - Left =5340 - Top =4200 - TabIndex =13 - Name ="chkFormatSQL" - - LayoutCachedLeft =5340 - LayoutCachedTop =4200 - LayoutCachedWidth =5600 - LayoutCachedHeight =4440 - Begin - Begin Label - OverlapFlags =247 - Left =5640 - Top =4140 - Width =2340 - Height =315 - ForeColor =5324600 - Name ="Label249" - Caption ="Format SQL" - LayoutCachedLeft =5640 - LayoutCachedTop =4140 - LayoutCachedWidth =7980 - LayoutCachedHeight =4455 - ForeThemeColorIndex =-1 - ForeTint =100.0 - End - End + Left =6120 + Top =2400 + Width =2160 + Height =240 + FontSize =10 + Name ="Label46" + Caption ="(Blank for default)" + LayoutCachedLeft =6120 + LayoutCachedTop =2400 + LayoutCachedWidth =8280 + LayoutCachedHeight =2640 End End End diff --git a/Version Control.accda.src/modules/clsOptions.cls b/Version Control.accda.src/modules/clsOptions.cls index adb97d98..4f091b0b 100644 --- a/Version Control.accda.src/modules/clsOptions.cls +++ b/Version Control.accda.src/modules/clsOptions.cls @@ -32,6 +32,7 @@ Public SaveQuerySQL As Boolean Public FormatSQL As Boolean Public ForceImportOriginalQuerySQL As Boolean Public SaveTableSQL As Boolean +Public SplitLayoutFromVBA As Boolean Public StripPublishOption As Boolean Public SanitizeColors As eSanitizeLevel Public SanitizeLevel As eSanitizeLevel @@ -78,6 +79,7 @@ Public Sub LoadDefaults() .FormatSQL = True .ForceImportOriginalQuerySQL = False .SaveTableSQL = True + .SplitLayoutFromVBA = True .StripPublishOption = True .SanitizeLevel = eslStandard .SanitizeColors = eslMinimal @@ -568,6 +570,7 @@ Private Sub Class_Initialize() .Add "FormatSQL" .Add "ForceImportOriginalQuerySQL" .Add "SaveTableSQL" + .Add "SplitLayoutFromVBA" .Add "StripPublishOption" .Add "SanitizeColors" .Add "SanitizeLevel" From 07e364e01dc9cc63450f41de4b322a6a8e6dad4f Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 24 Oct 2023 14:16:53 -0500 Subject: [PATCH 19/73] Refactor code module export to shared function This logic will be shared when exporting code modules from forms and reports. --- .../modules/clsDbModule.cls | 24 ++------------- .../modules/modVCSUtility.bas | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbModule.cls b/Version Control.accda.src/modules/clsDbModule.cls index 147db934..79c13336 100644 --- a/Version Control.accda.src/modules/clsDbModule.cls +++ b/Version Control.accda.src/modules/clsDbModule.cls @@ -42,17 +42,11 @@ Implements IDbComponent ' Private Sub IDbComponent_Export(Optional strAlternatePath As String) - Dim strTempFile As String - Dim strContent As String Dim strExt As String Dim strAlternateFile As String - ' Export to temp file and convert to UTF-8 encoding - strTempFile = GetTempFile - ExportVbComponent strTempFile - strContent = SanitizeVBA(ReadFile(strTempFile, GetSystemEncoding)) - WriteFile strContent, Nz2(strAlternatePath, IDbComponent_SourceFile) - DeleteFile strTempFile + ' Export as sanitized UTF-8 file + ExportCodeModule m_Module.Name, Nz2(strAlternatePath, IDbComponent_SourceFile) ' Remove any file with the same name but alternate extension strExt = IIf(GetExtension = ".bas", ".cls", ".bas") @@ -243,20 +237,6 @@ Private Sub IDbComponent_Merge(strFile As String) End Sub -'--------------------------------------------------------------------------------------- -' Procedure : ExportVbComponent -' Author : Adam Waller -' Date : 5/26/2021 -' Purpose : Export the code module VB component -'--------------------------------------------------------------------------------------- -' -Private Sub ExportVbComponent(strFile As String) - Perf.OperationStart "Export VBE Module" - CurrentVBProject.VBComponents(m_Module.Name).Export strFile - Perf.OperationEnd -End Sub - - '--------------------------------------------------------------------------------------- ' Procedure : IDbComponent_MoveSource ' Author : Adam Waller diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index b15f3b4d..de666e55 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -574,6 +574,36 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ End Sub +'--------------------------------------------------------------------------------------- +' Procedure : ExportVbComponent +' Author : Adam Waller +' Date : 5/26/2021 +' Purpose : Export the code module VB component and convert to UTF-8 +'--------------------------------------------------------------------------------------- +' +Public Sub ExportCodeModule(strName As String, strFile As String) + + Dim strTempFile As String + Dim strContent As String + + Perf.OperationStart "Export VBE Module" + + ' Export to a temp file so we can convert to UTF-8 encoding + strTempFile = GetTempFile + CurrentVBProject.VBComponents(strName).Export strFile + + ' Sanitize the VBA code while reading the temp file + strContent = SanitizeVBA(ReadFile(strTempFile, GetSystemEncoding)) + + ' Write the content as UTF-8 to the final destination + WriteFile strContent, strFile + DeleteFile strTempFile + + Perf.OperationEnd + +End Sub + + '--------------------------------------------------------------------------------------- ' Procedure : RemoveNonBuiltInReferences ' Author : Adam Waller From 0beaee56eae55e54336029cea1051b6f8c3145b7 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 25 Oct 2023 11:53:30 -0500 Subject: [PATCH 20/73] Support "|" in performance log entry names Refactored parsing the key from the performance item so that we are not dependent upon a unique delimiter. The timing value is always a number, so we can be confident that the first pipe character is the delimiter. The text after that can be anything, including pipe characters. #450 --- Version Control.accda.src/modules/clsPerformance.cls | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index aae14f60..bf141388 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -622,8 +622,9 @@ Private Function SortItemsByTime(dItems As Dictionary) As Dictionary ' (We are walking backwards through the array to flip the sort to descending) Set dSorted = New Dictionary For lngCnt = dItems.Count - 1 To 0 Step -1 - ' Parse key from record - varKey = Trim(Split(varItems(lngCnt), "|")(1)) + ' Parse key from record (text after first pipe character) + strRecord = varItems(lngCnt) + varKey = Trim(Mid$(strRecord, InStr(1, strRecord, "|") + 1)) ' Reference performance item class Set cItem = dItems(varKey) ' Add to dictionary of resorted items From 1ca6886959f05bac939c5f97c44812e2850562f7 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 25 Oct 2023 16:08:09 -0500 Subject: [PATCH 21/73] Adjust indenting (minor change) --- Version Control.accda.src/modules/modSanitize.bas | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Version Control.accda.src/modules/modSanitize.bas b/Version Control.accda.src/modules/modSanitize.bas index 5eb760be..c13653bb 100644 --- a/Version Control.accda.src/modules/modSanitize.bas +++ b/Version Control.accda.src/modules/modSanitize.bas @@ -394,12 +394,12 @@ Private Sub CloseBlock() ' Skip if we are not using themes for this control (UseTheme=0) ' (Applies to "CommandButton", "Tab", "ToggleButton") - If dBlock.Exists("UseTheme") Then - ' Remove this block + If dBlock.Exists("UseTheme") Then + ' Remove this block m_colBlocks.Remove m_colBlocks.Count - Exit Sub - End If - + Exit Sub + End If + ' Build array of base properties varBase = Array("Back", "AlternateBack", "Border", _ "Fore", "Gridline", "HoverFore", _ From 8e70ad2323cc2ec5e6397c89f1053701d67da9ac Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 25 Oct 2023 16:20:38 -0500 Subject: [PATCH 22/73] Convert Sanitize module to class In some cases sanitizing a source file actually creates two distinct outputs. A layout file and a code file. Rather than making the sanitize function more complicated with byref outputs and non-obvious side effects, I am taking the approach of a more explicit object-oriented route where the code is easier to understand and maintain. (And also allows for future enhancements such as SQL extraction for query definition files.) --- .../modules/{modSanitize.bas => clsSanitize.cls} | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) rename Version Control.accda.src/modules/{modSanitize.bas => clsSanitize.cls} (99%) diff --git a/Version Control.accda.src/modules/modSanitize.bas b/Version Control.accda.src/modules/clsSanitize.cls similarity index 99% rename from Version Control.accda.src/modules/modSanitize.bas rename to Version Control.accda.src/modules/clsSanitize.cls index c13653bb..4cfb4d1c 100644 --- a/Version Control.accda.src/modules/modSanitize.bas +++ b/Version Control.accda.src/modules/clsSanitize.cls @@ -1,4 +1,12 @@ -Attribute VB_Name = "modSanitize" +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsSanitize" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False '--------------------------------------------------------------------------------------- ' Module : modSanitize ' Author : Adam Waller @@ -6,7 +14,6 @@ ' Purpose : Functions to sanitize files to remove non-essential metadata '--------------------------------------------------------------------------------------- Option Compare Database -Option Private Module Option Explicit Private Const ModuleName = "modSanitize" From 1c0d3f4be5298ef46d1e194cc162010c9fc4064e Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 25 Oct 2023 16:30:30 -0500 Subject: [PATCH 23/73] Refactor sanitizing to use class Updating the existing code to use the new class. --- .../modules/clsDbTableData.cls | 4 ++- .../modules/clsDbTableDef.cls | 4 ++- .../modules/clsSanitize.cls | 25 ------------------- .../modules/modFunctions.bas | 25 +++++++++++++++++++ 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbTableData.cls b/Version Control.accda.src/modules/clsDbTableData.cls index c16c1f8f..480ea475 100644 --- a/Version Control.accda.src/modules/clsDbTableData.cls +++ b/Version Control.accda.src/modules/clsDbTableData.cls @@ -70,7 +70,9 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) Application.ExportXML acExportTable, m_Table.Name, strFile End If Perf.OperationEnd - SanitizeXML strFile, False + With New clsSanitize + .SanitizeXML strFile, False + End With End Select End If Next intFormat diff --git a/Version Control.accda.src/modules/clsDbTableDef.cls b/Version Control.accda.src/modules/clsDbTableDef.cls index cbb183a1..9370b4e2 100644 --- a/Version Control.accda.src/modules/clsDbTableDef.cls +++ b/Version Control.accda.src/modules/clsDbTableDef.cls @@ -59,7 +59,9 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) Perf.OperationEnd ' Rewrite sanitized XML as formatted UTF-8 content - strHash = SanitizeXML(strFile, True) + With New clsSanitize + strHash = .SanitizeXML(strFile, True) + End With Else ' Linked table - Save as JSON diff --git a/Version Control.accda.src/modules/clsSanitize.cls b/Version Control.accda.src/modules/clsSanitize.cls index 4cfb4d1c..88464b8c 100644 --- a/Version Control.accda.src/modules/clsSanitize.cls +++ b/Version Control.accda.src/modules/clsSanitize.cls @@ -732,31 +732,6 @@ Public Function TrimTabs(strText As String) As String End Function -'--------------------------------------------------------------------------------------- -' Procedure : StartsWith -' Author : Adam Waller -' Date : 11/5/2020 -' Purpose : See if a string begins with a specified string. -'--------------------------------------------------------------------------------------- -' -Public Function StartsWith(strText As String, strStartsWith As String, Optional Compare As VbCompareMethod = vbBinaryCompare) As Boolean - StartsWith = (InStr(1, strText, strStartsWith, Compare) = 1) -End Function - - -'--------------------------------------------------------------------------------------- -' Procedure : EndsWith -' Author : Adam Waller -' Date : 4/29/2021 -' Purpose : See if a string ends with a specified string. -'--------------------------------------------------------------------------------------- -' -Public Function EndsWith(strText As String, strEndsWith As String, Optional Compare As VbCompareMethod = vbBinaryCompare) As Boolean - EndsWith = (StrComp(Right$(strText, Len(strEndsWith)), strEndsWith, Compare) = 0) - 'EndsWith = (InStr(1, strText, strEndsWith, Compare) = len(strtext len(strendswith) 1) -End Function - - '--------------------------------------------------------------------------------------- ' Procedure : GetIndent ' Author : Adam Waller diff --git a/Version Control.accda.src/modules/modFunctions.bas b/Version Control.accda.src/modules/modFunctions.bas index ffb07d17..2f20d2b6 100644 --- a/Version Control.accda.src/modules/modFunctions.bas +++ b/Version Control.accda.src/modules/modFunctions.bas @@ -877,6 +877,31 @@ Public Function DeDupString(strText As String, strDuplicated As String) As Strin End Function +'--------------------------------------------------------------------------------------- +' Procedure : StartsWith +' Author : Adam Waller +' Date : 11/5/2020 +' Purpose : See if a string begins with a specified string. +'--------------------------------------------------------------------------------------- +' +Public Function StartsWith(strText As String, strStartsWith As String, Optional Compare As VbCompareMethod = vbBinaryCompare) As Boolean + StartsWith = (InStr(1, strText, strStartsWith, Compare) = 1) +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : EndsWith +' Author : Adam Waller +' Date : 4/29/2021 +' Purpose : See if a string ends with a specified string. +'--------------------------------------------------------------------------------------- +' +Public Function EndsWith(strText As String, strEndsWith As String, Optional Compare As VbCompareMethod = vbBinaryCompare) As Boolean + EndsWith = (StrComp(Right$(strText, Len(strEndsWith)), strEndsWith, Compare) = 0) + 'EndsWith = (InStr(1, strText, strEndsWith, Compare) = len(strtext len(strendswith) 1) +End Function + + '--------------------------------------------------------------------------------------- ' Procedure : SwapExtension ' Author : Adam Waller From c87261a7de7c5be96a51db0356fefdc26716ee18 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 25 Oct 2023 16:49:04 -0500 Subject: [PATCH 24/73] Refactor class variables --- .../modules/clsSanitize.cls | 98 +++++++++++-------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/Version Control.accda.src/modules/clsSanitize.cls b/Version Control.accda.src/modules/clsSanitize.cls index 88464b8c..e7d42bc6 100644 --- a/Version Control.accda.src/modules/clsSanitize.cls +++ b/Version Control.accda.src/modules/clsSanitize.cls @@ -16,12 +16,18 @@ Attribute VB_Exposed = False Option Compare Database Option Explicit -Private Const ModuleName = "modSanitize" +Public ObjectName As String -' Array of lines to skip -Private m_SkipLines() As Long -Private m_lngSkipIndex As Long -Private m_colBlocks As Collection + +' Private type to handle internal variables +Private Type udtThis + lngSkipLines() As Long ' Array of lines to skip + lngSkipIndex As Long + cOutput As clsConcat + cVBA As clsConcat + colBlocks As Collection +End Type +Private this As udtThis '--------------------------------------------------------------------------------------- @@ -77,9 +83,9 @@ Public Function SanitizeFile(strPath As String, blnReturnHash As Boolean) As Str If Options.SanitizeLevel = eslNone Then GoTo Build_Output ' Set up index of lines to skip - ReDim m_SkipLines(0 To UBound(varLines)) As Long - m_lngSkipIndex = 0 - Set m_colBlocks = New Collection + ReDim this.lngSkipLines(0 To UBound(varLines)) As Long + this.lngSkipIndex = 0 + Set this.colBlocks = New Collection ' Initialize concatenation class to include line breaks ' after each line that we add when building new file text. @@ -237,11 +243,11 @@ Public Function SanitizeFile(strPath As String, blnReturnHash As Boolean) As Str Loop ' Ensure that we correctly processed the nested block sequence. - If m_colBlocks.Count > 0 Then + If this.colBlocks.Count > 0 Then Log.Error eelWarning, Replace(Replace( _ "Found ${BlockCount} unclosed blocks after sanitizing ${File}.", _ - "${BlockCount}", m_colBlocks.Count), _ - "${File}", strPath), ModuleName & ".SanitizeFile" + "${BlockCount}", this.colBlocks.Count), _ + "${File}", strPath), ModuleName(Me) & ".SanitizeFile" End If Build_Output: @@ -253,12 +259,12 @@ Build_Output: If blnReturnHash Then SanitizeFile = GetStringHash(strContent, True) ' Log performance - Set m_colBlocks = Nothing + Set this.colBlocks = Nothing Perf.OperationEnd Log.Add " Sanitized in " & Format$(Perf.MicroTimer - curStart, "0.000") & " seconds.", Options.ShowDebug ' Log any errors - CatchAny eelError, "Error sanitizing file " & FSO.GetFileName(strPath), ModuleName & ".SanitizeFile" + CatchAny eelError, "Error sanitizing file " & FSO.GetFileName(strPath), ModuleName(Me) & ".SanitizeFile" End Function @@ -277,14 +283,14 @@ Private Function BuildOutput(varLines As Variant) As String Dim lngLine As Long ' Check index of skipped lines - If m_lngSkipIndex = 0 Then + If this.lngSkipIndex = 0 Then ' No lines to skip - ReDim m_SkipLines(0 To 0) - m_SkipLines(0) = UBound(varLines) + 1 + ReDim this.lngSkipLines(0 To 0) + this.lngSkipLines(0) = UBound(varLines) + 1 Else ' Trim and sort index array - ReDim Preserve m_SkipLines(0 To m_lngSkipIndex - 1) - QuickSort m_SkipLines + ReDim Preserve this.lngSkipLines(0 To this.lngSkipIndex - 1) + QuickSort this.lngSkipLines End If ' Use concatenation class to maximize performance @@ -297,12 +303,12 @@ Private Function BuildOutput(varLines As Variant) As String ' Iterate the sorted skipped lines index to keep up with main loop ' (Using parallel loops to optimize performance) - If m_SkipLines(lngSkip) < lngLine Then - If lngSkip < UBound(m_SkipLines) Then lngSkip = lngSkip + 1 + If this.lngSkipLines(lngSkip) < lngLine Then + If lngSkip < UBound(this.lngSkipLines) Then lngSkip = lngSkip + 1 End If ' Add content, unless the line is flagged to skip - If m_SkipLines(lngSkip) <> lngLine Then .Add CStr(varLines(lngLine)) + If this.lngSkipLines(lngSkip) <> lngLine Then .Add CStr(varLines(lngLine)) Next lngLine @@ -326,8 +332,8 @@ End Function ' Private Function SkipLine(lngLine As Long, Optional intMinSanitizeLevel As eSanitizeLevel) If Options.SanitizeLevel >= intMinSanitizeLevel Then - m_SkipLines(m_lngSkipIndex) = lngLine - m_lngSkipIndex = m_lngSkipIndex + 1 + this.lngSkipLines(this.lngSkipIndex) = lngLine + this.lngSkipIndex = this.lngSkipIndex + 1 End If End Function @@ -370,10 +376,10 @@ End Function ' Private Sub BeginBlock(Optional strType As String) Dim dBlock As Dictionary - If m_colBlocks Is Nothing Then Set m_colBlocks = New Collection + If this.colBlocks Is Nothing Then Set this.colBlocks = New Collection Set dBlock = New Dictionary If strType <> vbNullString Then dBlock.Add "Type", strType - m_colBlocks.Add dBlock + this.colBlocks.Add dBlock End Sub @@ -396,14 +402,14 @@ Private Sub CloseBlock() If Options.SanitizeColors <= eslNone Then Exit Sub ' Bail out if we don't have a block to review - If m_colBlocks.Count = 0 Then Exit Sub - Set dBlock = m_colBlocks(m_colBlocks.Count) + If this.colBlocks.Count = 0 Then Exit Sub + Set dBlock = this.colBlocks(this.colBlocks.Count) ' Skip if we are not using themes for this control (UseTheme=0) ' (Applies to "CommandButton", "Tab", "ToggleButton") If dBlock.Exists("UseTheme") Then ' Remove this block - m_colBlocks.Remove m_colBlocks.Count + this.colBlocks.Remove this.colBlocks.Count Exit Sub End If @@ -448,7 +454,7 @@ Private Sub CloseBlock() Next intCnt ' Remove this block - m_colBlocks.Remove m_colBlocks.Count + this.colBlocks.Remove this.colBlocks.Count End Sub @@ -472,9 +478,9 @@ Private Sub CheckColorProperties(strTLine As String, lngLine As Long) If Options.SanitizeColors <= eslNone Then Exit Sub ' Exit if we are not inside a block - If Not m_colBlocks Is Nothing Then lngCnt = m_colBlocks.Count + If Not this.colBlocks Is Nothing Then lngCnt = this.colBlocks.Count If lngCnt = 0 Then Exit Sub - Set dBlock = m_colBlocks(m_colBlocks.Count) + Set dBlock = this.colBlocks(this.colBlocks.Count) ' Split on property/value varParts = Split(strTLine, " =") @@ -631,7 +637,7 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri If objXml.LoadXML(strFile) = False Then Log.Error eelError, _ - "Unable to parse the XML for file '" & strPath & "'. This may be due to containing malformed XML. Check the source XML document for validity. In some cases, this may be due to table data containing characters not allowed in XML documents.", ModuleName & ".SanitizeXML" + "Unable to parse the XML for file '" & strPath & "'. This may be due to containing malformed XML. Check the source XML document for validity. In some cases, this may be due to table data containing characters not allowed in XML documents.", ModuleName(Me) & ".SanitizeXML" Exit Function End If @@ -685,7 +691,7 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri Log.Add " Sanitized in " & Format$(Perf.MicroTimer - curStart, "0.000") & " seconds.", Options.ShowDebug ' Log any errors - CatchAny eelError, "Error sanitizing XML file " & FSO.GetFileName(strPath), ModuleName & ".SanitizeXML" + CatchAny eelError, "Error sanitizing XML file " & FSO.GetFileName(strPath), ModuleName(Me) & ".SanitizeXML" End Function @@ -697,7 +703,7 @@ End Function ' Purpose : Trim off tabs from beginning and end of string '--------------------------------------------------------------------------------------- ' -Public Function TrimTabs(strText As String) As String +Private Function TrimTabs(strText As String) As String Dim dblStart As Double Dim dblEnd As Double @@ -739,7 +745,7 @@ End Function ' Purpose : Returns the number of spaces until the first non-space character. '--------------------------------------------------------------------------------------- ' -Public Function GetIndent(strLine As Variant) As Integer +Private Function GetIndent(strLine As Variant) As Integer Dim strChar As String strChar = Left$(Trim(strLine), 1) If strLine <> vbNullString Then GetIndent = InStr(1, strLine, strChar) - 1 @@ -753,10 +759,8 @@ End Function ' Purpose : Format XML content for consistent and readable output. '--------------------------------------------------------------------------------------- ' -Private Function FormatXML( _ - objInput As MSXML2.DOMDocument60, _ - Optional blnOmitDeclaration As Boolean _ -) As String +Private Function FormatXML(objInput As MSXML2.DOMDocument60, _ + Optional blnOmitDeclaration As Boolean) As String ' XSLT stylesheet that allow us to control indenting and also get a better indent result. ' For testing and adjusting, you can use https://www.online-toolz.com/tools/xslt-validator-tester-online.php @@ -798,7 +802,7 @@ Private Function FormatXML( _ End If ' Check for any errors parsing the XML - If CatchAny(eelError, "Error parsing XML content", ModuleName & ".FormatXML") Then + If CatchAny(eelError, "Error parsing XML content", ModuleName(Me) & ".FormatXML") Then ' Fall back to input XML strOutput = objInput.XML ' Output XML to log file @@ -812,3 +816,17 @@ Private Function FormatXML( _ FormatXML = strOutput End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : Class_Terminate +' Author : Adam Waller +' Date : 10/25/2023 +' Purpose : Release private objects when terminating +'--------------------------------------------------------------------------------------- +' +Private Sub Class_Terminate() + Set this.colBlocks = Nothing + Set this.cOutput = Nothing + Set this.cVBA = Nothing +End Sub From 89101c5038fa1d90cc0b7a61ff406cddd8e29c02 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 2 Nov 2023 15:06:49 -0500 Subject: [PATCH 25/73] Refactor form/report export to split VBA Export is now splitting the VBA from Form and Report objects to separate files with a .cls extension. Moving on to the code that will stitch these files back together before import. --- .../modules/clsDbForm.cls | 3 +- .../modules/clsDbReport.cls | 3 +- .../modules/clsDbTableData.cls | 3 +- .../modules/clsDbTableDef.cls | 5 +- .../modules/clsSanitize.cls | 294 ++++++++++++++---- .../modules/modVCSUtility.bas | 54 +++- 6 files changed, 280 insertions(+), 82 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbForm.cls b/Version Control.accda.src/modules/clsDbForm.cls index b8a27c77..4e54ff3e 100644 --- a/Version Control.accda.src/modules/clsDbForm.cls +++ b/Version Control.accda.src/modules/clsDbForm.cls @@ -124,6 +124,7 @@ End Sub Private Sub IDbComponent_MoveSource(strFromFolder As String, strToFolder As String) MoveFileIfExists strFromFolder & FSO.GetFileName(IDbComponent_SourceFile), strToFolder MoveFileIfExists strFromFolder & FSO.GetBaseName(IDbComponent_SourceFile) & ".json", strToFolder + MoveFileIfExists strFromFolder & FSO.GetBaseName(IDbComponent_SourceFile) & ".cls", strToFolder End Sub @@ -180,7 +181,7 @@ End Function ' Private Sub IDbComponent_ClearOrphanedSourceFiles() If Not Options.SavePrintVars Then ClearFilesByExtension IDbComponent_BaseFolder, "json" - ClearOrphanedSourceFiles Me, "bas", "json" + ClearOrphanedSourceFiles Me, "bas", "json", "cls" End Sub diff --git a/Version Control.accda.src/modules/clsDbReport.cls b/Version Control.accda.src/modules/clsDbReport.cls index 7ed0b967..203ac99f 100644 --- a/Version Control.accda.src/modules/clsDbReport.cls +++ b/Version Control.accda.src/modules/clsDbReport.cls @@ -93,6 +93,7 @@ End Sub Private Sub IDbComponent_MoveSource(strFromFolder As String, strToFolder As String) MoveFileIfExists strFromFolder & FSO.GetFileName(IDbComponent_SourceFile), strToFolder MoveFileIfExists strFromFolder & FSO.GetBaseName(IDbComponent_SourceFile) & ".json", strToFolder + MoveFileIfExists strFromFolder & FSO.GetBaseName(IDbComponent_SourceFile) & ".cls", strToFolder End Sub @@ -150,7 +151,7 @@ End Function Private Sub IDbComponent_ClearOrphanedSourceFiles() ClearFilesByExtension IDbComponent_BaseFolder, "pv" ' Remove legacy files If Not Options.SavePrintVars Then ClearFilesByExtension IDbComponent_BaseFolder, "json" - ClearOrphanedSourceFiles Me, "bas", "json" + ClearOrphanedSourceFiles Me, "bas", "json", "cls" End Sub diff --git a/Version Control.accda.src/modules/clsDbTableData.cls b/Version Control.accda.src/modules/clsDbTableData.cls index 480ea475..4c5fbbbc 100644 --- a/Version Control.accda.src/modules/clsDbTableData.cls +++ b/Version Control.accda.src/modules/clsDbTableData.cls @@ -71,7 +71,8 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) End If Perf.OperationEnd With New clsSanitize - .SanitizeXML strFile, False + .LoadSourceFile strFile + WriteFile .Sanitize(ectXML), strFile End With End Select End If diff --git a/Version Control.accda.src/modules/clsDbTableDef.cls b/Version Control.accda.src/modules/clsDbTableDef.cls index 9370b4e2..880f850b 100644 --- a/Version Control.accda.src/modules/clsDbTableDef.cls +++ b/Version Control.accda.src/modules/clsDbTableDef.cls @@ -60,7 +60,10 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) ' Rewrite sanitized XML as formatted UTF-8 content With New clsSanitize - strHash = .SanitizeXML(strFile, True) + .LoadSourceFile strFile + DeleteFile strFile + WriteFile .Sanitize(ectXML), strFile + strHash = .Hash End With Else diff --git a/Version Control.accda.src/modules/clsSanitize.cls b/Version Control.accda.src/modules/clsSanitize.cls index e7d42bc6..9b353538 100644 --- a/Version Control.accda.src/modules/clsSanitize.cls +++ b/Version Control.accda.src/modules/clsSanitize.cls @@ -18,69 +18,210 @@ Option Explicit Public ObjectName As String +Public Enum eContentType + ectObjectDefinition + ectXML + ectVBA +End Enum ' Private type to handle internal variables Private Type udtThis lngSkipLines() As Long ' Array of lines to skip lngSkipIndex As Long - cOutput As clsConcat - cVBA As clsConcat + strFilePath As String ' Path to loaded file + strInput As String + strOutput As String + strVBA As String colBlocks As Collection End Type Private this As udtThis '--------------------------------------------------------------------------------------- -' Procedure : SanitizeFile +' Procedure : LoadFile ' Author : Adam Waller -' Date : 11/4/2020 -' Purpose : Rewritten version of sanitize function. Returns hash of content as well -' : as saving to the specified path. +' Date : 10/25/2023 +' Purpose : Load a source file to sanitize '--------------------------------------------------------------------------------------- ' -Public Function SanitizeFile(strPath As String, blnReturnHash As Boolean) As String +Public Function LoadSourceFile(strPath As String) - Dim strFile As String - Dim varLines As Variant - Dim lngLine As Long - Dim strLine As String - Dim strTLine As String - Dim blnInsideIgnoredBlock As Boolean - Dim intIndent As Integer - Dim blnIsReport As Boolean - Dim blnIsPassThroughQuery As Boolean - Dim curStart As Currency Dim strTempFile As String - Dim strContent As String - If DebugMode(True) Then On Error GoTo 0 Else On Error Resume Next + ResetContent ' Read text from file, and split into lines If HasUcs2Bom(strPath) Then - strFile = ReadFile(strPath, "Unicode") + this.strInput = ReadFile(strPath, "Unicode") Else ' ADP projects may contain mixed Unicode content If CurrentProject.ProjectType = acADP Then strTempFile = GetTempFile ConvertUcs2Utf8 strPath, strTempFile, False - strFile = ReadFile(strTempFile) + this.strInput = ReadFile(strTempFile) DeleteFile strTempFile Else If DbVersion <= 4 Then ' Access 2000 format exports using system codepage ' See issue #217 - strFile = ReadFile(strPath, GetSystemEncoding) + this.strInput = ReadFile(strPath, GetSystemEncoding) Else ' Newer versions export as UTF-8 - strFile = ReadFile(strPath) + this.strInput = ReadFile(strPath) End If End If End If - Perf.OperationStart "Sanitize File" - varLines = Split(strFile, vbCrLf) +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : LoadString +' Author : Adam Waller +' Date : 10/27/2023 +' Purpose : Load input from a string +'--------------------------------------------------------------------------------------- +' +Public Function LoadString(ByVal strContent As String) + ResetContent + this.strInput = strContent +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : ResetContent +' Author : Adam Waller +' Date : 10/31/2023 +' Purpose : Resets the local variables when data is loaded from a new source. +'--------------------------------------------------------------------------------------- +' +Private Sub ResetContent() + With this + .strFilePath = vbNullString + .strInput = vbNullString + .strOutput = vbNullString + .strVBA = vbNullString + End With + Me.ObjectName = vbNullString +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : Sanitize +' Author : Adam Waller +' Date : 10/25/2023 +' Purpose : Public wrapper for sanitize functions +'--------------------------------------------------------------------------------------- +' +Public Function Sanitize(intContentType As eContentType) As String + Select Case intContentType + Case ectObjectDefinition: SanitizeObject + Case ectVBA: SanitizeVBA + Case ectXML: SanitizeXML + End Select + Sanitize = this.strOutput +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : MergeVBA +' Author : Adam Waller +' Date : 10/27/2023 +' Purpose : Merge VBA into the output content. +'--------------------------------------------------------------------------------------- +' +Public Sub MergeVBA(strVbaCode As String) + + Dim varLines As Variant + Dim lngLine As Long + + ' Rebuild output using provided VBA code + With New clsConcat + .AppendOnAdd = vbCrLf + + ' Load in existing sanitized content + varLines = Split(this.strOutput, vbCrLf) + For lngLine = 0 To UBound(varLines) + ' Note that the same heading name is used in both forms and reports + If varLines(lngLine) = "CodeBehindForm" Then + ' Allow merge of empty string to remove VBA code module + If Len(strVbaCode) Then .Add CStr(varLines(lngLine)) + Exit For + Else + ' Add all other lines + .Add CStr(varLines(lngLine)) + End If + Next lngLine + + ' Add the VBA code here, and remove extra vbCrLf + .Add strVbaCode + .Remove 2 - If Options.SanitizeLevel = eslNone Then GoTo Build_Output + ' Update output with combined content + this.strOutput = .GetStr + End With + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : SaveObjectVBA +' Author : Adam Waller +' Date : 10/31/2023 +' Purpose : Return the extracted VBA code +'--------------------------------------------------------------------------------------- +' +Public Function GetObjectVBA() As String + GetObjectVBA = this.strVBA +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : Hash +' Author : Adam Waller +' Date : 10/25/2023 +' Purpose : Return a hash of the sanitized content +'--------------------------------------------------------------------------------------- +' +Public Function Hash(Optional blnWithBOM As Boolean = True) As String + Hash = GetStringHash(this.strOutput, blnWithBOM) +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : SanitizeFile +' Author : Adam Waller +' Date : 11/4/2020 +' Purpose : Rewritten version of sanitize function. Returns hash of content as well +' : as saving to the specified path. +'--------------------------------------------------------------------------------------- +' +Private Function SanitizeObject() As String 'strPath As String, blnReturnHash As Boolean, Optional strObjectName As String) As String + + Dim varLines As Variant + Dim lngLine As Long + Dim strLine As String + Dim strTLine As String + Dim blnInsideIgnoredBlock As Boolean + Dim intIndent As Integer + Dim blnIsReport As Boolean + Dim blnIsPassThroughQuery As Boolean + Dim curStart As Currency + Dim strTempFile As String + Dim strContent As String + Dim cVBA As clsConcat + + If DebugMode(True) Then On Error GoTo 0 Else On Error Resume Next + + ' If not sanitizing, then return output + If Options.SanitizeLevel = eslNone Then + this.strOutput = this.strInput + SanitizeObject = this.strOutput + Exit Function + End If + + Perf.OperationStart "Sanitize File" + varLines = Split(this.strInput, vbCrLf) ' Set up index of lines to skip ReDim this.lngSkipLines(0 To UBound(varLines)) As Long @@ -177,10 +318,28 @@ Public Function SanitizeFile(strPath As String, blnReturnHash As Boolean) As Str ' Code section behind form or report object Case "CodeBehindForm" - ' Apply sanitize rules to VBA code - SanitizeCodeLines lngLine, varLines - ' Keep everything from this point on - Exit Do + If Options.SplitLayoutFromVBA Then + ' Remove the VBA code from the layout file, but add a placeholder + ' comment just in case the user wonders what happened to the VBA + ' source code. + lngLine = lngLine + 1 + Set cVBA = New clsConcat + cVBA.AppendOnAdd = vbCrLf + cVBA.Add CStr(varLines(lngLine)) + varLines(lngLine) = "' See """ & Nz2(Me.ObjectName, "FileName") & ".cls""" + ' Skip remaining lines + Do While lngLine < UBound(varLines) + lngLine = lngLine + 1 + cVBA.Add CStr(varLines(lngLine)) + SkipLine lngLine, eslStandard + Loop + Exit Do + Else + ' Apply sanitize rules to VBA code + SanitizeCodeLines lngLine, varLines + ' Keep everything from this point on + Exit Do + End If Case Else If blnInsideIgnoredBlock Then @@ -244,19 +403,25 @@ Public Function SanitizeFile(strPath As String, blnReturnHash As Boolean) As Str ' Ensure that we correctly processed the nested block sequence. If this.colBlocks.Count > 0 Then - Log.Error eelWarning, Replace(Replace( _ - "Found ${BlockCount} unclosed blocks after sanitizing ${File}.", _ - "${BlockCount}", this.colBlocks.Count), _ - "${File}", strPath), ModuleName(Me) & ".SanitizeFile" + Log.Error eelWarning, MultiReplace( _ + "Found ${BlockCount} unclosed blocks after sanitizing ${File}.", _ + "${BlockCount}", this.colBlocks.Count, _ + "${File}", Nz2(Me.ObjectName, this.strFilePath)), _ + ModuleName(Me) & ".SanitizeFile" End If -Build_Output: - ' Build the final output - strContent = BuildOutput(varLines) - WriteFile strContent, strPath - - ' Return hash of content - If blnReturnHash Then SanitizeFile = GetStringHash(strContent, True) + ' Prepare primary output + this.strOutput = BuildOutput(varLines) + SanitizeObject = this.strOutput + + ' Prepare VBA output (if used) + If Not cVBA Is Nothing Then + ' Build sanitized VBA string + With New clsSanitize + .LoadString cVBA.GetStr + this.strVBA = .Sanitize(ectVBA) + End With + End If ' Log performance Set this.colBlocks = Nothing @@ -264,7 +429,7 @@ Build_Output: Log.Add " Sanitized in " & Format$(Perf.MicroTimer - curStart, "0.000") & " seconds.", Options.ShowDebug ' Log any errors - CatchAny eelError, "Error sanitizing file " & FSO.GetFileName(strPath), ModuleName(Me) & ".SanitizeFile" + CatchAny eelError, "Error sanitizing " & Nz2(Me.ObjectName, FSO.GetFileName(this.strFilePath)), ModuleName(Me) & ".SanitizeFile" End Function @@ -541,7 +706,7 @@ End Sub ' : - Remove extra trailing lines from the end of the module. '--------------------------------------------------------------------------------------- ' -Public Function SanitizeVBA(strCode As String) As String +Private Function SanitizeVBA() As String Dim lngLine As Long Dim varLines As Variant @@ -549,14 +714,14 @@ Public Function SanitizeVBA(strCode As String) As String ' Skip sanitizing if not using that option. If Options.SanitizeLevel < eslStandard Then - SanitizeVBA = strCode + SanitizeVBA = this.strInput Exit Function End If Perf.OperationStart "Sanitize VBA Code" ' Split code into lines - varLines = Split(strCode, vbCrLf) + varLines = Split(this.strInput, vbCrLf) ' Build sanitized content With New clsConcat @@ -576,7 +741,9 @@ Public Function SanitizeVBA(strCode As String) As String Next lngLine ' Return standardized code block - SanitizeVBA = .GetStr + this.strOutput = .GetStr + this.strVBA = this.strOutput + SanitizeVBA = this.strOutput Perf.OperationEnd End With @@ -592,11 +759,11 @@ End Function ' : back again afterwards to compute the hash.) '--------------------------------------------------------------------------------------- ' -Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As String +Private Function SanitizeXML() As String Dim curStart As Currency Dim cData As clsConcat - Dim strFile As String + Dim strXML As String Dim rxLine As VBScript_RegExp_55.RegExp If DebugMode(True) Then On Error GoTo 0 Else On Error Resume Next @@ -605,15 +772,9 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri cData.AppendOnAdd = vbCrLf Set rxLine = New VBScript_RegExp_55.RegExp - ' Read text from file - If HasUcs2Bom(strPath) Then - ' Table data macro XML is exported as UTF-16 LE BOM - strFile = ReadFile(strPath, "Unicode") - Else - strFile = ReadFile(strPath) - End If Perf.OperationStart "Sanitize XML" curStart = Perf.MicroTimer + strXML = this.strInput ' Exporting Table Def as XML does not properly encode ampersand character (See #314) ' Most likely if any ampersands are encoded correctly, all of them will be. @@ -622,9 +783,9 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri .Global = True ' Match & " > < etc... .Pattern = "&[A-z]{2,6};" - If Not .Test(strFile) Then + If Not .Test(strXML) Then ' Properly encode any embedded ampersand characters to make valid XML - strFile = Replace(strFile, "&", "&") + strXML = Replace(strXML, "&", "&") End If End With @@ -635,9 +796,12 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri Set objXml = New MSXML2.DOMDocument60 End If - If objXml.LoadXML(strFile) = False Then + If objXml.LoadXML(strXML) = False Then Log.Error eelError, _ - "Unable to parse the XML for file '" & strPath & "'. This may be due to containing malformed XML. Check the source XML document for validity. In some cases, this may be due to table data containing characters not allowed in XML documents.", ModuleName(Me) & ".SanitizeXML" + "Unable to parse the XML for file '" & Nz2(this.strFilePath, Me.ObjectName) & _ + "'. This may be due to containing malformed XML. Check the source XML document for validity. " & _ + "In some cases, this may be due to table data containing characters not allowed in XML documents.", _ + ModuleName(Me) & ".SanitizeXML" Exit Function End If @@ -681,17 +845,15 @@ Public Function SanitizeXML(strPath As String, blnReturnHash As Boolean) As Stri Perf.OperationEnd - ' Write out sanitized XML file - WriteFile FormatXML(objXml), strPath - - ' Return hash, if requested - If blnReturnHash Then SanitizeXML = GetStringHash(cData.GetStr, True) + ' Save the output + this.strOutput = FormatXML(objXml) + SanitizeXML = this.strOutput ' Show stats if debug turned on. Log.Add " Sanitized in " & Format$(Perf.MicroTimer - curStart, "0.000") & " seconds.", Options.ShowDebug ' Log any errors - CatchAny eelError, "Error sanitizing XML file " & FSO.GetFileName(strPath), ModuleName(Me) & ".SanitizeXML" + CatchAny eelError, "Error sanitizing XML for " & Nz2(FSO.GetFileName(this.strFilePath), Me.ObjectName), ModuleName(Me) & ".SanitizeXML" End Function @@ -827,6 +989,4 @@ End Function ' Private Sub Class_Terminate() Set this.colBlocks = Nothing - Set this.cOutput = Nothing - Set this.cVBA = Nothing End Sub diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index de666e55..80cb3cf1 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -409,8 +409,11 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Optional cDbObjectClass As IDbComponent = Nothing) As String Dim strTempFile As String + Dim strAltFile As String + Dim strPrefix As String Dim strPrintSettingsFile As String Dim strHash As String + Dim cSanitize As clsSanitize On Error GoTo ErrHandler @@ -421,7 +424,11 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Perf.OperationEnd VerifyPath strFile + ' Delete any existing source file + If FSO.FileExists(strFile) Then DeleteFile strFile + ' Sanitize certain object types + Set cSanitize = New clsSanitize Select Case intType Case acForm, acReport With New clsDevMode @@ -445,25 +452,44 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ If FSO.FileExists(strPrintSettingsFile) Then DeleteFile strPrintSettingsFile End If End With + ' Sanitizing converts to UTF-8 - If FSO.FileExists(strFile) Then DeleteFile strFile - strHash = SanitizeFile(strTempFile, True) - FSO.MoveFile strTempFile, strFile + With cSanitize + .LoadSourceFile strTempFile + .ObjectName = FSO.GetBaseName(strFile) + WriteFile .Sanitize(ectObjectDefinition), strFile + strHash = .Hash + + ' Process any VBA + strAltFile = SwapExtension(strFile, "cls") + If Options.SplitLayoutFromVBA And Len(.GetObjectVBA) Then + ' Write VBA code as separate .cls file. + WriteFile .GetObjectVBA, strAltFile + Else + ' Remove any split VBA file + If FSO.FileExists(strAltFile) Then DeleteFile strAltFile + End If + End With Case acQuery, acMacro ' Sanitizing converts to UTF-8 - If FSO.FileExists(strFile) Then DeleteFile strFile - strHash = SanitizeFile(strTempFile, True) - FSO.MoveFile strTempFile, strFile + With cSanitize + .LoadSourceFile strTempFile + WriteFile .Sanitize(ectObjectDefinition), strFile + strHash = .Hash + End With ' Case acModule - Use VBE export instead. Case acTableDataMacro ' Table data macros are stored in XML format + ' The file may not exist if no TD Macro was found If FSO.FileExists(strTempFile) Then - strHash = SanitizeXML(strTempFile, True) - If FSO.FileExists(strFile) Then DeleteFile strFile - FSO.MoveFile strTempFile, strFile + With cSanitize + .LoadSourceFile strTempFile + WriteFile .Sanitize(ectXML), strFile + strHash = .Hash + End With End If Case Else @@ -472,6 +498,9 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ End Select + ' Remove any leftover temp file. + If FSO.FileExists(strTempFile) Then DeleteFile strTempFile + ' Normal exit On Error GoTo 0 @@ -590,10 +619,13 @@ Public Sub ExportCodeModule(strName As String, strFile As String) ' Export to a temp file so we can convert to UTF-8 encoding strTempFile = GetTempFile - CurrentVBProject.VBComponents(strName).Export strFile + CurrentVBProject.VBComponents(strName).Export strTempFile ' Sanitize the VBA code while reading the temp file - strContent = SanitizeVBA(ReadFile(strTempFile, GetSystemEncoding)) + With New clsSanitize + .LoadString ReadFile(strTempFile, GetSystemEncoding) + strContent = .Sanitize(ectVBA) + End With ' Write the content as UTF-8 to the final destination WriteFile strContent, strFile From 47c650647b50403e479fb4d3d96c54321b0c8f0c Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 2 Nov 2023 17:21:57 -0500 Subject: [PATCH 26/73] Rename Sanitize class to SourceParser This better reflects the expanded role of the class. --- .../modules/{clsSanitize.cls => clsSourceParser.cls} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Version Control.accda.src/modules/{clsSanitize.cls => clsSourceParser.cls} (99%) diff --git a/Version Control.accda.src/modules/clsSanitize.cls b/Version Control.accda.src/modules/clsSourceParser.cls similarity index 99% rename from Version Control.accda.src/modules/clsSanitize.cls rename to Version Control.accda.src/modules/clsSourceParser.cls index 9b353538..fdf79007 100644 --- a/Version Control.accda.src/modules/clsSanitize.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -2,7 +2,7 @@ BEGIN MultiUse = -1 'True END -Attribute VB_Name = "clsSanitize" +Attribute VB_Name = "clsSourceParser" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False From c2ffccf0f017cf5f76a4be582ca9990cdb736916 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Thu, 2 Nov 2023 17:27:43 -0500 Subject: [PATCH 27/73] Refactor for name change --- Version Control.accda.src/modules/clsDbTableData.cls | 2 +- Version Control.accda.src/modules/clsDbTableDef.cls | 2 +- .../modules/clsSourceParser.cls | 4 ++-- Version Control.accda.src/modules/modVCSUtility.bas | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbTableData.cls b/Version Control.accda.src/modules/clsDbTableData.cls index 4c5fbbbc..ed00152b 100644 --- a/Version Control.accda.src/modules/clsDbTableData.cls +++ b/Version Control.accda.src/modules/clsDbTableData.cls @@ -70,7 +70,7 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) Application.ExportXML acExportTable, m_Table.Name, strFile End If Perf.OperationEnd - With New clsSanitize + With New clsSourceParser .LoadSourceFile strFile WriteFile .Sanitize(ectXML), strFile End With diff --git a/Version Control.accda.src/modules/clsDbTableDef.cls b/Version Control.accda.src/modules/clsDbTableDef.cls index 880f850b..c119bee4 100644 --- a/Version Control.accda.src/modules/clsDbTableDef.cls +++ b/Version Control.accda.src/modules/clsDbTableDef.cls @@ -59,7 +59,7 @@ Private Sub IDbComponent_Export(Optional strAlternatePath As String) Perf.OperationEnd ' Rewrite sanitized XML as formatted UTF-8 content - With New clsSanitize + With New clsSourceParser .LoadSourceFile strFile DeleteFile strFile WriteFile .Sanitize(ectXML), strFile diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index fdf79007..7f73ee67 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -8,7 +8,7 @@ Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False '--------------------------------------------------------------------------------------- -' Module : modSanitize +' Module : clsSourceParser ' Author : Adam Waller ' Date : 12/4/2020 ' Purpose : Functions to sanitize files to remove non-essential metadata @@ -417,7 +417,7 @@ Private Function SanitizeObject() As String 'strPath As String, blnReturnHash As ' Prepare VBA output (if used) If Not cVBA Is Nothing Then ' Build sanitized VBA string - With New clsSanitize + With New clsSourceParser .LoadString cVBA.GetStr this.strVBA = .Sanitize(ectVBA) End With diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 80cb3cf1..b90ba8ee 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -413,7 +413,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Dim strPrefix As String Dim strPrintSettingsFile As String Dim strHash As String - Dim cSanitize As clsSanitize + Dim cParser As clsSourceParser On Error GoTo ErrHandler @@ -428,7 +428,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ If FSO.FileExists(strFile) Then DeleteFile strFile ' Sanitize certain object types - Set cSanitize = New clsSanitize + Set cParser = New clsSourceParser Select Case intType Case acForm, acReport With New clsDevMode @@ -454,7 +454,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ End With ' Sanitizing converts to UTF-8 - With cSanitize + With cParser .LoadSourceFile strTempFile .ObjectName = FSO.GetBaseName(strFile) WriteFile .Sanitize(ectObjectDefinition), strFile @@ -473,7 +473,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Case acQuery, acMacro ' Sanitizing converts to UTF-8 - With cSanitize + With cParser .LoadSourceFile strTempFile WriteFile .Sanitize(ectObjectDefinition), strFile strHash = .Hash @@ -485,7 +485,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ ' Table data macros are stored in XML format ' The file may not exist if no TD Macro was found If FSO.FileExists(strTempFile) Then - With cSanitize + With cParser .LoadSourceFile strTempFile WriteFile .Sanitize(ectXML), strFile strHash = .Hash @@ -622,7 +622,7 @@ Public Sub ExportCodeModule(strName As String, strFile As String) CurrentVBProject.VBComponents(strName).Export strTempFile ' Sanitize the VBA code while reading the temp file - With New clsSanitize + With New clsSourceParser .LoadString ReadFile(strTempFile, GetSystemEncoding) strContent = .Sanitize(ectVBA) End With From 2d21eb696631e1d33c609c48fedcf3dec79ec4ac Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Fri, 3 Nov 2023 11:48:21 -0500 Subject: [PATCH 28/73] Verify ribbon active state when the add-in loads Ensure that the ribbon is active when installing or activating the add-in. See #451 --- .../modules/clsVersionControl.cls | 4 ++++ .../modules/modComAddIn.bas | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Version Control.accda.src/modules/clsVersionControl.cls b/Version Control.accda.src/modules/clsVersionControl.cls index 35c22c47..e0913d46 100644 --- a/Version Control.accda.src/modules/clsVersionControl.cls +++ b/Version Control.accda.src/modules/clsVersionControl.cls @@ -437,6 +437,10 @@ End Sub ' Private Sub Class_Initialize() SaveState + ' When the class is initialized, make sure the ribbon is active (if installed). + ' This way if the COM add-in is not active, it will be automatically activated + ' when the add-in is opened from the [Database Tools\Add-ins] menu. (See #451) + modCOMAddIn.VerifyRibbon End Sub diff --git a/Version Control.accda.src/modules/modComAddIn.bas b/Version Control.accda.src/modules/modComAddIn.bas index f6e328ba..f1ff74c5 100644 --- a/Version Control.accda.src/modules/modComAddIn.bas +++ b/Version Control.accda.src/modules/modComAddIn.bas @@ -87,6 +87,9 @@ Public Sub VerifyComAddIn() ' Reload the add-in to refresh the ribbon UnloadAddIn LoadAddIn + Else + ' Verify that the ribbon is active + VerifyRibbon End If End If @@ -107,6 +110,24 @@ Public Sub ReloadRibbon() End Sub +'--------------------------------------------------------------------------------------- +' Procedure : VerifyRibbon +' Author : Adam Waller +' Date : 11/3/2023 +' Purpose : A lightweight function to verify that the ribbon add-in is active. +' : (It may get turned off if Access is opened in administrator mode.) +'--------------------------------------------------------------------------------------- +' +Public Sub VerifyRibbon() + Dim objAddIn As COMAddIn + Set objAddIn = GetCOMAddIn + If Not objAddIn Is Nothing Then + ' Activate the add-in if it is not currently active + If Not objAddIn.Connect Then objAddIn.Connect = True + End If +End Sub + + '--------------------------------------------------------------------------------------- ' Procedure : UninstallComAddIn ' Author : Adam Waller From da9f94abf16782d0e3868fb87f0b17b01d17c003 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 6 Nov 2023 16:11:33 -0600 Subject: [PATCH 29/73] Don't auto split layout/VBA for existing projects For existing projects in git repositories, form and report layouts should not be automatically split from the VBA code classes. There is another process that will allow us to split the files while preserving history in both files, but this involves a couple commits and requires a clean branch. For existing projects, this is a manual upgrade (option changes). For new projects, it can happen by default. --- .../modules/clsGitIntegration.cls | 81 +++++++++++++++---- .../modules/clsOptions.cls | 60 ++++++++++++++ 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/Version Control.accda.src/modules/clsGitIntegration.cls b/Version Control.accda.src/modules/clsGitIntegration.cls index 5d59acd3..ace9583d 100644 --- a/Version Control.accda.src/modules/clsGitIntegration.cls +++ b/Version Control.accda.src/modules/clsGitIntegration.cls @@ -35,11 +35,12 @@ Private Enum eGitCommand egcGetUntrackedFiles egcGetHeadCommit egcGetBranchName - egcSetTaggedCommit egcGetReproPath egcGetRevision egcGetStatusPorcelain + egcIsInsideTree ' Action commands + egcSetTaggedCommit egcInitialize egcAddAll egcCommit @@ -101,6 +102,7 @@ Private Function RunGitCommand(intCmd As eGitCommand, Optional strArgument As St Case egcCheckoutNewBranch: strCmd = "git checkout -b {MyArg}" Case egcCheckoutHeadToCurrent: strCmd = "git checkout HEAD~ ." Case egcDeleteBranch: strCmd = "git branch --delete {MyArg}" + Case egcIsInsideTree: strCmd = "git rev-parse --is-inside-work-tree" Case Else Log.Error eelError, "Unrecognized Git Command Enum: " & intCmd Stop @@ -111,7 +113,7 @@ Private Function RunGitCommand(intCmd As eGitCommand, Optional strArgument As St ' Run command, and get result Perf.OperationStart "Git Command (id:" & intCmd & ")" - strResult = ShellRun(strCmd) + strResult = ShellRun(strCmd, intCmd) Perf.OperationEnd ' Trim any trailing vbLf @@ -170,7 +172,7 @@ End Function ' Purpose : Returns the path to the root of the repository. '--------------------------------------------------------------------------------------- ' -Public Function GetRepositoryRoot() As String +Public Function GetRepositoryRoot(Optional blnFallBackToWorking As Boolean = True) As String Static strLastFolder As String ' Working folder Static strLastRoot As String ' Repository Root @@ -180,6 +182,12 @@ Public Function GetRepositoryRoot() As String ' Determine the current working folder strWorking = GetWorkingFolder + ' Make sure git is actually installed + If Not Me.GitInstalled Then + If blnFallBackToWorking Then GetRepositoryRoot = strWorking + Exit Function + End If + ' On first call, we will attempt to get the repository root from the working ' folder, or the export folder if a working folder is not specified. If strLastRoot = vbNullString Or (strLastFolder <> strWorking) Then @@ -189,16 +197,20 @@ Public Function GetRepositoryRoot() As String strLastFolder = strWorking strLastRoot = vbNullString ' Recursively call this function to verify the path with git - GetRepositoryRoot = GetRepositoryRoot() + GetRepositoryRoot = GetRepositoryRoot(blnFallBackToWorking) Else ' Run git command from last folder strLastRoot = strLastFolder ' Use Git to look up root folder in repository. strLastRoot = Replace(RunGitCommand(egcGetReproPath), "/", PathSep) & PathSep If strLastRoot = PathSep Then - ' Might not be in a git repository. Fall back to working folder. - GetRepositoryRoot = strWorking - strLastRoot = strWorking + If blnFallBackToWorking Then + ' Might not be in a git repository. Fall back to working folder. + GetRepositoryRoot = strWorking + strLastRoot = strWorking + Else + GetRepositoryRoot = vbNullString + End If Else ' Found the root folder. Return to caller. GetRepositoryRoot = strLastRoot @@ -221,7 +233,20 @@ End Function '--------------------------------------------------------------------------------------- ' Private Function GetWorkingFolder() As String - GetWorkingFolder = StripSlash(Nz2(Me.WorkingFolder, Options.GetExportFolder)) & PathSep + + Dim strWorking As String + + ' Avoid calling Options if the working folder is already defined to prevent + ' a possible stack overflow. (That's why we don't use Nz2() here) + If Len(Me.WorkingFolder) Then + strWorking = Me.WorkingFolder + Else + strWorking = Options.GetExportFolder + End If + + ' Return path in consistent format + GetWorkingFolder = StripSlash(strWorking) & PathSep + End Function @@ -229,11 +254,28 @@ End Function ' Procedure : Version ' Author : Adam Waller ' Date : 3/10/2023 -' Purpose : Return git version +' Purpose : Return git version (Cached between calls) '--------------------------------------------------------------------------------------- ' Public Function Version() As String - Version = Replace(RunGitCommand(egcGetVersion), "git version ", vbNullString) + Static strVersion As String + If strVersion = vbNullString Then strVersion = Replace(RunGitCommand(egcGetVersion), "git version ", vbNullString) + Version = strVersion +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : IsInsideRepository +' Author : Adam Waller +' Date : 11/6/2023 +' Purpose : Returns true if the current working folder is inside a git repository. +'--------------------------------------------------------------------------------------- +' +Public Function IsInsideRepository() As Boolean + Dim strResult As String + If Me.GitInstalled Then + IsInsideRepository = (RunGitCommand(egcIsInsideTree) = "true") + End If End Function @@ -356,7 +398,7 @@ End Sub ' Purpose : Pass a git command to this function to return the result as a string. '--------------------------------------------------------------------------------------- ' -Private Function ShellRun(strCmd As String) As String +Private Function ShellRun(strCmd As String, intCmd As eGitCommand) As String Dim oShell As WshShell Dim strFile As String @@ -366,10 +408,16 @@ Private Function ShellRun(strCmd As String) As String ' Build command line string With New clsConcat - ' Open command prompt in repository folder - .Add "cmd.exe /c cd ", GetRepositoryRoot - ' Run git command - .Add " & ", strCmd + Select Case intCmd + Case egcGetVersion + ' Run independent of repository + .Add "cmd.exe /c ", strCmd + Case Else + ' Open command prompt in repository folder + .Add "cmd.exe /c cd ", GetRepositoryRoot + ' Run git command + .Add " & ", strCmd + End Select ' Output to temp file .Add " > """, strFile, """" ' Execute command @@ -478,8 +526,7 @@ End Function '--------------------------------------------------------------------------------------- ' Public Function GitInstalled() As Boolean - ' Expecting something like "git version 2.29.2.windows.2" - GitInstalled = InStr(1, RunGitCommand(egcGetVersion), "git version ") = 1 + GitInstalled = (Len(Me.Version)) End Function diff --git a/Version Control.accda.src/modules/clsOptions.cls b/Version Control.accda.src/modules/clsOptions.cls index 4f091b0b..3368b3e5 100644 --- a/Version Control.accda.src/modules/clsOptions.cls +++ b/Version Control.accda.src/modules/clsOptions.cls @@ -275,6 +275,9 @@ End Sub ' Private Sub Upgrade(ByRef dOptions As Dictionary) + Dim dFiles As Dictionary + Dim strPath As String + ' 6/16/2021 ' Aggressive sanitize to sanitize levels If dOptions.Exists("AggressiveSanitize") Then @@ -287,9 +290,66 @@ Private Sub Upgrade(ByRef dOptions As Dictionary) End If End If + ' 11/3/2023 + ' Check option to split VBA from object layout + If Not dOptions.Exists("SplitLayoutFromVBA") Then + ' The existing options file does not have this option defined. + ' See if we have any source files from previous exports. + If HasUnifiedLayoutFilesInGit(Me.GetExportFolder) Then + ' Set the option as false by default, and let the user + ' turn it on explicitly for this project. + ' (That way they are not forced to make a decision immediately) + Me.SplitLayoutFromVBA = False + Else + ' If we already have split files, or if this project is + ' being exported for the first time, leave the option at + ' the default setting. + End If + End If + End Sub +'--------------------------------------------------------------------------------------- +' Procedure : HasUnifiedLayoutFilesInGit +' Author : Adam Waller +' Date : 11/3/2023 +' Purpose : Returns true if the current project seems to have existing form or report +' : source files AND appears to be in a .git repository. +' : (This function is used when determining the default for splitting VBA +' : from layout files in new projects.) +' : For performance reasons this is not a fully comprehensive check of every +' : possible source file, but should be a pretty good indication of whether +' : existing source files need to be split in git to preserve the history in +' : both source files. +'--------------------------------------------------------------------------------------- +' +Private Function HasUnifiedLayoutFilesInGit(strExportPath As String) As Boolean + + Dim blnHasFiles As Boolean + Dim strFolder As String + + ' See if we have any ".bas" files, but no corresponding ".cls" files in the + ' forms and reports export folders. + ' Hard-coding the folder names to avoid calling options. + If GetFileList(BuildPath2(strExportPath, "forms"), "*.bas").Count > 0 Then + blnHasFiles = (GetFileList(BuildPath2(strExportPath, "forms"), "*.cls").Count = 0) + ElseIf GetFileList(BuildPath2(strExportPath, "reports"), "*.bas").Count > 0 Then + blnHasFiles = (GetFileList(BuildPath2(strExportPath, "reports"), "*.cls").Count = 0) + End If + + If blnHasFiles Then + ' Check to see if this folder is in a git repository + If Git.GitInstalled Then + ' Check export path + Git.WorkingFolder = strExportPath + HasUnifiedLayoutFilesInGit = Git.IsInsideRepository + End If + End If + +End Function + + '--------------------------------------------------------------------------------------- ' Procedure : LoadProjectOptions ' Author : Adam Waller From 54e070ae8bf3618bf57f2cd2ec1aedab946005e9 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 15:35:24 -0600 Subject: [PATCH 30/73] Move print settings processing to clsSourceParser This keeps the LoadComponentFromText function cleaner and easier to read. --- .../modules/clsDevMode.cls | 44 +++++-------------- .../modules/clsSourceParser.cls | 33 ++++++++++++++ .../modules/modVCSUtility.bas | 7 +++ 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/Version Control.accda.src/modules/clsDevMode.cls b/Version Control.accda.src/modules/clsDevMode.cls index 7482fbdb..c8b1f821 100644 --- a/Version Control.accda.src/modules/clsDevMode.cls +++ b/Version Control.accda.src/modules/clsDevMode.cls @@ -216,7 +216,7 @@ End Function ' Purpose : Load sections from export file '--------------------------------------------------------------------------------------- ' -Public Sub LoadFromExportFile(strFile As String) +Public Sub LoadFromExportFile(strFileContent As String) Dim varLines As Variant Dim lngLine As Long @@ -240,17 +240,11 @@ Public Sub LoadFromExportFile(strFile As String) ' Clear existing structures and create block classes. ClearStructures - If Not FSO.FileExists(strFile) Then Exit Sub - - ' Open the export file, checking to see if it is in UCS format - If HasUcs2Bom(strFile) Then - varLines = Split(ReadFile(strFile, "Unicode"), vbCrLf) - Else - varLines = Split(ReadFile(strFile), vbCrLf) - End If + If Not Len(strFileContent) Then Exit Sub ' Read the text file line by line, loading the block data Perf.OperationStart "Read File DevMode" + varLines = Split(strFileContent, vbCrLf) For lngLine = 0 To UBound(varLines) strLine = Trim$(varLines(lngLine)) ' Look for header if not inside block @@ -315,7 +309,7 @@ Public Sub LoadFromExportFile(strFile As String) Next intBlock Perf.OperationEnd - CatchAny eelError, "Error loading printer settings from file: " & strFile, _ + CatchAny eelError, "Error loading printer settings from file content.", _ ModuleName(Me) & ".LoadFromExportFile", True, True End Sub @@ -1007,18 +1001,6 @@ Public Sub ApplySettings(dSettings As Dictionary) End Sub -'--------------------------------------------------------------------------------------- -' Procedure : GetPrintSettingsFileName -' Author : Adam Waller -' Date : 1/14/2021 -' Purpose : Return the file name for the print vars json file. -'--------------------------------------------------------------------------------------- -' -Public Function GetPrintSettingsFileName(cDbObject As IDbComponent) As String - GetPrintSettingsFileName = cDbObject.BaseFolder & GetSafeFileName(cDbObject.Name) & ".json" -End Function - - '--------------------------------------------------------------------------------------- ' Procedure : AddToExportFile ' Author : Adam Waller @@ -1028,7 +1010,7 @@ End Function ' : file for import into the database using the loaded print settings. '--------------------------------------------------------------------------------------- ' -Public Function AddToExportFile(strFile As String) As String +Public Function AddToExportFile(strFileContent As String) As String Dim strTempFile As String Dim strLine As String @@ -1040,15 +1022,12 @@ Public Function AddToExportFile(strFile As String) As String If DebugMode(True) Then On Error GoTo 0 Else On Error Resume Next - ' Load data from export file - strData = ReadFile(strFile) - varLines = Split(strData, vbCrLf) - ' Use concatenation class for performance reasons. With New clsConcat .AppendOnAdd = vbCrLf ' Loop through lines in file, searching for location to insert blocks. + varLines = Split(strFileContent, vbCrLf) For lngLine = LBound(varLines) To UBound(varLines) ' Get single line @@ -1088,16 +1067,13 @@ Public Function AddToExportFile(strFile As String) As String End If Next lngLine - ' Write to new file - strTempFile = GetTempFile - WriteFile .GetStr, strTempFile - + ' Return content + AddToExportFile = .GetStr End With - ' Return path to temp file - AddToExportFile = strTempFile - CatchAny eelError, "Error adding to export file: " & strFile, _ + CatchAny eelError, "Error adding print settings to export file.", _ ModuleName(Me) & ".AddToExportFile", True, True + End Function diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index 7f73ee67..d158b225 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -164,6 +164,39 @@ Public Sub MergeVBA(strVbaCode As String) End Sub +'--------------------------------------------------------------------------------------- +' Procedure : MergePrintSettings +' Author : Adam Waller +' Date : 11/8/2023 +' Purpose : Merge print settings into the current source file. +'--------------------------------------------------------------------------------------- +' +Public Sub MergePrintSettings(strJson As String) + + Dim dSettings As Dictionary + + ' Make sure we have some output + If Not Len(this.strOutput) Then this.strOutput = this.strInput + + ' Don't try to parse an empty string + If strJson = vbNullString Then Exit Sub + + ' Read settings from JSON + Set dSettings = ParseJson(strJson) + If dSettings.Exists("Items") Then + With New clsDevMode + ' Load default printer settings, then overlay + ' settings saved with report. + .ApplySettings dSettings("Items") + ' Write the printer settings to the output content + this.strOutput = .AddToExportFile(this.strOutput) + this.blnOutputModified = True + End With + End If + +End Sub + + '--------------------------------------------------------------------------------------- ' Procedure : SaveObjectVBA ' Author : Adam Waller diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index b90ba8ee..9dbafb62 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -562,6 +562,13 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ End If End With End If + + ' Check for print settings file + strAltFile = SwapExtension(strFile, "json") + If FSO.FileExists(strAltFile) Then + ' Merge the print settings into the source file content + .MergePrintSettings ReadFile(strAltFile) + End If End Select ' Check UCS-2-LE requirement for the current database. From 1390f2ce304c25a40269c66d62d142dd6475510c Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 15:37:32 -0600 Subject: [PATCH 31/73] Move source reading function This is used in several areas, and allows us to maintain the source file encoding determination in a single location. --- .../modules/clsSourceParser.cls | 27 +------------- .../modules/modVCSUtility.bas | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index d158b225..a0a55af8 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -45,33 +45,8 @@ Private this As udtThis '--------------------------------------------------------------------------------------- ' Public Function LoadSourceFile(strPath As String) - - Dim strTempFile As String - ResetContent - - ' Read text from file, and split into lines - If HasUcs2Bom(strPath) Then - this.strInput = ReadFile(strPath, "Unicode") - Else - ' ADP projects may contain mixed Unicode content - If CurrentProject.ProjectType = acADP Then - strTempFile = GetTempFile - ConvertUcs2Utf8 strPath, strTempFile, False - this.strInput = ReadFile(strTempFile) - DeleteFile strTempFile - Else - If DbVersion <= 4 Then - ' Access 2000 format exports using system codepage - ' See issue #217 - this.strInput = ReadFile(strPath, GetSystemEncoding) - Else - ' Newer versions export as UTF-8 - this.strInput = ReadFile(strPath) - End If - End If - End If - + this.strInput = ReadSourceFile(strPath) End Function diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 9dbafb62..2974bbff 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -1027,3 +1027,39 @@ Public Function PassesSchemaFilter(strItem As String, varFilterArray As Variant) PassesSchemaFilter = blnPass End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : ReadSourceFile +' Author : Adam Waller +' Date : 11/8/2023 +' Purpose : Load source file content into a string. (Considers BOM and file type) +'--------------------------------------------------------------------------------------- +' +Public Function ReadSourceFile(strPath As String) As String + + Dim strTempFile As String + + ' Read text from file, and split into lines + If HasUcs2Bom(strPath) Then + ReadSourceFile = ReadFile(strPath, "Unicode") + Else + ' ADP projects may contain mixed Unicode content + If CurrentProject.ProjectType = acADP Then + strTempFile = GetTempFile + ConvertUcs2Utf8 strPath, strTempFile, False + ReadSourceFile = ReadFile(strTempFile) + DeleteFile strTempFile + Else + If DbVersion <= 4 Then + ' Access 2000 format exports using system codepage + ' See issue #217 + ReadSourceFile = ReadFile(strPath, GetSystemEncoding) + Else + ' Newer versions export as UTF-8 + ReadSourceFile = ReadFile(strPath) + End If + End If + End If + +End Function From 932ac842dd4cd9fc16a3034176292be6ca17d15b Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 15:41:33 -0600 Subject: [PATCH 32/73] Rework merging source content before import Cleaning this up to avoid reading and writing the file additional times while merging content from different sources. (Print settings, VBA code) --- .../modules/clsSourceParser.cls | 31 +++++++++ .../modules/modVCSUtility.bas | 63 +++++++++++-------- 2 files changed, 68 insertions(+), 26 deletions(-) diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index a0a55af8..391d1c9d 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -29,6 +29,7 @@ Private Type udtThis lngSkipLines() As Long ' Array of lines to skip lngSkipIndex As Long strFilePath As String ' Path to loaded file + blnOutputModified As Boolean strInput As String strOutput As String strVBA As String @@ -63,6 +64,30 @@ Public Function LoadString(ByVal strContent As String) End Function +'--------------------------------------------------------------------------------------- +' Procedure : GetOutput +' Author : Adam Waller +' Date : 11/8/2023 +' Purpose : Wrapper to return output string +'--------------------------------------------------------------------------------------- +' +Public Function GetOutput() As String + GetOutput = this.strOutput +End Function + + +'--------------------------------------------------------------------------------------- +' Procedure : OutputModified +' Author : Adam Waller +' Date : 11/8/2023 +' Purpose : Return true if the output has been modified +'--------------------------------------------------------------------------------------- +' +Public Property Get OutputModified() As Boolean + OutputModified = this.blnOutputModified +End Property + + '--------------------------------------------------------------------------------------- ' Procedure : ResetContent ' Author : Adam Waller @@ -76,6 +101,7 @@ Private Sub ResetContent() .strInput = vbNullString .strOutput = vbNullString .strVBA = vbNullString + .blnOutputModified = False End With Me.ObjectName = vbNullString End Sub @@ -110,6 +136,9 @@ Public Sub MergeVBA(strVbaCode As String) Dim varLines As Variant Dim lngLine As Long + ' Make sure we have some output + If Not Len(this.strOutput) Then this.strOutput = this.strInput + ' Rebuild output using provided VBA code With New clsConcat .AppendOnAdd = vbCrLf @@ -134,6 +163,8 @@ Public Sub MergeVBA(strVbaCode As String) ' Update output with combined content this.strOutput = .GetStr + this.strVBA = strVbaCode + this.blnOutputModified = True End With End Sub diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 2974bbff..641101cc 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -410,6 +410,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Dim strTempFile As String Dim strAltFile As String + Dim strContent As String Dim strPrefix As String Dim strPrintSettingsFile As String Dim strHash As String @@ -431,13 +432,18 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Set cParser = New clsSourceParser Select Case intType Case acForm, acReport + + ' Load content from file + strContent = ReadSourceFile(strTempFile) + + ' Process any saved devmode settings With New clsDevMode ' Build print settings file name. - strPrintSettingsFile = .GetPrintSettingsFileName(cDbObjectClass) + strPrintSettingsFile = SwapExtension(strFile, "json") ' See if we are exporting print vars. If Options.SavePrintVars = True Then ' Grab the printer settings before sanitizing the file. - .LoadFromExportFile strTempFile + .LoadFromExportFile strContent ' Only need to save print settings if they are different ' from the default printer settings. If (.GetHash <> VCSIndex.DefaultDevModeHash) And .HasData Then @@ -455,7 +461,7 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ ' Sanitizing converts to UTF-8 With cParser - .LoadSourceFile strTempFile + .LoadString strContent .ObjectName = FSO.GetBaseName(strFile) WriteFile .Sanitize(ectObjectDefinition), strFile strHash = .Hash @@ -533,35 +539,24 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ Optional cDbObjectClass As IDbComponent = Nothing) Dim strTempFile As String - Dim strPrintSettingsFile As String Dim strSourceFile As String + Dim strAltFile As String + Dim strContent As String Dim blnConvert As Boolean Dim dFile As Dictionary + Dim cParser As clsSourceParser - ' The path to the source file may change if we add print settings. + ' In most cases we are importing/converting the actual source file. strSourceFile = strFile - ' Add DevMode structures back into forms/reports + ' Add DevMode structures and VBA code back into forms/reports Select Case intType Case acForm, acReport - 'Insert print settings (if needed) - If Not (cDbObjectClass Is Nothing) Then - With New clsDevMode - ' Manually build the print settings file path since we don't have - ' a database object we can use with the clsDevMode.GetPrintSettingsFileName - strPrintSettingsFile = cDbObjectClass.BaseFolder & GetSafeFileName(strName) & ".json" - Set dFile = ReadJsonFile(strPrintSettingsFile) - ' Check to ensure dictionary was loaded - If Not (dFile Is Nothing) Then - ' Insert DevMode structures into file before importing. - ' Load default printer settings, then overlay - ' settings saved with report. - .ApplySettings dFile("Items") - ' Insert the settings into a combined export file. - strSourceFile = .AddToExportFile(strFile) - End If - End With - End If + + ' Read file content. (Should be UTF-8) + strContent = ReadFile(strFile) + With New clsSourceParser + .LoadString strContent ' Check for print settings file strAltFile = SwapExtension(strFile, "json") @@ -569,6 +564,22 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ ' Merge the print settings into the source file content .MergePrintSettings ReadFile(strAltFile) End If + + ' For forms and reports, check for VBA code file that needs to be merged + strAltFile = SwapExtension(strFile, "cls") + If FSO.FileExists(strAltFile) Then + ' Found a companion class file. + .MergeVBA ReadFile(strAltFile) + End If + + ' Write ouput to a new file if anything has changed + If .OutputModified Then + strSourceFile = GetTempFile + WriteFile .GetOutput, strSourceFile + End If + + End With + End Select ' Check UCS-2-LE requirement for the current database. @@ -604,8 +615,8 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ Perf.OperationEnd End If - ' Remove any temporary combined source file - If strSourceFile <> strFile Then DeleteFile strSourceFile + ' Clean up any temp file + If FSO.FileExists(strSourceFile) Then DeleteFile strSourceFile End Sub From 9069232968a7e3eb82a87abee2b9060cb4e3f63d Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 15:45:32 -0600 Subject: [PATCH 33/73] Add support to overlay VBA code after import For some (rare) situations, it is necessary to push the VBA code directly using VBE to preserve certain extended characters that may be corrupted in a regular round-trip export/import cycle. --- .../modules/clsSourceParser.cls | 58 +++++++++++++++ .../modules/modVCSUtility.bas | 74 +++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index 391d1c9d..c418160b 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -526,6 +526,64 @@ Private Function BuildOutput(varLines As Variant) As String End Function +'--------------------------------------------------------------------------------------- +' Procedure : StripClassHeader +' Author : Adam Waller +' Date : 10/24/2023 +' Purpose : Strip the class header section from the VBA content. (Remove the version +' : and VBE attributes lines that come before the actual VBA code.) +'--------------------------------------------------------------------------------------- +' +Public Function StripClassHeader(strContent As String, blnStripNameOnly As Boolean) As String + + Dim lngLine As Long + Dim varLines As Variant + Dim strLine As String + Dim blnPastHeader As Boolean + + ' Split code into lines + varLines = Split(strContent, vbCrLf) + + With New clsConcat + .AppendOnAdd = vbCrLf + + ' Skip the header information saved in the VBA class + For lngLine = 0 To UBound(varLines) + If Not blnPastHeader Then + strLine = varLines(lngLine) + If blnStripNameOnly Then + If StartsWith(strLine, "Attribute VB_Name = ") Then + ' Just skip that line. Keep everything else. + blnPastHeader = True + Else + .Add CStr(varLines(lngLine)) + End If + Else + Select Case True + Case (strLine = "VERSION 1.0 CLASS") + Case (strLine = "BEGIN") + Case (strLine = " MultiUse = -1 'True") + Case (strLine = "END") + Case StartsWith(strLine, "Attribute VB_") + Case Else + blnPastHeader = True + .Add CStr(varLines(lngLine)) + End Select + End If + Else + ' Add remaining lines + .Add CStr(varLines(lngLine)) + End If + Next lngLine + + ' remove trailing CrLf and return result + .Remove 2 + StripClassHeader = .GetStr + End With + +End Function + + '--------------------------------------------------------------------------------------- ' Procedure : SkipLine ' Author : Adam Waller diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 641101cc..179f19b6 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -542,6 +542,7 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ Dim strSourceFile As String Dim strAltFile As String Dim strContent As String + Dim blnVbaOverlay As Boolean Dim blnConvert As Boolean Dim dFile As Dictionary Dim cParser As clsSourceParser @@ -570,6 +571,7 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ If FSO.FileExists(strAltFile) Then ' Found a companion class file. .MergeVBA ReadFile(strAltFile) + blnVbaOverlay = RequiresOverlay(.GetObjectVBA) End If ' Write ouput to a new file if anything has changed @@ -618,6 +620,9 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ ' Clean up any temp file If FSO.FileExists(strSourceFile) Then DeleteFile strSourceFile + ' Check for VBA overlay + If blnVbaOverlay Then OverlayCodeModule strName, SwapExtension(strFile, "cls") + End Sub @@ -654,6 +659,75 @@ Public Sub ExportCodeModule(strName As String, strFile As String) End Sub +'--------------------------------------------------------------------------------------- +' Procedure : OverlayCodeModule +' Author : Adam Waller +' Date : 10/24/2023 +' Purpose : Overlay VBA code from an object's *.cls file to the form or report +' : Note that this opens the object in design view, which may slow the build +' : process if a large number of items are invovled. +'--------------------------------------------------------------------------------------- +' +Public Sub OverlayCodeModule(strName As String, strClassFile As String) + + Dim objModule As VBIDE.CodeModule + Dim strContent As String + Dim intType As AcObjectType + Dim strShortName As String + Dim cParser As clsSourceParser + + LogUnhandledErrors + 'On Error Resume Next + Set objModule = CurrentVBProject.VBComponents(strName).CodeModule + If CatchAny(eelError, "Could not find code module for " & strName, ModuleName & ".OverlayCodeModule") Then Exit Sub + + ' Read class file content + strContent = ReadFile(strClassFile) + If strContent = vbNullString Then + Log.Error eelError, "Unable to read " & strClassFile, ModuleName & ".OverlayCodeModule" + Exit Sub + End If + + ' Get object type and short name + If strName Like "Form_*" Then + intType = acForm + strShortName = Mid$(strName, 6) + DoCmd.OpenForm strShortName, acDesign, , , , acHidden + ElseIf strName Like "Report_*" Then + intType = acReport + strShortName = Mid$(strName, 8) + DoCmd.OpenReport strShortName, acViewDesign, , , acHidden + End If + + ' Overlay the VBA code, replacing any existing code. + Set cParser = New clsSourceParser + objModule.DeleteLines 1, objModule.CountOfLines + objModule.AddFromString cParser.StripClassHeader(strContent, False) + + ' Close any form or report object + Select Case intType + Case acForm, acReport + DoCmd.Close intType, strShortName, acSaveYes + End Select + +End Sub + + +'--------------------------------------------------------------------------------------- +' Procedure : RequiresOverlay +' Author : Adam Waller +' Date : 11/2/2023 +' Purpose : Returns true if we need to overlay the VBA code through VBE for a form +' : or report object. +'--------------------------------------------------------------------------------------- +' +Private Function RequiresOverlay(strVbaCode As String) As Boolean + If modEncoding.GetSystemEncoding(True) = "utf-8" Then + RequiresOverlay = StringHasExtendedASCII(strVbaCode) + End If +End Function + + '--------------------------------------------------------------------------------------- ' Procedure : RemoveNonBuiltInReferences ' Author : Adam Waller From 8f1649a30886f23eaa4b478b06b356d5a7ed1f83 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 16:18:47 -0600 Subject: [PATCH 34/73] Code cleanup and minor tweaks --- Version Control.accda.src/modules/clsDbForm.cls | 2 +- Version Control.accda.src/modules/clsDbModule.cls | 2 +- Version Control.accda.src/modules/clsDbReport.cls | 2 +- Version Control.accda.src/modules/clsDevMode.cls | 2 -- Version Control.accda.src/modules/clsGitIntegration.cls | 1 - Version Control.accda.src/modules/clsOptions.cls | 4 ---- Version Control.accda.src/modules/clsSourceParser.cls | 6 ++---- Version Control.accda.src/modules/clsSqlFormatter.cls | 1 + Version Control.accda.src/modules/modFunctions.bas | 4 ++-- Version Control.accda.src/modules/modInstall.bas | 4 ++-- Version Control.accda.src/modules/modVCSUtility.bas | 8 +------- 11 files changed, 11 insertions(+), 25 deletions(-) diff --git a/Version Control.accda.src/modules/clsDbForm.cls b/Version Control.accda.src/modules/clsDbForm.cls index 4e54ff3e..f3ca77c8 100644 --- a/Version Control.accda.src/modules/clsDbForm.cls +++ b/Version Control.accda.src/modules/clsDbForm.cls @@ -70,7 +70,7 @@ Private Sub IDbComponent_Import(strFile As String) End If ' Load the form from the source file - LoadComponentFromText acForm, strName, strFile, Me + LoadComponentFromText acForm, strName, strFile Set m_Form = CurrentProject.AllForms(strName) VCSIndex.Update Me, eatImport, GetCodeModuleHash(IDbComponent_ComponentType, strName) diff --git a/Version Control.accda.src/modules/clsDbModule.cls b/Version Control.accda.src/modules/clsDbModule.cls index 79c13336..e342a840 100644 --- a/Version Control.accda.src/modules/clsDbModule.cls +++ b/Version Control.accda.src/modules/clsDbModule.cls @@ -145,7 +145,7 @@ Private Function ParseSourceFile(strFile As String, strName As String) As udtVba blnIsClass = True Exit For End If - ' Exit after 10 lines + ' Exit after 9 lines If lngLine > 8 Then Exit For Next lngLine diff --git a/Version Control.accda.src/modules/clsDbReport.cls b/Version Control.accda.src/modules/clsDbReport.cls index 203ac99f..755c5539 100644 --- a/Version Control.accda.src/modules/clsDbReport.cls +++ b/Version Control.accda.src/modules/clsDbReport.cls @@ -58,7 +58,7 @@ Private Sub IDbComponent_Import(strFile As String) If Not strFile Like "*.bas" Then Exit Sub strName = GetObjectNameFromFileName(strFile) - LoadComponentFromText acReport, strName, strFile, Me + LoadComponentFromText acReport, strName, strFile Set m_Report = CurrentProject.AllReports(strName) VCSIndex.Update Me, eatImport, GetFileHash(strFile), GetCodeModuleHash(IDbComponent_ComponentType, strName) diff --git a/Version Control.accda.src/modules/clsDevMode.cls b/Version Control.accda.src/modules/clsDevMode.cls index c8b1f821..f51e763a 100644 --- a/Version Control.accda.src/modules/clsDevMode.cls +++ b/Version Control.accda.src/modules/clsDevMode.cls @@ -1012,10 +1012,8 @@ End Sub ' Public Function AddToExportFile(strFileContent As String) As String - Dim strTempFile As String Dim strLine As String Dim varLines As Variant - Dim strData As String Dim lngLine As Long Dim blnFound As Boolean Dim blnInBlock As Boolean diff --git a/Version Control.accda.src/modules/clsGitIntegration.cls b/Version Control.accda.src/modules/clsGitIntegration.cls index ace9583d..8b71d0cf 100644 --- a/Version Control.accda.src/modules/clsGitIntegration.cls +++ b/Version Control.accda.src/modules/clsGitIntegration.cls @@ -272,7 +272,6 @@ End Function '--------------------------------------------------------------------------------------- ' Public Function IsInsideRepository() As Boolean - Dim strResult As String If Me.GitInstalled Then IsInsideRepository = (RunGitCommand(egcIsInsideTree) = "true") End If diff --git a/Version Control.accda.src/modules/clsOptions.cls b/Version Control.accda.src/modules/clsOptions.cls index 3368b3e5..1247615e 100644 --- a/Version Control.accda.src/modules/clsOptions.cls +++ b/Version Control.accda.src/modules/clsOptions.cls @@ -275,9 +275,6 @@ End Sub ' Private Sub Upgrade(ByRef dOptions As Dictionary) - Dim dFiles As Dictionary - Dim strPath As String - ' 6/16/2021 ' Aggressive sanitize to sanitize levels If dOptions.Exists("AggressiveSanitize") Then @@ -327,7 +324,6 @@ End Sub Private Function HasUnifiedLayoutFilesInGit(strExportPath As String) As Boolean Dim blnHasFiles As Boolean - Dim strFolder As String ' See if we have any ".bas" files, but no corresponding ".cls" files in the ' forms and reports export folders. diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index c418160b..b1cfd187 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -222,8 +222,8 @@ End Function ' Purpose : Return a hash of the sanitized content '--------------------------------------------------------------------------------------- ' -Public Function Hash(Optional blnWithBOM As Boolean = True) As String - Hash = GetStringHash(this.strOutput, blnWithBOM) +Public Function Hash(Optional blnWithBom As Boolean = True) As String + Hash = GetStringHash(this.strOutput, blnWithBom) End Function @@ -246,8 +246,6 @@ Private Function SanitizeObject() As String 'strPath As String, blnReturnHash As Dim blnIsReport As Boolean Dim blnIsPassThroughQuery As Boolean Dim curStart As Currency - Dim strTempFile As String - Dim strContent As String Dim cVBA As clsConcat If DebugMode(True) Then On Error GoTo 0 Else On Error Resume Next diff --git a/Version Control.accda.src/modules/clsSqlFormatter.cls b/Version Control.accda.src/modules/clsSqlFormatter.cls index bdbe7866..ea683dba 100644 --- a/Version Control.accda.src/modules/clsSqlFormatter.cls +++ b/Version Control.accda.src/modules/clsSqlFormatter.cls @@ -1628,6 +1628,7 @@ End Function ' Author : Adam Waller ' Date : 8/18/2023 ' Purpose : Print out AscW indexes of each character in the string. +' : (Use this when building case statements) '--------------------------------------------------------------------------------------- ' Private Function GetAscWFromString(strText As String) As String diff --git a/Version Control.accda.src/modules/modFunctions.bas b/Version Control.accda.src/modules/modFunctions.bas index 2f20d2b6..a19e3e4a 100644 --- a/Version Control.accda.src/modules/modFunctions.bas +++ b/Version Control.accda.src/modules/modFunctions.bas @@ -910,10 +910,10 @@ End Function ' : I.e. c:\test.bas > c:\test.cls '--------------------------------------------------------------------------------------- ' -Public Function SwapExtension(strFilePath As String, strNewExtension As String) As String +Public Function SwapExtension(strFilePath As String, strNewExtensionWithoutDelimiter As String) As String Dim strCurrentExt As String strCurrentExt = FSO.GetExtensionName(strFilePath) - SwapExtension = Left(strFilePath, Len(strFilePath) - Len(strCurrentExt)) & strNewExtension + SwapExtension = Left(strFilePath, Len(strFilePath) - Len(strCurrentExt)) & strNewExtensionWithoutDelimiter End Function diff --git a/Version Control.accda.src/modules/modInstall.bas b/Version Control.accda.src/modules/modInstall.bas index f2a331bc..8945635f 100644 --- a/Version Control.accda.src/modules/modInstall.bas +++ b/Version Control.accda.src/modules/modInstall.bas @@ -113,7 +113,7 @@ Public Sub InstallVCSAddin(blnTrustFolder As Boolean, blnUseRibbon As Boolean, b .blnTrustAddInFolder = blnTrustFolder If .strInstallFolder <> strInstallFolder Then ' Attempt to migrate any saved user settings files - MigrateUserFiles .strInstallFolder, strInstallFolder, GetFilePathsInFolder(.strInstallFolder) + MigrateUserFiles strInstallFolder, GetFilePathsInFolder(.strInstallFolder) ' Update install folder to new path .strInstallFolder = strInstallFolder End If @@ -305,7 +305,7 @@ End Function ' : the source file. '--------------------------------------------------------------------------------------- ' -Private Sub MigrateUserFiles(strFromFolder As String, strToFolder As String, colNames As Dictionary) +Private Sub MigrateUserFiles(strToFolder As String, colNames As Dictionary) Dim varKey As Variant Dim strFile As String diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 179f19b6..cc09512f 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -411,7 +411,6 @@ Public Function SaveComponentAsText(intType As AcObjectType, _ Dim strTempFile As String Dim strAltFile As String Dim strContent As String - Dim strPrefix As String Dim strPrintSettingsFile As String Dim strHash As String Dim cParser As clsSourceParser @@ -533,10 +532,7 @@ End Function ' Purpose : Load the object into the database from the saved source file. '--------------------------------------------------------------------------------------- ' -Public Sub LoadComponentFromText(intType As AcObjectType, _ - strName As String, _ - strFile As String, _ - Optional cDbObjectClass As IDbComponent = Nothing) +Public Sub LoadComponentFromText(intType As AcObjectType, strName As String, strFile As String) Dim strTempFile As String Dim strSourceFile As String @@ -544,8 +540,6 @@ Public Sub LoadComponentFromText(intType As AcObjectType, _ Dim strContent As String Dim blnVbaOverlay As Boolean Dim blnConvert As Boolean - Dim dFile As Dictionary - Dim cParser As clsSourceParser ' In most cases we are importing/converting the actual source file. strSourceFile = strFile From 4f9247de70f04466509ac7f6dd9cd5657b09d09a Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 17:02:50 -0600 Subject: [PATCH 35/73] Fix bugs in build logic Uncovered these while testing. --- Version Control.accda.src/modules/clsDevMode.cls | 2 +- Version Control.accda.src/modules/clsSourceParser.cls | 4 ++-- Version Control.accda.src/modules/modVCSUtility.bas | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Version Control.accda.src/modules/clsDevMode.cls b/Version Control.accda.src/modules/clsDevMode.cls index f51e763a..d1438712 100644 --- a/Version Control.accda.src/modules/clsDevMode.cls +++ b/Version Control.accda.src/modules/clsDevMode.cls @@ -240,7 +240,7 @@ Public Sub LoadFromExportFile(strFileContent As String) ' Clear existing structures and create block classes. ClearStructures - If Not Len(strFileContent) Then Exit Sub + If Len(strFileContent) = 0 Then Exit Sub ' Read the text file line by line, loading the block data Perf.OperationStart "Read File DevMode" diff --git a/Version Control.accda.src/modules/clsSourceParser.cls b/Version Control.accda.src/modules/clsSourceParser.cls index b1cfd187..1a1e3e26 100644 --- a/Version Control.accda.src/modules/clsSourceParser.cls +++ b/Version Control.accda.src/modules/clsSourceParser.cls @@ -137,7 +137,7 @@ Public Sub MergeVBA(strVbaCode As String) Dim lngLine As Long ' Make sure we have some output - If Not Len(this.strOutput) Then this.strOutput = this.strInput + If Len(this.strOutput) = 0 Then this.strOutput = this.strInput ' Rebuild output using provided VBA code With New clsConcat @@ -182,7 +182,7 @@ Public Sub MergePrintSettings(strJson As String) Dim dSettings As Dictionary ' Make sure we have some output - If Not Len(this.strOutput) Then this.strOutput = this.strInput + If Len(this.strOutput) = 0 Then this.strOutput = this.strInput ' Don't try to parse an empty string If strJson = vbNullString Then Exit Sub diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index cc09512f..21bf7f7d 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -611,8 +611,10 @@ Public Sub LoadComponentFromText(intType As AcObjectType, strName As String, str Perf.OperationEnd End If - ' Clean up any temp file - If FSO.FileExists(strSourceFile) Then DeleteFile strSourceFile + ' Clean up any additional temp file used in the building process + If strFile <> strSourceFile Then + If FSO.FileExists(strSourceFile) Then DeleteFile strSourceFile + End If ' Check for VBA overlay If blnVbaOverlay Then OverlayCodeModule strName, SwapExtension(strFile, "cls") From 925b019806cd3d2f55d575913ed4e3ab35dc61ae Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Wed, 8 Nov 2023 17:04:08 -0600 Subject: [PATCH 36/73] Check for diff tool before comparing objects --- .../forms/frmVCSConflictList.bas | 13 +++++++++---- Version Control.accda.src/modules/clsViewDiff.cls | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSConflictList.bas b/Version Control.accda.src/forms/frmVCSConflictList.bas index 8fce9846..c530c465 100644 --- a/Version Control.accda.src/forms/frmVCSConflictList.bas +++ b/Version Control.accda.src/forms/frmVCSConflictList.bas @@ -16,10 +16,8 @@ Begin Form Width =5040 DatasheetFontHeight =11 ItemSuffix =31 - Left =435 - Top =2250 - Right =12315 - Bottom =7335 + Right =15720 + Bottom =11745 RecSrcDt = Begin 0x9bf1b7f2f3a6e540 End @@ -481,6 +479,13 @@ Private Sub txtDiff_Click() cboResolution.SetFocus DoEvents + ' Make sure we have a valid tool defined + If Not (modObjects.Diff.HasValidCompareTool) Then + MsgBox2 "No Compare Tool Defined", _ + "Please specify a compare tool (i.e. WinMerge, VSCode) in the add-in options.", , vbExclamation + Exit Sub + End If + ' Make sure we have a file name to compare strFileName = Nz(txtFileName) If strFileName = vbNullString Then diff --git a/Version Control.accda.src/modules/clsViewDiff.cls b/Version Control.accda.src/modules/clsViewDiff.cls index b895c808..72579031 100644 --- a/Version Control.accda.src/modules/clsViewDiff.cls +++ b/Version Control.accda.src/modules/clsViewDiff.cls @@ -219,7 +219,7 @@ End Function ' : valid for use. '--------------------------------------------------------------------------------------- ' -Private Function HasValidCompareTool() As Boolean +Public Function HasValidCompareTool() As Boolean ' Check the current option for compare tool. Select Case Me.ToolName From 7b8be8faef5984c3cccc4c88a0c11e3cf7ce5995 Mon Sep 17 00:00:00 2001 From: Tanarri Date: Fri, 10 Nov 2023 15:24:54 +0100 Subject: [PATCH 37/73] Implement correction according to rubberduck (#453) replace VBA commands: format with format$ trim with trim$ --- Version Control.accda.src/modules/clsPerformance.cls | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Version Control.accda.src/modules/clsPerformance.cls b/Version Control.accda.src/modules/clsPerformance.cls index bf141388..fa5f62dc 100644 --- a/Version Control.accda.src/modules/clsPerformance.cls +++ b/Version Control.accda.src/modules/clsPerformance.cls @@ -447,14 +447,14 @@ Public Function GetReports() As String .Add ListResult("Category", "Count", "Seconds", lngCol), vbCrLf, strSpacer For Each varKey In this.Categories.Keys .Add ListResult(CStr(varKey), CStr(this.Categories(varKey).Count), _ - Format(this.Categories(varKey).Total, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) + Format$(this.Categories(varKey).Total, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) ' Add to totals dblCount = dblCount + this.Categories(varKey).Count curTotal = curTotal + this.Categories(varKey).Total Next varKey .Add strSpacer .Add ListResult("TOTALS:", CStr(dblCount), _ - Format(curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) + Format$(curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) .Add strSpacer .Add vbNullString End If @@ -467,13 +467,13 @@ Public Function GetReports() As String .Add ListResult("Operations", "Count", "Seconds", lngCol), vbCrLf, strSpacer For Each varKey In this.Operations.Keys .Add ListResult(CStr(varKey), CStr(this.Operations(varKey).Count), _ - Format(this.Operations(varKey).Total, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) + Format$(this.Operations(varKey).Total, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) curTotal = curTotal + this.Operations(varKey).Total Next varKey .Add strSpacer If Not this.Overall Is Nothing Then .Add ListResult("Other Operations", vbNullString, _ - Format(this.Overall.Total - curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) + Format$(this.Overall.Total - curTotal, "0." & String$(this.intDigitsAfterDecimal, "0")), lngCol) .Add strSpacer End If .Add vbNullString @@ -608,7 +608,7 @@ Private Function SortItemsByTime(dItems As Dictionary) As Dictionary ' Build our list of records For Each varKey In dItems.Keys ' Create a record like this: "00062840.170000|Export Form Objects ..." - strRecord = Format(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 255) + strRecord = Format$(dItems(varKey).Total, "00000000.000000") & "|" & PadRight(CStr(varKey), 255) ' Add to array. varItems(lngCnt) = strRecord ' Increment counter for array @@ -624,7 +624,7 @@ Private Function SortItemsByTime(dItems As Dictionary) As Dictionary For lngCnt = dItems.Count - 1 To 0 Step -1 ' Parse key from record (text after first pipe character) strRecord = varItems(lngCnt) - varKey = Trim(Mid$(strRecord, InStr(1, strRecord, "|") + 1)) + varKey = Trim$(Mid$(strRecord, InStr(1, strRecord, "|") + 1)) ' Reference performance item class Set cItem = dItems(varKey) ' Add to dictionary of resorted items From 9577eab41ed214a9ebf5425a2931f4301d8b147a Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 13 Nov 2023 15:08:11 -0600 Subject: [PATCH 38/73] Add wiki page for Split Files Describes the process in a little more detail. --- Wiki/Split-Files.md | 29 +++++++++++++++++++++++++++++ Wiki/img/split-files-dialog.jpg | Bin 0 -> 46257 bytes 2 files changed, 29 insertions(+) create mode 100644 Wiki/Split-Files.md create mode 100644 Wiki/img/split-files-dialog.jpg diff --git a/Wiki/Split-Files.md b/Wiki/Split-Files.md new file mode 100644 index 00000000..211085d1 --- /dev/null +++ b/Wiki/Split-Files.md @@ -0,0 +1,29 @@ +One of the challenges with the .git system is that when a single file is split into two different files the line history *might* follow one of the files, but not both. You loose the line history in one or both files. + +That might be a serious problem if you have years of history that you are wanting to preserve. Thankfully, there is a [technical workaround](https://devblogs.microsoft.com/oldnewthing/20190919-00/?p=102904) that does allow you to preserve the history in both files. + +# Please Read Before Splitting Files in Existing .git Projects + +You can turn on the option and simply perform an export to split the files, **but if you want to preserve the .git history in both files**, please read this section carefully. This add-in includes a utility to help you split the files while retaining the line history in both files. Because this involves **committing** to the repository as part of the process, I want to clarify exactly how this works. + +# How This Works + +Because this isn't a built-in feature in .git, we need to implement a bit of a clever workaround as documented by Raymond Chen in [this article](https://devblogs.microsoft.com/oldnewthing/20190919-00/?p=102904). In a nutshell, we will create a (temporary) new branch in git, rename the file in the new branch, restore the original file, then merge the new branch back into the original. This will result in two files that both carry the history of the original file. + +# Before You Start + +You will need to run this process from a **clean branch**. If you have any outstanding changes, please commit or discard them before splitting files. Be aware that this will create **two additional commits** in your repository, so it can be helpful to do this for batches of files, rather than individually for each file. + +# Select and Split Layout from VBA + +On the VCS ribbon, click Open the **Advanced Tools > Split Files** to open the following dialog. + +![split-files-dialog](img/split-files-dialog.jpg) + +Click the *Add Forms and Reports...* link to automatically load in the source files for the forms and reports in the current project and create the corresponding `*.cls` files. + +Click the button to Split Files. This will begin the automated process of splitting the files while preserving change history. Details and the full output of the git commands can be found in `git.log` in the source files folder. + +With the files now split, turn on the VCS option to **Split Layout from VBA** and run an export. At this point you should see both source files modified for each object as the layout is removed from the class file, and the VBA is removed from the layout file. + +That's it! Now the VBA code changes can be tracked in the `*.cls` file, and the layout in the `*.bas` file. \ No newline at end of file diff --git a/Wiki/img/split-files-dialog.jpg b/Wiki/img/split-files-dialog.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6884a8143d6d29dbc038b4e97afa0791a65e0b5 GIT binary patch literal 46257 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 XGyY9+{{2Loe^y}Xzxg@EZ*%_xy^DF0 literal 0 HcmV?d00001 From 07bc57ccaac96960bcc67cfbfa331d5502906f21 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 13 Nov 2023 15:48:48 -0600 Subject: [PATCH 39/73] Add change hook for options Used for special processing when certain options change. --- .../forms/frmVCSOptions.bas | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSOptions.bas b/Version Control.accda.src/forms/frmVCSOptions.bas index df1e3ac2..08870769 100644 --- a/Version Control.accda.src/forms/frmVCSOptions.bas +++ b/Version Control.accda.src/forms/frmVCSOptions.bas @@ -16,10 +16,10 @@ Begin Form Width =10080 DatasheetFontHeight =11 ItemSuffix =252 - Left =3225 - Top =2430 - Right =13890 - Bottom =14175 + Left =-25575 + Top =1500 + Right =-255 + Bottom =14085 RecSrcDt = Begin 0x79e78b777268e540 End @@ -4296,6 +4296,9 @@ Private Sub MapControlsToOptions(eAction As eMapAction) If eAction = emaClassToForm Then ctl = CallByName(Options, strKey, VbGet) ElseIf eAction = emaFormToClass Then + ' Check for any hooks on option change + OnOptionChange strKey, Nz(ctl.Value) + ' Set the option value CallByName Options, strKey, VbLet, Nz(ctl.Value) End If End Select @@ -4328,6 +4331,46 @@ Private Sub MapControlsToOptions(eAction As eMapAction) End Sub +'--------------------------------------------------------------------------------------- +' Procedure : OnOptionChange +' Author : Adam Waller +' Date : 11/9/2023 +' Purpose : A hook to run special code or processing when specific options are changed +' : from their existing values. Add any specific rules here. +'--------------------------------------------------------------------------------------- +' +Private Sub OnOptionChange(strName As String, varNewValue As Variant) + + Dim blnChanged As Boolean + + ' Determine if the option was changed + blnChanged = Not (CVar(CallByName(Options, strName, VbGet)) = varNewValue) + If Not blnChanged Then Exit Sub + + ' Define actual rules here + Select Case strName + + ' If a user turns on the option to split files + Case "SplitLayoutFromVBA" + If varNewValue = True Then + If Git.Installed Then + If Git.IsInsideRepository Then + ' Prompt user with suggestion + If MsgBox2("May I make a Suggestion?", _ + "This project appears to be within a Git repository. This add-in includes a special utility " & _ + "that can split the files (layout and VBA) while preserving this history of previous changes in BOTH files.", _ + "Would you like to see additional information on this from the wiki?", vbQuestion + vbYesNo) = vbYes Then + FollowHyperlink "https://github.com/joyfullservice/msaccess-vcs-addin/wiki/Split-Files" + End If + End If + End If + End If + + End Select + +End Sub + + '--------------------------------------------------------------------------------------- ' Procedure : cmdAddOtherTableData_Click ' Author : Adam Waller From e2049faaa48903cdbef98ebd1af4f00efbb2837a Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 13 Nov 2023 15:49:49 -0600 Subject: [PATCH 40/73] Automate splitting forms and reports Adds a link and some code automation to split forms and reports in existing projects to layout and class files. --- .../forms/frmVCSSplitFiles.bas | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSSplitFiles.bas b/Version Control.accda.src/forms/frmVCSSplitFiles.bas index d26fe877..9958cbb7 100644 --- a/Version Control.accda.src/forms/frmVCSSplitFiles.bas +++ b/Version Control.accda.src/forms/frmVCSSplitFiles.bas @@ -18,7 +18,7 @@ Begin Form ItemSuffix =245 Left =-25575 Top =1500 - Right =-5310 + Right =-255 Bottom =14085 RecSrcDt = Begin 0x79e78b777268e540 @@ -276,13 +276,13 @@ Begin Form OverlapFlags =85 Left =720 Top =1440 - Width =4020 + Width =2280 Height =315 Name ="Label241" Caption ="List of files to split:" LayoutCachedLeft =720 LayoutCachedTop =1440 - LayoutCachedWidth =4740 + LayoutCachedWidth =3000 LayoutCachedHeight =1755 End End @@ -866,6 +866,43 @@ Begin Form End End End + Begin CommandButton + FontUnderline = NotDefault + OverlapFlags =85 + Left =4020 + Top =1440 + Width =2820 + TabIndex =3 + Name ="cmdAddFormsAndReports" + Caption ="Add Forms and Reports..." + OnClick ="[Event Procedure]" + HorizontalAnchor =1 + BackStyle =0 + + CursorOnHover =1 + LayoutCachedLeft =4020 + LayoutCachedTop =1440 + LayoutCachedWidth =6840 + LayoutCachedHeight =1800 + Alignment =3 + ForeThemeColorIndex =10 + ForeTint =100.0 + Gradient =0 + BackColor =14262935 + BackThemeColorIndex =-1 + BackTint =100.0 + OldBorderStyle =0 + BorderColor =15321539 + BorderThemeColorIndex =-1 + BorderTint =100.0 + HoverColor =15321539 + HoverThemeColorIndex =-1 + HoverTint =100.0 + PressedColor =13072231 + PressedThemeColorIndex =-1 + PressedShade =100.0 + Overlaps =1 + End End End End @@ -879,6 +916,69 @@ Option Compare Database Option Explicit +'--------------------------------------------------------------------------------------- +' Procedure : cmdAddFormsAndReports_Click +' Author : Adam Waller +' Date : 11/9/2023 +' Purpose : Add the forms and reports source files for the project. Doing this +' : intelligently by only adding items that have a VBA code module. +'--------------------------------------------------------------------------------------- +' +Private Sub cmdAddFormsAndReports_Click() + + Dim intType As AcObjectType + Dim cComponent As IDbComponent + Dim varKey As Variant + Dim strFile As String + Dim strPrefix As String + Dim cList As clsConcat + + ' Prepare class for new list + Set cList = New clsConcat + cList.AppendOnAdd = vbCrLf + + ' Process for forms and reports (2 to 3) + DoCmd.Hourglass True + For intType = acForm To acReport + + ' Get component type + If intType = acForm Then + Set cComponent = New clsDbForm + strPrefix = "Form_" + ElseIf intType = acReport Then + Set cComponent = New clsDbReport + strPrefix = "Report_" + End If + + ' Loop through files + For Each varKey In cComponent.GetFileList.Keys + strFile = SwapExtension(CStr(varKey), "cls") + ' Skip files that already exist + If Not FSO.FileExists(strFile) Then + ' Check for code module marker in source file + If InStr(1, ReadFile(CStr(varKey)), "CodeBehindForm") > 0 Then + ' Add to list of files to split + cList.Add CStr(varKey), "|", strFile + End If + End If + Next varKey + Next intType + DoCmd.Hourglass False + cmdSplitFiles.SetFocus + + ' See if we found any files to split. + If cList.Length > 0 Then + ' Replace existing content. + txtFileList = cList.GetStr + Else + MsgBox2 "No Relevant Files Found", _ + "Could not find any combined form or report source files that contained VBA modules", _ + , vbInformation + End If + +End Sub + + '--------------------------------------------------------------------------------------- ' Procedure : cmdSplitFiles_Click ' Author : Adam Waller From e723123437405db5c5321071b4ae10d13fe41325 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Mon, 13 Nov 2023 15:51:10 -0600 Subject: [PATCH 41/73] Rename function Git.Installed sounds better than Git.GitInstalled, and will almost always be called in the context of the git class. --- Version Control.accda.src/forms/frmVCSSplitFiles.bas | 4 ++-- Version Control.accda.src/modules/clsGitIntegration.cls | 8 ++++---- Version Control.accda.src/modules/clsOptions.cls | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSSplitFiles.bas b/Version Control.accda.src/forms/frmVCSSplitFiles.bas index 9958cbb7..a73b3411 100644 --- a/Version Control.accda.src/forms/frmVCSSplitFiles.bas +++ b/Version Control.accda.src/forms/frmVCSSplitFiles.bas @@ -1012,7 +1012,7 @@ Private Sub cmdSplitFiles_Click() AddToArray strPaths, varPaths(0) AddToArray strNew, varPaths(1) Else - If UBound(varPaths) = 0 And Trim(varPaths(0)) = vbNullString Then + If Len(Trim(varEntries(lngLine))) = 0 Then ' Ignore blank lines Else strError = "Expecting two file paths, separated by | character. See line: '" & varPaths(0) & "'" @@ -1033,7 +1033,7 @@ Private Sub cmdSplitFiles_Click() ' Require clean branch with git installation If Not Git.IsCleanBranch Then strError = "Cannot split files in Git when changes are present in the branch" - If Not Git.GitInstalled Then strError = "Git must be installed to use this tool." + If Not Git.Installed Then strError = "Git must be installed to use this tool." ' Make sure we don't have any errors with the Git commands If Len(strError) Then diff --git a/Version Control.accda.src/modules/clsGitIntegration.cls b/Version Control.accda.src/modules/clsGitIntegration.cls index 8b71d0cf..e30e355b 100644 --- a/Version Control.accda.src/modules/clsGitIntegration.cls +++ b/Version Control.accda.src/modules/clsGitIntegration.cls @@ -183,7 +183,7 @@ Public Function GetRepositoryRoot(Optional blnFallBackToWorking As Boolean = Tru strWorking = GetWorkingFolder ' Make sure git is actually installed - If Not Me.GitInstalled Then + If Not Me.Installed Then If blnFallBackToWorking Then GetRepositoryRoot = strWorking Exit Function End If @@ -272,7 +272,7 @@ End Function '--------------------------------------------------------------------------------------- ' Public Function IsInsideRepository() As Boolean - If Me.GitInstalled Then + If Me.Installed Then IsInsideRepository = (RunGitCommand(egcIsInsideTree) = "true") End If End Function @@ -524,8 +524,8 @@ End Function ' Purpose : Returns true if git is installed. '--------------------------------------------------------------------------------------- ' -Public Function GitInstalled() As Boolean - GitInstalled = (Len(Me.Version)) +Public Function Installed() As Boolean + Installed = (Len(Me.Version)) End Function diff --git a/Version Control.accda.src/modules/clsOptions.cls b/Version Control.accda.src/modules/clsOptions.cls index 1247615e..9769b4f8 100644 --- a/Version Control.accda.src/modules/clsOptions.cls +++ b/Version Control.accda.src/modules/clsOptions.cls @@ -336,7 +336,7 @@ Private Function HasUnifiedLayoutFilesInGit(strExportPath As String) As Boolean If blnHasFiles Then ' Check to see if this folder is in a git repository - If Git.GitInstalled Then + If Git.Installed Then ' Check export path Git.WorkingFolder = strExportPath HasUnifiedLayoutFilesInGit = Git.IsInsideRepository From de4602e2e05cf39c16bfadc85e619765cf1e17ce Mon Sep 17 00:00:00 2001 From: hecon5 <54177882+hecon5@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:56:40 -0500 Subject: [PATCH 42/73] Fixes #354 and Fixes #452 (#454) From @hecon5: Bump version minor number because it's not clear that the index will allow round trip from prior types in all cases; it worked on my machine, but that may not always be the case. The date types for the index are handled natively by modJsonConverter and should import/export correctly regardless of user's date / time zone or date encoding on machines. --- Version Control.accda.src/dbs-properties.json | 2 +- .../modules/clsOptions.cls | 1 + .../modules/clsVCSIndex.cls | 10 +- .../modules/modJsonConverter.bas | 283 ++---------------- .../modules/modUtcConverter.bas | 31 +- 5 files changed, 57 insertions(+), 270 deletions(-) diff --git a/Version Control.accda.src/dbs-properties.json b/Version Control.accda.src/dbs-properties.json index a4853bdb..58b40237 100644 --- a/Version Control.accda.src/dbs-properties.json +++ b/Version Control.accda.src/dbs-properties.json @@ -41,7 +41,7 @@ "Type": 10 }, "AppVersion": { - "Value": "4.0.25", + "Value": "4.1.0", "Type": 10 }, "Auto Compact": { diff --git a/Version Control.accda.src/modules/clsOptions.cls b/Version Control.accda.src/modules/clsOptions.cls index 9769b4f8..915d11ac 100644 --- a/Version Control.accda.src/modules/clsOptions.cls +++ b/Version Control.accda.src/modules/clsOptions.cls @@ -654,6 +654,7 @@ Private Sub Class_Initialize() ' Other run-time options JsonOptions.AllowUnicodeChars = True + JsonOptions.ConvertDateToIso = True End Sub diff --git a/Version Control.accda.src/modules/clsVCSIndex.cls b/Version Control.accda.src/modules/clsVCSIndex.cls index 837a3ce1..396e42e6 100644 --- a/Version Control.accda.src/modules/clsVCSIndex.cls +++ b/Version Control.accda.src/modules/clsVCSIndex.cls @@ -118,7 +118,7 @@ Public Sub Save(Optional strInFolder As String) varValue = CallByName(Me, CStr(varKey), VbGet) ' Save blank dates as null If Right(varKey, 4) = "Date" Then - m_dIndex(varKey) = ZNDate(CStr(varValue)) + m_dIndex(varKey) = ZNDate(varValue) Else m_dIndex(varKey) = CStr(varValue) End If @@ -222,14 +222,14 @@ Public Function Update(cItem As IDbComponent, intAction As eIndexOperationType, If dteDateTime = 0 Then dteDateTime = Now Select Case intAction Case eatExport, eatAltExport - .Item("ExportDate") = CStr(dteDateTime) + .Item("ExportDate") = dteDateTime Case eatImport - .Item("ImportDate") = CStr(dteDateTime) + .Item("ImportDate") = dteDateTime End Select ' Save timestamp of exported source file. dteDateTime = GetLastModifiedDate(cItem.SourceFile) - .Item("SourceModified") = ZNDate(CStr(dteDateTime)) + .Item("SourceModified") = ZNDate(dteDateTime) ' Save hash of file properties .Item("FilePropertiesHash") = GetFilePropertyHash(cItem.SourceFile) @@ -494,7 +494,7 @@ Public Function GetModifiedSourceFiles(cCategory As IDbComponent) As Dictionary strPath = Join(Array("Components", cCategory.Category, FSO.GetFileName(strFile), "SourceModified"), PathSep) ' Compare modified date of file with modified date in index. ' File is considered not modified if the index date matches the file modification date. - blnModified = Not (dNZ(m_dIndex, strPath) = CStr(GetLastModifiedDate(strFile))) + blnModified = Not (dNZ(m_dIndex, strPath) = GetLastModifiedDate(strFile)) End If ' Add modified files to collection If blnModified Then .Add strFile, vbNullString diff --git a/Version Control.accda.src/modules/modJsonConverter.bas b/Version Control.accda.src/modules/modJsonConverter.bas index 1fe4fba9..d7c0ea7b 100644 --- a/Version Control.accda.src/modules/modJsonConverter.bas +++ b/Version Control.accda.src/modules/modJsonConverter.bas @@ -157,6 +157,10 @@ Private Type json_Options ' The solidus (/) is not required to be escaped, use this option to escape them as \/ in ConvertToJson EscapeSolidus As Boolean + ' Before version 2.3.1 dates were converted to UTC in ConvertToJson method, but not when json was parsed. + ' Convert datetime values to UTC/ISO8601 (false, slower) or dont change local <-> global times (true, faster) + ConvertDateToIso As Boolean + ' Allow Unicode characters in JSON text. Set to True to use native Unicode or false for escaped values. AllowUnicodeChars As Boolean End Type @@ -240,11 +244,16 @@ Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitesp Select Case VBA.VarType(JsonValue) Case VBA.vbNull ConvertToJson = "null" + Case VBA.vbDate ' Date - json_DateStr = ConvertToIso(VBA.CDate(JsonValue)) - + If JsonOptions.ConvertDateToIso Then + json_DateStr = ConvertToIsoTime(VBA.CDate(JsonValue)) + Else + json_DateStr = VBA.CStr(JsonValue) + End If ConvertToJson = """" & json_DateStr & """" + Case VBA.vbString ' String (or large number encoded as string) If Not JsonOptions.UseDoubleForLargeNumbers And json_StringIsLargeNumber(JsonValue) Then @@ -556,7 +565,9 @@ Private Function json_ParseValue(json_String As String, ByRef json_Index As Long End Select End Function -Private Function json_ParseString(json_String As String, ByRef json_Index As Long) As String +Private Function json_ParseString(ByRef json_String As String _ + , ByRef json_Index As Long) As Variant + Dim json_Quote As String Dim json_Char As String Dim json_Code As String @@ -609,6 +620,10 @@ Private Function json_ParseString(json_String As String, ByRef json_Index As Lon End Select Case json_Quote json_ParseString = json_BufferToString(json_Buffer, json_BufferPosition) + If JsonOptions.ConvertDateToIso Then ' Only convert and test for condition if needed for speed boost. + If (json_ParseString Like "####-##-##T##:##:##*") Then _ + json_ParseString = ParseIso(VBA.CStr$(json_ParseString)) ' Return as a date + End If json_Index = json_Index + 1 Exit Function Case Else @@ -881,265 +896,7 @@ Private Function json_BufferToString(ByRef json_Buffer As String, ByVal json_Buf End If End Function -'' -' VBA-UTC v1.0.6 -' (c) Tim Hall - https://github.com/VBA-tools/VBA-UtcConverter -' -' UTC/ISO 8601 Converter for VBA -' -' Errors: -' 10011 - UTC parsing error -' 10012 - UTC conversion error -' 10013 - ISO 8601 parsing error -' 10014 - ISO 8601 conversion error -' -' @module UtcConverter -' @author tim.hall.engr@gmail.com -' @license MIT (http://www.opensource.org/licenses/mit-license.php) -'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' - -' (Declarations moved to top) - -' ============================================= ' -' Public Methods -' ============================================= ' - -'' -' Parse UTC date to local date -' -' @method ParseUtc -' @param {Date} UtcDate -' @return {Date} Local date -' @throws 10011 - UTC parsing error -'' -Public Function ParseUtc(utc_UtcDate As Date) As Date - On Error GoTo utc_ErrorHandling - -#If Mac Then - ParseUtc = utc_ConvertDate(utc_UtcDate) -#Else - Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION - Dim utc_LocalDate As utc_SYSTEMTIME - - utc_GetTimeZoneInformation utc_TimeZoneInfo - utc_SystemTimeToTzSpecificLocalTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_UtcDate), utc_LocalDate - - ParseUtc = utc_SystemTimeToDate(utc_LocalDate) -#End If - - Exit Function - -utc_ErrorHandling: - Err.Raise 10011, "UtcConverter.ParseUtc", "UTC parsing error: " & Err.Number & " - " & Err.Description -End Function - -'' -' Convert local date to UTC date -' -' @method ConvertToUrc -' @param {Date} utc_LocalDate -' @return {Date} UTC date -' @throws 10012 - UTC conversion error -'' -Public Function ConvertToUtc(utc_LocalDate As Date) As Date - On Error GoTo utc_ErrorHandling - -#If Mac Then - ConvertToUtc = utc_ConvertDate(utc_LocalDate, utc_ConvertToUtc:=True) -#Else - Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION - Dim utc_UtcDate As utc_SYSTEMTIME - utc_GetTimeZoneInformation utc_TimeZoneInfo - utc_TzSpecificLocalTimeToSystemTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_LocalDate), utc_UtcDate - - ConvertToUtc = utc_SystemTimeToDate(utc_UtcDate) -#End If - - Exit Function - -utc_ErrorHandling: - Err.Raise 10012, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description +Private Function ConvertToIso(utc_LocalDate As Date) As String + ConvertToIso = ConvertToUTCISO8601TimeStamp(utc_LocalDate) End Function - -'' -' Parse ISO 8601 date string to local date -' -' @method ParseIso -' @param {Date} utc_IsoString -' @return {Date} Local date -' @throws 10013 - ISO 8601 parsing error -'' -Public Function ParseIso(utc_IsoString As String) As Date - On Error GoTo utc_ErrorHandling - - Dim utc_Parts() As String - Dim utc_DateParts() As String - Dim utc_TimeParts() As String - Dim utc_OffsetIndex As Long - Dim utc_HasOffset As Boolean - Dim utc_NegativeOffset As Boolean - Dim utc_OffsetParts() As String - Dim utc_Offset As Date - - utc_Parts = VBA.Split(utc_IsoString, "T") - utc_DateParts = VBA.Split(utc_Parts(0), "-") - ParseIso = VBA.DateSerial(VBA.CInt(utc_DateParts(0)), VBA.CInt(utc_DateParts(1)), VBA.CInt(utc_DateParts(2))) - - If UBound(utc_Parts) > 0 Then - If VBA.InStr(utc_Parts(1), "Z") Then - utc_TimeParts = VBA.Split(VBA.Replace(utc_Parts(1), "Z", vbNullString), ":") - Else - utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "+") - If utc_OffsetIndex = 0 Then - utc_NegativeOffset = True - utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "-") - End If - - If utc_OffsetIndex > 0 Then - utc_HasOffset = True - utc_TimeParts = VBA.Split(VBA.Left$(utc_Parts(1), utc_OffsetIndex - 1), ":") - utc_OffsetParts = VBA.Split(VBA.Right$(utc_Parts(1), Len(utc_Parts(1)) - utc_OffsetIndex), ":") - - Select Case UBound(utc_OffsetParts) - Case 0 - utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), 0, 0) - Case 1 - utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), 0) - Case 2 - ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues - utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), Int(VBA.Val(utc_OffsetParts(2)))) - End Select - - If utc_NegativeOffset Then: utc_Offset = -utc_Offset - Else - utc_TimeParts = VBA.Split(utc_Parts(1), ":") - End If - End If - - Select Case UBound(utc_TimeParts) - Case 0 - ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), 0, 0) - Case 1 - ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), 0) - Case 2 - ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues - ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), Int(VBA.Val(utc_TimeParts(2)))) - End Select - - ParseIso = ParseUtc(ParseIso) - - If utc_HasOffset Then - ParseIso = ParseIso - utc_Offset - End If - End If - - Exit Function - -utc_ErrorHandling: - Err.Raise 10013, "UtcConverter.ParseIso", "ISO 8601 parsing error for " & utc_IsoString & ": " & Err.Number & " - " & Err.Description -End Function - -'' -' Convert local date to ISO 8601 string -' -' @method ConvertToIso -' @param {Date} utc_LocalDate -' @return {Date} ISO 8601 string -' @throws 10014 - ISO 8601 conversion error -'' -Public Function ConvertToIso(utc_LocalDate As Date) As String - On Error GoTo utc_ErrorHandling - - ConvertToIso = VBA.Format$(ConvertToUtc(utc_LocalDate), "yyyy-mm-ddTHH:mm:ss.000Z") - - Exit Function - -utc_ErrorHandling: - Err.Raise 10014, "UtcConverter.ConvertToIso", "ISO 8601 conversion error: " & Err.Number & " - " & Err.Description -End Function - -' ============================================= ' -' Private Functions -' ============================================= ' - -#If Mac Then - -Private Function utc_ConvertDate(utc_Value As Date, Optional utc_ConvertToUtc As Boolean = False) As Date - Dim utc_ShellCommand As String - Dim utc_Result As utc_ShellResult - Dim utc_Parts() As String - Dim utc_DateParts() As String - Dim utc_TimeParts() As String - - If utc_ConvertToUtc Then - utc_ShellCommand = "date -ur `date -jf '%Y-%m-%d %H:%M:%S' " & _ - "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & "' " & _ - " +'%s'` +'%Y-%m-%d %H:%M:%S'" - Else - utc_ShellCommand = "date -jf '%Y-%m-%d %H:%M:%S %z' " & _ - "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & " +0000' " & _ - "+'%Y-%m-%d %H:%M:%S'" - End If - - utc_Result = utc_ExecuteInShell(utc_ShellCommand) - - If utc_Result.utc_Output = "" Then - Err.Raise 10015, "UtcConverter.utc_ConvertDate", "'date' command failed" - Else - utc_Parts = Split(utc_Result.utc_Output, " ") - utc_DateParts = Split(utc_Parts(0), "-") - utc_TimeParts = Split(utc_Parts(1), ":") - - utc_ConvertDate = DateSerial(utc_DateParts(0), utc_DateParts(1), utc_DateParts(2)) + _ - TimeSerial(utc_TimeParts(0), utc_TimeParts(1), utc_TimeParts(2)) - End If -End Function - -Private Function utc_ExecuteInShell(utc_ShellCommand As String) As utc_ShellResult -#If VBA7 Then - Dim utc_File As LongPtr - Dim utc_Read As LongPtr -#Else - Dim utc_File As Long - Dim utc_Read As Long -#End If - - Dim utc_Chunk As String - - On Error GoTo utc_ErrorHandling - utc_File = utc_popen(utc_ShellCommand, "r") - - If utc_File = 0 Then: Exit Function - - Do While utc_feof(utc_File) = 0 - utc_Chunk = VBA.Space$(50) - utc_Read = CLng(utc_fread(utc_Chunk, 1, Len(utc_Chunk) - 1, utc_File)) - If utc_Read > 0 Then - utc_Chunk = VBA.Left$(utc_Chunk, CLng(utc_Read)) - utc_ExecuteInShell.utc_Output = utc_ExecuteInShell.utc_Output & utc_Chunk - End If - Loop - -utc_ErrorHandling: - utc_ExecuteInShell.utc_ExitCode = CLng(utc_pclose(utc_File)) -End Function - -#Else - -Private Function utc_DateToSystemTime(utc_Value As Date) As utc_SYSTEMTIME - utc_DateToSystemTime.utc_wYear = VBA.Year(utc_Value) - utc_DateToSystemTime.utc_wMonth = VBA.Month(utc_Value) - utc_DateToSystemTime.utc_wDay = VBA.Day(utc_Value) - utc_DateToSystemTime.utc_wHour = VBA.Hour(utc_Value) - utc_DateToSystemTime.utc_wMinute = VBA.Minute(utc_Value) - utc_DateToSystemTime.utc_wSecond = VBA.Second(utc_Value) - utc_DateToSystemTime.utc_wMilliseconds = 0 -End Function - -Private Function utc_SystemTimeToDate(utc_Value As utc_SYSTEMTIME) As Date - utc_SystemTimeToDate = DateSerial(utc_Value.utc_wYear, utc_Value.utc_wMonth, utc_Value.utc_wDay) + _ - TimeSerial(utc_Value.utc_wHour, utc_Value.utc_wMinute, utc_Value.utc_wSecond) -End Function - -#End If diff --git a/Version Control.accda.src/modules/modUtcConverter.bas b/Version Control.accda.src/modules/modUtcConverter.bas index fa996bb5..d980bfa5 100644 --- a/Version Control.accda.src/modules/modUtcConverter.bas +++ b/Version Control.accda.src/modules/modUtcConverter.bas @@ -297,14 +297,43 @@ utc_ErrorHandling: Err.Raise 10012, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description End Function + +Public Function TimeStampDate(Optional LocalTimeStamp As Boolean = False) As Date + + Dim TimeStampOut As Date + +#If Mac Then + ' I'm sure there's a way to do this better, but this works for now. + TimeStampOut = ConvertToUtc(VBA.Now()) + If Not LocalTimeStamp Then TimeStampOut = ConvertToUtc(TimeStampOut) + +#Else + Dim tSysTime As utc_SYSTEMTIME + + If Not LocalTimeStamp Then + GetSystemTime tSysTime + TimeStampOut = utc_SystemTimeToDate(tSysTime) + + Else + GetLocalTime tSysTime + TimeStampOut = utc_SystemTimeToDate(tSysTime) + End If +#End If + + TimeStampDate = TimeStampOut + +End Function + + ' NOTE: As of now, "LocalTimeStamp" does nothing on a Mac; need to build "getTimeZoneOffset" for Mac, and I don't have one. ' It will, however, output a UTC string that is correct for local time (eg, in the correct UTC for the given local time) ' I also don't know how to get millisecond values out of a Mac, so that'll return zero, as well. Public Function ISO8601TimeStamp(Optional IncludeMilliseconds As Boolean = True _ , Optional LocalTimeStamp As Boolean = False) As String - Dim CurrentTimeVB As Date + Dim CurrentTimeVB As Date Dim tString_Buffer As StringBufferCache + ' Note: This varies slightly from ConvertToISO8601Time because it's faster to do on Windows if you have SYSTEMTIME #If Mac Then ' I'm sure there's a way to do this better, but this works for now. From d5e76f722a2dd633a873e3360cf14b52039cee90 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 09:02:39 -0600 Subject: [PATCH 43/73] Add performance timing to ISO date parsing See #354 --- Version Control.accda.src/modules/modJsonConverter.bas | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modJsonConverter.bas b/Version Control.accda.src/modules/modJsonConverter.bas index d7c0ea7b..77d9f229 100644 --- a/Version Control.accda.src/modules/modJsonConverter.bas +++ b/Version Control.accda.src/modules/modJsonConverter.bas @@ -248,7 +248,9 @@ Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitesp Case VBA.vbDate ' Date If JsonOptions.ConvertDateToIso Then + Perf.OperationStart "Convert JSON Date to ISO" json_DateStr = ConvertToIsoTime(VBA.CDate(JsonValue)) + Perf.OperationEnd Else json_DateStr = VBA.CStr(JsonValue) End If @@ -621,8 +623,11 @@ Private Function json_ParseString(ByRef json_String As String _ Case json_Quote json_ParseString = json_BufferToString(json_Buffer, json_BufferPosition) If JsonOptions.ConvertDateToIso Then ' Only convert and test for condition if needed for speed boost. - If (json_ParseString Like "####-##-##T##:##:##*") Then _ + If (json_ParseString Like "####-##-##T##:##:##*") Then + Perf.OperationStart "Parse JSON ISO Date" json_ParseString = ParseIso(VBA.CStr$(json_ParseString)) ' Return as a date + Perf.OperationEnd + End If End If json_Index = json_Index + 1 Exit Function From 286679d1846f259d6db6b421e8485ec79f16657e Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 09:03:32 -0600 Subject: [PATCH 44/73] Add high-performance wrapper functions Avoids the use of RegEx when it is not necessary to parse a standard date format. #354 --- .../modules/modUtcConverter.bas | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modUtcConverter.bas b/Version Control.accda.src/modules/modUtcConverter.bas index d980bfa5..7d58a827 100644 --- a/Version Control.accda.src/modules/modUtcConverter.bas +++ b/Version Control.accda.src/modules/modUtcConverter.bas @@ -466,7 +466,7 @@ Public Function ParseIso(utc_IsoString As String _ Exit Function #Else If UBound(utc_Parts) > 0 Then - utc_DateTimeOut = ConvDateUTC(utc_Parts(0)) + ConvTimeUTC(utc_Parts(1)) + utc_DateTimeOut = ConvDateUTC2(utc_Parts(0)) + ConvTimeUTC2(utc_Parts(1)) If Not OutputUTCDate Then ParseIso = ConvertToLocalDate(utc_DateTimeOut) Else @@ -706,6 +706,29 @@ Private Function utc_SystemTimeToDate(ByRef utc_Value As utc_SYSTEMTIME) As Date End Function +'--------------------------------------------------------------------------------------- +' Procedure : ConvDateUTC2 +' Author : Adam Waller +' Date : 11/14/2023 +' Purpose : Attempt a higher performance conversion first, then fall back to RegEx. +'--------------------------------------------------------------------------------------- +' +Private Function ConvDateUTC2(ByVal InVal As String) As Date + + Dim varParts As Variant + + If InVal Like "####-##-##" Then + ' Use high-performance conversion to date + varParts = Split(InVal, "-") + ConvDateUTC2 = DateSerial(varParts(0), varParts(1), varParts(2)) + Else + ' Fall back to slower RegEx function + ConvDateUTC2 = ConvDateUTC(InVal) + End If + +End Function + + Private Function ConvDateUTC(ByVal InVal As String) As Date Dim RetVal As Variant @@ -753,6 +776,30 @@ Private Function ConvDateUTC(ByVal InVal As String) As Date ConvDateUTC = RetVal End Function + +'--------------------------------------------------------------------------------------- +' Procedure : ConvTimeUTC2 +' Author : Adam Waller +' Date : 11/14/2023 +' Purpose : Attempt a higher performance conversion first, then fall back to RegEx. +'--------------------------------------------------------------------------------------- +' +Private Function ConvTimeUTC2(ByVal InVal As String) As Date + + Dim varParts As Variant + + If InVal Like "##:##:##.###Z" Then + ' Use high-performance conversion to date + varParts = Split(InVal, ":") + ConvTimeUTC2 = TimeSerial(varParts(0), varParts(1), Left(varParts(2), 2)) + Else + ' Fall back to slower RegEx function + ConvTimeUTC2 = ConvDateUTC(InVal) + End If + +End Function + + Private Function ConvTimeUTC(ByRef InVal As String) As Date Dim dblHours As Double From aff7be99d8ced2622c2f09005d42b93abda6d5be Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 09:21:33 -0600 Subject: [PATCH 45/73] Fix copy-paste oversight #354 --- Version Control.accda.src/modules/modUtcConverter.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modUtcConverter.bas b/Version Control.accda.src/modules/modUtcConverter.bas index 7d58a827..d8d403b0 100644 --- a/Version Control.accda.src/modules/modUtcConverter.bas +++ b/Version Control.accda.src/modules/modUtcConverter.bas @@ -794,7 +794,7 @@ Private Function ConvTimeUTC2(ByVal InVal As String) As Date ConvTimeUTC2 = TimeSerial(varParts(0), varParts(1), Left(varParts(2), 2)) Else ' Fall back to slower RegEx function - ConvTimeUTC2 = ConvDateUTC(InVal) + ConvTimeUTC2 = ConvTimeUTC(InVal) End If End Function From e18d8188cddff509bb24e971224a84fcb1970d5c Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 15:41:10 -0600 Subject: [PATCH 46/73] Update error handling Refactored a number of locations to use the new syntax for On Error Resume Next, and added code to clear expected errors. --- Version Control.accda.src/forms/frmVCSMain.bas | 3 ++- .../modules/clsDbModule.cls | 6 ++++-- .../modules/clsDbRelation.cls | 4 +++- .../modules/clsDbSavedSpec.cls | 3 ++- .../modules/clsDbTableDef.cls | 6 ++++-- .../modules/clsDbVbeProject.cls | 3 ++- .../modules/clsVCSIndex.cls | 3 ++- .../modules/clsViewDiff.cls | 6 ++++-- .../modules/modComAddIn.bas | 4 +++- .../modules/modDatabase.bas | 17 ++++++++++++----- .../modules/modFileAccess.bas | 3 ++- .../modules/modFunctions.bas | 3 +++ Version Control.accda.src/modules/modHash.bas | 12 +++++++++--- .../modules/modImportExport.bas | 3 ++- .../modules/modInstall.bas | 14 ++++++++++---- .../modules/modOrphaned.bas | 3 +++ .../modules/modUIAutomation.bas | 3 ++- .../modules/modVCSUtility.bas | 4 +++- .../modules/modVbeForm.bas | 1 + Version Control.accda.src/vcs-options.json | 3 ++- 20 files changed, 75 insertions(+), 29 deletions(-) diff --git a/Version Control.accda.src/forms/frmVCSMain.bas b/Version Control.accda.src/forms/frmVCSMain.bas index de354f4c..bdfc5463 100644 --- a/Version Control.accda.src/forms/frmVCSMain.bas +++ b/Version Control.accda.src/forms/frmVCSMain.bas @@ -1965,7 +1965,8 @@ End Sub ' Private Sub cmdClose_Click() ' Ignore the error if the user resumes (cancels the close operation) - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next DoCmd.Close acForm, Me.Name Catch 2501 ' Close form was canceled. End Sub diff --git a/Version Control.accda.src/modules/clsDbModule.cls b/Version Control.accda.src/modules/clsDbModule.cls index e342a840..d1b98c36 100644 --- a/Version Control.accda.src/modules/clsDbModule.cls +++ b/Version Control.accda.src/modules/clsDbModule.cls @@ -95,7 +95,8 @@ Private Sub IDbComponent_Import(strFile As String) DoCmd.Save acModule, strName ' Set reference to object - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next Set m_Module = CurrentProject.AllModules(strName) If Catch(2467) Then Log.Error eelCritical, _ "Imported module not found after import: " & strName, ModuleName(Me) & ".Import" @@ -205,7 +206,8 @@ Private Sub LoadVbeModuleFromFile(strFile As String, strName As String) With proj.VBComponents ' Remove any existing component (In most cases the module will exist) - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next .Remove .Item(strName) If DebugMode(False) Then On Error GoTo 0 Else On Error Resume Next diff --git a/Version Control.accda.src/modules/clsDbRelation.cls b/Version Control.accda.src/modules/clsDbRelation.cls index 4d8cd14e..1ecbf3d6 100644 --- a/Version Control.accda.src/modules/clsDbRelation.cls +++ b/Version Control.accda.src/modules/clsDbRelation.cls @@ -95,7 +95,8 @@ Private Sub IDbComponent_Import(strFile As String) ' Relationships create indexes, so we need to make sure an index ' with this name doesn't already exist. (Also check to be sure that ' we don't already have a relationship with this name.) - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next With m_Dbs .TableDefs(rel.Table).Indexes.Delete rel.Name .TableDefs(rel.ForeignTable).Indexes.Delete rel.Name @@ -134,6 +135,7 @@ Private Sub IDbComponent_Merge(strFile As String) On Error Resume Next CurrentDb.Relations.Delete FSO.GetBaseName(strFile) VCSIndex.Remove Me, strFile + If Err Then Err.Clear End If End Sub diff --git a/Version Control.accda.src/modules/clsDbSavedSpec.cls b/Version Control.accda.src/modules/clsDbSavedSpec.cls index 713deb2f..745ad4ae 100644 --- a/Version Control.accda.src/modules/clsDbSavedSpec.cls +++ b/Version Control.accda.src/modules/clsDbSavedSpec.cls @@ -179,7 +179,8 @@ Private Function GetDictionary() As Dictionary Set dSpec = New Dictionary - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' For some reason it throws an error if there is no ' description in the specification. With dSpec diff --git a/Version Control.accda.src/modules/clsDbTableDef.cls b/Version Control.accda.src/modules/clsDbTableDef.cls index c119bee4..963558e6 100644 --- a/Version Control.accda.src/modules/clsDbTableDef.cls +++ b/Version Control.accda.src/modules/clsDbTableDef.cls @@ -518,7 +518,8 @@ Private Function IndexAvailable(tdf As TableDef) As Boolean Dim lngTest As Long - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next lngTest = tdf.Indexes.Count If Err Then Err.Clear @@ -547,7 +548,8 @@ Private Function ImportLinkedTable(strFile As String) As Boolean Dim strSql As String Dim strConnect As String - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' Read json file Set dTable = ReadJsonFile(strFile) diff --git a/Version Control.accda.src/modules/clsDbVbeProject.cls b/Version Control.accda.src/modules/clsDbVbeProject.cls index df196575..5c2730b4 100644 --- a/Version Control.accda.src/modules/clsDbVbeProject.cls +++ b/Version Control.accda.src/modules/clsDbVbeProject.cls @@ -108,7 +108,8 @@ Private Sub SafeSetProperty(cProj As VBProject, strProperty As String, varValue If varValue = varCurrent Then Exit Sub ' Switch to on error resume next after checking for current errors - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' Attempt to set the property CallByName cProj, strProperty, VbLet, varValue diff --git a/Version Control.accda.src/modules/clsVCSIndex.cls b/Version Control.accda.src/modules/clsVCSIndex.cls index 396e42e6..ccee4137 100644 --- a/Version Control.accda.src/modules/clsVCSIndex.cls +++ b/Version Control.accda.src/modules/clsVCSIndex.cls @@ -776,7 +776,8 @@ End Property Public Sub ClearTempExportFolder() If m_strTempExportFolderPath <> vbNullString Then If FSO.FolderExists(m_strTempExportFolderPath) Then - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' Use FSO to delete the folder and contents FSO.DeleteFolder m_strTempExportFolderPath, True CatchAny eelWarning, "Unable to delete temp folder: '" & m_strTempExportFolderPath & _ diff --git a/Version Control.accda.src/modules/clsViewDiff.cls b/Version Control.accda.src/modules/clsViewDiff.cls index 72579031..1d863e02 100644 --- a/Version Control.accda.src/modules/clsViewDiff.cls +++ b/Version Control.accda.src/modules/clsViewDiff.cls @@ -140,7 +140,8 @@ Public Sub ClearTempFiles() Dim varFile As Variant If Not m_colTempFiles Is Nothing Then - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next For Each varFile In m_colTempFiles If FSO.FileExists(varFile) Then DeleteFile CStr(varFile) Next varFile @@ -185,7 +186,8 @@ Private Sub RunCompare(strFile1 As String, strFile2 As String) End Select ' Run command to launch compare - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next If strCmd <> vbNullString Then With New WshShell .Run strCmd diff --git a/Version Control.accda.src/modules/modComAddIn.bas b/Version Control.accda.src/modules/modComAddIn.bas index f1ff74c5..0c801f7d 100644 --- a/Version Control.accda.src/modules/modComAddIn.bas +++ b/Version Control.accda.src/modules/modComAddIn.bas @@ -306,7 +306,8 @@ Private Function DllIsRegistered() As Boolean ' Check HKLM registry key With New IWshRuntimeLibrary.WshShell ' We should have a value here if the install ran in the past. - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' Look up the class ID from the COM registration strTest = .RegRead("HKCU\SOFTWARE\Classes\MSAccessVCSLib.AddInRibbon\CLSID\") If strTest <> vbNullString Then @@ -318,6 +319,7 @@ Private Function DllIsRegistered() As Boolean DllIsRegistered = FSO.FileExists(strTest) End If End If + If Err Then Err.Clear End With End Function diff --git a/Version Control.accda.src/modules/modDatabase.bas b/Version Control.accda.src/modules/modDatabase.bas index 35e10d5d..89ed41c7 100644 --- a/Version Control.accda.src/modules/modDatabase.bas +++ b/Version Control.accda.src/modules/modDatabase.bas @@ -392,9 +392,11 @@ Public Function ObjectExists(intType As AcObjectType, strName As String, Optiona Log.Error eelError, "Parent container not supported for this object type: " & intType, ModuleName & ".ObjectExists" Else ' Attempt to reference the object by name - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next Set objTest = objContainer(strName) ObjectExists = Not Catch(2467) + If Err Then Err.Clear End If End Function @@ -573,7 +575,8 @@ Public Function DatabaseFileOpen() As Boolean Else ' For ADP projects, CurrentProject may be an invalid object reference ' after the database file (adp) is closed. - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next strTest = CurrentProject.FullName CatchAny eelNoError, vbNullString DatabaseFileOpen = (strTest <> vbNullString) @@ -637,7 +640,8 @@ Public Function DeleteObjectIfExists(intType As AcObjectType, strName As String) End Select ' Trap errors when attempting to delete the object - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next If Not blnExistsInAddIn Then ' Nice! We can use a simple call to delete the object @@ -718,10 +722,12 @@ Public Function FormLoaded(frmMe As Form) As Boolean ' If no forms are open, we already have our answer. :-) If Forms.Count > 0 Then ' We will throw an error accessing the name property if the form is closed - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next strName = frmMe.Name ' Return true if we were able to read the name property FormLoaded = strName <> vbNullString + If Err Then Err.Clear End If End Function @@ -747,7 +753,8 @@ Public Function VerifyFocus(ctlWithFocus As Control) As Boolean Set frmParent = objParent ' Ignore any errors with Screen.* functions - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' Verify focus of parent form Set frmParent = Screen.ActiveForm diff --git a/Version Control.accda.src/modules/modFileAccess.bas b/Version Control.accda.src/modules/modFileAccess.bas index a6c68029..754edcd7 100644 --- a/Version Control.accda.src/modules/modFileAccess.bas +++ b/Version Control.accda.src/modules/modFileAccess.bas @@ -138,7 +138,8 @@ Public Sub WriteFile(strText As String, strPath As String, Optional strEncoding ' Write to disk VerifyPath strPath ' Watch out for possible write error - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next .SaveToFile strPath, adSaveCreateOverWrite If Catch(3004) Then ' File is locked. Try again after 1 second, just in case something diff --git a/Version Control.accda.src/modules/modFunctions.bas b/Version Control.accda.src/modules/modFunctions.bas index a19e3e4a..12bcaeb0 100644 --- a/Version Control.accda.src/modules/modFunctions.bas +++ b/Version Control.accda.src/modules/modFunctions.bas @@ -789,6 +789,9 @@ Public Function IsEmptyArray(varArray As Variant) As Boolean lngLowBound = clngTest lngLowBound = LBound(varArray) + ' Clear any error thrown while attempting to read LBound() + If Err Then Err.Clear + ' If the above assignment fails, we have an empty array IsEmptyArray = (lngLowBound = clngTest) diff --git a/Version Control.accda.src/modules/modHash.bas b/Version Control.accda.src/modules/modHash.bas index 6045bc0f..56d2071e 100644 --- a/Version Control.accda.src/modules/modHash.bas +++ b/Version Control.accda.src/modules/modHash.bas @@ -132,14 +132,16 @@ End Function '--------------------------------------------------------------------------------------- ' Private Function HashBytes(Data() As Byte, Optional HashingAlgorithm As String = DefaultHashAlgorithm) As Byte() - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next HashBytes = NGHash(VarPtr(Data(LBound(Data))), UBound(Data) - LBound(Data) + 1, HashingAlgorithm) If Catch(9) Then HashBytes = NGHash(VarPtr(Null), UBound(Data) - LBound(Data) + 1, HashingAlgorithm) CatchAny eelCritical, "Error hashing data!", ModuleName & ".HashBytes", True, True End Function Private Function HashString(str As String, Optional HashingAlgorithm As String = DefaultHashAlgorithm) As Byte() - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next HashString = NGHash(StrPtr(str), Len(str) * 2, HashingAlgorithm) If Catch(9) Then HashString = NGHash(StrPtr(vbNullString), Len(str) * 2, HashingAlgorithm) CatchAny eelCritical, "Error hashing string!", ModuleName & ".HashString", True, True @@ -274,7 +276,8 @@ Public Function GetCodeModuleHash(intType As eDatabaseComponentType, strName As Set proj = CurrentVBProject ' Attempt to locate the object in the VBComponents collection - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next Set cmpItem = proj.VBComponents(strPrefix & strName) Catch 9 ' Component not found. (Could be an object with no code module) CatchAny eelError, "Error accessing VBComponent for '" & strPrefix & strName & "'", ModuleName & ".GetCodeModuleHash" @@ -383,4 +386,7 @@ Public Function GetSimpleHash(strText As String) As String ' Return short hash GetSimpleHash = Left(strHash, 7) + ' Clear any errors + If Err Then Err.Clear + End Function diff --git a/Version Control.accda.src/modules/modImportExport.bas b/Version Control.accda.src/modules/modImportExport.bas index 4e9b0270..f3eb1a4f 100644 --- a/Version Control.accda.src/modules/modImportExport.bas +++ b/Version Control.accda.src/modules/modImportExport.bas @@ -1489,7 +1489,8 @@ Public Sub InitializeForms(cContainers As Dictionary) Dim varKey As Variant ' Trap any errors that may occur when opening forms - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' See if we imported any forms For Each cont In cContainers diff --git a/Version Control.accda.src/modules/modInstall.bas b/Version Control.accda.src/modules/modInstall.bas index 8945635f..31253764 100644 --- a/Version Control.accda.src/modules/modInstall.bas +++ b/Version Control.accda.src/modules/modInstall.bas @@ -439,7 +439,8 @@ Private Sub RemoveMenuItem(ByVal strName As String, Optional Hive As eHive = ehH strPath = GetAddinRegPath(Hive) & strName & "\" With New IWshRuntimeLibrary.WshShell ' Just in case someone changed some of the keys... - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next .RegDelete strPath & "Expression" .RegDelete strPath & "Library" .RegDelete strPath & "Version" @@ -552,7 +553,8 @@ Private Sub RunUpgrades() ' Check for installation in HKLM hive. strOldPath = GetAddinRegPath(ehHKLM) & "&Version Control\Library" Set objShell = New IWshRuntimeLibrary.WshShell - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next strTest = objShell.RegRead(strOldPath) If Err Then Err.Clear On Error GoTo 0 @@ -634,7 +636,8 @@ End Sub Public Function HasLegacyRC4Keys() Dim strValue As String With New IWshRuntimeLibrary.WshShell - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next strValue = .RegRead("HKCU\SOFTWARE\VB and VBA Program Settings\MSAccessVCS\Private Keys\") HasLegacyRC4Keys = Not Catch(-2147024894) CatchAny eelError, "Checking for legacy RC4 keys", ModuleName & ".HasLegacyRC4Keys" @@ -797,7 +800,8 @@ Public Sub RemoveTrustedLocation(Optional strName As String) strPath = GetTrustedLocationRegPath(strName) With New IWshRuntimeLibrary.WshShell - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next .RegDelete strPath & "Path" .RegDelete strPath & "Date" .RegDelete strPath & "Description" @@ -998,6 +1002,8 @@ Private Function CheckRegKey(strPath As String, ParamArray AllowedValues() As Va End If Next intCnt + If Err Then Err.Clear + End Function diff --git a/Version Control.accda.src/modules/modOrphaned.bas b/Version Control.accda.src/modules/modOrphaned.bas index cbad9677..5b04f387 100644 --- a/Version Control.accda.src/modules/modOrphaned.bas +++ b/Version Control.accda.src/modules/modOrphaned.bas @@ -279,4 +279,7 @@ Public Sub RemoveOrphanedDatabaseObjects(cCategory As IDbComponent) End If Next varKey + ' Handle any uncaught errors + CatchAny eelError, "Error removing orphaned objects.", ModuleName & ".RemoveOrphanedDatabaseObjects" + End Sub diff --git a/Version Control.accda.src/modules/modUIAutomation.bas b/Version Control.accda.src/modules/modUIAutomation.bas index 6c5990ef..7b07c492 100644 --- a/Version Control.accda.src/modules/modUIAutomation.bas +++ b/Version Control.accda.src/modules/modUIAutomation.bas @@ -73,7 +73,8 @@ Private Function GetUnderlyingDbObjectFromButton(oClient As CUIAutomation, oElem strImage = GetImageName(oClient, oElement) ' Just in case something doesn't work right... - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next ' There are multiple icons for some objects If LikeAny(strImage, "Table*", "*Tabelle") Then diff --git a/Version Control.accda.src/modules/modVCSUtility.bas b/Version Control.accda.src/modules/modVCSUtility.bas index 21bf7f7d..8a296512 100644 --- a/Version Control.accda.src/modules/modVCSUtility.bas +++ b/Version Control.accda.src/modules/modVCSUtility.bas @@ -923,8 +923,10 @@ End Function Public Sub LoadVCSAddIn() ' The following lines will load the add-in at the application level, ' but will not actually call the function. Ignore the error of function not found. - If DebugMode(True) Then On Error Resume Next Else On Error Resume Next + LogUnhandledErrors + On Error Resume Next Application.Run GetAddInFileName & "!DummyFunction" + If Err Then Err.Clear End Sub diff --git a/Version Control.accda.src/modules/modVbeForm.bas b/Version Control.accda.src/modules/modVbeForm.bas index 44e53636..fbba0dd9 100644 --- a/Version Control.accda.src/modules/modVbeForm.bas +++ b/Version Control.accda.src/modules/modVbeForm.bas @@ -144,6 +144,7 @@ Private Sub AddProperty(dic As Dictionary, o As Object, strName As Variant) ' Use CallByName on object to get value if the property exists On Error Resume Next dic.Add strName, CallByName(o, strName, VbGet) + If Err Then Err.Clear End Select End Sub diff --git a/Version Control.accda.src/vcs-options.json b/Version Control.accda.src/vcs-options.json index 7e3f5716..673e5d5c 100644 --- a/Version Control.accda.src/vcs-options.json +++ b/Version Control.accda.src/vcs-options.json @@ -1,6 +1,6 @@ { "Info": { - "AddinVersion": "4.0.22", + "AddinVersion": "4.0.26", "AccessVersion": "14.0 32-bit" }, "Options": { @@ -36,6 +36,7 @@ "FormatSQL": true, "ForceImportOriginalQuerySQL": false, "SaveTableSQL": true, + "SplitLayoutFromVBA": false, "StripPublishOption": true, "SanitizeColors": 3, "SanitizeLevel": 2, From b7985e9f61496aa199d5a8b9c6e74d0649ab0721 Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 15:42:19 -0600 Subject: [PATCH 47/73] Use faster date parsing for date only values --- Version Control.accda.src/modules/modUtcConverter.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Version Control.accda.src/modules/modUtcConverter.bas b/Version Control.accda.src/modules/modUtcConverter.bas index d8d403b0..a632f023 100644 --- a/Version Control.accda.src/modules/modUtcConverter.bas +++ b/Version Control.accda.src/modules/modUtcConverter.bas @@ -473,7 +473,7 @@ Public Function ParseIso(utc_IsoString As String _ ParseIso = utc_DateTimeOut End If Else ' Assume any "Date Only" Text doesn't have a timezone (they aren't converted the other way, either) - ParseIso = ConvDateUTC(utc_Parts(0)) + ParseIso = ConvDateUTC2(utc_Parts(0)) End If Exit Function #End If From fe35d3bd4b75300e72e1a6b23c83ddac6bf2888d Mon Sep 17 00:00:00 2001 From: joyfullservice Date: Tue, 14 Nov 2023 15:44:30 -0600 Subject: [PATCH 48/73] Add Split Files utility to ribbon (Advanced Tools) Also added an informational message box when the split is complete. --- Ribbon/Ribbon.xml | 3 ++- Version Control.accda.src/forms/frmVCSSplitFiles.bas | 9 +++++++++ .../modules/clsVersionControl.cls | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Ribbon/Ribbon.xml b/Ribbon/Ribbon.xml index 1738fb3b..607abca0 100644 --- a/Ribbon/Ribbon.xml +++ b/Ribbon/Ribbon.xml @@ -14,7 +14,7 @@