I may be misunderstanding, but I don't agree with this suggestion. I don't intend to be harsh here, just thorough, so if I come off as aggressive at all I do apologize.
Python's
if __name__ == '__main__': construct, in my opinion at least, has more to do with making runnable files also includable rather than the other way around of making includable library files also runnable. So I don't necessarily agree that this is a Python-esque standard, but that's neither here nor there. I have more basic problems with the premise.
Small examples to demonstrate particular features of a library should not be divorced from the methods implementing that behavior. The further away the examples are from the method, the less likely someone who needs to see the example will see it at the time they would most benefit from seeing it. Additionally, the further away they are the more likely they are to be ignored when making changes to the method that would break compatibility. Though this is less of a concern if those examples are unit tested somehow.
Larger examples I believe belong in their own separate files rather than mixing concerns within the same file. Practices pioneered in other more established languages dictate more or less that a project should contain one class definition per library file, with that file sharing the name of the class. Not all AHK files should be class-based, but for the ones that are I think it's important to do that. Breaking the convention can make it more difficult to find what you are looking for in large unfamiliar projects, as well as cause confusion about include paths for complicated dependency chains. Granted, we do not have to deal with things like PHP autoloaders which actually require that this convention be followed.
I believe projects really should not be monolithic with separate concerns all together in one file. Whereas in web design we break up document structure, document styling, and dynamic behavior, in AHK we might break up implementation, examples, and unit tests.
Here in Neutron for example, the Load method contains a brief description, long description, minimal example, and other metadata following the documentation format to allow Intellisense under thqby's VSCode AHKv2 extension.
Code: Select all
class NeutronWindow {
/**
* Loads an HTML file by name (not path).
*
* When running the script uncompiled, looks for the file in the local
* directory. When running the script compiled, looks for the file in the
* EXE's RCDATA. Files included in your compiled EXE by FileInstall are
* stored in RCDATA whether they get extracted or not. An easy way to get
* your Neutron resources into a compiled script, then, is to put
* FileInstall commands for them at the start or end of the AutoExecute
* section, wrapped in `if False {}`. For example:
*
* ```ahk2
* ; AutoExecute Section
* neutron := NeutronWindow().Load("index.html").Show()
*
* if False {
* FileInstall "index.html", "*"
* FileInstall "index.css", "*"
* }
* return
* ```
*
* @param fileName The name of the HTML file to load into the Neutron window.
* Make sure to give just the file name, not the full path.
*
* @return {NeutronWindow} The instance, for chaining
*/
Load(fileName) {
With this documentation, hovering the method wherever it is called produces a tooltip containing the example with appropriate syntax highlighting, allowing someone who is using the library to benefit from the minimal example without having to search for it specifically.
Then in addition to this small example directly inside the in-code documentation block comment, several distinct examples are available in the examples folder for anyone who needs a full example. That there is more than one example is notable I think, as there is not an obvious solution to including
multiple full examples under the
if A_LineFile = A_ScriptFullPath. I suppose it would require you to build in some kind of example-launching UI, which is extra boilerplate code not really related to the underlying library.
I see some potential issues with your positive example, though I think they can be addressed.
- Under Python it is standard to name the entrypoint function used by this construct
main, but AutoHotkey's lack of namespacing means you cannot have multiple
main functions in separate files so it is not a suitable name to use in a library. You also cannot create a function sharing the name of the class, so assuming you name both the entrypoint and the class the same as the file name, the entrypoint will require a suffix which
should be standard if your goal is to provide uniformity across libraries. Your choice of
pantryExample doesn't fit, because it diverges from the name
PantryAPI, so anyone including the file who would want to invoke the entrypoint for whatever reason has to guess or look it up, and anyone reading that code would have to guess which include file it came from. If I could suggest instead,
PantryAPI_Main might be a more suitable name/naming scheme for this entrypoint function.
- Your example is not appropriate for code that may ever be compiled. Example snippets that are in some kind of documenting comment gets stripped out of the resulting exe by ahk2exe, examples that live in a separate file never get loaded into the runtime to begin with, but example code behind a guard like the one you're using makes its way all the way to the resulting exe file and (as written) actually becomes active code because
A_LineFile and
A_ScriptFullPath will now both be set to the same exe path.
This can be addressed through at least two options. First is to adjust your guard to check for the compiled status like
if A_LineFile = A_ScriptFullPath && !A_IsCompiled. This will prevent the code from activating when the script is compiled, but the code for the example still lives rent-free as dead code in the resulting exe. So for best results, a second option is to use Ahk2Exe compiler directives so that the compiler will know to remove that code from the exe. With that in place, checking for
!A_IsCompiled is not necessary because the check itself is no longer there when that condition is true.
Code: Select all
;@Ahk2Exe-IgnoreBegin
if A_LineFile = A_ScriptFullPath
pantryExample()
pantryExample()
{
...
}
;@Ahk2Exe-IgnoreEnd
class PantryAPI {
...
I do think there could be a place for
if A_LineFile = A_ScriptFullPath guarded sections in library files, but maybe something more akin to how in v1 some people would write defensive classes that included __Get/__Set meta-functions to throw exceptions whenever a class was accessed in an unsupported way (like
MsgBox % class.tyop would throw because
tyop did not exist and they instead meant
typo). Here, rather than throwing if the class was accessed inappropriately, it would throw if the file was executed inappropriately. Because of how v2 processes static classes, you could even stick it into the class definition to keep top-level code out of the library file:
Code: Select all
class PantryAPI {
static __New() {
if A_LineFile = A_ScriptFullPath && !A_IsCompiled
throw Error("This file should not be executed directly")
}
}
Because the check code is just two lines I don't think having it stay in the compiled script as dead code is a huge deal either way, but if that is ever a concern the ahk2exe compiler directive could be applied here as well.
Wishing you the best,
geek