This comparison technique seems to give the desired effect. It's noteworthy that AHK v2 appears to have a
StrCompare function that allows case-sensitivity. Might be a good reason to make a v2 version of this in the future...
Code: Select all
#SingleInstance, Force
;===================
HtSt1 := "?C*"
var1 := "_abc_"
var2 := "_aBc_"
If (var1 !== var2) and (var1 = var2) and (InStr(HtSt1,"C"))
MsgBox Only difference is case.
else if (var1 = var2)
MsgBox Duplicates.
else
MsgBox Not duplicates.
ExitApp
Esc:: ExitApp
Okay here is a preliminary version.
Notes: This doesn't replace the one in the top post. It's more like a 'fork.' It no longer checks for beginning-of-word items like
:*:trig::expansion, but it does skip items if the only difference is case AND if
c or
C is in the hotstring options. This version also adds a third loop. The loop that used to be the inner loop, is now the middle loop. The third loop allows the script to handle triplicates/quads/etc better. I did timing checks with AutoCorrect.ahk again. The "case sensitivity" part adds about 0.2 minutes.
Also... Previous versions were skipping hotstrings that were indented. This one does not.
EDIT on 4-14-2023. Rearranged the logic so that, only if matching triggers are found, is case-sensitive matching done.
EDIT on 4-15-2023. This version observes block comments
/* ... */. Also the elapsed time is now presented as M:SS.
Second EDIT on 4-15-2023. It occurred to me that the regex was capturing four subpatterns, but only using the first two. The others will get used in another fork, but they are not needed here. Without capturing those, the AutoCorrect.ahk file is processed in under a minute...
Edit again one minute later... I just realized that the full string is no longer getting reported in the summary at the end
. I did sorta' like having it that way... I also like the script to be fast though. I dunno' maybe I'll leave it like this.
Code: Select all
#SingleInstance, Force
; A script to find duplicate hotstrings. Ver 4-15-2023
; for AHK v1
StartTime := A_TickCount
varMain =
(
::ddd::bluab
::ccc::yatta
:c:_a_::little a
:c:_A_::big A
msgbox no hotstring here
!#^+x::also not a hotstring
::aaa::blah
:c:B::big bee
:c:b::little bee1
/*
:c:b::little bee2
::aaa::blah
::ddd::doesn't get culled.
::fff::
::ccc::yaddaya
::aaa::blah
*/
::ddd::bluab
::aaa::blah
)
;~ MainList := "Autocorrect.ahk" ; Must be in same folder as this script.
;~ FileRead, varMain, %MainList% ; load entire file contents into variable.
;~ regex := "(:(?:\*|\?|\w)*:)(..*)::(.*?)(?=\s;|$)\s*(;.*)?" ; swagfag-based
regex := "(:(?:\*|\?|\w)*:)(..*)::.*?"
;~ Outputs -- 1 = :options: , 2 = trigger
duplicates := "" ; Checking for trigger string, not expansion text.
dupeCount := 0
isBlockCom := 0
; Then trim any left-spaces, cull commented lines.
Loop, parse, varMain, `n, `r ; Check contents line-by-line.
{ ; 000000 PreLoop 000000
pLoopFld := LTrim(A_LoopField, OmitChars := "`t ")
if (pLoopFld != A_LoopField)
varMain := StrReplace(varMain, A_LoopField, pLoopFld,, 1) ; Remove whitespace from list
; Cull commented-out hotstrings. Necessary for below StrReplaces to replace correct instance of hS.
If (SubStr(pLoopFld, "1", "2")=="/*")
isBlockCom := 1
If (SubStr(pLoopFld, "1", "2")=="*/")
isBlockCom := 0
If (SubStr(pLoopFld, "1", "1")!=":") or (isBlockCom = 1)
varMain := StrReplace(varMain, pLoopFld, "comment",, 1)
else
continue
} ; 00000000000
;MsgBox, varMain is: |%varMain%|
Loop, parse, varMain, `n, `r ; Check contents line-by-line.
{ ; 1111111111 OUTER LOOP 1111111111
oLoopFld := A_LoopField
If (SubStr(oLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
{
varMain := StrReplace(varMain, oLoopFld, "o1---XYZ",, 1)
continue
}
Else
{
RegExMatch(oLoopFld, regex, oHtSt)
oLineNum := A_Index ; Save index of outer loop before A_Index gets reused below.
varMain := StrReplace(varMain, oLoopFld, "o2---XYZ",, 1) ; Remove oHtSt from list so we don't compare it with itself.
Loop, parse, varMain, `n, `r ; Check contents line-by-line.
{ ; 2222222222 MIDDLE LOOP 2222222222
mLoopFld := A_LoopField ; This assignement is unneeded, but I'm keeping it for consistency.
If (SubStr(mLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
continue
else
{
RegExMatch(mLoopFld, regex, mHtSt)
if (mHtSt2 = oHtSt2) ; If they match ...
If (mHtSt2 !== oHtSt2) and (mHtSt2 = oHtSt2) and (InStr(mHtSt1,"C")) ; Check for cAsE.
continue
Else
{
duplicates := duplicates "------`nlines " oLineNum "`t" oHtSt1 "`t" oHtSt2 "::" oHtSt3 oHtSt4 "`n
and "A_Index "`t" mHtSt1 "`t" mHtSt2 "::" mHtSt3 mHtSt4 "`n"
dupeCount++
varMain := StrReplace(varMain, mLoopFld, "m---XYZ",,1) ; Remove mHtSt from list
Loop, parse, varMain, `n, `r ; Check contents line-by-line.
{ ; 3333333333 INNER LOOP 3333333333
;MsgBox, At top of INNER loop:`nvarMain is:`n %varMain%`n`noHtSt2 is: |%oHtSt2%| `nmHtSt2 is: |%mHtSt2%|`nA_LoopField is: |%A_LoopField%|`n`nduplicates is:`n%duplicates%
iLoopFld := A_LoopField
If (SubStr(iLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
continue
else
RegExMatch(iLoopFld, regex, iHtSt)
if (iHtSt2 = mHtSt2)
{
duplicates := duplicates " and " A_Index "`t" iHtSt1 "`t" iHtSt2 "::" iHtSt3 iHtSt4 "`n"
varMain := StrReplace(varMain, iLoopFld, "i---XYZ",,1) ; Remove iHtSt from list
}
} ; 33333333333333333333
oHtSt2 := "oX"
}
}
} ; 22222222222222222222
}
} ; 11111111111111111111
ElapsedTime := (A_TickCount - StartTime) / 1000
min := StrReplace((round(ElapsedTime // 60) . " min, "), "0 min, ","")
sec := round(Mod(ElapsedTime, 60), 3) . " seconds"
dupeSum := % dupeCount " Duplicates:`n" duplicates
MsgBox, Search of %MainList% took %min%%sec%.`n---------------------------`n%dupeSum%
ExitApp
Esc::ExitApp
Another EDIT on 4-15-2023.
I'll post this version separately. It's not really a "fork" of the script that is directly above this one, but the above one is technically faster, so folks might prefer it. This version has the advantage of showing the entire hotstring in the summary report. This could make it easier for you to decide which one of the duplicates you want to remove or change. The disadvantage is that it's a tiny bit slower (but still is <1min for AutoCorrrect.ahk). The summary report is not as sleek now either. Previously we were displaying
line number -> options -> trigger -> expansion. Now we are displaying
line number -> options -> trigger -> the entire line I did want to keep the options and the trigger strings each in their own columns to make them easier to differentiate. With the below version of the script, the summary report looks like this:
It's worth noting that the columns sometimes get messed up in the popup messagebox. They are fine when pasted into a text editor though (may depend on your Windows tab settings). (Note also that I changed the names of a couple vars.)
Code: Select all
#SingleInstance, Force
; A script to find duplicate hotstrings. Ver 4-15-2023
; for AHK v1
StartTime := A_TickCount
HotStrList =
(
::ddd::bluab
::ccc::yatta
:c:_a_::little a
:c:_A_::big A
msgbox no hotstring here
!#^+x::also not a hotstring
::aaa::blah
:c:B::big bee
:c:b::little bee1
:c:b::little bee2
::aaa::blah
:?:ddd::doesn't get culled.
::fff::
:*:ccc::yaddaya
::aaa::blah
::ddd::bluab
::aaa::blah
)
;~ TargetScript := "Autocorrect.ahk" ; Must be in same folder as this script.
;~ FileRead, HotStrList, %TargetScript% ; load entire file contents into variable.
regex := "(:(?:\*|\?|\w)*:)(..*)::.*?"
;~ Outputs -- 1 = :options: , 2 = trigger
duplicates := "" ; Checking for trigger string, not expansion text.
dupeCount := 0
isBlockCom := 0
; Then trim any left-spaces, cull commented lines.
Loop, parse, HotStrList, `n, `r ; Check contents line-by-line.
{ ; 000000 PreLoop 000000
pLoopFld := LTrim(A_LoopField, OmitChars := "`t ")
if (pLoopFld != A_LoopField)
HotStrList := StrReplace(HotStrList, A_LoopField, pLoopFld,, 1) ; Remove whitespace from list
; Cull commented-out hotstrings. Necessary for below StrReplaces to replace correct instance of hS.
If (SubStr(pLoopFld, "1", "2")=="/*")
isBlockCom := 1
If (SubStr(pLoopFld, "1", "2")=="*/")
isBlockCom := 0
If (SubStr(pLoopFld, "1", "1")!=":") or (isBlockCom = 1)
HotStrList := StrReplace(HotStrList, pLoopFld, "comment",, 1)
else
continue
} ; 00000000000
;MsgBox, HotStrList is: |%HotStrList%|
Loop, parse, HotStrList, `n, `r ; Check contents line-by-line.
{ ; 1111111111 OUTER LOOP 1111111111
oLoopFld := A_LoopField
If (SubStr(oLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
{
HotStrList := StrReplace(HotStrList, oLoopFld, "o1---XYZ",, 1)
continue
}
Else
{
RegExMatch(oLoopFld, regex, oHtSt)
oLineNum := A_Index ; Save index of outer loop before A_Index gets reused below.
HotStrList := StrReplace(HotStrList, oLoopFld, "o2---XYZ",, 1) ; Remove oHtSt from list so we don't compare it with itself.
Loop, parse, HotStrList, `n, `r ; Check contents line-by-line.
{ ; 2222222222 MIDDLE LOOP 2222222222
mLoopFld := A_LoopField ; This assignement is unneeded, but I'm keeping it for consistency.
If (SubStr(mLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
continue
else
{
RegExMatch(mLoopFld, regex, mHtSt)
if (mHtSt2 = oHtSt2) ; If they match ...
If (mHtSt2 !== oHtSt2) and (mHtSt2 = oHtSt2) and (InStr(mHtSt1,"C")) ; Check for cAsE.
continue
Else
{
duplicates .= "------------------------------------`nlines " oLineNum "`t" StrReplace(oHtSt1,":","") "`t" oHtSt2 "`t" oLoopFld "`n
and "A_Index "`t" StrReplace(mHtSt1,":","") "`t" mHtSt2 "`t" mLoopFld "`n"
dupeCount++
HotStrList := StrReplace(HotStrList, mLoopFld, "m---XYZ",,1) ; Remove mHtSt from list
Loop, parse, HotStrList, `n, `r ; Check contents line-by-line.
{ ; 3333333333 INNER LOOP 3333333333
;MsgBox, At top of INNER loop:`nHotStrList is:`n %HotStrList%`n`noHtSt2 is: |%oHtSt2%| `nmHtSt2 is: |%mHtSt2%|`nA_LoopField is: |%A_LoopField%|`n`nduplicates is:`n%duplicates%
iLoopFld := A_LoopField
If (SubStr(iLoopFld, "1", "1")!=":") ; If line does not start with colon, then skip rest of loop.
continue
else
RegExMatch(iLoopFld, regex, iHtSt)
if (iHtSt2 = mHtSt2)
{
duplicates .= " and " A_Index "`t" StrReplace(iHtSt1,":","") "`t" iHtSt2 "`t" iLoopFld "`n"
HotStrList := StrReplace(HotStrList, iLoopFld, "i---XYZ",,1) ; Remove iHtSt from list
}
} ; 33333333333333333333
oHtSt2 := "oX"
}
}
} ; 22222222222222222222
}
} ; 11111111111111111111
ElapsedTime := (A_TickCount - StartTime) / 1000
min := StrReplace((round(ElapsedTime // 60) . " min, "), "0 min, ","")
sec := round(Mod(ElapsedTime, 60), 3) . " seconds"
dupeSum := % dupeCount " Duplicates:`n" duplicates
MsgBox, Search of %MainList% took %min%%sec%.`n---------------------------`n%dupeSum%
ExitApp
Esc::ExitApp