Skip to content

Instantly share code, notes, and snippets.

@davebrny
Last active February 8, 2020 19:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davebrny/c914a936c1977a525a58c88d7d03792a to your computer and use it in GitHub Desktop.
Save davebrny/c914a936c1977a525a58c88d7d03792a to your computer and use it in GitHub Desktop.
💈 (autohotkey) - combine auto-execute sections from #included scripts into your main script
/*
[settings]
save_folder =
[script folders]
folder_1 =
folder_2 =
[ignore]
C:\Users\documents\example script name.ahk
C:\Users\documents\example folder name
[stats]
last_refresh_time = 20171120064635
a_tick_count =
[script info]
version = 0.3.6
description = combine auto-execute/exit sections from #included scripts into your main script
author = davebrny
source = https://gist.github.com/davebrny/c914a936c1977a525a58c88d7d03792a
*/
ex_this(byRef script_list="", refresh_all="") {
start_time := a_tickCount
revert_batch := a_batchLines
setBatchLines, -1
iniRead, save_folder, % a_lineFile, settings, save_folder
iniRead, last_refresh_time, % a_lineFile, stats, last_refresh_time
iniRead, ignore_pattern, % a_lineFile, ignore
save_folder := (fileExist(save_folder) ? save_folder : a_temp "\ex_this")
fileRead, last_script_list, % save_folder "\script list.txt"
;---------------------------------------------
loop,
{
iniRead, script_folder, % a_lineFile, script folders, folder_%a_index%
if (script_folder = "")
continue ; > > > > > > > > > > > > > > > > >
loop, files, % script_folder "\*.ahk", FR
{
if inStr(ignore_pattern . "`n", a_loopFileDir . "`n")
ignore_folder := a_loopFileDir
if a_loopFileDir contains %ignore_folder%
continue ; > > > > > > > > > > > > > > >
if inStr(ignore_pattern, a_loopFileFullPath)
continue ; > > > > > > > > > > > > > > >
script_list .= (script_list? "`n" : "") . a_loopFileFullPath
if (refresh_all)
or (a_loopFileTimeModified > last_refresh_time) ; if file has changed since
or (a_loopFileTimeCreated > last_refresh_time) ; if newly created (or copied into the folder)
or !inStr(last_script_list, a_loopFileFullPath) ; catch the files that have been 'moved' or 'cut'
{
fileRead, contents, % a_loopFileFullPath
if inStr(contents, "/*")
contents := ex_strip(contents) ; strip comment blocks
loop, 2
{
type := (a_index = 1) ? "execute" : "exit" ; check for each type
if !inStr(contents, ";" . type . "_this")
not_using_ex .= type . a_loopFileFullPath "`n"
else ; if file is using execute_this or exit_this
{
new_text := ex_section(type, contents) ; get execute/exit text
stringGetPos, pos, script_folder, \, R1 ; get parent folder name
stringMid, parent_folder, script_folder, pos + 2
end_path := strReplace(a_loopFileFullPath, script_folder "\") ; remove dir
ex_path := save_folder "\" type "\" parent_folder "\" end_path ; append to ex_this dir
fileRead, current_text, % ex_path
top_text := " `; " . a_loopFileFullPath . "`n"
. " `; ! auto-generated file. any changes made here will be overwritten`n`n"
if (current_text != top_text . new_text)
{
if fileExist(ex_path)
fileDelete, % ex_path
else fileCreateDir, % subStr(ex_path, 1, strLen(ex_path) - strLen(a_loopFileName))
fileAppend, % top_text . new_text, % ex_path
}
}
}
}
}
}
until (script_folder = "ERROR") ; loop end
;-----------------------------------------
loop, files, % save_folder . "\*.ahk", FR
{
if a_loopFileFullPath contains ~execute_this,~exit_this
continue ; > > > > > > > > > > > >
type := inStr(a_loopFileDir, "\execute\") ? "execute" : "exit"
source_path := ex_source(a_loopFileFullPath)
; if file no longer exists, or if it no longer contains execute/exit_this
if !inStr(script_list, source_path) or inStr(not_using_ex, type . source_path)
fileDelete, % a_loopFileFullPath
else %type%_this .= "#include, " a_loopFileFullPath "`n" ; create include list
}
ex_update_list(execute_this, save_folder "\~execute_this.ahk")
ex_update_list(exit_this, save_folder "\~exit_this.ahk")
ex_update_list(script_list, save_folder "\script list.txt")
iniWrite, % " " a_now, % a_lineFile, stats, last_refresh_time
iniWrite, % " " a_tickCount - start_time, % a_lineFile, stats, a_tick_count
setBatchLines, % revert_batch
}
ex_strip(string) { ;# strip out comment blocks
original_string := string
if inStr(string, "`r`n")
stringReplace, string, string, `r`n, `n, all
if (subStr(string, 1, 2) = "/*") ; if at first line
stringReplace, string, string, % "/*", % "`n/*"
strReplace(string, "`n/*", "", block_start) ; get number of blocks
strReplace(string, "`n*/", "", block_end)
block_count := (block_start > block_end) ? (block_end) : (block_start)
loop, % block_count
{
if (a_index = 1) ; get text before 1st block
{
stringGetPos, block_start, string, % "`n/*", L1
stringMid, first_section, string, block_start, , L
new_string .= first_section "`n"
}
else ; every other loop (text between blocks)
{
stringGetPos, block_start, string, % "`n/*", L%a_index%
stringMid, middle_sections, string, block_start, , L
stringGetPos, block_end, middle_sections, % "`n*/", R1,
stringMid, middle_sections, middle_sections, block_end + 5
new_string .= middle_sections "`n"
}
} ; text after last block
stringGetPos, block_end, string, % "`n*/", R1
stringMid, end_section, string, block_end + 5
new_string .= end_section
if inStr(original_string, "`r`n")
stringReplace, new_string, new_string, `n, `r`n, all
return new_string
}
ex_section(type, contents) { ;# get execute / exit text
strReplace(contents, ";" type "_this", "", this_count)
loop, % this_count
{
stringGetPos, pos, contents, % ";" type "_this", % "L" a_index
stringMid, text_after, contents, pos + 1
stringGetPos, pos, text_after, `n, L1 ; the first line after execute_this
stringMid, section_start, text_after, pos + 2
stringGetPos, pos, section_start, % ";" type "_end", L1
stringMid, section_block, section_start, % pos, , L
section_text .= (section_text ? "`r`n" : "") . section_block
}
if inStr(section_text, "`r`n`r`n")
{
loop, ; remove empty lines
stringReplace, section_text, section_text, `r`n`r`n, `r`n, all
until !InStr(section_text, "`r`n`r`n")
}
loop, parse, section_text, `n, `r
{
if a_loopField is space
continue ; skip lines that contains only spaces or tabs
if (inStr(ltrim(a_loopField), ";") = 1)
continue ; skip commented lines
new_text .= (new_text ? "`r`n" : "") . a_loopField
}
return new_text
}
ex_source(ex_path) { ;# get the source/original path from line 1 of the file
file := fileOpen(ex_path, "r")
source_path := trim(file.readLine(), "`r`n `;")
file.close()
return source_path
}
ex_update_list(new_text, list_file) { ;# update the list (if there are changes)
file := fileOpen(list_file, "r `n")
current_text := file.read()
top_text := " `; ! auto-generated file. any changes made here will be overwritten`n`n"
new_text := top_text . new_text
if (current_text != new_text)
{
file := fileOpen(list_file, "w `n")
file.write(new_text)
}
file.close()
}
::;exe:: ; hotstrings
::;exi::
::;exes:: ; (append ex_this source url)
::;exis::
ex_type := inStr(a_thisLabel, "exe") ? "execute" : "exit"
ex_link := inStr(a_thisLabel, "s") ? " `; source: git.io/vFQ1z" : ""
string := ";{1}_this{2}`n;{1}_end"
string := strReplace(string, "{1}", ex_type)
string := strReplace(string, "{2}", ex_link)
send % string
return
@davebrny
Copy link
Author

davebrny commented Nov 20, 2017

ex_this()

;execute_this
;execute_end
;exit_this
;exit_end

if all of your autohotkey scripts are split up into individual files and included into one main script then there can be a bit of going back and forth between the current file and the auto-execute section any time you need to change something there.  ex_this() solves this problem (in most cases) by letting you add to the auto-execute section (or exit subroutine) from anywhere in your script

it also solves the problem of getting "label not found" errors when you remove a file but forgot to remove the goSub or timer that was in the auto-execute section

another benefit of using this method is that sharing code with others becomes easier since everything is self contained in one file.
if the person youre sharing a script with is also using ex_this() then all they have to do is include it into their own script and reload to get things up and running.   if theyre not, then the execute/exit tags can act sort of like instructions about which parts need to be copied to auto-execute section of their script

lastly, since the execute/exit tags are just comments, they dont have any effect if someone decides to run the script it on its own

drawbacks

ex_this() copies the code between the execute/exit tags and then stores it in \AppData\Local\Temp\ex_this\
this means means that msgBox errors will show that location and not the source file, as will certain variables such as a_lineFile.

in certain situations you might be able to work around these problems by using the "goto error" timer below or by using the following function to convert a_lineFile to the actual source file

source_file := ex_source(a_lineFile)

i havnt tested this it out in all the different situations it could be used in yet so there are probably more drawbacks than the ones listed above

 
 

usage

  • #include ex_this.ahk into your main script
  • add your script folder(s) to the ini section at the top of the file
    • to add more folders, increment the number for each folder: folder_3, folder_4 etc
  • put ex_this() in the auto-execute section (or have it run from a hotkey)
  • after ex_this() is run once include the following into your main script:
; include near the top of the auto-execute section  
onExit, exit_label
#include, C:\Users\YOUR_USERNAME\AppData\Local\Temp\ex_this\~execute_this.ahk
; include anywhere else in your script
exit_label:
#include, C:\Users\YOUR_USERNAME\AppData\Local\Temp\ex_this\~exit_this.ahk
exitApp

if save_folder is left empty then the files will be created in \AppData\Local\Temp\ex_this

 

auto-execute example:

#noEnv     ; things that are set in the main script
#singleInstance, force
setWorkingDir, %a_scriptDir%

;execute_this
var = 1     ; things that are specific to the included script
setTimer, some_label, 1000
;execute_end

return ; end of auto-execute section

if the above was in a standalone script then everything would be executed.
if this was in an included script then only the commands between the tags would run, while the same script settings would be set in the auto-execute section of your main script

see restart clipboard.ahk for a working example

 

when/why files are scanned

  • the first time ex_this() is run, every file is read to see if it contains an execute_this or exit_this tag
  • after that, a file is only read again if it has been modified after the time ex_this() was last run
  • when theres a change anywhere in the file, the code between the tags is checked against the saved version and is only updated if there is a change
  • commented lines are ignored so any changes to these comments wont be updated to the saved version of the code

 

disable temporarily

put a space somewhere in the middle of the first tag and then reload your script

;exec ute_this

or put a comment block around the tags

/*
;execute_this
this = 1
that = 2
;execute_end
*/

or comment out every line

;execute_this
;this = 1
;that = 2
;execute_end

 

ignoring files and folders

if you have a lot of scripts that will be scanned every time you use ex_this() then you might be able to cut down the scan time a bit by ignoring certain folders that you know you will never be using the execute/exit tags in:

[ignore]
C:\Users\scripts\functions

check a_tick_count in the [stats] section to see how long the last scan took

 

hotstrings

use ;exe to add the execute_this tags and ;exi to add the exit_this tags

;exe
;exi

add an s to the end of these hotstrings to send the tag along with the source url for ex_this()

;exes
;exis

 

goto error

add the following code to your script to have it open the source file any time it detects a msgBox with an error in the \ex_this\ folder

;execute_this
setTimer, ex_goto_error, 1000
;execute_end


ex_goto_error:
if winExist("ahk_exe AutoHotkey.exe", "in #include file")
    and winExist("ahk_exe AutoHotkey.exe", "\ex_this\")
    {
    setTimer, ex_goto_error, off

    winGetText, error_text, ahk_class #32770
    stringGetPos, pos, error_text, % "in #include file"
    stringMid, text_after, error_text, pos + 19
    stringGetPos, pos, text_after, % """", L1
    stringMid, error_file, text_after, pos, , L
    if inStr(error_file, "\ex_this\")
        error_file := ex_source(error_file)
    ; -------------------------------------
    editor_path := ""
    ; -------------------------------------
    editor_path := (editor_path = "") ? "edit" : """" editor_path """"
    if fileExist(error_file)
        run, %editor_path% "%error_file%"

    winWaitClose, ahk_class #32770, in #include file
    setTimer, ex_goto_error, on
    }
return

if editor_path is empty then the file will be opened in the default program for editing text files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment